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 - Func and call op implementations --------------*- 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// Copyright 2026 Project LLZK
7// SPDX-License-Identifier: Apache-2.0
8//
9// Adapted from the LLVM Project's lib/Dialect/Func/IR/FuncOps.cpp
10// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
11// See https://llvm.org/LICENSE.txt for license information.
12// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
13//
14//===----------------------------------------------------------------------===//
15
17
28#include "llzk/Util/Compare.h"
33
34#include <mlir/IR/IRMapping.h>
35#include <mlir/IR/OpImplementation.h>
36#include <mlir/Interfaces/FunctionImplementation.h>
37
38#include <llvm/ADT/DenseSet.h>
39#include <llvm/ADT/MapVector.h>
40
41// TableGen'd implementation files
42#define GET_OP_CLASSES
44
45using namespace mlir;
46using namespace llzk::felt;
47using namespace llzk::component;
48using namespace llzk::polymorphic;
49
50namespace llzk::function {
51
52FunctionKind fnNameToKind(mlir::StringRef name) {
53 if (FUNC_NAME_COMPUTE == name) {
55 } else if (FUNC_NAME_CONSTRAIN == name) {
57 } else if (FUNC_NAME_PRODUCT == name) {
59 } else {
60 return FunctionKind::Free;
61 }
62}
63
64namespace {
66inline LogicalResult
67verifyTypeResolution(SymbolTableCollection &tables, Operation *origin, FunctionType funcType) {
69 tables, origin, ArrayRef<ArrayRef<Type>> {funcType.getInputs(), funcType.getResults()}
70 );
71}
72
77static LogicalResult verifyArgOrResNameAttrs(
78 ArrayAttr attrs, StringRef ownAttrName, StringRef crossAttrName, StringRef ownLabel,
79 StringRef crossLabel, EmitErrorFn emitFn
80) {
81 if (!attrs) {
82 return success();
83 }
84 llvm::DenseSet<StringAttr> seenNames;
85 for (auto [i, attr] : llvm::enumerate(attrs)) {
86 auto dictAttr = llvm::dyn_cast<DictionaryAttr>(attr);
87 if (!dictAttr) {
88 continue;
89 }
90 if (dictAttr.contains(crossAttrName)) {
91 return emitFn().append(
92 '\'', crossAttrName, "' is only valid on function ", crossLabel, "s but found on ",
93 ownLabel, ' ', i
94 );
95 }
96 Attribute nameAttr = dictAttr.get(ownAttrName);
97 if (!nameAttr) {
98 continue;
99 }
100 auto name = llvm::dyn_cast<StringAttr>(nameAttr);
101 if (!name) {
102 return emitFn().append(
103 '\'', ownAttrName, "' on ", ownLabel, ' ', i, " must be a string attribute"
104 );
105 }
106 if (!llvm::isa<NoneType>(name.getType())) {
107 return emitFn().append(
108 '\'', ownAttrName, "' on ", ownLabel, ' ', i, " must not have an explicit type"
109 );
110 }
111 if (name.getValue().empty()) {
112 return emitFn().append('\'', ownAttrName, "' on ", ownLabel, ' ', i, " must not be empty");
113 }
114 if (!seenNames.insert(name).second) {
115 return emitFn().append(
116 "duplicate '", ownAttrName, "' value \"", name.getValue(), "\" on ", ownLabel, ' ', i
117 );
118 }
119 }
120 return success();
121}
122
123static std::optional<StringAttr>
124getFunctionNameAttrAtIndex(ArrayAttr attrs, unsigned index, StringRef attrName) {
125 if (!attrs || index >= attrs.size()) {
126 return std::nullopt;
127 }
128 if (auto dictAttr = llvm::dyn_cast<DictionaryAttr>(attrs[index])) {
129 if (auto nameAttr = llvm::dyn_cast_if_present<StringAttr>(dictAttr.get(attrName))) {
130 return nameAttr;
131 }
132 }
133 return std::nullopt;
134}
135} // namespace
136
137//===----------------------------------------------------------------------===//
138// FuncDefOp
139//===----------------------------------------------------------------------===//
140
142 Location location, StringRef name, FunctionType type, ArrayRef<NamedAttribute> attrs
143) {
144 return delegate_to_build<FuncDefOp>(location, name, type, attrs);
145}
146
148 Location location, StringRef name, FunctionType type, Operation::dialect_attr_range attrs
149) {
150 SmallVector<NamedAttribute, 8> attrRef(attrs);
151 return create(location, name, type, llvm::ArrayRef(attrRef));
152}
153
155 Location location, StringRef name, FunctionType type, ArrayRef<NamedAttribute> attrs,
156 ArrayRef<DictionaryAttr> argAttrs
157) {
158 FuncDefOp func = create(location, name, type, attrs);
159 func.setAllArgAttrs(argAttrs);
160 return func;
161}
162
164 OpBuilder &builder, OperationState &state, StringRef name, FunctionType type,
165 ArrayRef<NamedAttribute> attrs, ArrayRef<DictionaryAttr> argAttrs
166) {
167 state.addAttribute(SymbolTable::getSymbolAttrName(), builder.getStringAttr(name));
168 state.addAttribute(getFunctionTypeAttrName(state.name), TypeAttr::get(type));
169 state.attributes.append(attrs.begin(), attrs.end());
170 state.addRegion();
171
172 if (argAttrs.empty()) {
173 return;
174 }
175 assert(type.getNumInputs() == argAttrs.size());
176 function_interface_impl::addArgAndResultAttrs(
177 builder, state, argAttrs, /*resultAttrs=*/std::nullopt, getArgAttrsAttrName(state.name),
178 getResAttrsAttrName(state.name)
179 );
180}
181
182ParseResult FuncDefOp::parse(OpAsmParser &parser, OperationState &result) {
183 auto buildFuncType = [](Builder &builder, ArrayRef<Type> argTypes, ArrayRef<Type> results,
184 function_interface_impl::VariadicFlag,
185 std::string &) { return builder.getFunctionType(argTypes, results); };
186
187 return function_interface_impl::parseFunctionOp(
188 parser, result, /*allowVariadic=*/false, getFunctionTypeAttrName(result.name), buildFuncType,
189 getArgAttrsAttrName(result.name), getResAttrsAttrName(result.name)
190 );
191}
192
193void FuncDefOp::print(OpAsmPrinter &p) {
194 function_interface_impl::printFunctionOp(
195 p, *this, /*isVariadic=*/false, getFunctionTypeAttrName(), getArgAttrsAttrName(),
197 );
198}
199
202void FuncDefOp::cloneInto(FuncDefOp dest, IRMapping &mapper) {
203 // Add the attributes of this function to dest.
204 llvm::MapVector<StringAttr, Attribute> newAttrMap;
205 for (const auto &attr : dest->getAttrs()) {
206 newAttrMap.insert({attr.getName(), attr.getValue()});
207 }
208 for (const auto &attr : (*this)->getAttrs()) {
209 newAttrMap.insert({attr.getName(), attr.getValue()});
210 }
211
212 auto newAttrs =
213 llvm::to_vector(llvm::map_range(newAttrMap, [](std::pair<StringAttr, Attribute> attrPair) {
214 return NamedAttribute(attrPair.first, attrPair.second);
215 }));
216 dest->setAttrs(DictionaryAttr::get(getContext(), newAttrs));
217
218 // Clone the body.
219 getBody().cloneInto(&dest.getBody(), mapper);
220}
221
227FuncDefOp FuncDefOp::clone(IRMapping &mapper) {
228 // Create the new function.
229 FuncDefOp newFunc = llvm::cast<FuncDefOp>(getOperation()->cloneWithoutRegions());
230
231 // If the function has a body, then the user might be deleting arguments to
232 // the function by specifying them in the mapper. If so, we don't add the
233 // argument to the input type vector.
234 if (!isExternal()) {
235 FunctionType oldType = getFunctionType();
236
237 unsigned oldNumArgs = oldType.getNumInputs();
238 SmallVector<Type, 4> newInputs;
239 newInputs.reserve(oldNumArgs);
240 for (unsigned i = 0; i != oldNumArgs; ++i) {
241 if (!mapper.contains(getArgument(i))) {
242 newInputs.push_back(oldType.getInput(i));
243 }
244 }
245
248 if (newInputs.size() != oldNumArgs) {
249 newFunc.setType(FunctionType::get(oldType.getContext(), newInputs, oldType.getResults()));
250
251 if (ArrayAttr argAttrs = getAllArgAttrs()) {
252 SmallVector<Attribute> newArgAttrs;
253 newArgAttrs.reserve(newInputs.size());
254 for (unsigned i = 0; i != oldNumArgs; ++i) {
255 if (!mapper.contains(getArgument(i))) {
256 newArgAttrs.push_back(argAttrs[i]);
257 }
258 }
259 newFunc.setAllArgAttrs(newArgAttrs);
260 }
261 }
262 }
263
265 cloneInto(newFunc, mapper);
266 return newFunc;
267}
268
270 IRMapping mapper;
271 return clone(mapper);
272}
273
275 if (newValue) {
276 getOperation()->setAttr(AllowConstraintAttr::name, UnitAttr::get(getContext()));
277 } else {
278 getOperation()->removeAttr(AllowConstraintAttr::name);
279 }
280}
281
283 if (newValue) {
284 getOperation()->setAttr(AllowWitnessAttr::name, UnitAttr::get(getContext()));
285 } else {
286 getOperation()->removeAttr(AllowWitnessAttr::name);
287 }
288}
289
291 if (newValue) {
292 getOperation()->setAttr(AllowNonNativeFieldOpsAttr::name, UnitAttr::get(getContext()));
293 } else {
294 getOperation()->removeAttr(AllowNonNativeFieldOpsAttr::name);
295 }
296}
297
299 if (newValue) {
300 getOperation()->setAttr(AllowVerifOpsAttr::name, UnitAttr::get(getContext()));
301 } else {
302 getOperation()->removeAttr(AllowVerifOpsAttr::name);
303 }
304}
305
306bool FuncDefOp::hasArgPublicAttr(unsigned index) {
307 if (index < this->getNumArguments()) {
308 DictionaryAttr res = function_interface_impl::getArgAttrDict(*this, index);
309 return res ? res.contains(PublicAttr::name) : false;
310 } else {
311 // TODO: print error? requested attribute for non-existant argument index
312 return false;
313 }
314}
315
316bool FuncDefOp::hasArgName(unsigned index) { return static_cast<bool>(getArgNameAttr(index)); }
317
318std::optional<StringAttr> FuncDefOp::getArgNameAttr(unsigned index) {
319 return getFunctionNameAttrAtIndex(getAllArgAttrs(), index, ARG_NAME_ATTR_NAME);
320}
321
322void FuncDefOp::setArgNameAttr(unsigned index, const StringAttr &attr) {
323 assert(index < getNumArguments() && "argument index out of range");
324 setArgAttr(index, ARG_NAME_ATTR_NAME, attr);
325}
326
327void FuncDefOp::setArgName(unsigned index, StringRef name) {
328 setArgNameAttr(index, StringAttr::get(getContext(), name));
329}
330
331bool FuncDefOp::hasResName(unsigned index) { return static_cast<bool>(getResNameAttr(index)); }
332
333std::optional<StringAttr> FuncDefOp::getResNameAttr(unsigned index) {
334 return getFunctionNameAttrAtIndex(getAllResultAttrs(), index, RES_NAME_ATTR_NAME);
335}
336
337void FuncDefOp::setResNameAttr(unsigned index, const StringAttr &attr) {
338 assert(index < getNumResults() && "result index out of range");
339 setResultAttr(index, RES_NAME_ATTR_NAME, attr);
340}
341
342void FuncDefOp::setResName(unsigned index, StringRef name) {
343 setResNameAttr(index, StringAttr::get(getContext(), name));
344}
345
346LogicalResult FuncDefOp::verify() {
347 OwningEmitErrorFn emitErrorFunc = getEmitOpErrFn(this);
348
349 if ((*this)->hasAttr(ARG_NAME_ATTR_NAME)) {
350 return emitErrorFunc() << '\'' << ARG_NAME_ATTR_NAME << "' is only valid on function arguments";
351 }
352 if ((*this)->hasAttr(RES_NAME_ATTR_NAME)) {
353 return emitErrorFunc() << '\'' << RES_NAME_ATTR_NAME << "' is only valid on function results";
354 }
355
356 if (failed(verifyArgOrResNameAttrs(
357 getAllResultAttrs(), RES_NAME_ATTR_NAME, ARG_NAME_ATTR_NAME, "result", "argument",
358 emitErrorFunc
359 ))) {
360 return failure();
361 }
362
363 if (failed(verifyArgOrResNameAttrs(
364 getAllArgAttrs(), ARG_NAME_ATTR_NAME, RES_NAME_ATTR_NAME, "argument", "result",
365 emitErrorFunc
366 ))) {
367 return failure();
368 }
369
370 // Ensure that only valid LLZK types are used for arguments and return. Additionally, the struct
371 // functions may not use AffineMapAttrs in their parameter types. If such a scenario seems to make
372 // sense when generating LLZK IR, it's likely better to introduce a struct parameter to use
373 // instead and instantiate the struct with that AffineMapAttr.
374 FunctionType type = getFunctionType();
375 for (Type t : type.getInputs()) {
376 if (llzk::checkValidType(emitErrorFunc, t).failed()) {
377 return failure();
378 }
379 if (isInStruct() && hasAffineMapAttr(t)) {
380 return emitErrorFunc().append(
381 "\"@", getName(), "\" parameters cannot contain affine map attributes but found ", t
382 );
383 }
384 }
385 for (Type t : type.getResults()) {
386 if (llzk::checkValidType(emitErrorFunc, t).failed()) {
387 return failure();
388 }
389 }
390 // Ensure that the function does not contain nested modules.
391 // Functions also cannot contain nested structs, but this check is handled
392 // via struct.def's requirement of having module as a parent.
393 WalkResult res = this->walk<WalkOrder::PreOrder>([this](ModuleOp nestedMod) {
394 getEmitOpErrFn(nestedMod)().append(
395 "cannot be nested within '", getOperation()->getName(), "' operations"
396 );
397 return WalkResult::interrupt();
398 });
399 if (res.wasInterrupted()) {
400 return failure();
401 }
402
403 return success();
404}
405
406namespace {
407
408LogicalResult
409verifyFuncTypeCompute(FuncDefOp &origin, SymbolTableCollection &tables, StructDefOp &parent) {
410 FunctionType funcType = origin.getFunctionType();
411 llvm::ArrayRef<Type> resTypes = funcType.getResults();
412 // Must return type of parent struct
413 if (resTypes.size() != 1) {
414 return origin.emitOpError().append(
415 "\"@", FUNC_NAME_COMPUTE, "\" must have exactly one return type"
416 );
417 }
418 if (failed(checkSelfType(tables, parent, resTypes.front(), origin, "return"))) {
419 return failure();
420 }
421
422 // After the more specific checks (to ensure more specific error messages would be produced if
423 // necessary), do the general check that all symbol references in the types are valid. The return
424 // types were already checked so just check the input types.
425 return llzk::verifyTypeResolution(tables, origin, funcType.getInputs());
426}
427
428LogicalResult
429verifyFuncTypeProduct(FuncDefOp &origin, SymbolTableCollection &tables, StructDefOp &parent) {
430 // The signature for @product is the same as the signature for @compute
431 return verifyFuncTypeCompute(origin, tables, parent);
432}
433
434LogicalResult
435verifyFuncTypeConstrain(FuncDefOp &origin, SymbolTableCollection &tables, StructDefOp &parent) {
436 FunctionType funcType = origin.getFunctionType();
437 // Must return '()' type, i.e., have no return types
438 if (funcType.getResults().size() != 0) {
439 return origin.emitOpError() << "\"@" << FUNC_NAME_CONSTRAIN << "\" must have no return type";
440 }
441
442 // Type of the first parameter must match the parent StructDefOp of the current operation.
443 llvm::ArrayRef<Type> inputTypes = funcType.getInputs();
444 if (inputTypes.size() < 1) {
445 return origin.emitOpError() << "\"@" << FUNC_NAME_CONSTRAIN
446 << "\" must have at least one input type";
447 }
448 if (failed(checkSelfType(tables, parent, inputTypes.front(), origin, "first input"))) {
449 return failure();
450 }
451
452 // After the more specific checks (to ensure more specific error messages would be produced if
453 // necessary), do the general check that all symbol references in the types are valid. There are
454 // no return types, just check the remaining input types (the first was already checked via
455 // the checkSelfType() call above).
456 return llzk::verifyTypeResolution(tables, origin, inputTypes.drop_front());
457}
458
459} // namespace
460
461LogicalResult FuncDefOp::verifySymbolUses(SymbolTableCollection &tables) {
462 // Additional checks for the compute/constrain/product functions within a struct
463 if (StructDefOp parentStructOpt = getParentOfType<StructDefOp>(*this)) {
464 // Verify return type restrictions for functions within a StructDefOp
465 if (nameIsCompute()) {
466 return verifyFuncTypeCompute(*this, tables, parentStructOpt);
467 } else if (nameIsConstrain()) {
468 return verifyFuncTypeConstrain(*this, tables, parentStructOpt);
469 } else if (nameIsProduct()) {
470 return verifyFuncTypeProduct(*this, tables, parentStructOpt);
471 }
472 }
473 // In the general case, verify symbol resolution in all input and output types.
474 return verifyTypeResolution(tables, *this, getFunctionType());
475}
476
477SymbolRefAttr FuncDefOp::getFullyQualifiedName(bool requireParent) {
478 return llzk::getFullyQualifiedName(*this, requireParent);
479}
480
482 assert(nameIsCompute()); // skip inStruct check to allow dangling functions
483 // Get the single block of the function body
484 Region &body = getBody();
485 assert(!body.empty() && "compute() function body is empty");
486 Block &block = body.back();
487
488 // The terminator should be the return op
489 Operation *terminator = block.getTerminator();
490 assert(terminator && "compute() function has no terminator");
491 auto retOp = llvm::dyn_cast<ReturnOp>(terminator);
492 if (!retOp) {
493 llvm::errs() << "Expected '" << ReturnOp::getOperationName() << "' but found '"
494 << terminator->getName() << "'\n";
495 llvm_unreachable("compute() function must end with ReturnOp");
496 }
497 return retOp.getOperands().front();
498}
499
501 assert(nameIsConstrain()); // skip inStruct check to allow dangling functions
502 return getArguments().front();
503}
504
506 assert(isStructCompute() && "violated implementation pre-condition");
508}
509
510//===----------------------------------------------------------------------===//
511// ReturnOp
512//===----------------------------------------------------------------------===//
513
514LogicalResult ReturnOp::verify() {
515 auto function = getParentOp<FuncDefOp>(); // parent is FuncDefOp per ODS
516
517 // The operand number and types must match the function signature.
518 const auto results = function.getFunctionType().getResults();
519 if (getNumOperands() != results.size()) {
520 return emitOpError("has ") << getNumOperands() << " operands, but enclosing function (@"
521 << function.getName() << ") returns " << results.size();
522 }
523
524 for (unsigned i = 0, e = results.size(); i != e; ++i) {
525 if (!typesUnify(getOperand(i).getType(), results[i])) {
526 return emitError() << "type of return operand " << i << " (" << getOperand(i).getType()
527 << ") doesn't match function result type (" << results[i] << ')'
528 << " in function @" << function.getName();
529 }
530 }
531
532 return success();
533}
534
535//===----------------------------------------------------------------------===//
536// CallOp
537//===----------------------------------------------------------------------===//
538
539// Custom implementation to deserialize bytecode produced prior to version 2 which added optional
540// `OptionalAttr<ArrayAttr>:$templateParams`.
541LogicalResult CallOp::readProperties(DialectBytecodeReader &reader, OperationState &state) {
542 auto &prop = state.getOrAddProperties<Properties>();
543 if (failed(reader.readAttribute(prop.callee)) ||
544 failed(reader.readAttribute(prop.mapOpGroupSizes)) ||
545 failed(reader.readOptionalAttribute(prop.numDimsPerMap))) {
546 return failure();
547 }
548
549 if (reader.getBytecodeVersion() < /*kNativePropertiesODSSegmentSize=*/6) {
550 auto &propStorage = prop.operandSegmentSizes;
551 DenseI32ArrayAttr attr;
552 if (failed(reader.readAttribute(attr))) {
553 return failure();
554 }
555 if (attr.size() > static_cast<int64_t>(sizeof(propStorage) / sizeof(int32_t))) {
556 reader.emitError("size mismatch for operand/result_segment_size");
557 return failure();
558 }
559 llvm::copy(ArrayRef<int32_t>(attr), propStorage.begin());
560 }
561
562 // The `templateParams` is only available in version 2 or later.
563 auto versionOpt = reader.getDialectVersion<FunctionDialect>();
564 if (succeeded(versionOpt)) {
565 const auto &ver = static_cast<const LLZKDialectVersion &>(**versionOpt);
566 if (ver.majorVersion >= 2) {
567 if (failed(reader.readOptionalAttribute(prop.templateParams))) {
568 return failure();
569 }
570 }
571 }
572
573 if (reader.getBytecodeVersion() >= /*kNativePropertiesODSSegmentSize=*/6) {
574 return reader.readSparseArray(MutableArrayRef(prop.operandSegmentSizes));
575 };
576 return success();
577}
578
579// Same as tablegen would generate to serialize current version IR.
580void CallOp::writeProperties(DialectBytecodeWriter &writer) {
581 auto &prop = getProperties();
582 writer.writeAttribute(prop.callee);
583 writer.writeAttribute(prop.mapOpGroupSizes);
584 writer.writeOptionalAttribute(prop.numDimsPerMap);
585
586 if (writer.getBytecodeVersion() < /*kNativePropertiesODSSegmentSize=*/6) {
587 auto &propStorage = prop.operandSegmentSizes;
588 writer.writeAttribute(DenseI32ArrayAttr::get(this->getContext(), propStorage));
589 }
590
591 writer.writeOptionalAttribute(prop.templateParams);
592
593 auto &propStorage = prop.operandSegmentSizes;
594 if (writer.getBytecodeVersion() >= /*kNativePropertiesODSSegmentSize=*/6) {
595 writer.writeSparseArray(ArrayRef(propStorage));
596 }
597}
598
599void CallOp::build(
600 OpBuilder &odsBuilder, OperationState &odsState, TypeRange resultTypes, SymbolRefAttr callee,
601 ValueRange argOperands, ArrayRef<Attribute> templateParams
602) {
603 odsState.addTypes(resultTypes);
604 odsState.addOperands(argOperands);
606 odsBuilder, odsState, llzk::checkedCast<int32_t>(argOperands.size())
607 );
608 props.setCallee(callee);
609 addTemplateParams<CallOp>(odsBuilder, props, templateParams);
610}
611
612void CallOp::build(
613 OpBuilder &odsBuilder, OperationState &odsState, TypeRange resultTypes, SymbolRefAttr callee,
614 ArrayRef<ValueRange> mapOperands, DenseI32ArrayAttr numDimsPerMap, ValueRange argOperands,
615 ArrayRef<Attribute> templateParams
616) {
617 odsState.addTypes(resultTypes);
618 odsState.addOperands(argOperands);
620 odsBuilder, odsState, mapOperands, numDimsPerMap,
621 llzk::checkedCast<int32_t>(argOperands.size())
622 );
623 props.setCallee(callee);
624 addTemplateParams<CallOp>(odsBuilder, props, templateParams);
625}
626
627LogicalResult
628CallOp::verifyTemplateParamCompatibility(Attribute paramFromCallOp, TemplateParamOp targetParam) {
629 // A wildcard `?` (represented as kDynamic) defers inference to a later pass.
630 // It is only valid for parameters with a `!poly.tvar` type restriction.
631 if (auto intAttr = llvm::dyn_cast<IntegerAttr>(paramFromCallOp)) {
632 if (isDynamic(intAttr)) {
633 std::optional<Type> declaredType = targetParam.getTypeOpt();
634 if (!declaredType || !llvm::isa<TypeVarType>(*declaredType)) {
635 auto diag = this->emitOpError().append(
636 "wildcard `?` can only be used for template parameters with `!poly.tvar` "
637 "type restriction, but parameter \"@",
638 targetParam.getName(), "\" has "
639 );
640 if (declaredType) {
641 diag.append("type restriction ", *declaredType);
642 } else {
643 diag.append("no type restriction");
644 }
645 return diag;
646 }
647 return success();
648 }
649 }
650 if (std::optional<Type> declaredType = targetParam.getTypeOpt()) {
651 bool compatible = false;
652 if (auto sym = llvm::dyn_cast<SymbolRefAttr>(paramFromCallOp)) {
653 if (sym.getNestedReferences().empty()) {
654 SymbolTableCollection tables;
655 FailureOr<TemplateOp> parentTemplate = getConstResolutionTemplate(tables, *this);
656 if (failed(parentTemplate)) {
657 return failure();
658 }
659 if (TemplateOp p = *parentTemplate) {
660 auto binding = p.getConstNamed<TemplateSymbolBindingOpInterface>(sym.getRootReference());
661 if (binding) {
662 // Once we know it references a template symbol binding, assume it's compatible unless
663 // the optional type is present and doesn't unify with the declared type.
664 if (std::optional<Type> actualType = binding.getTypeOpt()) {
665 compatible = typesUnify(*actualType, *declaredType);
666 } else {
667 compatible = true;
668 }
669 }
670 }
671 }
672 } else if (llvm::isa<TypeVarType>(*declaredType)) {
673 compatible = llvm::isa<TypeAttr>(paramFromCallOp);
674 } else if (llvm::isa<FeltType>(*declaredType)) {
675 compatible = llvm::isa<FeltConstAttr, IntegerAttr>(paramFromCallOp) &&
676 isValidConstReadType(llvm::cast<TypedAttr>(paramFromCallOp).getType());
677 } else if (llvm::isa<IndexType, IntegerType>(*declaredType)) {
678 // Note: Just like struct type instantiation, there is no restriction on passing a
679 // larger value to an `i1`. The flattening pass will treat 0 as false and any other
680 // value as true (but give a warning if it's not 1).
681 compatible = llvm::isa<IntegerAttr>(paramFromCallOp) &&
682 isValidConstReadType(llvm::cast<TypedAttr>(paramFromCallOp).getType());
683 } else {
684 // Note: `declaredType` is restricted by `isValidConstReadType()`
685 llvm_unreachable("inconsistent with `isValidConstReadType()`");
686 }
687 if (!compatible) {
688 // Tested in call_with_template_params_fail.llzk
689 return this->emitOpError().append(
690 "instantiation value '", paramFromCallOp, "' is not compatible with parameter \"@",
691 targetParam.getName(), "\" type restriction ", *declaredType
692 );
693 }
694 }
695 return success();
696}
697
699 llvm::iterator_range<Region::op_iterator<TemplateParamOp>> targetParamDefs
700) {
701 ArrayAttr callParams = this->getTemplateParamsAttr();
702 assert(!isNullOrEmpty(callParams) && "pre-condition");
703 assert((callParams.size() == llvm::range_size(targetParamDefs)) && "pre-condition");
704
705 for (auto [paramOp, attr] : llvm::zip_equal(targetParamDefs, callParams.getValue())) {
706 if (failed(verifyTemplateParamCompatibility(attr, paramOp))) {
707 return failure();
708 }
709 }
710 return success();
711}
712
714 llvm::iterator_range<Region::op_iterator<TemplateParamOp>> targetParamDefs,
715 const UnificationMap &unifications
716) {
717 ArrayAttr callParams = this->getTemplateParamsAttr();
718 assert(!isNullOrEmpty(callParams) && "pre-condition");
719 assert((callParams.size() == llvm::range_size(targetParamDefs)) && "pre-condition");
720
721 for (auto [paramOp, attr] : llvm::zip_equal(targetParamDefs, callParams.getValue())) {
722 // Skip wildcards (`?` / kDynamic) - their value will be resolved by a later inference pass.
723 if (auto intAttr = llvm::dyn_cast<IntegerAttr>(attr)) {
724 if (isDynamic(intAttr)) {
725 continue;
726 }
727 }
728 auto it = unifications.find({FlatSymbolRefAttr::get(paramOp.getNameAttr()), Side::RHS});
729 if (it != unifications.end() && !typeParamsUnify({attr}, {it->second})) {
730 // Tested in call_with_template_params_fail.llzk
731 return this->emitOpError().append(
732 "template instantiation value '", attr, "' for parameter \"@", paramOp.getName(),
733 "\" conflicts with value '", it->second, "' inferred from function type signature"
734 );
735 }
736 }
737 return success();
738}
739
740namespace {
741
742struct CallOpVerifier {
743 CallOpVerifier(CallOp *c, FunctionKind tgtFuncKind) : callOp(c), tgtKind(tgtFuncKind) {}
744 CallOpVerifier(CallOp *c, StringRef tgtName) : CallOpVerifier(c, fnNameToKind(tgtName)) {}
745 virtual ~CallOpVerifier() = default;
746
747 LogicalResult verify() {
748 // Rather than immediately returning on failure, we check all verifier steps and aggregate to
749 // provide as many errors are possible in a single verifier run.
750 LogicalResult aggregateResult = success();
751 if (failed(verifyTargetAttributes())) {
752 aggregateResult = failure();
753 }
754 if (failed(verifyInputs())) {
755 aggregateResult = failure();
756 }
757 if (failed(verifyOutputs())) {
758 aggregateResult = failure();
759 }
760 if (failed(verifyTemplateParams())) {
761 aggregateResult = failure();
762 }
763 if (failed(verifyAffineMapParams())) {
764 aggregateResult = failure();
765 }
766 return aggregateResult;
767 }
768
769protected:
770 CallOp *callOp;
771 FunctionKind tgtKind;
772
773 virtual LogicalResult verifyTargetAttributes() = 0;
774 virtual LogicalResult verifyInputs() = 0;
775 virtual LogicalResult verifyOutputs() = 0;
776 virtual LogicalResult verifyTemplateParams() = 0;
777 virtual LogicalResult verifyAffineMapParams() = 0;
778
780 LogicalResult verifyTargetAttributesMatch(FuncDefOp target) {
781 LogicalResult aggregateRes = success();
782 if (FuncDefOp caller = (*callOp)->getParentOfType<FuncDefOp>()) {
783 auto emitAttrErr = [&](StringLiteral attrName) {
784 aggregateRes = callOp->emitOpError()
785 << "target '@" << target.getName() << "' has '" << attrName
786 << "' attribute, which is not specified by the caller '@" << caller.getName()
787 << '\'';
788 };
789
790 if (target.hasAllowConstraintAttr() && !caller.hasAllowConstraintAttr()) {
791 emitAttrErr(AllowConstraintAttr::name);
792 }
793 if (target.hasAllowWitnessAttr() && !caller.hasAllowWitnessAttr()) {
794 emitAttrErr(AllowWitnessAttr::name);
795 }
796 if (target.hasAllowNonNativeFieldOpsAttr() && !caller.hasAllowNonNativeFieldOpsAttr()) {
797 emitAttrErr(AllowNonNativeFieldOpsAttr::name);
798 }
799 }
800 return aggregateRes;
801 }
802
803 LogicalResult verifyNoTemplateInstantiations() {
804 if (!isNullOrEmpty(callOp->getTemplateParamsAttr())) {
805 // Tested in call_with_template_params_fail.llzk
806 return callOp->emitOpError().append(
807 "can only have template instantiations when targeting a templated free function"
808 );
809 }
810 return success();
811 }
812
813 LogicalResult verifyNoAffineMapInstantiations() {
814 if (!isNullOrEmpty(callOp->getMapOpGroupSizesAttr())) {
815 // Tested in call_with_affinemap_fail.llzk
816 return callOp->emitOpError().append(
817 "can only have affine map instantiations when targeting a \"@", FUNC_NAME_COMPUTE,
818 "\" function"
819 );
820 }
821 // ASSERT: the check above is sufficient due to VerifySizesForMultiAffineOps trait.
822 assert(isNullOrEmpty(callOp->getNumDimsPerMapAttr()));
823 assert(callOp->getMapOperands().empty());
824 return success();
825 }
826};
827
828struct KnownTargetVerifier : public CallOpVerifier {
829 KnownTargetVerifier(CallOp *c, SymbolLookupResult<FuncDefOp> &&tgtRes)
830 : CallOpVerifier(c, tgtRes.get().getSymName()), tgt(*tgtRes), tgtType(tgt.getFunctionType()),
831 includeSymNames(tgtRes.getNamespace()) {}
832
833 LogicalResult verifyTargetAttributes() override {
834 return CallOpVerifier::verifyTargetAttributesMatch(tgt);
835 }
836
837 LogicalResult verifyInputs() override {
838 return verifyTypesMatch(callOp->getArgOperands().getTypes(), tgtType.getInputs(), "operand");
839 }
840
841 LogicalResult verifyOutputs() override {
842 return verifyTypesMatch(callOp->getResultTypes(), tgtType.getResults(), "result");
843 }
844
845 LogicalResult verifyTemplateParams() override {
846 Operation *tgtOp = tgt.getOperation();
847 if (isInStruct(tgtOp)) {
848 // Struct function calls cannot contain template parameter instantiations.
849 return verifyNoTemplateInstantiations();
850 } else if (TemplateOp tgtOpParent = getParentOfType<TemplateOp>(tgtOp)) {
851 // When the target function is a free function within a TemplateOp, the CallOp may have
852 // template parameter instantiations that must be checked against the template parameters.
853 // - If the function type signature references all template parameters, then the parameter
854 // instantiation list on the CallOp is optional, otherwise it's required.
855 // - If present, the instantiation list must provide a value for every template parameter
856 // and the value must be type-compatible with the parameter's declared type (if any).
857 // - If present, the instantiation list must result in a function type signature that can
858 // be unified with the CallOp's operand and result types.
859 auto realParams = tgtOpParent.getConstOps<TemplateParamOp>();
860 ArrayAttr callParams = callOp->getTemplateParamsAttr();
861
862 // When there is no instantiation list, just ensure that it's not required.
863 if (isNullOrEmpty(callParams)) {
864 llvm::SmallDenseSet<SymbolRefAttr> referencedInSignature;
865 llzk::getSymbolsUsedIn(tgtType.getInputs(), referencedInSignature);
866 llzk::getSymbolsUsedIn(tgtType.getResults(), referencedInSignature);
867
868 bool allParamsReferenced = llvm::all_of(realParams, [&](TemplateParamOp p) {
869 return referencedInSignature.contains(FlatSymbolRefAttr::get(p.getNameAttr()));
870 });
871 if (allParamsReferenced) {
872 return success();
873 }
874 // Tested in call_with_template_params_fail.llzk
875 return callOp->emitOpError().append(
876 "must provide template instantiation parameters when calling \"@", tgt.getSymName(),
877 "\" because not all template parameters of \"@", tgtOpParent.getSymName(),
878 "\" appear in the function type signature"
879 );
880 }
881
882 // Ensure `forceIntAttrTypes()` was successful on the CallOp's template parameters.
883 if (failed(llzk::forceIntAttrTypes(callParams.getValue(), [this] {
884 return llzk::InFlightDiagnosticWrapper(this->callOp->emitOpError());
885 }))) {
886 return failure();
887 }
888
889 // The instantiation list is present. Check it has exactly one entry per template param.
890 size_t numTemplateParams = llvm::range_size(realParams);
891 if (callParams.size() != numTemplateParams) {
892 // Tested in call_with_template_params_fail.llzk
893 return callOp->emitOpError().append(
894 "template instantiation has ", callParams.size(), " parameter(s) but \"@",
895 tgtOpParent.getSymName(), "\" expects ", numTemplateParams, " template parameter(s)"
896 );
897 }
898
899 // Check type compatibility of each provided value with the declared parameter type (if any).
900 if (failed(callOp->verifyTemplateParamCompatibility(realParams))) {
901 return failure();
902 }
903
904 // Check that the provided instantiation values are consistent with what type unification
905 // of the target function types against the call's operand and result types would determine.
906 FailureOr<UnificationMap> unifyResult = callOp->unifyTypeSignature(tgtType);
907 assert(succeeded(unifyResult) && "already checked by `verifyInputs()` and `verifyOutputs()`");
908 return callOp->verifyTemplateParamsMatchInferred(realParams, unifyResult.value());
909 } else {
910 // Non-template functions cannot contain template parameter instantiations.
911 return verifyNoTemplateInstantiations();
912 }
913 }
914
915 LogicalResult verifyAffineMapParams() override {
916 if ((FunctionKind::StructCompute == tgtKind || FunctionKind::StructProduct == tgtKind) &&
917 isInStruct(tgt.getOperation())) {
918 // Return type should be a single StructType. If that is not the case here, just bail without
919 // producing an error. The combination of this KnownTargetVerifier resolving the callee to a
920 // specific FuncDefOp and verifyFuncTypeCompute() ensuring all FUNC_NAME_COMPUTE FuncOps have
921 // a single StructType return value will produce a more relevant error message in that case.
922 if (StructType retTy = callOp->getSingleResultTypeOfWitnessGen()) {
923 if (ArrayAttr params = retTy.getParams()) {
924 // Collect the struct parameters that are defined via AffineMapAttr
925 SmallVector<AffineMapAttr> mapAttrs;
926 for (Attribute a : params) {
927 if (AffineMapAttr m = dyn_cast<AffineMapAttr>(a)) {
928 mapAttrs.push_back(m);
929 }
930 }
932 callOp->getMapOperands(), callOp->getNumDimsPerMap(), mapAttrs, *callOp
933 );
934 }
935 }
936 return success();
937 } else {
938 // Global functions and constrain functions cannot have affine map instantiations.
939 return verifyNoAffineMapInstantiations();
940 }
941 }
942
943private:
944 template <typename T>
945 LogicalResult
946 verifyTypesMatch(ValueTypeRange<T> callOpTypes, ArrayRef<Type> tgtTypes, const char *aspect) {
947 if (tgtTypes.size() != callOpTypes.size()) {
948 return callOp->emitOpError()
949 .append("incorrect number of ", aspect, "s for callee, expected ", tgtTypes.size())
950 .attachNote(tgt.getLoc())
951 .append("callee defined here");
952 }
953 for (unsigned i = 0, e = tgtTypes.size(); i != e; ++i) {
954 if (!typesUnify(callOpTypes[i], tgtTypes[i], includeSymNames)) {
955 return callOp->emitOpError().append(
956 aspect, " type mismatch: expected type ", tgtTypes[i], ", but found ", callOpTypes[i],
957 " for ", aspect, " number ", i
958 );
959 }
960 }
961 return success();
962 }
963
964 FuncDefOp tgt;
965 FunctionType tgtType;
966 std::vector<llvm::StringRef> includeSymNames;
967};
968
971LogicalResult checkSelfTypeUnknownTarget(
972 StringAttr expectedParamName, Type actualType, CallOp *origin, const char *aspect
973) {
974 if (!llvm::isa<TypeVarType>(actualType) ||
975 llvm::cast<TypeVarType>(actualType).getRefName() != expectedParamName) {
976 // Tested in function_restrictions_fail.llzk:
977 // Non-tvar for constrain input via "call_target_constrain_without_self_non_struct"
978 // Non-tvar for compute output via "call_target_compute_wrong_type_ret"
979 // Wrong tvar for constrain input via "call_target_constrain_without_self_wrong_tvar_param"
980 // Wrong tvar for compute output via "call_target_compute_wrong_tvar_param_ret"
981 return origin->emitOpError().append(
982 "target \"@", origin->getCallee().getLeafReference().getValue(), "\" expected ", aspect,
983 " type '!", TypeVarType::name, "<@", expectedParamName.getValue(), ">' but found ",
984 actualType
985 );
986 }
987 return success();
988}
989
999struct UnknownTargetVerifier : public CallOpVerifier {
1000 UnknownTargetVerifier(CallOp *c, FunctionKind tgtFuncKind, SymbolRefAttr callee)
1001 : CallOpVerifier(c, tgtFuncKind), calleeAttr(callee) {
1002 assert(
1003 tgtFuncKind == FunctionKind::StructCompute ||
1004 tgtFuncKind == FunctionKind::StructConstrain || tgtFuncKind == FunctionKind::StructProduct
1005 ); // pre-condition mentioned above
1006 }
1007
1008 LogicalResult verifyTargetAttributes() override {
1009 // Based on the precondition of this verifier, the target must be either a
1010 // struct compute, constrain, or product function.
1011 LogicalResult aggregateRes = success();
1012 if (FuncDefOp caller = (*callOp)->getParentOfType<FuncDefOp>()) {
1013 auto emitAttrErr = [&](StringLiteral attrName) {
1014 aggregateRes = callOp->emitOpError()
1015 << "target '" << calleeAttr << "' has '" << attrName
1016 << "' attribute, which is not specified by the caller '@" << caller.getName()
1017 << '\'';
1018 };
1019
1020 switch (tgtKind) {
1022 if (!caller.hasAllowConstraintAttr()) {
1023 emitAttrErr(AllowConstraintAttr::name);
1024 }
1025 break;
1027 if (!caller.hasAllowWitnessAttr()) {
1028 emitAttrErr(AllowWitnessAttr::name);
1029 }
1030 break;
1032 if (!caller.hasAllowWitnessAttr()) {
1033 emitAttrErr(AllowWitnessAttr::name);
1034 }
1035 if (!caller.hasAllowConstraintAttr()) {
1036 emitAttrErr(AllowConstraintAttr::name);
1037 }
1038 break;
1039 default:
1040 break;
1041 }
1042 }
1043 return aggregateRes;
1044 }
1045
1046 LogicalResult verifyInputs() override {
1047 if (FunctionKind::StructCompute == tgtKind || FunctionKind::StructProduct == tgtKind) {
1048 // Without known target, no additional checks can be done.
1049 } else if (FunctionKind::StructConstrain == tgtKind) {
1050 // Without known target, this can only check that the first input is VarType using the same
1051 // struct parameter as the base of the callee (later replaced with the target struct's type).
1052 Operation::operand_type_range inputTypes = callOp->getArgOperands().getTypes();
1053 if (inputTypes.size() < 1) {
1054 // Tested in function_restrictions_fail.llzk
1055 return callOp->emitOpError()
1056 << "target \"@" << FUNC_NAME_CONSTRAIN << "\" must have at least one input type";
1057 }
1058 return checkSelfTypeUnknownTarget(
1059 calleeAttr.getRootReference(), inputTypes.front(), callOp, "first input"
1060 );
1061 }
1062 return success();
1063 }
1064
1065 LogicalResult verifyOutputs() override {
1066 if (FunctionKind::StructCompute == tgtKind || FunctionKind::StructProduct == tgtKind) {
1067 // Without known target, this can only check that the function returns VarType using the same
1068 // struct parameter as the base of the callee (later replaced with the target struct's type).
1069 Operation::result_type_range resTypes = callOp->getResultTypes();
1070 if (resTypes.size() != 1) {
1071 // Tested in function_restrictions_fail.llzk
1072 return callOp->emitOpError().append(
1073 "target \"@", FUNC_NAME_COMPUTE, "\" must have exactly one return type"
1074 );
1075 }
1076 return checkSelfTypeUnknownTarget(
1077 calleeAttr.getRootReference(), resTypes.front(), callOp, "return"
1078 );
1079 } else if (FunctionKind::StructConstrain == tgtKind) {
1080 // Without known target, this can only check that the function has no return
1081 if (callOp->getNumResults() != 0) {
1082 // Tested in function_restrictions_fail.llzk
1083 return callOp->emitOpError()
1084 << "target \"@" << FUNC_NAME_CONSTRAIN << "\" must have no return type";
1085 }
1086 }
1087 return success();
1088 }
1089
1090 LogicalResult verifyTemplateParams() override {
1091 // Struct function calls cannot contain template parameter instantiations.
1092 return verifyNoTemplateInstantiations();
1093 }
1094
1095 LogicalResult verifyAffineMapParams() override {
1096 if (FunctionKind::StructCompute == tgtKind || FunctionKind::StructProduct == tgtKind) {
1097 // Without known target, no additional checks can be done.
1098 } else if (FunctionKind::StructConstrain == tgtKind) {
1099 // Without known target, this can only check that there are no affine map instantiations.
1100 return verifyNoAffineMapInstantiations();
1101 }
1102 return success();
1103 }
1104
1105private:
1106 SymbolRefAttr calleeAttr;
1107};
1108
1109} // namespace
1110
1111LogicalResult CallOp::verifySymbolUses(SymbolTableCollection &tables) {
1112 // First, verify symbol resolution in all input and output types.
1113 if (failed(verifyTypeResolution(tables, *this, getTypeSignature()))) {
1114 return failure(); // verifyTypeResolution() already emits a sufficient error message
1115 }
1116
1117 // Check that the callee attribute was specified.
1118 SymbolRefAttr calleeAttr = getCalleeAttr();
1119 if (!calleeAttr) {
1120 return emitOpError("requires a 'callee' symbol reference attribute");
1121 }
1122
1123 // If the callee references a parameter of the template where this call appears, perform
1124 // the subset of checks that can be done even though the target is unknown.
1125 if (calleeAttr.getNestedReferences().size() == 1) {
1126 if (TemplateOp parent = getParentOfType<TemplateOp>(*this)) {
1127 if (parent.hasConstNamed<TemplateParamOp>(calleeAttr.getRootReference())) {
1128 FunctionKind tgtKind = fnNameToKind(calleeAttr.getLeafReference().getValue());
1129 if (tgtKind != FunctionKind::Free) {
1130 return UnknownTargetVerifier(this, tgtKind, calleeAttr).verify();
1131 }
1132 return this->emitError("expected parameterized callee to target a struct function")
1133 .append(
1134 " (i.e. \"@", FUNC_NAME_PRODUCT, "\", \"@", FUNC_NAME_COMPUTE, "\", or \"@",
1135 FUNC_NAME_CONSTRAIN, "\")"
1136 );
1137 }
1138 }
1139 }
1140
1141 // Otherwise, callee must be specified via full path from the root module. Perform the full set of
1142 // checks against the known target function.
1143 auto tgtOpt = lookupTopLevelSymbol<FuncDefOp>(tables, calleeAttr, *this);
1144 if (failed(tgtOpt)) {
1145 return this->emitError() << "expected '" << FuncDefOp::getOperationName() << "' named \""
1146 << calleeAttr << '"';
1147 }
1148 return KnownTargetVerifier(this, std::move(*tgtOpt)).verify();
1149}
1150
1152 return FunctionType::get(getContext(), getArgOperands().getTypes(), getResultTypes());
1153}
1154
1155FailureOr<UnificationMap> CallOp::unifyTypeSignature(FunctionType other) {
1156 UnificationMap unifications;
1157 if (functionTypesUnify(getTypeSignature(), other, {}, &unifications)) {
1158 return unifications;
1159 } else {
1160 return failure();
1161 }
1162}
1163
1164namespace {
1165
1166bool calleeIsStructFunctionImpl(
1167 const char *funcName, SymbolRefAttr callee, llvm::function_ref<StructType()> getType
1168) {
1169 if (callee.getLeafReference() == funcName) {
1170 if (StructType t = getType()) {
1171 // If the name ref within the StructType matches the `callee` prefix (i.e., sans the function
1172 // name itself), then the `callee` target must be within a StructDefOp because validation
1173 // checks elsewhere ensure that every StructType references a StructDefOp (i.e., the `callee`
1174 // function is not simply a free function nested within a ModuleOp)
1175 return t.getNameRef() == getPrefixAsSymbolRefAttr(callee);
1176 }
1177 }
1178 return false;
1179}
1180
1181} // namespace
1182
1184 return calleeIsStructFunctionImpl(FUNC_NAME_COMPUTE, getCallee(), [this]() {
1185 return this->getSingleResultTypeOfCompute();
1186 });
1187}
1188
1190 return calleeIsStructFunctionImpl(FUNC_NAME_PRODUCT, getCallee(), [this]() {
1191 return this->getSingleResultTypeOfWitnessGen();
1192 });
1193}
1194
1196 return calleeIsStructFunctionImpl(FUNC_NAME_CONSTRAIN, getCallee(), [this]() {
1197 return getAtIndex<StructType>(this->getArgOperands().getTypes(), 0);
1198 });
1199}
1200
1202 assert(calleeIsStructCompute());
1203 return getResults().front();
1204}
1205
1207 assert(calleeIsStructConstrain());
1208 return getArgOperands().front();
1209}
1210
1211FailureOr<SymbolLookupResult<FuncDefOp>> CallOp::getCalleeTarget(SymbolTableCollection &tables) {
1212 Operation *thisOp = this->getOperation();
1213 auto root = getRootModule(thisOp);
1214 assert(succeeded(root));
1215 return llzk::lookupSymbolIn<FuncDefOp>(tables, getCallee(), root->getOperation(), thisOp);
1216}
1217
1219 assert(calleeIsCompute() && "violated implementation pre-condition");
1220 return getIfSingleton<StructType>(getResultTypes());
1221}
1222
1224 assert(calleeContainsWitnessGen() && "violated implementation pre-condition");
1225 return getIfSingleton<StructType>(getResultTypes());
1226}
1227
1229CallInterfaceCallable CallOp::getCallableForCallee() { return getCalleeAttr(); }
1230
1232void CallOp::setCalleeFromCallable(CallInterfaceCallable callee) {
1233 setCalleeAttr(llvm::cast<SymbolRefAttr>(callee));
1234}
1235
1236SmallVector<ValueRange> CallOp::toVectorOfValueRange(OperandRangeRange input) {
1237 llvm::SmallVector<ValueRange, 4> output;
1238 output.reserve(input.size());
1239 for (OperandRange r : input) {
1240 output.push_back(r);
1241 }
1242 return output;
1243}
1244
1245Operation *CallOp::resolveCallableInTable(SymbolTableCollection *symbolTable) {
1246 FailureOr<SymbolLookupResult<FuncDefOp>> res =
1247 llzk::resolveCallable<FuncDefOp>(*symbolTable, *this);
1248 if (failed(res) || res->isManaged()) {
1249 // Cannot return pointer to a managed Operation since it would cause memory errors.
1250 return nullptr;
1251 }
1252 return res->get();
1253}
1254
1256 SymbolTableCollection tables;
1257 return resolveCallableInTable(&tables);
1258}
1259
1260} // namespace llzk::function
This file defines methods symbol lookup across LLZK operations and included files.
static void build(::mlir::OpBuilder &odsBuilder, ::mlir::OperationState &odsState, ::mlir::TypeRange resultTypes, ::mlir::SymbolRefAttr callee, ::mlir::ValueRange argOperands={}, ::llvm::ArrayRef<::mlir::Attribute > templateParams={})
bool calleeContainsWitnessGen()
Return true iff the callee function can contain witness generation code (this does not check if the c...
Definition Ops.h.inc:395
bool calleeIsStructConstrain()
Return true iff the callee function name is FUNC_NAME_CONSTRAIN within a StructDefOp.
Definition Ops.cpp:1195
::mlir::CallInterfaceCallable getCallableForCallee()
Return the callee of this operation.
Definition Ops.cpp:1229
::llzk::component::StructType getSingleResultTypeOfCompute()
Assuming the callee is FUNC_NAME_COMPUTE, return the single StructType result.
Definition Ops.cpp:1218
::mlir::SymbolRefAttr getCalleeAttr()
Definition Ops.h.inc:292
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:1111
::mlir::Operation * resolveCallableInTable(::mlir::SymbolTableCollection *symbolTable)
Required by CallOpInterface.
Definition Ops.cpp:1245
bool calleeIsStructCompute()
Return true iff the callee function name is FUNC_NAME_COMPUTE within a StructDefOp.
Definition Ops.cpp:1183
::mlir::SymbolRefAttr getCallee()
Definition Ops.cpp.inc:470
::mlir::Operation * resolveCallable()
Required by CallOpInterface.
Definition Ops.cpp:1255
void writeProperties(::mlir::DialectBytecodeWriter &writer)
Definition Ops.cpp:580
bool calleeIsStructProduct()
Return true iff the callee function name is FUNC_NAME_PRODUCT within a StructDefOp.
Definition Ops.cpp:1189
::mlir::FunctionType getTypeSignature()
Return the FunctionType inferred from the arg operands and result types of this CallOp.
Definition Ops.cpp:1151
bool calleeIsCompute()
Return true iff the callee function name is FUNC_NAME_COMPUTE (this does not check if the callee func...
Definition Ops.h.inc:383
::llvm::ArrayRef< int32_t > getNumDimsPerMap()
Definition Ops.cpp.inc:480
::mlir::Operation::operand_range getArgOperands()
Definition Ops.h.inc:266
::mlir::Value getSelfValueFromConstrain()
Return the "self" value (i.e.
Definition Ops.cpp:1206
::mlir::ArrayAttr getTemplateParamsAttr()
Definition Ops.h.inc:297
::mlir::OperandRangeRange getMapOperands()
Definition Ops.h.inc:270
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:1236
::llzk::component::StructType getSingleResultTypeOfWitnessGen()
Assuming the callee contains witness generation code, return the single StructType result.
Definition Ops.cpp:1223
FoldAdaptor::Properties Properties
Definition Ops.h.inc:209
void setCalleeAttr(::mlir::SymbolRefAttr attr)
Definition Ops.h.inc:312
::llvm::LogicalResult readProperties(::mlir::DialectBytecodeReader &reader, ::mlir::OperationState &state)
Definition Ops.cpp:541
::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:1155
::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:713
::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::Value getSelfValueFromCompute()
Return the "self" value (i.e.
Definition Ops.cpp:1201
void setCalleeFromCallable(::mlir::CallInterfaceCallable callee)
Set the callee for this operation.
Definition Ops.cpp:1232
::mlir::FailureOr<::llzk::SymbolLookupResult<::llzk::function::FuncDefOp > > getCalleeTarget(::mlir::SymbolTableCollection &tables)
Resolve and return the target FuncDefOp for this CallOp.
Definition Ops.cpp:1211
void setAllowWitnessAttr(bool newValue=true)
Add (resp. remove) the allow_witness attribute to (resp. from) the function def.
Definition Ops.cpp:282
void setArgNameAttr(unsigned index, const ::mlir::StringAttr &attr)
Set the function.arg_name attribute for the argument at the given index.
Definition Ops.cpp:322
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:327
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:461
::mlir::Value getSelfValueFromCompute()
Return the "self" value (i.e.
Definition Ops.cpp:481
::mlir::FunctionType getFunctionType()
Definition Ops.cpp.inc:984
static void build(::mlir::OpBuilder &odsBuilder, ::mlir::OperationState &odsState, ::llvm::StringRef name, ::mlir::FunctionType type, ::llvm::ArrayRef<::mlir::NamedAttribute > attrs={}, ::llvm::ArrayRef<::mlir::DictionaryAttr > argAttrs={})
void setAllowNonNativeFieldOpsAttr(bool newValue=true)
Add (resp. remove) the allow_non_native_field_ops attribute to (resp. from) the function def.
Definition Ops.cpp:290
void setResNameAttr(unsigned index, const ::mlir::StringAttr &attr)
Set the function.res_name attribute for the result at the given index.
Definition Ops.cpp:337
::mlir::StringAttr getFunctionTypeAttrName()
Definition Ops.h.inc:655
bool hasAllowNonNativeFieldOpsAttr()
Return true iff the function def has the allow_non_native_field_ops attribute.
Definition Ops.h.inc:833
::mlir::Value getSelfValueFromConstrain()
Return the "self" value (i.e.
Definition Ops.cpp:500
void print(::mlir::OpAsmPrinter &p)
Definition Ops.cpp:193
bool nameIsCompute()
Return true iff the function name is FUNC_NAME_COMPUTE (if needed, a check that this FuncDefOp is loc...
Definition Ops.h.inc:898
bool hasAllowWitnessAttr()
Return true iff the function def has the allow_witness attribute.
Definition Ops.h.inc:825
::llzk::component::StructType getSingleResultTypeOfCompute()
Assuming the name is FUNC_NAME_COMPUTE, return the single StructType result.
Definition Ops.cpp:505
::mlir::StringAttr getResAttrsAttrName()
Definition Ops.h.inc:663
::mlir::ParseResult parse(::mlir::OpAsmParser &parser, ::mlir::OperationState &result)
Definition Ops.cpp:182
bool hasArgName(unsigned index)
Return true iff the argument at the given index has a function.arg_name attribute.
Definition Ops.cpp:316
void cloneInto(FuncDefOp dest, ::mlir::IRMapping &mapper)
Clone the internal blocks and attributes from this function into dest.
Definition Ops.cpp:202
void setAllowVerifOpsAttr(bool newValue=true)
Add (resp. remove) the allow_verif_ops attribute to (resp. from) the function def.
Definition Ops.cpp:298
bool hasResName(unsigned index)
Return true iff the result at the given index has a function.res_name attribute.
Definition Ops.cpp:331
bool nameIsProduct()
Return true iff the function name is FUNC_NAME_PRODUCT (if needed, a check that this FuncDefOp is loc...
Definition Ops.h.inc:906
::std::optional<::mlir::StringAttr > getArgNameAttr(unsigned index)
Return the function.arg_name attribute for the argument at the given index.
Definition Ops.cpp:318
::std::optional<::mlir::StringAttr > getResNameAttr(unsigned index)
Return the function.res_name attribute for the result at the given index.
Definition Ops.cpp:333
bool nameIsConstrain()
Return true iff the function name is FUNC_NAME_CONSTRAIN (if needed, a check that this FuncDefOp is l...
Definition Ops.h.inc:902
bool isStructCompute()
Return true iff the function is within a StructDefOp and named FUNC_NAME_COMPUTE.
Definition Ops.h.inc:912
void setResName(unsigned index, ::llvm::StringRef name)
Set the function.res_name attribute for the result at the given index from a string.
Definition Ops.cpp:342
bool isInStruct()
Return true iff the function is within a StructDefOp.
Definition Ops.h.inc:909
::llvm::ArrayRef<::mlir::Type > getResultTypes()
Required by FunctionOpInterface.
Definition Ops.h.inc:887
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:679
void setAllowConstraintAttr(bool newValue=true)
Add (resp. remove) the allow_constraint attribute to (resp. from) the function def.
Definition Ops.cpp:274
static FuncDefOp create(::mlir::Location location, ::llvm::StringRef name, ::mlir::FunctionType type, ::llvm::ArrayRef<::mlir::NamedAttribute > attrs={})
::mlir::SymbolRefAttr getFullyQualifiedName(bool requireParent=true)
Return the full name for this function from the root module, including all surrounding symbol table n...
Definition Ops.cpp:477
bool hasArgPublicAttr(unsigned index)
Return true iff the argument at the given index has pub attribute.
Definition Ops.cpp:306
::llvm::LogicalResult verify()
Definition Ops.cpp:346
::mlir::Region & getBody()
Definition Ops.h.inc:703
bool hasAllowConstraintAttr()
Return true iff the function def has the allow_constraint attribute.
Definition Ops.h.inc:817
::mlir::StringAttr getArgAttrsAttrName()
Definition Ops.h.inc:647
::llvm::LogicalResult verify()
Definition Ops.cpp:514
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:1013
::std::optional<::mlir::Type > getTypeOpt()
Definition Ops.cpp.inc:1339
static constexpr ::llvm::StringLiteral name
Definition Types.h.inc:27
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,...
LogicalResult verifyAffineMapInstantiations(OperandRangeRange mapOps, ArrayRef< int32_t > numDimsPerMap, ArrayRef< AffineMapAttr > mapAttrs, Operation *origin)
OpClass::Properties & buildInstantiationAttrsEmpty(mlir::OpBuilder &odsBuilder, mlir::OperationState &odsState, int32_t firstSegmentSize=0)
Utility for build() functions that initializes the operandSegmentSizes, mapOpGroupSizes,...
bool isInStruct(Operation *op)
Definition Ops.cpp:57
LogicalResult checkSelfType(SymbolTableCollection &tables, StructDefOp expectedStruct, Type actualType, Operation *origin, const char *aspect)
Verifies that the given actualType matches the StructDefOp given (i.e., for the "self" type parameter...
Definition Ops.cpp:121
constexpr char ARG_NAME_ATTR_NAME[]
Attribute name for source-level function argument names.
Definition Ops.h:35
constexpr char RES_NAME_ATTR_NAME[]
Attribute name for source-level function result names.
Definition Ops.h:38
FunctionKind fnNameToKind(mlir::StringRef name)
Given a function name, return the corresponding FunctionKind.
Definition Ops.cpp:52
FunctionKind
Kinds of functions in LLZK.
Definition Ops.h:41
@ StructConstrain
Function within a struct named FUNC_NAME_CONSTRAIN.
Definition Ops.h:45
@ StructProduct
Function within a struct named FUNC_NAME_PRODUCT.
Definition Ops.h:47
@ StructCompute
Function within a struct named FUNC_NAME_COMPUTE.
Definition Ops.h:43
@ Free
Function that is not within a struct.
Definition Ops.h:49
constexpr char FUNC_NAME_COMPUTE[]
Symbol name for the witness generation (and resp.
Definition Constants.h:16
mlir::SymbolRefAttr getPrefixAsSymbolRefAttr(mlir::SymbolRefAttr symbol)
Return SymbolRefAttr like the one given but with the leaf/final element removed.
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
TypeClass getIfSingleton(mlir::TypeRange types)
Definition TypeHelper.h:302
FailureOr< ModuleOp > getRootModule(Operation *from)
FailureOr< TemplateOp > getConstResolutionTemplate(SymbolTableCollection &tables, Operation *origin)
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:217
llvm::function_ref< InFlightDiagnosticWrapper()> EmitErrorFn
Callback to produce an error diagnostic.
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:53
constexpr char FUNC_NAME_PRODUCT[]
Definition Constants.h:18
constexpr T checkedCast(U u) noexcept
Definition Compare.h:94
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)
TypeClass getAtIndex(mlir::TypeRange types, size_t index)
Definition TypeHelper.h:306
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)
OpClass delegate_to_build(mlir::Location location, Args &&...args)
bool hasAffineMapAttr(Type type)
mlir::LogicalResult checkValidType(EmitErrorFn emitError, mlir::Type type)
Definition TypeHelper.h:137
void addTemplateParams(mlir::OpBuilder &odsBuilder, typename OpClass::Properties &props, llvm::ArrayRef< mlir::Attribute > templateParams)
bool isValidConstReadType(Type type)