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 - Global value operation implementations --------*- C++ -*-===//
2//
3// Part of the LLZK Project, under the Apache License v2.0.
4// See LICENSE.txt for license information.
5// Copyright 2025 Veridise Inc.
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
9
11
12#include "InitializerUtils.h"
13
21
22// TableGen'd implementation files
24
25// TableGen'd implementation files
26#define GET_OP_CLASSES
28
29using namespace mlir;
30using namespace llzk::array;
31using namespace llzk::felt;
32using namespace llzk::string;
33
34namespace llzk::global {
35
36// Note: definition is placed here rather than the header to avoid the error:
37// "vtable will be emitted in every translation unit"
38mlir::StringRef GlobalMemoryResource::getName() const { return "GlobalMemory"; }
39
40namespace {
41
42static inline FailureOr<NormalizedGlobalInitializer>
43normalizeGlobalInitializer(IndexType expectedType, Attribute value, EmitErrorFn emitError) {
44 if (auto intValue = llvm::dyn_cast<IntegerAttr>(value)) {
45 if (!llvm::isa<BoolAttr>(value)) {
46 const APInt &intValueBits = intValue.getValue();
47 if (intValue.getType().isSignlessInteger() && intValueBits.isNegative() &&
48 intValueBits.getBitWidth() < IndexType::kInternalStorageBitWidth) {
49 return emitError().append(
50 "signless integer with sign bit set cannot be widened to `index`"
51 );
52 }
53 FailureOr<IntegerAttr> normalized = forceIntType(intValue, emitError);
54 if (failed(normalized)) {
55 return failure();
56 }
57 value = *normalized;
58 }
59 }
60 return NormalizedGlobalInitializer {expectedType, value};
61}
62
63static inline FailureOr<NormalizedGlobalInitializer>
64normalizeGlobalInitializer(FeltType expectedType, Attribute value, EmitErrorFn) {
65 if (auto intValue = llvm::dyn_cast<IntegerAttr>(value)) {
66 value =
67 FeltConstAttr::get(value.getContext(), integerAttrToDynamicAPInt(intValue), expectedType);
68 } else if (auto feltValue = llvm::dyn_cast<FeltConstAttr>(value)) {
69 FeltType valueType = feltValue.getType();
70 if (!expectedType.hasField() && valueType.hasField()) {
71 expectedType = valueType;
72 } else if (expectedType.hasField() && !valueType.hasField()) {
73 value = FeltConstAttr::get(value.getContext(), feltValue.getRawValue(), expectedType);
74 }
75 }
76 return NormalizedGlobalInitializer {expectedType, value};
77}
78
79static inline FailureOr<NormalizedGlobalInitializer>
80normalizeGlobalInitializer(ArrayType expectedType, Attribute value, EmitErrorFn emitError) {
81 if (auto arrayValue = llvm::dyn_cast<ArrayAttr>(value)) {
82 Type elementType = expectedType.getElementType();
83 if (auto feltElementType = llvm::dyn_cast<FeltType>(elementType)) {
84 // Infer an omitted field from a typed felt element before normalizing the
85 // full array. A conflicting explicit field is left for verification so it
86 // can report the conflict against the original initializer.
87 for (Attribute element : arrayValue) {
88 if (auto feltValue = llvm::dyn_cast<FeltConstAttr>(element)) {
89 FeltType valueType = feltValue.getType();
90 if (!valueType.hasField()) {
91 continue;
92 }
93 if (feltElementType.hasField() && feltElementType != valueType) {
94 return NormalizedGlobalInitializer {expectedType, value};
95 }
96 feltElementType = valueType;
97 }
98 }
99 elementType = feltElementType;
100 expectedType = expectedType.cloneWith(elementType);
101 }
102
103 // Normalize each element recursively. This also lets a nested initializer
104 // refine the element type (for example, an unqualified felt type).
105 SmallVector<Attribute> elements;
106 elements.reserve(arrayValue.size());
107 for (Attribute element : arrayValue) {
108 FailureOr<NormalizedGlobalInitializer> normalized =
109 llzk::global::normalizeGlobalInitializer(elementType, element, emitError);
110 if (failed(normalized)) {
111 return failure();
112 }
113 elementType = normalized->type;
114 elements.push_back(normalized->value);
115 }
116 // Rebuild the array type and attribute from the normalized element data.
117 expectedType = expectedType.cloneWith(elementType);
118 value = ArrayAttr::get(value.getContext(), elements);
119 }
120 return NormalizedGlobalInitializer {expectedType, value};
121}
122
123static inline FailureOr<NormalizedGlobalInitializer>
124normalizeGlobalInitializer(StringType expectedType, Attribute value, EmitErrorFn) {
125 if (auto stringValue = llvm::dyn_cast<StringAttr>(value)) {
126 value = StringAttr::get(stringValue.getValue(), expectedType);
127 }
128 return NormalizedGlobalInitializer {expectedType, value};
129}
130
131} // namespace
132
133FailureOr<NormalizedGlobalInitializer>
134normalizeGlobalInitializer(Type expectedType, Attribute value, EmitErrorFn emitError) {
135 if (auto idxType = llvm::dyn_cast<IndexType>(expectedType)) {
136 return normalizeGlobalInitializer(idxType, value, emitError);
137 } else if (auto feltType = llvm::dyn_cast<FeltType>(expectedType)) {
138 return normalizeGlobalInitializer(feltType, value, emitError);
139 } else if (auto arrayType = llvm::dyn_cast<ArrayType>(expectedType)) {
140 return normalizeGlobalInitializer(arrayType, value, emitError);
141 } else if (auto stringType = llvm::dyn_cast<StringType>(expectedType)) {
142 return normalizeGlobalInitializer(stringType, value, emitError);
143 } else if (expectedType.isSignlessInteger(1)) {
144 if (auto intValue = llvm::dyn_cast<IntegerAttr>(value)) {
145 const APInt &intValueBits = intValue.getValue();
146 if (!intValueBits.isZero() && !intValueBits.isOne()) {
147 return emitError().append("integer constant out of range for attribute");
148 }
149 value = IntegerAttr::get(expectedType, APInt(1, intValueBits.getZExtValue()));
150 }
151 return NormalizedGlobalInitializer {expectedType, value};
152 } else {
153 return NormalizedGlobalInitializer {expectedType, value};
154 }
155}
156
157//===------------------------------------------------------------------===//
158// GlobalDefOp
159//===------------------------------------------------------------------===//
160
161static ParseResult normalizeParsedInitialValue(
162 OpAsmParser &parser, SMLoc initializerLoc, Type &declaredType, Attribute &initialValue
163) {
164 FailureOr<NormalizedGlobalInitializer> normalized =
165 normalizeGlobalInitializer(declaredType, initialValue, [&parser, initializerLoc] {
166 return InFlightDiagnosticWrapper(parser.emitError(initializerLoc));
167 });
168 if (failed(normalized)) {
169 return failure();
170 }
171 declaredType = normalized->type;
172 initialValue = normalized->value;
173 return success();
174}
175
179static ParseResult parseInitialValueForType(OpAsmParser &parser, Type type, Attribute &value) {
180 if (llvm::isa<FeltType>(type)) {
181 FeltConstAttr feltValue;
182 if (parser.parseCustomAttributeWithFallback<FeltConstAttr>(feltValue)) {
183 return failure();
184 }
185 value = feltValue;
186 return success();
187 }
188 if (auto arrayType = llvm::dyn_cast<ArrayType>(type);
189 arrayType && llvm::isa<FeltType>(arrayType.getElementType())) {
190 SmallVector<Attribute> elements;
191 auto parseElement = [&]() -> ParseResult {
192 Attribute element;
193 if (failed(parseInitialValueForType(parser, arrayType.getElementType(), element))) {
194 return failure();
195 }
196 elements.push_back(element);
197 return success();
198 };
199 if (failed(parser.parseCommaSeparatedList(AsmParser::Delimiter::Square, parseElement))) {
200 return failure();
201 }
202 value = ArrayAttr::get(parser.getContext(), elements);
203 return success();
204 }
205 return parser.parseAttribute(value, type);
206}
207
208ParseResult GlobalDefOp::parse(OpAsmParser &parser, OperationState &result) {
209 auto &props = result.getOrAddProperties<GlobalDefOp::Properties>();
210 if (succeeded(parser.parseOptionalKeyword("const"))) {
211 props.constant = parser.getBuilder().getUnitAttr();
212 }
213
214 StringAttr symName;
215 if (parser.parseSymbolName(symName) || parser.parseColon()) {
216 return failure();
217 }
218 props.sym_name = symName;
219
220 TypeAttr typeAttr;
221 if (parser.parseCustomAttributeWithFallback(typeAttr, parser.getBuilder().getNoneType())) {
222 return failure();
223 }
224 Type declaredType = typeAttr.getValue();
225 if (succeeded(parser.parseOptionalEqual())) {
226 Attribute initialValue;
227 SMLoc initializerLoc = parser.getCurrentLocation();
228 if (failed(parseInitialValueForType(parser, declaredType, initialValue)) ||
229 failed(normalizeParsedInitialValue(parser, initializerLoc, declaredType, initialValue))) {
230 return failure();
231 }
232 props.initial_value = initialValue;
233 }
234 props.type = TypeAttr::get(declaredType);
235
236 SMLoc loc = parser.getCurrentLocation();
237 if (parser.parseOptionalAttrDict(result.attributes)) {
238 return failure();
239 }
240 return verifyInherentAttrs(result.name, result.attributes, [&]() {
241 return parser.emitError(loc) << '\'' << result.name.getStringRef() << "' op ";
242 });
243}
244
245namespace {
246
249static void printInitialValue(AsmPrinter &printer, Attribute value) {
250 if (auto arrayValue = llvm::dyn_cast<ArrayAttr>(value)) {
251 printer << '[';
252 llvm::interleaveComma(arrayValue, printer.getStream(), [&printer](Attribute element) {
253 printInitialValue(printer, element);
254 });
255 printer << ']';
256 } else if (auto feltValue = llvm::dyn_cast<FeltConstAttr>(value)) {
257 printer.printStrippedAttrOrType<FeltConstAttr>(feltValue);
258 } else {
259 printer.printAttributeWithoutType(value);
260 }
261}
262
263} // namespace
264
265void GlobalDefOp::print(OpAsmPrinter &p) {
266 if (getConstant()) {
267 p << " const";
268 }
269 p << ' ';
270 p.printSymbolName(getSymName());
271 p << " : ";
272 p.printAttributeWithoutType(getTypeAttr());
273 if (Attribute initialValue = getInitialValueAttr()) {
274 p << " = ";
275 printInitialValue(p, initialValue);
276 }
277 p.printOptionalAttrDict((*this)->getAttrs(), {"constant", "sym_name", "type", "initial_value"});
278}
279
280LogicalResult GlobalDefOp::verifySymbolUses(SymbolTableCollection &tables) {
281 // Ensure any SymbolRef used in the type are valid
282 return verifyTypeResolution(tables, *this, getType());
283}
284
285namespace {
286
287static inline InFlightDiagnosticWrapper reportMismatch(
288 EmitErrorFn errFn, Type rootType, const Twine &aspect, const Twine &expected, const Twine &found
289) {
290 return errFn().append(
291 "with type ", rootType, " expected ", expected, ' ', aspect, " but found ", found
292 );
293}
294
295static inline InFlightDiagnosticWrapper reportMismatch(
296 EmitErrorFn errFn, Type rootType, const Twine &aspect, const Twine &expected, Attribute found
297) {
298 return reportMismatch(errFn, rootType, aspect, expected, found.getAbstractAttribute().getName());
299}
300
301static LogicalResult ensureAttrTypeMatch(
302 Type type, Attribute valAttr, const OwningEmitErrorFn &errFn, Type rootType, const Twine &aspect
303) {
304 if (!isValidGlobalType(type)) {
305 // Same error message ODS-generated code would produce
306 return errFn().append(
307 "attribute 'type' failed to satisfy constraint: type attribute of "
308 "any LLZK type except non-constant types"
309 );
310 }
311 if (auto typedAttr = llvm::dyn_cast<TypedAttr>(valAttr);
312 typedAttr && typedAttr.getType() != type) {
313 return errFn().append(
314 "with type ", rootType, " expected ", aspect, " with type ", type, " but found ",
315 typedAttr.getType()
316 );
317 }
318 if (type.isSignlessInteger(1)) {
319 if (IntegerAttr ia = llvm::dyn_cast<IntegerAttr>(valAttr)) {
320 const APInt &val = ia.getValue();
321 if (!val.isZero() && !val.isOne()) {
322 return errFn().append("integer constant out of range for attribute");
323 }
324 } else if (!llvm::isa<BoolAttr>(valAttr)) {
325 return reportMismatch(errFn, rootType, aspect, "builtin.bool or builtin.integer", valAttr);
326 }
327 } else if (llvm::isa<IndexType>(type)) {
328 // The explicit check for BoolAttr is needed because the LLVM isa/cast functions treat
329 // BoolAttr as a subtype of IntegerAttr but this scenario should not allow BoolAttr.
330 bool isBool = llvm::isa<BoolAttr>(valAttr);
331 if (isBool || !llvm::isa<IntegerAttr>(valAttr)) {
332 return reportMismatch(
333 errFn, rootType, aspect, "builtin.index",
334 isBool ? "builtin.bool" : valAttr.getAbstractAttribute().getName()
335 );
336 }
337 } else if (llvm::isa<FeltType>(type)) {
338 if (!llvm::isa<FeltConstAttr>(valAttr)) {
339 return reportMismatch(errFn, rootType, aspect, "felt.type", valAttr);
340 }
341 } else if (llvm::isa<StringType>(type)) {
342 if (!llvm::isa<StringAttr>(valAttr)) {
343 return errFn().append(
344 "with type ", rootType, " expected ", aspect, " with type ", type, " but found ",
345 valAttr.getAbstractAttribute().getName()
346 );
347 }
348 } else if (ArrayType arrTy = llvm::dyn_cast<ArrayType>(type)) {
349 if (ArrayAttr arrVal = llvm::dyn_cast<ArrayAttr>(valAttr)) {
350 // Ensure the number of elements is correct for the ArrayType
351 assert(arrTy.hasStaticShape() && "implied by earlier isValidGlobalType() check");
352 int64_t expectedCount = arrTy.getNumElements();
353 size_t actualCount = arrVal.size();
354 if (std::cmp_not_equal(actualCount, expectedCount)) {
355 return reportMismatch(
356 errFn, rootType, Twine(aspect) + " to contain " + Twine(expectedCount) + " elements",
357 "builtin.array", Twine(actualCount)
358 );
359 }
360 if (auto feltElemTy = llvm::dyn_cast<FeltType>(arrTy.getElementType())) {
361 for (Attribute element : arrVal) {
362 if (auto feltValue = llvm::dyn_cast<FeltConstAttr>(element)) {
363 FeltType valueType = feltValue.getType();
364 if (!valueType.hasField()) {
365 continue;
366 }
367 if (feltElemTy.hasField() && feltElemTy != valueType) {
368 return errFn().append(
369 "initializer array contains conflicting types ", valueType, " vs ", feltElemTy
370 );
371 }
372 feltElemTy = valueType;
373 }
374 }
375 }
376 // Ensure the type of each element is correct for the ArrayType.
377 // Rather than immediately returning on failure, check all elements and aggregate to provide
378 // as many errors are possible in a single verifier run.
379 bool hasFailure = false;
380 Type expectedElemTy = arrTy.getElementType();
381 for (Attribute e : arrVal.getValue()) {
382 hasFailure |=
383 failed(ensureAttrTypeMatch(expectedElemTy, e, errFn, rootType, "array element"));
384 }
385 if (hasFailure) {
386 return failure();
387 }
388 } else {
389 return reportMismatch(errFn, rootType, aspect, "builtin.array", valAttr);
390 }
391 } else {
392 return errFn().append("expected a valid LLZK type but found ", type);
393 }
394 return success();
395}
396
397} // namespace
398
399LogicalResult GlobalDefOp::verify() {
400 if (Attribute initValAttr = getInitialValueAttr()) {
401 Type ty = getType();
402 OwningEmitErrorFn errFn = getEmitOpErrFn(this);
403 return ensureAttrTypeMatch(ty, initValAttr, errFn, ty, "attribute value");
404 }
405 // If there is no initial value, it cannot have "const".
406 if (isConstant()) {
407 return emitOpError("marked as 'const' must be assigned a value");
408 }
409 return success();
410}
411
412//===------------------------------------------------------------------===//
413// GlobalReadOp / GlobalWriteOp
414//===------------------------------------------------------------------===//
415
416FailureOr<SymbolLookupResult<GlobalDefOp>>
417GlobalRefOpInterface::getGlobalDefOp(SymbolTableCollection &tables) {
418 return lookupTopLevelSymbol<GlobalDefOp>(tables, getNameRef(), getOperation());
419}
420
421namespace {
422
423static FailureOr<SymbolLookupResult<GlobalDefOp>>
424verifySymbolUsesImpl(GlobalRefOpInterface refOp, SymbolTableCollection &tables) {
425 // Ensure this op references a valid GlobalDefOp name
426 auto tgt = refOp.getGlobalDefOp(tables);
427 if (failed(tgt)) {
428 return failure();
429 }
430 // Ensure the SSA Value type matches the GlobalDefOp type
431 Type globalType = tgt->get().getType();
432 if (!typesUnify(refOp.getVal().getType(), globalType, tgt->getIncludeSymNames())) {
433 return refOp->emitOpError() << "has wrong type; expected " << globalType << ", got "
434 << refOp.getVal().getType();
435 }
436 return tgt;
437}
438
439} // namespace
440
441LogicalResult GlobalReadOp::verifySymbolUses(SymbolTableCollection &tables) {
442 auto tgt = verifySymbolUsesImpl(*this, tables);
443 if (failed(tgt)) {
444 return failure();
445 }
446 if (isConstant() && !tgt->get().isConstant()) {
447 return emitOpError().append(
448 "marked as 'const' can only target '", GlobalDefOp::getOperationName(),
449 "' marked as 'const'"
450 );
451 }
452 if (!isConstant() && tgt->get().isConstant()) {
453 emitRemark()
454 .append(
455 "recommend adding 'const' when targeting '", GlobalDefOp::getOperationName(),
456 "' marked as 'const'"
457 )
458 .report();
459 }
460 // Ensure any SymbolRef used in the type are valid
461 return verifyTypeResolution(tables, *this, getType());
462}
463
464// Custom implementation to deserialize bytecode produced before version 3,
465// when GlobalReadOp did not have the optional `constant` property.
466LogicalResult GlobalReadOp::readProperties(DialectBytecodeReader &reader, OperationState &state) {
467 auto &prop = state.getOrAddProperties<Properties>();
468
469 auto version = reader.getDialectVersion<GlobalDialect>();
470 if (succeeded(version) && static_cast<const LLZKDialectVersion &>(**version).majorVersion < 3) {
471 return reader.readAttribute(prop.name_ref);
472 }
473
474 if (failed(reader.readOptionalAttribute(prop.constant))) {
475 return failure();
476 }
477 return reader.readAttribute(prop.name_ref);
478}
479
480// Same as TableGen would generate to serialize current-version IR.
481void GlobalReadOp::writeProperties(DialectBytecodeWriter &writer) {
482 auto &prop = getProperties();
483 writer.writeOptionalAttribute(prop.constant);
484 writer.writeAttribute(prop.name_ref);
485}
486
488 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> &effects
489) {
490 if (!isConstant()) {
491 effects.emplace_back(MemoryEffects::Read::get(), GlobalMemoryResource::get());
492 }
493}
494
495LogicalResult GlobalWriteOp::verifySymbolUses(SymbolTableCollection &tables) {
496 auto tgt = verifySymbolUsesImpl(*this, tables);
497 if (failed(tgt)) {
498 return failure();
499 }
500 if (tgt->get().isConstant()) {
501 return emitOpError().append(
502 "cannot target '", GlobalDefOp::getOperationName(), "' marked as 'const'"
503 );
504 }
505 return success();
506}
507
508} // namespace llzk::global
Wrapper around InFlightDiagnostic that can either be a regular InFlightDiagnostic or a special versio...
Definition ErrorHelper.h:26
InFlightDiagnosticWrapper & append(Args &&...args) &
Append arguments to the diagnostic.
Definition ErrorHelper.h:93
ArrayType cloneWith(std::optional<::llvm::ArrayRef< int64_t > > shape, ::mlir::Type elementType) const
Clone this type with the given shape and element type.
::mlir::Type getElementType() const
bool hasField() const
Definition Types.h.inc:50
::llvm::LogicalResult verifyInherentAttrs(::mlir::OperationName opName, ::mlir::NamedAttrList &attrs, llvm::function_ref<::mlir::InFlightDiagnostic()> emitError)
Definition Ops.cpp.inc:335
FoldAdaptor::Properties Properties
Definition Ops.h.inc:210
::mlir::Type getType()
Definition Ops.cpp.inc:400
::mlir::TypeAttr getTypeAttr()
Definition Ops.h.inc:291
::llvm::StringRef getSymName()
Definition Ops.cpp.inc:388
::mlir::ParseResult parse(::mlir::OpAsmParser &parser, ::mlir::OperationState &result)
Definition Ops.cpp:208
bool isConstant()
Returns true when this def is marked as const.
Definition Ops.h.inc:367
::mlir::Attribute getInitialValueAttr()
Definition Ops.h.inc:296
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:280
void print(::mlir::OpAsmPrinter &p)
Definition Ops.cpp:265
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:248
::llvm::LogicalResult verify()
Definition Ops.cpp:399
LLZK support for global values, defined outside of structs.
Definition Dialect.h.inc:13
void writeProperties(::mlir::DialectBytecodeWriter &writer)
Definition Ops.cpp:481
FoldAdaptor::Properties Properties
Definition Ops.h.inc:499
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:441
void getEffects(::llvm::SmallVectorImpl<::mlir::SideEffects::EffectInstance<::mlir::MemoryEffects::Effect > > &effects)
Definition Ops.cpp:487
bool isConstant()
Returns true when this read is marked as const.
Definition Ops.h.inc:622
::llvm::LogicalResult readProperties(::mlir::DialectBytecodeReader &reader, ::mlir::OperationState &state)
Definition Ops.cpp:466
::mlir::Value getVal()
Gets the SSA Value that holds the read/write data for the GlobalRefOp.
::mlir::FailureOr< SymbolLookupResult< GlobalDefOp > > getGlobalDefOp(::mlir::SymbolTableCollection &tables)
Gets the definition for the global referenced in this op.
Definition Ops.cpp:417
::mlir::SymbolRefAttr getNameRef()
Gets the global name attribute from the GlobalRefOp.
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:495
mlir::FailureOr< NormalizedGlobalInitializer > normalizeGlobalInitializer(mlir::Type type, mlir::Attribute value, EmitErrorFn emitError)
Normalize unambiguous initializer representations and their declared type.
mlir::FailureOr< SymbolLookupResultUntyped > lookupTopLevelSymbol(mlir::SymbolTableCollection &tables, mlir::SymbolRefAttr symbol, mlir::Operation *origin, bool reportMissing=true)
bool isValidGlobalType(Type type)
FailureOr< IntegerAttr > forceIntType(IntegerAttr attr, EmitErrorFn emitError)
llvm::function_ref< InFlightDiagnosticWrapper()> EmitErrorFn
Callback to produce an error diagnostic.
LogicalResult verifyTypeResolution(SymbolTableCollection &tables, Operation *origin, Type ty)
OwningEmitErrorFn getEmitOpErrFn(mlir::Operation *op)
std::function< InFlightDiagnosticWrapper()> OwningEmitErrorFn
This type is required in cases like the functions below to take ownership of the lambda so it is not ...
bool typesUnify(Type lhs, Type rhs, ArrayRef< StringRef > rhsReversePrefix, UnificationMap *unifications)
llvm::DynamicAPInt integerAttrToDynamicAPInt(mlir::IntegerAttr attr)
Decode an MLIR integer using its signedness; signless i1 denotes a boolean.
mlir::StringRef getName() const final
Definition Ops.cpp:38
A global initializer and its normalized type.