Skip to content

Commit 5dae3b2

Browse files
j2kuncopybara-github
authored andcommitted
Add pattern to ensure mgmt init on plaintext branch terminators
In some cases, an if/else can be produced in which the else branch consists of a single yield of a plaintext constant. This is not directly used in a ct-pt op, which causes the normal UseInitOpForPlaintextOperand pattern to fail to recognize it as requiring intialization. This change adds a new pattern, UseInitForPlaintextBranchTerminators, that looks for RegionBranchTerminatorOpInterface, determines if the yielded value is not secret, and then checks all other terminators of the op that forward to the same successor region, if any of them are secret, then this pattern will insert a mgmt.init op on the non-secret operand so it can later be understood to be a plaintext. PiperOrigin-RevId: 886259688
1 parent ae671e7 commit 5dae3b2

6 files changed

Lines changed: 131 additions & 9 deletions

File tree

lib/Transforms/Halo/Patterns.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,6 @@ FailureOr<SmallVector<Value>> isLoopStructuredForHaloUnroll(
201201
// Inject an scf::ForOp overload of this function, which exists upstream for
202202
// affine already.
203203
FailureOr<int64_t> getConstantTripCount(scf::ForOp forOp) {
204-
LLVM_DEBUG(llvm::dbgs() << "Getting constant trip count\n");
205204
if (auto step = forOp.getConstantStep();
206205
!step.has_value() || !step->isOne()) {
207206
if (step.has_value()) {
@@ -287,11 +286,9 @@ LogicalResult doPartialUnroll(ForOp forOp, PatternRewriter& rewriter,
287286
"Start and end levels were not inferable to be concrete integers");
288287
}
289288

290-
LLVM_DEBUG(llvm::dbgs() << "doPartialUnroll: Getting levelEndVal\n");
291289
int levelEndVal = levelEnd->getValue().getInt();
292290
LLVM_DEBUG(llvm::dbgs()
293291
<< "doPartialUnroll: levelEndVal=" << levelEndVal << "\n");
294-
LLVM_DEBUG(llvm::dbgs() << "doPartialUnroll: Getting levelStartVal\n");
295292
int levelStartVal = levelStart->getValue().getInt();
296293
LLVM_DEBUG(llvm::dbgs()
297294
<< "doPartialUnroll: levelStartVal=" << levelStartVal << "\n");

lib/Transforms/SecretInsertMgmt/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ cc_library(
9292
"@llvm-project//llvm:Support",
9393
"@llvm-project//mlir:Analysis",
9494
"@llvm-project//mlir:ArithDialect",
95+
"@llvm-project//mlir:ControlFlowInterfaces",
9596
"@llvm-project//mlir:IR",
9697
"@llvm-project//mlir:SideEffectInterfaces",
9798
"@llvm-project//mlir:Support",

lib/Transforms/SecretInsertMgmt/Pipeline.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,19 +223,23 @@ void makeLoopsTypeAndLevelInvariant(Operation* top) {
223223

224224
DataFlowSolver solver;
225225
makeAndRunSecretnessSolver(top, solver);
226-
227226
RewritePatternSet patterns(ctx);
228227
patterns.add<PeelPlaintextAffineForInit, PeelPlaintextScfForInit>(ctx,
229228
&solver);
230229
walkAndApplyPatterns(top, std::move(patterns));
231230

232231
DataFlowSolver solver2;
233232
makeAndRunSecretnessSolver(top, solver2);
234-
235233
patterns.clear();
236234
patterns.add<BootstrapIterArgsPattern<affine::AffineForOp>,
237235
BootstrapIterArgsPattern<scf::ForOp>>(ctx, &solver2);
238236
walkAndApplyPatterns(top, std::move(patterns));
237+
238+
DataFlowSolver solver3;
239+
makeAndRunSecretnessSolver(top, solver3);
240+
patterns.clear();
241+
patterns.add<UseInitForPlaintextBranchTerminators>(ctx, &solver3);
242+
walkAndApplyPatterns(top, std::move(patterns));
239243
}
240244

241245
void unrollLoopsForLevelUtilization(Operation* top, int levelBudget) {

lib/Transforms/SecretInsertMgmt/SecretInsertMgmtPatterns.cpp

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "mlir/include/mlir/IR/BuiltinAttributes.h" // from @llvm-project
1717
#include "mlir/include/mlir/IR/PatternMatch.h" // from @llvm-project
1818
#include "mlir/include/mlir/IR/Value.h" // from @llvm-project
19+
#include "mlir/include/mlir/Interfaces/ControlFlowInterfaces.h" // from @llvm-project
1920
#include "mlir/include/mlir/Interfaces/SideEffectInterfaces.h" // from @llvm-project
2021
#include "mlir/include/mlir/Support/LLVM.h" // from @llvm-project
2122

@@ -274,13 +275,60 @@ LogicalResult MatchCrossMulDepth<Op>::matchAndRewrite(
274275
}
275276
}
276277

277-
// FIXME: replace with updateResultMulDepthLattice(op, solver);
278-
// propagateIfChanged only push workitem to the worklist queue
279-
// actually execute the transfer for the new values
280278
solver->eraseAllStates();
281279
return solver->initializeAndRun(top);
282280
}
283281

282+
LogicalResult UseInitForPlaintextBranchTerminators::matchAndRewrite(
283+
RegionBranchTerminatorOpInterface op, PatternRewriter& rewriter) const {
284+
auto regionBranchOp = dyn_cast<RegionBranchOpInterface>(op->getParentOp());
285+
if (!regionBranchOp) {
286+
return rewriter.notifyMatchFailure(
287+
op, "parent does not implement RegionBranchOpInterface");
288+
}
289+
290+
// If the op's operands are all already secret, return failure
291+
if (!op->getOperands().empty() &&
292+
llvm::all_of(op->getOperands(),
293+
[&](Value operand) { return isSecret(operand, solver); })) {
294+
return rewriter.notifyMatchFailure(op, "all operands are already secret");
295+
}
296+
297+
bool changed = false;
298+
SmallVector<RegionSuccessor> successors;
299+
SmallVector<Attribute> operands(op->getNumOperands(), nullptr);
300+
op.getSuccessorRegions(operands, successors);
301+
302+
for (const auto& successor : successors) {
303+
auto successorOperands = op.getSuccessorOperands(successor);
304+
auto successorInputs = regionBranchOp.getSuccessorInputs(successor);
305+
306+
if (successorOperands.empty()) continue;
307+
308+
for (unsigned i = 0; i < successorOperands.size(); ++i) {
309+
Value operand = successorOperands[i];
310+
Value target = successorInputs[i];
311+
312+
if (isSecret(target, solver) && !isSecret(operand, solver)) {
313+
// Check if already initted
314+
if (auto definingOp = operand.getDefiningOp()) {
315+
if (isa<mgmt::InitOp>(definingOp)) continue;
316+
}
317+
318+
rewriter.setInsertionPoint(op);
319+
auto initOp = mgmt::InitOp::create(rewriter, op.getLoc(),
320+
operand.getType(), operand);
321+
322+
op->setOperand(successorOperands.getBeginOperandIndex() + i,
323+
initOp.getResult());
324+
changed = true;
325+
}
326+
}
327+
}
328+
329+
return changed ? success() : failure();
330+
}
331+
284332
template <typename Op>
285333
LogicalResult UseInitOpForPlaintextOperand<Op>::matchAndRewrite(
286334
Op op, PatternRewriter& rewriter) const {

lib/Transforms/SecretInsertMgmt/SecretInsertMgmtPatterns.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
#include "mlir/include/mlir/IR/MLIRContext.h" // from @llvm-project
66
#include "mlir/include/mlir/IR/Operation.h" // from @llvm-project
77
#include "mlir/include/mlir/IR/PatternMatch.h" // from @llvm-project
8-
#include "mlir/include/mlir/Support/LLVM.h" // from @llvm-project
8+
#include "mlir/include/mlir/Interfaces/ControlFlowInterfaces.h" // from @llvm-project
9+
#include "mlir/include/mlir/Support/LLVM.h" // from @llvm-project
910

1011
// This file include patterns that are combined and orchestrated in the
1112
// functions defined in `Pipeline.h`. Warning: use these patterns with care,
@@ -179,6 +180,23 @@ struct UseInitOpForPlaintextOperand : public OpRewritePattern<Op> {
179180
DataFlowSolver* solver;
180181
};
181182

183+
/// Insert mgmt.init op for region branch terminators where the other
184+
/// branch is secret.
185+
struct UseInitForPlaintextBranchTerminators
186+
: public OpInterfaceRewritePattern<RegionBranchTerminatorOpInterface> {
187+
UseInitForPlaintextBranchTerminators(MLIRContext* context,
188+
DataFlowSolver* solver)
189+
: OpInterfaceRewritePattern<RegionBranchTerminatorOpInterface>(
190+
context, /*benefit=*/1),
191+
solver(solver) {}
192+
193+
LogicalResult matchAndRewrite(RegionBranchTerminatorOpInterface op,
194+
PatternRewriter& rewriter) const override;
195+
196+
private:
197+
DataFlowSolver* solver;
198+
};
199+
182200
/// when reached a certain depth (water line), bootstrap
183201
///
184202
/// TODO(#1642): make it work with cross-level operation
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// RUN: heir-opt "--secret-insert-mgmt-ckks=after-mul=true before-mul-include-first-mul=false bootstrap-waterline=10 level-budget=2 slot-number=1024" %s | FileCheck %s
2+
3+
// This test was extracted from a larger matvec example. The main issue is
4+
// that, when the first loop iteration is peeled, the initializer is replaced
5+
// by the constant initializer in the else branch of the scf.yield (which just
6+
// naively returns the iter_arg). In some cases, sccp can resolve this issue
7+
// and detect that the if statement will not execute its else statement. But in
8+
// a doubly nested loop (approximated here when the loop condition is computed
9+
// using a func arg), the else cannot be removed. So we require an additional
10+
// mgmt.init to be inserted to ensure that the secretness aligns on both
11+
// branches.
12+
13+
module attributes {backend.lattigo, scheme.ckks} {
14+
func.func @loop_with_trivial_if_branch(
15+
%arg0: !secret.secret<tensor<1x1024xf32>>,
16+
%arg1: tensor<512x1024xf32>,
17+
%outer_loop_iv: index
18+
) -> !secret.secret<tensor<1x1024xf32>> {
19+
%c23 = arith.constant 23 : index
20+
// CHECK: [[CST:%[^ ]*]] = arith.constant dense<0.0{{.*}}> : tensor<1x1024xf32>
21+
// CHECK: else
22+
// CHECK-NEXT: [[INIT:%[^ ]*]] = mgmt.init [[CST]]
23+
// CHECK-NEXT: scf.yield [[INIT]]
24+
%cst = arith.constant dense<0.000000e+00> : tensor<1x1024xf32>
25+
%c-23 = arith.constant -23 : index
26+
%c512 = arith.constant 512 : index
27+
%c0 = arith.constant 0 : index
28+
%c1 = arith.constant 1 : index
29+
%0 = secret.generic(%arg0: !secret.secret<tensor<1x1024xf32>>) {
30+
^body(%input0: tensor<1x1024xf32>):
31+
%5 = scf.for %arg4 = %c0 to %c23 step %c1 iter_args(%arg5 = %cst) -> (tensor<1x1024xf32>) {
32+
%9 = arith.muli %outer_loop_iv, %c23 : index
33+
%10 = arith.addi %arg4, %9 : index
34+
%11 = arith.cmpi slt, %10, %c512 : index
35+
%12 = scf.if %11 -> (tensor<1x1024xf32>) {
36+
%extracted_slice = tensor.extract_slice %arg1[%10, 0] [1, 1024] [1, 1] : tensor<512x1024xf32> to tensor<1x1024xf32>
37+
%13 = arith.muli %outer_loop_iv, %c-23 : index
38+
%14 = tensor_ext.rotate %extracted_slice, %13 : tensor<1x1024xf32>, index
39+
%15 = tensor_ext.rotate %input0, %arg4 : tensor<1x1024xf32>, index
40+
%16 = arith.mulf %14, %15 : tensor<1x1024xf32>
41+
%17 = arith.addf %arg5, %16 : tensor<1x1024xf32>
42+
scf.yield %17 : tensor<1x1024xf32>
43+
} else {
44+
scf.yield %arg5 : tensor<1x1024xf32>
45+
}
46+
scf.yield %12 : tensor<1x1024xf32>
47+
}
48+
%6 = arith.muli %outer_loop_iv, %c23 : index
49+
%7 = tensor_ext.rotate %5, %6 : tensor<1x1024xf32>, index
50+
secret.yield %7 : tensor<1x1024xf32>
51+
} -> !secret.secret<tensor<1x1024xf32>>
52+
return %0 : !secret.secret<tensor<1x1024xf32>>
53+
}
54+
}

0 commit comments

Comments
 (0)