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/Dialect/SMT/IR/SMTDialect.h>
40#include <mlir/IR/BuiltinDialect.h>
41#include <mlir/IR/BuiltinOps.h>
42#include <mlir/IR/Diagnostics.h>
43#include <mlir/IR/DialectRegistry.h>
44#include <mlir/Parser/Parser.h>
45
46#include <llvm/ADT/StringExtras.h>
47#include <llvm/Support/CommandLine.h>
48#include <llvm/Support/FileSystem.h>
49#include <llvm/Support/FormatVariadic.h>
50#include <llvm/Support/JSON.h>
51#include <llvm/Support/MemoryBuffer.h>
52#include <llvm/Support/PrettyStackTrace.h>
53#include <llvm/Support/Signals.h>
54#include <llvm/Support/SourceMgr.h>
55
56#include <cstdlib>
57#include <exception>
58
59using namespace mlir;
60
62static int runMain(int argc, char **argv) {
63 llvm::cl::opt<std::string> InputFilename(llvm::cl::Positional, llvm::cl::Required);
64 llvm::cl::opt<std::string> InputsFilename(
65 "inputs", llvm::cl::Required, llvm::cl::desc("JSON input file")
66 );
67 llvm::cl::list<std::string> IncludeDirs(
68 "I", llvm::cl::desc("Directory of include files"), llvm::cl::value_desc("directory"),
69 llvm::cl::Prefix
70 );
71 llvm::cl::opt<std::string> BackendName(
72 "backend", llvm::cl::desc("Execution backend: interpreter or execution-engine"),
73 llvm::cl::init("interpreter")
74 );
75 llvm::cl::opt<std::string> OutputScopeName(
76 "output-scope", llvm::cl::desc("Output scope: public or full-witness"),
77 llvm::cl::init("public")
78 );
79 llvm::cl::opt<std::string> UninitializedBehaviorName(
80 "uninitialized-behavior",
81 llvm::cl::desc("Uninitialized value behavior: zero, random, or fail"), llvm::cl::init("zero")
82 );
83 llvm::cl::opt<uint64_t> UninitializedSeed(
84 "uninitialized-seed", llvm::cl::desc("Seed for random uninitialized values")
85 );
86 llvm::cl::opt<bool> DumpJITCore("dump-jit-core", llvm::cl::desc("Print the pre-LLVM JIT module"));
87 llvm::cl::opt<bool> DumpJITLLVM(
88 "dump-jit-llvm", llvm::cl::desc("Print the post-LLVM JIT module")
89 );
90 llvm::cl::opt<std::string> CheckOutputFilename(
91 "check-output", llvm::cl::desc("JSON file with expected witgen output")
92 );
93 llvm::cl::opt<std::string> WtnsOutputFilename(
94 "output-wtns", llvm::cl::desc("Write a snarkjs-compatible .wtns file")
95 );
96
97 llvm::sys::PrintStackTraceOnErrorSignal(llvm::StringRef());
98 llvm::setBugReportMsg(
99 "PLEASE submit a bug report to " BUG_REPORT_URL
100 " and include the crash backtrace, relevant LLZK files, and associated run script(s).\n"
101 );
102
103 llvm::cl::ParseCommandLineOptions(
104 argc, argv,
105 "llzk-witgen: execute LLZK compute semantics and emit JSON public outputs.\n"
106 "Note: llzk-witgen v1 ignores constrain() and traps on bool.assert.\n"
107 );
108
109 DialectRegistry registry;
110 llzk::registerDialects(registry);
112 mlir::func::registerInlinerExtension(registry);
113 registry.insert<
114 mlir::arith::ArithDialect, mlir::cf::ControlFlowDialect, mlir::func::FuncDialect,
115 mlir::memref::MemRefDialect, mlir::scf::SCFDialect>();
116
117 MLIRContext context;
118 context.appendDialectRegistry(registry);
119 context.loadAllAvailableDialects();
120
121 if (failed(llzk::GlobalSourceMgr::get().setup(IncludeDirs))) {
122 return EXIT_FAILURE;
123 }
124
125 auto sourceBuffer = llvm::MemoryBuffer::getFileOrSTDIN(InputFilename);
126 if (!sourceBuffer) {
127 llvm::errs() << sourceBuffer.getError().message() << '\n';
128 return EXIT_FAILURE;
129 }
130
131 ParserConfig parserConfig(&context);
132 llvm::SourceMgr sourceMgr;
133 sourceMgr.AddNewSourceBuffer(std::move(*sourceBuffer), llvm::SMLoc());
134 // Keep source diagnostics active through preprocessing and witness generation.
135 SourceMgrDiagnosticHandler diagnosticHandler(sourceMgr, &context);
136 OwningOpRef<ModuleOp> moduleOp = parseSourceFile<ModuleOp>(sourceMgr, parserConfig);
137 if (!moduleOp) {
138 return EXIT_FAILURE;
139 }
140
141 auto buffer = llvm::MemoryBuffer::getFileOrSTDIN(InputsFilename);
142 if (!buffer) {
143 llvm::errs() << buffer.getError().message() << '\n';
144 return EXIT_FAILURE;
145 }
146
147 auto parsed = llvm::json::parse(buffer.get()->getBuffer());
148 if (!parsed) {
149 llvm::errs() << "failed to parse JSON input: " << llvm::toString(parsed.takeError()) << '\n';
150 return EXIT_FAILURE;
151 }
152
154 if (BackendName == "execution-engine") {
156 } else if (BackendName == "interpreter") {
158 } else {
159 llvm::errs() << "unknown backend: " << BackendName << '\n';
160 return EXIT_FAILURE;
161 }
162 if (OutputScopeName == "full-witness") {
164 } else if (OutputScopeName == "public") {
166 } else {
167 llvm::errs() << "unknown output scope: " << OutputScopeName << '\n';
168 return EXIT_FAILURE;
169 }
170 if (UninitializedBehaviorName == "zero") {
172 } else if (UninitializedBehaviorName == "random") {
174 } else if (UninitializedBehaviorName == "fail") {
176 } else {
177 llvm::errs() << "unknown uninitialized behavior: " << UninitializedBehaviorName << '\n';
178 return EXIT_FAILURE;
179 }
180 if (UninitializedSeed.getNumOccurrences() > 0) {
181 options.randomSeed = UninitializedSeed;
182 }
183 options.inlineIncludes = true;
184 options.dumpJITCore = DumpJITCore;
185 options.dumpJITLLVM = DumpJITLLVM;
186 if (WtnsOutputFilename.getNumOccurrences() > 0) {
187 if (WtnsOutputFilename == "-") {
188 llvm::errs() << "--output-wtns does not support stdout; specify an output file\n";
189 return EXIT_FAILURE;
190 }
191 if (OutputScopeName.getNumOccurrences() > 0 && OutputScopeName != "full-witness") {
192 llvm::errs() << "--output-wtns conflicts with --output-scope=" << OutputScopeName
193 << "; use --output-scope=full-witness\n";
194 return EXIT_FAILURE;
195 }
196 // WTNS contains every R1CS wire, including ordinary private felt members.
198 }
199
200 auto result = llzk::witgen::runWitgen(*moduleOp, *parsed, options);
201 if (!result) {
202 llvm::errs() << "llzk-witgen error: " << llvm::toString(result.takeError()) << '\n';
203 return EXIT_FAILURE;
204 }
205
206 if (WtnsOutputFilename.getNumOccurrences() > 0) {
207 // The WTNS header records the field modulus and element width. Re-read the
208 // unique field from the preprocessed module so the header and generated
209 // values necessarily describe the same field.
210 llzk::FieldSet fields;
211 if (failed(llzk::collectFields(moduleOp->getOperation(), fields)) || fields.size() != 1) {
212 llvm::errs() << "llzk-witgen error: .wtns output requires exactly one field\n";
213 return EXIT_FAILURE;
214 }
215 const llzk::Field &field = (*fields.begin()).get();
216 if (auto error = llzk::witgen::writeWtns(*moduleOp, *result, field, WtnsOutputFilename)) {
217 llvm::errs() << "llzk-witgen error: " << llvm::toString(std::move(error)) << '\n';
218 return EXIT_FAILURE;
219 }
220 }
221
222 if (CheckOutputFilename.getNumOccurrences() > 0) {
223 auto expectedBuffer = llvm::MemoryBuffer::getFileOrSTDIN(CheckOutputFilename);
224 if (!expectedBuffer) {
225 llvm::errs() << expectedBuffer.getError().message() << '\n';
226 return EXIT_FAILURE;
227 }
228
229 auto expected = llvm::json::parse(expectedBuffer.get()->getBuffer());
230 if (!expected) {
231 llvm::errs() << "failed to parse expected JSON output: "
232 << llvm::toString(expected.takeError()) << '\n';
233 return EXIT_FAILURE;
234 }
235
236 llvm::SmallVector<llzk::witgen::JSONMismatch> mismatches;
237 llzk::witgen::diffJSON(*expected, *result, mismatches);
238 if (!mismatches.empty()) {
239 llvm::errs() << "llzk-witgen output mismatch:\n";
240 llzk::witgen::printJSONMismatches(llvm::errs(), mismatches);
241 return EXIT_FAILURE;
242 }
243
244 llvm::outs() << "output matched expected JSON\n";
245 return EXIT_SUCCESS;
246 }
247
248 llvm::outs() << llvm::formatv("{0:2}", *result) << '\n';
249 return EXIT_SUCCESS;
250}
251
252int main(int argc, char **argv) noexcept {
253 try {
254 return runMain(argc, argv);
255 } catch (const std::exception &ex) {
256 llvm::errs() << "llzk-witgen: unhandled exception: " << ex.what() << '\n';
257 } catch (...) {
258 llvm::errs() << "llzk-witgen: unhandled non-standard exception\n";
259 }
260 return EXIT_FAILURE;
261}
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) noexcept
Declares dialect and pass registration functions for core LLZK dialects.
@ 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:478
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:234
void printJSONMismatches(llvm::raw_ostream &os, llvm::ArrayRef< JSONMismatch > mismatches)
Render one human-readable mismatch report.
Definition JSON.cpp:509
void registerDialects(mlir::DialectRegistry &registry)
Register core LLZK dialects, the MLIR arith, scf, and smt dialects, and verification extensions in re...
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.
void registerInliningExtensions(DialectRegistry &registry)
Configure one llzk-witgen execution.
UninitializedBehavior uninitializedBehavior
std::optional< uint64_t > randomSeed