LLZK 0.1.0
Veridise's ZK Language IR
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 getFullFieldSymbol(FieldRefOpInterface op) {
43 SymbolRefAttr structSym = op.getStructType().getNameRef(); // this is fully qualified
44 return appendLeaf(structSym, op.getFieldNameAttr());
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 fields. This may allow more structs to be removed,
76 // if their final remaining uses are as types for unused fields.
77 removeUnusedFields(ctx);
78
79 // Last, remove unused structs if configured
80 if (removeStructs) {
81 removeUnusedStructs(ctx);
82 }
83 }
84
88 void removeUnusedFields(PassContext &ctx) {
89 ModuleOp modOp = getOperation();
90
91 // Map fully-qualified field symbols -> field ops
92 DenseMap<SymbolRefAttr, FieldDefOp> fields;
93 for (auto &[structDef, structSym] : ctx.structToSymbol) {
94 structDef.walk([&](FieldDefOp field) {
95 // We don't consider public fields 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 if (!structDef.isMainComponent() || !field.hasPublicAttr()) {
99 SymbolRefAttr fieldSym =
100 appendLeaf(structSym, FlatSymbolRefAttr::get(field.getSymNameAttr()));
101 fields[fieldSym] = field;
102 }
103 });
104 }
105
106 // Remove all fields that are read.
107 modOp.walk([&](FieldReadOp readf) {
108 SymbolRefAttr readFieldSym = getFullFieldSymbol(readf);
109 fields.erase(readFieldSym);
110 });
111
112 // Remove all writes that reference the remaining fields, as these writes
113 // are now known to only update write-only fields.
114 modOp.walk([&](FieldWriteOp writef) {
115 SymbolRefAttr writtenField = getFullFieldSymbol(writef);
116 if (fields.contains(writtenField)) {
117 // We need not check the users of a writef, since it produces no results.
118 LLVM_DEBUG(
119 llvm::dbgs() << "Removing write " << writef << " to write-only field " << writtenField
120 << '\n'
121 );
122 writef.erase();
123 }
124 });
125
126 // Finally, erase the remaining fields.
127 for (auto &[_, fieldDef] : fields) {
128 LLVM_DEBUG(llvm::dbgs() << "Removing field " << fieldDef << '\n');
129 fieldDef->erase();
130 }
131 }
132
137 void removeUnusedStructs(PassContext &ctx) {
138 DenseMap<StructDefOp, DenseSet<StructDefOp>> uses;
139 DenseMap<StructDefOp, DenseSet<StructDefOp>> usedBy;
140
141 // initialize both maps with empty sets so we can identify unused structs
142 for (auto &[structDef, _] : ctx.structToSymbol) {
143 uses[structDef] = {};
144 usedBy[structDef] = {};
145 }
146
147 getOperation().walk([&](Operation *op) {
148 auto structParent = op->getParentOfType<StructDefOp>();
149 if (structParent == nullptr) {
150 return WalkResult::advance();
151 }
152
153 auto tryAddUse = [&](Type ty) {
154 if (auto structTy = dyn_cast<StructType>(ty)) {
155 // This name ref is required to be fully qualified
156 SymbolRefAttr sym = structTy.getNameRef();
157 StructDefOp refStruct = ctx.getStruct(sym);
158 if (refStruct != structParent) {
159 uses[structParent].insert(refStruct);
160 usedBy[refStruct].insert(structParent);
161 }
162 }
163 };
164
165 // LLZK requires fully-qualified references to struct symbols. So, we
166 // simply need to look for the struct symbol within this op's symbol uses.
167
168 // Check operands
169 for (Value operand : op->getOperands()) {
170 tryAddUse(operand.getType());
171 }
172
173 // Check results
174 for (Value result : op->getResults()) {
175 tryAddUse(result.getType());
176 }
177
178 // Check block arguments
179 for (Region &region : op->getRegions()) {
180 for (Block &block : region) {
181 for (BlockArgument arg : block.getArguments()) {
182 tryAddUse(arg.getType());
183 }
184 }
185 }
186
187 // Check attributes
188 for (const auto &namedAttr : op->getAttrs()) {
189 namedAttr.getValue().walk([&](TypeAttr typeAttr) { tryAddUse(typeAttr.getValue()); });
190 }
191
192 return WalkResult::advance();
193 });
194
195 SmallVector<StructDefOp> unusedStructs;
196
197 auto updateUnusedStructs = [&]() {
198 for (auto &[structDef, users] : usedBy) {
199 if (users.empty() && !structDef.isMainComponent()) {
200 unusedStructs.push_back(structDef);
201 }
202 }
203 };
204
205 updateUnusedStructs();
206
207 while (!unusedStructs.empty()) {
208 StructDefOp unusedStruct = unusedStructs.back();
209 unusedStructs.pop_back();
210
211 // See what structs are being used by this unused struct
212 for (auto usedStruct : uses[unusedStruct]) {
213 // The usedStruct is no longer used by the unusedStruct
214 usedBy[usedStruct].erase(unusedStruct);
215 }
216
217 // Remove the unused struct from both maps and the IR
218 usedBy.erase(unusedStruct);
219 uses.erase(unusedStruct);
220 unusedStruct->erase();
221
222 // Check to see if we've created any more unused structs after we process
223 // all existing known unused structs (to avoid double processing).
224 if (unusedStructs.empty()) {
225 updateUnusedStructs();
226 }
227 }
228 }
229};
230
231} // namespace
232
233std::unique_ptr<mlir::Pass> llzk::createUnusedDeclarationEliminationPass() {
234 return std::make_unique<UnusedDeclarationEliminationPass>();
235};
This file defines methods symbol lookup across LLZK operations and included files.
::mlir::StringAttr getSymNameAttr()
Definition Ops.cpp.inc:576
::llzk::component::StructType getStructType()
Gets the struct type of the target component.
::mlir::FlatSymbolRefAttr getFieldNameAttr()
Gets the field name attribute from the FieldRefOp.
::mlir::SymbolRefAttr getNameRef() const
SymbolRefAttr appendLeaf(SymbolRefAttr orig, FlatSymbolRefAttr newLeaf)
void ensure(bool condition, llvm::Twine errMsg)
Definition ErrorHelper.h:32
FailureOr< SymbolRefAttr > getPathFromTopRoot(StructDefOp &to)
std::unique_ptr< mlir::Pass > createUnusedDeclarationEliminationPass()