LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
LLZKLayout.cpp
Go to the documentation of this file.
1//===-- LLZKLayout.cpp ----------------------------------------------------===//
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
11
20#include "llzk/Util/Compare.h"
22#include "llzk/Util/Walk.h"
23
24#include <mlir/IR/Builders.h>
25#include <mlir/IR/SymbolTable.h>
26
27#include <llvm/ADT/DenseMap.h>
28#include <llvm/ADT/DenseSet.h>
29#include <llvm/ADT/STLExtras.h>
30#include <llvm/Support/raw_ostream.h>
31
32#include <cassert>
33
34using namespace mlir;
35using namespace llzk::polymorphic;
36
37namespace {
38
41bool isValidFamilyEntry(TypeAttr type, ArrayAttr indices, IntegerAttr id) {
42 if (!type || !isa<llzk::component::StructType>(type.getValue()) || !indices || !id ||
43 !id.getValue().isSignedIntN(64) || id.getInt() < 0) {
44 return false;
45 }
46 return llvm::all_of(indices, [](Attribute attr) {
47 auto index = dyn_cast<IntegerAttr>(attr);
48 return index && index.getType().isIndex() && index.getInt() >= 0;
49 });
50}
51
53class LayoutBuilder {
54public:
55 explicit LayoutBuilder(ModuleOp module) : builder(module.getContext()) {}
56
57 FailureOr<llzk::LLZKLayout> build(ModuleOp module) {
58 auto mainType = llzk::getMainInstanceType(module);
59 if (failed(mainType)) {
60 return failure();
61 }
62 if (*mainType && mainType->getParams() && !mainType->getParams().empty()) {
63 return module.emitError("LLZK layout requires a monomorphized llzk.main");
64 }
65 auto main = llzk::getMainInstanceDef(tables, module);
66 if (failed(main)) {
67 return failure();
68 }
69 if (!*main) {
70 return module.emitError("LLZK layout requires a concrete llzk.main");
71 }
72 if (main->viaInclude()) {
73 return module.emitError("inline includes before exporting an LLZK layout");
74 }
75
76 auto root = llzk::getTopRootModule(module);
77 if (failed(root)) {
78 return failure();
79 }
80 specializations.emplace(*root);
81
82 auto constrain = main->get().getConstrainFuncOp();
83 if (!constrain) {
84 return main->get().emitError("LLZK layout requires a main constrain function");
85 }
86
87 SmallVector<Attribute> path;
88 // Arguments to main's constrain come before the member defs in the layout.
89 layout.argumentNames.resize(constrain.getNumArguments());
90 for (unsigned index = 1; index < constrain.getNumArguments(); ++index) {
91 if (auto name = constrain.getArgNameAttr(index)) {
92 layout.argumentNames[index] = *name;
93 }
94 path.assign(
95 {builder.getStringAttr(llzk::LAYOUT_ARGUMENT_ROOT), builder.getI64IntegerAttr(index)}
96 );
97 if (failed(visitType(constrain.getFunctionType().getInput(index), constrain, path, true))) {
98 return failure();
99 }
100 }
101
102 path.assign({builder.getStringAttr(llzk::LAYOUT_MAIN_ROOT)});
103 if (failed(visitStruct(main->get(), path, true))) {
104 return failure();
105 }
106
107 return std::move(layout);
108 }
109
110private:
111 LogicalResult visitStruct(
112 llzk::component::StructDefOp definition, SmallVectorImpl<Attribute> &path, bool isMain = false
113 ) {
115 return definition.emitError("LLZK layout requires monomorphized struct types");
116 }
117 if (!activeStructs.insert(definition).second) {
118 return definition.emitError("recursive struct storage has no finite LLZK layout");
119 }
120 for (auto member : definition.getMemberDefs()) {
121 path.push_back(member.getSymNameAttr());
122 bool isSignal = member.getSignal() || (isMain && member.hasPublicAttr());
123 if (failed(visitType(member.getType(), member, path, isSignal))) {
124 return failure();
125 }
126 path.pop_back();
127 }
128 activeStructs.erase(definition);
129 return success();
130 }
131
134 LogicalResult loadFamily(Operation *site) {
135 if (families.contains(site)) {
136 return success();
137 }
138 auto &entries = families[site];
139 Attribute metadata = site->getAttr(FAMILY_ATTR);
140 if (!metadata) {
141 return success();
142 }
143 auto records = dyn_cast<ArrayAttr>(metadata);
144 if (!records) {
145 return site->emitError("invalid poly.family metadata for LLZK layout");
146 }
147 for (Attribute record : records) {
148 auto dict = dyn_cast<DictionaryAttr>(record);
149 auto type = dict ? dict.getAs<TypeAttr>(FAMILY_TYPE_KEY) : TypeAttr();
150 auto indices = dict ? dict.getAs<ArrayAttr>(FAMILY_INDICES_KEY) : ArrayAttr();
151 auto id = dict ? dict.getAs<IntegerAttr>(FAMILY_SPECIALIZATION_KEY) : IntegerAttr();
152 if (!isValidFamilyEntry(type, indices, id)) {
153 return site->emitError("invalid poly.family entry for LLZK layout");
154 }
155 if (!entries.try_emplace(std::make_pair(type.getValue(), indices), id.getInt()).second) {
156 return site->emitError("duplicate poly.family entry for LLZK layout");
157 }
158 }
159 return success();
160 }
161
162 LogicalResult visitType(
163 Type type, Operation *site, SmallVectorImpl<Attribute> &path, bool isSignal,
164 ArrayRef<int64_t> indices = {}
165 ) {
166 // Unmarked expressions are not signals, but component storage can contain
167 // members with their own signal annotations.
168 if (!isSignal && !walkContains<llzk::component::StructType>(type)) {
169 return success();
170 }
171 if (isa<llzk::felt::FeltType>(type)) {
172 auto signalPath = builder.getArrayAttr(path);
173 layout.signalIds.try_emplace(signalPath, layout.signalPaths.size());
174 layout.signalPaths.push_back(signalPath);
175 return success();
176 }
177 if (auto array = dyn_cast<llzk::array::ArrayType>(type)) {
178 for (int64_t size : array.getShape()) {
179 if (size < 0) {
180 return site->emitError("LLZK layout requires concrete array dimensions");
181 }
182 }
183 SmallVector<int64_t> elementIndices;
184 return visitArray(array, site, path, isSignal, elementIndices, 0);
185 }
186 if (auto pod = dyn_cast<llzk::pod::PodType>(type)) {
187 for (auto record : pod.getRecords()) {
188 path.push_back(record.getName());
189 if (failed(visitType(record.getType(), site, path, isSignal, indices))) {
190 return failure();
191 }
192 path.pop_back();
193 }
194 return success();
195 }
196 if (auto structure = dyn_cast<llzk::component::StructType>(type)) {
197 if (auto params = structure.getParams(); params && !params.empty()) {
198 SmallVector<Attribute> indexAttrs;
199 for (int64_t index : indices) {
200 indexAttrs.push_back(builder.getIndexAttr(index));
201 }
202 if (failed(loadFamily(site))) {
203 return failure();
204 }
205 auto found = families[site].find({structure, builder.getArrayAttr(indexAttrs)});
206 if (found == families[site].end()) {
207 return site->emitError("missing concrete struct family element for LLZK layout");
208 }
209 auto definition =
210 specializations->resolve(tables, structure, found->second, site, "LLZK layout");
211 if (failed(definition)) {
212 return failure();
213 }
214 return visitStruct(*definition, path);
215 }
216 auto definition = structure.getDefinition(tables, site);
217 if (failed(definition)) {
218 return failure();
219 }
220 if (!*definition) {
221 return site->emitError() << "cannot resolve struct type " << structure
222 << " for LLZK layout";
223 }
224 if (definition->viaInclude()) {
225 return site->emitError("inline includes before exporting an LLZK layout");
226 }
228 return site->emitError("LLZK layout requires monomorphized struct types");
229 }
230 return visitStruct(definition->get(), path);
231 }
232 if (type.isIndex() || isa<IntegerType, llzk::string::StringType>(type)) {
233 return success();
234 }
235 return site->emitError() << "unsupported or unresolved type " << type << " in LLZK layout";
236 }
237
238 LogicalResult visitArray(
239 llzk::array::ArrayType array, Operation *site, SmallVectorImpl<Attribute> &path,
240 bool isSignal, SmallVectorImpl<int64_t> &indices, unsigned dimension
241 ) {
242 if (dimension == array.getRank()) {
243 return visitType(array.getElementType(), site, path, isSignal, indices);
244 }
245 for (int64_t index = 0; index < array.getShape()[dimension]; ++index) {
246 path.push_back(builder.getIndexAttr(index));
247 indices.push_back(index);
248 if (failed(visitArray(array, site, path, isSignal, indices, dimension + 1))) {
249 return failure();
250 }
251 path.pop_back();
252 indices.pop_back();
253 }
254 return success();
255 }
256
257 Builder builder;
258 SymbolTableCollection tables;
260 std::optional<StructSpecializationResolver> specializations;
261 llvm::DenseMap<Operation *, llvm::DenseMap<std::pair<Type, ArrayAttr>, int64_t>> families;
262 llvm::DenseSet<Operation *> activeStructs;
263 llzk::LLZKLayout layout;
264};
265
267void printPath(ArrayAttr path, ArrayRef<StringAttr> names, llvm::raw_ostream &output) {
268 // buildLLZKLayout prefixes paths with main, or arg followed by an argument position.
269 assert(path && !path.empty() && isa<StringAttr>(path[0]) && "expected a layout path root");
270 auto root = cast<StringAttr>(path[0]).getValue();
271 bool isMain = root == llzk::LAYOUT_MAIN_ROOT;
272 assert((isMain || root == llzk::LAYOUT_ARGUMENT_ROOT) && "unexpected layout path root");
273 if (isMain) {
274 output << llzk::LAYOUT_MAIN_ROOT;
275 } else {
276 assert(path.size() >= 2 && isa<IntegerAttr>(path[1]) && "expected an argument position");
277 auto argument = cast<IntegerAttr>(path[1]).getInt();
278 assert(argument > 0 && "self is excluded from the layout");
279 if (llzk::checkedCast<size_t>(argument) < names.size() && names[argument]) {
280 output << llzk::LAYOUT_ARGUMENT_ROOT << '[';
281 names[argument].print(output);
282 output << ']';
283 } else {
284 output << llzk::LAYOUT_ARGUMENT_ROOT << argument;
285 }
286 }
287 for (Attribute segment : path.getValue().drop_front(isMain ? 1 : 2)) {
288 output << '[';
289 if (auto name = dyn_cast<StringAttr>(segment)) {
290 name.print(output);
291 } else {
292 output << cast<IntegerAttr>(segment).getInt();
293 }
294 output << ']';
295 }
296}
297
298} // namespace
299
300FailureOr<llzk::LLZKLayout> llzk::buildLLZKLayout(ModuleOp module) {
301 return LayoutBuilder(module).build(module);
302}
303
304std::optional<uint64_t> llzk::LLZKLayout::getSignalId(ArrayAttr path) const {
305 if (!path) {
306 return std::nullopt;
307 }
308 auto found = signalIds.find(path);
309 if (found == signalIds.end()) {
310 return std::nullopt;
311 }
312 return found->second;
313}
314
315void llzk::printLLZKLayout(const LLZKLayout &layout, llvm::raw_ostream &output) {
316 output << "# LLZK layout map v1\n# signals\n";
317 for (auto [id, path] : llvm::enumerate(layout.signalPaths)) {
318 output << "signal " << id << '\t';
319 printPath(path, layout.argumentNames, output);
320 output << '\n';
321 }
322}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for and distribution as defined by Sections through of this document Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License Legal Entity shall mean the union of the acting entity and all other entities that control are controlled by or are under common control with that entity For the purposes of this definition control direct or to cause the direction or management of such whether by contract or including but not limited to software source documentation and configuration files Object form shall mean any form resulting from mechanical transformation or translation of a Source including but not limited to compiled object generated and conversions to other media types Work shall mean the work of whether in Source or Object made available under the as indicated by a copyright notice that is included in or attached to the whether in Source or Object that is based or other modifications as a an original work of authorship For the purposes of this Derivative Works shall not include works that remain separable or merely the Work and Derivative Works thereof Contribution shall mean any work of including the original version of the Work and any modifications or additions to that Work or Derivative Works that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner For the purposes of this definition
Definition LICENSE.txt:53
within a display generated by the Derivative if and wherever such third party notices normally appear The contents of the NOTICE file are for informational purposes only and do not modify the License You may add Your own attribution notices within Derivative Works that You alongside or as an addendum to the NOTICE text from the provided that such additional attribution notices cannot be construed as modifying the License You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for or distribution of Your or for any such Derivative Works as a provided Your and distribution of the Work otherwise complies with the conditions stated in this License Submission of Contributions Unless You explicitly state any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this without any additional terms or conditions Notwithstanding the nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions Trademarks This License does not grant permission to use the trade names
Definition LICENSE.txt:139
::llvm::ArrayRef< int64_t > getShape() const
::mlir::Type getElementType() const
int main(int argc, char **argv)
constexpr llvm::StringLiteral FAMILY_ATTR
Definition Ops.h:40
constexpr llvm::StringLiteral FAMILY_SPECIALIZATION_KEY
Definition Ops.h:43
constexpr llvm::StringLiteral FAMILY_INDICES_KEY
Definition Ops.h:42
bool isInTemplate(Operation *op)
Definition Ops.cpp:34
constexpr llvm::StringLiteral FAMILY_TYPE_KEY
The original affine-parameterized struct family, paired with element indices and a concrete specializ...
Definition Ops.h:46
void printLLZKLayout(const LLZKLayout &layout, llvm::raw_ostream &output)
Print the logical signal section of the .llzk-layout format.
FailureOr< StructType > getMainInstanceType(Operation *lookupFrom)
mlir::FailureOr< LLZKLayout > buildLLZKLayout(mlir::ModuleOp module)
Enumerate the signal leaves of llzk.main and its constrain arguments.
constexpr T checkedCast(U u) noexcept
Definition Compare.h:94
FailureOr< ModuleOp > getTopRootModule(Operation *from)
FailureOr< SymbolLookupResult< StructDefOp > > getMainInstanceDef(SymbolTableCollection &symbolTable, Operation *lookupFrom)
constexpr char LAYOUT_ARGUMENT_ROOT[]
Structural path root for a constrain argument, followed by its position.
Definition LLZKLayout.h:31
constexpr char LAYOUT_MAIN_ROOT[]
Structural path root for storage in the main instance.
Definition LLZKLayout.h:29
Logical felt-signal paths in a monomorphized LLZK circuit.
Definition LLZKLayout.h:45
llvm::SmallVector< mlir::ArrayAttr > signalPaths
Structural paths indexed by logical signal ID.
Definition LLZKLayout.h:47
llvm::SmallVector< mlir::StringAttr > argumentNames
Optional display names indexed by constrain argument position.
Definition LLZKLayout.h:52
std::optional< uint64_t > getSignalId(mlir::ArrayAttr path) const
Look up a structural path in this layout.
llvm::DenseMap< mlir::ArrayAttr, uint64_t > signalIds
Reverse mapping from structural storage paths to logical signal IDs.
Definition LLZKLayout.h:49