23#include <mlir/Dialect/SCF/Utils/Utils.h>
25#include <llvm/Support/Debug.h>
26#include <llvm/Support/SMTAPI.h>
33#define GEN_PASS_DEF_FUSEPRODUCTCONTROLFLOWPASS
43constexpr int INDEX_WIDTH = 64;
45static inline bool isConstOrStructParam(Value val) {
47 return llvm::isa<arith::ConstantIndexOp, polymorphic::ConstReadOp, felt::FeltConstantOp>(
52static llvm::SMTExprRef mkExpr(Value value, llvm::SMTSolver *solver) {
53 if (
auto constOp = value.getDefiningOp<arith::ConstantIndexOp>()) {
54 return solver->mkBitvector(llvm::APSInt::get(constOp.value()), INDEX_WIDTH);
57 return solver->mkSymbol(
58 std::string {polyReadOp.getConstName()}.c_str(), solver->getBitvectorSort(INDEX_WIDTH)
61 assert(
false &&
"unsupported: checking non-constant trip counts");
65static llvm::SMTExprRef tripCount(scf::ForOp op, llvm::SMTSolver *solver) {
66 const auto *one = solver->mkBitvector(llvm::APSInt::get(1), INDEX_WIDTH);
67 return solver->mkBVSDiv(
70 solver->mkBVSub(mkExpr(op.getUpperBound(), solver), mkExpr(op.getLowerBound(), solver))
72 mkExpr(op.getStep(), solver)
76static inline bool canLoopsBeFused(scf::ForOp a, scf::ForOp b) {
83 if (a->getParentRegion() != b->getParentRegion()) {
104 auto tripCountA = constantTripCount(
105 a.getLowerBound(), a.getUpperBound(), a.getStep(), !a.getUnsignedCmp(),
106 scf::computeUbMinusLb
108 auto tripCountB = constantTripCount(
109 b.getLowerBound(), b.getUpperBound(), b.getStep(), !b.getUnsignedCmp(),
110 scf::computeUbMinusLb
112 if (tripCountA.has_value() && tripCountB.has_value() && *tripCountA == *tripCountB) {
116 if (!isConstOrStructParam(a.getLowerBound()) || !isConstOrStructParam(a.getUpperBound()) ||
117 !isConstOrStructParam(a.getStep()) || !isConstOrStructParam(b.getLowerBound()) ||
118 !isConstOrStructParam(b.getUpperBound()) || !isConstOrStructParam(b.getStep())) {
122 llvm::SMTSolverRef solver = llvm::CreateZ3Solver();
123 solver->addConstraint( solver->mkNot(
124 solver->mkEqual(tripCount(a, solver.get()), tripCount(b, solver.get()))
127 return !*solver->check();
133static FailureOr<SmallVector<Operation *>>
134canPrepareForFusion(scf::ForOp witnessLoop, scf::ForOp constraintLoop) {
135 if (witnessLoop->getBlock() != constraintLoop->getBlock()) {
139 SmallVector<Operation *> opsToSink;
140 for (
auto *op = witnessLoop->getNextNode(); op != constraintLoop; op = op->getNextNode()) {
148 opsToSink.push_back(op);
155prepareForFusion(scf::ForOp witnessLoop, scf::ForOp constraintLoop, IRRewriter &rewriter) {
156 auto computeOpsToSink = canPrepareForFusion(witnessLoop, constraintLoop);
157 if (failed(computeOpsToSink)) {
161 Operation *insertionPoint = constraintLoop.getOperation();
162 for (Operation *op : *computeOpsToSink) {
163 rewriter.moveOpAfter(op, insertionPoint);
170static LogicalResult fuseMatchingLoopPairs(Region &body, MLIRContext *context) {
172 llvm::SmallVector<scf::ForOp> witnessLoops, constraintLoops;
173 body.walk<WalkOrder::PreOrder>([&witnessLoops, &constraintLoops](scf::ForOp forOp) {
175 return WalkResult::skip();
177 auto productSource = forOp->getAttrOfType<StringAttr>(
PRODUCT_SOURCE);
179 witnessLoops.push_back(forOp);
181 constraintLoops.push_back(forOp);
184 return WalkResult::skip();
190 witnessLoops, constraintLoops, canLoopsBeFused
194 if (failed(fusionCandidates)) {
199 IRRewriter rewriter {context};
200 for (
auto [w, c] : *fusionCandidates) {
201 if (failed(prepareForFusion(w, c, rewriter))) {
204 auto fusedLoop = fuseIndependentSiblingForLoops(w, c, rewriter);
205 fusedLoop->setAttr(
PRODUCT_SOURCE, rewriter.getAttr<StringAttr>(
"fused"));
207 if (failed(fuseMatchingLoopPairs(fusedLoop.getBodyRegion(), context))) {
215 using Base = FuseProductControlFlowPassBase<PassImpl>;
218 void runOnOperation()
override {
219 ModuleOp
mod = getOperation();
220 mod.walk([
this](function::FuncDefOp funcDef) {
222 if (failed(fuseMatchingLoopPairs(funcDef.getFunctionBody(), &getContext()))) {
bool isStructProduct()
Return true iff the function is within a StructDefOp and named FUNC_NAME_PRODUCT.
llvm::FailureOr< llvm::SetVector< std::pair< ValueT, ValueT > > > getMatchingPairs(llvm::ArrayRef< ValueT > as, llvm::ArrayRef< ValueT > bs, FnT doesMatch, bool allowPartial=true)
constexpr char FUNC_NAME_COMPUTE[]
Symbol name for the witness generation (and resp.
ExpressionValue mod(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
constexpr char PRODUCT_SOURCE[]
Name of the attribute on aligned product program ops that specifies where they came from.
constexpr char FUNC_NAME_CONSTRAIN[]