LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
WitnessSelection.cpp
Go to the documentation of this file.
1//===-- WitnessSelection.cpp ---------------------------- -------*- 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 "WitnessSelection.h"
11
16
17#include <mlir/IR/Operation.h>
18
19using namespace mlir;
20
21namespace llzk::witgen {
22namespace {
23
25static FailureOr<bool>
26typeContainsSignals(Type type, SymbolTableCollection &tables, Operation *origin);
27
29static FailureOr<bool> structContainsSignals(
30 component::StructDefOp def, SymbolTableCollection &tables, Operation *origin
31) {
32 for (component::MemberDefOp member : def.getMemberDefs()) {
33 if (memberIsSignal(def, member)) {
34 return true;
35 }
36 auto nested = typeContainsSignals(member.getType(), tables, origin);
37 if (failed(nested)) {
38 return failure();
39 }
40 if (*nested) {
41 return true;
42 }
43 }
44 return false;
45}
46
48static FailureOr<bool>
49typeContainsSignals(Type type, SymbolTableCollection &tables, Operation *origin) {
50 if (auto structType = dyn_cast<component::StructType>(type)) {
51 auto defLookup = structType.getDefinition(tables, origin);
52 if (failed(defLookup)) {
53 return failure();
54 }
55 return structContainsSignals(defLookup->get(), tables, origin);
56 }
57 return false;
58}
59
61static LogicalResult appendSignalLeafBindings(
62 Type type, ArrayRef<std::string> prefix, SmallVectorImpl<OutputBinding> &out, Operation *origin
63) {
64 if (isa<felt::FeltType, array::ArrayType>(type)) {
65 out.push_back(
66 OutputBinding {llvm::SmallVector<std::string>(prefix.begin(), prefix.end()), type}
67 );
68 return success();
69 }
70
71 if (auto podType = dyn_cast<pod::PodType>(type)) {
72 for (pod::RecordAttr record : podType.getRecords()) {
73 llvm::SmallVector<std::string> path(prefix.begin(), prefix.end());
74 path.push_back(record.getName().getValue().str());
75 if (failed(appendSignalLeafBindings(record.getType(), path, out, origin))) {
76 return failure();
77 }
78 }
79 return success();
80 }
81
82 origin->emitError("signal members in llzk-witgen must be felts, felt arrays, or PODs of felts");
83 return failure();
84}
85
87static LogicalResult appendStructSignalBindings(
88 component::StructDefOp def, SymbolTableCollection &tables, Operation *origin,
89 SmallVectorImpl<OutputBinding> &out, bool includePrivateMainFelts,
90 ArrayRef<std::string> prefix = {}
91) {
92 for (component::MemberDefOp member : def.getMemberDefs()) {
93 llvm::SmallVector<std::string> path(prefix.begin(), prefix.end());
94 path.push_back(member.getSymName().str());
95
96 bool isR1CSMember =
97 includePrivateMainFelts && def.isMainComponent() && isa<felt::FeltType>(member.getType());
98 if (memberIsSignal(def, member) || isR1CSMember) {
99 if (failed(appendSignalLeafBindings(member.getType(), path, out, origin))) {
100 return failure();
101 }
102 continue;
103 }
104
105 auto nested = typeContainsSignals(member.getType(), tables, origin);
106 if (failed(nested)) {
107 return failure();
108 }
109 if (!*nested) {
110 continue;
111 }
112
113 auto structType = dyn_cast<component::StructType>(member.getType());
114 if (!structType) {
115 member.emitError("non-struct signal container is unsupported in llzk-witgen");
116 return failure();
117 }
118 auto defLookup = structType.getDefinition(tables, origin);
119 if (failed(defLookup)) {
120 return failure();
121 }
122 if (failed(appendStructSignalBindings(
123 defLookup->get(), tables, origin, out, includePrivateMainFelts, path
124 ))) {
125 return failure();
126 }
127 }
128 return success();
129}
130
132static void
133insertLeafJSON(llvm::json::Object &root, ArrayRef<std::string> path, llvm::json::Value value) {
134 if (path.empty()) {
135 return;
136 }
137 if (path.size() == 1) {
138 root[path.front()] = std::move(value);
139 return;
140 }
141
142 llvm::json::Value *slot = &root[path.front()];
143 if (!slot->getAsObject()) {
144 *slot = llvm::json::Object();
145 }
146 insertLeafJSON(*slot->getAsObject(), path.drop_front(), std::move(value));
147}
148
149} // namespace
150
153 return member.getSignal() || (owner.isMainComponent() && member.hasPublicAttr());
154}
155
157llvm::SmallVector<InputBinding> collectInputBindings(function::FuncDefOp computeFunc) {
158 llvm::SmallVector<InputBinding> bindings;
159 bindings.reserve(computeFunc.getNumArguments());
160 for (unsigned i = 0; i < computeFunc.getNumArguments(); ++i) {
161 std::string name;
162 if (std::optional<StringAttr> argName = computeFunc.getArgNameAttr(i)) {
163 name = argName->getValue().str();
164 } else {
165 name = "arg" + std::to_string(i);
166 }
167 bindings.push_back(InputBinding {std::move(name), computeFunc.getArgumentTypes()[i], i});
168 }
169 return bindings;
170}
171
173FailureOr<llvm::SmallVector<OutputBinding>> collectOutputBindings(
174 component::StructDefOp mainDef, SymbolTableCollection &tables, Operation *origin,
175 OutputScope scope
176) {
177 llvm::SmallVector<OutputBinding> bindings;
178 if (scope == OutputScope::Public) {
179 for (component::MemberDefOp member : mainDef.getMemberDefs()) {
180 if (!member.hasPublicAttr()) {
181 continue;
182 }
183 bindings.push_back(OutputBinding {{member.getSymName().str()}, member.getType()});
184 }
185 return bindings;
186 }
187
188 if (failed(appendStructSignalBindings(
189 mainDef, tables, origin, bindings, scope == OutputScope::R1CSWitness
190 ))) {
191 return failure();
192 }
193 return bindings;
194}
195
197llvm::json::Value buildSignalsJSONObject(
198 ArrayRef<OutputBinding> bindings, ArrayRef<llvm::json::Value> serializedLeaves
199) {
200 llvm::json::Object result;
201 for (auto [binding, leaf] : llvm::zip(bindings, serializedLeaves)) {
202 insertLeafJSON(result, binding.path, llvm::json::Value(leaf));
203 }
204 return llvm::json::Value(std::move(result));
205}
206
207} // namespace llzk::witgen
bool hasPublicAttr()
Returns whether this member is a public output.
Definition Ops.h.inc:463
::std::vector< MemberDefOp > getMemberDefs()
Get all MemberDefOp in this structure.
Definition Ops.cpp:454
bool isMainComponent()
Return true iff this struct.def is the main struct. See llzk::MAIN_ATTR_NAME.
Definition Ops.cpp:476
::llvm::ArrayRef<::mlir::Type > getArgumentTypes()
Required by FunctionOpInterface.
Definition Ops.h.inc:883
::std::optional<::mlir::StringAttr > getArgNameAttr(unsigned index)
Return the function.arg_name attribute for the argument at the given index.
Definition Ops.cpp:318
llvm::SmallVector< InputBinding > collectInputBindings(function::FuncDefOp computeFunc)
Collect stable JSON bindings for the main compute inputs.
OutputScope
Select the JSON scope emitted by llzk-witgen.
@ Public
Emit only public outputs.
@ R1CSWitness
Emit inputs plus every scalar main member needed to construct an R1CS witness.
FailureOr< llvm::SmallVector< OutputBinding > > collectOutputBindings(component::StructDefOp mainDef, SymbolTableCollection &tables, Operation *origin, OutputScope scope)
Collect the selected output bindings for the requested scope.
llvm::json::Value buildSignalsJSONObject(ArrayRef< OutputBinding > bindings, ArrayRef< llvm::json::Value > serializedLeaves)
Assemble a nested JSON object from selected witness leaves.
bool memberIsSignal(component::StructDefOp owner, component::MemberDefOp member)
Return true iff the member is considered a witness signal.
Describe one JSON-visible main input binding.
Describe one selected witness output leaf.