LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
SymbolTableLLZK.cpp
Go to the documentation of this file.
1//===-- SymbolTableLLZK.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// Adapted from the LLVM Project's mlir/lib/IR/SymbolTable.cpp
9// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
10// See https://llvm.org/LICENSE.txt for license information.
11// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
12//
13//===----------------------------------------------------------------------===//
34//===----------------------------------------------------------------------===//
35
37
38#include <llvm/ADT/SmallPtrSet.h>
39
40using namespace mlir;
41
42//===----------------------------------------------------------------------===//
43// Symbol Use Lists
44//===----------------------------------------------------------------------===//
45
46namespace {
47
50static bool isPotentiallyUnknownSymbolTable(Operation *op) {
51 return op->getNumRegions() == 1 && !op->getDialect();
52}
53
55static StringAttr getNameIfSymbol(Operation *op, StringAttr symbolAttrNameId) {
56 return op->getAttrOfType<StringAttr>(symbolAttrNameId);
57}
58
63static LogicalResult collectValidReferencesFor(
64 Operation *symbol, StringAttr symbolName, Operation *within,
65 SmallVectorImpl<SymbolRefAttr> &results
66) {
67 assert(within->isAncestor(symbol) && "expected 'within' to be an ancestor");
68 MLIRContext *ctx = symbol->getContext();
69
70 auto leafRef = FlatSymbolRefAttr::get(symbolName);
71 results.push_back(leafRef);
72
73 // Early exit for when 'within' is the parent of 'symbol'.
74 Operation *symbolTableOp = symbol->getParentOp();
75 if (within == symbolTableOp) {
76 return success();
77 }
78
79 // Collect references until 'symbolTableOp' reaches 'within'.
80 SmallVector<FlatSymbolRefAttr, 1> nestedRefs(1, leafRef);
81 StringAttr symbolNameId = StringAttr::get(ctx, SymbolTable::getSymbolAttrName());
82 do {
83 // Each parent of 'symbol' should define a symbol table.
84 if (!symbolTableOp->hasTrait<OpTrait::SymbolTable>()) {
85 return failure();
86 }
87 // Each parent of 'symbol' should also be a symbol.
88 StringAttr symbolTableName = getNameIfSymbol(symbolTableOp, symbolNameId);
89 if (!symbolTableName) {
90 return failure();
91 }
92 results.push_back(SymbolRefAttr::get(symbolTableName, nestedRefs));
93
94 symbolTableOp = symbolTableOp->getParentOp();
95 if (symbolTableOp == within) {
96 break;
97 }
98 nestedRefs.insert(nestedRefs.begin(), FlatSymbolRefAttr::get(symbolTableName));
99 } while (true);
100 return success();
101}
102
106static std::optional<WalkResult> walkSymbolTable(
107 MutableArrayRef<Region> regions, function_ref<std::optional<WalkResult>(Operation *)> callback
108) {
109 SmallVector<Region *, 1> worklist(llvm::make_pointer_range(regions));
110 while (!worklist.empty()) {
111 for (Operation &op : worklist.pop_back_val()->getOps()) {
112 std::optional<WalkResult> result = callback(&op);
113 if (result != WalkResult::advance()) {
114 return result;
115 }
116
117 // If this op defines a new symbol table scope, we can't traverse. Any
118 // symbol references nested within 'op' are different semantically.
119 if (!op.hasTrait<OpTrait::SymbolTable>()) {
120 for (Region &region : op.getRegions()) {
121 worklist.push_back(&region);
122 }
123 }
124 }
125 }
126 return WalkResult::advance();
127}
128
131static WalkResult
132walkSymbolRefs(Operation *op, function_ref<WalkResult(SymbolTable::SymbolUse)> callback) {
133 // This is modified for LLZK.
134 auto walkFn = [&op, &callback](SymbolRefAttr symbolRef) {
135 if (callback({op, symbolRef}).wasInterrupted()) {
136 return WalkResult::interrupt();
137 }
138 return WalkResult::skip(); // Don't walk nested references.
139 };
140 for (Type t : op->getOperandTypes()) {
141 if (t.walk<WalkOrder::PreOrder>(walkFn).wasInterrupted()) {
142 return WalkResult::interrupt();
143 }
144 }
145 for (Type t : op->getResultTypes()) {
146 if (t.walk<WalkOrder::PreOrder>(walkFn).wasInterrupted()) {
147 return WalkResult::interrupt();
148 }
149 }
150 return op->getAttrDictionary().walk<WalkOrder::PreOrder>(walkFn);
151}
152
156static std::optional<WalkResult> walkSymbolUses(
157 MutableArrayRef<Region> regions, function_ref<WalkResult(SymbolTable::SymbolUse)> callback
158) {
159 return walkSymbolTable(regions, [&](Operation *op) -> std::optional<WalkResult> {
160 // Check that this isn't a potentially unknown symbol table.
161 if (isPotentiallyUnknownSymbolTable(op)) {
162 return std::nullopt;
163 }
164 return walkSymbolRefs(op, callback);
165 });
166}
167
171static std::optional<WalkResult>
172walkSymbolUses(Operation *from, function_ref<WalkResult(SymbolTable::SymbolUse)> callback) {
173 // If this operation has regions, and it, as well as its dialect, isn't
174 // registered then conservatively fail. The operation may define a
175 // symbol table, so we can't opaquely know if we should traverse to find
176 // nested uses.
177 if (isPotentiallyUnknownSymbolTable(from)) {
178 return std::nullopt;
179 }
180
181 // Walk the uses on this operation.
182 if (walkSymbolRefs(from, callback).wasInterrupted()) {
183 return WalkResult::interrupt();
184 }
185
186 // Only recurse if this operation is not a symbol table. A symbol table
187 // defines a new scope, so we can't walk the attributes from within the symbol
188 // table op.
189 if (!from->hasTrait<OpTrait::SymbolTable>()) {
190 return walkSymbolUses(from->getRegions(), callback);
191 }
192 return WalkResult::advance();
193}
194
200struct SymbolScope {
204 template <
205 typename CallbackT,
206 std::enable_if_t<!std::is_same<
207 typename llvm::function_traits<CallbackT>::result_t, void>::value> * = nullptr>
208 std::optional<WalkResult> walk(CallbackT cback) {
209 if (Region *region = llvm::dyn_cast_if_present<Region *>(limit)) {
210 return walkSymbolUses(*region, cback);
211 }
212 return walkSymbolUses(llvm::cast<Operation *>(limit), cback);
213 }
216 template <
217 typename CallbackT,
218 std::enable_if_t<std::is_same<
219 typename llvm::function_traits<CallbackT>::result_t, void>::value> * = nullptr>
220 std::optional<WalkResult> walk(CallbackT cback) {
221 return walk([=](SymbolTable::SymbolUse use) { return cback(use), WalkResult::advance(); });
222 }
223
226 template <typename CallbackT>
227 [[maybe_unused]]
228 std::optional<WalkResult> walkSymbolTable(CallbackT &&cback) {
229 if (Region *region = llvm::dyn_cast_if_present<Region *>(limit)) {
230 return ::walkSymbolTable(*region, cback);
231 }
232 return ::walkSymbolTable(llvm::cast<Operation *>(limit), cback);
233 }
234
236 SymbolRefAttr symbol;
237
239 llvm::PointerUnion<Operation *, Region *> limit;
240};
241
243static SmallVector<SymbolScope, 2> collectSymbolScopes(Operation *symbol, Operation *limit) {
244 StringAttr symName = SymbolTable::getSymbolName(symbol);
245 assert(!symbol->hasTrait<OpTrait::SymbolTable>() || symbol != limit);
246
247 // Compute the ancestors of 'limit'.
248 SetVector<Operation *, SmallVector<Operation *, 4>, SmallPtrSet<Operation *, 4>> limitAncestors;
249 Operation *limitAncestor = limit;
250 do {
251 // Check to see if 'symbol' is an ancestor of 'limit'.
252 if (limitAncestor == symbol) {
253 // Check that the nearest symbol table is 'symbol's parent. SymbolRefAttr
254 // doesn't support parent references.
255 if (SymbolTable::getNearestSymbolTable(limit->getParentOp()) == symbol->getParentOp()) {
256 return {{SymbolRefAttr::get(symName), limit}};
257 }
258 return {};
259 }
260
261 limitAncestors.insert(limitAncestor);
262 } while ((limitAncestor = limitAncestor->getParentOp()));
263
264 // Try to find the first ancestor of 'symbol' that is an ancestor of 'limit'.
265 Operation *commonAncestor = symbol->getParentOp();
266 do {
267 if (limitAncestors.count(commonAncestor)) {
268 break;
269 }
270 } while ((commonAncestor = commonAncestor->getParentOp()));
271 assert(commonAncestor && "'limit' and 'symbol' have no common ancestor");
272
273 // Compute the set of valid nested references for 'symbol' as far up to the
274 // common ancestor as possible.
275 SmallVector<SymbolRefAttr, 2> references;
276 bool collectedAllReferences =
277 succeeded(collectValidReferencesFor(symbol, symName, commonAncestor, references));
278
279 // Handle the case where the common ancestor is 'limit'.
280 if (commonAncestor == limit) {
281 SmallVector<SymbolScope, 2> scopes;
282
283 // Walk each of the ancestors of 'symbol', calling the compute function for
284 // each one.
285 Operation *limitIt = symbol->getParentOp();
286 for (size_t i = 0, e = references.size(); i != e; ++i, limitIt = limitIt->getParentOp()) {
287 assert(limitIt->hasTrait<OpTrait::SymbolTable>());
288 scopes.push_back({references[i], &limitIt->getRegion(0)});
289 }
290 return scopes;
291 }
292
293 // Otherwise, we just need the symbol reference for 'symbol' that will be
294 // used within 'limit'. This is the last reference in the list we computed
295 // above if we were able to collect all references.
296 if (!collectedAllReferences) {
297 return {};
298 }
299 return {{references.back(), limit}};
300}
301
302static SmallVector<SymbolScope, 2> collectSymbolScopes(Operation *symbol, Region *limit) {
303 auto scopes = collectSymbolScopes(symbol, limit->getParentOp());
304
305 // If we collected some scopes to walk, make sure to constrain the one for
306 // limit to the specific region requested.
307 if (!scopes.empty()) {
308 scopes.back().limit = limit;
309 }
310 return scopes;
311}
312
313static SmallVector<SymbolScope, 1> collectSymbolScopes(StringAttr symbol, Region *limit) {
314 return {{SymbolRefAttr::get(symbol), limit}};
315}
316
317static SmallVector<SymbolScope, 1> collectSymbolScopes(StringAttr symbol, Operation *limit) {
318 SmallVector<SymbolScope, 1> scopes;
319 auto symbolRef = SymbolRefAttr::get(symbol);
320 for (auto &region : limit->getRegions()) {
321 scopes.push_back({symbolRef, &region});
322 }
323 return scopes;
324}
325
328static bool isReferencePrefixOf(SymbolRefAttr subRef, SymbolRefAttr ref) {
329 if (ref == subRef) {
330 return true;
331 }
332
333 // If the references are not pointer equal, check to see if `subRef` is a
334 // prefix of `ref`.
335 if (llvm::isa<FlatSymbolRefAttr>(ref) || ref.getRootReference() != subRef.getRootReference()) {
336 return false;
337 }
338
339 auto refLeafs = ref.getNestedReferences();
340 auto subRefLeafs = subRef.getNestedReferences();
341 return subRefLeafs.size() < refLeafs.size() &&
342 subRefLeafs == refLeafs.take_front(subRefLeafs.size());
343}
344
345} // namespace
346
347//===----------------------------------------------------------------------===//
348// llzk::getSymbolUses
349
350namespace {
351
353template <typename FromT>
354static std::optional<SymbolTable::UseRange> getSymbolUsesImpl(FromT from) {
355 std::vector<SymbolTable::SymbolUse> uses;
356 auto walkFn = [&](SymbolTable::SymbolUse symbolUse) {
357 uses.push_back(symbolUse);
358 return WalkResult::advance();
359 };
360 auto result = walkSymbolUses(from, walkFn);
361 return result ? std::optional<SymbolTable::UseRange>(std::move(uses)) : std::nullopt;
362}
363
364} // namespace
365
373std::optional<SymbolTable::UseRange> llzk::getSymbolUses(Operation *from) {
374 return getSymbolUsesImpl(from);
375}
376std::optional<SymbolTable::UseRange> llzk::getSymbolUses(Region *from) {
377 return getSymbolUsesImpl(MutableArrayRef<Region>(*from));
378}
379
380//===----------------------------------------------------------------------===//
381// llzk::getSymbolUses
382
383namespace {
384
386template <typename SymbolT, typename IRUnitT>
387static std::optional<SymbolTable::UseRange> getSymbolUsesImpl(SymbolT symbol, IRUnitT *limit) {
388 std::vector<SymbolTable::SymbolUse> uses;
389 for (SymbolScope &scope : collectSymbolScopes(symbol, limit)) {
390 if (!scope.walk([&](SymbolTable::SymbolUse symbolUse) {
391 if (isReferencePrefixOf(scope.symbol, symbolUse.getSymbolRef())) {
392 uses.push_back(symbolUse);
393 }
394 })) {
395 return std::nullopt;
396 }
397 }
398 return SymbolTable::UseRange(std::move(uses));
399}
400
401} // namespace
402
407std::optional<SymbolTable::UseRange> llzk::getSymbolUses(StringAttr symbol, Operation *from) {
408 return getSymbolUsesImpl(symbol, from);
409}
410std::optional<SymbolTable::UseRange> llzk::getSymbolUses(Operation *symbol, Operation *from) {
411 return getSymbolUsesImpl(symbol, from);
412}
413std::optional<SymbolTable::UseRange> llzk::getSymbolUses(StringAttr symbol, Region *from) {
414 return getSymbolUsesImpl(symbol, from);
415}
416std::optional<SymbolTable::UseRange> llzk::getSymbolUses(Operation *symbol, Region *from) {
417 return getSymbolUsesImpl(symbol, from);
418}
419
420//===----------------------------------------------------------------------===//
421// llzk::getSymbolsUsedIn
422
423void llzk::getSymbolsUsedIn(Type t, llvm::SmallDenseSet<SymbolRefAttr> &symbolsUsed) {
424 t.walk([&symbolsUsed](SymbolRefAttr symbolRef) { symbolsUsed.insert(symbolRef); });
425}
426
427void llzk::getSymbolsUsedIn(ArrayRef<Type> types, llvm::SmallDenseSet<SymbolRefAttr> &symbolsUsed) {
428 for (Type t : types) {
429 getSymbolsUsedIn(t, symbolsUsed);
430 }
431}
432
433llvm::SmallDenseSet<SymbolRefAttr> llzk::getSymbolsUsedIn(Type t) {
434 llvm::SmallDenseSet<SymbolRefAttr> symbolsUsed;
435 getSymbolsUsedIn(t, symbolsUsed);
436 return symbolsUsed;
437}
438
439llvm::SmallDenseSet<SymbolRefAttr> llzk::getSymbolsUsedIn(ArrayRef<Type> types) {
440 llvm::SmallDenseSet<SymbolRefAttr> symbolsUsed;
441 getSymbolsUsedIn(types, symbolsUsed);
442 return symbolsUsed;
443}
444
445//===----------------------------------------------------------------------===//
446// llzk::symbolKnownUseEmpty
447
448namespace {
449
451template <typename SymbolT, typename IRUnitT>
452static bool symbolKnownUseEmptyImpl(SymbolT symbol, IRUnitT *limit) {
453 for (SymbolScope &scope : collectSymbolScopes(symbol, limit)) {
454 // Walk all of the symbol uses looking for a reference to 'symbol'.
455 if (scope.walk([&](SymbolTable::SymbolUse symbolUse) {
456 return isReferencePrefixOf(scope.symbol, symbolUse.getSymbolRef()) ? WalkResult::interrupt()
457 : WalkResult::advance();
458 }) != WalkResult::advance()) {
459 return false;
460 }
461 }
462 return true;
463}
464
465} // namespace
466
471bool llzk::symbolKnownUseEmpty(StringAttr symbol, Operation *from) {
472 return symbolKnownUseEmptyImpl(symbol, from);
473}
474bool llzk::symbolKnownUseEmpty(Operation *symbol, Operation *from) {
475 return symbolKnownUseEmptyImpl(symbol, from);
476}
477bool llzk::symbolKnownUseEmpty(StringAttr symbol, Region *from) {
478 return symbolKnownUseEmptyImpl(symbol, from);
479}
480bool llzk::symbolKnownUseEmpty(Operation *symbol, Region *from) {
481 return symbolKnownUseEmptyImpl(symbol, from);
482}
483
484//===----------------------------------------------------------------------===//
485// llzk::getSymbolName
486
487StringAttr llzk::getSymbolName(Operation *op) {
488 // This is modified for LLZK.
489 // `SymbolTable::getSymbolName(Operation*)` asserts if there is no name (ex: in the case of
490 // ModuleOp where the symbol name is optional) and there's no other way to check if the name
491 // exists so this fully involved retrieval method must be used to return `nullptr` if no name.
492 return op->getAttrOfType<StringAttr>(SymbolTable::getSymbolAttrName());
493}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition LICENSE.txt:9
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for and distribution as defined by Sections through of this document Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License Legal Entity shall mean the union of the acting entity and all other entities that control are controlled by or are under common control with that entity For the purposes of this definition control direct or to cause the direction or management of such whether by contract or including but not limited to software source documentation and configuration files Object form shall mean any form resulting from mechanical transformation or translation of a Source including but not limited to compiled object generated and conversions to other media types Work shall mean the work of whether in Source or Object made available under the as indicated by a copyright notice that is included in or attached to the whether in Source or Object that is based or other modifications as a an original work of authorship For the purposes of this Derivative Works shall not include works that remain separable from
Definition LICENSE.txt:45
std::optional< mlir::SymbolTable::UseRange > getSymbolUses(mlir::Operation *from)
Get an iterator range for all of the uses, for any symbol, that are nested within the given operation...
mlir::StringAttr getSymbolName(mlir::Operation *symbol)
Returns the name of the given symbol operation, or nullptr if no symbol is present.
void getSymbolsUsedIn(mlir::Type t, llvm::SmallDenseSet< mlir::SymbolRefAttr > &symbolsUsed)
Add all symbols used within the given Type to the provided set.
bool symbolKnownUseEmpty(mlir::StringAttr symbol, mlir::Operation *from)
Return if the given symbol is known to have no uses that are nested within the given operation 'from'...