LLZK 2.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
LLZKUnusedDeclarationEliminationPass.cpp
Go to the documentation of this file.
1//===-- LLZKUnusedDeclarationEliminationPass.cpp ----------------*- C++ -*-===//
2//
3// Part of the LLZK Project, under the Apache License v2.0.
4// See LICENSE.txt for license information.
5// Copyright 2025 Veridise Inc.
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
13//===----------------------------------------------------------------------===//
14
19
20#include <mlir/IR/BuiltinOps.h>
21
22#include <llvm/ADT/SmallVector.h>
23#include <llvm/Support/Debug.h>
24
25// Include the generated base pass class definitions.
26namespace llzk {
27// the *DECL* macro is required when a pass has options to declare the option struct
28#define GEN_PASS_DECL_UNUSEDDECLARATIONELIMINATIONPASS
29#define GEN_PASS_DEF_UNUSEDDECLARATIONELIMINATIONPASS
31} // namespace llzk
32
33using namespace mlir;
34using namespace llzk;
35using namespace llzk::component;
36
37#define DEBUG_TYPE "llzk-unused-declaration-elim"
38
39namespace {
40
42SymbolRefAttr getFullMemberSymbol(MemberRefOpInterface op) {
43 SymbolRefAttr structSym = op.getStructType().getNameRef(); // this is fully qualified
44 return appendLeaf(structSym, op.getMemberNameAttr());
45}
46
47class UnusedDeclarationEliminationPass
48 : public llzk::impl::UnusedDeclarationEliminationPassBase<UnusedDeclarationEliminationPass> {
49
52 struct PassContext {
53 DenseMap<SymbolRefAttr, StructDefOp> symbolToStruct;
54 DenseMap<StructDefOp, SymbolRefAttr> structToSymbol;
55
56 const SymbolRefAttr &getSymbol(StructDefOp s) const { return structToSymbol.at(s); }
57 StructDefOp getStruct(const SymbolRefAttr &sym) const { return symbolToStruct.at(sym); }
58
59 static PassContext populate(ModuleOp modOp) {
60 PassContext ctx;
61
62 modOp.walk<WalkOrder::PreOrder>([&ctx](StructDefOp structDef) {
63 auto structSymbolRes = getPathFromTopRoot(structDef);
64 ensure(succeeded(structSymbolRes), "failed to lookup struct symbol");
65 SymbolRefAttr structSym = *structSymbolRes;
66 ctx.symbolToStruct[structSym] = structDef;
67 ctx.structToSymbol[structDef] = structSym;
68 });
69 return ctx;
70 }
71 };
72
73 void runOnOperation() override {
74 PassContext ctx = PassContext::populate(getOperation());
75 // First, remove unused members. This may allow more structs to be removed,
76 // if their final remaining uses are as types for unused members.
77 removeUnusedMembers(ctx);
78
79 // Last, remove unused structs if configured
80 if (removeStructs) {
81 removeUnusedStructs(ctx);
82 }
83 }
84
88 void removeUnusedMembers(PassContext &ctx) {
89 ModuleOp modOp = getOperation();
90
91 // Map fully-qualified member symbols -> member ops
92 DenseMap<SymbolRefAttr, MemberDefOp> members;
93 for (auto &[structDef, structSym] : ctx.structToSymbol) {
94 structDef.walk([&](MemberDefOp member) {
95 // We don't consider public members in the Main component for removal,
96 // as these are output values and removing them would result in modifying
97 // the overall circuit interface.
98 // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)
99 if (!structDef.isMainComponent() || !member.hasPublicAttr()) {
100 SymbolRefAttr memberSym =
101 appendLeaf(structSym, FlatSymbolRefAttr::get(member.getSymNameAttr()));
102 members[memberSym] = member;
103 }
104 });
105 }
106
107 // Remove all members that are read.
108 modOp.walk([&](MemberReadOp readm) {
109 SymbolRefAttr readMemberSym = getFullMemberSymbol(readm);
110 members.erase(readMemberSym);
111 });
112
113 // Remove all writes that reference the remaining members, as these writes
114 // are now known to only update write-only members.
115 modOp.walk([&](MemberWriteOp writem) {
116 SymbolRefAttr writtenMember = getFullMemberSymbol(writem);
117 if (members.contains(writtenMember)) {
118 // We need not check the users of a writem, since it produces no results.
119 LLVM_DEBUG(
120 llvm::dbgs() << "Removing write " << writem << " to write-only member " << writtenMember
121 << '\n'
122 );
123 writem.erase();
124 }
125 });
126
127 // Finally, erase the remaining members.
128 for (auto &[_, memberDef] : members) {
129 LLVM_DEBUG(llvm::dbgs() << "Removing member " << memberDef << '\n');
130 memberDef->erase();
131 }
132 }
133
138 void removeUnusedStructs(PassContext &ctx) {
139 DenseMap<StructDefOp, DenseSet<StructDefOp>> uses;
140 DenseMap<StructDefOp, DenseSet<StructDefOp>> usedBy;
141
142 // initialize both maps with empty sets so we can identify unused structs
143 for (auto &[structDef, _] : ctx.structToSymbol) {
144 uses[structDef] = {};
145 usedBy[structDef] = {};
146 }
147
148 getOperation().walk([&](Operation *op) {
149 auto structParent = op->getParentOfType<StructDefOp>();
150 if (structParent == nullptr) {
151 return WalkResult::advance();
152 }
153
154 auto tryAddUse = [&](Type ty) {
155 if (auto structTy = dyn_cast<StructType>(ty)) {
156 // This name ref is required to be fully qualified
157 SymbolRefAttr sym = structTy.getNameRef();
158 StructDefOp refStruct = ctx.getStruct(sym);
159 if (refStruct != structParent) {
160 uses[structParent].insert(refStruct);
161 usedBy[refStruct].insert(structParent);
162 }
163 }
164 };
165
166 // LLZK requires fully-qualified references to struct symbols. So, we
167 // simply need to look for the struct symbol within this op's symbol uses.
168
169 // Check operands
170 for (Value operand : op->getOperands()) {
171 tryAddUse(operand.getType());
172 }
173
174 // Check results
175 for (Value result : op->getResults()) {
176 tryAddUse(result.getType());
177 }
178
179 // Check block arguments
180 for (Region &region : op->getRegions()) {
181 for (Block &block : region) {
182 for (BlockArgument arg : block.getArguments()) {
183 tryAddUse(arg.getType());
184 }
185 }
186 }
187
188 // Check attributes
189 for (const auto &namedAttr : op->getAttrs()) {
190 namedAttr.getValue().walk([&](TypeAttr typeAttr) { tryAddUse(typeAttr.getValue()); });
191 }
192
193 return WalkResult::advance();
194 });
195
196 SmallVector<StructDefOp> unusedStructs;
197
198 auto updateUnusedStructs = [&]() {
199 for (auto &[structDef, users] : usedBy) {
200 if (users.empty() && !structDef.isMainComponent()) {
201 unusedStructs.push_back(structDef);
202 }
203 }
204 };
205
206 updateUnusedStructs();
207
208 while (!unusedStructs.empty()) {
209 StructDefOp unusedStruct = unusedStructs.back();
210 unusedStructs.pop_back();
211
212 // See what structs are being used by this unused struct
213 for (auto usedStruct : uses[unusedStruct]) {
214 // The usedStruct is no longer used by the unusedStruct
215 usedBy[usedStruct].erase(unusedStruct);
216 }
217
218 // Remove the unused struct from both maps and the IR
219 usedBy.erase(unusedStruct);
220 uses.erase(unusedStruct);
221 unusedStruct->erase();
222
223 // Check to see if we've created any more unused structs after we process
224 // all existing known unused structs (to avoid double processing).
225 if (unusedStructs.empty()) {
226 updateUnusedStructs();
227 }
228 }
229 }
230};
231
232} // namespace
233
234std::unique_ptr<mlir::Pass> llzk::createUnusedDeclarationEliminationPass() {
235 return std::make_unique<UnusedDeclarationEliminationPass>();
236};
This file defines methods symbol lookup across LLZK operations and included files.
::mlir::StringAttr getSymNameAttr()
Definition Ops.h.inc:386
::mlir::FlatSymbolRefAttr getMemberNameAttr()
Gets the member name attribute from the MemberRefOp.
::llzk::component::StructType getStructType()
Gets the struct type of the target component.
::mlir::SymbolRefAttr getNameRef() const
void ensure(bool condition, const llvm::Twine &errMsg)
SymbolRefAttr appendLeaf(SymbolRefAttr orig, FlatSymbolRefAttr newLeaf)
std::unique_ptr< mlir::Pass > createUnusedDeclarationEliminationPass()
FailureOr< SymbolRefAttr > getPathFromTopRoot(SymbolOpInterface to, ModuleOp *foundRoot)