LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
Field.cpp
Go to the documentation of this file.
1//===-- Field.cpp -----------------------------------------------*- 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
10#include "llzk/Util/Field.h"
11
19#include "llzk/Util/Constants.h"
20#include "llzk/Util/Debug.h"
22
23#include <mlir/IR/Attributes.h>
24#include <mlir/IR/BuiltinAttributes.h>
25#include <mlir/IR/Operation.h>
26
27#include <llvm/ADT/APSInt.h>
28#include <llvm/ADT/SlowDynamicAPInt.h>
29#include <llvm/ADT/Twine.h>
30#include <llvm/ADT/TypeSwitch.h>
31#include <llvm/Support/LogicalResult.h>
32
33#include <algorithm>
34#include <mutex>
35
36using namespace mlir;
37
38namespace llzk {
39
40// Having `knownFields` as a static local object ensures it is initialized when
41// `getKnownFields` is called, rather than relying on non-local static initialization
42// order (https://en.cppreference.com/cpp/language/initialization).
43static DenseMap<StringRef, Field> &getKnownFields() {
44 static DenseMap<StringRef, Field> knownFields;
45 return knownFields;
46}
47
48Field::Field(std::string_view primeStr, StringRef name) : Field(toDynamicAPInt(primeStr), name) {}
49
50Field::Field(const llvm::DynamicAPInt &prime, StringRef name)
51 : primeName(name), primeMod(prime), halfPrime((primeMod + 1) / 2),
52 bitwidth(std::max(1U, toAPSInt(prime).getActiveBits())) {}
53
54FailureOr<std::reference_wrapper<const Field>> Field::tryGetField(StringRef fieldName) {
55 static std::once_flag fieldsInit;
56 std::call_once(fieldsInit, initKnownFields);
57
58 auto &knownFields = getKnownFields();
59 if (auto it = knownFields.find(fieldName); it != knownFields.end()) {
60 return {it->second};
61 }
62 return failure();
63}
64
65LogicalResult Field::verifyFieldDefined(StringRef fieldName, EmitErrorFn errFn) {
66 if (failed(Field::tryGetField(fieldName))) {
67 return errFn().append("field '", fieldName, "' is not defined");
68 }
69 return success();
70}
71
72const Field &Field::getField(StringRef fieldName, EmitErrorFn errFn) {
73 auto res = tryGetField(fieldName);
74 if (succeeded(res)) {
75 return res.value().get();
76 }
77 std::string msg = "field \"" + fieldName.str() + "\" is unsupported";
78 if (errFn) {
79 errFn().append(msg).report();
80 }
81 llvm::report_fatal_error(msg.c_str());
82}
83
84void Field::addField(StringRef fieldName, StringRef primeStr, EmitErrorFn errFn) {
85 auto prime = parseDynamicAPInt(primeStr);
86 if (!prime) {
87 std::string message = llvm::toString(prime.takeError());
88 if (errFn) {
89 errFn().append(message).report();
90 } else {
91 llvm::report_fatal_error(message.c_str());
92 }
93 return;
94 }
95 addField(fieldName, *prime, errFn);
96}
97
98void Field::addField(Field &&f, EmitErrorFn errFn) {
99 if (f.prime() < 2) {
100 if (errFn) {
101 errFn().append("field modulus must be at least 2").report();
102 } else {
103 llvm::report_fatal_error("field modulus must be at least 2");
104 }
105 return;
106 }
107 // Use `tryGetField()` to ensure knownFields is initialized before checking for conflicts.
108 auto existing = Field::tryGetField(f.name());
109 if (succeeded(existing)) {
110 // Field exists and conflicts with existing definition.
111 std::string msg;
112 debug::Appender(msg) << "Definition of \"" << f.name()
113 << "\" conflicts with prior definition: prior="
114 << existing.value().get().prime() << ", new=" << f.prime();
115 if (errFn) {
116 errFn().append(msg).report();
117 } else {
118 llvm::report_fatal_error(msg.c_str());
119 }
120 return;
121 }
122 // Field does not exist, add it.
123 getKnownFields().try_emplace(f.name(), f);
124}
125
126void Field::initKnownFields() {
127 static constexpr const char BN128[] = "bn128", BN254[] = "bn254", GRUMPKIN[] = "grumpkin",
128 BABYBEAR[] = "babybear", GOLDILOCKS[] = "goldilocks",
129 MERSENNE31[] = "mersenne31", KOALABEAR[] = "koalabear";
130
131 auto insert = [](const char *name, const char *primeStr) {
132 getKnownFields().try_emplace(name, Field(primeStr, name));
133 };
134
135 // Reference: https://github.com/iden3/circom/blob/master/program_structure/src/utils/constants.rs
136 // bn128/254, default for circom
137 insert(BN128, "21888242871839275222246405745257275088548364400416034343698204186575808495617");
138 insert(BN254, "21888242871839275222246405745257275088548364400416034343698204186575808495617");
139 // Grumpkin scalar field
140 insert(GRUMPKIN, "21888242871839275222246405745257275088696311157297823662689037894645226208583");
141 // 15 * 2^27 + 1, default for zirgen
142 insert(BABYBEAR, "2013265921");
143 // 2^64 - 2^32 + 1, used for plonky2
144 insert(GOLDILOCKS, "18446744069414584321");
145 // 2^31 - 1, used for Plonky3
146 insert(MERSENNE31, "2147483647");
147 // 2^31 - 2^24 + 1, also for Plonky3
148 insert(KOALABEAR, "2130706433");
149}
150
151DynamicAPInt Field::reduce(const DynamicAPInt &i) const {
152 DynamicAPInt m = i % prime();
153 if (m < 0) {
154 return prime() + m;
155 }
156 return m;
157}
158
159DynamicAPInt Field::toSigned(const DynamicAPInt &i) const { return i < half() ? i : i - prime(); }
160
161DynamicAPInt Field::inv(const DynamicAPInt &i) const { return modInversePrime(i, prime()); }
162
163IntegerAttr Field::getPrimeAttr(MLIRContext *context, unsigned bitWidth) const {
164 return IntegerAttr::get(
165 IntegerType::get(context, bitWidth), cantFail(checkedToAPInt(prime(), bitWidth, false))
166 );
167}
168
169// Parses Fields from the given attribute, if able.
170static LogicalResult parseFields(Attribute a) {
171 // clang-format off
172 return TypeSwitch<
173 Attribute, FailureOr<SmallVector<std::reference_wrapper<const Field>>>>(a)
174 .Case<UnitAttr>(
175 [](auto) {
176 return success();
177 })
178 .Case<StringAttr>(
179 [](auto s) -> FailureOr<SmallVector<std::reference_wrapper<const Field>>> {
180 auto fieldRes = Field::tryGetField(s);
181 if (failed(fieldRes)) {
182 return failure();
183 }
184 return SmallVector<std::reference_wrapper<const Field>> {fieldRes.value()};
185 })
186 .Case<ArrayAttr>(
187 [](auto arr) -> FailureOr<SmallVector<std::reference_wrapper<const Field>>> {
188 // An ArrayAttr may only contain inner StringAttr
189 SmallVector<std::reference_wrapper<const Field>> res;
190 for (Attribute elem : arr) {
191 if (auto s = llvm::dyn_cast<StringAttr>(elem)) {
192 auto fieldRes = Field::tryGetField(s);
193 if (failed(fieldRes)) {
194 return failure();
195 }
196 res.push_back(fieldRes.value());
197 } else {
198 return failure();
199 }
200 }
201 return res;
202 })
203 .Default([](auto) { return failure(); });
204 // clang-format on
205}
206
207LogicalResult addSpecifiedFields(ModuleOp modOp) {
208 if (Attribute a = modOp->getAttr(FIELD_ATTR_NAME)) {
209 return parseFields(a);
210 }
211 // Always recurse.
212 if (ModuleOp parentMod = modOp->getParentOfType<ModuleOp>()) {
213 return addSpecifiedFields(parentMod);
214 }
215 return success();
216}
217
218} // namespace llzk
219
220namespace {
221
222struct FieldsCtx {
223 llzk::FieldSet &fields;
224 LogicalResult &status;
225 Operation *scope;
226};
227
228} // namespace
229
230static void handleAttribute(Attribute, FieldsCtx &);
231
232static void handleType(Type type, FieldsCtx &ctx) {
233 TypeSwitch<Type> ts(type);
234 ts.Case([&ctx](llzk::felt::FeltType felt) {
235 if (felt.hasField()) {
236 ctx.fields.insert(felt.getField());
237 } else {
238 ctx.status = failure();
239 if (ctx.scope) {
240 ctx.scope->emitWarning("felt type is unspecified, which may cause some passes to fail");
241 }
242 }
243 });
244 ts.Case([&ctx](llzk::array::ArrayType array) { handleType(array.getElementType(), ctx); });
245 ts.Case([&ctx](llzk::pod::PodType pod) {
246 for (auto record : pod.getRecords()) {
247 handleAttribute(record, ctx);
248 }
249 });
250 ts.Case([&ctx](FunctionType funcType) {
251 for (auto i : funcType.getInputs()) {
252 handleType(i, ctx);
253 }
254 for (auto o : funcType.getResults()) {
255 handleType(o, ctx);
256 }
257 });
258 // Do nothing by default for any other type
259 ts.Default([](auto) {});
260}
261
262static void handleAttribute(Attribute attr, FieldsCtx &ctx) {
263 TypeSwitch<Attribute> ts(attr);
264 ts.Case([&ctx](TypeAttr typeAttr) { handleType(typeAttr.getValue(), ctx); });
265 ts.Case([&ctx](ArrayAttr arrayAttr) {
266 for (auto a : arrayAttr) {
267 handleAttribute(a, ctx);
268 }
269 });
270 ts.Case([&ctx](DictionaryAttr dictAttr) {
271 for (auto a : dictAttr.getValue()) {
272 handleAttribute(a.getValue(), ctx);
273 }
274 });
275 ts.Case([&ctx](llzk::pod::RecordAttr recordAttr) { handleType(recordAttr.getType(), ctx); });
276 ts.Default([](auto) {});
277}
278
279LogicalResult llzk::collectFields(Operation *root, llzk::FieldSet &fields, bool silent) {
280 if (!root) {
281 return success(); // Nothing to do
282 }
283 LogicalResult status = success();
284 root->walk([&fields, &status, silent](Operation *op) {
285 FieldsCtx ctx = {.fields = fields, .status = status, .scope = silent ? nullptr : op};
286 // Crawl for types in the results,
287 for (auto result : op->getOpResults()) {
288 handleType(result.getType(), ctx);
289 }
290 // the attributes,
291 for (auto attr : op->getAttrs()) {
292 handleAttribute(attr.getValue(), ctx);
293 }
294 // block arguments (if any)
295 for (auto &region : op->getRegions()) {
296 for (auto &block : region) {
297 for (auto &arg : block.getArguments()) {
298 handleType(arg.getType(), ctx);
299 }
300 }
301 }
302 });
303
304 return status;
305}
306
307std::optional<std::reference_wrapper<const llzk::Field>>
308llzk::tryDetectSpecifiedField(Operation *root) {
309 if (!root) {
310 return std::nullopt;
311 }
312
313 ModuleOp modOp = dyn_cast<ModuleOp>(root);
314 if (!modOp) {
315 modOp = root->getParentOfType<ModuleOp>();
316 }
317
318 if (!modOp) {
319 return std::nullopt;
320 }
321
322 FieldSet fields;
323 if (failed(collectFields(modOp, fields)) || fields.size() != 1) {
324 return std::nullopt;
325 }
326 return *fields.begin();
327}
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
llvm::DynamicAPInt half() const
Returns ceil(p / 2), the first negative signed representative.
Definition Field.h:73
mlir::IntegerAttr getPrimeAttr(mlir::MLIRContext *context, unsigned bitWidth) const
Return the field prime modulus materialized as an integer attribute at bitWidth.
Definition Field.cpp:163
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
Field()=delete
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
unsigned bitWidth() const
Width sufficient for the modulus and its representatives, independent of literal storage.
Definition Field.h:103
llvm::DynamicAPInt inv(const llvm::DynamicAPInt &i) const
Returns the multiplicative inverse of i in prime field p.
Definition Field.cpp:161
static llvm::LogicalResult verifyFieldDefined(llvm::StringRef fieldName, EmitErrorFn errFn)
Search for a field with the given name, reporting an error if the field is not found.
Definition Field.cpp:65
static const Field & getField(llvm::StringRef fieldName, EmitErrorFn errFn)
Get a Field from a given field name string.
::mlir::Type getElementType() const
const ::llzk::Field & getField() const
Definition Dialect.cpp:255
bool hasField() const
Definition Types.h.inc:50
::llvm::ArrayRef<::llzk::pod::RecordAttr > getRecords() const
llvm::function_ref< InFlightDiagnosticWrapper()> EmitErrorFn
Callback to produce an error diagnostic.
DynamicAPInt toDynamicAPInt(StringRef str)
Expected< DynamicAPInt > parseDynamicAPInt(StringRef str)
LogicalResult addSpecifiedFields(ModuleOp modOp)
Definition Field.cpp:207
DynamicAPInt modInversePrime(const DynamicAPInt &f, const DynamicAPInt &p)
llvm::SmallSet< FieldRef, 2 > FieldSet
Typealias for a set of Fields.
Definition Field.h:160
APSInt toAPSInt(const DynamicAPInt &i)
std::optional< std::reference_wrapper< const Field > > tryDetectSpecifiedField(mlir::Operation *root)
Try to detect a uniquely used field from the enclosing LLZK module.
constexpr char FIELD_ATTR_NAME[]
Name of the attribute on the top-level ModuleOp that defines prime fields used in the circuit.
Definition Constants.h:32
mlir::LogicalResult collectFields(mlir::Operation *root, FieldSet &fields, bool silent=true)
Collects all the fields used in a circuit.
Expected< APInt > checkedToAPInt(const DynamicAPInt &value, unsigned bitWidth, bool isSigned)