26#include <mlir/Conversion/AffineToStandard/AffineToStandard.h>
27#include <mlir/Conversion/ArithToLLVM/ArithToLLVM.h>
28#include <mlir/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.h>
29#include <mlir/Conversion/FuncToLLVM/ConvertFuncToLLVM.h>
30#include <mlir/Conversion/IndexToLLVM/IndexToLLVM.h>
31#include <mlir/Conversion/MemRefToLLVM/MemRefToLLVM.h>
32#include <mlir/Conversion/Passes.h>
33#include <mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h>
34#include <mlir/Conversion/UBToLLVM/UBToLLVM.h>
35#include <mlir/Conversion/VectorToLLVM/ConvertVectorToLLVM.h>
36#include <mlir/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.h>
37#include <mlir/Dialect/MemRef/Transforms/Passes.h>
38#include <mlir/Dialect/Utils/IndexingUtils.h>
39#include <mlir/ExecutionEngine/CRunnerUtils.h>
40#include <mlir/ExecutionEngine/ExecutionEngine.h>
41#include <mlir/Pass/PassManager.h>
42#include <mlir/Target/LLVMIR/Dialect/All.h>
43#include <mlir/Target/LLVMIR/Dialect/Builtin/BuiltinToLLVMIRTranslation.h>
44#include <mlir/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.h>
45#include <mlir/Transforms/Passes.h>
47#include <llvm/ADT/APInt.h>
48#include <llvm/Support/Endian.h>
49#include <llvm/Support/MathExtras.h>
50#include <llvm/Support/TargetSelect.h>
51#include <llvm/Support/raw_ostream.h>
64[[noreturn]]
static void abortWithFlushedStdout() {
73 unsigned feltBitWidth = 0;
75 std::vector<int64_t> shape;
76 std::vector<int64_t> strides;
77 std::vector<uint8_t> descriptor;
78 std::vector<uint8_t> storage;
82static size_t getElementBytes(
unsigned bitWidth) {
return (bitWidth + 7U) / 8U; }
85static llvm::Expected<std::vector<int64_t>> getBoundaryShape(Type type) {
86 if (isa<felt::FeltType>(type)) {
87 return std::vector<int64_t> {1};
89 if (
auto arrayType = dyn_cast<array::ArrayType>(type)) {
90 if (!isa<felt::FeltType>(arrayType.getElementType())) {
92 "execution-engine backend only supports arrays of felt values at the main boundary"
95 if (!arrayType.hasStaticShape()) {
97 "execution-engine backend only supports statically shaped arrays at the main boundary"
100 return std::vector<int64_t>(arrayType.getShape().begin(), arrayType.getShape().end());
103 "execution-engine backend only supports felt and array<...xfelt> main boundaries"
108static llvm::Expected<std::vector<int64_t>> computeStaticStrides(ArrayRef<int64_t> shape) {
109 for (int64_t dim : shape) {
112 return checkedDim.takeError();
116 auto strides = mlir::computeStrides(shape);
117 return std::vector<int64_t>(strides.begin(), strides.end());
121static llvm::Error buildDescriptor(BufferPack &buffer) {
124 return rank.takeError();
126 constexpr int64_t descriptorSize =
sizeof(
void *) * 2;
127 auto shapeAndStrideCount = llvm::DynamicAPInt(1) + *rank + *rank;
128 auto dynamicPart =
static_cast<int64_t
>(
sizeof(int64_t)) * shapeAndStrideCount;
129 auto totalSize = descriptorSize + dynamicPart;
130 auto checkedTotalSize =
checkedToSize(totalSize,
"execution-engine memref descriptor");
131 if (!checkedTotalSize) {
132 return checkedTotalSize.takeError();
134 buffer.descriptor.resize(*checkedTotalSize);
135 uint8_t *cursor = buffer.descriptor.data();
136 uint8_t *base = buffer.storage.data();
137 std::memcpy(cursor,
static_cast<const void *
>(&base),
sizeof(
void *));
138 cursor +=
sizeof(
void *);
139 std::memcpy(cursor,
static_cast<const void *
>(&base),
sizeof(
void *));
140 cursor +=
sizeof(
void *);
141 const int64_t offset = 0;
142 std::memcpy(cursor, &offset,
sizeof(int64_t));
143 cursor +=
sizeof(int64_t);
144 for (int64_t size : buffer.shape) {
145 std::memcpy(cursor, &size,
sizeof(int64_t));
146 cursor +=
sizeof(int64_t);
148 for (int64_t stride : buffer.strides) {
149 std::memcpy(cursor, &stride,
sizeof(int64_t));
150 cursor +=
sizeof(int64_t);
152 return llvm::Error::success();
156static llvm::Expected<BufferPack> createBufferPack(Type type,
const Field &field) {
157 auto shape = getBoundaryShape(type);
159 return shape.takeError();
162 buffer.originalType = type;
163 buffer.feltBitWidth = field.bitWidth();
164 buffer.elemBytes = getElementBytes(buffer.feltBitWidth);
165 buffer.shape = std::move(*shape);
166 auto strides = computeStaticStrides(buffer.shape);
168 return strides.takeError();
170 buffer.strides = std::move(*strides);
173 return elementCount.takeError();
175 bool overflow =
false;
176 size_t storageBytes = llvm::SaturatingMultiply(*elementCount, buffer.elemBytes, &overflow);
178 return makeError(
"execution-engine buffer storage would overflow size_t");
180 buffer.storage.resize(storageBytes);
181 if (
auto error = buildDescriptor(buffer)) {
182 return std::move(error);
189storeElement(BufferPack &buffer,
size_t flatIndex,
const llvm::DynamicAPInt &value) {
190 bool overflow =
false;
191 size_t byteOffset = llvm::SaturatingMultiply(flatIndex, buffer.elemBytes, &overflow);
193 return makeError(
"execution-engine buffer store offset would overflow size_t");
197 return elemBytesU.takeError();
201 return raw.takeError();
203 llvm::StoreIntToMemory(*raw, buffer.storage.data() + byteOffset, *elemBytesU);
204 return llvm::Error::success();
208static llvm::Expected<llvm::DynamicAPInt> loadElement(
const BufferPack &buffer,
size_t flatIndex) {
209 bool overflow =
false;
210 size_t byteOffset = llvm::SaturatingMultiply(flatIndex, buffer.elemBytes, &overflow);
212 return makeError(
"execution-engine buffer load offset would overflow size_t");
216 return elemBytesU.takeError();
218 llvm::APInt raw(buffer.feltBitWidth, 0);
219 llvm::LoadIntFromMemory(raw, buffer.storage.data() + byteOffset, *elemBytesU);
224static llvm::Error fillInputBuffer(BufferPack &buffer,
const WitnessVal &value) {
225 if (isa<felt::FeltType>(buffer.originalType)) {
226 auto feltValue =
asFelt(value);
228 return feltValue.takeError();
230 return storeElement(buffer, 0, *feltValue);
233 auto arrayValue =
asArray(value);
235 return arrayValue.takeError();
239 return elementCount.takeError();
241 if ((*arrayValue)->elements.size() != *elementCount) {
242 return makeError(
"input array element count mismatch");
244 for (
size_t i = 0; i < (*arrayValue)->elements.size(); ++i) {
245 auto feltValue =
asFelt((*arrayValue)->elements[i]);
247 return feltValue.takeError();
249 if (
auto err = storeElement(buffer, i, *feltValue)) {
253 return llvm::Error::success();
257static llvm::Expected<llvm::json::Value>
258feltElementToJSON(
const BufferPack &buffer,
size_t flatIndex,
const Field &field) {
259 auto element = loadElement(buffer, flatIndex);
261 return element.takeError();
267static llvm::Expected<llvm::json::Value> bufferToJSONArray(
268 const BufferPack &buffer,
const Field &field,
size_t dimIndex,
size_t flatOffset
270 if (dimIndex == SIZE_MAX) {
271 return makeError(
"execution-engine JSON output would overflow size_t");
275 return dimSize.takeError();
277 if (dimIndex + 1 == buffer.shape.size()) {
278 llvm::json::Array result;
279 for (
size_t i = 0; i < *dimSize; ++i) {
280 bool overflow =
false;
281 size_t elementOffset = llvm::SaturatingAdd(i, flatOffset, &overflow);
283 return makeError(
"execution-engine JSON output would overflow size_t");
285 auto element = feltElementToJSON(buffer, elementOffset, field);
287 return element.takeError();
289 result.push_back(*element);
291 return llvm::json::Value(std::move(result));
295 llvm::ArrayRef<int64_t>(buffer.shape).drop_front(dimIndex + 1),
"execution-engine JSON output"
298 return subArraySize.takeError();
301 llvm::json::Array result;
302 for (
size_t i = 0; i < *dimSize; ++i) {
303 bool overflow =
false;
304 size_t nextOffset = llvm::SaturatingMultiplyAdd(i, *subArraySize, flatOffset, &overflow);
306 return makeError(
"execution-engine JSON output would overflow size_t");
308 auto subArray = bufferToJSONArray(buffer, field, dimIndex + 1, nextOffset);
310 return subArray.takeError();
312 result.push_back(*subArray);
314 return llvm::json::Value(std::move(result));
318static llvm::Expected<llvm::json::Value>
319bufferToJSON(
const BufferPack &buffer,
const Field &field) {
320 if (isa<felt::FeltType>(buffer.originalType)) {
321 return feltElementToJSON(buffer, 0, field);
323 return bufferToJSONArray(buffer, field, 0, 0);
327static llvm::Expected<OwningOpRef<ModuleOp>> buildExecutionEngineModule(
330 OwningOpRef<ModuleOp> cloned = cast<ModuleOp>(moduleOp->clone());
331 PassManager pm(cloned->getContext());
334 if (failed(pm.run(*cloned))) {
335 return makeError(
"failed to lower LLZK compute IR to execution-engine core dialects");
341static llvm::Error finalizeExecutionEngineModule(ModuleOp moduleOp) {
342 PassManager pm(moduleOp.getContext());
343 pm.addPass(mlir::createCanonicalizerPass());
344 pm.addPass(mlir::createCSEPass());
345 pm.addPass(mlir::memref::createExpandStridedMetadataPass());
346 pm.addPass(mlir::createLowerAffinePass());
347 pm.addPass(mlir::createSCFToControlFlowPass());
348 pm.addPass(mlir::createCanonicalizerPass());
349 pm.addPass(mlir::createCSEPass());
350 pm.addPass(mlir::createConvertToLLVMPass());
351 pm.addPass(mlir::createReconcileUnrealizedCastsPass());
352 if (failed(pm.run(moduleOp))) {
353 return makeError(
"failed to lower execution-engine module to LLVM dialect");
355 return llvm::Error::success();
359static void maybeDumpModule(ModuleOp moduleOp,
bool enabled, llvm::StringRef title) {
363 llvm::errs() << title <<
":\n";
364 moduleOp.print(llvm::errs());
365 llvm::errs() <<
'\n';
372 ModuleOp moduleOp, SymbolTableCollection &tables,
const Field &field,
376 if (failed(mainDef) || !mainDef.value()) {
377 return makeError(
"module is missing a concrete llzk.main struct");
381 return makeError(
"main struct is missing @compute");
392 auto parsedArgs = [&]() -> llvm::Expected<llvm::SmallVector<WitnessVal>> {
393 llvm::SmallVector<WitnessVal> args;
394 const auto *jsonObject = input.getAsObject();
395 const auto *jsonArray = input.getAsArray();
396 if (!jsonObject && !jsonArray) {
397 return makeError(
"inputs JSON must be either an object or an array");
400 for (
unsigned i = 0; i < computeFunc.getNumArguments(); ++i) {
401 std::optional<StringAttr> argName = computeFunc.
getArgNameAttr(i);
403 return makeError(
"JSON object input requires function.arg_name on every main argument");
405 const llvm::json::Value *value = jsonObject->get(argName->getValue());
407 return makeError(llvm::Twine(
"missing JSON input field: ") + argName->getValue());
413 return parsed.takeError();
415 args.push_back(*parsed);
419 if (jsonArray->size() != computeFunc.getNumArguments()) {
420 return makeError(
"JSON positional input length does not match main compute arity");
422 for (
unsigned i = 0; i < computeFunc.getNumArguments(); ++i) {
424 &(*jsonArray)[i], computeFunc.
getArgumentTypes()[i], field, computeFunc.getOperation()
427 return parsed.takeError();
429 args.push_back(*parsed);
434 return parsedArgs.takeError();
439 mainDef->get(), tables, computeFunc.getOperation(), options.
outputScope
441 if (failed(outputs)) {
442 return makeError(
"failed to select witness outputs for execution-engine mode");
445 llvm::SmallVector<BufferPack> inputBuffers;
446 for (
auto [argType, parsed] : llvm::zip(computeFunc.
getArgumentTypes(), *parsedArgs)) {
447 auto buffer = createBufferPack(argType, field);
449 return buffer.takeError();
451 if (
auto err = fillInputBuffer(*buffer, parsed)) {
452 return std::move(err);
454 inputBuffers.push_back(std::move(*buffer));
457 llvm::SmallVector<BufferPack> outputBuffers;
459 auto buffer = createBufferPack(output.type, field);
461 return buffer.takeError();
463 outputBuffers.push_back(std::move(*buffer));
466 auto loweredModule = buildExecutionEngineModule(moduleOp, options.
outputScope, options);
467 if (!loweredModule) {
468 return loweredModule.takeError();
471 DialectRegistry registry;
473 mlir::LLVM::registerInlinerInterface(registry);
474 mlir::arith::registerConvertArithToLLVMInterface(registry);
475 mlir::cf::registerConvertControlFlowToLLVMInterface(registry);
476 mlir::registerConvertFuncToLLVMInterface(registry);
477 mlir::index::registerConvertIndexToLLVMInterface(registry);
478 mlir::registerConvertMemRefToLLVMInterface(registry);
479 mlir::ub::registerConvertUBToLLVMInterface(registry);
480 mlir::vector::registerConvertVectorToLLVMInterface(registry);
481 (*loweredModule)->getContext()->appendDialectRegistry(registry);
482 (*loweredModule)->getContext()->loadAllAvailableDialects();
484 maybeDumpModule(**loweredModule, options.
dumpJITCore,
"llzk-witgen JIT core");
485 if (
auto err = finalizeExecutionEngineModule(**loweredModule)) {
486 return std::move(err);
488 maybeDumpModule(**loweredModule, options.
dumpJITLLVM,
"llzk-witgen JIT LLVM");
490 llvm::InitializeNativeTarget();
491 llvm::InitializeNativeTargetAsmPrinter();
492 mlir::registerBuiltinDialectTranslation(*(*loweredModule)->getContext());
493 mlir::registerLLVMDialectTranslation(*(*loweredModule)->getContext());
495 auto maybeEngine = mlir::ExecutionEngine::create(loweredModule->get());
497 return maybeEngine.takeError();
499 (*maybeEngine)->registerSymbols([](llvm::orc::MangleAndInterner interner) {
500 llvm::orc::SymbolMap symbolMap;
501 symbolMap[interner(
"memrefCopy")] = {
502 llvm::orc::ExecutorAddr::fromPtr(&memrefCopy), llvm::JITSymbolFlags::Exported
504 symbolMap[interner(
"abort")] = {
505 llvm::orc::ExecutorAddr::fromPtr(&abortWithFlushedStdout), llvm::JITSymbolFlags::Exported
510 llvm::SmallVector<void *> descriptorPtrs;
511 descriptorPtrs.reserve(inputBuffers.size() + outputBuffers.size());
512 for (BufferPack &buffer : inputBuffers) {
513 descriptorPtrs.push_back(buffer.descriptor.data());
515 for (BufferPack &buffer : outputBuffers) {
516 descriptorPtrs.push_back(buffer.descriptor.data());
519 llvm::SmallVector<void *> packedArgs;
520 packedArgs.reserve(descriptorPtrs.size());
521 for (
void *&descriptorPtr : descriptorPtrs) {
522 packedArgs.push_back(
static_cast<void *
>(&descriptorPtr));
525 if (
auto err = (*maybeEngine)->invokePacked(
"_mlir_ciface___llzk_witgen_main", packedArgs)) {
526 return std::move(err);
529 llvm::SmallVector<llvm::json::Value> serializedOutputs;
530 serializedOutputs.reserve(outputBuffers.size());
531 for (
const BufferPack &buffer : outputBuffers) {
532 auto serialized = bufferToJSON(buffer, field);
534 return serialized.takeError();
536 serializedOutputs.push_back(*serialized);
546 return inputsJSON.takeError();
548 llvm::json::Object result;
549 result[
"inputs"] = llvm::json::Value(std::move(*inputsJSON));
551 return llvm::json::Value(std::move(result));
This file implements helpers for DynamicAPInt operations and conversions that LLVM does not provide.
Information about the prime finite field used for the interval analysis.
::llvm::ArrayRef<::mlir::Type > getArgumentTypes()
Required by FunctionOpInterface.
::std::optional<::mlir::StringAttr > getArgNameAttr(unsigned index)
Return the function.arg_name attribute for the argument at the given index.
Drive witness generation for the concrete llzk.main instance.
void setOutputScope(OutputScope newOutputScope)
Select which witness JSON scope this interpreter emits.
llvm::Expected< llvm::json::Value > runMainFromJSON(const llvm::json::Value &input)
Execute the main compute() function using JSON inputs.
llvm::Expected< llvm::json::Value > runWithExecutionEngine(ModuleOp moduleOp, SymbolTableCollection &tables, const Field &field, const llvm::json::Value &input, const WitgenOptions &options)
Execute witness generation through MLIR lowering and the LLVM execution engine.
llvm::Expected< T > checkedCast(U u)
llvm::Expected< llvm::json::Object > buildInputsJSONObject(ArrayRef< InputBinding > bindings, ArrayRef< WitnessVal > values, SymbolTableCollection &tables, Operation *origin)
Serialize named input values into a JSON object.
llvm::SmallVector< InputBinding > collectInputBindings(function::FuncDefOp computeFunc)
Collect stable JSON bindings for the main compute inputs.
std::mt19937_64 makeDefaultValueRng(const WitgenOptions &options)
Seed an RNG for random/default witness value materialization.
OutputScope
Select the JSON scope emitted by llzk-witgen.
@ Public
Emit only public outputs.
std::variant< std::monostate, bool, int64_t, llvm::DynamicAPInt, ArrayValueRef, PodValueRef, StructValueRef > WitnessVal
Runtime value representation used by the tool-local interpreter.
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.
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...
std::unique_ptr< Pass > createLowerComputeToCorePass(const WitgenOptions &options)
Create the pass that lowers supported LLZK compute IR into core MLIR dialects suitable for LLVM lower...
llvm::Expected< size_t > getStaticShapeElementCount(llvm::ArrayRef< int64_t > shape, llvm::StringRef context)
Return the static element count for one shape, rejecting dynamic sizes.
std::unique_ptr< Pass > createCreateWitgenEntryPass(OutputScope outputScope)
Create the pass that synthesizes the stable llzk-witgen JIT entry wrapper.
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.
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< ArrayValueRef > asArray(const WitnessVal &value)
Require an array value from the runtime variant.
Expected< size_t > checkedToSize(const DynamicAPInt &value, const Twine &context)
DynamicAPInt toDynamicAPInt(StringRef str)
FailureOr< SymbolLookupResult< StructDefOp > > getMainInstanceDef(SymbolTableCollection &symbolTable, Operation *lookupFrom)
Expected< APInt > checkedToAPInt(const DynamicAPInt &value, unsigned bitWidth, bool isSigned)
Describe one selected witness output leaf.
Configure one llzk-witgen execution.
UninitializedBehavior uninitializedBehavior