LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
llzk-witgen.cpp
Go to the documentation of this file.
1//===-- llzk-witgen.cpp - LLZK witness generation tool ----------*- C++ -*-===//
2//
3// Part of the LLZK Project, under the Apache License v2.0.
4// See LICENSE.txt for license information.
5// Copyright 2026 Project LLZK
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
9
10#include "JSON.h"
11#include "WitgenDriver.h"
12#include "Wtns.h"
13#include "tools/config.h"
14
32
33#include <mlir/Dialect/Arith/IR/Arith.h>
34#include <mlir/Dialect/ControlFlow/IR/ControlFlowOps.h>
35#include <mlir/Dialect/Func/Extensions/InlinerExtension.h>
36#include <mlir/Dialect/Func/IR/FuncOps.h>
37#include <mlir/Dialect/MemRef/IR/MemRef.h>
38#include <mlir/Dialect/SCF/IR/SCF.h>
39#include <mlir/IR/BuiltinDialect.h>
40#include <mlir/IR/BuiltinOps.h>
41#include <mlir/IR/DialectRegistry.h>
42#include <mlir/Parser/Parser.h>
43
44#include <llvm/ADT/StringExtras.h>
45#include <llvm/Support/CommandLine.h>
46#include <llvm/Support/FileSystem.h>
47#include <llvm/Support/FormatVariadic.h>
48#include <llvm/Support/JSON.h>
49#include <llvm/Support/MemoryBuffer.h>
50#include <llvm/Support/PrettyStackTrace.h>
51#include <llvm/Support/Signals.h>
52
53using namespace mlir;
54
55static llvm::cl::opt<std::string> InputFilename(llvm::cl::Positional, llvm::cl::Required);
56static llvm::cl::opt<std::string>
57 InputsFilename("inputs", llvm::cl::Required, llvm::cl::desc("JSON input file"));
58static llvm::cl::list<std::string> IncludeDirs(
59 "I", llvm::cl::desc("Directory of include files"), llvm::cl::value_desc("directory"),
60 llvm::cl::Prefix
61);
62static llvm::cl::opt<std::string> BackendName(
63 "backend", llvm::cl::desc("Execution backend: interpreter or execution-engine"),
64 llvm::cl::init("interpreter")
65);
66static llvm::cl::opt<std::string> OutputScopeName(
67 "output-scope", llvm::cl::desc("Output scope: public or full-witness"), llvm::cl::init("public")
68);
69static llvm::cl::opt<std::string> UninitializedBehaviorName(
70 "uninitialized-behavior", llvm::cl::desc("Uninitialized value behavior: zero, random, or fail"),
71 llvm::cl::init("zero")
72);
73static llvm::cl::opt<uint64_t>
74 UninitializedSeed("uninitialized-seed", llvm::cl::desc("Seed for random uninitialized values"));
75static llvm::cl::opt<bool>
76 DumpJITCore("dump-jit-core", llvm::cl::desc("Print the pre-LLVM JIT module"));
77static llvm::cl::opt<bool>
78 DumpJITLLVM("dump-jit-llvm", llvm::cl::desc("Print the post-LLVM JIT module"));
79static llvm::cl::opt<std::string>
80 CheckOutputFilename("check-output", llvm::cl::desc("JSON file with expected witgen output"));
81static llvm::cl::opt<std::string>
82 WtnsOutputFilename("output-wtns", llvm::cl::desc("Write a snarkjs-compatible .wtns file"));
83
85int main(int argc, char **argv) {
86 llvm::sys::PrintStackTraceOnErrorSignal(llvm::StringRef());
87 llvm::setBugReportMsg(
88 "PLEASE submit a bug report to " BUG_REPORT_URL
89 " and include the crash backtrace, relevant LLZK files, and associated run script(s).\n"
90 );
91
92 llvm::cl::ParseCommandLineOptions(
93 argc, argv,
94 "llzk-witgen: execute LLZK compute semantics and emit JSON public outputs.\n"
95 "Note: llzk-witgen v1 ignores constrain() and traps on bool.assert.\n"
96 );
97
98 DialectRegistry registry;
100 mlir::func::registerInlinerExtension(registry);
101 registry.insert<
102 mlir::arith::ArithDialect, mlir::cf::ControlFlowDialect, mlir::func::FuncDialect,
103 mlir::memref::MemRefDialect, mlir::scf::SCFDialect>();
104 MLIRContext context;
105 context.appendDialectRegistry(registry);
106 context.loadAllAvailableDialects();
107 context.loadDialect<
108 mlir::arith::ArithDialect, mlir::cf::ControlFlowDialect, mlir::func::FuncDialect,
109 mlir::memref::MemRefDialect, mlir::scf::SCFDialect>();
110 if (failed(llzk::GlobalSourceMgr::get().setup(IncludeDirs))) {
111 return EXIT_FAILURE;
112 }
113
114 auto sourceBuffer = llvm::MemoryBuffer::getFileOrSTDIN(InputFilename);
115 if (!sourceBuffer) {
116 llvm::errs() << sourceBuffer.getError().message() << '\n';
117 return EXIT_FAILURE;
118 }
119
120 ParserConfig parserConfig(&context);
121 OwningOpRef<ModuleOp> moduleOp =
122 parseSourceString<ModuleOp>(sourceBuffer.get()->getBuffer(), parserConfig, InputFilename);
123 if (!moduleOp) {
124 return EXIT_FAILURE;
125 }
126
127 auto buffer = llvm::MemoryBuffer::getFileOrSTDIN(InputsFilename);
128 if (!buffer) {
129 llvm::errs() << buffer.getError().message() << '\n';
130 return EXIT_FAILURE;
131 }
132
133 auto parsed = llvm::json::parse(buffer.get()->getBuffer());
134 if (!parsed) {
135 llvm::errs() << "failed to parse JSON input: " << llvm::toString(parsed.takeError()) << '\n';
136 return EXIT_FAILURE;
137 }
138
140 if (BackendName == "execution-engine") {
142 } else if (BackendName == "interpreter") {
144 } else {
145 llvm::errs() << "unknown backend: " << BackendName << '\n';
146 return EXIT_FAILURE;
147 }
148 if (OutputScopeName == "full-witness") {
150 } else if (OutputScopeName == "public") {
152 } else {
153 llvm::errs() << "unknown output scope: " << OutputScopeName << '\n';
154 return EXIT_FAILURE;
155 }
156 if (UninitializedBehaviorName == "zero") {
158 } else if (UninitializedBehaviorName == "random") {
160 } else if (UninitializedBehaviorName == "fail") {
162 } else {
163 llvm::errs() << "unknown uninitialized behavior: " << UninitializedBehaviorName << '\n';
164 return EXIT_FAILURE;
165 }
166 if (UninitializedSeed.getNumOccurrences() > 0) {
167 options.randomSeed = UninitializedSeed;
168 }
169 options.inlineIncludes = true;
170 options.dumpJITCore = DumpJITCore;
171 options.dumpJITLLVM = DumpJITLLVM;
172 if (WtnsOutputFilename.getNumOccurrences() > 0) {
173 if (WtnsOutputFilename == "-") {
174 llvm::errs() << "--output-wtns does not support stdout; specify an output file\n";
175 return EXIT_FAILURE;
176 }
177 if (OutputScopeName.getNumOccurrences() > 0 && OutputScopeName != "full-witness") {
178 llvm::errs() << "--output-wtns conflicts with --output-scope=" << OutputScopeName
179 << "; use --output-scope=full-witness\n";
180 return EXIT_FAILURE;
181 }
182 // WTNS contains every R1CS wire, including ordinary private felt members.
184 }
185
186 auto result = llzk::witgen::runWitgen(*moduleOp, *parsed, options);
187 if (!result) {
188 llvm::errs() << "llzk-witgen error: " << llvm::toString(result.takeError()) << '\n';
189 return EXIT_FAILURE;
190 }
191
192 if (WtnsOutputFilename.getNumOccurrences() > 0) {
193 // The WTNS header records the field modulus and element width. Re-read the
194 // unique field from the preprocessed module so the header and generated
195 // values necessarily describe the same field.
196 llzk::FieldSet fields;
197 if (failed(llzk::collectFields(moduleOp->getOperation(), fields)) || fields.size() != 1) {
198 llvm::errs() << "llzk-witgen error: .wtns output requires exactly one field\n";
199 return EXIT_FAILURE;
200 }
201 const llzk::Field &field = (*fields.begin()).get();
202 if (auto error = llzk::witgen::writeWtns(*moduleOp, *result, field, WtnsOutputFilename)) {
203 llvm::errs() << "llzk-witgen error: " << llvm::toString(std::move(error)) << '\n';
204 return EXIT_FAILURE;
205 }
206 }
207
208 if (CheckOutputFilename.getNumOccurrences() > 0) {
209 auto expectedBuffer = llvm::MemoryBuffer::getFileOrSTDIN(CheckOutputFilename);
210 if (!expectedBuffer) {
211 llvm::errs() << expectedBuffer.getError().message() << '\n';
212 return EXIT_FAILURE;
213 }
214
215 auto expected = llvm::json::parse(expectedBuffer.get()->getBuffer());
216 if (!expected) {
217 llvm::errs() << "failed to parse expected JSON output: "
218 << llvm::toString(expected.takeError()) << '\n';
219 return EXIT_FAILURE;
220 }
221
222 llvm::SmallVector<llzk::witgen::JSONMismatch> mismatches;
223 llzk::witgen::diffJSON(*expected, *result, mismatches);
224 if (!mismatches.empty()) {
225 llvm::errs() << "llzk-witgen output mismatch:\n";
226 llzk::witgen::printJSONMismatches(llvm::errs(), mismatches);
227 return EXIT_FAILURE;
228 }
229
230 llvm::outs() << "output matched expected JSON\n";
231 return EXIT_SUCCESS;
232 }
233
234 llvm::outs() << llvm::formatv("{0:2}", *result) << '\n';
235 return EXIT_SUCCESS;
236}
Information about the prime finite field used for the interval analysis.
Definition Field.h:36
static GlobalSourceMgr & get()
#define BUG_REPORT_URL
Definition config.h:15
int main(int argc, char **argv)
Execute the llzk-witgen command-line tool.
This file defines llzk::registerAllDialects.
@ Public
Emit only public outputs.
@ FullWitness
Emit inputs plus public outputs and members marked as witness signals.
@ R1CSWitness
Emit inputs plus every scalar main member needed to construct an R1CS witness.
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.
Definition JSON.cpp:473
llvm::Expected< llvm::json::Value > runWitgen(ModuleOp moduleOp, const llvm::json::Value &input, const WitgenOptions &options)
Run include preprocessing, field validation, and backend execution.
llvm::Error writeWtns(ModuleOp moduleOp, const llvm::json::Value &fullWitness, const Field &field, StringRef outputFilename)
Definition Wtns.cpp:222
void printJSONMismatches(llvm::raw_ostream &os, llvm::ArrayRef< JSONMismatch > mismatches)
Render one human-readable mismatch report.
Definition JSON.cpp:504
void registerAllDialects(mlir::DialectRegistry &registry)
llvm::SmallSet< FieldRef, 2 > FieldSet
Typealias for a set of Fields.
Definition Field.h:160
mlir::LogicalResult collectFields(mlir::Operation *root, FieldSet &fields, bool silent=true)
Collects all the fields used in a circuit.
Definition Field.cpp:264
Configure one llzk-witgen execution.
UninitializedBehavior uninitializedBehavior
std::optional< uint64_t > randomSeed