|
12 | 12 | #include "lib/Utils/AttributeUtils.h" |
13 | 13 | #include "llvm/include/llvm/ADT/STLExtras.h" // from @llvm-project |
14 | 14 | #include "llvm/include/llvm/ADT/SmallVector.h" // from @llvm-project |
| 15 | +#include "mlir/include/mlir/Dialect/Affine/IR/AffineOps.h" // from @llvm-project |
15 | 16 | #include "mlir/include/mlir/Dialect/Affine/Transforms/Passes.h" // from @llvm-project |
16 | 17 | #include "mlir/include/mlir/Dialect/Arith/IR/Arith.h" // from @llvm-project |
17 | 18 | #include "mlir/include/mlir/Dialect/Func/IR/FuncOps.h" // from @llvm-project |
|
28 | 29 | #include "mlir/include/mlir/IR/MLIRContext.h" // from @llvm-project |
29 | 30 | #include "mlir/include/mlir/IR/OpDefinition.h" // from @llvm-project |
30 | 31 | #include "mlir/include/mlir/IR/Operation.h" // from @llvm-project |
| 32 | +#include "mlir/include/mlir/IR/PatternMatch.h" // from @llvm-project |
31 | 33 | #include "mlir/include/mlir/IR/Region.h" // from @llvm-project |
32 | 34 | #include "mlir/include/mlir/IR/TypeUtilities.h" // from @llvm-project |
33 | 35 | #include "mlir/include/mlir/IR/Types.h" // from @llvm-project |
@@ -120,6 +122,86 @@ static bool isAllowedPlaintextType(Type type) { |
120 | 122 | return false; |
121 | 123 | } |
122 | 124 |
|
| 125 | +// Rebuild each affine.for in `funcOp` without the iter_args whose region |
| 126 | +// argument and loop result are both unused. |
| 127 | +// |
| 128 | +// When cloning a loop into the preprocessing function we leave behind a dummy |
| 129 | +// initializer for any loop-carried value that isn't part of the plaintext |
| 130 | +// slice (e.g. a ciphertext accumulator), relying on remove-dead-values to drop |
| 131 | +// the now-unused iter_arg. That works for scf.for, but remove-dead-values only |
| 132 | +// poisons (it does not structurally remove) dead affine.for iter_args, so a |
| 133 | +// ub.poison-typed loop-carried value would otherwise survive into backend |
| 134 | +// lowering, where it cannot be converted or emitted. This performs the removal |
| 135 | +// remove-dead-values cannot, after which the dummy initializers become dead and |
| 136 | +// are cleaned up normally. |
| 137 | +static void removeDeadAffineForIterArgs(func::FuncOp funcOp) { |
| 138 | + IRRewriter rewriter(funcOp.getContext()); |
| 139 | + |
| 140 | + SmallVector<affine::AffineForOp> loops; |
| 141 | + funcOp.walk([&](affine::AffineForOp forOp) { loops.push_back(forOp); }); |
| 142 | + |
| 143 | + for (affine::AffineForOp forOp : loops) { |
| 144 | + unsigned numIterArgs = forOp.getNumIterOperands(); |
| 145 | + if (numIterArgs == 0) continue; |
| 146 | + |
| 147 | + SmallVector<unsigned> keptIndices; |
| 148 | + for (unsigned i = 0; i < numIterArgs; ++i) { |
| 149 | + if (!forOp.getRegionIterArgs()[i].use_empty() || |
| 150 | + !forOp.getResult(i).use_empty()) { |
| 151 | + keptIndices.push_back(i); |
| 152 | + } |
| 153 | + } |
| 154 | + if (keptIndices.size() == numIterArgs) continue; // nothing dead |
| 155 | + |
| 156 | + rewriter.setInsertionPoint(forOp); |
| 157 | + SmallVector<Value> keptInits; |
| 158 | + for (unsigned i : keptIndices) keptInits.push_back(forOp.getInits()[i]); |
| 159 | + |
| 160 | + auto newLoop = affine::AffineForOp::create( |
| 161 | + rewriter, forOp.getLoc(), forOp.getLowerBoundOperands(), |
| 162 | + forOp.getLowerBoundMap(), forOp.getUpperBoundOperands(), |
| 163 | + forOp.getUpperBoundMap(), forOp.getStepAsInt(), keptInits); |
| 164 | + |
| 165 | + // Trim the existing terminator down to the kept loop-carried values. |
| 166 | + auto yieldOp = |
| 167 | + cast<affine::AffineYieldOp>(forOp.getBody()->getTerminator()); |
| 168 | + SmallVector<Value> keptYields; |
| 169 | + for (unsigned i : keptIndices) keptYields.push_back(yieldOp.getOperand(i)); |
| 170 | + rewriter.modifyOpInPlace( |
| 171 | + yieldOp, [&]() { yieldOp.getOperandsMutable().assign(keptYields); }); |
| 172 | + |
| 173 | + // With no kept iter_args the builder added a default terminator; drop it so |
| 174 | + // the merged (trimmed) affine.yield is the loop's only terminator. |
| 175 | + if (keptInits.empty()) { |
| 176 | + rewriter.eraseOp(newLoop.getBody()->getTerminator()); |
| 177 | + } |
| 178 | + |
| 179 | + // Map the old block arguments onto the new loop: induction var, then each |
| 180 | + // iter_arg. Kept ones map to the new region args; dead ones are unused, so |
| 181 | + // their (type-matched, dominating) original initializer is a safe |
| 182 | + // placeholder that is never actually referenced. |
| 183 | + SmallVector<Value> blockArgReplacements; |
| 184 | + blockArgReplacements.push_back(newLoop.getInductionVar()); |
| 185 | + unsigned keptCursor = 0; |
| 186 | + for (unsigned i = 0; i < numIterArgs; ++i) { |
| 187 | + if (keptCursor < keptIndices.size() && keptIndices[keptCursor] == i) { |
| 188 | + blockArgReplacements.push_back( |
| 189 | + newLoop.getRegionIterArgs()[keptCursor++]); |
| 190 | + } else { |
| 191 | + blockArgReplacements.push_back(forOp.getInits()[i]); |
| 192 | + } |
| 193 | + } |
| 194 | + rewriter.mergeBlocks(forOp.getBody(), newLoop.getBody(), |
| 195 | + blockArgReplacements); |
| 196 | + |
| 197 | + for (auto [newIdx, oldIdx] : llvm::enumerate(keptIndices)) { |
| 198 | + rewriter.replaceAllUsesWith(forOp.getResult(oldIdx), |
| 199 | + newLoop.getResult(newIdx)); |
| 200 | + } |
| 201 | + rewriter.eraseOp(forOp); |
| 202 | + } |
| 203 | +} |
| 204 | + |
123 | 205 | struct SplitPreprocessingPass |
124 | 206 | : impl::SplitPreprocessingBase<SplitPreprocessingPass> { |
125 | 207 | using SplitPreprocessingBase::SplitPreprocessingBase; |
@@ -194,6 +276,15 @@ struct SplitPreprocessingPass |
194 | 276 | (void)runPipeline(pipeline, preprocessingFuncOp); |
195 | 277 | (void)runPipeline(pipeline, preprocessedFuncOp); |
196 | 278 | (void)runPipeline(pipeline, funcOp); |
| 279 | + |
| 280 | + // remove-dead-values poisons but cannot structurally strip a dead |
| 281 | + // affine.for iter_arg (it only does so for scf.for), so the dummy |
| 282 | + // ciphertext iter_arg left when cloning a loop survives as a ub.poison |
| 283 | + // loop-carried value that later backend lowering can neither convert nor |
| 284 | + // emit. Strip those dead iter_args now, then re-run the cleanup so the |
| 285 | + // orphaned ub.poison initializers are removed too. |
| 286 | + removeDeadAffineForIterArgs(preprocessingFuncOp); |
| 287 | + (void)runPipeline(pipeline, preprocessingFuncOp); |
197 | 288 | } |
198 | 289 |
|
199 | 290 | void updateOriginalFunc(FuncOp funcOp, FuncOp preprocessingFuncOp, |
|
0 commit comments