20#include <mlir/IR/Operation.h>
22#include <llvm/ADT/SmallString.h>
23#include <llvm/ADT/TypeSwitch.h>
24#include <llvm/Support/FormatVariadic.h>
25#include <llvm/Support/MathExtras.h>
26#include <llvm/Support/raw_ostream.h>
33static std::string renderJSON(
const llvm::json::Value &value) {
34 return llvm::formatv(
"{0:2}", value).str();
37static llvm::StringRef jsonKind(
const llvm::json::Value &value) {
38 if (value.getAsNull()) {
41 if (value.getAsBoolean().has_value()) {
44 if (value.getAsNumber().has_value()) {
47 if (value.getAsString()) {
50 if (value.getAsArray()) {
53 if (value.getAsObject()) {
59static std::string appendObjectPath(llvm::StringRef path, llvm::StringRef key) {
60 llvm::SmallString<64> out(path);
63 return std::string(out);
66static std::string appendIndexPath(llvm::StringRef path,
size_t index) {
67 llvm::SmallString<64> out(path);
68 llvm::raw_svector_ostream os(out);
69 os <<
'[' << index <<
']';
70 return std::string(out);
73static void pushMismatch(
74 llvm::SmallVectorImpl<JSONMismatch> &out, llvm::StringRef path,
const llvm::Twine &message
79static void diffObjects(
80 const llvm::json::Object &expected,
const llvm::json::Object &actual,
81 llvm::SmallVectorImpl<JSONMismatch> &out, llvm::StringRef path
83 for (
const auto &kv : expected) {
84 if (
const llvm::json::Value *actualValue = actual.get(kv.first)) {
85 diffJSON(kv.second, *actualValue, out, appendObjectPath(path, kv.first));
88 pushMismatch(out, appendObjectPath(path, kv.first),
"missing key");
90 for (
const auto &kv : actual) {
91 if (!expected.get(kv.first)) {
92 pushMismatch(out, appendObjectPath(path, kv.first),
"unexpected key");
97static void diffArrays(
98 const llvm::json::Array &expected,
const llvm::json::Array &actual,
99 llvm::SmallVectorImpl<JSONMismatch> &out, llvm::StringRef path
101 if (expected.size() != actual.size()) {
104 llvm::Twine(
"array length mismatch: expected ") + llvm::Twine(expected.size()) +
", got " +
105 llvm::Twine(actual.size())
108 size_t shared = std::min(expected.size(), actual.size());
109 for (
size_t i = 0; i < shared; ++i) {
110 diffJSON(expected[i], actual[i], out, appendIndexPath(path, i));
117static llvm::Expected<int64_t> jsonToInt(
const llvm::json::Value *json) {
118 if (std::optional<int64_t> integer = json->getAsInteger()) {
121 if (std::optional<llvm::StringRef> str = json->getAsString()) {
123 if (!str->getAsInteger(10, value)) {
127 return makeError(
"expected integer-compatible JSON value");
131static llvm::Expected<llvm::DynamicAPInt>
132jsonToFelt(
const llvm::json::Value *json,
const Field &field) {
133 if (std::optional<llvm::StringRef> str = json->getAsString()) {
136 return value.takeError();
138 return field.reduce(*value);
140 if (std::optional<int64_t> integer = json->getAsInteger()) {
141 return field.reduce(*integer);
143 return makeError(
"expected felt value as JSON integer or decimal string");
147static llvm::Expected<WitnessVal> parseJSONArray(
148 const llvm::json::Value *json, array::ArrayType type,
const Field &field, Operation *origin,
151 const auto *jsonArray = json->getAsArray();
156 llvm::ArrayRef<int64_t> shape = type.getShape();
157 if (dimIndex >= shape.size()) {
162 return expectedSize.takeError();
164 if (jsonArray->size() != *expectedSize) {
165 return makeError(
"JSON array length does not match LLZK array dimension");
168 auto arrayValue = std::make_shared<ArrayValue>();
169 arrayValue->type = type;
170 if (dimIndex == shape.size() - 1) {
171 arrayValue->elements.reserve(jsonArray->size());
172 for (
const llvm::json::Value &elem : *jsonArray) {
173 auto parsed =
parseJSONValue(&elem, type.getElementType(), field, origin);
175 return parsed.takeError();
177 arrayValue->elements.push_back(*parsed);
182 arrayValue->elements.reserve(jsonArray->size());
183 for (
const llvm::json::Value &elem : *jsonArray) {
184 auto parsed = parseJSONArray(&elem, type, field, origin, dimIndex + 1);
186 return parsed.takeError();
188 auto subArray =
asArray(*parsed);
190 return subArray.takeError();
192 for (
const WitnessVal &subElem : (*subArray)->elements) {
193 arrayValue->elements.push_back(subElem);
200 std::string rendered;
201 llvm::raw_string_ostream(rendered) << field.
reduce(value);
202 return llvm::json::Value(rendered);
206static llvm::Expected<llvm::json::Value> serializeJSONArray(
208 Operation *origin,
SerializationMode mode,
size_t dimIndex = 0,
size_t flatOffset = 0
210 llvm::json::Array jsonArray;
211 llvm::ArrayRef<int64_t> shape = type.
getShape();
214 return dimSize.takeError();
216 if (dimIndex == shape.size() - 1) {
217 for (
size_t i = 0; i < *dimSize; ++i) {
218 bool overflow =
false;
219 size_t elementOffset = llvm::SaturatingAdd(flatOffset, i, &overflow);
221 return makeError(
"JSON array output flat index would overflow size_t");
224 arrayValue->elements[elementOffset], type.
getElementType(), tables, origin, mode
227 return elem.takeError();
229 jsonArray.push_back(*elem);
231 return llvm::json::Value(std::move(jsonArray));
234 size_t subArraySize = 1;
235 for (
size_t i = dimIndex + 1; i < shape.size(); ++i) {
238 return nextDimSize.takeError();
240 bool overflow =
false;
241 subArraySize = llvm::SaturatingMultiply(subArraySize, *nextDimSize, &overflow);
243 return makeError(
"JSON array output sub-array size would overflow size_t");
247 for (
size_t i = 0; i < *dimSize; ++i) {
248 bool overflow =
false;
249 size_t nextOffset = llvm::SaturatingMultiplyAdd(i, subArraySize, flatOffset, &overflow);
251 return makeError(
"JSON array output flat offset would overflow size_t");
254 serializeJSONArray(arrayValue, type, tables, origin, mode, dimIndex + 1, nextOffset);
256 return subArray.takeError();
258 jsonArray.push_back(*subArray);
260 return llvm::json::Value(std::move(jsonArray));
264llvm::Expected<WitnessVal>
266 return llvm::TypeSwitch<Type, llvm::Expected<WitnessVal>>(type)
267 .Case([&](
felt::FeltType) -> llvm::Expected<WitnessVal> {
return jsonToFelt(json, field); })
269 return parseJSONArray(json, arrayType, field, origin);
272 return makeError(
"pod JSON inputs are not supported in llzk-witgen v1");
275 return makeError(
"struct JSON inputs are not supported in llzk-witgen v1");
277 .Case([&](IndexType) -> llvm::Expected<WitnessVal> {
278 auto integer = jsonToInt(json);
280 return integer.takeError();
284 .Case([&](IntegerType intType) -> llvm::Expected<WitnessVal> {
285 if (intType.getWidth() == 1) {
286 if (std::optional<bool> boolValue = json->getAsBoolean()) {
289 auto integer = jsonToInt(json);
291 return integer.takeError();
293 return *integer != 0;
295 return makeError(
"only i1 integer JSON inputs are supported");
296 }).Default([&](Type) -> llvm::Expected<WitnessVal> {
297 return makeError(
"unsupported input type in llzk-witgen");
303 const WitnessVal &value, Type type, SymbolTableCollection &tables, Operation *origin,
306 return llvm::TypeSwitch<Type, llvm::Expected<llvm::json::Value>>(type)
307 .Case([&](
felt::FeltType feltType) -> llvm::Expected<llvm::json::Value> {
308 auto feltValue =
asFelt(value);
310 return feltValue.takeError();
313 return makeError(
"felt JSON output requires a declared field");
317 .Case([&](
array::ArrayType arrayType) -> llvm::Expected<llvm::json::Value> {
318 auto arrayValue =
asArray(value);
320 return arrayValue.takeError();
322 return serializeJSONArray(*arrayValue, arrayType, tables, origin, mode);
324 .Case([&](
pod::PodType podType) -> llvm::Expected<llvm::json::Value> {
325 auto podValue =
asPod(value);
327 return podValue.takeError();
329 llvm::json::Object result;
330 for (pod::RecordAttr record : podType.
getRecords()) {
331 auto it = (*podValue)->records.find(record.getName().getValue());
332 if (it == (*podValue)->records.end()) {
333 return makeError(
"missing POD record during JSON serialization");
335 auto serialized =
serializeJSONValue(it->second, record.getType(), tables, origin, mode);
337 return serialized.takeError();
339 result[record.getName().getValue()] = *serialized;
341 return llvm::json::Value(std::move(result));
346 return structValue.takeError();
349 if (failed(defLookup)) {
350 return makeError(
"could not resolve struct type during JSON serialization");
352 llvm::json::Object result;
354 auto it = (*structValue)->members.find(member.getSymName());
355 if (it == (*structValue)->members.end()) {
356 return makeError(
"missing struct member during JSON serialization");
360 if (!member.hasPublicAttr()) {
365 !isa<component::StructType>(member.getType())) {
370 auto serialized =
serializeJSONValue(it->second, member.getType(), tables, origin, mode);
372 return serialized.takeError();
375 auto *
object = serialized->getAsObject();
376 if (!
object || object->empty()) {
380 result[member.getSymName()] = *serialized;
382 return llvm::json::Value(std::move(result));
384 .Case([&](IndexType) -> llvm::Expected<llvm::json::Value> {
385 auto indexValue =
asIndex(value);
387 return indexValue.takeError();
389 return llvm::json::Value(*indexValue);
391 .Case([&](IntegerType intType) -> llvm::Expected<llvm::json::Value> {
392 if (intType.getWidth() != 1) {
393 return makeError(
"only i1 integer JSON serialization is supported");
395 auto boolValue =
asBool(value);
397 return boolValue.takeError();
399 return llvm::json::Value(*boolValue);
400 }).Default([&](Type) -> llvm::Expected<llvm::json::Value> {
401 return makeError(
"unsupported output type in llzk-witgen");
407 ArrayRef<InputBinding> bindings, ArrayRef<WitnessVal> values, SymbolTableCollection &tables,
410 if (bindings.size() != values.size()) {
411 return makeError(
"input binding count mismatch during witness JSON assembly");
414 llvm::json::Object result;
415 for (
auto [binding, value] : llvm::zip(bindings, values)) {
419 return serialized.takeError();
421 result[binding.name] = *serialized;
428 const WitnessVal &root, Type rootType, ArrayRef<std::string> path,
429 SymbolTableCollection &tables, Operation *origin
435 if (
auto structType = dyn_cast<component::StructType>(rootType)) {
438 return structValue.takeError();
440 auto defLookup = structType.getDefinition(tables, origin);
441 if (failed(defLookup)) {
442 return makeError(
"could not resolve struct type while extracting witness value");
445 if (member.getSymName() != path.front()) {
448 auto it = (*structValue)->members.find(member.getSymName());
449 if (it == (*structValue)->members.end()) {
450 return makeError(
"missing struct member while extracting witness value");
452 return extractValueAtPath(it->second, member.getType(), path.drop_front(), tables, origin);
454 return makeError(
"unknown struct member while extracting witness value");
457 if (
auto podType = dyn_cast<pod::PodType>(rootType)) {
458 auto podValue =
asPod(root);
460 return podValue.takeError();
462 for (pod::RecordAttr record : podType.getRecords()) {
463 if (record.getName().getValue() != path.front()) {
466 auto it = (*podValue)->records.find(record.getName().getValue());
467 if (it == (*podValue)->records.end()) {
468 return makeError(
"missing POD record while extracting witness value");
470 return extractValueAtPath(it->second, record.getType(), path.drop_front(), tables, origin);
472 return makeError(
"unknown POD record while extracting witness value");
475 return makeError(
"extra witness path components for non-aggregate value");
479 const llvm::json::Value &expected,
const llvm::json::Value &actual,
480 llvm::SmallVectorImpl<JSONMismatch> &out, llvm::StringRef path
482 if (expected.kind() != actual.kind()) {
485 llvm::Twine(
"type mismatch: expected ") + jsonKind(expected) +
", got " + jsonKind(actual)
490 if (
const auto *expectedObject = expected.getAsObject()) {
491 diffObjects(*expectedObject, *actual.getAsObject(), out, path);
494 if (
const auto *expectedArray = expected.getAsArray()) {
495 diffArrays(*expectedArray, *actual.getAsArray(), out, path);
498 if (expected == actual) {
504 llvm::Twine(
"value mismatch: expected ") + renderJSON(expected) +
", got " +
511 os << mismatch.path <<
": " << mismatch.message <<
'\n';
Information about the prime finite field used for the interval analysis.
llvm::DynamicAPInt reduce(const llvm::DynamicAPInt &i) const
Returns the canonical representative of i mod p in [0, p).
::llvm::ArrayRef< int64_t > getShape() const
::mlir::Type getElementType() const
::mlir::FailureOr< SymbolLookupResult< StructDefOp > > getDefinition(::mlir::SymbolTableCollection &symbolTable, ::mlir::Operation *op, bool reportMissing=true) const
Gets the struct op that defines this struct.
const ::llzk::Field & getField() const
::llvm::ArrayRef<::llzk::pod::RecordAttr > getRecords() const
llvm::Expected< llvm::json::Object > buildInputsJSONObject(ArrayRef< InputBinding > bindings, ArrayRef< WitnessVal > values, SymbolTableCollection &tables, Operation *origin)
Serialize named input values into a JSON object.
std::variant< std::monostate, bool, int64_t, llvm::DynamicAPInt, ArrayValueRef, PodValueRef, StructValueRef > WitnessVal
Runtime value representation used by the tool-local interpreter.
llvm::Expected< PodValueRef > asPod(const WitnessVal &value)
Require a POD value from the runtime variant.
llvm::Expected< llvm::json::Value > serializeJSONValue(const WitnessVal &value, Type type, SymbolTableCollection &tables, Operation *origin, SerializationMode mode)
Serialize a supported LLZK runtime value into JSON.
llvm::Expected< bool > asBool(const WitnessVal &value)
Require a boolean value from the runtime variant.
llvm::Expected< WitnessVal > extractValueAtPath(const WitnessVal &root, Type rootType, ArrayRef< std::string > path, SymbolTableCollection &tables, Operation *origin)
Extract one nested runtime leaf by path.
void diffJSON(const llvm::json::Value &expected, const llvm::json::Value &actual, llvm::SmallVectorImpl< JSONMismatch > &out, llvm::StringRef path)
Compare two JSON values structurally and append any mismatches to out.
llvm::json::Value serializeFeltToJSON(const llvm::DynamicAPInt &value, const Field &field)
Render a runtime field element as its canonical decimal string without modifying the raw runtime valu...
SerializationMode
Select how struct values are filtered during JSON serialization.
std::shared_ptr< ArrayValue > ArrayValueRef
Shared runtime storage for LLZK array values.
llvm::Expected< int64_t > asIndex(const WitnessVal &value)
Require an index value from the runtime variant.
bool memberIsSignal(component::StructDefOp owner, component::MemberDefOp member)
Return true iff the member is considered a witness signal.
llvm::Expected< size_t > checkedShapeDimToSize(int64_t dim, llvm::StringRef context)
Convert one static dimension to size_t, rejecting dynamic or invalid sizes.
llvm::Expected< WitnessVal > parseJSONValue(const llvm::json::Value *json, Type type, const Field &field, Operation *origin)
Parse a supported LLZK input type from JSON.
void printJSONMismatches(llvm::raw_ostream &os, llvm::ArrayRef< JSONMismatch > mismatches)
Render one human-readable mismatch report.
llvm::Expected< llvm::DynamicAPInt > asFelt(const WitnessVal &value)
Require a felt value from the runtime variant.
llvm::Error makeError(const llvm::Twine &msg)
Build a string-backed error for user-facing witgen failures.
llvm::Expected< StructValueRef > asStruct(const WitnessVal &value)
Require a struct value from the runtime variant.
llvm::Expected< ArrayValueRef > asArray(const WitnessVal &value)
Require an array value from the runtime variant.
Expected< DynamicAPInt > parseDynamicAPInt(StringRef str)
One structured JSON mismatch between expected and actual witgen output.