LLZK 2.1.1
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
LLZKRemoveUnusedDiscardableAllocationsPass.cpp
Go to the documentation of this file.
1//===-- LLZKRemoveUnusedDiscardableAllocationsPass.cpp ---------*- C++ -*-===//
2//
3// Part of the LLZK Project, under the Apache License v2.0.
4// See LICENSE.txt for license information.
5// Copyright 2026 Project LLZK
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
13//===----------------------------------------------------------------------===//
14
17
18#include <mlir/Analysis/DataLayoutAnalysis.h>
19#include <mlir/IR/BuiltinOps.h>
20#include <mlir/IR/Operation.h>
21#include <mlir/Interfaces/SideEffectInterfaces.h>
22
23#include <llvm/ADT/ArrayRef.h>
24#include <llvm/ADT/STLExtras.h>
25#include <llvm/ADT/SmallPtrSet.h>
26#include <llvm/ADT/SmallVector.h>
27#include <llvm/Support/Casting.h>
28
29// Include the generated base pass class definitions.
30namespace llzk {
31#define GEN_PASS_DEF_REMOVEUNUSEDDISCARDABLEALLOCATIONSPASS
33} // namespace llzk
34
35using namespace mlir;
36using namespace llzk;
37
38namespace {
39
41static bool hasDiscardableAllocationEffect(Operation *allocator) {
42 auto effectInterface = llvm::dyn_cast<MemoryEffectOpInterface>(allocator);
43 if (!effectInterface) {
44 return false;
45 }
46
47 llvm::SmallVector<SideEffects::EffectInstance<MemoryEffects::Effect>, 4> effects;
48 effectInterface.getEffects(effects);
49 return llvm::any_of(effects, [](const auto &effect) {
50 return llvm::isa<MemoryEffects::Allocate>(effect.getEffect()) &&
51 llvm::isa<DiscardableAllocationResource>(effect.getResource());
52 });
53}
54
56static FailureOr<bool> collectRemovableUsers(
57 Operation *allocator, SmallVectorImpl<Operation *> &usersToErase, const DataLayout &dataLayout
58) {
59 if (allocator->getNumResults() != 1) {
60 allocator->emitOpError()
61 << "selected for discardable-allocation cleanup but does not have one result";
62 return failure();
63 }
64
65 Value allocation = allocator->getResult(0);
66 if (allocation.use_empty()) {
67 return true;
68 }
69
70 for (OpOperand &use : allocation.getUses()) {
71 Operation *user = use.getOwner();
72 if (user->mightHaveTrait<OpTrait::IsTerminator>()) {
73 return false;
74 }
75
76 auto accessor = llvm::dyn_cast<DiscardableAllocationAccessorOpInterface>(user);
77 if (!accessor || accessor.loadsFromDiscardableAllocation(allocation) ||
78 !accessor.storesToDiscardableAllocation(allocation) ||
79 !accessor.canEraseAsDeadStoreTo(allocation, dataLayout)) {
80 return false;
81 }
82 usersToErase.push_back(user);
83 }
84
85 return true;
86}
87
89static void collectOperandDefiningOps(
90 llvm::ArrayRef<Operation *> opsToErase,
91 const llvm::SmallPtrSetImpl<Operation *> &opsBeingErased,
92 SmallVectorImpl<Operation *> &maybeDeadDefs
93) {
94 llvm::SmallPtrSet<Operation *, 16> seenDefs;
95 for (Operation *op : opsToErase) {
96 for (Value operand : op->getOperands()) {
97 Operation *definingOp = operand.getDefiningOp();
98 if (definingOp && !opsBeingErased.contains(definingOp) &&
99 seenDefs.insert(definingOp).second) {
100 maybeDeadDefs.push_back(definingOp);
101 }
102 }
103 }
104}
105
107static bool eraseTriviallyDeadDefs(SmallVectorImpl<Operation *> &maybeDeadDefs) {
108 bool changed = false;
109 bool changedThisIteration = false;
110 llvm::SmallPtrSet<Operation *, 16> seen(maybeDeadDefs.begin(), maybeDeadDefs.end());
111 llvm::SmallPtrSet<Operation *, 16> erased;
112
113 do {
114 changedThisIteration = false;
115 for (size_t i = 0; i < maybeDeadDefs.size(); ++i) {
116 Operation *op = maybeDeadDefs[i];
117 if (erased.contains(op) || !isOpTriviallyDead(op)) {
118 continue;
119 }
120 for (Value operand : op->getOperands()) {
121 if (Operation *definingOp = operand.getDefiningOp()) {
122 if (seen.insert(definingOp).second) {
123 maybeDeadDefs.push_back(definingOp);
124 }
125 }
126 }
127 op->erase();
128 erased.insert(op);
129 changedThisIteration = true;
130 }
131 changed |= changedThisIteration;
132 } while (changedThisIteration);
133
134 return changed;
135}
136
140static FailureOr<bool> removeUnusedDiscardableAllocations(
141 ModuleOp module, StringRef allocatorOpName, const DataLayout &dataLayout
142) {
143 bool changed = false;
144 bool changedThisIteration = false;
145 do {
146 changedThisIteration = false;
147 SmallVector<Operation *> opsToErase;
148 SmallVector<Operation *> maybeDeadDefs;
149 llvm::SmallPtrSet<Operation *, 16> seen;
150
151 WalkResult walkResult = module->walk([&](Operation *allocator) {
152 if (allocator->getName().getStringRef() != allocatorOpName) {
153 return WalkResult::advance();
154 }
155 if (!hasDiscardableAllocationEffect(allocator)) {
156 allocator->emitOpError()
157 << "selected for discardable-allocation cleanup but is not marked with "
158 "MemAlloc<DiscardableAllocationResource>";
159 return WalkResult::interrupt();
160 }
161
162 SmallVector<Operation *> usersToErase;
163 FailureOr<bool> canRemove = collectRemovableUsers(allocator, usersToErase, dataLayout);
164 if (failed(canRemove)) {
165 return WalkResult::interrupt();
166 }
167 if (!*canRemove) {
168 return WalkResult::advance();
169 }
170 for (Operation *user : usersToErase) {
171 if (seen.insert(user).second) {
172 opsToErase.push_back(user);
173 }
174 }
175 if (seen.insert(allocator).second) {
176 opsToErase.push_back(allocator);
177 }
178 return WalkResult::advance();
179 });
180 if (walkResult.wasInterrupted()) {
181 return failure();
182 }
183
184 collectOperandDefiningOps(opsToErase, seen, maybeDeadDefs);
185 for (Operation *op : opsToErase) {
186 op->erase();
187 }
188
189 changedThisIteration = !opsToErase.empty();
190 changedThisIteration |= eraseTriviallyDeadDefs(maybeDeadDefs);
191 changed |= changedThisIteration;
192 } while (changedThisIteration);
193
194 return changed;
195}
196
197class PassImpl : public llzk::impl::RemoveUnusedDiscardableAllocationsPassBase<PassImpl> {
198 using Base = RemoveUnusedDiscardableAllocationsPassBase<PassImpl>;
199 using Base::Base;
200
201 void runOnOperation() override {
202 ModuleOp module = getOperation();
203 if (allocatorOpName.empty()) {
204 module.emitError() << "requires non-empty 'allocator-op' option";
205 signalPassFailure();
206 return;
207 }
208
209 auto &dataLayoutAnalysis = getAnalysis<DataLayoutAnalysis>();
210 const DataLayout &dataLayout = dataLayoutAnalysis.getAtOrAbove(module);
211
212 FailureOr<bool> changed =
213 removeUnusedDiscardableAllocations(module, allocatorOpName, dataLayout);
214 if (failed(changed)) {
215 signalPassFailure();
216 return;
217 }
218
219 if (!*changed) {
220 markAllAnalysesPreserved();
221 }
222 }
223};
224
225} // namespace
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition LICENSE.txt:9