LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
SharedImpl.cpp
Go to the documentation of this file.
1//===-- SharedImpl.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//===----------------------------------------------------------------------===//
9
10#include "SharedImpl.h"
11
26
27#include <llvm/ADT/STLExtras.h>
28#include <llvm/ADT/SmallVector.h>
29#include <llvm/Support/Debug.h>
30
31#define DEBUG_TYPE "poly-dialect-shared"
32
33mlir::ConversionTarget llzk::polymorphic::detail::newBaseTarget(mlir::MLIRContext *ctx) {
34 mlir::ConversionTarget target(*ctx);
35 target.addLegalDialect<
40 llzk::string::StringDialect, mlir::arith::ArithDialect, mlir::scf::SCFDialect>();
41 target.addLegalOp<mlir::ModuleOp>();
42 return target;
43}
44
46 mlir::ModuleOp root, const llzk::SymbolDefTree &symDefTree,
47 const llzk::SymbolUseGraph &symUseGraph
48)
49 : rootMod(root), defTree(symDefTree), useGraph(symUseGraph) {}
50
52 if (llvm::isa<llzk::component::StructDefOp>(op)) {
53 return true;
54 }
55 if (llzk::function::FuncDefOp fdef = llvm::dyn_cast<llzk::function::FuncDefOp>(op)) {
56 return !fdef.isInStruct();
57 }
58 return false;
59}
60
62 mlir::ModuleOp root, const llzk::SymbolDefTree &symDefTree,
63 const llzk::SymbolUseGraph &symUseGraph, llvm::DenseSet<mlir::SymbolRefAttr> &&tryToErasePaths
64)
65 : CleanupBase(root, symDefTree, symUseGraph) {
66 // Convert the set of paths targeted for erasure into a set of cleanup-candidate definitions.
67 for (mlir::SymbolRefAttr path : tryToErasePaths) {
68 LLVM_DEBUG(llvm::dbgs() << "[FromEraseSet] path to erase: " << path << '\n';);
69 mlir::Operation *lookupFrom = rootMod.getOperation();
70 auto res = lookupSymbolIn(tables, path, Within(), lookupFrom);
71 assert(mlir::succeeded(res) && "inputs must be valid symbol references");
72 assert(isErasableDefinition(res->get()) && "inputs must be cleanup candidates");
73 if (!res->viaInclude()) { // do not remove if it's from another source file
74 mlir::SymbolOpInterface op = llvm::cast<mlir::SymbolOpInterface>(res->get());
75 LLVM_DEBUG(llvm::dbgs() << "[FromEraseSet] added op to the erase set: " << op << '\n';);
76 tryToErase.insert(op);
77 } else {
78 LLVM_DEBUG(
79 llvm::dbgs() << "[FromEraseSet] ignored op because it comes from an include: "
80 << res->get() << '\n';
81 );
82 }
83 }
84}
85
87 // Collect the subset of 'tryToErase' that has no remaining uses.
88 for (mlir::SymbolOpInterface sym : tryToErase) {
89 collectSafeToErase(sym);
90 }
91 // The `visitedPlusSafetyResult` may contain child FuncDefOp within an erased StructDefOp, so
92 // reduce the map to only top-level erase targets before erasing in a separate loop.
93 for (auto &it : llvm::make_early_inc_range(visitedPlusSafetyResult)) {
94 if (!it.second || !tryToErase.contains(it.first)) {
95 visitedPlusSafetyResult.erase(it.first);
96 }
97 }
98 for (auto &[sym, _] : visitedPlusSafetyResult) {
99 LLVM_DEBUG(llvm::dbgs() << "[EraseIfUnused] removing: " << sym.getNameAttr() << '\n');
100 sym.erase();
101 }
102 return mlir::success();
103}
104
105bool llzk::polymorphic::detail::FromEraseSet::collectSafeToErase(mlir::SymbolOpInterface check) {
106 assert(check); // pre-condition
107
108 // If previously visited, return the safety result.
109 auto visited = visitedPlusSafetyResult.find(check);
110 if (visited != visitedPlusSafetyResult.end()) {
111 return visited->second;
112 }
113
114 // If it's an erasable definition that is not in `tryToErase` then it cannot be erased.
115 if (isErasableDefinition(check.getOperation()) && !tryToErase.contains(check)) {
116 visitedPlusSafetyResult[check] = false;
117 return false;
118 }
119
120 // Otherwise, temporarily mark as safe b/c a node cannot keep itself live (and this prevents
121 // the recursion from getting stuck in an infinite loop).
122 visitedPlusSafetyResult[check] = true;
123
124 // Check if it's safe according to both the def tree and use graph.
125 // Note: Every symbol must have a def node, but symbols with no references do not have use
126 // nodes. Those are safe from the use-graph perspective.
127 if (collectSafeToErase(defTree.lookupNode(check))) {
128 const auto *useNode = useGraph.lookupNode(check);
129 if (!useNode || collectSafeToErase(useNode)) {
130 return true;
131 }
132 }
133
134 // Otherwise, revert the safety decision and return it.
135 visitedPlusSafetyResult[check] = false;
136 return false;
137}
138
139bool llzk::polymorphic::detail::FromEraseSet::collectSafeToErase(
140 const llzk::SymbolDefTreeNode *check
141) {
142 assert(check); // pre-condition
143 if (const llzk::SymbolDefTreeNode *p = check->getParent()) {
144 if (mlir::SymbolOpInterface checkOp = p->getOp()) { // safe if parent is root
145 return collectSafeToErase(checkOp);
146 }
147 }
148 return true;
149}
150
151bool llzk::polymorphic::detail::FromEraseSet::collectSafeToErase(
152 const llzk::SymbolUseGraphNode *check
153) {
154 assert(check); // pre-condition
155 for (const llzk::SymbolUseGraphNode *p : check->predecessorIter()) {
156 if (mlir::SymbolOpInterface checkOp = cachedLookup(p)) { // safe if via IncludeOp
157 if (!collectSafeToErase(checkOp)) {
158 return false;
159 }
160 }
161 }
162 return true;
163}
164
165mlir::SymbolOpInterface
166llzk::polymorphic::detail::FromEraseSet::cachedLookup(const llzk::SymbolUseGraphNode *node) {
167 assert(node && "must provide a node"); // pre-condition
168 // Check for cached result
169 auto fromCache = lookupCache.find(node);
170 if (fromCache != lookupCache.end()) {
171 return fromCache->second;
172 }
173 // Otherwise, perform lookup and cache
174 auto lookupRes = node->lookupSymbol(tables);
175 assert(mlir::succeeded(lookupRes) && "graph contains node with invalid path");
176 assert(lookupRes->get() != nullptr && "lookup must return an Operation");
177 // If loaded via an IncludeOp it's not in the current AST anyway so ignore.
178 // NOTE: The SymbolUseGraph does contain nodes for struct parameters which cannot cast to
179 // SymbolOpInterface. However, those will always be leaf nodes in the SymbolUseGraph and
180 // therefore will not be traversed by this analysis so directly casting is fine.
181 mlir::SymbolOpInterface actualRes =
182 lookupRes->viaInclude() ? nullptr : llvm::cast<mlir::SymbolOpInterface>(lookupRes->get());
183 // Cache and return
184 lookupCache[node] = actualRes;
185 assert((!actualRes == lookupRes->viaInclude()) && "not found iff included"); // post-condition
186 return actualRes;
187}
188
190 llzk::array::ArrayType inputTy, mlir::Type convertedElemTy
191) {
192 llvm::SmallVector<mlir::Attribute> mergedDims(inputTy.getDimensionSizes());
193 while (auto nestedArrTy = llvm::dyn_cast<llzk::array::ArrayType>(convertedElemTy)) {
194 llvm::append_range(mergedDims, nestedArrTy.getDimensionSizes());
195 convertedElemTy = nestedArrTy.getElementType();
196 }
197 return llzk::array::ArrayType::get(convertedElemTy, mergedDims);
198}
199
200#undef DEBUG_TYPE
#define check(x)
Definition Ops.cpp:286
Common private implementation for poly dialect passes.
This file defines methods symbol lookup across LLZK operations and included files.
Builds a tree structure representing the symbol table structure.
mlir::FailureOr< SymbolLookupResultUntyped > lookupSymbol(mlir::SymbolTableCollection &tables, bool reportMissing=true) const
Builds a graph structure representing the relationships between symbols and their uses.
static ArrayType get(::mlir::Type elementType, ::llvm::ArrayRef<::mlir::Attribute > dimensionSizes)
Definition Types.cpp.inc:83
::llvm::ArrayRef<::mlir::Attribute > getDimensionSizes() const
const SymbolUseGraph & useGraph
Definition SharedImpl.h:78
mlir::SymbolTableCollection tables
Definition SharedImpl.h:69
CleanupBase(mlir::ModuleOp root, const SymbolDefTree &symDefTree, const SymbolUseGraph &symUseGraph)
mlir::LogicalResult eraseUnusedDefinitions()
FromEraseSet(mlir::ModuleOp root, const SymbolDefTree &symDefTree, const SymbolUseGraph &symUseGraph, llvm::DenseSet< mlir::SymbolRefAttr > &&tryToErasePaths)
Note: paths in tryToErase should be relative to root.
bool isErasableDefinition(mlir::Operation *op)
Return true iff op is a cleanup candidate.
mlir::ConversionTarget newBaseTarget(mlir::MLIRContext *ctx)
Return a new ConversionTarget allowing all LLZK-required dialects.
array::ArrayType flattenInstantiatedArrayType(array::ArrayType inputTy, mlir::Type convertedElemTy)
Merge nested array dimensions produced by replacing an array element type.
mlir::FailureOr< SymbolLookupResultUntyped > lookupSymbolIn(mlir::SymbolTableCollection &tables, mlir::SymbolRefAttr symbol, Within &&lookupWithin, mlir::Operation *origin, bool reportMissing=true)