Enzyme main
Loading...
Searching...
No Matches
LowerBinomialProgressPass.cpp
Go to the documentation of this file.
1//===------------------------------------------------------------------------ //
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements a pass to lower the enzyme.binomial_progress op (the
10// Revolve binomial-checkpointing "split" function) into scf/arith ops.
11//
12//===----------------------------------------------------------------------===//
13
14#include "Dialect/Ops.h"
15#include "Passes/Passes.h"
16#include "mlir/Dialect/Arith/IR/Arith.h"
17#include "mlir/Dialect/SCF/IR/SCF.h"
18#include "mlir/IR/Matchers.h"
19
20namespace mlir {
21namespace enzyme {
22#define GEN_PASS_DEF_LOWERBINOMIALPROGRESSPASS
23#include "Passes/Passes.h.inc"
24} // namespace enzyme
25} // namespace mlir
26
27using namespace mlir;
28
29namespace {
30
31// Lower enzyme.binomial_progress(n, s) to the Revolve advance distance; see
32// BinomialProgressOp's description for the derivation.
33//
34// %one = arith.constant 1
35// %c = (n <= %one) or (s <= %one)
36// %r = scf.if %c {
37// // n <= 1 yields n (0 or 1); otherwise s <= 1 yields n. Both are n.
38// scf.yield n
39// } else {
40// // smallest t with beta = C(s+t, t) >= n
41// %w:2 = scf.while (%t = %zero, %beta = %one) {
42// %lt = arith.cmpi slt, %beta, n
43// scf.condition(%lt) %t, %beta
44// } do {
45// ^bb0(%t: index, %beta: index):
46// %t2 = %t + %one
47// %beta2 = %beta * (s + %t2) / %t2
48// scf.yield %t2, %beta2
49// }
50// // window [n - beta(s-1,t), beta(s,t-1)], clamped; take the midpoint
51// %lo = maxsi(n - (%beta * s) / (s + %t), %one)
52// %hi = minsi((%beta * %t) / (s + %t), n - %one)
53// scf.yield (%lo + %hi) / 2
54// }
55//
56// The guard must be a branch, not a select: for s <= 1 the update leaves %beta
57// at 1 and the loop would spin forever.
58static int64_t binomialProgress(int64_t n, int64_t s) {
59 if (n <= 0)
60 return 0;
61 if (n == 1)
62 return 1;
63 if (s <= 1)
64 return n;
65 int64_t t = 0, beta = 1; // beta == C(s + t, t)
66 while (beta < n) {
67 ++t;
68 beta = beta * (s + t) / t;
69 }
70 int64_t lo = n - beta * s / (s + t);
71 int64_t hi = beta * t / (s + t);
72 if (lo < 1)
73 lo = 1;
74 if (hi > n - 1)
75 hi = n - 1;
76 int64_t m = (lo + hi) / 2;
77 int64_t cap = n - (s - 1); // leave a step for each slot still to be placed
78 if (m > cap)
79 m = cap;
80 return m < 1 ? 1 : m;
81}
82
83static void lowerBinomialProgress(enzyme::BinomialProgressOp op) {
84 // Tensor operands are lowered elsewhere; this pass only handles the
85 // scalar integer/index case.
86 if (isa<TensorType>(op.getType()))
87 return;
88
89 OpBuilder builder(op);
90 Location loc = op.getLoc();
91 Value n = op.getNumSteps();
92 Value s = op.getBudget();
93 Type idxTy = op.getType();
94 auto constOfType = [&](int64_t v) -> Value {
95 return arith::ConstantOp::create(builder, loc,
96 builder.getIntegerAttr(idxTy, v));
97 };
98
99 // Constant fast-path: fold to a plain constant.
100 llvm::APInt nCst, sCst;
101 if (matchPattern(n, m_ConstantInt(&nCst)) &&
102 matchPattern(s, m_ConstantInt(&sCst)) && nCst.getSExtValue() > 0 &&
103 sCst.getSExtValue() > 0) {
104 Value c =
105 constOfType(binomialProgress(nCst.getSExtValue(), sCst.getSExtValue()));
106 op.getResult().replaceAllUsesWith(c);
107 op->erase();
108 return;
109 }
110
111 Value zero = constOfType(0);
112 Value one = constOfType(1);
113
114 // Guard both degenerate cases. It has to be a branch: with s <= 1 the loop
115 // body below leaves beta at 1 and would never terminate.
116 Value nSmall =
117 arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::sle, n, one);
118 Value sSmall =
119 arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::sle, s, one);
120 Value cond = arith::OrIOp::create(builder, loc, nSmall, sSmall);
121
122 auto ifOp = scf::IfOp::create(builder, loc, TypeRange{idxTy}, cond,
123 /*withElseRegion=*/true);
124
125 {
126 OpBuilder::InsertionGuard guard(builder);
127 builder.setInsertionPointToStart(ifOp.thenBlock());
128 // n <= 1 yields n itself (0 or 1); s <= 1 advances the whole remainder,
129 // which is also n. So both degenerate cases are just n.
130 scf::YieldOp::create(builder, loc, ValueRange{n});
131 }
132
133 {
134 OpBuilder::InsertionGuard guard(builder);
135 builder.setInsertionPointToStart(ifOp.elseBlock());
136
137 // Smallest t with beta = C(s + t, t) >= n, carrying (t, beta).
138 auto whileOp = scf::WhileOp::create(builder, loc, TypeRange{idxTy, idxTy},
139 ValueRange{zero, one});
140
141 // Before region: continue while beta < n.
142 {
143 Block *before = builder.createBlock(&whileOp.getBefore(), {},
144 TypeRange{idxTy, idxTy}, {loc, loc});
145 builder.setInsertionPointToEnd(before);
146 Value beta = before->getArgument(1);
147 Value lt = arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::slt,
148 beta, n);
149 scf::ConditionOp::create(builder, loc, lt, before->getArguments());
150 }
151
152 // After region: t' = t + 1; beta' = beta * (s + t') / t'. That steps
153 // C(s+t-1, t-1) to C(s+t, t), so the division is exact.
154 {
155 Block *after = builder.createBlock(&whileOp.getAfter(), {},
156 TypeRange{idxTy, idxTy}, {loc, loc});
157 builder.setInsertionPointToEnd(after);
158 Value t = after->getArgument(0);
159 Value beta = after->getArgument(1);
160 Value t2 = arith::AddIOp::create(builder, loc, t, one);
161 Value sPlusT = arith::AddIOp::create(builder, loc, s, t2);
162 Value num = arith::MulIOp::create(builder, loc, beta, sPlusT);
163 Value beta2 = arith::DivUIOp::create(builder, loc, num, t2);
164 scf::YieldOp::create(builder, loc, ValueRange{t2, beta2});
165 }
166
167 builder.setInsertionPointAfter(whileOp);
168 Value t = whileOp.getResult(0);
169 Value beta = whileOp.getResult(1);
170
171 // beta(s-1, t) = beta * s / (s + t) and beta(s, t-1) = beta * t / (s + t),
172 // both exact in integers. Every advance between n - beta(s-1,t) and
173 // beta(s, t-1) attains the optimal repetition count; clamp that window to
174 // [1, n-1] -- so the caller always progresses and always leaves a tail --
175 // and take its midpoint, since either edge can collapse onto the clamp and
176 // waste a checkpoint on a one-step advance.
177 Value sPlusT = arith::AddIOp::create(builder, loc, s, t);
178 Value loNum = arith::MulIOp::create(builder, loc, beta, s);
179 Value loRaw = arith::SubIOp::create(
180 builder, loc, n, arith::DivUIOp::create(builder, loc, loNum, sPlusT));
181 Value hiNum = arith::MulIOp::create(builder, loc, beta, t);
182 Value hiRaw = arith::DivUIOp::create(builder, loc, hiNum, sPlusT);
183
184 Value nm1 = arith::SubIOp::create(builder, loc, n, one);
185 Value lo = arith::MaxSIOp::create(builder, loc, loRaw, one);
186 Value hi = arith::MinSIOp::create(builder, loc, hiRaw, nm1);
187 Value two = constOfType(2);
188 Value sum = arith::AddIOp::create(builder, loc, lo, hi);
189 Value mid = arith::DivUIOp::create(builder, loc, sum, two);
190
191 // Leave a step for each of the s-1 checkpoints still to be placed. Without
192 // this the advances can exhaust the interval before the slots run out, and
193 // a caller walking one slot per iteration then records slots at a step past
194 // the end, holding the final state rather than a checkpoint.
195 Value sm1 = arith::SubIOp::create(builder, loc, s, one);
196 Value cap = arith::SubIOp::create(builder, loc, n, sm1);
197 Value capped = arith::MinSIOp::create(builder, loc, mid, cap);
198 Value res = arith::MaxSIOp::create(builder, loc, capped, one);
199 scf::YieldOp::create(builder, loc, ValueRange{res});
200 }
201
202 op.getResult().replaceAllUsesWith(ifOp.getResult(0));
203 op->erase();
204}
205
206struct LowerBinomialProgressPass
207 : public enzyme::impl::LowerBinomialProgressPassBase<
208 LowerBinomialProgressPass> {
209 void runOnOperation() override {
210 SmallVector<enzyme::BinomialProgressOp> ops;
211 getOperation()->walk(
212 [&](enzyme::BinomialProgressOp op) { ops.push_back(op); });
213 for (auto op : ops)
214 lowerBinomialProgress(op);
215 }
216};
217} // end anonymous namespace
static int64_t binomialProgress(int64_t n, int64_t s)
Definition Ops.cpp:96