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
298bool FuncDefOp::hasArgPublicAttr(unsigned index) {
299 if (index < this->getNumArguments()) {
300 DictionaryAttr res = function_interface_impl::getArgAttrDict(*this, index);
301 return res ? res.contains(PublicAttr::name) : false;
302 } else {
303 // TODO: print error? requested attribute for non-existant argument index
304 return false;
305 }
306}
307
308bool FuncDefOp::hasArgName(unsigned index) { return static_cast<bool>(getArgNameAttr(index)); }
309
310std::optional<StringAttr> FuncDefOp::getArgNameAttr(unsigned index) {
311 return getFunctionNameAttrAtIndex(getAllArgAttrs(), index, ARG_NAME_ATTR_NAME);
312}
313
314void FuncDefOp::setArgNameAttr(unsigned index, const StringAttr &attr) {
315 assert(index < getNumArguments() && "argument index out of range");
316 setArgAttr(index, ARG_NAME_ATTR_NAME, attr);
317}
318
319void FuncDefOp::setArgName(unsigned index, StringRef name) {
320 setArgNameAttr(index, StringAttr::get(getContext(), name));
321}
322
323bool FuncDefOp::hasResName(unsigned index) { return static_cast<bool>(getResNameAttr(index)); }
324
325std::optional<StringAttr> FuncDefOp::getResNameAttr(unsigned index) {
326 return getFunctionNameAttrAtIndex(getAllResultAttrs(), index, RES_NAME_ATTR_NAME);
327}
328
329void FuncDefOp::setResNameAttr(unsigned index, const StringAttr &attr) {
330 assert(index < getNumResults() && "result index out of range");
331 setResultAttr(index, RES_NAME_ATTR_NAME, attr);
332}
333
334void FuncDefOp::setResName(unsigned index, StringRef name) {
335 setResNameAttr(index, StringAttr::get(getContext(), name));
336}
337
338LogicalResult FuncDefOp::verify() {
339 OwningEmitErrorFn emitErrorFunc = getEmitOpErrFn(this);
340
341 if ((*this)->hasAttr(ARG_NAME_ATTR_NAME)) {
342 return emitErrorFunc() << '\'' << ARG_NAME_ATTR_NAME << "' is only valid on function arguments";
343 }
344 if ((*this)->hasAttr(RES_NAME_ATTR_NAME)) {
345 return emitErrorFunc() << '\'' << RES_NAME_ATTR_NAME << "' is only valid on function results";
346 }
347
348 if (failed(verifyArgOrResNameAttrs(
349 getAllResultAttrs(), RES_NAME_ATTR_NAME, ARG_NAME_ATTR_NAME, "result", "argument",
350 emitErrorFunc
351 ))) {
352 return failure();
353 }
354
355 if (failed(verifyArgOrResNameAttrs(
356 getAllArgAttrs(), ARG_NAME_ATTR_NAME, RES_NAME_ATTR_NAME, "argument", "result",
357 emitErrorFunc
358 ))) {
359 return failure();
360 }
361
362 // Ensure that only valid LLZK types are used for arguments and return. Additionally, the struct
363 // functions may not use AffineMapAttrs in their parameter types. If such a scenario seems to make
364 // sense when generating LLZK IR, it's likely better to introduce a struct parameter to use
365 // instead and instantiate the struct with that AffineMapAttr.
366 FunctionType type = getFunctionType();
367 for (Type t : type.getInputs()) {
368 if (llzk::checkValidType(emitErrorFunc, t).failed()) {
369 return failure();
370 }
371 if (isInStruct() && hasAffineMapAttr(t)) {
372 return emitErrorFunc().append(
373 "\"@", getName(), "\" parameters cannot contain affine map attributes but found ", t
374 );
375 }
376 }
377 for (Type t : type.getResults()) {
378 if (llzk::checkValidType(emitErrorFunc, t).failed()) {
379 return failure();
380 }
381 }
382 // Ensure that the function does not contain nested modules.
383 // Functions also cannot contain nested structs, but this check is handled
384 // via struct.def's requirement of having module as a parent.
385 WalkResult res = this->walk<WalkOrder::PreOrder>([this](ModuleOp nestedMod) {
386 getEmitOpErrFn(nestedMod)().append(
387 "cannot be nested within '", getOperation()->getName(), "' operations"
388 );
389 return WalkResult::interrupt();
390 });
391 if (res.wasInterrupted()) {
392 return failure();
393 }
394
395 return success();
396}
397
398namespace {
399
400LogicalResult
401verifyFuncTypeCompute(FuncDefOp &origin, SymbolTableCollection &tables, StructDefOp &parent) {
402 FunctionType funcType = origin.getFunctionType();
403 llvm::ArrayRef<Type> resTypes = funcType.getResults();
404 // Must return type of parent struct
405 if (resTypes.size() != 1) {
406 return origin.emitOpError().append(
407 "\"@", FUNC_NAME_COMPUTE, "\" must have exactly one return type"
408 );
409 }
410 if (failed(checkSelfType(tables, parent, resTypes.front(), origin, "return"))) {
411 return failure();
412 }
413
414 // After the more specific checks (to ensure more specific error messages would be produced if
415 // necessary), do the general check that all symbol references in the types are valid. The return
416 // types were already checked so just check the input types.
417 return llzk::verifyTypeResolution(tables, origin, funcType.getInputs());
418}
419
420LogicalResult
421verifyFuncTypeProduct(FuncDefOp &origin, SymbolTableCollection &tables, StructDefOp &parent) {
422 // The signature for @product is the same as the signature for @compute
423 return verifyFuncTypeCompute(origin, tables, parent);
424}
425
426LogicalResult
427verifyFuncTypeConstrain(FuncDefOp &origin, SymbolTableCollection &tables, StructDefOp &parent) {
428 FunctionType funcType = origin.getFunctionType();
429 // Must return '()' type, i.e., have no return types
430 if (funcType.getResults().size() != 0) {
431 return origin.emitOpError() << "\"@" << FUNC_NAME_CONSTRAIN << "\" must have no return type";
432 }
433
434 // Type of the first parameter must match the parent StructDefOp of the current operation.
435 llvm::ArrayRef<Type> inputTypes = funcType.getInputs();
436 if (inputTypes.size() < 1) {
437 return origin.emitOpError() << "\"@" << FUNC_NAME_CONSTRAIN
438 << "\" must have at least one input type";
439 }
440 if (failed(checkSelfType(tables, parent, inputTypes.front(), origin, "first input"))) {
441 return failure();
442 }
443
444 // After the more specific checks (to ensure more specific error messages would be produced if
445 // necessary), do the general check that all symbol references in the types are valid. There are
446 // no return types, just check the remaining input types (the first was already checked via
447 // the checkSelfType() call above).
448 return llzk::verifyTypeResolution(tables, origin, inputTypes.drop_front());
449}
450
451} // namespace
452
453LogicalResult FuncDefOp::verifySymbolUses(SymbolTableCollection &tables) {
454 // Additional checks for the compute/constrain/product functions within a struct
455 if (StructDefOp parentStructOpt = getParentOfType<StructDefOp>(*this)) {
456 // Verify return type restrictions for functions within a StructDefOp
457 if (nameIsCompute()) {
458 return verifyFuncTypeCompute(*this, tables, parentStructOpt);
459 } else if (nameIsConstrain()) {
460 return verifyFuncTypeConstrain(*this, tables, parentStructOpt);
461 } else if (nameIsProduct()) {
462 return verifyFuncTypeProduct(*this, tables, parentStructOpt);
463 }
464 }
465 // In the general case, verify symbol resolution in all input and output types.
466 return verifyTypeResolution(tables, *this, getFunctionType());
467}
468
469SymbolRefAttr FuncDefOp::getFullyQualifiedName(bool requireParent) {
470 return llzk::getFullyQualifiedName(*this, requireParent);
471}
472
474 assert(nameIsCompute()); // skip inStruct check to allow dangling functions
475 // Get the single block of the function body
476 Region &body = getBody();
477 assert(!body.empty() && "compute() function body is empty");
478 Block &block = body.back();
479
480 // The terminator should be the return op
481 Operation *terminator = block.getTerminator();
482 assert(terminator && "compute() function has no terminator");
483 auto retOp = llvm::dyn_cast<ReturnOp>(terminator);
484 if (!retOp) {
485 llvm::errs() << "Expected '" << ReturnOp::getOperationName() << "' but found '"
486 << terminator->getName() << "'\n";
487 llvm_unreachable("compute() function must end with ReturnOp");
488 }
489 return retOp.getOperands().front();
490}
491
493 assert(nameIsConstrain()); // skip inStruct check to allow dangling functions
494 return getArguments().front();
495}
496
498 assert(isStructCompute() && "violated implementation pre-condition");
500}
501
502//===----------------------------------------------------------------------===//
503// ReturnOp
504//===----------------------------------------------------------------------===//
505
506LogicalResult ReturnOp::verify() {
507 auto function = getParentOp<FuncDefOp>(); // parent is FuncDefOp per ODS
508
509 // The operand number and types must match the function signature.
510 const auto results = function.getFunctionType().getResults();
511 if (getNumOperands() != results.size()) {
512 return emitOpError("has ") << getNumOperands() << " operands, but enclosing function (@"
513 << function.getName() << ") returns " << results.size();
514 }
515
516 for (unsigned i = 0, e = results.size(); i != e; ++i) {
517 if (!typesUnify(getOperand(i).getType(), results[i])) {
518 return emitError() << "type of return operand " << i << " (" << getOperand(i).getType()
519 << ") doesn't match function result type (" << results[i] << ")"
520 << " in function @" << function.getName();
521 }
522 }
523
524 return success();
525}
526
527//===----------------------------------------------------------------------===//
528// CallOp
529//===----------------------------------------------------------------------===//
530
531// Custom implementation to deserialize bytecode produced prior to version 2 which added optional
532// `OptionalAttr<ArrayAttr>:$templateParams`.
533LogicalResult CallOp::readProperties(DialectBytecodeReader &reader, OperationState &state) {
534 auto &prop = state.getOrAddProperties<Properties>();
535 if (failed(reader.readAttribute(prop.callee)) ||
536 failed(reader.readAttribute(prop.mapOpGroupSizes)) ||
537 failed(reader.readOptionalAttribute(prop.numDimsPerMap))) {
538 return failure();
539 }
540
541 if (reader.getBytecodeVersion() < /*kNativePropertiesODSSegmentSize=*/6) {
542 auto &propStorage = prop.operandSegmentSizes;
543 DenseI32ArrayAttr attr;
544 if (failed(reader.readAttribute(attr))) {
545 return failure();
546 }
547 if (attr.size() > static_cast<int64_t>(sizeof(propStorage) / sizeof(int32_t))) {
548 reader.emitError("size mismatch for operand/result_segment_size");
549 return failure();
550 }
551 llvm::copy(ArrayRef<int32_t>(attr), propStorage.begin());
552 }
553
554 // The `templateParams` is only available in version 2 or later.
555 auto versionOpt = reader.getDialectVersion<FunctionDialect>();
556 if (succeeded(versionOpt)) {
557 const auto &ver = static_cast<const LLZKDialectVersion &>(**versionOpt);
558 if (ver.majorVersion >= 2) {
559 if (failed(reader.readOptionalAttribute(prop.templateParams))) {
560 return failure();
561 }
562 }
563 }
564
565 if (reader.getBytecodeVersion() >= /*kNativePropertiesODSSegmentSize=*/6) {
566 return reader.readSparseArray(MutableArrayRef(prop.operandSegmentSizes));
567 };
568 return success();
569}
570
571// Same as tablegen would generate to serialize current version IR.
572void CallOp::writeProperties(DialectBytecodeWriter &writer) {
573 auto &prop = getProperties();
574 writer.writeAttribute(prop.callee);
575 writer.writeAttribute(prop.mapOpGroupSizes);
576 writer.writeOptionalAttribute(prop.numDimsPerMap);
577
578 if (writer.getBytecodeVersion() < /*kNativePropertiesODSSegmentSize=*/6) {
579 auto &propStorage = prop.operandSegmentSizes;
580 writer.writeAttribute(DenseI32ArrayAttr::get(this->getContext(), propStorage));
581 }
582
583 writer.writeOptionalAttribute(prop.templateParams);
584
585 auto &propStorage = prop.operandSegmentSizes;
586 if (writer.getBytecodeVersion() >= /*kNativePropertiesODSSegmentSize=*/6) {
587 writer.writeSparseArray(ArrayRef(propStorage));
588 }
589}
590
591void CallOp::build(
592 OpBuilder &odsBuilder, OperationState &odsState, TypeRange resultTypes, SymbolRefAttr callee,
593 ValueRange argOperands, ArrayRef<Attribute> templateParams
594) {
595 odsState.addTypes(resultTypes);
596 odsState.addOperands(argOperands);
598 odsBuilder, odsState, llzk::checkedCast<int32_t>(argOperands.size())
599 );
600 props.setCallee(callee);
601 addTemplateParams<CallOp>(odsBuilder, props, templateParams);
602}
603
604void CallOp::build(
605 OpBuilder &odsBuilder, OperationState &odsState, TypeRange resultTypes, SymbolRefAttr callee,
606 ArrayRef<ValueRange> mapOperands, DenseI32ArrayAttr numDimsPerMap, ValueRange argOperands,
607 ArrayRef<Attribute> templateParams
608) {
609 odsState.addTypes(resultTypes);
610 odsState.addOperands(argOperands);
612 odsBuilder, odsState, mapOperands, numDimsPerMap,
613 llzk::checkedCast<int32_t>(argOperands.size())
614 );
615 props.setCallee(callee);
616 addTemplateParams<CallOp>(odsBuilder, props, templateParams);
617}
618
619LogicalResult
620CallOp::verifyTemplateParamCompatibility(Attribute paramFromCallOp, TemplateParamOp targetParam) {
621 // A wildcard `?` (represented as kDynamic) defers inference to a later pass.
622 // It is only valid for parameters with a `!poly.tvar` type restriction.
623 if (auto intAttr = llvm::dyn_cast<IntegerAttr>(paramFromCallOp)) {
624 if (isDynamic(intAttr)) {
625 std::optional<Type> declaredType = targetParam.getTypeOpt();
626 if (!declaredType || !llvm::isa<TypeVarType>(*declaredType)) {
627 auto diag = this->emitOpError().append(
628 "wildcard `?` can only be used for template parameters with `!poly.tvar` "
629 "type restriction, but parameter \"@",
630 targetParam.getName(), "\" has "
631 );
632 if (declaredType) {
633 diag.append("type restriction ", *declaredType);
634 } else {
635 diag.append("no type restriction");
636 }
637 return diag;
638 }
639 return success();
640 }
641 }
642 if (std::optional<Type> declaredType = targetParam.getTypeOpt()) {
643 bool compatible = false;
644 if (auto sym = llvm::dyn_cast<SymbolRefAttr>(paramFromCallOp)) {
645 if (sym.getNestedReferences().empty()) {
646 SymbolTableCollection tables;
647 FailureOr<TemplateOp> parentTemplate = getConstResolutionTemplate(tables, *this);
648 if (failed(parentTemplate)) {
649 return failure();
650 }
651 if (TemplateOp p = *parentTemplate) {
652 auto binding = p.getConstNamed<TemplateSymbolBindingOpInterface>(sym.getRootReference());
653 if (binding) {
654 // Once we know it references a template symbol binding, assume it's compatible unless
655 // the optional type is present and doesn't unify with the declared type.
656 if (std::optional<Type> actualType = binding.getTypeOpt()) {
657 compatible = typesUnify(*actualType, *declaredType);
658 } else {
659 compatible = true;
660 }
661 }
662 }
663 }
664 } else if (llvm::isa<TypeVarType>(*declaredType)) {
665 compatible = llvm::isa<TypeAttr>(paramFromCallOp);
666 } else if (llvm::isa<FeltType>(*declaredType)) {
667 compatible = llvm::isa<FeltConstAttr, IntegerAttr>(paramFromCallOp) &&
668 isValidConstReadType(llvm::cast<TypedAttr>(paramFromCallOp).getType());
669 } else if (llvm::isa<IndexType, IntegerType>(*declaredType)) {
670 // Note: Just like struct type instantiation, there is no restriction on passing a
671 // larger value to an `i1`. The flattening pass will treat 0 as false and any other
672 // value as true (but give a warning if it's not 1).
673 compatible = llvm::isa<IntegerAttr>(paramFromCallOp) &&
674 isValidConstReadType(llvm::cast<TypedAttr>(paramFromCallOp).getType());
675 } else {
676 // Note: `declaredType` is restricted by `isValidConstReadType()`
677 llvm_unreachable("inconsistent with `isValidConstReadType()`");
678 }
679 if (!compatible) {
680 // Tested in call_with_template_params_fail.llzk
681 return this->emitOpError().append(
682 "instantiation value '", paramFromCallOp, "' is not compatible with parameter \"@",
683 targetParam.getName(), "\" type restriction ", *declaredType
684 );
685 }
686 }
687 return success();
688}
689
691 llvm::iterator_range<Region::op_iterator<TemplateParamOp>> targetParamDefs
692) {
693 ArrayAttr callParams = this->getTemplateParamsAttr();
694 assert(!isNullOrEmpty(callParams) && "pre-condition");
695 assert((callParams.size() == llvm::range_size(targetParamDefs)) && "pre-condition");
696
697 for (auto [paramOp, attr] : llvm::zip_equal(targetParamDefs, callParams.getValue())) {
698 if (failed(verifyTemplateParamCompatibility(attr, paramOp))) {
699 return failure();
700 }
701 }
702 return success();
703}
704
706 llvm::iterator_range<Region::op_iterator<TemplateParamOp>> targetParamDefs,
707 const UnificationMap &unifications
708) {
709 ArrayAttr callParams = this->getTemplateParamsAttr();
710 assert(!isNullOrEmpty(callParams) && "pre-condition");
711 assert((callParams.size() == llvm::range_size(targetParamDefs)) && "pre-condition");
712
713 for (auto [paramOp, attr] : llvm::zip_equal(targetParamDefs, callParams.getValue())) {
714 // Skip wildcards (`?` / kDynamic) - their value will be resolved by a later inference pass.
715 if (auto intAttr = llvm::dyn_cast<IntegerAttr>(attr)) {
716 if (isDynamic(intAttr)) {
717 continue;
718 }
719 }
720 auto it = unifications.find({FlatSymbolRefAttr::get(paramOp.getNameAttr()), Side::RHS});
721 if (it != unifications.end() && !typeParamsUnify({attr}, {it->second})) {
722 // Tested in call_with_template_params_fail.llzk
723 return this->emitOpError().append(
724 "template instantiation value '", attr, "' for parameter \"@", paramOp.getName(),
725 "\" conflicts with value '", it->second, "' inferred from function type signature"
726 );
727 }
728 }
729 return success();
730}
731
732namespace {
733
734struct CallOpVerifier {
735 CallOpVerifier(CallOp *c, FunctionKind tgtFuncKind) : callOp(c), tgtKind(tgtFuncKind) {}
736 CallOpVerifier(CallOp *c, StringRef tgtName) : CallOpVerifier(c, fnNameToKind(tgtName)) {}
737 virtual ~CallOpVerifier() = default;
738
739 LogicalResult verify() {
740 // Rather than immediately returning on failure, we check all verifier steps and aggregate to
741 // provide as many errors are possible in a single verifier run.
742 LogicalResult aggregateResult = success();
743 if (failed(verifyTargetAttributes())) {
744 aggregateResult = failure();
745 }
746 if (failed(verifyInputs())) {
747 aggregateResult = failure();
748 }
749 if (failed(verifyOutputs())) {
750 aggregateResult = failure();
751 }
752 if (failed(verifyTemplateParams())) {
753 aggregateResult = failure();
754 }
755 if (failed(verifyAffineMapParams())) {
756 aggregateResult = failure();
757 }
758 return aggregateResult;
759 }
760
761protected:
762 CallOp *callOp;
763 FunctionKind tgtKind;
764
765 virtual LogicalResult verifyTargetAttributes() = 0;
766 virtual LogicalResult verifyInputs() = 0;
767 virtual LogicalResult verifyOutputs() = 0;
768 virtual LogicalResult verifyTemplateParams() = 0;
769 virtual LogicalResult verifyAffineMapParams() = 0;
770
772 LogicalResult verifyTargetAttributesMatch(FuncDefOp target) {
773 LogicalResult aggregateRes = success();
774 if (FuncDefOp caller = (*callOp)->getParentOfType<FuncDefOp>()) {
775 auto emitAttrErr = [&](StringLiteral attrName) {
776 aggregateRes = callOp->emitOpError()
777 << "target '@" << target.getName() << "' has '" << attrName
778 << "' attribute, which is not specified by the caller '@" << caller.getName()
779 << '\'';
780 };
781
782 if (target.hasAllowConstraintAttr() && !caller.hasAllowConstraintAttr()) {
783 emitAttrErr(AllowConstraintAttr::name);
784 }
785 if (target.hasAllowWitnessAttr() && !caller.hasAllowWitnessAttr()) {
786 emitAttrErr(AllowWitnessAttr::name);
787 }
788 if (target.hasAllowNonNativeFieldOpsAttr() && !caller.hasAllowNonNativeFieldOpsAttr()) {
789 emitAttrErr(AllowNonNativeFieldOpsAttr::name);
790 }
791 }
792 return aggregateRes;
793 }
794
795 LogicalResult verifyNoTemplateInstantiations() {
796 if (!isNullOrEmpty(callOp->getTemplateParamsAttr())) {
797 // Tested in call_with_template_params_fail.llzk
798 return callOp->emitOpError().append(
799 "can only have template instantiations when targeting a templated free function"
800 );
801 }
802 return success();
803 }
804
805 LogicalResult verifyNoAffineMapInstantiations() {
806 if (!isNullOrEmpty(callOp->getMapOpGroupSizesAttr())) {
807 // Tested in call_with_affinemap_fail.llzk
808 return callOp->emitOpError().append(
809 "can only have affine map instantiations when targeting a \"@", FUNC_NAME_COMPUTE,
810 "\" function"
811 );
812 }
813 // ASSERT: the check above is sufficient due to VerifySizesForMultiAffineOps trait.
814 assert(isNullOrEmpty(callOp->getNumDimsPerMapAttr()));
815 assert(callOp->getMapOperands().empty());
816 return success();
817 }
818};
819
820struct KnownTargetVerifier : public CallOpVerifier {
821 KnownTargetVerifier(CallOp *c, SymbolLookupResult<FuncDefOp> &&tgtRes)
822 : CallOpVerifier(c, tgtRes.get().getSymName()), tgt(*tgtRes), tgtType(tgt.getFunctionType()),
823 includeSymNames(tgtRes.getNamespace()) {}
824
825 LogicalResult verifyTargetAttributes() override {
826 return CallOpVerifier::verifyTargetAttributesMatch(tgt);
827 }
828
829 LogicalResult verifyInputs() override {
830 return verifyTypesMatch(callOp->getArgOperands().getTypes(), tgtType.getInputs(), "operand");
831 }
832
833 LogicalResult verifyOutputs() override {
834 return verifyTypesMatch(callOp->getResultTypes(), tgtType.getResults(), "result");
835 }
836
837 LogicalResult verifyTemplateParams() override {
838 Operation *tgtOp = tgt.getOperation();
839 if (isInStruct(tgtOp)) {
840 // Struct function calls cannot contain template parameter instantiations.
841 return verifyNoTemplateInstantiations();
842 } else if (TemplateOp tgtOpParent = getParentOfType<TemplateOp>(tgtOp)) {
843 // When the target function is a free function within a TemplateOp, the CallOp may have
844 // template parameter instantiations that must be checked against the template parameters.
845 // - If the function type signature references all template parameters, then the parameter
846 // instantiation list on the CallOp is optional, otherwise it's required.
847 // - If present, the instantiation list must provide a value for every template parameter
848 // and the value must be type-compatible with the parameter's declared type (if any).
849 // - If present, the instantiation list must result in a function type signature that can
850 // be unified with the CallOp's operand and result types.
851 auto realParams = tgtOpParent.getConstOps<TemplateParamOp>();
852 ArrayAttr callParams = callOp->getTemplateParamsAttr();
853
854 // When there is no instantiation list, just ensure that it's not required.
855 if (isNullOrEmpty(callParams)) {
856 llvm::SmallDenseSet<SymbolRefAttr> referencedInSignature;
857 llzk::getSymbolsUsedIn(tgtType.getInputs(), referencedInSignature);
858 llzk::getSymbolsUsedIn(tgtType.getResults(), referencedInSignature);
859
860 bool allParamsReferenced = llvm::all_of(realParams, [&](TemplateParamOp p) {
861 return referencedInSignature.contains(FlatSymbolRefAttr::get(p.getNameAttr()));
862 });
863 if (allParamsReferenced) {
864 return success();
865 }
866 // Tested in call_with_template_params_fail.llzk
867 return callOp->emitOpError().append(
868 "must provide template instantiation parameters when calling \"@", tgt.getSymName(),
869 "\" because not all template parameters of \"@", tgtOpParent.getSymName(),
870 "\" appear in the function type signature"
871 );
872 }
873
874 // Ensure `forceIntAttrTypes()` was successful on the CallOp's template parameters.
875 if (failed(llzk::forceIntAttrTypes(callParams.getValue(), [this] {
876 return llzk::InFlightDiagnosticWrapper(this->callOp->emitOpError());
877 }))) {
878 return failure();
879 }
880
881 // The instantiation list is present. Check it has exactly one entry per template param.
882 size_t numTemplateParams = llvm::range_size(realParams);
883 if (callParams.size() != numTemplateParams) {
884 // Tested in call_with_template_params_fail.llzk
885 return callOp->emitOpError().append(
886 "template instantiation has ", callParams.size(), " parameter(s) but \"@",
887 tgtOpParent.getSymName(), "\" expects ", numTemplateParams, " template parameter(s)"
888 );
889 }
890
891 // Check type compatibility of each provided value with the declared parameter type (if any).
892 if (failed(callOp->verifyTemplateParamCompatibility(realParams))) {
893 return failure();
894 }
895
896 // Check that the provided instantiation values are consistent with what type unification
897 // of the target function types against the call's operand and result types would determine.
898 FailureOr<UnificationMap> unifyResult = callOp->unifyTypeSignature(tgtType);
899 assert(succeeded(unifyResult) && "already checked by `verifyInputs()` and `verifyOutputs()`");
900 return callOp->verifyTemplateParamsMatchInferred(realParams, unifyResult.value());
901 } else {
902 // Non-template functions cannot contain template parameter instantiations.
903 return verifyNoTemplateInstantiations();
904 }
905 }
906
907 LogicalResult verifyAffineMapParams() override {
908 if ((FunctionKind::StructCompute == tgtKind || FunctionKind::StructProduct == tgtKind) &&
909 isInStruct(tgt.getOperation())) {
910 // Return type should be a single StructType. If that is not the case here, just bail without
911 // producing an error. The combination of this KnownTargetVerifier resolving the callee to a
912 // specific FuncDefOp and verifyFuncTypeCompute() ensuring all FUNC_NAME_COMPUTE FuncOps have
913 // a single StructType return value will produce a more relevant error message in that case.
914 if (StructType retTy = callOp->getSingleResultTypeOfWitnessGen()) {
915 if (ArrayAttr params = retTy.getParams()) {
916 // Collect the struct parameters that are defined via AffineMapAttr
917 SmallVector<AffineMapAttr> mapAttrs;
918 for (Attribute a : params) {
919 if (AffineMapAttr m = dyn_cast<AffineMapAttr>(a)) {
920 mapAttrs.push_back(m);
921 }
922 }
924 callOp->getMapOperands(), callOp->getNumDimsPerMap(), mapAttrs, *callOp
925 );
926 }
927 }
928 return success();
929 } else {
930 // Global functions and constrain functions cannot have affine map instantiations.
931 return verifyNoAffineMapInstantiations();
932 }
933 }
934
935private:
936 template <typename T>
937 LogicalResult
938 verifyTypesMatch(ValueTypeRange<T> callOpTypes, ArrayRef<Type> tgtTypes, const char *aspect) {
939 if (tgtTypes.size() != callOpTypes.size()) {
940 return callOp->emitOpError()
941 .append("incorrect number of ", aspect, "s for callee, expected ", tgtTypes.size())
942 .attachNote(tgt.getLoc())
943 .append("callee defined here");
944 }
945 for (unsigned i = 0, e = tgtTypes.size(); i != e; ++i) {
946 if (!typesUnify(callOpTypes[i], tgtTypes[i], includeSymNames)) {
947 return callOp->emitOpError().append(
948 aspect, " type mismatch: expected type ", tgtTypes[i], ", but found ", callOpTypes[i],
949 " for ", aspect, " number ", i
950 );
951 }
952 }
953 return success();
954 }
955
956 FuncDefOp tgt;
957 FunctionType tgtType;
958 std::vector<llvm::StringRef> includeSymNames;
959};
960
963LogicalResult checkSelfTypeUnknownTarget(
964 StringAttr expectedParamName, Type actualType, CallOp *origin, const char *aspect
965) {
966 if (!llvm::isa<TypeVarType>(actualType) ||
967 llvm::cast<TypeVarType>(actualType).getRefName() != expectedParamName) {
968 // Tested in function_restrictions_fail.llzk:
969 // Non-tvar for constrain input via "call_target_constrain_without_self_non_struct"
970 // Non-tvar for compute output via "call_target_compute_wrong_type_ret"
971 // Wrong tvar for constrain input via "call_target_constrain_without_self_wrong_tvar_param"
972 // Wrong tvar for compute output via "call_target_compute_wrong_tvar_param_ret"
973 return origin->emitOpError().append(
974 "target \"@", origin->getCallee().getLeafReference().getValue(), "\" expected ", aspect,
975 " type '!", TypeVarType::name, "<@", expectedParamName.getValue(), ">' but found ",
976 actualType
977 );
978 }
979 return success();
980}
981
991struct UnknownTargetVerifier : public CallOpVerifier {
992 UnknownTargetVerifier(CallOp *c, FunctionKind tgtFuncKind, SymbolRefAttr callee)
993 : CallOpVerifier(c, tgtFuncKind), calleeAttr(callee) {
994 assert(
995 tgtFuncKind == FunctionKind::StructCompute ||
996 tgtFuncKind == FunctionKind::StructConstrain || tgtFuncKind == FunctionKind::StructProduct
997 ); // pre-condition mentioned above
998 }
999
1000 LogicalResult verifyTargetAttributes() override {
1001 // Based on the precondition of this verifier, the target must be either a
1002 // struct compute, constrain, or product function.
1003 LogicalResult aggregateRes = success();
1004 if (FuncDefOp caller = (*callOp)->getParentOfType<FuncDefOp>()) {
1005 auto emitAttrErr = [&](StringLiteral attrName) {
1006 aggregateRes = callOp->emitOpError()
1007 << "target '" << calleeAttr << "' has '" << attrName
1008 << "' attribute, which is not specified by the caller '@" << caller.getName()
1009 << '\'';
1010 };
1011
1012 switch (tgtKind) {
1014 if (!caller.hasAllowConstraintAttr()) {
1015 emitAttrErr(AllowConstraintAttr::name);
1016 }
1017 break;
1019 if (!caller.hasAllowWitnessAttr()) {
1020 emitAttrErr(AllowWitnessAttr::name);
1021 }
1022 break;
1024 if (!caller.hasAllowWitnessAttr()) {
1025 emitAttrErr(AllowWitnessAttr::name);
1026 }
1027 if (!caller.hasAllowConstraintAttr()) {
1028 emitAttrErr(AllowConstraintAttr::name);
1029 }
1030 break;
1031 default:
1032 break;
1033 }
1034 }
1035 return aggregateRes;
1036 }
1037
1038 LogicalResult verifyInputs() override {
1039 if (FunctionKind::StructCompute == tgtKind || FunctionKind::StructProduct == tgtKind) {
1040 // Without known target, no additional checks can be done.
1041 } else if (FunctionKind::StructConstrain == tgtKind) {
1042 // Without known target, this can only check that the first input is VarType using the same
1043 // struct parameter as the base of the callee (later replaced with the target struct's type).
1044 Operation::operand_type_range inputTypes = callOp->getArgOperands().getTypes();
1045 if (inputTypes.size() < 1) {
1046 // Tested in function_restrictions_fail.llzk
1047 return callOp->emitOpError()
1048 << "target \"@" << FUNC_NAME_CONSTRAIN << "\" must have at least one input type";
1049 }
1050 return checkSelfTypeUnknownTarget(
1051 calleeAttr.getRootReference(), inputTypes.front(), callOp, "first input"
1052 );
1053 }
1054 return success();
1055 }
1056
1057 LogicalResult verifyOutputs() override {
1058 if (FunctionKind::StructCompute == tgtKind || FunctionKind::StructProduct == tgtKind) {
1059 // Without known target, this can only check that the function returns VarType using the same
1060 // struct parameter as the base of the callee (later replaced with the target struct's type).
1061 Operation::result_type_range resTypes = callOp->getResultTypes();
1062 if (resTypes.size() != 1) {
1063 // Tested in function_restrictions_fail.llzk
1064 return callOp->emitOpError().append(
1065 "target \"@", FUNC_NAME_COMPUTE, "\" must have exactly one return type"
1066 );
1067 }
1068 return checkSelfTypeUnknownTarget(
1069 calleeAttr.getRootReference(), resTypes.front(), callOp, "return"
1070 );
1071 } else if (FunctionKind::StructConstrain == tgtKind) {
1072 // Without known target, this can only check that the function has no return
1073 if (callOp->getNumResults() != 0) {
1074 // Tested in function_restrictions_fail.llzk
1075 return callOp->emitOpError()
1076 << "target \"@" << FUNC_NAME_CONSTRAIN << "\" must have no return type";
1077 }
1078 }
1079 return success();
1080 }
1081
1082 LogicalResult verifyTemplateParams() override {
1083 // Struct function calls cannot contain template parameter instantiations.
1084 return verifyNoTemplateInstantiations();
1085 }
1086
1087 LogicalResult verifyAffineMapParams() override {
1088 if (FunctionKind::StructCompute == tgtKind || FunctionKind::StructProduct == tgtKind) {
1089 // Without known target, no additional checks can be done.
1090 } else if (FunctionKind::StructConstrain == tgtKind) {
1091 // Without known target, this can only check that there are no affine map instantiations.
1092 return verifyNoAffineMapInstantiations();
1093 }
1094 return success();
1095 }
1096
1097private:
1098 SymbolRefAttr calleeAttr;
1099};
1100
1101} // namespace
1102
1103LogicalResult CallOp::verifySymbolUses(SymbolTableCollection &tables) {
1104 // First, verify symbol resolution in all input and output types.
1105 if (failed(verifyTypeResolution(tables, *this, getTypeSignature()))) {
1106 return failure(); // verifyTypeResolution() already emits a sufficient error message
1107 }
1108
1109 // Check that the callee attribute was specified.
1110 SymbolRefAttr calleeAttr = getCalleeAttr();
1111 if (!calleeAttr) {
1112 return emitOpError("requires a 'callee' symbol reference attribute");
1113 }
1114
1115 // If the callee references a parameter of the template where this call appears, perform
1116 // the subset of checks that can be done even though the target is unknown.
1117 if (calleeAttr.getNestedReferences().size() == 1) {
1118 if (TemplateOp parent = getParentOfType<TemplateOp>(*this)) {
1119 if (parent.hasConstNamed<TemplateParamOp>(calleeAttr.getRootReference())) {
1120 FunctionKind tgtKind = fnNameToKind(calleeAttr.getLeafReference().getValue());
1121 if (tgtKind != FunctionKind::Free) {
1122 return UnknownTargetVerifier(this, tgtKind, calleeAttr).verify();
1123 }
1124 return this->emitError("expected parameterized callee to target a struct function")
1125 .append(
1126 " (i.e. \"@", FUNC_NAME_PRODUCT, "\", \"@", FUNC_NAME_COMPUTE, "\", or \"@",
1127 FUNC_NAME_CONSTRAIN, "\")"
1128 );
1129 }
1130 }
1131 }
1132
1133 // Otherwise, callee must be specified via full path from the root module. Perform the full set of
1134 // checks against the known target function.
1135 auto tgtOpt = lookupTopLevelSymbol<FuncDefOp>(tables, calleeAttr, *this);
1136 if (failed(tgtOpt)) {
1137 return this->emitError() << "expected '" << FuncDefOp::getOperationName() << "' named \""
1138 << calleeAttr << '"';
1139 }
1140 return KnownTargetVerifier(this, std::move(*tgtOpt)).verify();
1141}
1142
1144 return FunctionType::get(getContext(), getArgOperands().getTypes(), getResultTypes());
1145}
1146
1147FailureOr<UnificationMap> CallOp::unifyTypeSignature(FunctionType other) {
1148 UnificationMap unifications;
1149 if (functionTypesUnify(getTypeSignature(), other, {}, &unifications)) {
1150 return unifications;
1151 } else {
1152 return failure();
1153 }
1154}
1155
1156namespace {
1157
1158bool calleeIsStructFunctionImpl(
1159 const char *funcName, SymbolRefAttr callee, llvm::function_ref<StructType()> getType
1160) {
1161 if (callee.getLeafReference() == funcName) {
1162 if (StructType t = getType()) {
1163 // If the name ref within the StructType matches the `callee` prefix (i.e., sans the function
1164 // name itself), then the `callee` target must be within a StructDefOp because validation
1165 // checks elsewhere ensure that every StructType references a StructDefOp (i.e., the `callee`
1166 // function is not simply a free function nested within a ModuleOp)
1167 return t.getNameRef() == getPrefixAsSymbolRefAttr(callee);
1168 }
1169 }
1170 return false;
1171}
1172
1173} // namespace
1174
1176 return calleeIsStructFunctionImpl(FUNC_NAME_COMPUTE, getCallee(), [this]() {
1177 return this->getSingleResultTypeOfCompute();
1178 });
1179}
1180
1182 return calleeIsStructFunctionImpl(FUNC_NAME_PRODUCT, getCallee(), [this]() {
1183 return this->getSingleResultTypeOfWitnessGen();
1184 });
1185}
1186
1188 return calleeIsStructFunctionImpl(FUNC_NAME_CONSTRAIN, getCallee(), [this]() {
1189 return getAtIndex<StructType>(this->getArgOperands().getTypes(), 0);
1190 });
1191}
1192
1194 assert(calleeIsStructCompute());
1195 return getResults().front();
1196}
1197
1199 assert(calleeIsStructConstrain());
1200 return getArgOperands().front();
1201}
1202
1203FailureOr<SymbolLookupResult<FuncDefOp>> CallOp::getCalleeTarget(SymbolTableCollection &tables) {
1204 Operation *thisOp = this->getOperation();
1205 auto root = getRootModule(thisOp);
1206 assert(succeeded(root));
1207 return llzk::lookupSymbolIn<FuncDefOp>(tables, getCallee(), root->getOperation(), thisOp);
1208}
1209
1211 assert(calleeIsCompute() && "violated implementation pre-condition");
1212 return getIfSingleton<StructType>(getResultTypes());
1213}
1214
1216 assert(calleeContainsWitnessGen() && "violated implementation pre-condition");
1217 return getIfSingleton<StructType>(getResultTypes());
1218}
1219
1221CallInterfaceCallable CallOp::getCallableForCallee() { return getCalleeAttr(); }
1222
1224void CallOp::setCalleeFromCallable(CallInterfaceCallable callee) {
1225 setCalleeAttr(llvm::cast<SymbolRefAttr>(callee));
1226}
1227
1228SmallVector<ValueRange> CallOp::toVectorOfValueRange(OperandRangeRange input) {
1229 llvm::SmallVector<ValueRange, 4> output;
1230 output.reserve(input.size());
1231 for (OperandRange r : input) {
1232 output.push_back(r);
1233 }
1234 return output;
1235}
1236
1237Operation *CallOp::resolveCallableInTable(SymbolTableCollection *symbolTable) {
1238 FailureOr<SymbolLookupResult<FuncDefOp>> res =
1239 llzk::resolveCallable<FuncDefOp>(*symbolTable, *this);
1240 if (failed(res) || res->isManaged()) {
1241 // Cannot return pointer to a managed Operation since it would cause memory errors.
1242 return nullptr;
1243 }
1244 return res->get();
1245}
1246
1248 SymbolTableCollection tables;
1249 return resolveCallableInTable(&tables);
1250}
1251
1252} // 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:1187
::mlir::CallInterfaceCallable getCallableForCallee()
Return the callee of this operation.
Definition Ops.cpp:1221
::llzk::component::StructType getSingleResultTypeOfCompute()
Assuming the callee is FUNC_NAME_COMPUTE, return the single StructType result.
Definition Ops.cpp:1210
::mlir::SymbolRefAttr getCalleeAttr()
Definition Ops.h.inc:292
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:1103
::mlir::Operation * resolveCallableInTable(::mlir::SymbolTableCollection *symbolTable)
Required by CallOpInterface.
Definition Ops.cpp:1237
bool calleeIsStructCompute()
Return true iff the callee function name is FUNC_NAME_COMPUTE within a StructDefOp.
Definition Ops.cpp:1175
::mlir::SymbolRefAttr getCallee()
Definition Ops.cpp.inc:470
::mlir::Operation * resolveCallable()
Required by CallOpInterface.
Definition Ops.cpp:1247
void writeProperties(::mlir::DialectBytecodeWriter &writer)
Definition Ops.cpp:572
bool calleeIsStructProduct()
Return true iff the callee function name is FUNC_NAME_PRODUCT within a StructDefOp.
Definition Ops.cpp:1181
::mlir::FunctionType getTypeSignature()
Return the FunctionType inferred from the arg operands and result types of this CallOp.
Definition Ops.cpp:1143
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:1198
::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:1228
::llzk::component::StructType getSingleResultTypeOfWitnessGen()
Assuming the callee contains witness generation code, return the single StructType result.
Definition Ops.cpp:1215
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:533
::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:1147
::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:705
::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:1193
void setCalleeFromCallable(::mlir::CallInterfaceCallable callee)
Set the callee for this operation.
Definition Ops.cpp:1224
::mlir::FailureOr<::llzk::SymbolLookupResult<::llzk::function::FuncDefOp > > getCalleeTarget(::mlir::SymbolTableCollection &tables)
Resolve and return the target FuncDefOp for this CallOp.
Definition Ops.cpp:1203
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:314
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:319
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:453
::mlir::Value getSelfValueFromCompute()
Return the "self" value (i.e.
Definition Ops.cpp:473
::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:329
::mlir::StringAttr getFunctionTypeAttrName()
Definition Ops.h.inc:650
bool hasAllowNonNativeFieldOpsAttr()
Return true iff the function def has the allow_non_native_field_ops attribute.
Definition Ops.h.inc:828
::mlir::Value getSelfValueFromConstrain()
Return the "self" value (i.e.
Definition Ops.cpp:492
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:885
bool hasAllowWitnessAttr()
Return true iff the function def has the allow_witness attribute.
Definition Ops.h.inc:820
::llzk::component::StructType getSingleResultTypeOfCompute()
Assuming the name is FUNC_NAME_COMPUTE, return the single StructType result.
Definition Ops.cpp:497
::mlir::StringAttr getResAttrsAttrName()
Definition Ops.h.inc:658
::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:308
void cloneInto(FuncDefOp dest, ::mlir::IRMapping &mapper)
Clone the internal blocks and attributes from this function into dest.
Definition Ops.cpp:202
bool hasResName(unsigned index)
Return true iff the result at the given index has a function.res_name attribute.
Definition Ops.cpp:323
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:893
::std::optional<::mlir::StringAttr > getArgNameAttr(unsigned index)
Return the function.arg_name attribute for the argument at the given index.
Definition Ops.cpp:310
::std::optional<::mlir::StringAttr > getResNameAttr(unsigned index)
Return the function.res_name attribute for the result at the given index.
Definition Ops.cpp:325
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:889
bool isStructCompute()
Return true iff the function is within a StructDefOp and named FUNC_NAME_COMPUTE.
Definition Ops.h.inc:899
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:334
bool isInStruct()
Return true iff the function is within a StructDefOp.
Definition Ops.h.inc:896
::llvm::ArrayRef<::mlir::Type > getResultTypes()
Required by FunctionOpInterface.
Definition Ops.h.inc:874
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:674
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:469
bool hasArgPublicAttr(unsigned index)
Return true iff the argument at the given index has pub attribute.
Definition Ops.cpp:298
::llvm::LogicalResult verify()
Definition Ops.cpp:338
::mlir::Region & getBody()
Definition Ops.h.inc:698
bool hasAllowConstraintAttr()
Return true iff the function def has the allow_constraint attribute.
Definition Ops.h.inc:812
::mlir::StringAttr getArgAttrsAttrName()
Definition Ops.h.inc:642
::llvm::LogicalResult verify()
Definition Ops.cpp:506
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:1000
::std::optional<::mlir::Type > getTypeOpt()
Definition Ops.cpp.inc:1337
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:307
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:223
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: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)
TypeClass getAtIndex(mlir::TypeRange types, size_t index)
Definition TypeHelper.h:311
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:143
void addTemplateParams(mlir::OpBuilder &odsBuilder, typename OpClass::Properties &props, llvm::ArrayRef< mlir::Attribute > templateParams)
bool isValidConstReadType(Type type)