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 - Struct 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//===----------------------------------------------------------------------===//
10
12
23#include "llzk/Util/Constants.h"
24#include "llzk/Util/Debug.h"
27
28#include <mlir/IR/IRMapping.h>
29#include <mlir/IR/OpImplementation.h>
30
31#include <llvm/ADT/MapVector.h>
32#include <llvm/ADT/STLExtras.h>
33#include <llvm/ADT/StringRef.h>
34#include <llvm/ADT/StringSet.h>
35#include <llvm/ADT/TypeSwitch.h>
36
37#include <optional>
38
39// TableGen'd implementation files
41
42// TableGen'd implementation files
43#define GET_OP_CLASSES
45
46using namespace mlir;
47using namespace llzk::felt;
48using namespace llzk::array;
49using namespace llzk::felt;
50using namespace llzk::function;
51using namespace llzk::pod;
52using namespace llzk::polymorphic;
53using namespace llzk::verif;
54
55namespace llzk::component {
56
57bool isInStruct(Operation *op) { return getParentOfType<StructDefOp>(op); }
58
59FailureOr<StructDefOp> verifyInStruct(Operation *op) {
61 return res;
62 }
63 return op->emitOpError() << "only valid within a '" << StructDefOp::getOperationName()
64 << "' ancestor";
65}
66
67bool isInStructFunctionNamed(Operation *op, char const *funcName) {
68 if (FuncDefOp parentFunc = getParentOfType<FuncDefOp>(op)) {
69 if (isInStruct(parentFunc.getOperation())) {
70 if (parentFunc.getSymName().compare(funcName) == 0) {
71 return true;
72 }
73 }
74 }
75 return false;
76}
77
78// Again, only valid/implemented for StructDefOp
79template <> LogicalResult SetFuncAllowAttrs<StructDefOp>::verifyTrait(Operation *structOp) {
80 assert(llvm::isa<StructDefOp>(structOp));
81 Region &bodyRegion = llvm::cast<StructDefOp>(structOp).getBodyRegion();
82 if (!bodyRegion.empty()) {
83 bodyRegion.front().walk([](FuncDefOp funcDef) {
84 if (funcDef.nameIsConstrain()) {
85 funcDef.setAllowConstraintAttr();
86 funcDef.setAllowWitnessAttr(false);
87 } else if (funcDef.nameIsCompute()) {
88 funcDef.setAllowConstraintAttr(false);
89 funcDef.setAllowWitnessAttr();
90 } else if (funcDef.nameIsProduct()) {
91 funcDef.setAllowConstraintAttr();
92 funcDef.setAllowWitnessAttr();
93 }
94 });
95 }
96 return success();
97}
98
99InFlightDiagnostic genCompareErr(StructDefOp expected, Operation *origin, const char *aspect) {
100 std::string prefix = std::string();
101 if (SymbolOpInterface symbol = llvm::dyn_cast<SymbolOpInterface>(origin)) {
102 prefix += "\"@";
103 prefix += symbol.getName();
104 prefix += "\" ";
105 }
106 return origin->emitOpError().append(
107 prefix, "must use type of its ancestor '", StructDefOp::getOperationName(), "' \"",
108 expected.getHeaderString(), "\" as ", aspect, " type"
109 );
110}
111
112static inline InFlightDiagnostic structFuncDefError(Operation *origin) {
113 return origin->emitError() << '\'' << StructDefOp::getOperationName() << "' op "
114 << "must define either only a non-derived \"@" << FUNC_NAME_PRODUCT
115 << "\" function, or both non-derived \"@" << FUNC_NAME_COMPUTE
116 << "\" and \"@" << FUNC_NAME_CONSTRAIN << "\" functions; ";
117}
118
121LogicalResult checkSelfType(
122 SymbolTableCollection &tables, StructDefOp expectedStruct, Type actualType, Operation *origin,
123 const char *aspect
124) {
125 if (StructType actualStructType = llvm::dyn_cast<StructType>(actualType)) {
126 auto actualStructOpt =
127 lookupTopLevelSymbol<StructDefOp>(tables, actualStructType.getNameRef(), origin);
128 if (failed(actualStructOpt)) {
129 return origin->emitError().append(
130 "could not find '", StructDefOp::getOperationName(), "' named \"",
131 actualStructType.getNameRef(), '"'
132 );
133 }
134 StructDefOp actualStruct = actualStructOpt.value().get();
135 if (actualStruct != expectedStruct) {
136 return genCompareErr(expectedStruct, origin, aspect)
137 .attachNote(actualStruct.getLoc())
138 .append("uses this type instead");
139 }
140 // Check for an EXACT match in the parameter list since it must reference the "self" type.
141 ArrayAttr actualTypeParamsAttr = actualStructType.getParams(); // may be nullptr
142 ArrayRef<Attribute> actualTypeParams =
143 actualTypeParamsAttr ? actualTypeParamsAttr.getValue() : ArrayRef<Attribute> {};
144 if (ArrayRef(expectedStruct.getTemplateParamOpNames()) != actualTypeParams) {
145 // To make error messages more consistent and meaningful, if the parameters don't match
146 // because the actual type uses symbols that are not defined, generate an error about the
147 // undefined symbol(s).
148 if (failed(verifyParamsOfType(tables, actualTypeParams, actualStructType, origin))) {
149 return failure();
150 }
151 // Otherwise, generate an error stating the parent struct type must be used.
152 return genCompareErr(expectedStruct, origin, aspect)
153 .attachNote(actualStruct.getLoc())
154 .append("should be type of this '", StructDefOp::getOperationName(), '\'');
155 }
156 } else {
157 return genCompareErr(expectedStruct, origin, aspect);
158 }
159 return success();
160}
161
162//===------------------------------------------------------------------===//
163// StructDefOp
164//===------------------------------------------------------------------===//
165
166StructType StructDefOp::getType(std::optional<ArrayAttr> constParams) {
167 auto pathRes = getPathFromRoot(*this);
168 assert(succeeded(pathRes)); // consistent with StructType::get() with invalid args
169 // Use the specified parameters if provided.
170 if (constParams.has_value()) {
171 return StructType::get(pathRes.value(), constParams.value());
172 }
173 // Check if there is an enclosing `TemplateOp` defining parameters, else there are none.
174 if (TemplateOp parent = getParentOfType<TemplateOp>(*this)) {
175 auto params = parent.getConstNames<TemplateParamOp>();
176 if (!params.empty()) {
177 return StructType::get(pathRes.value(), params);
178 }
179 }
180 return StructType::get(pathRes.value());
181}
182
184 return buildStringViaCallback([this](llvm::raw_ostream &ss) {
185 FailureOr<SymbolRefAttr> pathToExpected = getPathFromRoot(*this);
186 if (succeeded(pathToExpected)) {
187 ss << pathToExpected.value();
188 } else {
189 // When there is a failure trying to get the resolved name of the struct,
190 // just print its symbol name directly.
191 ss << '@' << this->getSymName();
192 }
193 ss << '<' << debug::toStringList(this->getTemplateParamOpNames()) << '>';
194 });
195}
196
198 if (TemplateOp parent = getParentOfType<TemplateOp>(*this)) {
199 return parent.hasConstOps<TemplateSymbolBindingOpInterface>();
200 }
201 return false;
202}
203
204SmallVector<Attribute> StructDefOp::getTemplateParamOpNames() {
205 if (TemplateOp parent = getParentOfType<TemplateOp>(*this)) {
206 return parent.getConstNames<TemplateParamOp>();
207 } else {
208 return SmallVector<Attribute>();
209 }
210}
211
212SmallVector<Attribute> StructDefOp::getTemplateExprOpNames() {
213 if (TemplateOp parent = getParentOfType<TemplateOp>(*this)) {
214 return parent.getConstNames<TemplateExprOp>();
215 } else {
216 return SmallVector<Attribute>();
217 }
218}
219
221
222namespace {
223
224inline LogicalResult
225checkMainFuncParamType(Type pType, FuncDefOp inFunc, std::optional<StructType> appendSelfType) {
226 if (isValidMainSignalType(pType)) {
227 return success();
228 }
229
230 std::string message = buildStringViaCallback([&inFunc, appendSelfType](llvm::raw_ostream &ss) {
231 ss << "main entry component \"@" << inFunc.getSymName()
232 << "\" function parameters must be one of: {";
233 if (appendSelfType.has_value()) {
234 ss << appendSelfType.value() << ", ";
235 }
236 ss << '!' << FeltType::name << ", ";
237 ss << '!' << ArrayType::name << "<.. x !" << FeltType::name << ">}";
238 });
239 return inFunc.emitError(message);
240}
241
242inline LogicalResult checkMainFuncOutputSignalType(Type pType, StructDefOp structOp) {
243 if (isValidMainSignalType(pType)) {
244 return success();
245 }
246
247 std::string message = buildStringViaCallback([](llvm::raw_ostream &ss) {
248 ss << "main entry component output signals must be one of: {";
249 ss << '!' << FeltType::name << ", ";
250 ss << '!' << ArrayType::name << "<.. x !" << FeltType::name << ">}";
251 });
252 return structOp.emitError(message);
253}
254
255inline LogicalResult verifyStructComputeConstrain(
256 StructDefOp structDef, FuncDefOp computeFunc, FuncDefOp constrainFunc
257) {
258 // ASSERT: The `SetFuncAllowAttrs` trait on StructDefOp set the attributes correctly.
259 assert(constrainFunc.hasAllowConstraintAttr());
260 assert(!computeFunc.hasAllowConstraintAttr());
261 assert(!constrainFunc.hasAllowWitnessAttr());
262 assert(computeFunc.hasAllowWitnessAttr());
263
264 // Verify parameter types are valid. Skip the first parameter of the "constrain" function; it is
265 // already checked via verifyFuncTypeConstrain() in Function/IR/Ops.cpp.
266 ArrayRef<Type> computeParams = computeFunc.getFunctionType().getInputs();
267 ArrayRef<Type> constrainParams = constrainFunc.getFunctionType().getInputs().drop_front();
268 if (structDef.isMainComponent()) {
269 // Verify the input parameter types are legal. The error message is explicit about what types
270 // are allowed so there is no benefit to report multiple errors if more than one parameter in
271 // the referenced function has an illegal type.
272 for (Type t : computeParams) {
273 if (failed(checkMainFuncParamType(t, computeFunc, std::nullopt))) {
274 return failure(); // checkMainFuncParamType() already emits a sufficient error message
275 }
276 }
277 auto appendSelf = std::make_optional(structDef.getType());
278 for (Type t : constrainParams) {
279 if (failed(checkMainFuncParamType(t, constrainFunc, appendSelf))) {
280 return failure(); // checkMainFuncParamType() already emits a sufficient error message
281 }
282 }
283 }
284
285 if (!typeListsUnify(computeParams, constrainParams)) {
286 return constrainFunc.emitError()
287 .append(
288 "expected \"@", FUNC_NAME_CONSTRAIN,
289 "\" function argument types (sans the first one) to match \"@", FUNC_NAME_COMPUTE,
290 "\" function argument types"
291 )
292 .attachNote(computeFunc.getLoc())
293 .append("\"@", FUNC_NAME_COMPUTE, "\" function defined here");
294 }
295
296 return success();
297}
298
299inline LogicalResult verifyStructProduct(StructDefOp structDef, FuncDefOp productFunc) {
300 // ASSERT: The `SetFuncAllowAttrs` trait on StructDefOp set the attributes correctly
301 assert(productFunc.hasAllowConstraintAttr());
302 assert(productFunc.hasAllowWitnessAttr());
303
304 // Verify parameter types are valid
305 if (structDef.isMainComponent()) {
306 ArrayRef<Type> productParams = productFunc.getFunctionType().getInputs();
307 // Verify the input parameter types are legal. The error message is explicit about what types
308 // are allowed so there is no benefit to report multiple errors if more than one parameter in
309 // the referenced function has an illegal type.
310 for (Type t : productParams) {
311 if (failed(checkMainFuncParamType(t, productFunc, std::nullopt))) {
312 return failure(); // checkMainFuncParamType() already emits a sufficient error message
313 }
314 }
315 }
316
317 return success();
318}
319
320} // namespace
321
323 std::optional<FuncDefOp> foundCompute = std::nullopt;
324 std::optional<FuncDefOp> foundConstrain = std::nullopt;
325 std::optional<FuncDefOp> foundProduct = std::nullopt;
326 {
327 // Verify the following:
328 // 1. The only ops within the body are member and function definitions
329 // 2. The only functions defined in the struct are `@compute()` and `@constrain()`, or
330 // `@product()`
331 OwningEmitErrorFn emitError = getEmitOpErrFn(this);
332 Region &bodyRegion = getBodyRegion();
333 if (!bodyRegion.empty()) {
334 for (Operation &op : bodyRegion.front()) {
335 auto member = llvm::dyn_cast<MemberDefOp>(op);
336 if (!member) {
337 if (FuncDefOp funcDef = llvm::dyn_cast<FuncDefOp>(op)) {
338 if (funcDef.nameIsCompute()) {
339 if (foundCompute) {
340 return structFuncDefError(funcDef.getOperation())
341 << "found multiple \"@" << FUNC_NAME_COMPUTE << "\" functions";
342 }
343 foundCompute = std::make_optional(funcDef);
344 } else if (funcDef.nameIsConstrain()) {
345 if (foundConstrain) {
346 return structFuncDefError(funcDef.getOperation())
347 << "found multiple \"@" << FUNC_NAME_CONSTRAIN << "\" functions";
348 }
349 foundConstrain = std::make_optional(funcDef);
350 } else if (funcDef.nameIsProduct()) {
351 if (foundProduct) {
352 return structFuncDefError(funcDef.getOperation())
353 << "found multiple \"@" << FUNC_NAME_PRODUCT << "\" functions";
354 }
355 foundProduct = std::make_optional(funcDef);
356 } else {
357 // Must do a little more than a simple call to '?.emitOpError()' to
358 // tag the error with correct location and correct op name.
359 return structFuncDefError(funcDef.getOperation())
360 << "found \"@" << funcDef.getSymName() << '"';
361 }
362 } else {
363 return op.emitOpError()
364 << "invalid operation in '" << StructDefOp::getOperationName() << "'; only '"
365 << MemberDefOp::getOperationName() << '\'' << " and '"
366 << FuncDefOp::getOperationName() << "' operations are permitted";
367 }
368 }
369 // Also check if the member complies with output signal restrictions
370 else if (isMainComponent() && member.hasPublicAttr() &&
371 failed(checkMainFuncOutputSignalType(member.getType(), *this))) {
372 // checkMainFuncOutputSignalType already emits a sufficient error message
373 return failure();
374 }
375 }
376 }
377
378 if (!foundCompute.has_value() && foundConstrain.has_value()) {
379 return structFuncDefError(getOperation()) << "found \"@" << FUNC_NAME_CONSTRAIN
380 << "\", missing \"@" << FUNC_NAME_COMPUTE << "\"";
381 }
382 if (!foundConstrain.has_value() && foundCompute.has_value()) {
383 return structFuncDefError(getOperation()) << "found \"@" << FUNC_NAME_COMPUTE
384 << "\", missing \"@" << FUNC_NAME_CONSTRAIN << "\"";
385 }
386 }
387
388 if (!foundCompute.has_value() && !foundConstrain.has_value() && !foundProduct.has_value()) {
389 return structFuncDefError(getOperation())
390 << "could not find \"@" << FUNC_NAME_PRODUCT << "\", \"@" << FUNC_NAME_COMPUTE
391 << "\", or \"@" << FUNC_NAME_CONSTRAIN << "\"";
392 }
393
394 // Check which funcs are present and not marked with {llzk.derived}
395 auto nonderived = [](std::optional<FuncDefOp> op) -> bool {
396 return op && !(*op)->hasAttr(DERIVED_ATTR_NAME);
397 };
398
399 auto attachDerivedNotes = [&foundCompute, &foundConstrain,
400 &foundProduct](InFlightDiagnostic &&error) {
401 if (foundProduct && (*foundProduct)->hasAttr(DERIVED_ATTR_NAME)) {
402 error.attachNote(foundProduct->getLoc()) << "derived \"@" << FUNC_NAME_PRODUCT << "\" here";
403 }
404 if (foundCompute && (*foundCompute)->hasAttr(DERIVED_ATTR_NAME)) {
405 error.attachNote(foundCompute->getLoc()) << "derived \"@" << FUNC_NAME_COMPUTE << "\" here";
406 }
407 if (foundConstrain && (*foundConstrain)->hasAttr(DERIVED_ATTR_NAME)) {
408 error.attachNote(foundConstrain->getLoc())
409 << "derived \"@" << FUNC_NAME_CONSTRAIN << "\" here";
410 }
411 return error;
412 };
413
414 // We know that (@compute+@constrain) is present or @product is present, or both
415
416 // Error cases:
417 // Everything is derived
418 if (!nonderived(foundCompute) && !nonderived(foundConstrain) && !nonderived(foundProduct)) {
419 return attachDerivedNotes(
420 structFuncDefError(getOperation())
421 << "could not find non-derived \"@" << FUNC_NAME_PRODUCT << "\", \"@" << FUNC_NAME_COMPUTE
422 << "\", or \"@" << FUNC_NAME_CONSTRAIN << "\""
423 );
424 }
425
426 // Only one of @compute/@constrain is non-derived
427 if (nonderived(foundCompute) ^ nonderived(foundConstrain)) {
428 return attachDerivedNotes(
429 structFuncDefError(getOperation())
430 << "\"@" << FUNC_NAME_COMPUTE << "\" and \"@" << FUNC_NAME_CONSTRAIN
431 << "\" must both be either derived or non-derived"
432 );
433 }
434
435 // Here, at least one thing is non-derived, and @compute/@constrain are derived or non-derived
436 // together so everything is fine
437 if (nonderived(foundCompute) && nonderived(foundConstrain) && !nonderived(foundProduct)) {
438 return verifyStructComputeConstrain(*this, *foundCompute, *foundConstrain);
439 }
440
441 assert(!nonderived(foundCompute) && !nonderived(foundConstrain) && nonderived(foundProduct));
442 return verifyStructProduct(*this, *foundProduct);
443}
444
446 for (Operation &op : *getBody()) {
447 if (MemberDefOp memberDef = llvm::dyn_cast_if_present<MemberDefOp>(op)) {
448 if (memberName.compare(memberDef.getSymNameAttr()) == 0) {
449 return memberDef;
450 }
451 }
452 }
453 return nullptr;
454}
455
456std::vector<MemberDefOp> StructDefOp::getMemberDefs() {
457 std::vector<MemberDefOp> res;
458 for (Operation &op : *getBody()) {
459 if (MemberDefOp memberDef = llvm::dyn_cast_if_present<MemberDefOp>(op)) {
460 res.push_back(memberDef);
461 }
462 }
463 return res;
464}
465
467 return llvm::dyn_cast_if_present<FuncDefOp>(lookupSymbol(FUNC_NAME_COMPUTE));
468}
469
471 return llvm::dyn_cast_if_present<FuncDefOp>(lookupSymbol(FUNC_NAME_CONSTRAIN));
472}
473
475 return llvm::dyn_cast_if_present<FuncDefOp>(lookupSymbol(FUNC_NAME_PRODUCT));
476}
477
479 FailureOr<StructType> mainTypeOpt = getMainInstanceType(this->getOperation());
480 if (succeeded(mainTypeOpt)) {
481 if (StructType mainType = mainTypeOpt.value()) {
482 return structTypesUnify(mainType, this->getType());
483 }
484 }
485 return false;
486}
487
488// Custom implementation to deserialize bytecode produced prior to version 2 when `StructDefOp` had
489// an optional `const_params` attribute serialized before `sym_name`.
490LogicalResult StructDefOp::readProperties(DialectBytecodeReader &reader, OperationState &state) {
491 auto &prop = state.getOrAddProperties<Properties>();
492
493 auto versionOpt = reader.getDialectVersion<StructDialect>();
494 if (succeeded(versionOpt)) {
495 const auto &ver = static_cast<const LLZKDialectVersion &>(**versionOpt);
496 if (ver.majorVersion < 2) {
497 // Read and stash the old `const_params` as a temporary attribute so `upgradeFromVersion()`
498 // can wrap this `StructDefOp` in a `TemplateOp` with the corresponding `TemplateParamOps`.
499 ArrayAttr constParams;
500 if (failed(reader.readOptionalAttribute(constParams))) {
501 return failure();
502 }
503 if (constParams) {
504 state.addAttribute(llzk::kV1ConstParamsAttr, constParams);
505 }
506 return reader.readAttribute(prop.sym_name);
507 }
508 }
509
510 // Same as tablegen would generate to deserialize current-version IR.
511 return reader.readAttribute(prop.sym_name);
512}
513
514// Same as tablegen would generate to serialize current version IR.
515void StructDefOp::writeProperties(DialectBytecodeWriter &writer) {
516 auto &prop = getProperties();
517 writer.writeAttribute(prop.sym_name);
518}
519
520//===------------------------------------------------------------------===//
521// MemberDefOp
522//===------------------------------------------------------------------===//
523
525 OpBuilder &odsBuilder, OperationState &odsState, StringAttr sym_name, TypeAttr type,
526 bool isSignal, bool isColumn
527) {
528 Properties &props = odsState.getOrAddProperties<Properties>();
529 props.setSymName(sym_name);
530 props.setType(type);
531 if (isColumn) {
532 props.column = odsBuilder.getUnitAttr();
533 }
534 if (isSignal) {
535 props.signal = odsBuilder.getUnitAttr();
536 }
537}
538
540 OpBuilder &odsBuilder, OperationState &odsState, StringRef sym_name, Type type, bool isSignal,
541 bool isColumn
542) {
543 build(
544 odsBuilder, odsState, odsBuilder.getStringAttr(sym_name), TypeAttr::get(type), isSignal,
545 isColumn
546 );
547}
548
550 OpBuilder &odsBuilder, OperationState &odsState, TypeRange resultTypes, ValueRange operands,
551 ArrayRef<NamedAttribute> attributes, bool isSignal, bool isColumn
552) {
553 assert(operands.size() == 0u && "mismatched number of parameters");
554 odsState.addOperands(operands);
555 odsState.addAttributes(attributes);
556 assert(resultTypes.size() == 0u && "mismatched number of return types");
557 odsState.addTypes(resultTypes);
558 if (isColumn) {
559 odsState.getOrAddProperties<Properties>().column = odsBuilder.getUnitAttr();
560 }
561 if (isSignal) {
562 odsState.getOrAddProperties<Properties>().signal = odsBuilder.getUnitAttr();
563 }
564}
565
566void MemberDefOp::setPublicAttr(bool newValue) {
567 if (newValue) {
568 getOperation()->setAttr(PublicAttr::name, UnitAttr::get(getContext()));
569 } else {
570 getOperation()->removeAttr(PublicAttr::name);
571 }
572}
573
574static LogicalResult
575verifyMemberDefTypeImpl(Type memberType, SymbolTableCollection &tables, Operation *origin) {
576 if (StructType memberStructType = llvm::dyn_cast<StructType>(memberType)) {
577 // Special case for StructType verifies that the member type can resolve and that it is NOT the
578 // parent struct (i.e., struct members cannot create circular references).
579 auto memberTypeRes = verifyStructTypeResolution(tables, memberStructType, origin);
580 if (failed(memberTypeRes)) {
581 return failure(); // above already emits a sufficient error message
582 }
583 StructDefOp parentRes = getParentOfType<StructDefOp>(origin);
584 assert(parentRes && "MemberDefOp parent is always StructDefOp"); // per ODS def
585 if (memberTypeRes.value() == parentRes) {
586 return origin->emitOpError()
587 .append("type is circular")
588 .attachNote(parentRes.getLoc())
589 .append("references parent component defined here");
590 }
591 return success();
592 } else {
593 return verifyTypeResolution(tables, origin, memberType);
594 }
595}
596
597LogicalResult MemberDefOp::verifySymbolUses(SymbolTableCollection &tables) {
598 Type memberType = this->getType();
599 if (failed(verifyMemberDefTypeImpl(memberType, tables, *this))) {
600 return failure();
601 }
602
603 if (!getColumn()) {
604 return success();
605 }
606 // If the member is marked as a column only a small subset of types are allowed.
607 if (!isValidColumnType(getType(), tables, *this)) {
608 return emitOpError() << "marked as column can only contain felts, arrays of column types, or "
609 "structs with columns, but has type "
610 << getType();
611 }
612 return success();
613}
614
615LogicalResult MemberDefOp::verify() {
617 return emitOpError() << "with type " << getType() << " cannot have the signal attribute";
618 }
619 return success();
620}
621
622//===------------------------------------------------------------------===//
623// MemberRefOp implementations
624//===------------------------------------------------------------------===//
625namespace {
626
627FailureOr<SymbolLookupResult<MemberDefOp>>
628getMemberDefOpImpl(MemberRefOpInterface refOp, SymbolTableCollection &tables, StructType tyStruct) {
629 Operation *op = refOp.getOperation();
630 auto structDefRes = tyStruct.getDefinition(tables, op);
631 if (failed(structDefRes)) {
632 return failure(); // getDefinition() already emits a sufficient error message
633 }
634 // Copy namespace because we will need it later.
635 llvm::SmallVector<llvm::StringRef> structDefOpNs(structDefRes->getNamespace());
637 tables, SymbolRefAttr::get(refOp->getContext(), refOp.getMemberName()),
638 std::move(*structDefRes), op
639 );
640 if (failed(res)) {
641 return refOp->emitError() << "could not find '" << MemberDefOp::getOperationName()
642 << "' named \"@" << refOp.getMemberName() << "\" in \""
643 << tyStruct.getNameRef() << '"';
644 }
645 // Prepend the namespace of the struct lookup since the type of the member is meant to be resolved
646 // within that scope.
647 res->prependNamespace(structDefOpNs);
648 return std::move(res.value());
649}
650
651static FailureOr<SymbolLookupResult<MemberDefOp>>
652findMember(MemberRefOpInterface refOp, SymbolTableCollection &tables) {
653 // Ensure the base component/struct type reference can be resolved.
654 StructType tyStruct = refOp.getStructType();
655 if (failed(tyStruct.verifySymbolRef(tables, refOp.getOperation()))) {
656 return failure();
657 }
658 // Ensure the member name can be resolved in that struct.
659 return getMemberDefOpImpl(refOp, tables, tyStruct);
660}
661
662static LogicalResult verifySymbolUsesImpl(
663 MemberRefOpInterface refOp, SymbolTableCollection &tables,
664 SymbolLookupResult<MemberDefOp> &member
665) {
666 // Ensure the type of the referenced member declaration matches the type used in this op.
667 Type actualType = refOp.getVal().getType();
668 Type memberType = member.get().getType();
669 if (!typesUnify(actualType, memberType, member.getNamespace())) {
670 return refOp->emitOpError() << "has wrong type; expected " << memberType << ", got "
671 << actualType;
672 }
673 // Ensure any SymbolRef used in the type are valid
674 return verifyTypeResolution(tables, refOp.getOperation(), actualType);
675}
676
677LogicalResult verifySymbolUsesImpl(MemberRefOpInterface refOp, SymbolTableCollection &tables) {
678 // Ensure the member name can be resolved in that struct.
679 auto member = findMember(refOp, tables);
680 if (failed(member)) {
681 return member; // getMemberDefOp() already emits a sufficient error message
682 }
683 return verifySymbolUsesImpl(refOp, tables, *member);
684}
685
686} // namespace
687
688FailureOr<SymbolLookupResult<MemberDefOp>>
689MemberRefOpInterface::getMemberDefOp(SymbolTableCollection &tables) {
690 return getMemberDefOpImpl(*this, tables, getStructType());
691}
692
693LogicalResult MemberReadOp::verifySymbolUses(SymbolTableCollection &tables) {
694 auto member = findMember(*this, tables);
695 if (failed(member)) {
696 return failure();
697 }
698 if (failed(verifySymbolUsesImpl(*this, tables, *member))) {
699 return failure();
700 }
701 // If the member is not a column and an offset was specified then fail to validate
702 if (!member->get().getColumn() && getTableOffset().has_value()) {
703 return emitOpError("cannot read with table offset from a member that is not a column")
704 .attachNote(member->get().getLoc())
705 .append("member defined here");
706 }
707 // If the member is private and this read is outside the struct, then fail to validate.
708 // The current op may be inside a struct or a free function, but the
709 // member op (the member definition) is always inside a struct.
710 FailureOr<StructDefOp> memberParentRes = verifyInStruct(member->get());
711 if (failed(memberParentRes)) {
712 return failure(); // verifyInStruct() already emits a sufficient error message
713 }
714 // Can only read private members within the defining struct or from a verif
715 // contract targeting the struct.
716 StructDefOp thisParent = getParentOfType<StructDefOp>(*this);
717 // Defaults to failure
718 FailureOr<SymbolLookupResult<StructDefOp>> contractTarget;
719 if (auto contractParent = getParentOfType<ContractOp>(*this)) {
720 contractTarget = contractParent.getStructTarget(tables);
721 }
722 StructDefOp memberParentStruct = memberParentRes.value();
723 bool correctContractTarget =
724 succeeded(contractTarget) && memberParentStruct == contractTarget->get();
725 bool inMemberParent = thisParent && (thisParent == memberParentStruct);
726 bool validParent = inMemberParent || correctContractTarget;
727 if (!member->get().hasPublicAttr() && !validParent) {
728 return emitOpError()
729 .append(
730 "cannot read from private member of struct \"", memberParentStruct.getHeaderString(),
731 "\""
732 )
733 .attachNote(member->get().getLoc())
734 .append("member defined here");
735 }
736 return success();
737}
738
739LogicalResult MemberWriteOp::verifySymbolUses(SymbolTableCollection &tables) {
740 // Ensure the write op only targets members in the current struct.
741 FailureOr<StructDefOp> getParentRes = verifyInStruct(*this);
742 if (failed(getParentRes)) {
743 return failure(); // verifyInStruct() already emits a sufficient error message
744 }
745 if (failed(checkSelfType(tables, *getParentRes, getComponent().getType(), *this, "base value"))) {
746 return failure(); // checkSelfType() already emits a sufficient error message
747 }
748 // Perform the standard member ref checks.
749 return verifySymbolUsesImpl(*this, tables);
750}
751
752//===------------------------------------------------------------------===//
753// MemberReadOp
754//===------------------------------------------------------------------===//
755
757 OpBuilder &builder, OperationState &state, Type resultType, Value component, StringAttr member
758) {
759 Properties &props = state.getOrAddProperties<Properties>();
760 props.setMemberName(FlatSymbolRefAttr::get(member));
761 state.addTypes(resultType);
762 state.addOperands(component);
764}
765
767 OpBuilder &builder, OperationState &state, Type resultType, Value component, StringAttr member,
768 Attribute dist, ValueRange mapOperands, std::optional<int32_t> numDims
769) {
770 // '!mapOperands.empty()' implies 'numDims.has_value()'
771 assert(mapOperands.empty() || numDims.has_value());
772 state.addOperands(component);
773 state.addTypes(resultType);
774 if (numDims.has_value()) {
776 builder, state, ArrayRef({mapOperands}), builder.getDenseI32ArrayAttr({*numDims})
777 );
778 } else {
780 }
781 Properties &props = state.getOrAddProperties<Properties>();
782 props.setMemberName(FlatSymbolRefAttr::get(member));
783 props.setTableOffset(dist);
784}
785
787 OpBuilder & /*odsBuilder*/, OperationState &odsState, TypeRange resultTypes,
788 ValueRange operands, ArrayRef<NamedAttribute> attrs
789) {
790 odsState.addTypes(resultTypes);
791 odsState.addOperands(operands);
792 odsState.addAttributes(attrs);
793}
794
795LogicalResult MemberReadOp::verify() {
796 SmallVector<AffineMapAttr, 1> mapAttrs;
797 if (AffineMapAttr map =
798 llvm::dyn_cast_if_present<AffineMapAttr>(getTableOffset().value_or(nullptr))) {
799 mapAttrs.push_back(map);
800 }
802 getMapOperands(), getNumDimsPerMap(), mapAttrs, *this
803 );
804}
805
806//===------------------------------------------------------------------===//
807// CreateStructOp
808//===------------------------------------------------------------------===//
809
810void CreateStructOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
811 setNameFn(getResult(), "self");
812}
813
814LogicalResult CreateStructOp::verifySymbolUses(SymbolTableCollection &tables) {
815 FailureOr<StructDefOp> getParentRes = verifyInStruct(*this);
816 if (failed(getParentRes)) {
817 return failure(); // verifyInStruct() already emits a sufficient error message
818 }
819 if (failed(checkSelfType(tables, *getParentRes, this->getType(), *this, "result"))) {
820 return failure();
821 }
822 return success();
823}
824
825} // namespace llzk::component
llvm::ArrayRef< llvm::StringRef > getNamespace() const
Return the stack of symbol names from either IncludeOp or ModuleOp that were traversed to load this r...
static constexpr ::llvm::StringLiteral name
Definition Types.h.inc:56
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:814
void getAsmResultNames(::mlir::OpAsmSetValueNameFn setNameFn)
Definition Ops.cpp:810
::mlir::TypedValue<::llzk::component::StructType > getResult()
Definition Ops.h.inc:143
void setPublicAttr(bool newValue=true)
Adds or removes the unit llzk.pub attribute according to newValue.
Definition Ops.cpp:566
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:353
static void build(::mlir::OpBuilder &odsBuilder, ::mlir::OperationState &odsState, ::mlir::StringAttr sym_name, ::mlir::TypeAttr type, bool isSignal=false, bool isColumn=false)
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:597
::llvm::LogicalResult verify()
Definition Ops.cpp:615
FoldAdaptor::Properties Properties
Definition Ops.h.inc:315
::std::optional<::mlir::Attribute > getTableOffset()
Definition Ops.cpp.inc:979
::mlir::OperandRangeRange getMapOperands()
Definition Ops.h.inc:695
static void build(::mlir::OpBuilder &odsBuilder, ::mlir::OperationState &odsState, ::mlir::Type resultType, ::mlir::Value component, ::mlir::StringAttr member)
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:693
::llvm::ArrayRef< int32_t > getNumDimsPerMap()
Definition Ops.cpp.inc:984
::llvm::LogicalResult verify()
Definition Ops.cpp:795
FoldAdaptor::Properties Properties
Definition Ops.h.inc:642
::mlir::Value getVal()
Gets the SSA Value that holds the read/write data for the MemberRefOp.
::mlir::FailureOr< SymbolLookupResult< MemberDefOp > > getMemberDefOp(::mlir::SymbolTableCollection &tables)
Gets the definition for the member referenced in this op.
Definition Ops.cpp:689
::llvm::StringRef getMemberName()
Gets the member name attribute value from the MemberRefOp.
::llzk::component::StructType getStructType()
Gets the struct type of the target component.
::mlir::TypedValue<::llzk::component::StructType > getComponent()
Definition Ops.h.inc:956
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:739
static mlir::LogicalResult verifyTrait(mlir::Operation *op)
::llvm::SmallVector<::mlir::Attribute > getTemplateParamOpNames()
If this struct.def is within a poly.template, return names of all poly.param within the poly....
Definition Ops.cpp:204
StructType getType(::std::optional<::mlir::ArrayAttr > constParams={})
Gets the StructType representing this struct.
::mlir::Region & getBodyRegion()
Definition Ops.h.inc:1194
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:1170
::llvm::LogicalResult readProperties(::mlir::DialectBytecodeReader &reader, ::mlir::OperationState &state)
Definition Ops.cpp:490
::llvm::SmallVector<::mlir::Attribute > getTemplateExprOpNames()
If this struct.def is within a poly.template, return names of all poly.expr within the poly....
Definition Ops.cpp:212
::llvm::StringRef getSymName()
Definition Ops.cpp.inc:1608
::mlir::SymbolRefAttr getFullyQualifiedName()
Return the full name for this struct from the root module, including any surrounding module scopes.
Definition Ops.cpp:220
::std::vector< MemberDefOp > getMemberDefs()
Get all MemberDefOp in this structure.
Definition Ops.cpp:456
FoldAdaptor::Properties Properties
Definition Ops.h.inc:1156
::llzk::function::FuncDefOp getProductFuncOp()
Gets the FuncDefOp that defines the product function in this structure, if present,...
Definition Ops.cpp:474
MemberDefOp getMemberDef(::mlir::StringAttr memberName)
Gets the MemberDefOp that defines the member in this structure with the given name,...
Definition Ops.cpp:445
void writeProperties(::mlir::DialectBytecodeWriter &writer)
Definition Ops.cpp:515
::llzk::function::FuncDefOp getConstrainFuncOp()
Gets the FuncDefOp that defines the constrain function in this structure, if present,...
Definition Ops.cpp:470
bool hasTemplateSymbolBindings()
Return true iff the struct.def appears within a poly.template that defines constant parameters and/or...
Definition Ops.cpp:197
::llvm::LogicalResult verifyRegions()
Definition Ops.cpp:322
::llzk::function::FuncDefOp getComputeFuncOp()
Gets the FuncDefOp that defines the compute function in this structure, if present,...
Definition Ops.cpp:466
bool isMainComponent()
Return true iff this struct.def is the main struct. See llzk::MAIN_ATTR_NAME.
Definition Ops.cpp:478
::std::string getHeaderString()
Generate header string, in the same format as the assemblyFormat.
Definition Ops.cpp:183
::mlir::SymbolRefAttr getNameRef() const
static StructType get(::mlir::SymbolRefAttr structName)
Definition Types.cpp.inc:79
::mlir::FailureOr< SymbolLookupResult< StructDefOp > > getDefinition(::mlir::SymbolTableCollection &symbolTable, ::mlir::Operation *op, bool reportMissing=true) const
Gets the struct op that defines this struct.
Definition Types.cpp:26
::mlir::LogicalResult verifySymbolRef(::mlir::SymbolTableCollection &symbolTable, ::mlir::Operation *op)
Definition Types.cpp:60
static constexpr ::llvm::StringLiteral name
Definition Types.h.inc:32
void setAllowWitnessAttr(bool newValue=true)
Add (resp. remove) the allow_witness attribute to (resp. from) the function def.
Definition Ops.cpp:282
::mlir::FunctionType getFunctionType()
Definition Ops.cpp.inc:984
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
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
::llvm::StringRef getSymName()
Definition Ops.cpp.inc:979
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
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
bool hasAllowConstraintAttr()
Return true iff the function def has the allow_constraint attribute.
Definition Ops.h.inc:812
OpClass::Properties & buildInstantiationAttrsEmptyNoSegments(mlir::OpBuilder &odsBuilder, mlir::OperationState &odsState)
Utility for build() functions that initializes the mapOpGroupSizes, and numDimsPerMap attributes for ...
void buildInstantiationAttrsNoSegments(mlir::OpBuilder &odsBuilder, mlir::OperationState &odsState, mlir::ArrayRef< mlir::ValueRange > mapOperands, mlir::DenseI32ArrayAttr numDimsPerMap)
Utility for build() functions that initializes the mapOpGroupSizes, and numDimsPerMap attributes for ...
LogicalResult verifyAffineMapInstantiations(OperandRangeRange mapOps, ArrayRef< int32_t > numDimsPerMap, ArrayRef< AffineMapAttr > mapAttrs, Operation *origin)
bool isInStruct(Operation *op)
Definition Ops.cpp:57
InFlightDiagnostic genCompareErr(StructDefOp expected, Operation *origin, const char *aspect)
Definition Ops.cpp:99
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
FailureOr< StructDefOp > verifyInStruct(Operation *op)
Definition Ops.cpp:59
bool isInStructFunctionNamed(Operation *op, char const *funcName)
Definition Ops.cpp:67
std::string toStringList(InputIt begin, InputIt end)
Generate a comma-separated string representation by traversing elements from begin to end where the e...
Definition Debug.h:156
constexpr char FUNC_NAME_COMPUTE[]
Symbol name for the witness generation (and resp.
Definition Constants.h:16
bool typeListsUnify(Iter1 lhs, Iter2 rhs, mlir::ArrayRef< llvm::StringRef > rhsReversePrefix={}, UnificationMap *unifications=nullptr)
Return true iff the two lists of Type instances are equivalent or could be equivalent after full inst...
Definition TypeHelper.h:277
mlir::FailureOr< SymbolLookupResultUntyped > lookupTopLevelSymbol(mlir::SymbolTableCollection &tables, mlir::SymbolRefAttr symbol, mlir::Operation *origin, bool reportMissing=true)
FailureOr< StructType > getMainInstanceType(Operation *lookupFrom)
constexpr char FUNC_NAME_CONSTRAIN[]
Definition Constants.h:17
bool structTypesUnify(StructType lhs, StructType rhs, ArrayRef< StringRef > rhsReversePrefix, UnificationMap *unifications)
bool isFeltOrSimpleFeltAggregate(Type ty)
bool isValidColumnType(Type type, SymbolTableCollection &symbolTable, Operation *op)
bool isValidMainSignalType(Type pType)
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 char DERIVED_ATTR_NAME[]
Name of the attribute on a @product func that has been automatically aligned from @compute + @constra...
Definition Constants.h:28
FailureOr< StructDefOp > verifyStructTypeResolution(SymbolTableCollection &tables, StructType ty, Operation *origin)
LogicalResult verifyParamsOfType(SymbolTableCollection &tables, ArrayRef< Attribute > tyParams, Type parameterizedType, Operation *origin, std::optional< Type > requiredParamType)
LogicalResult verifyTypeResolution(SymbolTableCollection &tables, Operation *origin, Type ty)
OwningEmitErrorFn getEmitOpErrFn(mlir::Operation *op)
mlir::FailureOr< SymbolLookupResultUntyped > lookupSymbolIn(mlir::SymbolTableCollection &tables, mlir::SymbolRefAttr symbol, Within &&lookupWithin, mlir::Operation *origin, bool reportMissing=true)
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)
std::string buildStringViaCallback(Func &&appendFn, Args &&...args)
Generate a string by calling the given appendFn with an llvm::raw_ostream & as the first argument fol...
FailureOr< SymbolRefAttr > getPathFromRoot(SymbolOpInterface to, ModuleOp *foundRoot)
void setSymName(const ::mlir::StringAttr &propValue)
Definition Ops.h.inc:200
void setMemberName(const ::mlir::FlatSymbolRefAttr &propValue)
Definition Ops.h.inc:500