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 - Boolean 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
15#include "llzk/Util/Field.h"
17
18#include <mlir/IR/BuiltinAttributes.h>
19#include <mlir/IR/OpImplementation.h>
20#include <mlir/Support/LLVM.h>
21
22#include <cassert>
23
24// TableGen'd implementation files
25#define GET_OP_CLASSES
27
28using namespace mlir;
29
30namespace llzk::boolean {
31
32//===------------------------------------------------------------------===//
33// AssertOp
34//===------------------------------------------------------------------===//
35
36// This side effect models "program termination". Based on
37// https://github.com/llvm/llvm-project/blob/f325e4b2d836d6e65a4d0cf3efc6b0996ccf3765/mlir/lib/Dialect/ControlFlow/IR/ControlFlowOps.cpp#L92-L97
39 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> &effects
40) {
41 effects.emplace_back(MemoryEffects::Write::get());
42}
43
44//===------------------------------------------------------------------===//
45// Fold helpers
46//===------------------------------------------------------------------===//
47
48namespace {
49
52static FailureOr<bool> getBoolValue(Attribute attr) {
53 auto ia = llvm::dyn_cast_or_null<IntegerAttr>(attr);
54 if (!ia || !ia.getType().isInteger(1)) {
55 return failure();
56 }
57 return ia.getValue().getBoolValue();
58}
59
61static IntegerAttr makeBoolAttr(MLIRContext *ctx, bool val) {
62 auto i1Ty = IntegerType::get(ctx, 1);
63 return IntegerAttr::get(i1Ty, val ? 1 : 0);
64}
65
66} // namespace
67
68//===------------------------------------------------------------------===//
69// AndBoolOp
70//===------------------------------------------------------------------===//
71
72OpFoldResult AndBoolOp::fold(FoldAdaptor adaptor) {
73 auto lhs = getBoolValue(adaptor.getLhs());
74 auto rhs = getBoolValue(adaptor.getRhs());
75 if (failed(lhs) || failed(rhs)) {
76 return {};
77 }
78 return makeBoolAttr(getContext(), *lhs && *rhs);
79}
80
81//===------------------------------------------------------------------===//
82// OrBoolOp
83//===------------------------------------------------------------------===//
84
85OpFoldResult OrBoolOp::fold(FoldAdaptor adaptor) {
86 auto lhs = getBoolValue(adaptor.getLhs());
87 auto rhs = getBoolValue(adaptor.getRhs());
88 if (failed(lhs) || failed(rhs)) {
89 return {};
90 }
91 return makeBoolAttr(getContext(), *lhs || *rhs);
92}
93
94//===------------------------------------------------------------------===//
95// XorBoolOp
96//===------------------------------------------------------------------===//
97
98OpFoldResult XorBoolOp::fold(FoldAdaptor adaptor) {
99 auto lhs = getBoolValue(adaptor.getLhs());
100 auto rhs = getBoolValue(adaptor.getRhs());
101 if (failed(lhs) || failed(rhs)) {
102 return {};
103 }
104 return makeBoolAttr(getContext(), *lhs != *rhs);
105}
106
107//===------------------------------------------------------------------===//
108// NotBoolOp
109//===------------------------------------------------------------------===//
110
111OpFoldResult NotBoolOp::fold(FoldAdaptor adaptor) {
112 auto val = getBoolValue(adaptor.getOperand());
113 if (failed(val)) {
114 return {};
115 }
116 return makeBoolAttr(getContext(), !*val);
117}
118
119//===------------------------------------------------------------------===//
120// CmpOp
121//===------------------------------------------------------------------===//
122
123inline static bool
124eval(FeltCmpPredicate pred, const llvm::DynamicAPInt &lval, const llvm::DynamicAPInt &rval) {
125 switch (pred) {
127 return lval == rval;
129 return lval != rval;
131 return lval < rval;
133 return lval <= rval;
135 return lval > rval;
137 return lval >= rval;
138 }
139 llvm_unreachable("invalid FeltCmpPredicate");
140}
141
142OpFoldResult CmpOp::fold(FoldAdaptor adaptor) {
143 auto lhsAttr = llvm::dyn_cast_or_null<felt::FeltConstAttr>(adaptor.getLhs());
144 auto rhsAttr = llvm::dyn_cast_or_null<felt::FeltConstAttr>(adaptor.getRhs());
145 const auto predicate = getPredicate();
146 // Field comparisons use nonnegative reduced representatives. A zero
147 // boundary can therefore determine the result even with one symbolic operand.
148 if (getLhs().getType() == getRhs().getType()) {
149 auto isZero = [](felt::FeltConstAttr value) {
150 if (!value) {
151 return false;
152 }
153 // Literal zero is zero in every field, including an unspecified field.
154 if (value.getRawValue() == 0) {
155 return true;
156 }
157 auto reduced = value.getReducedValue();
158 return reduced && *reduced == 0;
159 };
160 if (isZero(rhsAttr)) {
161 if (predicate == FeltCmpPredicate::GE || predicate == FeltCmpPredicate::LT) {
162 return makeBoolAttr(getContext(), predicate == FeltCmpPredicate::GE);
163 }
164 }
165 if (isZero(lhsAttr)) {
166 if (predicate == FeltCmpPredicate::LE || predicate == FeltCmpPredicate::GT) {
167 return makeBoolAttr(getContext(), predicate == FeltCmpPredicate::LE);
168 }
169 }
170 }
171 if (!lhsAttr || !rhsAttr || lhsAttr.getFieldName() != rhsAttr.getFieldName()) {
172 return {};
173 }
174
175 auto lhs = lhsAttr.getReducedValue();
176 auto rhs = rhsAttr.getReducedValue();
177 if (!lhs || !rhs) {
178 return {};
179 }
180
181 return makeBoolAttr(getContext(), eval(predicate, *lhs, *rhs));
182}
183
184//===------------------------------------------------------------------===//
185// Quantifier ops common impl
186//===------------------------------------------------------------------===//
187
188namespace {
193template <typename Op> LogicalResult verifyQuantOp(Op op) {
194 auto *block = op.getBody();
195 if (!block || block->getNumArguments() != 1) {
196 return op->emitOpError("must have one block argument");
197 }
198 auto argType = block->getArgument(0).getType();
199 auto eltType = getQuantifierOpDomainIterType(op.getSort().getType());
200 if (argType != eltType) {
201 return op->emitOpError() << "expects element type " << argType << " but sort has element type "
202 << eltType;
203 }
204
205 auto termOp = block->getTerminator();
206 if (!llvm::dyn_cast_if_present<YieldOp>(termOp)) {
207 return op->emitOpError() << "expects '" << YieldOp::getOperationName() << "' terminator op";
208 }
209 return success();
210}
211
220ParseResult parseQuantOp(OpAsmParser &parser, OperationState &result) {
221 OpAsmParser::Argument arg;
222 if (parser.parseArgument(arg)) {
223 return failure();
224 }
225 assert(!arg.type);
226 if (succeeded(parser.parseOptionalColon())) {
227 if (parser.parseType(arg.type)) {
228 return failure();
229 }
230 }
231 if (parser.parseKeyword("in")) {
232 return failure();
233 }
234 OpAsmParser::UnresolvedOperand sortOperand;
235 array::ArrayType sortType;
236 if (parser.parseOperand(sortOperand)) {
237 return failure();
238 }
239 if (parser.parseColonType(sortType)) {
240 return failure();
241 }
242 if (parser.resolveOperand(sortOperand, sortType, result.operands)) {
243 return failure();
244 }
245
246 if (!arg.type) {
247 arg.type = getQuantifierOpDomainIterType(sortType);
248 assert(arg.type && "argument type must be inferred from the array element type");
249 }
250
251 auto *body = result.addRegion();
252 SMLoc loc = parser.getCurrentLocation();
253 if (parser.parseRegion(
254 *body, {arg},
255 /*enableNameShadowing=*/false
256 )) {
257 return failure();
258 }
259
260 if (body->empty()) {
261 return parser.emitError(loc, "expected non-empty body");
262 }
263 if (parser.parseOptionalAttrDictWithKeyword(result.attributes)) {
264 return failure();
265 }
266
267 result.types = {parser.getBuilder().getI1Type()};
268 return success();
269}
270
272template <typename Op> void printQuantOp(OpAsmPrinter &p, Op op) {
273 p << ' ';
274 p.printRegionArgument(op.getBody()->getArgument(0));
275 p << " in ";
276 p.printOperand(op.getSort());
277 p << " : " << op.getSort().getType();
278 p << ' ';
279 p.printRegion(op.getRegion(), /*printEntryBlockArgs=*/false);
280 p << ' ';
281 p.printOptionalAttrDictWithKeyword(op->getAttrs());
282}
283} // namespace
284
285//===------------------------------------------------------------------===//
286// ForAllOp
287//===------------------------------------------------------------------===//
288
289LogicalResult ForAllOp::verify() { return verifyQuantOp(*this); }
290
291ParseResult ForAllOp::parse(OpAsmParser &parser, OperationState &result) {
292 return parseQuantOp(parser, result);
293}
294
295void ForAllOp::print(OpAsmPrinter &p) { printQuantOp(p, *this); }
296
297//===------------------------------------------------------------------===//
298// ExistsOp
299//===------------------------------------------------------------------===//
300
301LogicalResult ExistsOp::verify() { return verifyQuantOp(*this); }
302
303ParseResult ExistsOp::parse(OpAsmParser &parser, OperationState &result) {
304 return parseQuantOp(parser, result);
305}
306
307void ExistsOp::print(OpAsmPrinter &p) { printQuantOp(p, *this); }
308
309} // namespace llzk::boolean
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:72
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:635
void getEffects(::llvm::SmallVectorImpl<::mlir::SideEffects::EffectInstance<::mlir::MemoryEffects::Effect > > &effects)
Definition Ops.cpp:38
::mlir::TypedValue<::llzk::felt::FeltType > getLhs()
Definition Ops.h.inc:1134
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:1103
::llzk::boolean::FeltCmpPredicate getPredicate()
Definition Ops.cpp.inc:1444
::mlir::TypedValue<::llzk::felt::FeltType > getRhs()
Definition Ops.h.inc:1138
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:142
::llvm::LogicalResult verify()
Definition Ops.cpp:301
void print(::mlir::OpAsmPrinter &p)
Definition Ops.cpp:307
::mlir::ParseResult parse(::mlir::OpAsmParser &parser, ::mlir::OperationState &result)
Definition Ops.cpp:303
::mlir::ParseResult parse(::mlir::OpAsmParser &parser, ::mlir::OperationState &result)
Definition Ops.cpp:291
::llvm::LogicalResult verify()
Definition Ops.cpp:289
void print(::mlir::OpAsmPrinter &p)
Definition Ops.cpp:295
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:1358
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:111
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:1564
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:85
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:1780
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:98
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:2000
mlir::Type getQuantifierOpDomainIterType(llzk::array::ArrayType arr)
Extracts the type used for a quantifier op block argument.
Definition Utils.h:20