LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
Ops.cpp
Go to the documentation of this file.
1//===-- Ops.cpp - Verif operation implementations ---------------*- C++ -*-===//
2//
3// Part of the LLZK Project, under the Apache License v2.0.
4// See LICENSE.txt for license information.
5// Copyright 2026 Project LLZK
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
9
11
21#include "llzk/Util/Compare.h"
25#include "llzk/Util/Walk.h"
26
27#include <mlir/Dialect/Arith/IR/Arith.h>
28#include <mlir/Dialect/SCF/IR/SCF.h>
29#include <mlir/Dialect/Utils/IndexingUtils.h>
30#include <mlir/IR/Attributes.h>
31#include <mlir/IR/BuiltinOps.h>
32#include <mlir/IR/Diagnostics.h>
33#include <mlir/IR/SymbolTable.h>
34#include <mlir/IR/ValueRange.h>
35#include <mlir/Interfaces/FunctionImplementation.h>
36#include <mlir/Support/LLVM.h>
37#include <mlir/Support/LogicalResult.h>
38
39#include <llvm/ADT/ArrayRef.h>
40#include <llvm/ADT/STLExtras.h>
41#include <llvm/ADT/SmallVectorExtras.h>
42#include <llvm/ADT/Twine.h>
43
44#include <memory>
45
46// TableGen'd implementation files
48
49// TableGen'd implementation files
50#define GET_OP_CLASSES
52
53using namespace mlir;
54using namespace llzk::polymorphic;
55using namespace llzk::felt;
56using namespace llzk::component;
57using namespace llzk::function;
58
59namespace {
60
61using namespace llzk::verif;
62
63// Check if the op is a valid contract target.
64bool isValidTarget(Operation *op) {
65 if (auto fnOp = dyn_cast<FuncDefOp>(op)) {
66 // Cannot target struct functions directly
67 return fnOp->getParentOfType<StructDefOp>() == nullptr;
68 }
69 // Only other supported target currently is a struct
70 return isa<StructDefOp>(op);
71}
72
73inline bool hasConflictingUnifications(const llzk::UnificationMap &unifications) {
74 return llvm::any_of(unifications, [](const auto &entry) { return !entry.second; });
75}
76
77struct TargetTypeInfo {
78 FunctionType funcType {};
79 ArrayAttr argAttrs {};
80};
81
82FailureOr<TargetTypeInfo> getTargetTypeInfo(Operation *op) {
83 if (auto fnOp = dyn_cast<FuncDefOp>(op)) {
84 // Recreate the function type with return types appended to the arguments.
85 FunctionType fnTy = fnOp.getFunctionType();
86 ArrayRef<Type> curInputs = fnTy.getInputs(), curResults = fnTy.getResults();
87 SmallVector<Type> newInputs;
88 newInputs.reserve(curInputs.size() + curResults.size());
89 newInputs.insert(newInputs.end(), curInputs.begin(), curInputs.end());
90 newInputs.insert(newInputs.end(), curResults.begin(), curResults.end());
91 // And no return types
92 auto newFnTy = fnTy.clone(newInputs, {});
93 // Do the same appending to the return attrs
94 ArrayAttr curArgAttrs = fnOp.getArgAttrsAttr(), curResAttrs = fnOp.getResAttrsAttr();
95 ArrayAttr newArgAttrsAttr {};
96 if (curArgAttrs || curResAttrs) {
97 auto *ctx = op->getContext();
98 SmallVector<Attribute> newArgAttrs;
99 // Since there are some attributes, it must match the length of the input arguments
100 newArgAttrs.reserve(newInputs.size());
101 if (curArgAttrs) {
102 newArgAttrs.insert(newArgAttrs.end(), curArgAttrs.begin(), curArgAttrs.end());
103 } else {
104 // Pad
105 newArgAttrs.insert(newArgAttrs.end(), curInputs.size(), DictionaryAttr::get(ctx));
106 }
107 if (curResAttrs) {
108 newArgAttrs.insert(newArgAttrs.end(), curResAttrs.begin(), curResAttrs.end());
109 } else {
110 // pad
111 newArgAttrs.insert(newArgAttrs.end(), curResults.size(), DictionaryAttr::get(ctx));
112 }
113 newArgAttrsAttr = ArrayAttr::get(ctx, newArgAttrs);
114 }
115
116 return TargetTypeInfo {
117 .funcType = newFnTy,
118 .argAttrs = newArgAttrsAttr,
119 };
120 }
121 if (auto structOp = dyn_cast<StructDefOp>(op)) {
122 if (FuncDefOp fnOp = structOp.getConstrainFuncOp(); fnOp && structOp.getComputeFuncOp()) {
123 return TargetTypeInfo {
124 .funcType = fnOp.getFunctionType(),
125 .argAttrs = fnOp.getArgAttrsAttr(),
126 };
127 } else {
128 FuncDefOp productFn = structOp.getProductFuncOp();
129 if (!productFn) {
130 return failure();
131 }
132 // Augment the product function signature to accept the self argument.
133 FunctionType fnTy = productFn.getFunctionType();
134 ArrayRef<Type> curInputs = fnTy.getInputs();
135 // Accept the self struct type in addition to existing inputs
136 SmallVector<Type> newInputs;
137 newInputs.reserve(curInputs.size() + 1);
138 newInputs.push_back(structOp.getType());
139 newInputs.insert(newInputs.end(), curInputs.begin(), curInputs.end());
140 // And no return types
141 auto newFnTy = fnTy.clone(newInputs, {});
142 // We also need to expand the arg attributes by one
143 auto *ctx = op->getContext();
144 ArrayAttr curArgAttrs = productFn.getArgAttrsAttr();
145 ArrayAttr newArgAttrsAttr = curArgAttrs;
146 if (curArgAttrs) {
147 SmallVector<Attribute> newArgAttrs;
148 newArgAttrs.reserve(curArgAttrs.size() + 1);
149 newArgAttrs.push_back(DictionaryAttr::get(ctx));
150 newArgAttrs.insert(newArgAttrs.end(), curArgAttrs.begin(), curArgAttrs.end());
151 newArgAttrsAttr = ArrayAttr::get(ctx, newArgAttrs);
152 }
153 return TargetTypeInfo {
154 .funcType = newFnTy,
155 .argAttrs = newArgAttrsAttr,
156 };
157 }
158 }
159
160 return failure();
161}
162
163enum class ForbiddenRequireConditionKind : uint8_t {
166};
167
169struct ForbiddenRequireCondition {
170 ForbiddenRequireConditionKind kind;
171 llvm::SmallSetVector<Location, 2> sourceLocs;
172};
173
176struct ForbiddenIncludedPrecondition {
177 std::optional<Location> calleePreconditionLoc = std::nullopt;
178 ForbiddenRequireConditionKind kind;
179 llvm::SmallSetVector<Location, 2> sourceLocs;
180};
181
183struct ForbiddenIncludedPreconditions {
184 IncludeOp includeOp;
185 llvm::SmallVector<ForbiddenIncludedPrecondition> failures;
186};
187
188std::optional<ForbiddenRequireCondition> classifyForbiddenConditionProvenance(
189 ModuleOp module, PreconditionOpInterface preCondOp, ContractOp contract
190) {
192 analyzeForbiddenPreconditionOpInfluenceInfo(module, contract, preCondOp);
194 return ForbiddenRequireCondition {
195 .kind = ForbiddenRequireConditionKind::StructMember,
196 .sourceLocs = influence.structMemberLocs,
197 };
198 }
200 return ForbiddenRequireCondition {
201 .kind = ForbiddenRequireConditionKind::FunctionReturn,
202 .sourceLocs = {},
203 };
204 }
205 return std::nullopt;
206}
207
208std::optional<ForbiddenIncludedPreconditions>
209classifyForbiddenIncludedPrecondition(ModuleOp module, IncludeOp includeOp) {
210 SymbolTableCollection tables;
211 auto calleeTarget = includeOp.getCalleeTarget(tables);
212 if (failed(calleeTarget)) {
213 return std::nullopt;
214 }
215 ContractOp parentContract = includeOp->getParentOfType<ContractOp>();
216 auto summary = analyzeForbiddenIncludedOpSummary(module, parentContract, includeOp);
217 if (!summary) {
218 return std::nullopt;
219 }
220
221 ForbiddenIncludedPreconditions result {.includeOp = includeOp, .failures = {}};
222 for (const auto &failure : summary.failures) {
223 if (hasInfluence(
224 failure.influenceInfo.influence, ForbiddenPreconditionInfluence::StructMember
225 )) {
226 result.failures.push_back(
227 ForbiddenIncludedPrecondition {
228 .calleePreconditionLoc = failure.preconditionLoc,
229 .kind = ForbiddenRequireConditionKind::StructMember,
230 .sourceLocs = failure.influenceInfo.structMemberLocs,
231 }
232 );
233 continue;
234 }
235 if (hasInfluence(
236 failure.influenceInfo.influence, ForbiddenPreconditionInfluence::FunctionReturn
237 )) {
238 result.failures.push_back(
239 ForbiddenIncludedPrecondition {
240 .calleePreconditionLoc = failure.preconditionLoc,
241 .kind = ForbiddenRequireConditionKind::FunctionReturn,
242 .sourceLocs = {},
243 }
244 );
245 }
246 }
247 return result.failures.empty() ? std::nullopt
248 : std::optional<ForbiddenIncludedPreconditions>(result);
249}
250
251// Map a classified restriction failure to the verifier diagnostic emitted on
252// the offending require op.
253LogicalResult emitForbiddenPrecondition(
254 PreconditionOpInterface preCondOp, ForbiddenRequireConditionKind kind,
255 llvm::ArrayRef<Location> sourceLocs = {}
256) {
257 switch (kind) {
258 case ForbiddenRequireConditionKind::StructMember: {
259 InFlightDiagnostic diag =
260 preCondOp->emitOpError("condition cannot be derived from a struct member value");
261 for (auto sourceLoc : sourceLocs) {
262 diag.attachNote(sourceLoc) << "forbidden struct member value originates here";
263 }
264 return diag;
265 }
266 case ForbiddenRequireConditionKind::FunctionReturn: {
267 return preCondOp->emitOpError("condition cannot be derived from a function return value");
268 }
269 }
270 llvm_unreachable("unknown forbidden require condition kind");
271}
272
273LogicalResult emitForbiddenIncludedPreconditions(
274 IncludeOp includeOp, llvm::ArrayRef<ForbiddenIncludedPrecondition> failures
275) {
276 bool sawStructMember = false;
277 bool sawFunctionReturn = false;
278 for (const ForbiddenIncludedPrecondition &failure : failures) {
279 sawStructMember |= failure.kind == ForbiddenRequireConditionKind::StructMember;
280 sawFunctionReturn |= failure.kind == ForbiddenRequireConditionKind::FunctionReturn;
281 }
282
283 InFlightDiagnostic diag = [&]() -> InFlightDiagnostic {
284 if (sawStructMember && sawFunctionReturn) {
285 return includeOp.emitOpError(
286 "includes preconditions whose conditions cannot be derived from forbidden sources"
287 );
288 }
289 if (sawStructMember) {
290 return includeOp.emitOpError(
291 "includes preconditions whose conditions cannot be derived from a struct member value"
292 );
293 }
294 return includeOp.emitOpError(
295 "includes preconditions whose conditions cannot be derived from a function return value"
296 );
297 }();
298
299 for (const ForbiddenIncludedPrecondition &failure : failures) {
300 if (failure.calleePreconditionLoc) {
301 diag.attachNote(failure.calleePreconditionLoc) << "included precondition triggered here";
302 }
303 for (Location sourceLoc : failure.sourceLocs) {
304 diag.attachNote(sourceLoc) << "forbidden struct member value originates here";
305 }
306 }
307 return diag;
308}
309
310} // namespace
311
312namespace llzk::verif {
313
314//===------------------------------------------------------------------===//
315// ContractOp
316//===------------------------------------------------------------------===//
317
318void ContractOp::initializeEmptyBody(
319 OpBuilder &builder, OperationState &state, FunctionType functionType
320) {
321 Region *body = state.addRegion();
322 auto *entryBlock = new Block();
323
324 SmallVector<Location> argLocs(functionType.getNumInputs(), state.location);
325 entryBlock->addArguments(functionType.getInputs(), argLocs);
326 body->push_back(entryBlock);
327
328 ContractOp::ensureTerminator(*body, builder, state.location);
329}
330
332 OpBuilder &odsBuilder, OperationState &odsState, StringRef name, llvm::StringRef target
333) {
334 build(odsBuilder, odsState, name, SymbolRefAttr::get(odsBuilder.getContext(), target));
335}
336
338 ::mlir::OpBuilder &odsBuilder, ::mlir::OperationState &odsState, ::llvm::StringRef name,
339 ::mlir::SymbolRefAttr target
340) {
341 // Any errors here in the construction from the target information are not
342 // reported here, but will instead be reported when the verify function fails
343 // to verify this op.
344 SymbolTableCollection tables;
345 // Find the target of the contract
346 FailureOr<SymbolLookupResultUntyped> targetRes =
347 lookupTopLevelSymbol(tables, target, odsBuilder.getBlock()->getParentOp());
348 if (failed(targetRes)) {
349 return;
350 }
351 Operation *targetOp = targetRes->get();
352 if (!isValidTarget(targetOp)) {
353 return;
354 }
355 FailureOr<TargetTypeInfo> infoRes = getTargetTypeInfo(targetOp);
356 if (failed(infoRes)) {
357 return;
358 }
359 TargetTypeInfo &info = *infoRes;
360 build(odsBuilder, odsState, name, target, info.funcType, info.argAttrs);
361}
362
363bool ContractOp::hasArgPublicAttr(unsigned index) {
364 if (index < this->getNumArguments()) {
365 DictionaryAttr res = function_interface_impl::getArgAttrDict(*this, index);
366 return res ? res.contains(PublicAttr::name) : false;
367 }
368 return false;
369}
370
371bool ContractOp::hasArgName(unsigned index) { return static_cast<bool>(getArgNameAttr(index)); }
372
373std::optional<StringAttr> ContractOp::getArgNameAttr(unsigned index) {
374 if (index >= getNumArguments()) {
375 return std::nullopt;
376 }
377 if (StringAttr attr = getArgAttrOfType<StringAttr>(index, ARG_NAME_ATTR_NAME)) {
378 return attr;
379 }
380 return std::nullopt;
381}
382
383void ContractOp::setArgNameAttr(unsigned index, const StringAttr &attr) {
384 assert(index < getNumArguments() && "argument index out of range");
385 setArgAttr(index, ARG_NAME_ATTR_NAME, attr);
386}
387
388void ContractOp::setArgName(unsigned index, llvm::StringRef name) {
389 setArgNameAttr(index, StringAttr::get(getContext(), name));
390}
391
392SymbolRefAttr ContractOp::getFullyQualifiedName(bool requireParent) {
393 return llzk::getFullyQualifiedName(*this, requireParent);
394}
395
396LogicalResult ContractOp::verifySymbolUses(SymbolTableCollection &tables) {
397 // Verify the target of the contract
398 FailureOr<ModuleOp> rootRes = getRootModule(getOperation());
399 if (failed(rootRes)) {
400 return emitOpError().append("could not lookup root module");
401 }
402 FailureOr<SymbolLookupResultUntyped> targetRes =
403 lookupTopLevelSymbol(tables, getTargetAttr(), rootRes->getOperation());
404 if (failed(targetRes)) {
405 return emitOpError().append("could not find target \"@", getTarget(), "\"");
406 }
407
408 FunctionType contractTy = getFunctionType();
409 // Verify the symbols in the contract argument
410 if (failed(verifyTypeResolution(tables, *this, contractTy))) {
411 // verifyTypeResolution already reports error messages
412 return failure();
413 }
414
415 // Verify the target symbol
416 Operation *targetOp = targetRes->get();
417 if (!isValidTarget(targetOp)) {
418 return emitOpError()
419 .append("target \"", getTargetAttr(), "\" is not a supported contract target")
420 .attachNote(targetOp->getLoc())
421 .append("target defined here");
422 }
423 if (auto contractParentTemplate = getParentOfType<TemplateOp>(*this)) {
424 auto targetParentTemplate = getParentOfType<TemplateOp>(targetOp);
425 if (targetParentTemplate != contractParentTemplate) {
426 InFlightDiagnostic diag = emitOpError().append(
427 "contract nested in template \"@", contractParentTemplate.getSymName(),
428 "\" must target a symbol in the same template"
429 );
430 if (targetParentTemplate) {
431 diag.attachNote(targetParentTemplate.getLoc()).append("target template defined here");
432 } else {
433 diag.attachNote(targetOp->getLoc()).append("target defined here");
434 }
435 return diag;
436 }
437 }
438 FailureOr<TargetTypeInfo> targetInfoRes = getTargetTypeInfo(targetOp);
439 if (failed(targetInfoRes)) {
440 // The struct verifier reports malformed struct bodies; avoid cascading diagnostics here.
441 // Returning failure() would actually cause the expected diagnostic from the first failure
442 // to be suppressed, so we return success() to avoid that.
443 if (isa<StructDefOp>(targetOp)) {
444 return success();
445 }
446 return emitOpError()
447 .append("unsupported target type \"", targetOp->getName(), "\"")
448 .attachNote(targetOp->getLoc())
449 .append("target defined here");
450 }
451 TargetTypeInfo &targetInfo = *targetInfoRes;
452 UnificationMap unifications;
453 bool unifies =
454 functionTypesUnify(contractTy, targetInfo.funcType, targetRes->getNamespace(), &unifications);
455 if (!unifies || hasConflictingUnifications(unifications)) {
456 return emitOpError()
457 .append("contract type does not match target type")
458 .attachNote(targetOp->getLoc())
459 .append("target defined here");
460 }
461 if (targetInfo.argAttrs != getArgAttrsAttr()) {
462 return emitOpError()
463 .append(
464 "contract arg attributes ", getArgAttrsAttr(), " does not match target arg attributes ",
465 targetInfo.argAttrs
466 )
467 .attachNote(targetOp->getLoc())
468 .append("target defined here");
469 }
470
471 return success();
472}
473
474// Parse the ContractOp syntax using the built-in parsing of function-like
475// operations. We'll verify contract-specific restrictions in `verify`.
476ParseResult ContractOp::parse(OpAsmParser &parser, OperationState &result) {
477 StringAttr typeAttrName = getFunctionTypeAttrName(result.name);
478 StringAttr argAttrsName = getArgAttrsAttrName(result.name);
479
480 SmallVector<OpAsmParser::Argument> entryArgs;
481 SmallVector<DictionaryAttr> resultAttrs;
482 SmallVector<Type> resultTypes;
483 auto &builder = parser.getBuilder();
484
485 // Parse the name as a symbol.
486 StringAttr nameAttr;
487 if (parser.parseSymbolName(nameAttr, SymbolTable::getSymbolAttrName(), result.attributes)) {
488 return failure();
489 }
490
491 // Parse the target symbol
492 if (parser.parseKeyword("for")) {
493 return failure();
494 }
495
496 SymbolRefAttr targetAttr;
497 if (parser.parseCustomAttributeWithFallback(
498 targetAttr, parser.getBuilder().getType<::mlir::NoneType>()
499 )) {
500 return failure();
501 }
502 if (!targetAttr) {
503 return failure();
504 }
505 result.getOrAddProperties<ContractOp::Properties>().target = targetAttr;
506
507 // Parse the function signature.
508 SMLoc signatureLocation = parser.getCurrentLocation();
509 bool isVariadic = false;
510
511 if (function_interface_impl::parseFunctionSignature(
512 parser, /*allowVariadic*/ false, entryArgs, isVariadic, resultTypes, resultAttrs
513 )) {
514 return failure();
515 }
516 assert(isVariadic == false);
517 // There should be no return types or attributes.
518 if (!resultTypes.empty() || !resultAttrs.empty()) {
519 return failure();
520 }
521
522 std::string errorMessage;
523 SmallVector<Type> argTypes;
524 argTypes.reserve(entryArgs.size());
525 for (auto &arg : entryArgs) {
526 argTypes.push_back(arg.type);
527 }
528 Type type = builder.getFunctionType(argTypes, resultTypes);
529 if (!type) {
530 return parser.emitError(signatureLocation)
531 << "failed to construct function type" << (errorMessage.empty() ? "" : ": ")
532 << errorMessage;
533 }
534 result.addAttribute(typeAttrName, TypeAttr::get(type));
535
536 // If function attributes are present, parse them.
537 NamedAttrList parsedAttributes;
538 SMLoc attributeDictLocation = parser.getCurrentLocation();
539 if (parser.parseOptionalAttrDictWithKeyword(parsedAttributes)) {
540 return failure();
541 }
542
543 // Disallow attributes that are inferred from elsewhere in the attribute
544 // dictionary.
545 for (StringRef disallowed :
546 {SymbolTable::getVisibilityAttrName(), SymbolTable::getSymbolAttrName(),
547 typeAttrName.getValue()}) {
548 if (parsedAttributes.get(disallowed)) {
549 return parser.emitError(attributeDictLocation, "'")
550 << disallowed
551 << "' is an inferred attribute and should not be specified in the "
552 "explicit attribute dictionary";
553 }
554 }
555 result.attributes.append(parsedAttributes);
556
557 // Add the attributes to the function arguments.
558 function_interface_impl::addArgAndResultAttrs(
559 builder, result, entryArgs, resultAttrs, argAttrsName,
560 /*resAttrsName*/ StringAttr::get(parser.getContext())
561 );
562
563 // Parse the required contract body.
564 auto *body = result.addRegion();
565 SMLoc loc = parser.getCurrentLocation();
566 if (parser.parseRegion(
567 *body, entryArgs,
568 /*enableNameShadowing=*/false
569 )) {
570 return failure();
571 }
572
573 // Contract body was parsed, make sure its not empty.
574 if (body->empty()) {
575 return parser.emitError(loc, "expected non-empty contract body");
576 }
577
578 ContractOp::ensureTerminator(*body, parser.getBuilder(), result.location);
579
580 return success();
581}
582
583void ContractOp::print(OpAsmPrinter &p) {
584 // Print the operation and the contract name.
585 p << ' ';
586 p.printSymbolName(getSymName());
587
588 // Print the name of the contract's target.
589 p << " for ";
590 p.printAttributeWithoutType(getTarget());
591 p << ' ';
592
593 ArrayRef<Type> argTypes = getArgumentTypes();
594 function_interface_impl::printFunctionSignature(
595 p, *this, argTypes, /*isVariadic*/ false, /*resultTypes*/ ArrayRef<Type>()
596 );
597 function_interface_impl::printFunctionAttributes(
598 p, *this,
600 );
601 // Print the body.
602 Region &body = getRegion();
603 p << ' ';
604 p.printRegion(
605 body, /*printEntryBlockArgs=*/false,
606 /*printBlockTerminators=*/false
607 );
608}
609
610LogicalResult ContractOp::verify() {
611 OwningEmitErrorFn emitErrorFunc = getEmitOpErrFn(this);
612
613 if ((*this)->hasAttr(ARG_NAME_ATTR_NAME)) {
614 return emitOpError() << '\'' << ARG_NAME_ATTR_NAME << "' is only valid on function arguments";
615 }
616
617 if (ArrayAttr argAttrs = getAllArgAttrs()) {
618 llvm::DenseSet<StringAttr> seenNames;
619 for (auto [i, attr] : llvm::enumerate(argAttrs)) {
620 auto dictAttr = llvm::dyn_cast<DictionaryAttr>(attr);
621 if (!dictAttr) {
622 continue;
623 }
624 Attribute argNameAttr = dictAttr.get(ARG_NAME_ATTR_NAME);
625 if (!argNameAttr) {
626 continue;
627 }
628 auto argName = llvm::dyn_cast<StringAttr>(argNameAttr);
629 if (!argName) {
630 return emitOpError() << '\'' << ARG_NAME_ATTR_NAME << "' on argument " << i
631 << " must be a string attribute";
632 }
633 if (!llvm::isa<NoneType>(argName.getType())) {
634 return emitOpError() << '\'' << ARG_NAME_ATTR_NAME << "' on argument " << i
635 << " must not have an explicit type";
636 }
637 if (argName.getValue().empty()) {
638 return emitOpError() << '\'' << ARG_NAME_ATTR_NAME << "' on argument " << i
639 << " must not be empty";
640 }
641 if (!seenNames.insert(argName).second) {
642 return emitOpError() << "duplicate '" << ARG_NAME_ATTR_NAME << "' value \""
643 << argName.getValue() << "\" on argument " << i;
644 }
645 }
646 }
647
648 // Unlike for FuncDefOps, we don't verify that the inputs are valid LLZK types,
649 // as we will check that the args match the target arguments in `verifySymbolUses()`
650
651 // Ensure that the contract does not contain nested modules, structs, or functions.
652 WalkResult res = this->walk<WalkOrder::PreOrder>([this](Operation *op) {
653 if (isa<ModuleOp, TemplateOp, FuncDefOp, StructDefOp>(op)) {
654 getEmitOpErrFn(op)().append(
655 "cannot be nested within '", getOperation()->getName(), "' operations"
656 );
657 return WalkResult::interrupt();
658 }
659 return WalkResult::advance();
660 });
661 return failure(res.wasInterrupted());
662}
663
665 // Verify precondition restrictions in the region verifier so that ops contained
666 // within the contract are verified before these checks. This avoids segfaults
667 // when there are malformed inner ops and instead allows appropriate inner diagnostics
668 // to be generated first. In sum, we can rest assured that the ops we traverse and
669 // analyze here have already been verified.
670
671 SmallVector<PreconditionOpInterface> preconditionOps =
672 walkCollect<PreconditionOpInterface>(*this);
673 SmallVector<IncludeOp> includeOps = walkCollect<IncludeOp>(*this);
674 if (preconditionOps.empty() && includeOps.empty()) {
675 return success();
676 }
677
678 ModuleOp module = getOperation()->getParentOfType<ModuleOp>();
679 if (!module) {
680 return emitOpError("must have a parent module to analyze condition provenance");
681 }
682
683 for (PreconditionOpInterface preCond : preconditionOps) {
684 if (auto forbidden = classifyForbiddenConditionProvenance(module, preCond, *this)) {
685 return emitForbiddenPrecondition(
686 preCond, forbidden->kind, forbidden->sourceLocs.getArrayRef()
687 );
688 }
689 }
690
691 for (IncludeOp includeOp : includeOps) {
692 if (auto forbidden = classifyForbiddenIncludedPrecondition(module, includeOp)) {
693 return emitForbiddenIncludedPreconditions(forbidden->includeOp, forbidden->failures);
694 }
695 }
696
697 return success();
698}
699
700FailureOr<SymbolLookupResult<StructDefOp>>
701ContractOp::getStructTarget(SymbolTableCollection &tables) {
703 tables, getTarget(), getParentOfType<ModuleOp>(getOperation()), /*reportMissing*/ false
704 );
705}
706
707FailureOr<SymbolLookupResult<FuncDefOp>> ContractOp::getFuncTarget(SymbolTableCollection &tables) {
709 tables, getTarget(), getParentOfType<ModuleOp>(getOperation()), /*reportMissing*/ false
710 );
711}
712
713FailureOr<SymbolLookupResult<ContractTargetOpInterface>>
714ContractOp::getTargetOp(SymbolTableCollection &tables) {
716 tables, getTarget(), getParentOfType<ModuleOp>(getOperation()), /*reportMissing*/ false
717 );
718}
719
720FailureOr<Value> ContractOp::getSelfValue() {
721 if (failed(getStructTarget()) || getNumArguments() == 0) {
722 return failure();
723 }
724 return getArgument(0);
725}
726
727//===------------------------------------------------------------------===//
728// IncludeOp
729//===------------------------------------------------------------------===//
730
732 OpBuilder &odsBuilder, OperationState &odsState, SymbolRefAttr callee, ValueRange argOperands,
733 ArrayRef<Attribute> templateParams
734) {
735 odsState.addOperands(argOperands);
737 odsBuilder, odsState, llzk::checkedCast<int32_t>(argOperands.size())
738 );
739 props.setCallee(callee);
740 addTemplateParams<IncludeOp>(odsBuilder, props, templateParams);
741}
742
744 OpBuilder &odsBuilder, OperationState &odsState, SymbolRefAttr callee,
745 ArrayRef<ValueRange> mapOperands, DenseI32ArrayAttr numDimsPerMap, ValueRange argOperands,
746 ArrayRef<Attribute> templateParams
747) {
748 odsState.addOperands(argOperands);
750 odsBuilder, odsState, mapOperands, numDimsPerMap,
751 llzk::checkedCast<int32_t>(argOperands.size())
752 );
753 props.setCallee(callee);
754 addTemplateParams<IncludeOp>(odsBuilder, props, templateParams);
755}
756
758 Attribute paramFromIncludeOp, TemplateParamOp targetParam
759) {
760 // A wildcard `?` (represented as kDynamic) defers inference to a later pass.
761 // It is only valid for parameters with a `!poly.tvar` type restriction.
762 if (auto intAttr = llvm::dyn_cast<IntegerAttr>(paramFromIncludeOp)) {
763 if (isDynamic(intAttr)) {
764 std::optional<Type> declaredType = targetParam.getTypeOpt();
765 if (!declaredType || !llvm::isa<TypeVarType>(*declaredType)) {
766 auto diag = this->emitOpError().append(
767 "wildcard `?` can only be used for template parameters with `!poly.tvar` "
768 "type restriction, but parameter \"@",
769 targetParam.getName(), "\" has "
770 );
771 if (declaredType) {
772 diag.append("type restriction ", *declaredType);
773 } else {
774 diag.append("no type restriction");
775 }
776 return diag;
777 }
778 return success();
779 }
780 }
781 if (std::optional<Type> declaredType = targetParam.getTypeOpt()) {
782 // Note: `declaredType` is restricted by `isValidConstReadType()`
783 bool compatible = false;
784 if (llvm::isa<TypeVarType>(*declaredType)) {
785 compatible = llvm::isa<TypeAttr>(paramFromIncludeOp);
786 } else if (llvm::isa<FeltType>(*declaredType)) {
787 compatible = llvm::isa<FeltConstAttr, IntegerAttr>(paramFromIncludeOp) &&
788 isValidConstReadType(llvm::cast<TypedAttr>(paramFromIncludeOp).getType());
789 } else if (llvm::isa<IndexType, IntegerType>(*declaredType)) {
790 // Note: Just like struct type instantiation, there is no restriction on passing a
791 // larger value to an `i1`. The flattening pass will treat 0 as false and any other
792 // value as true (but give a warning if it's not 1).
793 compatible = llvm::isa<IntegerAttr>(paramFromIncludeOp) &&
794 isValidConstReadType(llvm::cast<TypedAttr>(paramFromIncludeOp).getType());
795 } else {
796 llvm_unreachable("inconsistent with `isValidConstReadType()`");
797 }
798 if (!compatible) {
799 return this->emitOpError().append(
800 "instantiation value '", paramFromIncludeOp, "' is not compatible with parameter \"@",
801 targetParam.getName(), "\" type restriction ", *declaredType
802 );
803 }
804 }
805 return success();
806}
807
809 llvm::iterator_range<Region::op_iterator<TemplateParamOp>> targetParamDefs
810) {
811 ArrayAttr callParams = this->getTemplateParamsAttr();
812 assert(!isNullOrEmpty(callParams) && "pre-condition");
813 assert((callParams.size() == llvm::range_size(targetParamDefs)) && "pre-condition");
814
815 for (auto [paramOp, attr] : llvm::zip_equal(targetParamDefs, callParams.getValue())) {
816 if (failed(verifyTemplateParamCompatibility(attr, paramOp))) {
817 return failure();
818 }
819 }
820 return success();
821}
822
824 llvm::iterator_range<Region::op_iterator<TemplateParamOp>> targetParamDefs,
825 const UnificationMap &unifications
826) {
827 ArrayAttr callParams = this->getTemplateParamsAttr();
828 assert(!isNullOrEmpty(callParams) && "pre-condition");
829 assert((callParams.size() == llvm::range_size(targetParamDefs)) && "pre-condition");
830
831 for (auto [paramOp, attr] : llvm::zip_equal(targetParamDefs, callParams.getValue())) {
832 // Skip wildcards (`?` / kDynamic) - their value will be resolved by a later inference pass.
833 if (auto intAttr = llvm::dyn_cast<IntegerAttr>(attr)) {
834 if (isDynamic(intAttr)) {
835 continue;
836 }
837 }
838 auto it = unifications.find({FlatSymbolRefAttr::get(paramOp.getNameAttr()), Side::RHS});
839 if (it != unifications.end() && !typeParamsUnify({attr}, {it->second})) {
840 return this->emitOpError().append(
841 "template instantiation value '", attr, "' for parameter \"@", paramOp.getName(),
842 "\" conflicts with value '", it->second, "' inferred from function type signature"
843 );
844 }
845 }
846 return success();
847}
848
849namespace {
850
851struct IncludeOpVerifier {
852 explicit IncludeOpVerifier(IncludeOp *c) : includeOp(c) {}
853 virtual ~IncludeOpVerifier() = default;
854
855 LogicalResult verify() {
856 // Rather than immediately returning on failure, we check all verifier steps and aggregate to
857 // provide as many errors are possible in a single verifier run.
858 LogicalResult aggregateResult = success();
859 if (failed(verifyInputs())) {
860 aggregateResult = failure();
861 }
862 if (failed(verifyTemplateParams())) {
863 aggregateResult = failure();
864 }
865 return aggregateResult;
866 }
867
868protected:
869 IncludeOp *includeOp;
870
871 virtual LogicalResult verifyInputs() = 0;
872 virtual LogicalResult verifyTemplateParams() = 0;
873
874 LogicalResult verifyNoTemplateInstantiations() {
875 if (!isNullOrEmpty(includeOp->getTemplateParamsAttr())) {
876 return includeOp->emitOpError().append(
877 "can only have template instantiations when targeting a templated contract"
878 );
879 }
880 return success();
881 }
882};
883
884struct KnownTargetVerifier : public IncludeOpVerifier {
885 KnownTargetVerifier(IncludeOp *c, SymbolLookupResult<ContractOp> &&tgtRes)
886 : IncludeOpVerifier(c), tgt(*tgtRes), tgtType(tgt.getFunctionType()),
887 includeSymNames(tgtRes.getNamespace()) {}
888
889 LogicalResult verifyInputs() override {
890 return verifyTypesMatch(includeOp->getArgOperands().getTypes(), tgtType.getInputs(), "operand");
891 }
892
893 LogicalResult verifyTemplateParams() override {
894 Operation *tgtOp = tgt.getOperation();
895 if (TemplateOp tgtOpParent = getParentOfType<TemplateOp>(tgtOp)) {
896 // When the target function is a free function within a TemplateOp, the IncludeOp may have
897 // template parameter instantiations that must be checked against the template parameters.
898 // - If the function type signature references all template parameters, then the parameter
899 // instantiation list on the IncludeOp is optional, otherwise it's required.
900 // - If present, the instantiation list must provide a value for every template parameter
901 // and the value must be type-compatible with the parameter's declared type (if any).
902 // - If present, the instantiation list must result in a function type signature that can
903 // be unified with the IncludeOp's operand and result types.
904 auto realParams = tgtOpParent.getConstOps<TemplateParamOp>();
905 ArrayAttr callParams = includeOp->getTemplateParamsAttr();
906
907 // When there is no instantiation list, just ensure that it's not required.
908 if (isNullOrEmpty(callParams)) {
909 llvm::SmallDenseSet<SymbolRefAttr> referencedInSignature;
910 llzk::getSymbolsUsedIn(tgtType.getInputs(), referencedInSignature);
911 llzk::getSymbolsUsedIn(tgtType.getResults(), referencedInSignature);
912
913 bool allParamsReferenced = llvm::all_of(realParams, [&](TemplateParamOp p) {
914 return referencedInSignature.contains(FlatSymbolRefAttr::get(p.getNameAttr()));
915 });
916 if (allParamsReferenced) {
917 return success();
918 }
919 return includeOp->emitOpError().append(
920 "must provide template instantiation parameters when calling \"@", tgt.getSymName(),
921 "\" because not all template parameters of \"@", tgtOpParent.getSymName(),
922 "\" appear in the function type signature"
923 );
924 }
925
926 // Ensure `forceIntAttrTypes()` was successful on the IncludeOp's template parameters.
927 if (failed(llzk::forceIntAttrTypes(callParams.getValue(), [this] {
928 return llzk::InFlightDiagnosticWrapper(this->includeOp->emitOpError());
929 }))) {
930 return failure();
931 }
932
933 // The instantiation list is present. Check it has exactly one entry per template param.
934 size_t numTemplateParams = llvm::range_size(realParams);
935 if (callParams.size() != numTemplateParams) {
936 return includeOp->emitOpError().append(
937 "template instantiation has ", callParams.size(), " parameter(s) but \"@",
938 tgtOpParent.getSymName(), "\" expects ", numTemplateParams, " template parameter(s)"
939 );
940 }
941
942 // Check type compatibility of each provided value with the declared parameter type (if any).
943 if (failed(includeOp->verifyTemplateParamCompatibility(realParams))) {
944 return failure();
945 }
946
947 // Check that the provided instantiation values are consistent with what type unification
948 // of the target function types against the call's operand and result types would determine.
949 FailureOr<UnificationMap> unifyResult = includeOp->unifyTypeSignature(tgtType);
950 // This is already checked by `verifyInputs()`, but `verifyTemplateParams()` is called
951 // even if `verifyInputs()` fails for error aggregation, so we still need to return
952 // early here.
953 if (failed(unifyResult)) {
954 return failure();
955 }
956 return includeOp->verifyTemplateParamsMatchInferred(realParams, unifyResult.value());
957 } else {
958 // Non-template functions cannot contain template parameter instantiations.
959 return verifyNoTemplateInstantiations();
960 }
961 }
962
963private:
964 template <typename T>
965 LogicalResult
966 verifyTypesMatch(ValueTypeRange<T> includeOpTypes, ArrayRef<Type> tgtTypes, const char *aspect) {
967 if (tgtTypes.size() != includeOpTypes.size()) {
968 return includeOp->emitOpError()
969 .append("incorrect number of ", aspect, "s for callee, expected ", tgtTypes.size())
970 .attachNote(tgt.getLoc())
971 .append("callee defined here");
972 }
973 for (unsigned i = 0, e = tgtTypes.size(); i != e; ++i) {
974 if (!typesUnify(includeOpTypes[i], tgtTypes[i], includeSymNames)) {
975 return includeOp->emitOpError().append(
976 aspect, " type mismatch: expected type ", tgtTypes[i], ", but found ",
977 includeOpTypes[i], " for ", aspect, " number ", i
978 );
979 }
980 }
981 return success();
982 }
983
984 ContractOp tgt;
985 FunctionType tgtType;
986 std::vector<llvm::StringRef> includeSymNames;
987};
988
989} // namespace
990
991LogicalResult IncludeOp::verifySymbolUses(SymbolTableCollection &tables) {
992 // First, verify symbol resolution in all input and output types.
993 if (failed(verifyTypeResolution(tables, *this, getTypeSignature()))) {
994 return failure(); // verifyTypeResolution() already emits a sufficient error message
995 }
996
997 // Check that the callee attribute was specified.
998 SymbolRefAttr calleeAttr = getCalleeAttr();
999 if (!calleeAttr) {
1000 return emitOpError("requires a 'callee' symbol reference attribute");
1001 }
1002
1003 // If the callee references a parameter of the template where this call appears, perform
1004 // the subset of checks that can be done even though the target is unknown.
1005 if (calleeAttr.getNestedReferences().size() == 1) {
1006 if (TemplateOp parent = getParentOfType<TemplateOp>(*this)) {
1007 if (auto constParam = parent.getConstNamed<TemplateParamOp>(calleeAttr.getRootReference())) {
1008 return this->emitError("expected parameterized callee to target a struct function")
1009 .attachNote(constParam->getLoc())
1010 .append(
1011 " (i.e. \"@", FUNC_NAME_PRODUCT, "\", \"@", FUNC_NAME_COMPUTE, "\", or \"@",
1012 FUNC_NAME_CONSTRAIN, "\")"
1013 );
1014 }
1015 }
1016 }
1017
1018 // Otherwise, callee must be specified via full path from the root module. Perform the full set of
1019 // checks against the known target function.
1021 tables, calleeAttr, getParentOfType<ModuleOp>(getOperation())
1022 );
1023 if (failed(tgtOpt)) {
1024 return this->emitError() << "expected '" << ContractOp::getOperationName() << "' named \""
1025 << calleeAttr << '"';
1026 }
1027 return KnownTargetVerifier(this, std::move(*tgtOpt)).verify();
1028}
1029
1031 return FunctionType::get(getContext(), getArgOperands().getTypes(), /*results*/ {});
1032}
1033
1034FailureOr<UnificationMap> IncludeOp::unifyTypeSignature(FunctionType other) {
1035 UnificationMap unifications;
1036 if (functionTypesUnify(getTypeSignature(), other, {}, &unifications)) {
1037 return unifications;
1038 }
1039 return failure();
1040}
1041
1042FailureOr<SymbolLookupResult<ContractOp>>
1043IncludeOp::getCalleeTarget(SymbolTableCollection &tables) {
1044 Operation *thisOp = this->getOperation();
1045 auto root = getRootModule(thisOp);
1046 assert(succeeded(root));
1047 return llzk::lookupSymbolIn<ContractOp>(tables, getCallee(), root->getOperation(), thisOp);
1048}
1049
1051 SymbolTableCollection tables;
1052 auto callee = getCalleeTarget(tables);
1053 return succeeded(callee) && callee->get().hasStructTarget();
1054}
1055
1057 SymbolTableCollection tables;
1058 auto callee = getCalleeTarget(tables);
1059 assert(succeeded(callee) && "include callee must resolve");
1060 if (!callee->get().hasStructTarget()) {
1061 return nullptr;
1062 }
1063 assert(getNumOperands() > 0 && "include op must have a self operand");
1064 return getOperand(0);
1065}
1066
1068CallInterfaceCallable IncludeOp::getCallableForCallee() { return getCalleeAttr(); }
1069
1071void IncludeOp::setCalleeFromCallable(CallInterfaceCallable callee) {
1072 setCalleeAttr(llvm::cast<SymbolRefAttr>(callee));
1073}
1074
1075SmallVector<ValueRange> IncludeOp::toVectorOfValueRange(OperandRangeRange input) {
1076 llvm::SmallVector<ValueRange, 4> output;
1077 output.reserve(input.size());
1078 output.insert(output.end(), input.begin(), input.end());
1079 return output;
1080}
1081
1082Operation *IncludeOp::resolveCallableInTable(SymbolTableCollection *symbolTable) {
1083 FailureOr<SymbolLookupResult<ContractOp>> res =
1084 llzk::resolveCallable<ContractOp>(*symbolTable, *this);
1085 if (failed(res)) {
1086 return nullptr;
1087 }
1088 if (res->isManaged()) {
1089 this->emitWarning(
1090 "IncludeOp::resolveCallableInTable: cannot return "
1091 "pointer to a managed Operation since it would cause memory errors. "
1092 "Consider running -llzk-inline-includes to avoid encountering managed Operations."
1093 );
1094 return nullptr;
1095 }
1096 return res->get();
1097}
1098
1100 SymbolTableCollection tables;
1101 return resolveCallableInTable(&tables);
1102}
1103
1104//===------------------------------------------------------------------===//
1105// InvariantOp
1106//===------------------------------------------------------------------===//
1107
1109 OpBuilder &odsBuilder, OperationState &odsState, StringRef loop_name,
1110 ArrayRef<Type> loop_arg_types, ArrayRef<Location> loop_arg_locs
1111) {
1112 odsState.getOrAddProperties<InvariantOp::Properties>().loop_name =
1113 odsBuilder.getStringAttr(loop_name);
1114 odsState.getOrAddProperties<InvariantOp::Properties>().loop_arg_types =
1115 odsBuilder.getTypeArrayAttr(loop_arg_types);
1116 auto region = std::make_unique<Region>();
1117 auto &block = region->emplaceBlock();
1118 block.addArguments(loop_arg_types, loop_arg_locs);
1119 odsState.regions.push_back(std::move(region));
1120}
1121
1122namespace {
1123static LogicalResult verifyArgTypes(InvariantTargetOpInterface target, InvariantOp *op) {
1124 auto targetArgTypes = target.getArgumentTypes();
1125 auto declaredTypes = op->getLoopArgTypes().getValue();
1126 auto bodyArgTypes = op->getBody()->getArgumentTypes();
1127
1128 if (targetArgTypes.size() != declaredTypes.size()) {
1129 return op->emitOpError() << "target has " << targetArgTypes.size()
1130 << " arguments but invariant declared " << declaredTypes.size();
1131 }
1132 if (bodyArgTypes.size() != declaredTypes.size()) {
1133 return op->emitOpError() << "invariant body has " << targetArgTypes.size()
1134 << " arguments but declared " << declaredTypes.size();
1135 }
1136
1137 bool failed = false;
1138 for (auto [n, types] :
1139 llvm::enumerate(llvm::zip_equal(targetArgTypes, bodyArgTypes, declaredTypes))) {
1140 auto [targetType, bodyArgType, declaredType] = types;
1141
1142 if (targetType != mlir::cast<TypeAttr>(declaredType).getValue()) {
1143 failed = true;
1144 op->emitOpError() << "target argument #" << n << " expected type " << targetType
1145 << " but invariant declared type " << declaredType;
1146 }
1147 if (bodyArgType != mlir::cast<TypeAttr>(declaredType).getValue()) {
1148 failed = true;
1149 op->emitOpError() << "invariant argument #" << n << " expected type " << targetType
1150 << " but invariant declared type " << declaredType;
1151 }
1152 }
1153
1154 return failure(failed);
1155}
1156} // namespace
1157
1158LogicalResult InvariantOp::verify() {
1159 auto invariantTarget = getTarget();
1160 if (failed(invariantTarget)) {
1161 return failure();
1162 }
1163
1164 return verifyArgTypes(*invariantTarget, this);
1165}
1166
1167ParseResult InvariantOp::parse(OpAsmParser &parser, OperationState &result) {
1168 if (failed(parser.parseKeyword("for"))) {
1169 return failure();
1170 }
1171
1172 // Parse the loop label as a symbol.
1173 StringAttr loopNameAttr;
1174 if (parser.parseSymbolName(loopNameAttr)) {
1175 return failure();
1176 }
1177 result.getOrAddProperties<InvariantOp::Properties>().loop_name = loopNameAttr;
1178
1179 // Parse the function signature.
1180 bool isVariadic = false;
1181 SmallVector<OpAsmParser::Argument> entryArgs;
1182 SmallVector<DictionaryAttr> resultAttrs;
1183 SmallVector<Type> resultTypes;
1184
1185 if (function_interface_impl::parseFunctionSignature(
1186 parser, /*allowVariadic*/ false, entryArgs, isVariadic, resultTypes, resultAttrs
1187 )) {
1188 return failure();
1189 }
1190 assert(isVariadic == false);
1191 // There should be no return types or attributes.
1192 if (!resultTypes.empty() || !resultAttrs.empty()) {
1193 return failure();
1194 }
1195
1196 SmallVector<Type> argTypes = llvm::map_to_vector(entryArgs, [](auto arg) { return arg.type; });
1197 result.getOrAddProperties<InvariantOp::Properties>().loop_arg_types =
1198 parser.getBuilder().getTypeArrayAttr(argTypes);
1199
1200 auto *body = result.addRegion();
1201 SMLoc loc = parser.getCurrentLocation();
1202 if (parser.parseRegion(
1203 *body, entryArgs,
1204 /*enableNameShadowing=*/false
1205 )) {
1206 return failure();
1207 }
1208
1209 if (body->empty()) {
1210 return parser.emitError(loc, "expected non-empty invariant body");
1211 }
1212
1213 return success();
1214}
1215
1216void InvariantOp::print(OpAsmPrinter &p) {
1217 // Print the name of the invariants's target.
1218 p << " for ";
1219 p.printSymbolName(getLoopName());
1220 p << "(";
1221 llvm::interleave(getBody()->getArguments(), [&p](auto arg) {
1222 p.printRegionArgument(arg);
1223 }, [&p]() { p << ", "; });
1224 p << ") ";
1225 // Print the body.
1226 Region &body = getRegion();
1227 p.printRegion(
1228 body, /*printEntryBlockArgs=*/false,
1229 /*printBlockTerminators=*/true
1230 );
1231}
1232
1234 return this->getOperation()->getParentOfType<ContractOp>();
1235}
1236
1237FailureOr<InvariantTargetOpInterface> InvariantOp::getTarget() {
1238 auto target = getParentContract().getTargetOp();
1239 if (failed(target)) {
1240 return failure();
1241 }
1242 SmallVector<InvariantTargetOpInterface> matches;
1243 for (auto invariantTarget : target->get().getLoops()) {
1244 auto targetLabel = invariantTarget.getLabel();
1245 if (succeeded(targetLabel) && *targetLabel == getLoopName()) {
1246 matches.push_back(invariantTarget);
1247 }
1248 }
1249
1250 if (matches.size() == 0) {
1251 return emitOpError() << "no invariant target with label \"" << getLoopName()
1252 << "\" found in contract target " << target->get().getNameAttr();
1253 }
1254 if (matches.size() > 1) {
1255 return emitOpError() << "ambiguous label \"" << getLoopName() << "\" matched " << matches.size()
1256 << " invariant targets in contract target " << target->get().getNameAttr();
1257 }
1258 return matches[0];
1259}
1260
1261} // namespace llzk::verif
This file contains an analysis and utilities for determining if a verif precondition is dependent,...
::mlir::FunctionType getFunctionType()
Definition Ops.cpp.inc:984
::mlir::ArrayAttr getArgAttrsAttr()
Definition Ops.h.inc:721
::std::optional<::mlir::Type > getTypeOpt()
Definition Ops.cpp.inc:1337
::mlir::StringAttr getFunctionTypeAttrName()
Definition Ops.h.inc:395
::llvm::LogicalResult verifyRegions()
Definition Ops.cpp:664
void setArgNameAttr(unsigned index, const ::mlir::StringAttr &attr)
Set the function.arg_name attribute for the argument at the given index.
Definition Ops.cpp:383
bool hasArgName(unsigned index)
Return true iff the argument at the given index has a function.arg_name attribute.
Definition Ops.cpp:371
bool hasArgPublicAttr(unsigned index)
Return true iff the argument at the given index has pub attribute.
Definition Ops.cpp:363
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:419
::mlir::StringAttr getTargetAttrName()
Definition Ops.h.inc:411
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:396
::llvm::LogicalResult verify()
Definition Ops.cpp:610
void print(::mlir::OpAsmPrinter &p)
Definition Ops.cpp:583
::mlir::FunctionType getFunctionType()
Definition Ops.cpp.inc:558
void setArgName(unsigned index, ::llvm::StringRef name)
Set the function.arg_name attribute for the argument at the given index from a string.
Definition Ops.cpp:388
::mlir::FailureOr< SymbolLookupResult<::llzk::verif::ContractTargetOpInterface > > getTargetOp(::mlir::SymbolTableCollection &tables)
Return the operation that this contract targets, or failure if it does not target an operation that i...
::std::optional<::mlir::StringAttr > getArgNameAttr(unsigned index)
Return the function.arg_name attribute for the argument at the given index.
Definition Ops.cpp:373
::llvm::ArrayRef<::mlir::Type > getArgumentTypes()
Required by FunctionOpInterface.
Definition Ops.h.inc:564
::mlir::StringAttr getArgAttrsAttrName()
Definition Ops.h.inc:387
::mlir::SymbolRefAttr getTargetAttr()
Definition Ops.h.inc:461
::mlir::SymbolRefAttr getFullyQualifiedName(bool requireParent=true)
Return the full name for this contract from the root module, including all surrounding symbol table n...
Definition Ops.cpp:392
::mlir::FailureOr< SymbolLookupResult<::llzk::verif::ContractTargetOpInterface > > getTargetOp()
Definition Ops.h.inc:610
::mlir::FailureOr<::mlir::Value > getSelfValue()
Return the "self" value (i.e.
Definition Ops.cpp:720
::mlir::ArrayAttr getArgAttrsAttr()
Definition Ops.h.inc:471
::mlir::ParseResult parse(::mlir::OpAsmParser &parser, ::mlir::OperationState &result)
Definition Ops.cpp:476
::mlir::SymbolRefAttr getTarget()
Definition Ops.cpp.inc:553
FoldAdaptor::Properties Properties
Definition Ops.h.inc:381
::mlir::FailureOr< SymbolLookupResult< component::StructDefOp > > getStructTarget()
Definition Ops.h.inc:585
::mlir::FailureOr< SymbolLookupResult< function::FuncDefOp > > getFuncTarget()
Definition Ops.h.inc:601
static void build(::mlir::OpBuilder &odsBuilder, ::mlir::OperationState &odsState, ::mlir::StringAttr sym_name, ::mlir::SymbolRefAttr target, ::mlir::TypeAttr function_type, ::mlir::ArrayAttr arg_attrs={})
Definition Ops.cpp.inc:576
::llvm::StringRef getSymName()
Definition Ops.cpp.inc:548
::mlir::LogicalResult verifyTemplateParamCompatibility(::mlir::Attribute paramFromCallOp, ::llzk::polymorphic::TemplateParamOp targetParam)
Check type compatibility of the given template parameter value from this CallOp against the declared ...
::mlir::ArrayAttr getTemplateParamsAttr()
Definition Ops.h.inc:1295
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:991
::mlir::SymbolRefAttr getCalleeAttr()
Definition Ops.h.inc:1290
FoldAdaptor::Properties Properties
Definition Ops.h.inc:1204
::mlir::Operation::operand_range getArgOperands()
Definition Ops.h.inc:1261
void setCalleeAttr(::mlir::SymbolRefAttr attr)
Definition Ops.h.inc:1310
::mlir::FailureOr<::llzk::SymbolLookupResult<::llzk::verif::ContractOp > > getCalleeTarget(::mlir::SymbolTableCollection &tables)
Resolve and return the target Contract for this CallOp.
Definition Ops.cpp:1043
::mlir::Operation * resolveCallable()
Required by CallOpInterface.
Definition Ops.cpp:1099
::mlir::SymbolRefAttr getCallee()
Definition Ops.cpp.inc:1285
::mlir::Value getSelfValue()
Return the "self" value (i.e.
Definition Ops.cpp:1056
void setCalleeFromCallable(::mlir::CallInterfaceCallable callee)
Set the callee for this operation.
Definition Ops.cpp:1071
bool contractTargetsStruct()
Return true iff the contract targets a struct type.
Definition Ops.cpp:1050
static void build(::mlir::OpBuilder &odsBuilder, ::mlir::OperationState &odsState, ::mlir::SymbolRefAttr callee, ::mlir::ValueRange argOperands={}, ::llvm::ArrayRef<::mlir::Attribute > templateParams={})
::mlir::FunctionType getTypeSignature()
Return the FunctionType inferred from the arg operands of this CallOp.
Definition Ops.cpp:1030
::mlir::LogicalResult verifyTemplateParamsMatchInferred(::llvm::iterator_range<::mlir::Region::op_iterator<::llzk::polymorphic::TemplateParamOp > > targetParamDefs, const UnificationMap &unifications)
Verify that each template parameter value provided in this CallOp is consistent with the value inferr...
Definition Ops.cpp:823
::mlir::FailureOr< UnificationMap > unifyTypeSignature(::mlir::FunctionType other)
Attempt type unfication between the inferred FunctionType from this CallOp (as LHS) and the given Fun...
Definition Ops.cpp:1034
::mlir::Operation * resolveCallableInTable(::mlir::SymbolTableCollection *symbolTable)
Required by CallOpInterface.
Definition Ops.cpp:1082
static ::llvm::SmallVector<::mlir::ValueRange > toVectorOfValueRange(::mlir::OperandRangeRange)
Allocate consecutive storage of the ValueRange instances in the parameter so it can be passed to the ...
Definition Ops.cpp:1075
::mlir::CallInterfaceCallable getCallableForCallee()
Return the callee of this operation.
Definition Ops.cpp:1068
::mlir::FailureOr<::llzk::verif::InvariantTargetOpInterface > getTarget()
Returns the loop target.
Definition Ops.cpp:1237
::mlir::ParseResult parse(::mlir::OpAsmParser &parser, ::mlir::OperationState &result)
Definition Ops.cpp:1167
void print(::mlir::OpAsmPrinter &p)
Definition Ops.cpp:1216
::mlir::ArrayAttr getLoopArgTypes()
Definition Ops.cpp.inc:1773
::llvm::StringRef getLoopName()
Definition Ops.cpp.inc:1768
::llzk::verif::ContractOp getParentContract()
Returns the contract operation that contains this invariant.
Definition Ops.cpp:1233
static void build(::mlir::OpBuilder &odsBuilder, ::mlir::OperationState &odsState, ::mlir::StringRef loop_name, ::llvm::ArrayRef<::mlir::Type > loop_arg_types={}, ::llvm::ArrayRef<::mlir::Location > loop_arg_locs={})
Definition Ops.cpp:1108
FoldAdaptor::Properties Properties
Definition Ops.h.inc:1694
::llvm::LogicalResult verify()
Definition Ops.cpp:1158
::mlir::Region & getRegion()
Definition Ops.h.inc:1740
::mlir::SmallVector<::mlir::Type > getArgumentTypes()
Gets the types of the values that the invariant binds inside its body.
OpClass::Properties & buildInstantiationAttrs(mlir::OpBuilder &odsBuilder, mlir::OperationState &odsState, mlir::ArrayRef< mlir::ValueRange > mapOperands, mlir::DenseI32ArrayAttr numDimsPerMap, int32_t firstSegmentSize=0)
Utility for build() functions that initializes the operandSegmentSizes, mapOpGroupSizes,...
OpClass::Properties & buildInstantiationAttrsEmpty(mlir::OpBuilder &odsBuilder, mlir::OperationState &odsState, int32_t firstSegmentSize=0)
Utility for build() functions that initializes the operandSegmentSizes, mapOpGroupSizes,...
constexpr char ARG_NAME_ATTR_NAME[]
Attribute name for source-level function argument names.
Definition Ops.h:35
detail::IncludedContractSummary analyzeForbiddenIncludedOpSummary(mlir::ModuleOp module, verif::ContractOp contract, verif::IncludeOp includeOp)
Analyze whether a specific include op triggers forbidden preconditions in the callee,...
bool hasInfluence(ForbiddenPreconditionInfluence influence, ForbiddenPreconditionInfluence flag)
Return true when influence contains the requested flag.
ForbiddenPreconditionInfluenceInfo analyzeForbiddenPreconditionOpInfluenceInfo(mlir::ModuleOp module, verif::ContractOp contract, verif::PreconditionOpInterface preCondOp)
Analyze whether a precondition op depends on forbidden sources, including both its condition operand ...
constexpr char FUNC_NAME_COMPUTE[]
Symbol name for the witness generation (and resp.
Definition Constants.h:16
mlir::FailureOr< SymbolLookupResultUntyped > lookupTopLevelSymbol(mlir::SymbolTableCollection &tables, mlir::SymbolRefAttr symbol, mlir::Operation *origin, bool reportMissing=true)
constexpr char FUNC_NAME_CONSTRAIN[]
Definition Constants.h:17
FailureOr< ModuleOp > getRootModule(Operation *from)
void getSymbolsUsedIn(mlir::Type t, llvm::SmallDenseSet< mlir::SymbolRefAttr > &symbolsUsed)
Add all symbols used within the given Type to the provided set.
mlir::DenseMap< std::pair< mlir::SymbolRefAttr, Side >, mlir::Attribute > UnificationMap
Optional result from type unifications.
Definition TypeHelper.h:223
FailureOr< SmallVector< Attribute > > forceIntAttrTypes(ArrayRef< Attribute > attrList, EmitErrorFn emitError)
bool isNullOrEmpty(mlir::ArrayAttr a)
OpClass getParentOfType(mlir::Operation *op)
Return the closest surrounding parent/ancestor operation that is of type 'OpClass'.
Definition OpHelpers.h:51
constexpr char FUNC_NAME_PRODUCT[]
Definition Constants.h:18
constexpr T checkedCast(U u) noexcept
Definition Compare.h:81
mlir::FailureOr< SymbolLookupResult< T > > resolveCallable(mlir::SymbolTableCollection &symbolTable, mlir::CallOpInterface call)
Based on mlir::CallOpInterface::resolveCallable, but using LLZK lookup helpers.
LogicalResult verifyTypeResolution(SymbolTableCollection &tables, Operation *origin, Type ty)
OwningEmitErrorFn getEmitOpErrFn(mlir::Operation *op)
mlir::FailureOr< SymbolLookupResultUntyped > lookupSymbolIn(mlir::SymbolTableCollection &tables, mlir::SymbolRefAttr symbol, Within &&lookupWithin, mlir::Operation *origin, bool reportMissing=true)
bool isDynamic(IntegerAttr intAttr)
std::function< InFlightDiagnosticWrapper()> OwningEmitErrorFn
This type is required in cases like the functions below to take ownership of the lambda so it is not ...
mlir::SymbolRefAttr getFullyQualifiedName(mlir::SymbolOpInterface symbol, bool requireParent=true)
Return the full name for this symbol from the root module, including any surrounding symbol table nam...
bool typesUnify(Type lhs, Type rhs, ArrayRef< StringRef > rhsReversePrefix, UnificationMap *unifications)
bool typeParamsUnify(const ArrayRef< Attribute > &lhsParams, const ArrayRef< Attribute > &rhsParams, UnificationMap *unifications)
bool functionTypesUnify(FunctionType lhs, FunctionType rhs, ArrayRef< StringRef > rhsReversePrefix, UnificationMap *unifications)
void addTemplateParams(mlir::OpBuilder &odsBuilder, typename OpClass::Properties &props, llvm::ArrayRef< mlir::Attribute > templateParams)
bool isValidConstReadType(Type type)
Summary of forbidden precondition influence along with representative source locations for each forbi...
llvm::SmallSetVector< mlir::Location, 2 > structMemberLocs