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 return StructType::get(pathRes.value(), parent.getConstNames<TemplateParamOp>());
176 } else {
177 return StructType::get(pathRes.value());
178 }
179}
180
182 return buildStringViaCallback([this](llvm::raw_ostream &ss) {
183 FailureOr<SymbolRefAttr> pathToExpected = getPathFromRoot(*this);
184 if (succeeded(pathToExpected)) {
185 ss << pathToExpected.value();
186 } else {
187 // When there is a failure trying to get the resolved name of the struct,
188 // just print its symbol name directly.
189 ss << '@' << this->getSymName();
190 }
191 ss << '<' << debug::toStringList(this->getTemplateParamOpNames()) << '>';
192 });
193}
194
196 if (TemplateOp parent = getParentOfType<TemplateOp>(*this)) {
197 return parent.hasConstOps<TemplateSymbolBindingOpInterface>();
198 }
199 return false;
200}
201
202SmallVector<Attribute> StructDefOp::getTemplateParamOpNames() {
203 if (TemplateOp parent = getParentOfType<TemplateOp>(*this)) {
204 return parent.getConstNames<TemplateParamOp>();
205 } else {
206 return SmallVector<Attribute>();
207 }
208}
209
210SmallVector<Attribute> StructDefOp::getTemplateExprOpNames() {
211 if (TemplateOp parent = getParentOfType<TemplateOp>(*this)) {
212 return parent.getConstNames<TemplateExprOp>();
213 } else {
214 return SmallVector<Attribute>();
215 }
216}
217
219
220namespace {
221
222inline LogicalResult
223checkMainFuncParamType(Type pType, FuncDefOp inFunc, std::optional<StructType> appendSelfType) {
224 if (isValidMainSignalType(pType)) {
225 return success();
226 }
227
228 std::string message = buildStringViaCallback([&inFunc, appendSelfType](llvm::raw_ostream &ss) {
229 ss << "main entry component \"@" << inFunc.getSymName()
230 << "\" function parameters must be one of: {";
231 if (appendSelfType.has_value()) {
232 ss << appendSelfType.value() << ", ";
233 }
234 ss << '!' << FeltType::name << ", ";
235 ss << '!' << ArrayType::name << "<.. x !" << FeltType::name << ">}";
236 });
237 return inFunc.emitError(message);
238}
239
240inline LogicalResult checkMainFuncOutputSignalType(Type pType, StructDefOp structOp) {
241 if (isValidMainSignalType(pType)) {
242 return success();
243 }
244
245 std::string message = buildStringViaCallback([](llvm::raw_ostream &ss) {
246 ss << "main entry component output signals must be one of: {";
247 ss << '!' << FeltType::name << ", ";
248 ss << '!' << ArrayType::name << "<.. x !" << FeltType::name << ">}";
249 });
250 return structOp.emitError(message);
251}
252
253inline LogicalResult verifyStructComputeConstrain(
254 StructDefOp structDef, FuncDefOp computeFunc, FuncDefOp constrainFunc
255) {
256 // ASSERT: The `SetFuncAllowAttrs` trait on StructDefOp set the attributes correctly.
257 assert(constrainFunc.hasAllowConstraintAttr());
258 assert(!computeFunc.hasAllowConstraintAttr());
259 assert(!constrainFunc.hasAllowWitnessAttr());
260 assert(computeFunc.hasAllowWitnessAttr());
261
262 // Verify parameter types are valid. Skip the first parameter of the "constrain" function; it is
263 // already checked via verifyFuncTypeConstrain() in Function/IR/Ops.cpp.
264 ArrayRef<Type> computeParams = computeFunc.getFunctionType().getInputs();
265 ArrayRef<Type> constrainParams = constrainFunc.getFunctionType().getInputs().drop_front();
266 if (structDef.isMainComponent()) {
267 // Verify the input parameter types are legal. The error message is explicit about what types
268 // are allowed so there is no benefit to report multiple errors if more than one parameter in
269 // the referenced function has an illegal type.
270 for (Type t : computeParams) {
271 if (failed(checkMainFuncParamType(t, computeFunc, std::nullopt))) {
272 return failure(); // checkMainFuncParamType() already emits a sufficient error message
273 }
274 }
275 auto appendSelf = std::make_optional(structDef.getType());
276 for (Type t : constrainParams) {
277 if (failed(checkMainFuncParamType(t, constrainFunc, appendSelf))) {
278 return failure(); // checkMainFuncParamType() already emits a sufficient error message
279 }
280 }
281 }
282
283 if (!typeListsUnify(computeParams, constrainParams)) {
284 return constrainFunc.emitError()
285 .append(
286 "expected \"@", FUNC_NAME_CONSTRAIN,
287 "\" function argument types (sans the first one) to match \"@", FUNC_NAME_COMPUTE,
288 "\" function argument types"
289 )
290 .attachNote(computeFunc.getLoc())
291 .append("\"@", FUNC_NAME_COMPUTE, "\" function defined here");
292 }
293
294 return success();
295}
296
297inline LogicalResult verifyStructProduct(StructDefOp structDef, FuncDefOp productFunc) {
298 // ASSERT: The `SetFuncAllowAttrs` trait on StructDefOp set the attributes correctly
299 assert(productFunc.hasAllowConstraintAttr());
300 assert(productFunc.hasAllowWitnessAttr());
301
302 // Verify parameter types are valid
303 if (structDef.isMainComponent()) {
304 ArrayRef<Type> productParams = productFunc.getFunctionType().getInputs();
305 // Verify the input parameter types are legal. The error message is explicit about what types
306 // are allowed so there is no benefit to report multiple errors if more than one parameter in
307 // the referenced function has an illegal type.
308 for (Type t : productParams) {
309 if (failed(checkMainFuncParamType(t, productFunc, std::nullopt))) {
310 return failure(); // checkMainFuncParamType() already emits a sufficient error message
311 }
312 }
313 }
314
315 return success();
316}
317
318} // namespace
319
321 std::optional<FuncDefOp> foundCompute = std::nullopt;
322 std::optional<FuncDefOp> foundConstrain = std::nullopt;
323 std::optional<FuncDefOp> foundProduct = std::nullopt;
324 {
325 // Verify the following:
326 // 1. The only ops within the body are member and function definitions
327 // 2. The only functions defined in the struct are `@compute()` and `@constrain()`, or
328 // `@product()`
329 OwningEmitErrorFn emitError = getEmitOpErrFn(this);
330 Region &bodyRegion = getBodyRegion();
331 if (!bodyRegion.empty()) {
332 for (Operation &op : bodyRegion.front()) {
333 auto member = llvm::dyn_cast<MemberDefOp>(op);
334 if (!member) {
335 if (FuncDefOp funcDef = llvm::dyn_cast<FuncDefOp>(op)) {
336 if (funcDef.nameIsCompute()) {
337 if (foundCompute) {
338 return structFuncDefError(funcDef.getOperation())
339 << "found multiple \"@" << FUNC_NAME_COMPUTE << "\" functions";
340 }
341 foundCompute = std::make_optional(funcDef);
342 } else if (funcDef.nameIsConstrain()) {
343 if (foundConstrain) {
344 return structFuncDefError(funcDef.getOperation())
345 << "found multiple \"@" << FUNC_NAME_CONSTRAIN << "\" functions";
346 }
347 foundConstrain = std::make_optional(funcDef);
348 } else if (funcDef.nameIsProduct()) {
349 if (foundProduct) {
350 return structFuncDefError(funcDef.getOperation())
351 << "found multiple \"@" << FUNC_NAME_PRODUCT << "\" functions";
352 }
353 foundProduct = std::make_optional(funcDef);
354 } else {
355 // Must do a little more than a simple call to '?.emitOpError()' to
356 // tag the error with correct location and correct op name.
357 return structFuncDefError(funcDef.getOperation())
358 << "found \"@" << funcDef.getSymName() << '"';
359 }
360 } else {
361 return op.emitOpError()
362 << "invalid operation in '" << StructDefOp::getOperationName() << "'; only '"
363 << MemberDefOp::getOperationName() << '\'' << " and '"
364 << FuncDefOp::getOperationName() << "' operations are permitted";
365 }
366 }
367 // Also check if the member complies with output signal restrictions
368 else if (isMainComponent() && member.hasPublicAttr() &&
369 failed(checkMainFuncOutputSignalType(member.getType(), *this))) {
370 // checkMainFuncOutputSignalType already emits a sufficient error message
371 return failure();
372 }
373 }
374 }
375
376 if (!foundCompute.has_value() && foundConstrain.has_value()) {
377 return structFuncDefError(getOperation()) << "found \"@" << FUNC_NAME_CONSTRAIN
378 << "\", missing \"@" << FUNC_NAME_COMPUTE << "\"";
379 }
380 if (!foundConstrain.has_value() && foundCompute.has_value()) {
381 return structFuncDefError(getOperation()) << "found \"@" << FUNC_NAME_COMPUTE
382 << "\", missing \"@" << FUNC_NAME_CONSTRAIN << "\"";
383 }
384 }
385
386 if (!foundCompute.has_value() && !foundConstrain.has_value() && !foundProduct.has_value()) {
387 return structFuncDefError(getOperation())
388 << "could not find \"@" << FUNC_NAME_PRODUCT << "\", \"@" << FUNC_NAME_COMPUTE
389 << "\", or \"@" << FUNC_NAME_CONSTRAIN << "\"";
390 }
391
392 // Check which funcs are present and not marked with {llzk.derived}
393 auto nonderived = [](std::optional<FuncDefOp> op) -> bool {
394 return op && !(*op)->hasAttr(DERIVED_ATTR_NAME);
395 };
396
397 auto attachDerivedNotes = [&foundCompute, &foundConstrain,
398 &foundProduct](InFlightDiagnostic &&error) {
399 if (foundProduct && (*foundProduct)->hasAttr(DERIVED_ATTR_NAME)) {
400 error.attachNote(foundProduct->getLoc()) << "derived \"@" << FUNC_NAME_PRODUCT << "\" here";
401 }
402 if (foundCompute && (*foundCompute)->hasAttr(DERIVED_ATTR_NAME)) {
403 error.attachNote(foundCompute->getLoc()) << "derived \"@" << FUNC_NAME_COMPUTE << "\" here";
404 }
405 if (foundConstrain && (*foundConstrain)->hasAttr(DERIVED_ATTR_NAME)) {
406 error.attachNote(foundConstrain->getLoc())
407 << "derived \"@" << FUNC_NAME_CONSTRAIN << "\" here";
408 }
409 return error;
410 };
411
412 // We know that (@compute+@constrain) is present or @product is present, or both
413
414 // Error cases:
415 // Everything is derived
416 if (!nonderived(foundCompute) && !nonderived(foundConstrain) && !nonderived(foundProduct)) {
417 return attachDerivedNotes(
418 structFuncDefError(getOperation())
419 << "could not find non-derived \"@" << FUNC_NAME_PRODUCT << "\", \"@" << FUNC_NAME_COMPUTE
420 << "\", or \"@" << FUNC_NAME_CONSTRAIN << "\""
421 );
422 }
423
424 // Only one of @compute/@constrain is non-derived
425 if (nonderived(foundCompute) ^ nonderived(foundConstrain)) {
426 return attachDerivedNotes(
427 structFuncDefError(getOperation())
428 << "\"@" << FUNC_NAME_COMPUTE << "\" and \"@" << FUNC_NAME_CONSTRAIN
429 << "\" must both be either derived or non-derived"
430 );
431 }
432
433 // Here, at least one thing is non-derived, and @compute/@constrain are derived or non-derived
434 // together so everything is fine
435 if (nonderived(foundCompute) && nonderived(foundConstrain) && !nonderived(foundProduct)) {
436 return verifyStructComputeConstrain(*this, *foundCompute, *foundConstrain);
437 }
438
439 assert(!nonderived(foundCompute) && !nonderived(foundConstrain) && nonderived(foundProduct));
440 return verifyStructProduct(*this, *foundProduct);
441}
442
444 for (Operation &op : *getBody()) {
445 if (MemberDefOp memberDef = llvm::dyn_cast_if_present<MemberDefOp>(op)) {
446 if (memberName.compare(memberDef.getSymNameAttr()) == 0) {
447 return memberDef;
448 }
449 }
450 }
451 return nullptr;
452}
453
454std::vector<MemberDefOp> StructDefOp::getMemberDefs() {
455 std::vector<MemberDefOp> res;
456 for (Operation &op : *getBody()) {
457 if (MemberDefOp memberDef = llvm::dyn_cast_if_present<MemberDefOp>(op)) {
458 res.push_back(memberDef);
459 }
460 }
461 return res;
462}
463
465 return llvm::dyn_cast_if_present<FuncDefOp>(lookupSymbol(FUNC_NAME_COMPUTE));
466}
467
469 return llvm::dyn_cast_if_present<FuncDefOp>(lookupSymbol(FUNC_NAME_CONSTRAIN));
470}
471
473 return llvm::dyn_cast_if_present<FuncDefOp>(lookupSymbol(FUNC_NAME_PRODUCT));
474}
475
477 FailureOr<StructType> mainTypeOpt = getMainInstanceType(this->getOperation());
478 if (succeeded(mainTypeOpt)) {
479 if (StructType mainType = mainTypeOpt.value()) {
480 return structTypesUnify(mainType, this->getType());
481 }
482 }
483 return false;
484}
485
486// Custom implementation to deserialize bytecode produced prior to version 2 when `StructDefOp` had
487// an optional `const_params` attribute serialized before `sym_name`.
488LogicalResult StructDefOp::readProperties(DialectBytecodeReader &reader, OperationState &state) {
489 auto &prop = state.getOrAddProperties<Properties>();
490
491 auto versionOpt = reader.getDialectVersion<StructDialect>();
492 if (succeeded(versionOpt)) {
493 const auto &ver = static_cast<const LLZKDialectVersion &>(**versionOpt);
494 if (ver.majorVersion < 2) {
495 // Read and stash the old `const_params` as a temporary attribute so `upgradeFromVersion()`
496 // can wrap this `StructDefOp` in a `TemplateOp` with the corresponding `TemplateParamOps`.
497 ArrayAttr constParams;
498 if (failed(reader.readOptionalAttribute(constParams))) {
499 return failure();
500 }
501 if (constParams) {
502 state.addAttribute(llzk::kV1ConstParamsAttr, constParams);
503 }
504 return reader.readAttribute(prop.sym_name);
505 }
506 }
507
508 // Same as tablegen would generate to deserialize current-version IR.
509 return reader.readAttribute(prop.sym_name);
510}
511
512// Same as tablegen would generate to serialize current version IR.
513void StructDefOp::writeProperties(DialectBytecodeWriter &writer) {
514 auto &prop = getProperties();
515 writer.writeAttribute(prop.sym_name);
516}
517
518//===------------------------------------------------------------------===//
519// MemberDefOp
520//===------------------------------------------------------------------===//
521
523 OpBuilder &odsBuilder, OperationState &odsState, StringAttr sym_name, TypeAttr type,
524 bool isSignal, bool isColumn
525) {
526 Properties &props = odsState.getOrAddProperties<Properties>();
527 props.setSymName(sym_name);
528 props.setType(type);
529 if (isColumn) {
530 props.column = odsBuilder.getUnitAttr();
531 }
532 if (isSignal) {
533 props.signal = odsBuilder.getUnitAttr();
534 }
535}
536
538 OpBuilder &odsBuilder, OperationState &odsState, StringRef sym_name, Type type, bool isSignal,
539 bool isColumn
540) {
541 build(
542 odsBuilder, odsState, odsBuilder.getStringAttr(sym_name), TypeAttr::get(type), isSignal,
543 isColumn
544 );
545}
546
548 OpBuilder &odsBuilder, OperationState &odsState, TypeRange resultTypes, ValueRange operands,
549 ArrayRef<NamedAttribute> attributes, bool isSignal, bool isColumn
550) {
551 assert(operands.size() == 0u && "mismatched number of parameters");
552 odsState.addOperands(operands);
553 odsState.addAttributes(attributes);
554 assert(resultTypes.size() == 0u && "mismatched number of return types");
555 odsState.addTypes(resultTypes);
556 if (isColumn) {
557 odsState.getOrAddProperties<Properties>().column = odsBuilder.getUnitAttr();
558 }
559 if (isSignal) {
560 odsState.getOrAddProperties<Properties>().signal = odsBuilder.getUnitAttr();
561 }
562}
563
564void MemberDefOp::setPublicAttr(bool newValue) {
565 if (newValue) {
566 getOperation()->setAttr(PublicAttr::name, UnitAttr::get(getContext()));
567 } else {
568 getOperation()->removeAttr(PublicAttr::name);
569 }
570}
571
572static LogicalResult
573verifyMemberDefTypeImpl(Type memberType, SymbolTableCollection &tables, Operation *origin) {
574 if (StructType memberStructType = llvm::dyn_cast<StructType>(memberType)) {
575 // Special case for StructType verifies that the member type can resolve and that it is NOT the
576 // parent struct (i.e., struct members cannot create circular references).
577 auto memberTypeRes = verifyStructTypeResolution(tables, memberStructType, origin);
578 if (failed(memberTypeRes)) {
579 return failure(); // above already emits a sufficient error message
580 }
581 StructDefOp parentRes = getParentOfType<StructDefOp>(origin);
582 assert(parentRes && "MemberDefOp parent is always StructDefOp"); // per ODS def
583 if (memberTypeRes.value() == parentRes) {
584 return origin->emitOpError()
585 .append("type is circular")
586 .attachNote(parentRes.getLoc())
587 .append("references parent component defined here");
588 }
589 return success();
590 } else {
591 return verifyTypeResolution(tables, origin, memberType);
592 }
593}
594
595LogicalResult MemberDefOp::verifySymbolUses(SymbolTableCollection &tables) {
596 Type memberType = this->getType();
597 if (failed(verifyMemberDefTypeImpl(memberType, tables, *this))) {
598 return failure();
599 }
600
601 if (!getColumn()) {
602 return success();
603 }
604 // If the member is marked as a column only a small subset of types are allowed.
605 if (!isValidColumnType(getType(), tables, *this)) {
606 return emitOpError() << "marked as column can only contain felts, arrays of column types, or "
607 "structs with columns, but has type "
608 << getType();
609 }
610 return success();
611}
612
613LogicalResult MemberDefOp::verify() {
615 return emitOpError() << "with type " << getType() << " cannot have the signal attribute";
616 }
617 return success();
618}
619
620//===------------------------------------------------------------------===//
621// MemberRefOp implementations
622//===------------------------------------------------------------------===//
623namespace {
624
625FailureOr<SymbolLookupResult<MemberDefOp>>
626getMemberDefOpImpl(MemberRefOpInterface refOp, SymbolTableCollection &tables, StructType tyStruct) {
627 Operation *op = refOp.getOperation();
628 auto structDefRes = tyStruct.getDefinition(tables, op);
629 if (failed(structDefRes)) {
630 return failure(); // getDefinition() already emits a sufficient error message
631 }
632 // Copy namespace because we will need it later.
633 llvm::SmallVector<llvm::StringRef> structDefOpNs(structDefRes->getNamespace());
635 tables, SymbolRefAttr::get(refOp->getContext(), refOp.getMemberName()),
636 std::move(*structDefRes), op
637 );
638 if (failed(res)) {
639 return refOp->emitError() << "could not find '" << MemberDefOp::getOperationName()
640 << "' named \"@" << refOp.getMemberName() << "\" in \""
641 << tyStruct.getNameRef() << '"';
642 }
643 // Prepend the namespace of the struct lookup since the type of the member is meant to be resolved
644 // within that scope.
645 res->prependNamespace(structDefOpNs);
646 return std::move(res.value());
647}
648
649static FailureOr<SymbolLookupResult<MemberDefOp>>
650findMember(MemberRefOpInterface refOp, SymbolTableCollection &tables) {
651 // Ensure the base component/struct type reference can be resolved.
652 StructType tyStruct = refOp.getStructType();
653 if (failed(tyStruct.verifySymbolRef(tables, refOp.getOperation()))) {
654 return failure();
655 }
656 // Ensure the member name can be resolved in that struct.
657 return getMemberDefOpImpl(refOp, tables, tyStruct);
658}
659
660static LogicalResult verifySymbolUsesImpl(
661 MemberRefOpInterface refOp, SymbolTableCollection &tables,
662 SymbolLookupResult<MemberDefOp> &member
663) {
664 // Ensure the type of the referenced member declaration matches the type used in this op.
665 Type actualType = refOp.getVal().getType();
666 Type memberType = member.get().getType();
667 if (!typesUnify(actualType, memberType, member.getNamespace())) {
668 return refOp->emitOpError() << "has wrong type; expected " << memberType << ", got "
669 << actualType;
670 }
671 // Ensure any SymbolRef used in the type are valid
672 return verifyTypeResolution(tables, refOp.getOperation(), actualType);
673}
674
675LogicalResult verifySymbolUsesImpl(MemberRefOpInterface refOp, SymbolTableCollection &tables) {
676 // Ensure the member name can be resolved in that struct.
677 auto member = findMember(refOp, tables);
678 if (failed(member)) {
679 return member; // getMemberDefOp() already emits a sufficient error message
680 }
681 return verifySymbolUsesImpl(refOp, tables, *member);
682}
683
684} // namespace
685
686FailureOr<SymbolLookupResult<MemberDefOp>>
687MemberRefOpInterface::getMemberDefOp(SymbolTableCollection &tables) {
688 return getMemberDefOpImpl(*this, tables, getStructType());
689}
690
691LogicalResult MemberReadOp::verifySymbolUses(SymbolTableCollection &tables) {
692 auto member = findMember(*this, tables);
693 if (failed(member)) {
694 return failure();
695 }
696 if (failed(verifySymbolUsesImpl(*this, tables, *member))) {
697 return failure();
698 }
699 // If the member is not a column and an offset was specified then fail to validate
700 if (!member->get().getColumn() && getTableOffset().has_value()) {
701 return emitOpError("cannot read with table offset from a member that is not a column")
702 .attachNote(member->get().getLoc())
703 .append("member defined here");
704 }
705 // If the member is private and this read is outside the struct, then fail to validate.
706 // The current op may be inside a struct or a free function, but the
707 // member op (the member definition) is always inside a struct.
708 FailureOr<StructDefOp> memberParentRes = verifyInStruct(member->get());
709 if (failed(memberParentRes)) {
710 return failure(); // verifyInStruct() already emits a sufficient error message
711 }
712 // Can only read private members within the defining struct or from a verif
713 // contract targeting the struct.
714 StructDefOp thisParent = getParentOfType<StructDefOp>(*this);
715 // Defaults to failure
716 FailureOr<SymbolLookupResult<StructDefOp>> contractTarget;
717 if (auto contractParent = getParentOfType<ContractOp>(*this)) {
718 contractTarget = contractParent.getStructTarget(tables);
719 }
720 StructDefOp memberParentStruct = memberParentRes.value();
721 bool correctContractTarget =
722 succeeded(contractTarget) && memberParentStruct == contractTarget->get();
723 bool inMemberParent = thisParent && (thisParent == memberParentStruct);
724 bool validParent = inMemberParent || correctContractTarget;
725 if (!member->get().hasPublicAttr() && !validParent) {
726 return emitOpError()
727 .append(
728 "cannot read from private member of struct \"", memberParentStruct.getHeaderString(),
729 "\""
730 )
731 .attachNote(member->get().getLoc())
732 .append("member defined here");
733 }
734 return success();
735}
736
737LogicalResult MemberWriteOp::verifySymbolUses(SymbolTableCollection &tables) {
738 // Ensure the write op only targets members in the current struct.
739 FailureOr<StructDefOp> getParentRes = verifyInStruct(*this);
740 if (failed(getParentRes)) {
741 return failure(); // verifyInStruct() already emits a sufficient error message
742 }
743 if (failed(checkSelfType(tables, *getParentRes, getComponent().getType(), *this, "base value"))) {
744 return failure(); // checkSelfType() already emits a sufficient error message
745 }
746 // Perform the standard member ref checks.
747 return verifySymbolUsesImpl(*this, tables);
748}
749
750//===------------------------------------------------------------------===//
751// MemberReadOp
752//===------------------------------------------------------------------===//
753
755 OpBuilder &builder, OperationState &state, Type resultType, Value component, StringAttr member
756) {
757 Properties &props = state.getOrAddProperties<Properties>();
758 props.setMemberName(FlatSymbolRefAttr::get(member));
759 state.addTypes(resultType);
760 state.addOperands(component);
762}
763
765 OpBuilder &builder, OperationState &state, Type resultType, Value component, StringAttr member,
766 Attribute dist, ValueRange mapOperands, std::optional<int32_t> numDims
767) {
768 // '!mapOperands.empty()' implies 'numDims.has_value()'
769 assert(mapOperands.empty() || numDims.has_value());
770 state.addOperands(component);
771 state.addTypes(resultType);
772 if (numDims.has_value()) {
774 builder, state, ArrayRef({mapOperands}), builder.getDenseI32ArrayAttr({*numDims})
775 );
776 } else {
778 }
779 Properties &props = state.getOrAddProperties<Properties>();
780 props.setMemberName(FlatSymbolRefAttr::get(member));
781 props.setTableOffset(dist);
782}
783
785 OpBuilder & /*odsBuilder*/, OperationState &odsState, TypeRange resultTypes,
786 ValueRange operands, ArrayRef<NamedAttribute> attrs
787) {
788 odsState.addTypes(resultTypes);
789 odsState.addOperands(operands);
790 odsState.addAttributes(attrs);
791}
792
793LogicalResult MemberReadOp::verify() {
794 SmallVector<AffineMapAttr, 1> mapAttrs;
795 if (AffineMapAttr map =
796 llvm::dyn_cast_if_present<AffineMapAttr>(getTableOffset().value_or(nullptr))) {
797 mapAttrs.push_back(map);
798 }
800 getMapOperands(), getNumDimsPerMap(), mapAttrs, *this
801 );
802}
803
804//===------------------------------------------------------------------===//
805// CreateStructOp
806//===------------------------------------------------------------------===//
807
808void CreateStructOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
809 setNameFn(getResult(), "self");
810}
811
812LogicalResult CreateStructOp::verifySymbolUses(SymbolTableCollection &tables) {
813 FailureOr<StructDefOp> getParentRes = verifyInStruct(*this);
814 if (failed(getParentRes)) {
815 return failure(); // verifyInStruct() already emits a sufficient error message
816 }
817 if (failed(checkSelfType(tables, *getParentRes, this->getType(), *this, "result"))) {
818 return failure();
819 }
820 return success();
821}
822
823} // 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:812
void getAsmResultNames(::mlir::OpAsmSetValueNameFn setNameFn)
Definition Ops.cpp:808
::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:564
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:595
::llvm::LogicalResult verify()
Definition Ops.cpp:613
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:691
::llvm::ArrayRef< int32_t > getNumDimsPerMap()
Definition Ops.cpp.inc:984
::llvm::LogicalResult verify()
Definition Ops.cpp:793
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:687
::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:737
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:202
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:488
::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:210
::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:218
::std::vector< MemberDefOp > getMemberDefs()
Get all MemberDefOp in this structure.
Definition Ops.cpp:454
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:472
MemberDefOp getMemberDef(::mlir::StringAttr memberName)
Gets the MemberDefOp that defines the member in this structure with the given name,...
Definition Ops.cpp:443
void writeProperties(::mlir::DialectBytecodeWriter &writer)
Definition Ops.cpp:513
::llzk::function::FuncDefOp getConstrainFuncOp()
Gets the FuncDefOp that defines the constrain function in this structure, if present,...
Definition Ops.cpp:468
bool hasTemplateSymbolBindings()
Return true iff the struct.def appears within a poly.template that defines constant parameters and/or...
Definition Ops.cpp:195
::llvm::LogicalResult verifyRegions()
Definition Ops.cpp:320
::llzk::function::FuncDefOp getComputeFuncOp()
Gets the FuncDefOp that defines the compute function in this structure, if present,...
Definition Ops.cpp:464
bool isMainComponent()
Return true iff this struct.def is the main struct. See llzk::MAIN_ATTR_NAME.
Definition Ops.cpp:476
::std::string getHeaderString()
Generate header string, in the same format as the assemblyFormat.
Definition Ops.cpp:181
::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:898
bool hasAllowWitnessAttr()
Return true iff the function def has the allow_witness attribute.
Definition Ops.h.inc:825
bool nameIsProduct()
Return true iff the function name is FUNC_NAME_PRODUCT (if needed, a check that this FuncDefOp is loc...
Definition Ops.h.inc:906
::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:902
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:679
void setAllowConstraintAttr(bool newValue=true)
Add (resp. remove) the allow_constraint attribute to (resp. from) the function def.
Definition Ops.cpp:274
bool hasAllowConstraintAttr()
Return true iff the function def has the allow_constraint attribute.
Definition Ops.h.inc:817
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:271
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:53
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