36#include <mlir/Interfaces/SideEffectInterfaces.h>
38#include <llvm/Support/Debug.h>
45#define GEN_PASS_DEF_WHILETOFORPASS
49#define DEBUG_TYPE "while-to-for"
60 std::optional<Value> lb, ub, step;
63 std::optional<size_t> ivarIndexBefore;
65 std::optional<size_t> ivarIndexAfter;
67 bool success()
const {
68 return lb.has_value() && ub.has_value() && step.has_value() && ivarIndexBefore.has_value() &&
69 ivarIndexAfter.has_value();
73static inline ForOpInfo parseInfo(WhileOp op) {
74 auto reportFailureReason = [&op](Twine reason) {
75 return op.emitWarning(
"'llzk-while-to-for' failed to transform loop: ") << reason;
81 op.getBeforeArguments().begin(), op.getBeforeArguments().end(),
82 op.getConditionOp().getArgs().begin()
84 reportFailureReason(
"block arguments not passed through from preamble to body");
88 auto condition = op.getConditionOp().getCondition();
90 if (
auto cmp = condition.getDefiningOp<
CmpOp>();
93 ivarBefore =
cmp.getLhs();
94 info.ub =
cmp.getRhs();
96 reportFailureReason(
"could not identify an upper bound");
100 auto getBlockArgIndex = [](Value v, ValueRange argList, std::optional<size_t> &index) {
101 for (
auto [i, arg] : llvm::enumerate(argList)) {
110 getBlockArgIndex(ivarBefore, op.getBeforeArguments(), info.ivarIndexBefore);
111 getBlockArgIndex(ivarBefore, op.getConditionOp().getArgs(), info.ivarIndexAfter);
113 if (!info.ivarIndexBefore.has_value() || !info.ivarIndexAfter.has_value()) {
114 reportFailureReason(
"could not identify an induction variable");
121 auto yieldedIVar = op->getResults().drop_front(*info.ivarIndexAfter).front();
122 if (!yieldedIVar.use_empty()) {
123 auto report = reportFailureReason(
"final ivar value unsafe to drop");
124 for (
auto *
use : yieldedIVar.getUsers()) {
125 report.attachNote(
use->getLoc()) <<
"used here";
130 OpBuilder builder(op);
132 auto createBound = [&builder, &op](
const llvm::DynamicAPInt &value,
FeltType type) -> Value {
133 auto attr = FeltConstAttr::get(builder.getContext(), value, type);
139 if (op->hasAttr(llzk::LoopBoundsAttr::name)) {
140 auto bounds = op->getAttrOfType<llzk::LoopBoundsAttr>(llzk::LoopBoundsAttr::name);
141 auto ivarType =
cast<FeltType>(op.getBeforeArguments()[*info.ivarIndexBefore].getType());
144 info.lb = createBound(bounds.getLower(), ivarType);
145 info.ub = createBound(bounds.getUpper(), ivarType);
146 info.step = createBound(bounds.getStep(), ivarType);
150 Value ivarAfter = op.getAfterArguments().drop_front(*info.ivarIndexAfter).front();
153 info.lb = *op.getInits().drop_front(*info.ivarIndexBefore).begin();
156 auto nextIvar = *op.getYieldedValues().drop_front(*info.ivarIndexBefore).begin();
157 if (
auto incOp = nextIvar.getDefiningOp<
AddFeltOp>()) {
158 if (incOp.getRhs() == ivarAfter) {
159 info.step = incOp.getLhs();
160 }
else if (incOp.getLhs() == ivarAfter) {
161 info.step = incOp.getRhs();
168 if (!info.step.has_value()) {
169 if (
auto type = dyn_cast<FeltType>(ivarAfter.getType()); type && type.hasField()) {
173 if (lower && upper && next) {
176 assert(lower.getType() == type &&
"lower bound type must match induction variable");
177 assert(upper.getType() == type &&
"upper bound type must match induction variable");
178 assert(next.getType() == type &&
"yielded constant type must match induction variable");
180 auto lo = field.
reduce(lower.getValue().getRawValue());
183 if (lo < hi && end >= hi) {
186 if (hi > std::numeric_limits<int64_t>::max()) {
187 reportFailureReason(
"upper bound cannot be represented as a nonnegative index");
193 llvm::all_of(op.getBefore().front().without_terminator(), [](Operation &preambleOp) {
194 return mlir::isPure(&preambleOp);
197 reportFailureReason(
"condition region contains operations that are not pure");
201 info.lb = createBound(lo, type);
202 info.ub = createBound(hi, type);
203 info.step = createBound(hi - lo, type);
209 if (!info.step.has_value()) {
210 reportFailureReason(
"could not identify step");
216 std::function<bool(Value, InFlightDiagnostic &)> isRuntimeConstant;
217 isRuntimeConstant = [&op, &isRuntimeConstant](Value val, InFlightDiagnostic &reporter) ->
bool {
219 if (
auto blockArg = dyn_cast<BlockArgument>(val)) {
220 reporter.attachNote(blockArg.getLoc()) <<
"depends on loop-carried value";
221 return blockArg.getParentBlock()->getParentOp() != op;
226 return !llvm::any_of(val.getDefiningOp()->getOperands(), [&](Value operand) {
227 return !isRuntimeConstant(operand, reporter);
231 auto ubReport = reportFailureReason(
"upper bound may not be constant");
232 if (!isRuntimeConstant(*info.ub, ubReport)) {
237 auto stepReport = reportFailureReason(
"step may not be constant");
238 if (!isRuntimeConstant(*info.step, stepReport)) {
241 stepReport.abandon();
246static inline FailureOr<scf::ForOp>
247transformWhileToFor(scf::WhileOp op, ForOpInfo info, RewriterBase &rewriter) {
248 llzk::ensure(info.success(),
"attempting to convert non-constant while loop");
250 rewriter.setInsertionPointAfter(op);
253 auto copyIfNeeded = [op, &rewriter, &mapping](Value val) -> Value {
254 if (
auto *definingOp = val.getDefiningOp();
255 definingOp && definingOp->getParentOfType<scf::WhileOp>() == op) {
256 return rewriter.clone(*definingOp, mapping)->getResult(0);
263 auto toIndex = [&rewriter](Value val) -> Value {
264 if (!isa<FeltType>(val.getType())) {
271 auto lb = copyIfNeeded(*info.lb);
272 auto ub = copyIfNeeded(*info.ub);
273 auto step = copyIfNeeded(*info.step);
277 lb.getType() == ub.getType() && lb.getType() == step.getType(),
278 "cannot have differing types for loop bounds"
280 Type ivarType = lb.getType();
281 if (isa<FeltType>(ivarType)) {
284 step = toIndex(step);
287 SmallVector<Value> inits;
288 for (
auto [i, init] : llvm::enumerate(op.getInits())) {
289 if (i == info.ivarIndexBefore) {
292 inits.push_back(init);
297 op, scf::ForOp::create(rewriter, op->getLoc(), lb, ub, step, inits)
299 rewriter.setInsertionPointToStart(forOp.getBody());
301 auto inductionVar = forOp.getInductionVar();
302 if (isa<FeltType>(ivarType)) {
313 auto *whilePreamble = op.getBeforeBody();
314 for (
size_t i = 0; i < whilePreamble->getNumArguments(); i++) {
315 if (i == info.ivarIndexBefore) {
316 mapping.map(whilePreamble->getArgument(i), inductionVar);
320 whilePreamble->getArgument(i), forOp.getRegionIterArg(i > info.ivarIndexBefore ? i - 1 : i)
325 for (
auto &preambleOp : *whilePreamble) {
328 if (
auto condOp = dyn_cast<scf::ConditionOp>(&preambleOp)) {
329 for (
auto [value, blockArg] : llvm::zip(condOp.getArgs(), op.getAfterArguments())) {
331 mapping.map(blockArg, mapping.lookupOrDefault(value));
335 rewriter.clone(preambleOp, mapping);
338 auto *whileBody = op.getAfterBody();
340 for (
auto &bodyOp : *whileBody) {
343 if (
auto yieldOp = dyn_cast<scf::YieldOp>(&bodyOp)) {
344 SmallVector<Value> valuesToYield;
345 for (
auto [i, val] : llvm::enumerate(yieldOp.getResults())) {
346 if (i == info.ivarIndexAfter) {
350 valuesToYield.push_back(mapping.lookupOrDefault(val));
352 if (!valuesToYield.empty()) {
353 scf::YieldOp::create(rewriter, yieldOp.getLoc(), valuesToYield);
357 rewriter.clone(bodyOp, mapping);
362 SmallVector<Value> replacedValues;
363 for (
auto [i, result] : llvm::enumerate(op.getResults())) {
364 if (i == info.ivarIndexBefore) {
369 replacedValues.push_back(*info.ub);
373 replacedValues.push_back(forOp.getResult(i > info.ivarIndexBefore ? i - 1 : i));
376 rewriter.replaceOp(op, replacedValues);
381 using Base = WhileToForPassBase<PassImpl>;
384 void getDependentDialects(DialectRegistry ®istry)
const override {
387 void runOnOperation()
override {
388 Operation *op = getOperation();
389 IRRewriter rewriter {op->getContext()};
390 auto result = op->walk([&rewriter](scf::WhileOp loop) -> WalkResult {
391 ForOpInfo info = parseInfo(loop);
392 if (!info.success()) {
394 return WalkResult::advance();
396 return LogicalResult(transformWhileToFor(loop, info, rewriter));
398 if (result.wasInterrupted()) {
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Information about the prime finite field used for the interval analysis.
llvm::DynamicAPInt reduce(const llvm::DynamicAPInt &i) const
Returns the canonical representative of i mod p in [0, p).
static const Field & getField(llvm::StringRef fieldName, EmitErrorFn errFn)
Get a Field from a given field name string.
LLZK type conversion operations.
static FeltToIndexOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::Type result, ::llzk::cast::OverflowSemanticsAttr overflow, ::mlir::Value value)
::mlir::TypedValue<::mlir::IndexType > getResult()
::mlir::TypedValue<::llzk::felt::FeltType > getResult()
static IntToFeltOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::Type result, ::llzk::cast::OverflowSemanticsAttr overflow, ::mlir::Value value)
::mlir::TypedValue<::mlir::Type > getResult()
static FeltConstantOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::Type result, ::llzk::felt::FeltConstAttr value)
::llzk::felt::FeltConstAttr getValue()
void ensure(bool condition, const llvm::Twine &errMsg)
ExpressionValue cmp(const llvm::SMTSolverRef &solver, CmpOp op, const ExpressionValue &lhs, const ExpressionValue &rhs)
OpTy preserveDiscardableAttrs(mlir::Operation *src, OpTy dst)
Copy discardable attributes from src to dst.