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 - Felt 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
14#include "llzk/Util/Field.h"
16
17#include <mlir/IR/Builders.h>
18
19#include <llvm/ADT/DynamicAPInt.h>
20#include <llvm/ADT/SmallString.h>
21
22// TableGen'd implementation files
24
25// TableGen'd implementation files
26#define GET_OP_CLASSES
28
29using namespace mlir;
30using namespace llzk;
31
32namespace llzk::felt {
33
34//===------------------------------------------------------------------===//
35// Constant folding helpers
36//===------------------------------------------------------------------===//
37
38namespace {
39
40struct BinaryFoldData {
41 DynamicAPInt lhsVal, rhsVal;
42 StringRef fieldName;
43 const Field *field;
44};
45
46struct UnaryFoldData {
47 DynamicAPInt val;
48 StringRef fieldName;
49 const Field *field;
50};
51
57static std::optional<BinaryFoldData> tryGetBinaryFoldData(Attribute lhsAttr, Attribute rhsAttr) {
58 auto lhs = llvm::dyn_cast_or_null<FeltConstAttr>(lhsAttr);
59 auto rhs = llvm::dyn_cast_or_null<FeltConstAttr>(rhsAttr);
60 if (!lhs || !rhs) {
61 return std::nullopt;
62 }
63
64 StringAttr lhsFieldName = lhs.getFieldName();
65 StringAttr rhsFieldName = rhs.getFieldName();
66 if (!lhsFieldName || !rhsFieldName || lhsFieldName != rhsFieldName) {
67 return std::nullopt;
68 }
69
70 auto fieldRes = Field::tryGetField(lhsFieldName.getValue());
71 if (failed(fieldRes)) {
72 return std::nullopt;
73 }
74
75 return BinaryFoldData {
76 *lhs.getReducedValue(), *rhs.getReducedValue(), lhsFieldName.getValue(),
77 &fieldRes.value().get()
78 };
79}
80
82static std::optional<UnaryFoldData> tryGetUnaryFoldData(Attribute operandAttr) {
83 auto operand = llvm::dyn_cast_or_null<FeltConstAttr>(operandAttr);
84 if (!operand) {
85 return std::nullopt;
86 }
87
88 StringAttr fieldNameAttr = operand.getFieldName();
89 if (!fieldNameAttr) {
90 return std::nullopt;
91 }
92
93 auto fieldRes = Field::tryGetField(fieldNameAttr.getValue());
94 if (failed(fieldRes)) {
95 return std::nullopt;
96 }
97
98 return UnaryFoldData {
99 *operand.getReducedValue(), fieldNameAttr.getValue(), &fieldRes.value().get()
100 };
101}
102
104static FeltConstAttr buildFoldResult(
105 MLIRContext *ctx, const DynamicAPInt &val, const Field &field, StringRef fieldName
106) {
107 return FeltConstAttr::get(ctx, field.reduce(val), fieldName);
108}
109
110} // namespace
111
112//===------------------------------------------------------------------===//
113// FeltConstantOp
114//===------------------------------------------------------------------===//
115
116void FeltConstantOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
117 std::string buf;
118 llvm::raw_string_ostream(buf) << "felt_const_" << getValue().getRawValue();
119 setNameFn(getResult(), buf);
120}
121
123
125LogicalResult FeltConstantOp::canonicalize(FeltConstantOp op, PatternRewriter &rewriter) {
126 FeltConstAttr valueAttr = op.getValue();
127 auto reduced = valueAttr.getReducedValue();
128 if (!reduced || valueAttr.getRawValue() == *reduced) {
129 return failure();
130 }
131 rewriter.modifyOpInPlace(op, [&]() {
132 op.setValueAttr(FeltConstAttr::get(op.getContext(), *reduced, valueAttr.getType()));
133 });
134 return success();
135}
136
138 MLIRContext *context, std::optional<Location> /*loc*/, Adaptor adaptor,
139 SmallVectorImpl<Type> &inferred
140) {
141 inferred.resize(1);
142 auto value = adaptor.getValue(); // FeltConstAttr
143 inferred[0] = value ? value.getType() : FeltType::get(context, StringAttr());
144 return success();
145}
146
147bool FeltConstantOp::isCompatibleReturnTypes(TypeRange l, TypeRange r) { return l == r; }
148
149//===------------------------------------------------------------------===//
150// Binary op folds
151//===------------------------------------------------------------------===//
152
153OpFoldResult AddFeltOp::fold(FoldAdaptor adaptor) {
154 auto data = tryGetBinaryFoldData(adaptor.getLhs(), adaptor.getRhs());
155 if (!data) {
156 return {};
157 }
158 return buildFoldResult(
159 getContext(), data->field->reduce(data->lhsVal + data->rhsVal), *data->field, data->fieldName
160 );
161}
162
163OpFoldResult SubFeltOp::fold(FoldAdaptor adaptor) {
164 auto data = tryGetBinaryFoldData(adaptor.getLhs(), adaptor.getRhs());
165 if (!data) {
166 return {};
167 }
168 return buildFoldResult(
169 getContext(), data->field->reduce(data->lhsVal - data->rhsVal), *data->field, data->fieldName
170 );
171}
172
173OpFoldResult MulFeltOp::fold(FoldAdaptor adaptor) {
174 auto data = tryGetBinaryFoldData(adaptor.getLhs(), adaptor.getRhs());
175 if (!data) {
176 return {};
177 }
178 return buildFoldResult(
179 getContext(), data->field->reduce(data->lhsVal * data->rhsVal), *data->field, data->fieldName
180 );
181}
182
183OpFoldResult PowFeltOp::fold(FoldAdaptor adaptor) {
184 auto data = tryGetBinaryFoldData(adaptor.getLhs(), adaptor.getRhs());
185 if (!data) {
186 return {};
187 }
188 return buildFoldResult(
189 getContext(), modExp(data->lhsVal, data->rhsVal, data->field->prime()), *data->field,
190 data->fieldName
191 );
192}
193
194OpFoldResult DivFeltOp::fold(FoldAdaptor adaptor) {
195 auto data = tryGetBinaryFoldData(adaptor.getLhs(), adaptor.getRhs());
196 if (!data || data->rhsVal == 0) {
197 return {};
198 }
199 return buildFoldResult(
200 getContext(), data->field->reduce(data->lhsVal * data->field->inv(data->rhsVal)),
201 *data->field, data->fieldName
202 );
203}
204
206 auto data = tryGetBinaryFoldData(adaptor.getLhs(), adaptor.getRhs());
207 if (!data || data->rhsVal == 0) {
208 return {};
209 }
210 // Both values are non-negative field elements; standard integer division
211 // gives the correct unsigned quotient, already in [0, lhs] < prime.
212 return buildFoldResult(getContext(), data->lhsVal / data->rhsVal, *data->field, data->fieldName);
213}
214
216 auto data = tryGetBinaryFoldData(adaptor.getLhs(), adaptor.getRhs());
217 if (!data) {
218 return {};
219 }
220 const Field *field = data->field;
221 DynamicAPInt rhs = data->rhsVal;
222 if (rhs == 0 || rhs == field->prime()) {
223 return {};
224 }
225 DynamicAPInt sRhs = field->toSigned(rhs);
226 DynamicAPInt sLhs = field->toSigned(data->lhsVal);
227 // DynamicAPInt / truncates toward zero (same as C++ signed int division).
228 return buildFoldResult(getContext(), field->reduce(sLhs / sRhs), *field, data->fieldName);
229}
230
232 auto data = tryGetBinaryFoldData(adaptor.getLhs(), adaptor.getRhs());
233 if (!data || data->rhsVal == 0) {
234 return {};
235 }
236 // Both non-negative, so % gives the correct unsigned remainder in [0, rhs) < prime.
237 return buildFoldResult(getContext(), data->lhsVal % data->rhsVal, *data->field, data->fieldName);
238}
239
240OpFoldResult SignedModFeltOp::fold(FoldAdaptor adaptor) {
241 auto data = tryGetBinaryFoldData(adaptor.getLhs(), adaptor.getRhs());
242 if (!data) {
243 return {};
244 }
245 const Field *field = data->field;
246 DynamicAPInt rhs = data->rhsVal;
247 if (rhs == 0 || rhs == field->prime()) {
248 return {};
249 }
250 DynamicAPInt sRhs = field->toSigned(rhs);
251 DynamicAPInt sLhs = field->toSigned(data->lhsVal);
252 return buildFoldResult(getContext(), field->reduce(sLhs % sRhs), *field, data->fieldName);
253}
254
255OpFoldResult AndFeltOp::fold(FoldAdaptor adaptor) {
256 auto data = tryGetBinaryFoldData(adaptor.getLhs(), adaptor.getRhs());
257 if (!data) {
258 return {};
259 }
260 return buildFoldResult(
261 getContext(), data->field->reduce(data->lhsVal & data->rhsVal), *data->field, data->fieldName
262 );
263}
264
265OpFoldResult OrFeltOp::fold(FoldAdaptor adaptor) {
266 auto data = tryGetBinaryFoldData(adaptor.getLhs(), adaptor.getRhs());
267 if (!data) {
268 return {};
269 }
270 return buildFoldResult(
271 getContext(), data->field->reduce(data->lhsVal | data->rhsVal), *data->field, data->fieldName
272 );
273}
274
275OpFoldResult XorFeltOp::fold(FoldAdaptor adaptor) {
276 auto data = tryGetBinaryFoldData(adaptor.getLhs(), adaptor.getRhs());
277 if (!data) {
278 return {};
279 }
280 return buildFoldResult(
281 getContext(), data->field->reduce(data->lhsVal ^ data->rhsVal), *data->field, data->fieldName
282 );
283}
284
285OpFoldResult ShlFeltOp::fold(FoldAdaptor adaptor) {
286 auto data = tryGetBinaryFoldData(adaptor.getLhs(), adaptor.getRhs());
287 if (!data) {
288 return {};
289 }
290 return buildFoldResult(
291 getContext(),
292 data->field->reduce(
293 data->lhsVal * modExp(DynamicAPInt(2), data->rhsVal, data->field->prime())
294 ),
295 *data->field, data->fieldName
296 );
297}
298
299OpFoldResult ShrFeltOp::fold(FoldAdaptor adaptor) {
300 auto data = tryGetBinaryFoldData(adaptor.getLhs(), adaptor.getRhs());
301 if (!data) {
302 return {};
303 }
304 // Any shift `amount >= bitwidth` will yield zero.
305 const Field *field = data->field;
306 if (data->rhsVal >= field->bitWidth()) {
307 return buildFoldResult(getContext(), DynamicAPInt(0), *field, data->fieldName);
308 }
309 // Right-shifting a non-negative value always yields a value in [0, lhs] < prime;
310 // no modular reduction required.
311 return buildFoldResult(getContext(), data->lhsVal >> data->rhsVal, *field, data->fieldName);
312}
313
314//===------------------------------------------------------------------===//
315// Unary op folds
316//===------------------------------------------------------------------===//
317
318OpFoldResult NegFeltOp::fold(FoldAdaptor adaptor) {
319 auto data = tryGetUnaryFoldData(adaptor.getOperand());
320 if (!data) {
321 return {};
322 }
323 return buildFoldResult(
324 getContext(), data->field->reduce(-data->val), *data->field, data->fieldName
325 );
326}
327
328OpFoldResult InvFeltOp::fold(FoldAdaptor adaptor) {
329 auto data = tryGetUnaryFoldData(adaptor.getOperand());
330 if (!data || data->val == 0) {
331 return {};
332 }
333 return buildFoldResult(getContext(), data->field->inv(data->val), *data->field, data->fieldName);
334}
335
336OpFoldResult NotFeltOp::fold(FoldAdaptor adaptor) {
337 auto data = tryGetUnaryFoldData(adaptor.getOperand());
338 if (!data) {
339 return {};
340 }
341 // One's complement at field.bitWidth() bits: maxMask = 2^bitWidth - 1,
342 // result = reduce(maxMask ^ val). The operator<< here is llzk::operator<<
343 // on DynamicAPInt (defined in DynamicAPIntHelper.h).
344 DynamicAPInt maxMask = (DynamicAPInt(1) << data->field->bitWidth()) - 1;
345 return buildFoldResult(
346 getContext(), data->field->reduce(maxMask ^ data->val), *data->field, data->fieldName
347 );
348}
349
350} // namespace llzk::felt
This file implements helpers for DynamicAPInt operations and conversions that LLVM does not provide.
Information about the prime finite field used for the interval analysis.
Definition Field.h:36
static llvm::FailureOr< std::reference_wrapper< const Field > > tryGetField(llvm::StringRef fieldName)
Get a Field from a given field name string, or failure if the field is not defined.
Definition Field.cpp:54
llvm::DynamicAPInt toSigned(const llvm::DynamicAPInt &i) const
Converts a canonical field element to its signed integer representation: toSigned(f) = f if f < field...
Definition Field.cpp:159
llvm::DynamicAPInt prime() const
For the prime field p, returns p.
Definition Field.h:70
llvm::DynamicAPInt reduce(const llvm::DynamicAPInt &i) const
Returns the canonical representative of i mod p in [0, p).
unsigned bitWidth() const
Width sufficient for the modulus and its representatives, independent of literal storage.
Definition Field.h:103
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:153
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:281
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:255
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:507
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:194
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:733
::llvm::LogicalResult canonicalize(FeltConstantOp op, ::mlir::PatternRewriter &rewriter)
Replaces a constant's raw value with its canonical field representative.
Definition Ops.cpp:125
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:974
::llzk::felt::FeltConstAttr getValueAttr()
Definition Ops.h.inc:1030
::mlir::TypedValue<::mlir::Type > getResult()
Definition Ops.h.inc:1017
void setValueAttr(::llzk::felt::FeltConstAttr attr)
Definition Ops.h.inc:1035
void getAsmResultNames(::mlir::OpAsmSetValueNameFn setNameFn)
Definition Ops.cpp:116
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:122
static bool isCompatibleReturnTypes(::mlir::TypeRange l, ::mlir::TypeRange r)
Definition Ops.cpp:147
::llvm::LogicalResult inferReturnTypes(::mlir::MLIRContext *context, ::std::optional<::mlir::Location > location, ::mlir::ValueRange operands, ::mlir::DictionaryAttr attributes, ::mlir::PropertyRef properties, ::mlir::RegionRange regions, ::llvm::SmallVectorImpl<::mlir::Type > &inferredReturnTypes)
Definition Ops.cpp.inc:1520
::llzk::felt::FeltConstAttr getValue()
Definition Ops.cpp.inc:1264
static FeltType get(::mlir::MLIRContext *context, ::mlir::StringAttr fieldName)
Definition Types.cpp.inc:69
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:328
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:1189
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:1405
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:173
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:318
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:1626
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:1837
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:336
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:2053
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:265
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:183
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:2279
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:2505
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:285
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:299
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:2731
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:215
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:2957
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:240
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:3183
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:163
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:3409
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:3635
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:205
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:231
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:3861
GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute > > FoldAdaptor
Definition Ops.h.inc:4087
::mlir::OpFoldResult fold(FoldAdaptor adaptor)
Definition Ops.cpp:275
DynamicAPInt modExp(const DynamicAPInt &base, const DynamicAPInt &exp, const DynamicAPInt &mod)