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 - 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
20
21// Include TableGen'd declarations
23
24// TableGen'd implementation files
25#define GET_OP_CLASSES
27
28using namespace mlir;
29using namespace llzk::component;
30using namespace llzk::verif;
31
33
34bool isInTemplate(Operation *op) { return getParentOfType<TemplateOp>(op); }
35
36FailureOr<TemplateOp> verifyInTemplate(Operation *op) {
38 return res;
39 }
40 return op->emitOpError() << "only valid within a '" << TemplateOp::getOperationName()
41 << "' ancestor";
42}
43
48LogicalResult TemplateOp::verify() {
49 Attribute rawPattern = (*this)->getDiscardableAttr(TEMPLATE_NAME_PATTERN_ATTR);
50 if (!rawPattern) {
51 return success();
52 }
53
54 auto pattern = llvm::dyn_cast<ArrayAttr>(rawPattern);
55 if (!pattern) {
56 return emitOpError() << "expected '" << TEMPLATE_NAME_PATTERN_ATTR << "' to be an ArrayAttr";
57 }
58
59 size_t parameterCount = numConstOps<TemplateParamOp>();
60 size_t expectedChunkCount = parameterCount + 1;
61 if (pattern.size() != expectedChunkCount) {
62 return emitOpError() << "expected '" << TEMPLATE_NAME_PATTERN_ATTR << "' to contain "
63 << expectedChunkCount << " literal chunk(s) for " << parameterCount
64 << " template parameter(s), but found " << pattern.size();
65 }
66 for (size_t index = 0; index < pattern.size(); ++index) {
67 if (!llvm::isa<StringAttr>(pattern[index])) {
68 return emitOpError() << "expected '" << TEMPLATE_NAME_PATTERN_ATTR << "' element " << index
69 << " to be a StringAttr";
70 }
71 }
72 return success();
73}
74
75//===------------------------------------------------------------------===//
76// TemplateParamOp
77//===------------------------------------------------------------------===//
78
79namespace {
80
81LogicalResult checkForNameConflict(SymbolTableCollection &tables, SymbolOpInterface op) {
82 // Ensure parameter name does not conflict with an existing top-level symbol
83 // because that would cause an ambiguity in symbol resolution within structs.
84 auto res = lookupTopLevelSymbol(tables, FlatSymbolRefAttr::get(op.getNameAttr()), op, false);
85 if (succeeded(res)) {
86 return op.emitOpError()
87 .append("name conflicts with an existing symbol")
88 .attachNote(res->get()->getLoc())
89 .append("symbol already defined here");
90 }
91 return success();
92}
93
94} // namespace
95
96LogicalResult TemplateParamOp::verifySymbolUses(SymbolTableCollection &tables) {
97 return checkForNameConflict(tables, *this);
98}
99
100//===------------------------------------------------------------------===//
101// TemplateExprOp
102//===------------------------------------------------------------------===//
103
104LogicalResult TemplateExprOp::verifySymbolUses(SymbolTableCollection &tables) {
105 if (failed(checkForNameConflict(tables, *this))) {
106 return failure(); // checkForNameConflict() already emits a sufficient error message
107 }
108 // Ensure no symbol used within the initializer region is defined via a `TemplateExprOp`.
109 // This prevents cyclic definitions of `TemplateExprOp`. Searches all symbol uses within
110 // this op and also within any nested symbol tables.
111 Operation *thisOp = this->getOperation();
112 TemplateOp parentTemplate = getParentOfType<TemplateOp>(thisOp);
113 assert(parentTemplate && "per ODS");
114 LogicalResult errorState = success();
115 auto checkUses = [this, &parentTemplate, &errorState](Operation *symTableOp, bool) {
116 if (auto uses = llzk::getSymbolUses(symTableOp)) {
117 for (SymbolTable::SymbolUse use : uses.value()) {
118 // Only need to check flat refs since `TemplateExprOp` refs must be flat
119 auto usedSym = llvm::dyn_cast<FlatSymbolRefAttr>(use.getSymbolRef());
120 if (usedSym && parentTemplate.hasConstNamed<TemplateExprOp>(usedSym)) {
121 InFlightDiagnostic diag = this->emitOpError().append(
122 "initialization cannot use a symbol defined by another `",
123 TemplateExprOp::getOperationName(), "` within this template"
124 );
125 diag.attachNote(use.getUser()->getLoc()).append("symbol ", usedSym, " used here");
126 auto def = parentTemplate.getConstNamed<TemplateExprOp>(usedSym);
127 diag.attachNote(def.getLoc()).append("defined here");
128 errorState = diag; // transformation to LogicalResult reports the error
129 return;
130 }
131 }
132 }
133 };
134 checkUses(thisOp, true);
135 if (succeeded(errorState)) {
136 SymbolTable::walkSymbolTables(thisOp, /*allSymUsesVisible=*/true, checkUses);
137 }
138 return errorState;
139}
140
142 Region &region = getInitializerRegion();
143 if (!region.hasOneBlock()) {
144 return emitOpError("expected initializer region with a single block");
145 }
146 Block &block = region.back();
147 if (!llvm::isa<YieldOp>(block.getTerminator())) {
148 return emitOpError("expected initializer region to end with a '")
149 << YieldOp::getOperationName() << '\'';
150 }
151 // Check or ops with side-effects that are not allowed within `poly.expr`.
152 std::optional<InFlightDiagnostic> illegalOpDiagnostic;
153 auto walkRes = block.walk([&illegalOpDiagnostic](Operation *p) {
154 auto emitIllegalOpError = [&illegalOpDiagnostic, p](const Twine &description) {
155 illegalOpDiagnostic.emplace(
156 p->emitOpError() << description << " is not allowed within a `"
157 << TemplateExprOp::getOperationName() << "` initializer"
158 );
159 };
160 // Note: If memory-effect traits are added to call ops in the future,
161 // this check should be updated to check for those traits as well.
162 if (llvm::isa<function::CallOp>(p)) {
163 emitIllegalOpError(Twine("'") + p->getName().getStringRef() + "' op");
164 return WalkResult::interrupt();
165 }
166 if (llvm::isa<global::GlobalRefOpInterface>(p) && !isMemoryEffectFree(p)) {
167 if (llvm::isa<global::GlobalReadOp>(p)) {
168 emitIllegalOpError(
169 Twine("'") + global::GlobalReadOp::getOperationName() + "' op without `const`"
170 );
171 } else {
172 emitIllegalOpError(Twine("'") + p->getName().getStringRef() + "' op");
173 }
174 return WalkResult::interrupt();
175 }
176 return WalkResult::advance();
177 });
178 if (walkRes.wasInterrupted()) {
179 assert(illegalOpDiagnostic); // set in the walk above
180 illegalOpDiagnostic->report();
181 return failure();
182 }
183 return success();
184}
185
187 Region &region = getInitializerRegion();
188 assert(region.hasOneBlock() && "per `verifyRegions()`");
189 YieldOp yieldOp = llvm::dyn_cast<YieldOp>(region.back().getTerminator());
190 assert(yieldOp && "per `verifyRegions()`");
191 return yieldOp.getVal().getType();
192}
193
194std::optional<Type> TemplateExprOp::getTypeOpt() { return getType(); }
195
196//===------------------------------------------------------------------===//
197// ConstReadOp
198//===------------------------------------------------------------------===//
199
200LogicalResult ConstReadOp::verifySymbolUses(SymbolTableCollection &tables) {
201 FailureOr<TemplateOp> getParentRes = getConstResolutionTemplate(tables, *this);
202 if (failed(getParentRes)) {
203 return failure(); // getConstResolutionTemplate() failure cases emit a sufficient error message
204 }
205 if (!*getParentRes) {
206 return this->emitOpError() << "only valid within a '" << TemplateOp::getOperationName()
207 << "' ancestor or '" << ContractOp::getOperationName()
208 << "' that targets an operation with a '"
209 << TemplateOp::getOperationName() << "' ancestor";
210 }
211 // Ensure the named constant is a parameter of the parent struct
212 FlatSymbolRefAttr name = this->getConstNameAttr();
213 auto bindingOp = getParentRes->getConstNamed<TemplateSymbolBindingOpInterface>(name);
214 if (!bindingOp) {
215 return this->emitOpError()
216 .append("references unknown symbol \"", name, '"')
217 .attachNote(getParentRes->getLoc())
218 .append("must reference a param or expr of this template");
219 }
220 // Ensure the type of the constant read matches the type of the referenced parameter (if any).
221 if (std::optional<Type> paramType = bindingOp.getTypeOpt()) {
222 if (llvm::isa<TypeVarType>(*paramType)) {
223 return this->emitOpError().append(
224 "cannot target \"", name, "\" because it is a type variable"
225 );
226 }
227 if (this->getType() != *paramType) {
228 return this->emitOpError().append(
229 "type ", this->getType(), " does not match constant param type ", *paramType
230 );
231 }
232 }
233
234 // Ensure any SymbolRef used in the type are valid
235 return verifyTypeResolution(tables, *this, getType());
236}
237
238//===------------------------------------------------------------------===//
239// ApplyMapOp
240//===------------------------------------------------------------------===//
241
242LogicalResult ApplyMapOp::verify() {
243 // Check input and output dimensions match.
244 AffineMap map = getMap();
245
246 // Verify that the map only produces one result.
247 if (map.getNumResults() != 1) {
248 return emitOpError("must produce exactly one value");
249 }
250
251 // Verify that operand count matches affine map dimension and symbol count.
252 unsigned mapDims = map.getNumDims();
253 if (getNumOperands() != mapDims + map.getNumSymbols()) {
254 return emitOpError("operand count must equal affine map dimension+symbol count");
255 } else if (mapDims != getNumDimsAttr().getInt()) {
256 return emitOpError("dimension operand count must equal affine map dimension count");
257 }
258
259 return success();
260}
261
262OpFoldResult ApplyMapOp::fold(FoldAdaptor adaptor) {
263 SmallVector<Attribute> operands;
264 operands.reserve(adaptor.getMapOperands().size());
265 for (Attribute attr : adaptor.getMapOperands()) {
266 if (!attr) {
267 return {};
268 }
269 operands.push_back(attr);
270 }
271
272 SmallVector<Attribute> result;
273 bool hasPoison = false;
274 auto folded = getMap().constantFold(operands, result, &hasPoison);
275 if (failed(folded) || hasPoison || result.size() != 1) {
276 return {};
277 }
278 return result.front();
279}
280
281//===------------------------------------------------------------------===//
282// UnifiableCastOp
283//===------------------------------------------------------------------===//
284
285LogicalResult UnifiableCastOp::verify() {
286 if (!typesUnify(getInput().getType(), getResult().getType())) {
287 return emitOpError() << "input type " << getInput().getType() << " and output type "
288 << getResult().getType() << " are not unifiable";
289 }
290
291 return success();
292}
293
294} // namespace llzk::polymorphic
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition LICENSE.txt:9
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:521
::mlir::IntegerAttr getNumDimsAttr()
Definition Ops.h.inc:363
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:262
::llvm::LogicalResult verify()
Definition Ops.cpp:242
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:299
::mlir::AffineMap getMap()
Definition Ops.cpp.inc:357
::mlir::FlatSymbolRefAttr getConstNameAttr()
Definition Ops.h.inc:599
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:200
::llvm::LogicalResult verifyRegions()
Definition Ops.cpp:141
::mlir::Region & getInitializerRegion()
Definition Ops.h.inc:811
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:104
::mlir::Type getType()
Returns the type of the poly.yield op in the initializer region.
Definition Ops.cpp:186
::std::optional<::mlir::Type > getTypeOpt()
Definition Ops.cpp:194
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:787
OpT getConstNamed(::mlir::StringRef find)
Return the op of type OpT with the given name within the body region if it exists,...
Definition Ops.h.inc:1150
bool hasConstNamed(::mlir::StringRef find)
Return true if there is an op of type OpT with the given name within the body region.
Definition Ops.h.inc:1130
::llvm::LogicalResult verify()
Verify the optional transform-carried name pattern against current parameters.
Definition Ops.cpp:48
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:1015
size_t numConstOps()
Return the number of ops of type OpT within the body region.
Definition Ops.h.inc:1114
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:96
::llvm::LogicalResult verify()
Definition Ops.cpp:285
::mlir::TypedValue<::mlir::Type > getInput()
Definition Ops.h.inc:1538
::mlir::TypedValue<::mlir::Type > getResult()
Definition Ops.h.inc:1557
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:1692
::mlir::TypedValue<::mlir::Type > getVal()
Definition Ops.h.inc:1706
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:800
constexpr llvm::StringLiteral TEMPLATE_NAME_PATTERN_ATTR
Metadata carried across transformation passes preserving the literal chunks of a partially-instantiat...
Definition Ops.h:34
bool isInTemplate(Operation *op)
Definition Ops.cpp:34
FailureOr< TemplateOp > verifyInTemplate(Operation *op)
Definition Ops.cpp:36
mlir::FailureOr< SymbolLookupResultUntyped > lookupTopLevelSymbol(mlir::SymbolTableCollection &tables, mlir::SymbolRefAttr symbol, mlir::Operation *origin, bool reportMissing=true)
std::optional< mlir::SymbolTable::UseRange > getSymbolUses(mlir::Operation *from)
Get an iterator range for all of the uses, for any symbol, that are nested within the given operation...
FailureOr< TemplateOp > getConstResolutionTemplate(SymbolTableCollection &tables, Operation *origin)
OpClass getParentOfType(mlir::Operation *op)
Return the closest surrounding parent/ancestor operation that is of type 'OpClass'.
Definition OpHelpers.h:53
LogicalResult verifyTypeResolution(SymbolTableCollection &tables, Operation *origin, Type ty)
bool typesUnify(Type lhs, Type rhs, ArrayRef< StringRef > rhsReversePrefix, UnificationMap *unifications)