LLZK 3.0.0
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 return allocator->emitOpError()
61 << "selected for discardable-allocation cleanup but does not have one result";
62 }
63
64 Value allocation = allocator->getResult(0);
65 if (allocation.use_empty()) {
66 return true;
67 }
68
69 for (OpOperand &use : allocation.getUses()) {
70 Operation *user = use.getOwner();
71 if (user->mightHaveTrait<OpTrait::IsTerminator>()) {
72 return false;
73 }
74
75 auto accessor = llvm::dyn_cast<DiscardableAllocationAccessorOpInterface>(user);
76 if (!accessor || accessor.loadsFromDiscardableAllocation(allocation) ||
77 !accessor.storesToDiscardableAllocation(allocation) ||
78 !accessor.canEraseAsDeadStoreTo(allocation, dataLayout)) {
79 return false;
80 }
81 usersToErase.push_back(user);
82 }
83
84 return true;
85}
86
88static void collectOperandDefiningOps(
89 llvm::ArrayRef<Operation *> opsToErase,
90 const llvm::SmallPtrSetImpl<Operation *> &opsBeingErased,
91 SmallVectorImpl<Operation *> &maybeDeadDefs
92) {
93 llvm::SmallPtrSet<Operation *, 16> seenDefs;
94 for (Operation *op : opsToErase) {
95 for (Value operand : op->getOperands()) {
96 Operation *definingOp = operand.getDefiningOp();
97 if (definingOp && !opsBeingErased.contains(definingOp) &&
98 seenDefs.insert(definingOp).second) {
99 maybeDeadDefs.push_back(definingOp);
100 }
101 }
102 }
103}
104
106static bool eraseTriviallyDeadDefs(SmallVectorImpl<Operation *> &maybeDeadDefs) {
107 bool changed = false;
108 bool changedThisIteration = false;
109 llvm::SmallPtrSet<Operation *, 16> seen(maybeDeadDefs.begin(), maybeDeadDefs.end());
110 llvm::SmallPtrSet<Operation *, 16> erased;
111
112 do {
113 changedThisIteration = false;
114 for (size_t i = 0; i < maybeDeadDefs.size(); ++i) {
115 Operation *op = maybeDeadDefs[i];
116 if (erased.contains(op) || !isOpTriviallyDead(op)) {
117 continue;
118 }
119 for (Value operand : op->getOperands()) {
120 if (Operation *definingOp = operand.getDefiningOp()) {
121 if (seen.insert(definingOp).second) {
122 maybeDeadDefs.push_back(definingOp);
123 }
124 }
125 }
126 op->erase();
127 erased.insert(op);
128 changedThisIteration = true;
129 }
130 changed |= changedThisIteration;
131 } while (changedThisIteration);
132
133 return changed;
134}
135
139static FailureOr<bool> removeUnusedDiscardableAllocations(
140 ModuleOp module, StringRef allocatorOpName, const DataLayout &dataLayout
141) {
142 bool changed = false;
143 bool changedThisIteration;
144 do {
145 SmallVector<Operation *> opsToErase;
146 SmallVector<Operation *> maybeDeadDefs;
147 llvm::SmallPtrSet<Operation *, 16> seen;
148
149 auto walkResult = module->walk([&](Operation *allocator) -> WalkResult {
150 if (allocator->getName().getStringRef() != allocatorOpName) {
151 return WalkResult::advance();
152 }
153 if (!hasDiscardableAllocationEffect(allocator)) {
154 return allocator->emitOpError(
155 "selected for discardable-allocation cleanup but is not "
156 "marked with MemAlloc<DiscardableAllocationResource>"
157 );
158 }
159
160 SmallVector<Operation *> usersToErase;
161 FailureOr<bool> canRemove = collectRemovableUsers(allocator, usersToErase, dataLayout);
162 if (failed(canRemove)) {
163 return WalkResult::interrupt();
164 }
165 if (!*canRemove) {
166 return WalkResult::advance();
167 }
168 for (Operation *user : usersToErase) {
169 if (seen.insert(user).second) {
170 opsToErase.push_back(user);
171 }
172 }
173 if (seen.insert(allocator).second) {
174 opsToErase.push_back(allocator);
175 }
176 return WalkResult::advance();
177 });
178 if (walkResult.wasInterrupted()) {
179 return failure();
180 }
181
182 collectOperandDefiningOps(opsToErase, seen, maybeDeadDefs);
183 for (Operation *op : opsToErase) {
184 op->erase();
185 }
186
187 changedThisIteration = !opsToErase.empty();
188 changedThisIteration |= eraseTriviallyDeadDefs(maybeDeadDefs);
189 changed |= changedThisIteration;
190 } while (changedThisIteration);
191
192 return changed;
193}
194
195class PassImpl : public llzk::impl::RemoveUnusedDiscardableAllocationsPassBase<PassImpl> {
196 using Base = RemoveUnusedDiscardableAllocationsPassBase<PassImpl>;
197 using Base::Base;
198
199 void runOnOperation() override {
200 ModuleOp module = getOperation();
201 if (allocatorOpName.empty()) {
202 module.emitError("requires non-empty 'allocator-op' option");
203 signalPassFailure();
204 return;
205 }
206
207 auto &dataLayoutAnalysis = getAnalysis<DataLayoutAnalysis>();
208 const DataLayout &dataLayout = dataLayoutAnalysis.getAtOrAbove(module);
209
210 FailureOr<bool> changed =
211 removeUnusedDiscardableAllocations(module, allocatorOpName, dataLayout);
212 if (failed(changed)) {
213 signalPassFailure();
214 return;
215 }
216
217 if (!*changed) {
218 markAllAnalysesPreserved();
219 }
220 }
221};
222
223} // namespace
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition LICENSE.txt:9