LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
SymbolHelper.cpp
Go to the documentation of this file.
1//===-- SymbolHelper.cpp - LLZK Symbol Helpers ------------------*- 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
16
28
29#include <mlir/AsmParser/AsmParser.h>
30#include <mlir/IR/BuiltinOps.h>
31#include <mlir/IR/BuiltinTypes.h>
32#include <mlir/IR/Operation.h>
33
34#include <llvm/ADT/TypeSwitch.h>
35#include <llvm/Support/Debug.h>
36
37#include <algorithm>
38
39#define DEBUG_TYPE "llzk-symbol-helpers"
40
41using namespace mlir;
42
43namespace llzk {
44
45using namespace array;
46using namespace component;
47using namespace felt;
48using namespace function;
49using namespace global;
50using namespace polymorphic;
51
52void eraseEmptyNestedModules(ModuleOp rootModule) {
53 SmallVector<ModuleOp> emptyModules;
54 rootModule.walk<WalkOrder::PostOrder>([&](ModuleOp module) {
55 if (module == rootModule) {
56 return;
57 }
58 Region &region = module.getBodyRegion();
59 if (region.empty() || region.front().empty()) { // ModuleOp has the SingleBlock trait.
60 emptyModules.push_back(module);
61 }
62 });
63
64 for (ModuleOp module : emptyModules) {
65 LLVM_DEBUG(llvm::dbgs() << "Removing empty module " << module.getName() << '\n');
66 module.erase();
67 }
68}
69
70namespace {
71
72// NOTE: These may be used in SymbolRefAttr instances returned from these functions but there is no
73// restriction that the same value cannot be used as a symbol name in user code so these should not
74// be used in such a way that relies on that assumption. That's why they are (currently) defined in
75// this anonymous namespace rather than within the header file.
76constexpr char POSITION_IS_ROOT_INDICATOR[] = "<<symbol lookup root>>";
77constexpr char UNNAMED_SYMBOL_INDICATOR[] = "<<unnamed symbol>>";
78
79enum RootSelector : std::uint8_t { CLOSEST, FURTHEST };
80
81class RootPathBuilder {
82 RootSelector _whichRoot;
83 Operation *_origin;
84 ModuleOp *_foundRoot;
85
86public:
87 RootPathBuilder(RootSelector whichRoot, Operation *origin, ModuleOp *foundRoot)
88 : _whichRoot(whichRoot), _origin(origin), _foundRoot(foundRoot) {}
89
96 FailureOr<ModuleOp> collectPathToRoot(Operation *from, std::vector<FlatSymbolRefAttr> &path) {
97 Operation *check = from;
98 ModuleOp currRoot = nullptr;
99 do {
100 if (ModuleOp m = llvm::dyn_cast_if_present<ModuleOp>(check)) {
101 // Parsing may wrap the input in an extra module; llzk.lang identifies
102 // the program's lookup root independently of that wrapper.
103 if (m->hasAttr(LANG_ATTR_NAME)) {
104 if (_whichRoot == RootSelector::CLOSEST) {
105 return m;
106 }
107 currRoot = m;
108 }
109 if (StringAttr modName = m.getSymNameAttr()) {
110 path.push_back(FlatSymbolRefAttr::get(modName));
111 } else if (!currRoot) {
112 return _origin->emitOpError()
113 .append(
114 "has ancestor '", ModuleOp::getOperationName(), "' without \"", LANG_ATTR_NAME,
115 "\" attribute or a name"
116 )
117 .attachNote(m.getLoc())
118 .append("unnamed '", ModuleOp::getOperationName(), "' here");
119 }
120 } else if (TemplateOp t = llvm::dyn_cast_if_present<TemplateOp>(check)) {
121 StringAttr name = t.getSymNameAttr();
122 assert(name && "per ODS");
123 path.push_back(FlatSymbolRefAttr::get(name));
124 }
125 } while ((check = check->getParentOp()));
126
127 if (_whichRoot == RootSelector::FURTHEST && currRoot) {
128 return currRoot;
129 }
130
131 return _origin->emitOpError().append(
132 "has no ancestor '", ModuleOp::getOperationName(), "' with \"", LANG_ATTR_NAME,
133 "\" attribute"
134 );
135 }
136
139 FailureOr<SymbolRefAttr>
140 buildPathFromRootToAnyOp(Operation *position, std::vector<FlatSymbolRefAttr> &&path) {
141 // Collect the rest of the path to the root module
142 FailureOr<ModuleOp> rootMod = collectPathToRoot(position, path);
143 if (failed(rootMod)) {
144 return failure();
145 }
146 if (_foundRoot) {
147 *_foundRoot = rootMod.value();
148 }
149 // Special case for empty path (because asSymbolRefAttr() cannot handle it).
150 if (path.empty()) {
151 // ASSERT: This can only occur when the given `position` is the discovered root ModuleOp
152 // itself.
153 assert(position == rootMod.value().getOperation() && "empty path only at root itself");
154 return getFlatSymbolRefAttr(_origin->getContext(), POSITION_IS_ROOT_INDICATOR);
155 }
156 // Reverse the vector and convert it to a SymbolRefAttr
157 std::vector<FlatSymbolRefAttr> reversedVec(path.rbegin(), path.rend());
158 return asSymbolRefAttr(reversedVec);
159 }
160
162 FailureOr<SymbolRefAttr> getPathFromRootToAnyOp(Operation *op) {
163 std::vector<FlatSymbolRefAttr> path;
164 return buildPathFromRootToAnyOp(op, std::move(path));
165 }
166
169 FailureOr<SymbolRefAttr>
170 buildPathFromRootToStruct(StructDefOp to, std::vector<FlatSymbolRefAttr> &&path) {
171 // Add the name of the struct (its name is not optional) and then delegate to helper
172 path.push_back(FlatSymbolRefAttr::get(to.getSymNameAttr()));
173 return buildPathFromRootToAnyOp(to, std::move(path));
174 }
175
176 FailureOr<SymbolRefAttr> getPathFromRootToStruct(StructDefOp to) {
177 std::vector<FlatSymbolRefAttr> path;
178 return buildPathFromRootToStruct(to, std::move(path));
179 }
180
181 FailureOr<SymbolRefAttr> getPathFromRootToMember(MemberDefOp to) {
182 std::vector<FlatSymbolRefAttr> path;
183 // Add the name of the member (its name is not optional)
184 path.push_back(FlatSymbolRefAttr::get(to.getSymNameAttr()));
185 // Delegate to the parent handler (must be StructDefOp per ODS)
186 return buildPathFromRootToStruct(to.getParentOp<StructDefOp>(), std::move(path));
187 }
188
189 FailureOr<SymbolRefAttr> getPathFromRootToFunc(FuncDefOp to) {
190 std::vector<FlatSymbolRefAttr> path;
191 // Add the name of the function (its name is not optional)
192 path.push_back(FlatSymbolRefAttr::get(to.getSymNameAttr()));
193
194 // Delegate based on the type of the parent op
195 Operation *current = to.getOperation();
196 Operation *parent = current->getParentOp();
197 if (StructDefOp parentStruct = llvm::dyn_cast_if_present<StructDefOp>(parent)) {
198 return buildPathFromRootToStruct(parentStruct, std::move(path));
199 } else if (ModuleOp parentMod = llvm::dyn_cast_if_present<ModuleOp>(parent)) {
200 return buildPathFromRootToAnyOp(parentMod, std::move(path));
201 } else if (TemplateOp parentTemplate = llvm::dyn_cast_if_present<TemplateOp>(parent)) {
202 return buildPathFromRootToAnyOp(parentTemplate, std::move(path));
203 } else {
204 // This is an error in the compiler itself. In current implementation,
205 // FuncDefOp must have module, struct, or template as its parent.
206 return current->emitError().append("orphaned '", FuncDefOp::getOperationName(), '\'');
207 }
208 }
209
210 FailureOr<SymbolRefAttr> getPathFromRootToAnySymbol(SymbolOpInterface to) {
211 // clang-format off
212 return TypeSwitch<Operation *, FailureOr<SymbolRefAttr>>(to.getOperation())
213 // This more general function must check for the specific cases first.
214 .Case<FuncDefOp>([this](auto toOp) { return getPathFromRootToFunc(toOp); })
215 .Case<MemberDefOp>([this](auto toOp) { return getPathFromRootToMember(toOp); })
216 .Case<StructDefOp>([this](auto toOp) { return getPathFromRootToStruct(toOp); })
217 .Case<TemplateOp>([this](auto toOp) { return getPathFromRootToAnyOp(toOp); })
218 .Case<ModuleOp>([this](auto toOp) { return getPathFromRootToAnyOp(toOp); })
219
220 // For any other symbol, append the name of the symbol and then delegate to
221 // `buildPathFromRootToAnyOp()`.
222 .Default([this, &to](auto) {
223 std::vector<FlatSymbolRefAttr> path;
224 if (StringAttr name = llzk::getSymbolName(to)) {
225 path.push_back(FlatSymbolRefAttr::get(name));
226 } else {
227 // This can only happen if the symbol is optional. Add a placeholder name.
228 assert(to.isOptionalSymbol());
229 path.push_back(FlatSymbolRefAttr::get(to.getContext(), UNNAMED_SYMBOL_INDICATOR));
230 }
231 return buildPathFromRootToAnyOp(to, std::move(path));
232 });
233 // clang-format on
234 }
235};
236
237LogicalResult verifyTemplateSymbolType(
238 TemplateSymbolBindingOpInterface binding, SymbolRefAttr param, Type parameterizedType,
239 Operation *origin, std::optional<Type> requiredParamType
240) {
241 if (requiredParamType) {
242 std::optional<Type> actualType = binding.getTypeOpt();
243 if (!actualType) {
244 return origin->emitError().append(
245 "ref \"", param, "\" in type ", parameterizedType, " refers to a '", binding->getName(),
246 "' that must have type ", *requiredParamType
247 );
248 }
249 if (*actualType != *requiredParamType) {
250 return origin->emitError().append(
251 "ref \"", param, "\" in type ", parameterizedType, " refers to a '", binding->getName(),
252 "' with type ", *actualType, " but expected ", *requiredParamType
253 );
254 }
255 }
256 return success();
257}
258
259} // namespace
260
261llvm::SmallVector<StringRef> getNames(SymbolRefAttr ref) {
262 llvm::SmallVector<StringRef> names;
263 names.push_back(ref.getRootReference().getValue());
264 for (const FlatSymbolRefAttr &r : ref.getNestedReferences()) {
265 names.push_back(r.getValue());
266 }
267 return names;
268}
269
270llvm::SmallVector<FlatSymbolRefAttr> getPieces(SymbolRefAttr ref) {
271 llvm::SmallVector<FlatSymbolRefAttr> pieces;
272 pieces.push_back(FlatSymbolRefAttr::get(ref.getRootReference()));
273 for (const FlatSymbolRefAttr &r : ref.getNestedReferences()) {
274 pieces.push_back(r);
275 }
276 return pieces;
277}
278
279namespace {
280
281SymbolRefAttr changeLeafImpl(
282 StringAttr origRoot, ArrayRef<FlatSymbolRefAttr> origTail, FlatSymbolRefAttr newLeaf,
283 size_t drop = 1
284) {
285 llvm::SmallVector<FlatSymbolRefAttr> newTail;
286 newTail.append(origTail.begin(), origTail.drop_back(drop).end());
287 newTail.push_back(newLeaf);
288 return SymbolRefAttr::get(origRoot, newTail);
289}
290
291} // namespace
292
293SymbolRefAttr replaceLeaf(SymbolRefAttr orig, FlatSymbolRefAttr newLeaf) {
294 ArrayRef<FlatSymbolRefAttr> origTail = orig.getNestedReferences();
295 if (origTail.empty()) {
296 // If there is no tail, the root is the leaf so replace the whole thing
297 return newLeaf;
298 } else {
299 return changeLeafImpl(orig.getRootReference(), origTail, newLeaf);
300 }
301}
302
303SymbolRefAttr appendLeaf(SymbolRefAttr orig, FlatSymbolRefAttr newLeaf) {
304 return changeLeafImpl(orig.getRootReference(), orig.getNestedReferences(), newLeaf, 0);
305}
306
307SymbolRefAttr appendLeafName(SymbolRefAttr orig, const Twine &newLeafSuffix) {
308 ArrayRef<FlatSymbolRefAttr> origTail = orig.getNestedReferences();
309 if (origTail.empty()) {
310 // If there is no tail, the root is the leaf so append on the root instead
312 orig.getContext(), orig.getRootReference().getValue() + newLeafSuffix
313 );
314 } else {
315 return changeLeafImpl(
316 orig.getRootReference(), origTail,
317 getFlatSymbolRefAttr(orig.getContext(), origTail.back().getValue() + newLeafSuffix)
318 );
319 }
320}
321
322FailureOr<SymbolRefAttr> getPathRelativeToAncestor(
323 SymbolOpInterface symbol, ModuleOp ancestor, llvm::function_ref<InFlightDiagnostic()> emitError
324) {
325 Operation *definition = symbol.getOperation();
326 if (definition == ancestor || !ancestor->isAncestor(definition)) {
327 if (emitError) {
328 auto diagnostic = emitError();
329 if (definition == ancestor) {
330 diagnostic << " cannot be named relative to itself; a strict descendant is required";
331 diagnostic.attachNote(ancestor.getLoc())
332 << "symbol and supplied module are the same operation";
333 } else {
334 diagnostic << " is not visible from module \""
335 << ancestor.getSymName().value_or("<unnamed>") << '"';
336 diagnostic.attachNote(definition->getLoc())
337 << "symbol is defined outside the supplied module";
338 diagnostic.attachNote(ancestor.getLoc()) << "symbol path must start from this module";
339 }
340 }
341 return failure();
342 }
343 SmallVector<FlatSymbolRefAttr> path;
344 for (Operation *current = definition; current != ancestor; current = current->getParentOp()) {
345 if (current != definition && !current->hasTrait<OpTrait::SymbolTable>()) {
346 continue;
347 }
348 auto name = llzk::getSymbolName(current);
349 if (!name) {
350 if (emitError) {
351 auto diagnostic = emitError();
352 diagnostic << " cannot be named relative to module \""
353 << ancestor.getSymName().value_or("<unnamed>")
354 << "\" because an intervening symbol table is unnamed";
355 diagnostic.attachNote(current->getLoc())
356 << "unnamed symbol table prevents forming a symbol path";
357 diagnostic.attachNote(ancestor.getLoc()) << "symbol path must start from this module";
358 }
359 return failure();
360 }
361 path.push_back(FlatSymbolRefAttr::get(name));
362 }
363 std::reverse(path.begin(), path.end());
364 return asSymbolRefAttr(path);
365}
366
367FailureOr<ModuleOp> getRootModule(Operation *from) {
368 std::vector<FlatSymbolRefAttr> path;
369 return RootPathBuilder(RootSelector::CLOSEST, from, nullptr).collectPathToRoot(from, path);
370}
371
372FailureOr<SymbolRefAttr> getPathFromRoot(SymbolOpInterface to, ModuleOp *foundRoot) {
373 return RootPathBuilder(RootSelector::CLOSEST, to, foundRoot).getPathFromRootToAnySymbol(to);
374}
375
376FailureOr<SymbolRefAttr> getPathFromRoot(TemplateOp &to, ModuleOp *foundRoot) {
377 return RootPathBuilder(RootSelector::CLOSEST, to, foundRoot).getPathFromRootToAnyOp(to);
378}
379
380FailureOr<SymbolRefAttr> getPathFromRoot(StructDefOp &to, ModuleOp *foundRoot) {
381 return RootPathBuilder(RootSelector::CLOSEST, to, foundRoot).getPathFromRootToStruct(to);
382}
383
384FailureOr<SymbolRefAttr> getPathFromRoot(MemberDefOp &to, ModuleOp *foundRoot) {
385 return RootPathBuilder(RootSelector::CLOSEST, to, foundRoot).getPathFromRootToMember(to);
386}
387
388FailureOr<SymbolRefAttr> getPathFromRoot(FuncDefOp &to, ModuleOp *foundRoot) {
389 return RootPathBuilder(RootSelector::CLOSEST, to, foundRoot).getPathFromRootToFunc(to);
390}
391
392FailureOr<ModuleOp> getTopRootModule(Operation *from) {
393 std::vector<FlatSymbolRefAttr> path;
394 return RootPathBuilder(RootSelector::FURTHEST, from, nullptr).collectPathToRoot(from, path);
395}
396
397FailureOr<SymbolRefAttr> getPathFromTopRoot(SymbolOpInterface to, ModuleOp *foundRoot) {
398 return RootPathBuilder(RootSelector::FURTHEST, to, foundRoot).getPathFromRootToAnySymbol(to);
399}
400
401FailureOr<SymbolRefAttr> getPathFromTopRoot(TemplateOp &to, ModuleOp *foundRoot) {
402 return RootPathBuilder(RootSelector::FURTHEST, to, foundRoot).getPathFromRootToAnyOp(to);
403}
404
405FailureOr<SymbolRefAttr> getPathFromTopRoot(StructDefOp &to, ModuleOp *foundRoot) {
406 return RootPathBuilder(RootSelector::FURTHEST, to, foundRoot).getPathFromRootToStruct(to);
407}
408
409FailureOr<SymbolRefAttr> getPathFromTopRoot(MemberDefOp &to, ModuleOp *foundRoot) {
410 return RootPathBuilder(RootSelector::FURTHEST, to, foundRoot).getPathFromRootToMember(to);
411}
412
413FailureOr<SymbolRefAttr> getPathFromTopRoot(FuncDefOp &to, ModuleOp *foundRoot) {
414 return RootPathBuilder(RootSelector::FURTHEST, to, foundRoot).getPathFromRootToFunc(to);
415}
416
417FailureOr<StructType> getMainInstanceType(Operation *lookupFrom) {
418 FailureOr<ModuleOp> rootOpt = getRootModule(lookupFrom);
419 if (failed(rootOpt)) {
420 return failure();
421 }
422 ModuleOp root = rootOpt.value();
423 if (Attribute a = root->getAttr(MAIN_ATTR_NAME)) {
424 return getTypeFromLlzkMainAttr(root, a);
425 }
426 // The attribute is optional so it's okay if not present.
427 return success(nullptr);
428}
429
430FailureOr<SymbolLookupResult<StructDefOp>>
431getMainInstanceDef(SymbolTableCollection &symbolTable, Operation *lookupFrom) {
432 FailureOr<StructType> mainStructTypeOpt = getMainInstanceType(lookupFrom);
433 if (failed(mainStructTypeOpt)) {
434 return failure();
435 }
436 if (StructType st = mainStructTypeOpt.value()) {
437 return st.getDefinition(symbolTable, lookupFrom);
438 } else {
439 return success(nullptr);
440 }
441}
442
443FailureOr<SymbolLookupResult<StructDefOp>>
444getRootStructDef(SymbolTableCollection &tables, Operation *lookupFrom, StringRef symbolPath) {
445 symbolPath = symbolPath.trim();
446 if (symbolPath.empty()) {
447 return getMainInstanceDef(tables, lookupFrom);
448 }
449 std::string symbolText = symbolPath.str();
450 if (symbolText.front() != '@') {
451 symbolText.insert(symbolText.begin(), '@');
452 }
453 SymbolRefAttr symbol;
454 std::string parseError;
455 {
456 // Report parser details at the caller's location instead of the temporary buffer.
457 ScopedDiagnosticHandler handler(lookupFrom->getContext(), [&](Diagnostic &diagnostic) {
458 if (diagnostic.getSeverity() == DiagnosticSeverity::Error) {
459 parseError = diagnostic.str();
460 }
461 return success();
462 });
463 symbol = dyn_cast_or_null<SymbolRefAttr>(parseAttribute(symbolText, lookupFrom->getContext()));
464 }
465 if (!symbol) {
466 auto diagnostic = lookupFrom->emitError();
467 diagnostic << "invalid root struct symbol path '" << symbolPath << '\'';
468 if (!parseError.empty()) {
469 diagnostic << ": " << parseError;
470 }
471 return diagnostic;
472 }
473 auto found = lookupTopLevelSymbol(tables, symbol, lookupFrom, false);
474 if (failed(found)) {
475 return lookupFrom->emitError()
476 << "root struct symbol path '" << symbolPath << "' does not resolve to a symbol";
477 }
478 Operation *definition = found->get();
479 SymbolLookupResult<StructDefOp> root(std::move(*found));
480 if (!root) {
481 return lookupFrom->emitError()
482 << "root struct symbol path '" << symbolPath << "' references a '"
483 << definition->getName() << "'; expected a 'struct.def'";
484 }
485 return root;
486}
487
488FailureOr<TemplateOp> getConstResolutionTemplate(SymbolTableCollection &tables, Operation *origin) {
489 if (auto contract = origin->getParentOfType<verif::ContractOp>()) {
490 FailureOr<SymbolLookupResultUntyped> targetRes =
491 lookupTopLevelSymbol(tables, contract.getTargetAttr(), origin);
492 if (failed(targetRes)) {
493 return failure(); // lookupTopLevelSymbol() already emits a sufficient error message
494 }
495
496 if (TemplateOp targetTemplate = targetRes->get()->getParentOfType<TemplateOp>()) {
497 return targetTemplate;
498 }
499 }
500
501 return getParentOfType<TemplateOp>(origin);
502}
503
504LogicalResult
505verifyTemplateParamSymbol(SymbolTableCollection &tables, SymbolRefAttr symbol, Operation *origin) {
506 if (symbol.getNestedReferences().empty()) {
507 FailureOr<TemplateOp> parent = getConstResolutionTemplate(tables, origin);
508 if (failed(parent)) {
509 return failure();
510 }
511 if (*parent &&
512 parent->getConstNamed<TemplateSymbolBindingOpInterface>(symbol.getRootReference())) {
513 return success();
514 }
515 }
516
517 auto lookupRes = lookupTopLevelSymbol(tables, symbol, origin);
518 if (failed(lookupRes)) {
519 return failure();
520 }
521 auto global = llvm::dyn_cast<GlobalDefOp>(lookupRes->get());
522 if (!global) {
523 return origin->emitOpError() << "template argument '" << symbol << "' refers to a '"
524 << lookupRes->get()->getName() << "' which is not allowed";
525 }
526 if (!global.isConstant()) {
527 return origin->emitOpError() << "template argument '" << symbol
528 << "' refers to a global that is not marked as 'const'";
529 }
530 return success();
531}
532
534 Operation *origin, Attribute value, TemplateParamOp targetParam
535) {
536 std::optional<Type> declaredType = targetParam.getTypeOpt();
537
538 if (auto intAttr = llvm::dyn_cast<IntegerAttr>(value); intAttr && isDynamic(intAttr)) {
539 if (!declaredType || !llvm::isa<TypeVarType>(*declaredType)) {
540 auto diag = origin->emitOpError().append(
541 "wildcard `?` can only be used for template parameters with `!poly.tvar` "
542 "type restriction, but parameter \"@",
543 targetParam.getName(), "\" has "
544 );
545 if (declaredType) {
546 diag.append("type restriction ", *declaredType);
547 } else {
548 diag.append("no type restriction");
549 }
550 return diag;
551 }
552 return success();
553 }
554
555 if (auto symbol = llvm::dyn_cast<SymbolRefAttr>(value)) {
556 SymbolTableCollection tables;
557 if (failed(verifyTemplateParamSymbol(tables, symbol, origin))) {
558 return failure();
559 }
560 if (!declaredType) {
561 return success();
562 }
563 bool resolvedLocal = false;
564 bool compatible = false;
565 if (symbol.getNestedReferences().empty()) {
566 FailureOr<TemplateOp> parentTemplate = getConstResolutionTemplate(tables, origin);
567 if (failed(parentTemplate)) {
568 return failure();
569 }
570 if (*parentTemplate) {
571 auto binding = parentTemplate->getConstNamed<TemplateSymbolBindingOpInterface>(
572 symbol.getRootReference()
573 );
574 if (binding) {
575 resolvedLocal = true;
576 compatible = !binding.getTypeOpt() || typesUnify(*binding.getTypeOpt(), *declaredType);
577 }
578 }
579 }
580 // `verifyTemplateParamSymbol` establishes that this is a constant global, but the global's
581 // type is not otherwise constrained when the parameter is absent from the callee signature.
582 // Resolve it here to enforce the explicit template parameter restriction.
583 if (!resolvedLocal) {
584 FailureOr<SymbolLookupResultUntyped> lookupRes = lookupTopLevelSymbol(tables, symbol, origin);
585 if (failed(lookupRes)) {
586 return failure();
587 }
588 auto global = llvm::cast<GlobalDefOp>(lookupRes->get());
589 assert(global.isConstant() && "already verified by verifyTemplateParamSymbol");
590 compatible = typesUnify(global.getType(), *declaredType);
591 }
592 if (!compatible) {
593 return origin->emitOpError().append(
594 "instantiation value '", value, "' is not compatible with parameter \"@",
595 targetParam.getName(), "\" type restriction ", *declaredType
596 );
597 }
598 return success();
599 }
600
601 if (!declaredType) {
602 return success();
603 }
604 bool compatible = false;
605 if (llvm::isa<TypeVarType>(*declaredType)) {
606 compatible = llvm::isa<TypeAttr>(value);
607 } else if (llvm::isa<FeltType>(*declaredType)) {
608 compatible = llvm::isa<FeltConstAttr, IntegerAttr>(value) &&
609 isValidConstReadType(llvm::cast<TypedAttr>(value).getType());
610 } else if (llvm::isa<IndexType, IntegerType>(*declaredType)) {
611 compatible = llvm::isa<IntegerAttr>(value) &&
612 isValidConstReadType(llvm::cast<TypedAttr>(value).getType());
613 } else {
614 llvm_unreachable("inconsistent with `isValidConstReadType()`");
615 }
616 if (!compatible) {
617 return origin->emitOpError().append(
618 "instantiation value '", value, "' is not compatible with parameter \"@",
619 targetParam.getName(), "\" type restriction ", *declaredType
620 );
621 }
622 return success();
623}
624
626 Operation *origin, ArrayAttr explicitParams,
627 llvm::iterator_range<Region::op_iterator<TemplateParamOp>> targetParamDefs
628) {
629 assert(!isNullOrEmpty(explicitParams) && "pre-condition");
630 assert(explicitParams.size() == llvm::range_size(targetParamDefs) && "pre-condition");
631 for (auto [targetParam, value] : llvm::zip_equal(targetParamDefs, explicitParams.getValue())) {
632 if (failed(verifyTemplateParamValueCompatibility(origin, value, targetParam))) {
633 return failure();
634 }
635 }
636 return success();
637}
638
640 Operation *origin, ArrayAttr explicitParams,
641 llvm::iterator_range<Region::op_iterator<TemplateParamOp>> targetParamDefs,
642 const UnificationMap &unifications
643) {
644 assert(!isNullOrEmpty(explicitParams) && "pre-condition");
645 assert((explicitParams.size() == llvm::range_size(targetParamDefs)) && "pre-condition");
646
647 for (auto [paramOp, attr] : llvm::zip_equal(targetParamDefs, explicitParams.getValue())) {
648 // Skip wildcards (`?` / kDynamic) - their value will be resolved by a later inference pass.
649 if (auto intAttr = llvm::dyn_cast<IntegerAttr>(attr)) {
650 if (isDynamic(intAttr)) {
651 continue;
652 }
653 }
654 auto it = unifications.find({FlatSymbolRefAttr::get(paramOp.getNameAttr()), Side::RHS});
655 // A null entry represents conflicting inferred bindings. It may include symbolic values
656 // that become equal after instantiating an enclosing template, so defer validation.
657 if (it != unifications.end() && it->second && !typeParamsUnify({attr}, {it->second})) {
658 return origin->emitOpError().append(
659 "template instantiation value '", attr, "' for parameter \"@", paramOp.getName(),
660 "\" conflicts with value '", it->second, "' inferred from function type signature"
661 );
662 }
663 }
664 return success();
665}
666
667LogicalResult verifyParamOfType(
668 SymbolTableCollection &tables, SymbolRefAttr param, Type parameterizedType, Operation *origin,
669 std::optional<Type> requiredParamType
670) {
671 // Most often, StructType and ArrayType SymbolRefAttr parameters will be defined as parameters of
672 // the template that the current Operation is nested within. These are always flat references
673 // (i.e., contain no nested references).
674 if (param.getNestedReferences().empty()) {
675 FailureOr<TemplateOp> parent = getConstResolutionTemplate(tables, origin);
676 if (failed(parent)) {
677 return failure(); // getConstResolutionTemplate() failure cases emit a sufficient error
678 // message
679 }
680 if (*parent) {
681 if (auto b =
682 parent->getConstNamed<TemplateSymbolBindingOpInterface>(param.getRootReference())) {
683 return verifyTemplateSymbolType(b, param, parameterizedType, origin, requiredParamType);
684 }
685 }
686 }
687 // Otherwise, see if the symbol can be found via lookup from the `origin` Operation.
688 auto lookupRes = lookupTopLevelSymbol(tables, param, origin);
689 if (failed(lookupRes)) {
690 return failure(); // lookupTopLevelSymbol() already emits a sufficient error message
691 }
692 auto global = llvm::dyn_cast<GlobalDefOp>(lookupRes->get());
693 if (!global) {
694 return origin->emitError() << "ref \"" << param << "\" in type " << parameterizedType
695 << " refers to a '" << lookupRes->get()->getName()
696 << "' which is not allowed";
697 }
698 if (!global.isConstant()) {
699 return origin->emitError() << "ref \"" << param << "\" in type " << parameterizedType
700 << " refers to a global that is not marked as 'const'";
701 }
702 return success();
703}
704
705LogicalResult verifyParamsOfType(
706 SymbolTableCollection &tables, ArrayRef<Attribute> tyParams, Type parameterizedType,
707 Operation *origin, std::optional<Type> requiredParamType
708) {
709 // Rather than immediately returning on failure, we check all params and aggregate to provide as
710 // many errors are possible in a single verifier run.
711 LogicalResult paramCheckResult = success();
712 LLVM_DEBUG({
713 llvm::dbgs() << "[verifyParamOfType] parameterizedType = " << parameterizedType << '\n';
714 });
715 for (Attribute attr : tyParams) {
716 LLVM_DEBUG({ llvm::dbgs() << "[verifyParamOfType] checking attribute " << attr << '\n'; });
718 if (SymbolRefAttr symRefParam = llvm::dyn_cast<SymbolRefAttr>(attr)) {
719 auto r = verifyParamOfType(tables, symRefParam, parameterizedType, origin, requiredParamType);
720 if (failed(r)) {
721 LLVM_DEBUG({
722 llvm::dbgs() << "[verifyParamOfType] failed to verify symbol attribute\n";
723 });
724 paramCheckResult = failure();
725 }
726 } else if (TypeAttr typeParam = llvm::dyn_cast<TypeAttr>(attr)) {
727 if (failed(verifyTypeResolution(tables, origin, typeParam.getValue()))) {
728 LLVM_DEBUG({
729 llvm::dbgs() << "[verifyParamOfType] failed to verify type attribute\n";
730 });
731 paramCheckResult = failure();
732 }
733 }
734 LLVM_DEBUG({ llvm::dbgs() << "[verifyParamOfType] verified attribute\n"; });
735 // IntegerAttr and AffineMapAttr cannot contain symbol references
736 }
737 return paramCheckResult;
738}
739
740FailureOr<StructDefOp>
741verifyStructTypeResolution(SymbolTableCollection &tables, StructType ty, Operation *origin) {
742 auto res = ty.getDefinition(tables, origin);
743 if (failed(res)) {
744 return failure();
745 }
746 StructDefOp defForType = res.value().get();
747 if (!structTypesUnify(ty, defForType.getType({}), res->getNamespace())) {
748 return origin->emitError()
749 .append(
750 "Cannot unify parameters of type ", ty, " with parameters of '",
751 StructDefOp::getOperationName(), "' \"", defForType.getHeaderString(), '"'
752 )
753 .attachNote(defForType.getLoc())
754 .append("type parameters must unify with parameters defined here");
755 }
756 // If there are any SymbolRefAttr parameters on the StructType, ensure those refs are valid.
757 if (ArrayAttr tyParams = ty.getParams()) {
758 if (failed(verifyParamsOfType(tables, tyParams.getValue(), ty, origin))) {
759 return failure(); // verifyParamsOfType() already emits a sufficient error message
760 }
761 }
762 return defForType;
763}
764
765LogicalResult verifyTypeResolution(SymbolTableCollection &tables, Operation *origin, Type ty) {
766 if (StructType sTy = llvm::dyn_cast<StructType>(ty)) {
767 return verifyStructTypeResolution(tables, sTy, origin);
768 } else if (ArrayType aTy = llvm::dyn_cast<ArrayType>(ty)) {
769 auto r = verifyParamsOfType(
770 tables, aTy.getDimensionSizes(), aTy, origin, IndexType::get(aTy.getContext())
771 );
772 if (failed(r)) {
773 return failure();
774 }
775 return verifyTypeResolution(tables, origin, aTy.getElementType());
776 } else if (TypeVarType vTy = llvm::dyn_cast<TypeVarType>(ty)) {
777 // Unlike other type parameters, a type variable may only name a parameter
778 // of the enclosing template; it cannot resolve to a global.
779 FailureOr<TemplateOp> parent = getConstResolutionTemplate(tables, origin);
780 if (failed(parent)) {
781 return failure();
782 }
783 TemplateOp templateOp = *parent;
784 if (templateOp && templateOp.getConstNamed<TemplateParamOp>(vTy.getNameRef())) {
785 return success();
786 }
787 return origin->emitError() << "type variable " << vTy
788 << " must reference a parameter of its enclosing " << '\''
789 << TemplateOp::getOperationName() << '\'';
790 } else {
791 return success();
792 }
793}
794
795} // namespace llzk
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 or merely the Work and Derivative Works thereof Contribution shall mean any work of including the original version of the Work and any modifications or additions to that Work or Derivative Works that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner For the purposes of this definition
Definition LICENSE.txt:53
within a display generated by the Derivative if and wherever such third party notices normally appear The contents of the NOTICE file are for informational purposes only and do not modify the License You may add Your own attribution notices within Derivative Works that You alongside or as an addendum to the NOTICE text from the provided that such additional attribution notices cannot be construed as modifying the License You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for or distribution of Your or for any such Derivative Works as a provided Your and distribution of the Work otherwise complies with the conditions stated in this License Submission of Contributions Unless You explicitly state any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this without any additional terms or conditions Notwithstanding the nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions Trademarks This License does not grant permission to use the trade names
Definition LICENSE.txt:139
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
#define check(x)
Definition Ops.cpp:369
This file defines methods symbol lookup across LLZK operations and included files.
StructType getType(::std::optional<::mlir::ArrayAttr > constParams={})
Gets the StructType representing this struct.
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:1367
::std::string getHeaderString()
Generate header string, in the same format as the assemblyFormat.
Definition Ops.cpp:181
::mlir::FailureOr< SymbolLookupResult< StructDefOp > > getDefinition(::mlir::SymbolTableCollection &symbolTable, ::mlir::Operation *op, bool reportMissing=true) const
Gets the struct op that defines this struct.
Definition Types.cpp:26
::mlir::ArrayAttr getParams() const
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:847
OpT getConstNamed(::mlir::StringRef find)
Return the op of type OpT with the given name within the body region if it exists,...
Definition Ops.h.inc:1150
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:1015
::std::optional<::mlir::Type > getTypeOpt()
Definition Ops.cpp.inc:1637
void assertValidAttrForParamOfType(Attribute attr)
SymbolRefAttr appendLeafName(SymbolRefAttr orig, const Twine &newLeafSuffix)
constexpr char LANG_ATTR_NAME[]
Name of the attribute on the top-level ModuleOp that identifies the ModuleOp as the root module and s...
Definition Constants.h:23
mlir::FlatSymbolRefAttr getFlatSymbolRefAttr(mlir::MLIRContext *context, const mlir::Twine &twine)
Construct a FlatSymbolRefAttr with the given content.
mlir::FailureOr< SymbolLookupResultUntyped > lookupTopLevelSymbol(mlir::SymbolTableCollection &tables, mlir::SymbolRefAttr symbol, mlir::Operation *origin, bool reportMissing=true)
FailureOr< StructType > getMainInstanceType(Operation *lookupFrom)
llvm::SmallVector< StringRef > getNames(SymbolRefAttr ref)
mlir::StringAttr getSymbolName(mlir::Operation *symbol)
Returns the name of the given symbol operation, or nullptr if no symbol is present.
bool structTypesUnify(StructType lhs, StructType rhs, ArrayRef< StringRef > rhsReversePrefix, UnificationMap *unifications)
FailureOr< ModuleOp > getRootModule(Operation *from)
FailureOr< TemplateOp > getConstResolutionTemplate(SymbolTableCollection &tables, Operation *origin)
LogicalResult verifyTemplateParamsMatchInferred(Operation *origin, ArrayAttr explicitParams, llvm::iterator_range< Region::op_iterator< TemplateParamOp > > targetParamDefs, const UnificationMap &unifications)
mlir::DenseMap< std::pair< mlir::SymbolRefAttr, Side >, mlir::Attribute > UnificationMap
Optional result from type unifications.
Definition TypeHelper.h:208
bool isNullOrEmpty(mlir::ArrayAttr a)
SymbolRefAttr appendLeaf(SymbolRefAttr orig, FlatSymbolRefAttr newLeaf)
OpClass getParentOfType(mlir::Operation *op)
Return the closest surrounding parent/ancestor operation that is of type 'OpClass'.
Definition OpHelpers.h:53
SymbolRefAttr replaceLeaf(SymbolRefAttr orig, FlatSymbolRefAttr newLeaf)
FailureOr< StructDefOp > verifyStructTypeResolution(SymbolTableCollection &tables, StructType ty, Operation *origin)
FailureOr< ModuleOp > getTopRootModule(Operation *from)
LogicalResult verifyParamsOfType(SymbolTableCollection &tables, ArrayRef< Attribute > tyParams, Type parameterizedType, Operation *origin, std::optional< Type > requiredParamType)
LogicalResult verifyTypeResolution(SymbolTableCollection &tables, Operation *origin, Type ty)
LogicalResult verifyTemplateParamValueCompatibility(Operation *origin, Attribute value, TemplateParamOp targetParam)
LogicalResult verifyTemplateParamValuesCompatibility(Operation *origin, ArrayAttr explicitParams, llvm::iterator_range< Region::op_iterator< TemplateParamOp > > targetParamDefs)
bool isDynamic(IntegerAttr intAttr)
FailureOr< StructType > getTypeFromLlzkMainAttr(ModuleOp op, Attribute attr)
Definition Attrs.cpp:24
mlir::SymbolRefAttr asSymbolRefAttr(mlir::StringAttr root, mlir::SymbolRefAttr tail)
Build a SymbolRefAttr that prepends tail with root, i.e., root::tail.
bool typesUnify(Type lhs, Type rhs, ArrayRef< StringRef > rhsReversePrefix, UnificationMap *unifications)
void eraseEmptyNestedModules(ModuleOp rootModule)
FailureOr< SymbolLookupResult< StructDefOp > > getMainInstanceDef(SymbolTableCollection &symbolTable, Operation *lookupFrom)
bool typeParamsUnify(const ArrayRef< Attribute > &lhsParams, const ArrayRef< Attribute > &rhsParams, UnificationMap *unifications)
FailureOr< SymbolRefAttr > getPathFromTopRoot(SymbolOpInterface to, ModuleOp *foundRoot)
FailureOr< SymbolRefAttr > getPathRelativeToAncestor(SymbolOpInterface symbol, ModuleOp ancestor, llvm::function_ref< InFlightDiagnostic()> emitError)
LogicalResult verifyTemplateParamSymbol(SymbolTableCollection &tables, SymbolRefAttr symbol, Operation *origin)
llvm::SmallVector< FlatSymbolRefAttr > getPieces(SymbolRefAttr ref)
FailureOr< SymbolLookupResult< StructDefOp > > getRootStructDef(SymbolTableCollection &tables, Operation *lookupFrom, StringRef symbolPath)
FailureOr< SymbolRefAttr > getPathFromRoot(SymbolOpInterface to, ModuleOp *foundRoot)
constexpr char MAIN_ATTR_NAME[]
Name of the attribute on the top-level ModuleOp that specifies the type of the main struct.
Definition Constants.h:37
LogicalResult verifyParamOfType(SymbolTableCollection &tables, SymbolRefAttr param, Type parameterizedType, Operation *origin, std::optional< Type > requiredParamType)
bool isValidConstReadType(Type type)