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
20
21// TableGen'd implementation files
23
24// TableGen'd implementation files
25#define GET_OP_CLASSES
27
28using namespace mlir;
29using namespace llzk::array;
30using namespace llzk::felt;
31using namespace llzk::string;
32
33namespace llzk::global {
34
35namespace {
36
37static inline FailureOr<NormalizedGlobalInitializer>
38normalizeGlobalInitializer(IndexType expectedType, Attribute value, EmitErrorFn emitError) {
39 if (auto intValue = llvm::dyn_cast<IntegerAttr>(value)) {
40 if (!llvm::isa<BoolAttr>(value)) {
41 APInt intValueBits = intValue.getValue();
42 if (intValue.getType().isSignlessInteger() && intValueBits.isNegative() &&
43 intValueBits.getBitWidth() < IndexType::kInternalStorageBitWidth) {
44 return emitError().append(
45 "signless integer with sign bit set cannot be widened to `index`"
46 );
47 }
48 FailureOr<IntegerAttr> normalized = forceIntType(intValue, emitError);
49 if (failed(normalized)) {
50 return failure();
51 }
52 value = *normalized;
53 }
54 }
55 return NormalizedGlobalInitializer {expectedType, value};
56}
57
58static inline FailureOr<NormalizedGlobalInitializer>
59normalizeGlobalInitializer(FeltType expectedType, Attribute value, EmitErrorFn) {
60 if (auto intValue = llvm::dyn_cast<IntegerAttr>(value)) {
61 value = FeltConstAttr::get(value.getContext(), intValue.getValue(), expectedType);
62 } else if (auto feltValue = llvm::dyn_cast<FeltConstAttr>(value)) {
63 FeltType valueType = feltValue.getType();
64 if (!expectedType.hasField() && valueType.hasField()) {
65 expectedType = valueType;
66 } else if (expectedType.hasField() && !valueType.hasField()) {
67 value = FeltConstAttr::get(value.getContext(), feltValue.getValue(), expectedType);
68 }
69 }
70 return NormalizedGlobalInitializer {expectedType, value};
71}
72
73static inline FailureOr<NormalizedGlobalInitializer>
74normalizeGlobalInitializer(ArrayType expectedType, Attribute value, EmitErrorFn emitError) {
75 if (auto arrayValue = llvm::dyn_cast<ArrayAttr>(value)) {
76 Type elementType = expectedType.getElementType();
77 if (auto feltElementType = llvm::dyn_cast<FeltType>(elementType)) {
78 // Infer an omitted field from a typed felt element before normalizing the
79 // full array. A conflicting explicit field is left for verification so it
80 // can report the conflict against the original initializer.
81 for (Attribute element : arrayValue) {
82 if (auto feltValue = llvm::dyn_cast<FeltConstAttr>(element)) {
83 FeltType valueType = feltValue.getType();
84 if (!valueType.hasField()) {
85 continue;
86 }
87 if (feltElementType.hasField() && feltElementType != valueType) {
88 return NormalizedGlobalInitializer {expectedType, value};
89 }
90 feltElementType = valueType;
91 }
92 }
93 elementType = feltElementType;
94 expectedType = expectedType.cloneWith(elementType);
95 }
96
97 // Normalize each element recursively. This also lets a nested initializer
98 // refine the element type (for example, an unqualified felt type).
99 SmallVector<Attribute> elements;
100 elements.reserve(arrayValue.size());
101 for (Attribute element : arrayValue) {
102 FailureOr<NormalizedGlobalInitializer> normalized =
103 llzk::global::normalizeGlobalInitializer(elementType, element, emitError);
104 if (failed(normalized)) {
105 return failure();
106 }
107 elementType = normalized->type;
108 elements.push_back(normalized->value);
109 }
110 // Rebuild the array type and attribute from the normalized element data.
111 expectedType = expectedType.cloneWith(elementType);
112 value = ArrayAttr::get(value.getContext(), elements);
113 }
114 return NormalizedGlobalInitializer {expectedType, value};
115}
116
117static inline FailureOr<NormalizedGlobalInitializer>
118normalizeGlobalInitializer(StringType expectedType, Attribute value, EmitErrorFn) {
119 if (auto stringValue = llvm::dyn_cast<StringAttr>(value)) {
120 value = StringAttr::get(stringValue.getValue(), expectedType);
121 }
122 return NormalizedGlobalInitializer {expectedType, value};
123}
124
125} // namespace
126
127FailureOr<NormalizedGlobalInitializer>
128normalizeGlobalInitializer(Type expectedType, Attribute value, EmitErrorFn emitError) {
129 if (auto idxType = llvm::dyn_cast<IndexType>(expectedType)) {
130 return normalizeGlobalInitializer(idxType, value, emitError);
131 } else if (auto feltType = llvm::dyn_cast<FeltType>(expectedType)) {
132 return normalizeGlobalInitializer(feltType, value, emitError);
133 } else if (auto arrayType = llvm::dyn_cast<ArrayType>(expectedType)) {
134 return normalizeGlobalInitializer(arrayType, value, emitError);
135 } else if (auto stringType = llvm::dyn_cast<StringType>(expectedType)) {
136 return normalizeGlobalInitializer(stringType, value, emitError);
137 } else if (expectedType.isSignlessInteger(1)) {
138 if (auto intValue = llvm::dyn_cast<IntegerAttr>(value)) {
139 APInt intValueBits = intValue.getValue();
140 if (!intValueBits.isZero() && !intValueBits.isOne()) {
141 return emitError().append("integer constant out of range for attribute");
142 }
143 value = IntegerAttr::get(expectedType, APInt(1, intValueBits.getZExtValue()));
144 }
145 return NormalizedGlobalInitializer {expectedType, value};
146 } else {
147 return NormalizedGlobalInitializer {expectedType, value};
148 }
149}
150
151//===------------------------------------------------------------------===//
152// GlobalDefOp
153//===------------------------------------------------------------------===//
154
155static ParseResult normalizeParsedInitialValue(
156 OpAsmParser &parser, SMLoc initializerLoc, Type &declaredType, Attribute &initialValue
157) {
158 FailureOr<NormalizedGlobalInitializer> normalized =
159 normalizeGlobalInitializer(declaredType, initialValue, [&parser, initializerLoc] {
160 return InFlightDiagnosticWrapper(parser.emitError(initializerLoc));
161 });
162 if (failed(normalized)) {
163 return failure();
164 }
165 declaredType = normalized->type;
166 initialValue = normalized->value;
167 return success();
168}
169
173static ParseResult parseInitialValueForType(OpAsmParser &parser, Type type, Attribute &value) {
174 if (llvm::isa<FeltType>(type)) {
175 FeltConstAttr feltValue;
176 if (parser.parseCustomAttributeWithFallback<FeltConstAttr>(feltValue)) {
177 return failure();
178 }
179 value = feltValue;
180 return success();
181 }
182 if (auto arrayType = llvm::dyn_cast<ArrayType>(type);
183 arrayType && llvm::isa<FeltType>(arrayType.getElementType())) {
184 SmallVector<Attribute> elements;
185 auto parseElement = [&]() -> ParseResult {
186 Attribute element;
187 if (failed(parseInitialValueForType(parser, arrayType.getElementType(), element))) {
188 return failure();
189 }
190 elements.push_back(element);
191 return success();
192 };
193 if (failed(parser.parseCommaSeparatedList(AsmParser::Delimiter::Square, parseElement))) {
194 return failure();
195 }
196 value = ArrayAttr::get(parser.getContext(), elements);
197 return success();
198 }
199 return parser.parseAttribute(value, type);
200}
201
202ParseResult GlobalDefOp::parse(OpAsmParser &parser, OperationState &result) {
203 auto &props = result.getOrAddProperties<GlobalDefOp::Properties>();
204 if (succeeded(parser.parseOptionalKeyword("const"))) {
205 props.constant = parser.getBuilder().getUnitAttr();
206 }
207
208 StringAttr symName;
209 if (parser.parseSymbolName(symName) || parser.parseColon()) {
210 return failure();
211 }
212 props.sym_name = symName;
213
214 TypeAttr typeAttr;
215 if (parser.parseCustomAttributeWithFallback(typeAttr, parser.getBuilder().getNoneType())) {
216 return failure();
217 }
218 Type declaredType = typeAttr.getValue();
219 if (succeeded(parser.parseOptionalEqual())) {
220 Attribute initialValue;
221 SMLoc initializerLoc = parser.getCurrentLocation();
222 if (failed(parseInitialValueForType(parser, declaredType, initialValue)) ||
223 failed(normalizeParsedInitialValue(parser, initializerLoc, declaredType, initialValue))) {
224 return failure();
225 }
226 props.initial_value = initialValue;
227 }
228 props.type = TypeAttr::get(declaredType);
229
230 SMLoc loc = parser.getCurrentLocation();
231 if (parser.parseOptionalAttrDict(result.attributes)) {
232 return failure();
233 }
234 return verifyInherentAttrs(result.name, result.attributes, [&]() {
235 return parser.emitError(loc) << '\'' << result.name.getStringRef() << "' op ";
236 });
237}
238
239namespace {
240
243static void printInitialValue(AsmPrinter &printer, Attribute value) {
244 if (auto arrayValue = llvm::dyn_cast<ArrayAttr>(value)) {
245 printer << '[';
246 llvm::interleaveComma(arrayValue, printer.getStream(), [&printer](Attribute element) {
247 printInitialValue(printer, element);
248 });
249 printer << ']';
250 } else if (auto feltValue = llvm::dyn_cast<FeltConstAttr>(value)) {
251 printer.printStrippedAttrOrType<FeltConstAttr>(feltValue);
252 } else {
253 printer.printAttributeWithoutType(value);
254 }
255}
256
257} // namespace
258
259void GlobalDefOp::print(OpAsmPrinter &p) {
260 if (getConstant()) {
261 p << " const";
262 }
263 p << ' ';
264 p.printSymbolName(getSymName());
265 p << " : ";
266 p.printAttributeWithoutType(getTypeAttr());
267 if (Attribute initialValue = getInitialValueAttr()) {
268 p << " = ";
269 printInitialValue(p, initialValue);
270 }
271 p.printOptionalAttrDict((*this)->getAttrs(), {"constant", "sym_name", "type", "initial_value"});
272}
273
274LogicalResult GlobalDefOp::verifySymbolUses(SymbolTableCollection &tables) {
275 // Ensure any SymbolRef used in the type are valid
276 return verifyTypeResolution(tables, *this, getType());
277}
278
279namespace {
280
281static inline InFlightDiagnosticWrapper reportMismatch(
282 EmitErrorFn errFn, Type rootType, const Twine &aspect, const Twine &expected, const Twine &found
283) {
284 return errFn().append(
285 "with type ", rootType, " expected ", expected, ' ', aspect, " but found ", found
286 );
287}
288
289static inline InFlightDiagnosticWrapper reportMismatch(
290 EmitErrorFn errFn, Type rootType, const Twine &aspect, const Twine &expected, Attribute found
291) {
292 return reportMismatch(errFn, rootType, aspect, expected, found.getAbstractAttribute().getName());
293}
294
295static LogicalResult ensureAttrTypeMatch(
296 Type type, Attribute valAttr, const OwningEmitErrorFn &errFn, Type rootType, const Twine &aspect
297) {
298 if (!isValidGlobalType(type)) {
299 // Same error message ODS-generated code would produce
300 return errFn().append(
301 "attribute 'type' failed to satisfy constraint: type attribute of "
302 "any LLZK type except non-constant types"
303 );
304 }
305 if (auto typedAttr = llvm::dyn_cast<TypedAttr>(valAttr);
306 typedAttr && typedAttr.getType() != type) {
307 return errFn().append(
308 "with type ", rootType, " expected ", aspect, " with type ", type, " but found ",
309 typedAttr.getType()
310 );
311 }
312 if (type.isSignlessInteger(1)) {
313 if (IntegerAttr ia = llvm::dyn_cast<IntegerAttr>(valAttr)) {
314 APInt val = ia.getValue();
315 if (!val.isZero() && !val.isOne()) {
316 return errFn().append("integer constant out of range for attribute");
317 }
318 } else if (!llvm::isa<BoolAttr>(valAttr)) {
319 return reportMismatch(errFn, rootType, aspect, "builtin.bool or builtin.integer", valAttr);
320 }
321 } else if (llvm::isa<IndexType>(type)) {
322 // The explicit check for BoolAttr is needed because the LLVM isa/cast functions treat
323 // BoolAttr as a subtype of IntegerAttr but this scenario should not allow BoolAttr.
324 bool isBool = llvm::isa<BoolAttr>(valAttr);
325 if (isBool || !llvm::isa<IntegerAttr>(valAttr)) {
326 return reportMismatch(
327 errFn, rootType, aspect, "builtin.index",
328 isBool ? "builtin.bool" : valAttr.getAbstractAttribute().getName()
329 );
330 }
331 } else if (llvm::isa<FeltType>(type)) {
332 if (!llvm::isa<FeltConstAttr>(valAttr)) {
333 return reportMismatch(errFn, rootType, aspect, "felt.type", valAttr);
334 }
335 } else if (llvm::isa<StringType>(type)) {
336 if (!llvm::isa<StringAttr>(valAttr)) {
337 return errFn().append(
338 "with type ", rootType, " expected ", aspect, " with type ", type, " but found ",
339 valAttr.getAbstractAttribute().getName()
340 );
341 }
342 } else if (ArrayType arrTy = llvm::dyn_cast<ArrayType>(type)) {
343 if (ArrayAttr arrVal = llvm::dyn_cast<ArrayAttr>(valAttr)) {
344 // Ensure the number of elements is correct for the ArrayType
345 assert(arrTy.hasStaticShape() && "implied by earlier isValidGlobalType() check");
346 int64_t expectedCount = arrTy.getNumElements();
347 size_t actualCount = arrVal.size();
348 if (std::cmp_not_equal(actualCount, expectedCount)) {
349 return reportMismatch(
350 errFn, rootType, Twine(aspect) + " to contain " + Twine(expectedCount) + " elements",
351 "builtin.array", Twine(actualCount)
352 );
353 }
354 if (auto feltElemTy = llvm::dyn_cast<FeltType>(arrTy.getElementType())) {
355 for (Attribute element : arrVal) {
356 if (auto feltValue = llvm::dyn_cast<FeltConstAttr>(element)) {
357 FeltType valueType = feltValue.getType();
358 if (!valueType.hasField()) {
359 continue;
360 }
361 if (feltElemTy.hasField() && feltElemTy != valueType) {
362 return errFn().append(
363 "initializer array contains conflicting types ", valueType, " vs ", feltElemTy
364 );
365 }
366 feltElemTy = valueType;
367 }
368 }
369 }
370 // Ensure the type of each element is correct for the ArrayType.
371 // Rather than immediately returning on failure, check all elements and aggregate to provide
372 // as many errors are possible in a single verifier run.
373 bool hasFailure = false;
374 Type expectedElemTy = arrTy.getElementType();
375 for (Attribute e : arrVal.getValue()) {
376 hasFailure |=
377 failed(ensureAttrTypeMatch(expectedElemTy, e, errFn, rootType, "array element"));
378 }
379 if (hasFailure) {
380 return failure();
381 }
382 } else {
383 return reportMismatch(errFn, rootType, aspect, "builtin.array", valAttr);
384 }
385 } else {
386 return errFn().append("expected a valid LLZK type but found ", type);
387 }
388 return success();
389}
390
391} // namespace
392
393LogicalResult GlobalDefOp::verify() {
394 if (Attribute initValAttr = getInitialValueAttr()) {
395 Type ty = getType();
396 OwningEmitErrorFn errFn = getEmitOpErrFn(this);
397 return ensureAttrTypeMatch(ty, initValAttr, errFn, ty, "attribute value");
398 }
399 // If there is no initial value, it cannot have "const".
400 if (isConstant()) {
401 return emitOpError("marked as 'const' must be assigned a value");
402 }
403 return success();
404}
405
406//===------------------------------------------------------------------===//
407// GlobalReadOp / GlobalWriteOp
408//===------------------------------------------------------------------===//
409
410FailureOr<SymbolLookupResult<GlobalDefOp>>
411GlobalRefOpInterface::getGlobalDefOp(SymbolTableCollection &tables) {
412 return lookupTopLevelSymbol<GlobalDefOp>(tables, getNameRef(), getOperation());
413}
414
415namespace {
416
417static FailureOr<SymbolLookupResult<GlobalDefOp>>
418verifySymbolUsesImpl(GlobalRefOpInterface refOp, SymbolTableCollection &tables) {
419 // Ensure this op references a valid GlobalDefOp name
420 auto tgt = refOp.getGlobalDefOp(tables);
421 if (failed(tgt)) {
422 return failure();
423 }
424 // Ensure the SSA Value type matches the GlobalDefOp type
425 Type globalType = tgt->get().getType();
426 if (!typesUnify(refOp.getVal().getType(), globalType, tgt->getIncludeSymNames())) {
427 return refOp->emitOpError() << "has wrong type; expected " << globalType << ", got "
428 << refOp.getVal().getType();
429 }
430 return tgt;
431}
432
433} // namespace
434
435LogicalResult GlobalReadOp::verifySymbolUses(SymbolTableCollection &tables) {
436 if (failed(verifySymbolUsesImpl(*this, tables))) {
437 return failure();
438 }
439 // Ensure any SymbolRef used in the type are valid
440 return verifyTypeResolution(tables, *this, getType());
441}
442
443LogicalResult GlobalWriteOp::verifySymbolUses(SymbolTableCollection &tables) {
444 auto tgt = verifySymbolUsesImpl(*this, tables);
445 if (failed(tgt)) {
446 return failure();
447 }
448 if (tgt->get().isConstant()) {
449 return emitOpError().append(
450 "cannot target '", GlobalDefOp::getOperationName(), "' marked as 'const'"
451 );
452 }
453 return success();
454}
455
456} // 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:90
bool hasField() const
Definition Types.h.inc:26
::llvm::LogicalResult verifyInherentAttrs(::mlir::OperationName opName, ::mlir::NamedAttrList &attrs, llvm::function_ref<::mlir::InFlightDiagnostic()> emitError)
Definition Ops.cpp.inc:336
FoldAdaptor::Properties Properties
Definition Ops.h.inc:181
::mlir::Type getType()
Definition Ops.cpp.inc:401
::mlir::TypeAttr getTypeAttr()
Definition Ops.h.inc:262
::llvm::StringRef getSymName()
Definition Ops.cpp.inc:389
::mlir::ParseResult parse(::mlir::OpAsmParser &parser, ::mlir::OperationState &result)
Definition Ops.cpp:202
::mlir::Attribute getInitialValueAttr()
Definition Ops.h.inc:267
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:274
void print(::mlir::OpAsmPrinter &p)
Definition Ops.cpp:259
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:219
::llvm::LogicalResult verify()
Definition Ops.cpp:393
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:435
::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:411
::mlir::SymbolRefAttr getNameRef()
Gets the global name attribute from the GlobalRefOp.
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:443
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)
A global initializer and its normalized type.