diff --git a/README.md b/README.md index fa89a62b..38d0a0b7 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,11 @@ Run all benchmarks and compare: make build-compare-benchmarks ``` +Run all programs and compare output memory/trace for Zig/Rust cairo-vm: +```bash +make build-compare-output +``` + Run all unit tests with test summary: diff --git a/build.zig.zon b/build.zig.zon index 6c1a1c83..2af1cd6c 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -14,8 +14,8 @@ .hash = "1220ab73fb7cc11b2308edc3364988e05efcddbcac31b707f55e6216d1b9c0da13f1", }, .starknet = .{ - .url = "https://github.com/StringNick/starknet-zig/archive/8cfb4286ffda4ad2781647c3d96b2aec8ccfeb32.zip", - .hash = "122026eaa24834fd2e2cc7e8b6c4eefb03dda08158a2844615f189758fa24d32fc44", + .url = "https://github.com/StringNick/starknet-zig/archive/d9e95579ce9f61a8acf42da03d9371af62925a9e.zip", + .hash = "12205f6b98ed2d3b99420d95f94a28d4e2f25f49001aca5db539d6a23e7b1840710e", }, }, } diff --git a/scripts/benchmarks.sh b/scripts/benchmarks.sh index f4011ad2..aa91a06e 100644 --- a/scripts/benchmarks.sh +++ b/scripts/benchmarks.sh @@ -15,5 +15,5 @@ for file in $(ls ${BENCH_DIR} | grep .cairo | sed -E 's/\.cairo//'); do echo "Running ${file} benchmark" hyperfine --show-output --warmup 2 \ -n "cairo-vm (Rust)" "${CAIRO_VM_CLI} ${BENCH_DIR}/${file}.json --memory_file /dev/null --trace_file /dev/null --proof_mode --layout all_cairo" \ - -n "cairo-vm (Zig)" "${ZIG_CLI} execute --filename ${BENCH_DIR}/${file}.json --enable-trace=true --output-memory=/dev/null --output-trace=/dev/null --proof-mode=true --layout all_cairo" + -n "cairo-vm (Zig)" "${ZIG_CLI} execute --filename ${BENCH_DIR}/${file}.json --memory-file=/dev/null --trace-file=/dev/null --proof-mode=true --layout all_cairo" done diff --git a/scripts/test_compare_output.sh b/scripts/test_compare_output.sh index 737133a6..2cfb48e4 100644 --- a/scripts/test_compare_output.sh +++ b/scripts/test_compare_output.sh @@ -22,6 +22,10 @@ ZIG_MEMORY_OUTPUT="./tmp/zig_memory.tmp" RUST_TRACE_OUTPUT="./tmp/rust_trace.tmp" ZIG_TRACE_OUTPUT="./tmp/zig_trace.tmp" +Red='\033[0;31m' # Red +Green='\033[0;32m' # Green +NC='\033[0m' # No Color + trap ctrl_c INT ctrl_c() { @@ -32,24 +36,24 @@ ctrl_c() { mkdir tmp for file in $(ls ${CAIRO_PROGRAMS_DIR} | grep .cairo | sed -E 's/\.cairo//'); do - echo "Compiling ${file} program..." + echo "${NC}Compiling ${file} program..." cairo-compile --cairo_path="${CAIRO_DIR}:" ${CAIRO_PROGRAMS_DIR}/${file}.cairo --output ${CAIRO_PROGRAMS_DIR}/${file}.json --proof_mode echo "Running ${file}" ${CAIRO_VM_CLI} ${CAIRO_PROGRAMS_DIR}/${file}.json --memory_file $RUST_MEMORY_OUTPUT --trace_file $RUST_TRACE_OUTPUT --proof_mode --layout all_cairo - ${ZIG_CLI} execute --filename ${CAIRO_PROGRAMS_DIR}/${file}.json --enable-trace=true --output-memory=$ZIG_MEMORY_OUTPUT --output-trace=$ZIG_TRACE_OUTPUT --proof-mode=true --layout all_cairo + ${ZIG_CLI} execute --filename ${CAIRO_PROGRAMS_DIR}/${file}.json --memory-file=$ZIG_MEMORY_OUTPUT --trace-file=$ZIG_TRACE_OUTPUT --proof-mode=true --layout=all_cairo if sameContents $RUST_TRACE_OUTPUT $ZIG_TRACE_OUTPUT; then - echo "Rust & Zig output trace is same" + echo "${Green}Rust & Zig output trace is same" else - echo "Zig have different output trace" + echo "${Red}Zig have different output trace" fi if sameContents $RUST_MEMORY_OUTPUT $ZIG_MEMORY_OUTPUT; then - echo "Rust & Zig memory output is same" + echo "${Green}Rust & Zig memory output is same" else - echo "Zig have different output memory" + echo "${Red}Zig have different output memory" fi done diff --git a/src/build_options.zig b/src/build_options.zig deleted file mode 100644 index cdda1b98..00000000 --- a/src/build_options.zig +++ /dev/null @@ -1,7 +0,0 @@ -/// Whether tracing should be disabled globally. This prevents the -/// user from enabling tracing via the command line but it might -/// improve performance slightly. -pub const trace_disable = false; -/// The initial capacity of the buffer responsible for gathering execution trace -/// data. -pub const trace_initial_capacity: usize = 4096; diff --git a/src/cmd/cmd.zig b/src/cmd/cmd.zig index 301b1244..26452580 100644 --- a/src/cmd/cmd.zig +++ b/src/cmd/cmd.zig @@ -9,21 +9,36 @@ const vm_core = @import("../vm/core.zig"); const RunContext = @import("../vm/run_context.zig").RunContext; const relocatable = @import("../vm/memory/relocatable.zig"); const Config = @import("../vm/config.zig").Config; -const build_options = @import("../build_options.zig"); const cairo_runner = @import("../vm/runners/cairo_runner.zig"); const CairoRunner = cairo_runner.CairoRunner; const ProgramJson = @import("../vm/types/programjson.zig").ProgramJson; const cairo_run = @import("../vm/cairo_run.zig"); +const layout_lib = @import("../vm/types/layout.zig"); +const errors = @import("../vm/error.zig"); // var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const global_allocator = std.heap.c_allocator; // Configuration settings for the CLI. -var config = Config{ - .proof_mode = false, - .enable_trace = false, +const Args = struct { + filename: []const u8 = undefined, + trace_file: ?[]const u8 = null, + print_output: bool = false, + entrypoint: []const u8 = "main", + memory_file: ?[]const u8 = null, + layout: []const u8 = layout_lib.LayoutName.plain.toString(), + proof_mode: bool = false, + secure_run: ?bool = null, + // require proof_mode=true + air_public_input: ?[]const u8 = null, + // requires proof_mode, trace_file, memory_file + air_private_input: ?[]const u8 = null, + + allow_missing_builtins: ?bool = null, }; +var cfg: Args = .{}; + /// Runs the Command-Line Interface application. /// /// This function initializes and executes the CLI app with the provided configuration @@ -38,29 +53,25 @@ pub fn run() !void { var r = try cli.AppRunner.init(global_allocator); // Command-line option for enabling proof mode. - const execute_proof_mode_option = cli.Option{ + const proof_mode = cli.Option{ // The full name of the option. .long_name = "proof-mode", // Description of the option's purpose. .help = "Whether to run in proof mode or not.", - // Short alias for the option. - .short_alias = 'p', // Reference to the proof mode configuration. - .value_ref = r.mkRef(&config.proof_mode), + .value_ref = r.mkRef(&cfg.proof_mode), // Indicates if the option is required. .required = false, }; // Command-line option for enabling secure run. - const execute_secure_run = cli.Option{ + const secure_rune = cli.Option{ // The full name of the option. .long_name = "secure-run", // Description of the option's purpose. - .help = "Whether to run in proof mode or not.", - // Short alias for the option. - .short_alias = 'p', + .help = "Whether to run in secure mode or not.", // Reference to the proof mode configuration. - .value_ref = r.mkRef(&config.proof_mode), + .value_ref = r.mkRef(&cfg.secure_run), // Indicates if the option is required. .required = false, }; @@ -71,24 +82,32 @@ pub fn run() !void { .long_name = "filename", // Description of the option's purpose. .help = "The location of the program to be evaluated.", - // Short alias for the option. - .short_alias = 'f', // Reference to the program filename. - .value_ref = r.mkRef(&config.filename), + .value_ref = r.mkRef(&cfg.filename), // Indicates if the option is required. .required = true, }; - // Command-line option for enabling trace mode. - const enable_trace = cli.Option{ + // Command-line option for specifying the filename. + const air_input_public = cli.Option{ // The full name of the option. - .long_name = "enable-trace", + .long_name = "air-input-public", // Description of the option's purpose. - .help = "Enable trace mode.", - // Short alias for the option. - .short_alias = 't', - // Reference to the trace mode configuration. - .value_ref = r.mkRef(&config.enable_trace), + .help = "The location where we wrote air input public.", + // Reference to the program filename. + .value_ref = r.mkRef(&cfg.air_public_input), + // Indicates if the option is required. + .required = false, + }; + + // Command-line option for specifying the filename. + const air_input_private = cli.Option{ + // The full name of the option. + .long_name = "air-input-private", + // Description of the option's purpose. + .help = "The location where we wrote air input private.", + // Reference to the program filename. + .value_ref = r.mkRef(&cfg.air_private_input), // Indicates if the option is required. .required = false, }; @@ -96,27 +115,49 @@ pub fn run() !void { // Command-line option for specifying the output trace file. const output_trace = cli.Option{ // The full name of the option. - .long_name = "output-trace", + .long_name = "trace-file", // Description of the option's purpose. .help = "File where the register execution cycles are written.", // Short alias for the option. .short_alias = 'o', // Reference to the output trace file. - .value_ref = r.mkRef(&config.output_trace), + .value_ref = r.mkRef(&cfg.trace_file), + // Indicates if the option is required. + .required = false, + }; + + // Command-line option for specifying print output or not. + const print_output = cli.Option{ + // The full name of the option. + .long_name = "print-output", + // Description of the option's purpose. + .help = "Do we need to print output or not", + // Reference to the output trace file. + .value_ref = r.mkRef(&cfg.print_output), + // Indicates if the option is required. + .required = false, + }; + + // Command-line option for specifying entrypoint of program. + const entrypoint = cli.Option{ + // The full name of the option. + .long_name = "entrypoint", + // Description of the option's purpose. + .help = "Specifying entrypoint of program", + // Reference to the output trace file. + .value_ref = r.mkRef(&cfg.entrypoint), // Indicates if the option is required. .required = false, }; // Command-line option for specifying the output memory file. - const output_memory = cli.Option{ + const memory_file = cli.Option{ // The full name of the option. - .long_name = "output-memory", + .long_name = "memory-file", // Description of the option's purpose. .help = "File where the memory post-execution is written.", - // Short alias for the option. - .short_alias = 'm', // Reference to the output memory file. - .value_ref = r.mkRef(&config.output_memory), + .value_ref = r.mkRef(&cfg.memory_file), // Indicates if the option is required. .required = false, }; @@ -127,10 +168,8 @@ pub fn run() !void { .long_name = "layout", // Description of the option's purpose. .help = "The memory layout to use.", - // Short alias for the option. - .short_alias = 'l', // Reference to the memory layout. - .value_ref = r.mkRef(&config.layout), + .value_ref = r.mkRef(&cfg.layout), // Indicates if the option is required. .required = false, }; @@ -169,13 +208,16 @@ pub fn run() !void { }, // Options for the subcommand. .options = &.{ - execute_proof_mode_option, + proof_mode, layout, - enable_trace, program_option, output_trace, - output_memory, - execute_secure_run, + secure_rune, + memory_file, + air_input_public, + air_input_private, + print_output, + entrypoint, }, // Action to be executed for the subcommand. .target = .{ .action = .{ .exec = execute } }, @@ -206,13 +248,137 @@ const UsageError = error{ /// Returns a `UsageError` if there's a misuse of the CLI, specifically if tracing is attempted /// while it's disabled in the build. fn execute() anyerror!void { - var arena = std.heap.ArenaAllocator.init(global_allocator); + try runProgram(global_allocator, cfg); +} + +fn runProgram(allocator: std.mem.Allocator, _cfg: Args) !void { + var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); - if (build_options.trace_disable and config.enable_trace) { - std.log.err("Tracing is disabled in this build.\n", .{}); - return UsageError.IncompatibleBuildOptions; + const trace_enabled = _cfg.trace_file != null or _cfg.air_public_input != null; + + const file = try std.fs.cwd().openFile(_cfg.filename, .{}); + defer file.close(); + + // Read the entire file content into a buffer using the provided allocator + const buffer = try file.readToEndAlloc( + arena.allocator(), + try file.getEndPos(), + ); + defer arena.allocator().free(buffer); + + var runner = try cairo_run.cairoRun( + arena.allocator(), + buffer, + .{ + .entrypoint = _cfg.entrypoint, + .trace_enabled = trace_enabled, + .relocate_mem = _cfg.memory_file != null or _cfg.air_public_input != null, + .layout = _cfg.layout, + .proof_mode = _cfg.proof_mode, + .secure_run = _cfg.secure_run, + .allow_missing_builtins = _cfg.allow_missing_builtins, + }, + @constCast(&.{}), + ); + defer runner.deinit(arena.allocator()); + defer runner.vm.segments.memory.deinitData(arena.allocator()); + + if (_cfg.print_output) { + var output_buffer = try std.ArrayList(u8).initCapacity(arena.allocator(), 100); + output_buffer.appendSliceAssumeCapacity("Program Output:\n"); + + try runner.vm.writeOutput(output_buffer.writer()); + } + + if (_cfg.trace_file) |trace_path| { + const relocated_trace = runner.relocated_trace orelse return errors.TraceError.TraceNotRelocated; + + var writer: std.io.BufferedWriter(3 * 1024 * 1024, std.fs.File.Writer) = .{ .unbuffered_writer = undefined }; + + const trace_file = try std.fs.cwd().createFile(trace_path, .{}); + defer trace_file.close(); + + writer.unbuffered_writer = trace_file.writer(); + + try cairo_run.writeEncodedTrace(relocated_trace, &writer); + + try writer.flush(); } - try cairo_run.runConfig(arena.allocator(), config); + if (_cfg.memory_file) |memory_path| { + var writer: std.io.BufferedWriter(5 * 1024 * 1024, std.fs.File.Writer) = .{ .unbuffered_writer = undefined }; + + const memory_file = try std.fs.cwd().createFile(memory_path, .{}); + defer memory_file.close(); + + writer.unbuffered_writer = memory_file.writer(); + + try cairo_run.writeEncodedMemory(runner.relocated_memory.items, &writer); + + try writer.flush(); + } + + if (_cfg.air_public_input) |file_path| { + var public_input = try runner.getAirPublicInput(); + defer public_input.deinit(); + + const public_input_json = try public_input.serialize(arena.allocator()); + defer arena.allocator().free(public_input_json); + + var air_file = try std.fs.cwd().createFile(file_path, .{}); + defer air_file.close(); + + try air_file.writeAll(public_input_json); + } +} + +test "RunOK" { + for ([_][]const u8{ + "plain", + // "small", + // "dex", + // "starknet", + // "starknet_with_keccak", + // "recursive_large_output", + "all_cairo", + // "all_solidity", + }) |layout| { + inline for ([_]bool{ false, true }) |memory_file| { + inline for ([_]bool{ false, true }) |_trace_file| { + inline for ([_]bool{ false, true }) |proof_mode| { + inline for ([_]bool{ false, true }) |print_out| { + inline for ([_]bool{ false, true }) |entrypoint| { + inline for ([_]bool{ false, true }) |air_input_public| { + var trace_file = _trace_file; + + var _cfg: Args = + .{ + .layout = layout, + .air_public_input = if (air_input_public) "/dev/null" else null, + .proof_mode = if (proof_mode) val: { + trace_file = true; + break :val true; + } else false, + .memory_file = if (memory_file) "/dev/null" else null, + .trace_file = if (trace_file) "/dev/null" else null, + .print_output = print_out, + .filename = "cairo_programs/proof_programs/fibonacci.json", + }; + + if (entrypoint) _cfg.entrypoint = "main"; + + runProgram( + std.testing.allocator, + _cfg, + ) catch |err| { + if (air_input_public and !proof_mode) return {} else return err; + }; + } + } + } + } + } + } + } } diff --git a/src/hint_processor/ec_utils.zig b/src/hint_processor/ec_utils.zig index 55018516..e822b682 100644 --- a/src/hint_processor/ec_utils.zig +++ b/src/hint_processor/ec_utils.zig @@ -118,13 +118,11 @@ fn randomEcPointSeeded(allocator: std.mem.Allocator, seed_bytes: []const u8) !st const x = std.mem.readInt(u256, &hash_buffer, .big); - // const y_coef = std.math.pow(i32, -1, seed[0] & 1); - // Calculate y if (recoverY(Felt252.fromInt(u256, x))) |y| { return .{ Felt252.fromInt(u256, x), - y, + if (seed[0] & 1 == 1) y.neg() else y, }; } } diff --git a/src/lib.zig b/src/lib.zig index 0f1eceef..f2b3b5c0 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -8,6 +8,7 @@ pub const vm = struct { pub usingnamespace @import("vm/run_context.zig"); pub usingnamespace @import("vm/trace_context.zig"); pub usingnamespace @import("vm/security.zig"); + pub usingnamespace @import("vm/air_input_public.zig"); pub usingnamespace @import("vm/types/program.zig"); pub usingnamespace @import("vm/types/programjson.zig"); pub usingnamespace @import("vm/types/pedersen_instance_def.zig"); @@ -48,8 +49,6 @@ pub const utils = struct { pub usingnamespace @import("utils/time.zig"); }; -pub const build_options = @import("build_options.zig"); - pub const hint_processor = struct { pub usingnamespace @import("hint_processor/hint_processor_def.zig"); pub usingnamespace @import("hint_processor/hint_processor_utils.zig"); diff --git a/src/tests.zig b/src/tests.zig index 2e24a01b..55d20936 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -1,8 +1,10 @@ const std = @import("std"); const lib = @import("lib.zig"); +const cmd = @import("cmd/cmd.zig"); // Automatically run all tests in files declared in the `lib.zig` file. test { std.testing.log_level = std.log.Level.err; std.testing.refAllDecls(lib); + std.testing.refAllDecls(cmd); } diff --git a/src/vm/air_input_public.zig b/src/vm/air_input_public.zig new file mode 100644 index 00000000..46770677 --- /dev/null +++ b/src/vm/air_input_public.zig @@ -0,0 +1,268 @@ +const std = @import("std"); +const Felt252 = @import("starknet").fields.Felt252; +const RelocatedTraceEntry = @import("trace_context.zig").RelocatedTraceEntry; +const HintProcessor = @import("../hint_processor/hint_processor_def.zig").CairoVMHintProcessor; + +const Config = @import("config.zig").Config; +const cairo_run = @import("cairo_run.zig"); +const cairo_runner = @import("runners/cairo_runner.zig"); + +const errors = @import("error.zig"); + +pub const PublicInput = struct { + const Self = @This(); + + layout: []const u8, + rc_min: isize, + rc_max: isize, + n_steps: usize, + + // we use struct here only for json serialization/deserialization abstraction + memory_segments: struct { + value: std.StringHashMap(MemorySegmentAddresses), + + pub fn jsonParse(allocator: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) !@This() { + if (.object_begin != try source.next()) return error.UnexpectedToken; + var result = std.StringHashMap(MemorySegmentAddresses).init(allocator); + errdefer result.deinit(); + + while (true) { + const name_token: ?std.json.Token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?); + const field_name = switch (name_token.?) { + inline .string, .allocated_string => |slice| slice, + .object_end => { // No more fields. + break; + }, + else => { + return error.UnexpectedToken; + }, + }; + + try result.put(field_name, try std.json.innerParse(MemorySegmentAddresses, allocator, source, options)); + } + + return .{ .value = result }; + } + + pub fn jsonStringify(self: @This(), out: anytype) !void { + try out.beginObject(); + var it = self.value.iterator(); + while (it.next()) |x| { + try out.objectField(x.key_ptr.*); + try out.write(x.value_ptr.*); + } + try out.endObject(); + } + }, + + public_memory: struct { + value: std.ArrayList(PublicMemoryEntry), + + pub fn jsonParse(allocator: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) !@This() { + if (.array_begin != try source.next()) return error.UnexpectedToken; + var result = std.ArrayList(PublicMemoryEntry).init(allocator); + errdefer result.deinit(); + + while (true) { + switch (try source.peekNextTokenType()) { + inline .object_begin => try result.append(try std.json.innerParse(PublicMemoryEntry, allocator, source, options)), + inline .array_end => { + _ = try source.next(); + return .{ .value = result }; + }, + else => return error.UnexpectedToken, + } + } + + return .{ .value = result }; + } + + pub fn jsonStringify(self: @This(), out: anytype) !void { + try out.beginArray(); + for (self.value.items) |x| try out.write(x); + try out.endArray(); + } + }, + + // serializing PublicInput to json + pub fn serialize(self: *Self, allocator: std.mem.Allocator) ![]const u8 { + var buffer = std.ArrayList(u8).init(allocator); + errdefer buffer.deinit(); + + try std.json.stringify(self, .{}, buffer.writer()); + + return try buffer.toOwnedSlice(); + } + + pub fn deserialize(allocator: std.mem.Allocator, data: []const u8) !std.json.Parsed(Self) { + const parsed = try std.json.parseFromSlice( + Self, + allocator, + data, + .{ + // Always allocate memory during parsing + .allocate = .alloc_always, + }, + ); + + // Return the parsed `ProgramJson` instance + return parsed; + } + + pub fn deinit(self: *Self) void { + self.memory_segments.value.deinit(); + self.public_memory.value.deinit(); + } + + // new - creating new PublicInput, all arguments caller is owner + pub fn new( + allocator: std.mem.Allocator, + memory: []const ?Felt252, + layout: []const u8, + public_memory_addresses: []const std.meta.Tuple(&.{ usize, usize }), + memory_segment_addresses: std.StringHashMap(std.meta.Tuple(&.{ usize, usize })), + trace: []const RelocatedTraceEntry, + rc_limits: std.meta.Tuple(&.{ isize, isize }), + ) !Self { + const memory_entry = (struct { + fn func(mem: []const ?Felt252, addresses: std.meta.Tuple(&.{ usize, usize })) !PublicMemoryEntry { + const address, const page = addresses; + return .{ + .address = address, + .page = page, + .value = .{ .value = if (mem.len <= address) return errors.PublicInputError.MemoryNotFound else mem[address] }, + }; + } + }).func; + + var public_memory = try std.ArrayList(PublicMemoryEntry).initCapacity(allocator, public_memory_addresses.len); + errdefer public_memory.deinit(); + + for (public_memory_addresses) |maddr| public_memory.appendAssumeCapacity(try memory_entry(memory, maddr)); + + const rc_min, const rc_max = rc_limits; + + if (trace.len < 2) return errors.PublicInputError.EmptyTrace; + + const trace_first = trace[0]; + const trace_last = trace[trace.len - 1]; + + return .{ + .layout = layout, + .rc_min = rc_min, + .rc_max = rc_max, + .n_steps = trace.len, + .memory_segments = .{ .value = blk: { + var msa = std.StringHashMap(MemorySegmentAddresses).init(allocator); + errdefer msa.deinit(); + + var it = memory_segment_addresses.iterator(); + while (it.next()) |x| { + const begin_addr, const stop_ptr = x.value_ptr.*; + + try msa.put(x.key_ptr.*, .{ + .begin_addr = begin_addr, + .stop_ptr = stop_ptr, + }); + } + + try msa.put("program", .{ .begin_addr = trace_first.pc, .stop_ptr = trace_last.pc }); + + try msa.put("execution", .{ .begin_addr = trace_first.ap, .stop_ptr = trace_last.ap }); + + break :blk msa; + } }, + .public_memory = .{ .value = public_memory }, + }; + } +}; + +pub const PublicMemoryEntry = struct { + address: usize, + value: struct { + /// using struct only for json parse abstraction + value: ?Felt252, + + pub fn jsonParse(allocator: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) !@This() { + _ = allocator; // autofix + _ = options; // autofix + switch (try source.next()) { + inline .string => |data| { + const val = try std.fmt.parseInt(u256, data, 0); + + return .{ .value = Felt252.fromInt(u256, val) }; + }, + inline .null => return .{ + .value = null, + }, + else => return error.UnexpectedToken, + } + } + + pub fn jsonStringify(self: @This(), out: anytype) !void { + if (self.value) |v| try out.print("\"0x{x}\"", .{v.toU256()}) else try out.write(null); + } + }, + page: usize, +}; + +pub const MemorySegmentAddresses = struct { + begin_addr: usize, + stop_ptr: usize, +}; + +test "AirInputPublic" { + const serialize_and_deserialize_air_input_public = (struct { + fn func(program_content: []const u8) !void { + const cfg = cairo_run.CairoRunConfig{ + .proof_mode = true, + .relocate_mem = true, + .trace_enabled = true, + .layout = "all_cairo", + }; + + var processor: HintProcessor = .{}; + + var runner = try cairo_run.cairoRun(std.testing.allocator, program_content, cfg, &processor); + + defer std.testing.allocator.destroy(runner.vm); + defer runner.deinit(std.testing.allocator); + defer runner.vm.segments.memory.deinitData(std.testing.allocator); + + var public_input = try runner.getAirPublicInput(); + defer public_input.deinit(); + + const public_input_json = try public_input.serialize(std.testing.allocator); + defer std.testing.allocator.free(public_input_json); + + var deserialized_public_input = try PublicInput.deserialize(std.testing.allocator, public_input_json); + + defer deserialized_public_input.deinit(); + defer deserialized_public_input.value.deinit(); + + try std.testing.expectEqualSlices(u8, public_input.layout, deserialized_public_input.value.layout); + try std.testing.expectEqual(public_input.rc_max, deserialized_public_input.value.rc_max); + try std.testing.expectEqual(public_input.rc_min, deserialized_public_input.value.rc_min); + try std.testing.expectEqual(public_input.n_steps, deserialized_public_input.value.n_steps); + + try std.testing.expectEqualSlices(PublicMemoryEntry, public_input.public_memory.value.items, deserialized_public_input.value.public_memory.value.items); + + try std.testing.expectEqual(public_input.memory_segments.value.count(), deserialized_public_input.value.memory_segments.value.count()); + + var it = public_input.memory_segments.value.iterator(); + while (it.next()) |kv| { + try std.testing.expect(deserialized_public_input.value.memory_segments.value.get(kv.key_ptr.*) != null); + try std.testing.expectEqual(kv.value_ptr.*, deserialized_public_input.value.memory_segments.value.get(kv.key_ptr.*).?); + } + } + }).func; + + const file = try std.fs.cwd().openFile("cairo_programs/proof_programs/fibonacci.json", .{ .mode = .read_only }); + + const stat = try file.stat(); + + const data = try file.readToEndAlloc(std.testing.allocator, stat.size); + defer std.testing.allocator.free(data); + + try serialize_and_deserialize_air_input_public(data); +} diff --git a/src/vm/builtins/builtin_runner/builtin_runner.zig b/src/vm/builtins/builtin_runner/builtin_runner.zig index aae29b41..bac941f3 100644 --- a/src/vm/builtins/builtin_runner/builtin_runner.zig +++ b/src/vm/builtins/builtin_runner/builtin_runner.zig @@ -24,6 +24,8 @@ const EcdsaInstanceDef = @import("../../types/ecdsa_instance_def.zig").EcdsaInst const BitwiseInstanceDef = @import("../../types/bitwise_instance_def.zig").BitwiseInstanceDef; const EcOpInstanceDef = @import("../../types/ec_op_instance_def.zig").EcOpInstanceDef; +const programjson = @import("../../types/programjson.zig"); + const ArrayList = std.ArrayList; const expectError = std.testing.expectError; @@ -41,6 +43,9 @@ pub const HASH_BUILTIN_NAME = "pedersen_builtin"; /// The name of the range check builtin. pub const RANGE_CHECK_BUILTIN_NAME = "range_check_builtin"; +/// The name of the range check 96 builtin. +pub const RANGE_CHECK96_BUILTIN_NAME = "range_check96_builtin"; + /// The name of the ECDSA signature verification builtin. pub const SIGNATURE_BUILTIN_NAME = "ecdsa_builtin"; @@ -70,6 +75,8 @@ pub const BuiltinName = enum { Output, /// Range Check built-in runner for range check operations. RangeCheck, + /// Range Check 96 built-in runner for range check operations. + RangeCheck96, /// Keccak built-in runner for Keccak operations. Keccak, /// Signature built-in runner for signature operations. @@ -78,6 +85,21 @@ pub const BuiltinName = enum { Poseidon, /// Segment Arena built-in runner for segment arena operations. SegmentArena, + + pub fn fromProgramJsonName(name: programjson.BuiltinName) BuiltinName { + return switch (name) { + inline .output => .Output, + inline .range_check => .RangeCheck, + inline .pedersen => .Hash, + inline .ecdsa => .Signature, + inline .keccak => .Keccak, + inline .bitwise => .Bitwise, + inline .ec_op => .EcOp, + inline .poseidon => .Poseidon, + inline .segment_arena => .SegmentArena, + inline .range_check96 => .RangeCheck96, + }; + } }; /// Built-in runner @@ -94,6 +116,8 @@ pub const BuiltinRunner = union(BuiltinName) { Output: OutputBuiltinRunner, /// Range Check built-in runner for range check operations. RangeCheck: RangeCheckBuiltinRunner, + /// Range Check built-in runner for range check operations. + RangeCheck96: RangeCheckBuiltinRunner, /// Keccak built-in runner for Keccak operations. Keccak: KeccakBuiltinRunner, /// Signature built-in runner for signature operations. @@ -532,7 +556,7 @@ pub const BuiltinRunner = union(BuiltinName) { /// # Returns /// /// A null-terminated byte slice representing the name of the built-in runner. - pub fn name(self: *Self) []const u8 { + pub fn name(self: *const Self) []const u8 { return switch (self.*) { .Bitwise => BITWISE_BUILTIN_NAME, .EcOp => EC_OP_BUILTIN_NAME, @@ -543,6 +567,7 @@ pub const BuiltinRunner = union(BuiltinName) { .Signature => SIGNATURE_BUILTIN_NAME, .Poseidon => POSEIDON_BUILTIN_NAME, .SegmentArena => SEGMENT_ARENA_BUILTIN_NAME, + .RangeCheck96 => RANGE_CHECK96_BUILTIN_NAME, }; } @@ -655,7 +680,7 @@ pub const BuiltinRunner = union(BuiltinName) { // It is safe to asume this for normal program execution // If there are trailing None values at the end, the following security checks will fail - const offset_max, _ = @subWithOverflow(builtin_segment.len, 1); + const offset_max = builtin_segment.len -| 1; // offset_len is the amount of non-None values in the segment const offset_len = blk: { @@ -681,7 +706,7 @@ pub const BuiltinRunner = union(BuiltinName) { for (0..n) |i| { for (0..n_input_cells) |j| { const offset = cells_per_instance * i + j; - if (builtin_segment.len < offset or builtin_segment[offset].isNone()) { + if (builtin_segment.len <= offset or builtin_segment[offset].isNone()) { try missing_offsets.append(offset); } } @@ -695,7 +720,7 @@ pub const BuiltinRunner = union(BuiltinName) { for (0..n) |i| { for (n_input_cells..cells_per_instance) |j| { const offset = cells_per_instance * i + j; - if (builtin_segment.len < offset or builtin_segment[offset].isNone()) { + if (builtin_segment.len <= offset or builtin_segment[offset].isNone()) { try vm.verifyAutoDeductionsForAddr(allocator, Relocatable.init(@intCast(builtin_segment_index), offset), self); } } diff --git a/src/vm/cairo_run.zig b/src/vm/cairo_run.zig index ae3fed63..c006b7fc 100644 --- a/src/vm/cairo_run.zig +++ b/src/vm/cairo_run.zig @@ -3,13 +3,15 @@ const json = std.json; const Allocator = std.mem.Allocator; const security = @import("security.zig"); -const CairoRunner = @import("./runners/cairo_runner.zig").CairoRunner; -const CairoVM = @import("./core.zig").CairoVM; -const Config = @import("./config.zig").Config; +const CairoRunner = @import("runners/cairo_runner.zig").CairoRunner; +const CairoVM = @import("core.zig").CairoVM; +const Config = @import("config.zig").Config; const Felt252 = @import("../math/fields/starknet.zig").Felt252; -const Program = @import("./types/program.zig").Program; -const ProgramJson = @import("./types/programjson.zig").ProgramJson; +const Program = @import("types/program.zig").Program; +const ProgramJson = @import("types/programjson.zig").ProgramJson; const HintProcessor = @import("../hint_processor/hint_processor_def.zig").CairoVMHintProcessor; +const ExecutionScopes = @import("types/execution_scopes.zig").ExecutionScopes; +const MaybeRelocatable = @import("./memory/relocatable.zig").MaybeRelocatable; const trace_context = @import("./trace_context.zig"); const RelocatedTraceEntry = trace_context.RelocatedTraceEntry; @@ -53,13 +55,131 @@ pub fn writeEncodedMemory(relocated_memory: []?Felt252, dest: anytype) !void { } } +pub const CairoRunConfig = struct { + entrypoint: []const u8 = "main", + trace_enabled: bool = false, + relocate_mem: bool = false, + layout: []const u8 = "plain", + proof_mode: bool = false, + secure_run: ?bool = null, + disable_trace_padding: bool = false, + allow_missing_builtins: ?bool = null, +}; + +// cairoRun - running cairo program, after call u own runner.vm, own runner and own runner.vm.segments.memory +// so after call u need deallocate runner.vm, calll runner.vm.segments.memory.deinitData and runner.deinit +pub fn cairoRun( + allocator: std.mem.Allocator, + program_content: []const u8, + cairo_run_config: CairoRunConfig, + hint_processor: *HintProcessor, +) !CairoRunner { + var parsed_program = try ProgramJson.parseFromString(allocator, program_content); + defer parsed_program.deinit(); + + var program = try parsed_program.value.parseProgramJson(allocator, @constCast(&cairo_run_config.entrypoint)); + + const instructions = parsed_program.value.readData(allocator) catch |err| { + program.deinit(allocator); + return err; + }; + + return cairoRunProgram(allocator, program, cairo_run_config, hint_processor, instructions); +} + +pub fn cairoRunProgram( + allocator: std.mem.Allocator, + program: Program, + cairo_run_config: CairoRunConfig, + hint_processor: *HintProcessor, + instructions: std.ArrayList(MaybeRelocatable), +) !CairoRunner { + const execution_scopes = ExecutionScopes.init(allocator) catch |err| { + // hack to deallocate program (TODO: think how to do it better) + var p = program; + p.deinit(allocator); + instructions.deinit(); + return err; + }; + + return cairoRunProgramWithInitialScope(allocator, program, cairo_run_config, hint_processor, instructions, execution_scopes); +} + +/// Runs a program with a customized execution scope. +/// put exec_scopes as argument, func take controll on it deallocation due error, due success CairoRunner will control deallocate +/// also as passed program, CairoRunner take control on program on error/success run +pub fn cairoRunProgramWithInitialScope( + allocator: std.mem.Allocator, + program: Program, + cairo_run_config: CairoRunConfig, + hint_processor: *HintProcessor, + instructions: std.ArrayList(MaybeRelocatable), + exec_scopes: ExecutionScopes, +) !CairoRunner { + const secure_run = cairo_run_config.secure_run orelse !cairo_run_config.proof_mode; + + const allow_missing_builtins = cairo_run_config.allow_missing_builtins orelse cairo_run_config.proof_mode; + + var runner = val: { + errdefer instructions.deinit(); + errdefer exec_scopes.deinit(); + errdefer { + // hack to deallocate + var p = program; + p.deinit(allocator); + } + + var vm = try allocator.create(CairoVM); + errdefer allocator.destroy(vm); + + vm.* = try CairoVM.initV2(allocator, cairo_run_config.trace_enabled); + errdefer vm.deinit(); + + break :val try CairoRunner.initV2( + allocator, + program, + cairo_run_config.layout, + instructions, + vm, + cairo_run_config.proof_mode, + exec_scopes, + ); + }; + errdefer runner.deinit(allocator); + + const end = try runner.setupExecutionState(allow_missing_builtins); + + try runner.runUntilPC(end, hint_processor); + + if (cairo_run_config.proof_mode) try runner.runForSteps(1, hint_processor); + + try runner.endRun( + allocator, + cairo_run_config.disable_trace_padding, + false, + hint_processor, + ); + + try runner.vm.verifyAutoDeductions(allocator); + try runner.readReturnValue(allow_missing_builtins); + + // cairo_runner.read_return_values(allow_missing_builtins)?; + if (cairo_run_config.proof_mode) try runner.finalizeSegments(); + + if (secure_run) try security.verifySecureRunner(allocator, &runner, true, null); + + try runner.relocate(cairo_run_config.relocate_mem); + + return runner; +} + /// Instruments the `CairoRunner` to initialize an execution of a cairo program based on Config params. /// /// # Arguments /// /// - `allocator`: The allocator to initialize the CairoRunner and parsing of the program json. /// - `config`: The config struct that defines the params that the CairoRunner uses to instantiate the vm state for running. -pub fn runConfig(allocator: Allocator, config: Config) !void { +pub fn runConfig(allocator: Allocator, config: Config) !CairoRunner { const secure_run = config.secure_run orelse !config.proof_mode; var vm = try CairoVM.init( @@ -82,7 +202,7 @@ pub fn runConfig(allocator: Allocator, config: Config) !void { &vm, config.proof_mode, ); - defer runner.deinit(allocator); + errdefer runner.deinit(allocator); const end = try runner.setupExecutionState(config.allow_missing_builtins orelse config.proof_mode); @@ -135,6 +255,8 @@ pub fn runConfig(allocator: Allocator, config: Config) !void { try writer.flush(); } } + + return runner; } const expect = std.testing.expect; diff --git a/src/vm/core.zig b/src/vm/core.zig index 7f370a02..7b8a9fb5 100644 --- a/src/vm/core.zig +++ b/src/vm/core.zig @@ -19,7 +19,6 @@ const Config = @import("config.zig").Config; const TraceContext = @import("trace_context.zig").TraceContext; const TraceEntry = @import("trace_context.zig").TraceEntry; const RelocatedTraceEntry = @import("trace_context.zig").RelocatedTraceEntry; -const build_options = @import("../build_options.zig"); const RangeCheckBuiltinRunner = @import("builtins/builtin_runner/range_check.zig").RangeCheckBuiltinRunner; const SignatureBuiltinRunner = @import("builtins/builtin_runner/signature.zig").SignatureBuiltinRunner; const BuiltinRunner = @import("builtins/builtin_runner/builtin_runner.zig").BuiltinRunner; @@ -75,20 +74,13 @@ pub const CairoVM = struct { // * MEMORY ALLOCATION AND DEALLOCATION * // ************************************************************ - /// Creates a new Cairo VM. - /// # Arguments - /// - `allocator`: The allocator to use for the VM. - /// - `config`: Configurations used to initialize the VM. - /// # Returns - /// - `CairoVM`: The created VM. - /// # Errors - /// - If a memory allocation fails. - pub fn init(allocator: Allocator, config: Config) !Self { + pub fn initV2(allocator: Allocator, trace_enabled: bool) !Self { + // Initialize the memory segment manager. const memory_segment_manager = try segments.MemorySegmentManager.init(allocator); errdefer memory_segment_manager.deinit(); // Initialize the trace context. - var trace: ?std.ArrayList(TraceEntry) = if (config.enable_trace) try std.ArrayList(TraceEntry).initCapacity(allocator, 100) else null; + var trace: ?std.ArrayList(TraceEntry) = if (trace_enabled) try std.ArrayList(TraceEntry).initCapacity(allocator, 100) else null; errdefer if (trace != null) trace.?.deinit(); // Initialize the run context. const run_context = .{}; @@ -109,6 +101,18 @@ pub const CairoVM = struct { }; } + /// Creates a new Cairo VM. + /// # Arguments + /// - `allocator`: The allocator to use for the VM. + /// - `config`: Configurations used to initialize the VM. + /// # Returns + /// - `CairoVM`: The created VM. + /// # Errors + /// - If a memory allocation fails. + pub fn init(allocator: Allocator, config: Config) !Self { + return Self.initV2(allocator, config.enable_trace); + } + /// Safely deallocates resources used by the CairoVM instance. /// /// This function ensures safe deallocation of various components within the CairoVM instance, diff --git a/src/vm/core_test.zig b/src/vm/core_test.zig index 2f97e800..54d1f8e9 100644 --- a/src/vm/core_test.zig +++ b/src/vm/core_test.zig @@ -22,7 +22,6 @@ const MathError = @import("error.zig").MathError; const Config = @import("config.zig").Config; const TraceEntry = @import("trace_context.zig").TraceEntry; const RelocatedTraceEntry = @import("trace_context.zig").RelocatedTraceEntry; -const build_options = @import("../build_options.zig"); const BuiltinRunner = @import("./builtins/builtin_runner/builtin_runner.zig").BuiltinRunner; const BitwiseBuiltinRunner = @import("./builtins/builtin_runner/bitwise.zig").BitwiseBuiltinRunner; const KeccakBuiltinRunner = @import("./builtins/builtin_runner/keccak.zig").KeccakBuiltinRunner; diff --git a/src/vm/error.zig b/src/vm/error.zig index 62df1cc1..fbc13817 100644 --- a/src/vm/error.zig +++ b/src/vm/error.zig @@ -1,5 +1,7 @@ /// Represents different error conditions that occur in the Cairo VM. pub const CairoVMError = error{ + // Failed to find index in the vm's relocation table + RelocationNotFound, // Out of bounds access to program segment OutOfBoundsProgramSegmentAccess, // Out of bounds access to builtin segment @@ -156,6 +158,16 @@ pub const CairoRunnerError = error{ /// Represents different error conditions that occur in the built-in runners. pub const RunnerError = error{ + // Missing execution public memory + NoExecPublicMemory, + // Cannot add the return values to the public memory after segment finalization. + FailedAddingReturnValues, + // is missing + MissingBuiltin, + // The stop pointer of the missing builtin {0} must be 0 + MissingBuiltinStopPtrNotZero, + // end_run must be called before read_return_values. + ReadReturnValuesNoEndRun, // Error while finalizing segments FinalizeSegements, // Finalize_segments called but proof_mode is not enabled @@ -416,3 +428,14 @@ pub const ExecScopeError = error{ ExitMainScopeError, NoScopeError, }; + +pub const PublicInputError = error{ + // The trace slice provided is empty")] + EmptyTrace, + // The provided memory doesn't contain public address {0}")] + MemoryNotFound, + // Range check values are missing")] + NoRangeCheckLimits, + // Failed to (de)serialize data + Serialize, +}; diff --git a/src/vm/memory/memory.zig b/src/vm/memory/memory.zig index c0666c81..36806898 100644 --- a/src/vm/memory/memory.zig +++ b/src/vm/memory/memory.zig @@ -376,6 +376,9 @@ pub const Memory = struct { var data = self.getDataFromSegmentIndex(address.segment_index); const insert_segment_index = address.getAdjustedSegmentIndex(); + // if (address.segment_index == 0 and value.isFelt()) + // std.debug.panic("set {any}, value {any}", .{ address, value }); + // Check if the data segment is allocated for the given segment index. if (data.len <= insert_segment_index) return MemoryError.UnallocatedSegment; diff --git a/src/vm/memory/segments.zig b/src/vm/memory/segments.zig index b2d66414..b9355edb 100644 --- a/src/vm/memory/segments.zig +++ b/src/vm/memory/segments.zig @@ -238,7 +238,8 @@ pub const MemorySegmentManager = struct { var relocatable_table = try ArrayList(usize).initCapacity(allocator, 1 + self.segment_used_sizes.items.len); errdefer relocatable_table.deinit(); - try relocatable_table.append(1); + relocatable_table.appendAssumeCapacity(1); + for (0..self.segment_used_sizes.items.len) |i| { const segment_size = self.getSegmentSize(i) orelse return MemoryError.MissingSegmentUsedSizes; relocatable_table.appendAssumeCapacity(relocatable_table.items[i] + segment_size); diff --git a/src/vm/runners/cairo_runner.zig b/src/vm/runners/cairo_runner.zig index 99296bc8..d61a487d 100644 --- a/src/vm/runners/cairo_runner.zig +++ b/src/vm/runners/cairo_runner.zig @@ -28,6 +28,7 @@ const CairoVMError = @import("../error.zig").CairoVMError; const InsufficientAllocatedCellsError = @import("../error.zig").InsufficientAllocatedCellsError; const RunnerError = @import("../error.zig").RunnerError; const MemoryError = @import("../error.zig").MemoryError; +const PublicInputError = @import("../error.zig").PublicInputError; const trace_context = @import("../trace_context.zig"); const RelocatedTraceEntry = trace_context.RelocatedTraceEntry; const RelocatedFelt252 = trace_context.RelocatedFelt252; @@ -36,6 +37,7 @@ const Felt252 = starknet_felt.Felt252; const ExecutionScopes = @import("../types/execution_scopes.zig").ExecutionScopes; const TraceEntry = @import("../trace_context.zig").TraceEntry; const TestingUtils = @import("../../utils/testing.zig"); +const air_input_public = @import("../air_input_public.zig"); const cfg = @import("cfg"); @@ -158,13 +160,15 @@ pub const CairoRunner = struct { execution_scopes: ExecutionScopes = undefined, segments_finalized: bool, - pub fn init( + // taking own on instructions and exec_scopes + pub fn initV2( allocator: Allocator, program: Program, layout: []const u8, instructions: std.ArrayList(MaybeRelocatable), vm: *CairoVM, proof_mode: bool, + exec_scopes: ExecutionScopes, ) !Self { const Case = enum { plain, small, dynamic, all_cairo }; return .{ @@ -181,13 +185,24 @@ pub const CairoRunner = struct { .vm = vm, .runner_mode = if (proof_mode) .proof_mode_canonical else .execution_mode, .relocated_memory = ArrayList(?Felt252).init(allocator), - .execution_scopes = try ExecutionScopes.init(allocator), + .execution_scopes = exec_scopes, .entrypoint = program.shared_program_data.main, .segments_finalized = false, .execution_public_memory = if (proof_mode) std.ArrayList(usize).init(allocator) else null, }; } + pub fn init( + allocator: Allocator, + program: Program, + layout: []const u8, + instructions: std.ArrayList(MaybeRelocatable), + vm: *CairoVM, + proof_mode: bool, + ) !Self { + return Self.initV2(allocator, program, layout, instructions, vm, proof_mode, try ExecutionScopes.init(allocator)); + } + pub fn isProofMode(self: *Self) bool { return self.runner_mode == .proof_mode_canonical or self.runner_mode == .proof_mode_cairo1; } @@ -211,6 +226,7 @@ pub const CairoRunner = struct { .ec_op, .keccak, .poseidon, + .range_check96, }; for (self.program.builtins.items) |builtin| { @@ -298,6 +314,15 @@ pub const CairoRunner = struct { }); } + if (self.layout.builtins.range_check96) |instance_def| { + const included = program_builtins.remove(.range_check96); + + if (included or self.isProofMode()) + try self.vm.builtin_runners.append(.{ + .RangeCheck96 = RangeCheckBuiltinRunner.init(instance_def.ratio, instance_def.n_parts, included), + }); + } + if (program_builtins.count() != 0 and !allow_missing_builtins) return RunnerError.NoBuiltinForInstance; } @@ -434,17 +459,18 @@ pub const CairoRunner = struct { var stack_prefix = try std.ArrayList(MaybeRelocatable).initCapacity(self.allocator, 2 + stack.items.len); defer stack_prefix.deinit(); - try stack_prefix.append(MaybeRelocatable.fromRelocatable( + stack_prefix.appendAssumeCapacity(MaybeRelocatable.fromRelocatable( try (self.execution_base orelse return RunnerError.NoExecBase).addUint(target_offset), )); - try stack_prefix.append(MaybeRelocatable.fromFelt(Felt252.zero())); - try stack_prefix.appendSlice(stack.items); + stack_prefix.appendAssumeCapacity(MaybeRelocatable.fromFelt(Felt252.zero())); + + stack_prefix.appendSliceAssumeCapacity(stack.items); // Initialize public memory for execution context. var execution_public_memory = try std.ArrayList(usize).initCapacity(self.allocator, stack_prefix.items.len); for (0..stack_prefix.items.len) |v| { - try execution_public_memory.append(v); + execution_public_memory.appendAssumeCapacity(v); } self.execution_public_memory = execution_public_memory; @@ -707,8 +733,7 @@ pub const CairoRunner = struct { // Loop until all steps are executed or program ends while (remaining_steps >= 1) { // Check if the program has reached its end - if (self.final_pc.?.eq(self.vm.run_context.pc)) - return CairoVMError.EndOfProgram; + if (self.final_pc) |final_pc| if (final_pc.eq(self.vm.run_context.pc)) return CairoVMError.EndOfProgram; // Execute a single step of the program, considering extensive or non-extensive hints if (@import("cfg").extensive) { @@ -927,12 +952,57 @@ pub const CairoRunner = struct { } } - pub fn relocate(self: *Self) !void { + pub fn readReturnValue(self: *Self, allow_missing_builtins: bool) !void { + if (!self.run_ended) return RunnerError.ReadReturnValuesNoEndRun; + + var pointer = self.vm.run_context.getAP(); + const len = self.program.builtins.items.len; + for (0..len) |idx| { + const builtin_name = builtin_runner_import.BuiltinName.fromProgramJsonName(self.program.builtins.items[len - idx - 1]); + + for (self.vm.builtin_runners.items) |*builtin| { + if (std.meta.activeTag(builtin.*) == builtin_name) { + pointer = try builtin.finalStack(self.vm.segments, pointer); + break; + } + } else { + // builtin not found + if (!allow_missing_builtins) + return RunnerError.MissingBuiltin; + + pointer.offset = pointer.offset -| 1; + + if (!(try self.vm.getFelt(pointer)).isZero()) + return RunnerError.MissingBuiltinStopPtrNotZero; + } + } + + if (self.segments_finalized) return RunnerError.FailedAddingReturnValues; + + if (self.isProofMode()) { + const exec_base = self.execution_base orelse return RunnerError.NoExecBase; + + const begin = pointer.offset - exec_base.offset; + const ap = self.vm.run_context.getAP(); + const end = ap.offset - exec_base.offset; + + if (end - begin > 0) + if (self.execution_public_memory) |*memory| { + try memory.ensureUnusedCapacity(end - begin); + for (begin..end) |x| { + memory.appendAssumeCapacity(x); + } + } else return RunnerError.NoExecPublicMemory; + } + } + + pub fn relocate(self: *Self, relocate_mem: bool) !void { // Presuming the default case of `allow_tmp_segments` in python version _ = try self.vm.segments.computeEffectiveSize(false); + if (!relocate_mem and self.vm.trace == null) return; const totalSize, const relocation_table = try self.vm.segments.relocateSegments(self.allocator); - defer relocation_table.deinit(); + errdefer relocation_table.deinit(); try self.relocateMemory(totalSize, relocation_table.items); @@ -940,6 +1010,8 @@ pub const CairoRunner = struct { try self.vm.relocateTrace(relocation_table.items); self.relocated_trace = try self.vm.getRelocatedTrace(); } + + self.vm.relocation_table = relocation_table; } /// Retrieves information about the builtin segments. @@ -970,19 +1042,13 @@ pub const CairoRunner = struct { const memory_segment_addresses = builtin.getMemorySegmentAddresses(); // Uncomment the following line for debugging purposes. - // std.debug.print("memory_segment_addresses = {any}\n", .{memory_segment_addresses}); - - // Check if the stop pointer is present. - if (memory_segment_addresses[1]) |stop_pointer| { - // Append information about the segment to the ArrayList. - try builtin_segment_info.append(.{ - .segment_index = memory_segment_addresses[0], - .stop_pointer = stop_pointer, - }); - } else { - // Return an error if a stop pointer is missing. - return RunnerError.NoStopPointer; - } + // std.debug.print("memory_segment_addresses = {any}, {s}\n", .{ memory_segment_addresses, builtin.name() }); + + // Append information about the segment to the ArrayList. + try builtin_segment_info.append(.{ + .segment_index = memory_segment_addresses[0], + .stop_pointer = memory_segment_addresses[1] orelse return RunnerError.NoStopPointer, + }); } // Return the ArrayList containing information about the builtin segments. @@ -1035,6 +1101,65 @@ pub const CairoRunner = struct { ); } + pub fn getAirPublicInput(self: *Self) !air_input_public.PublicInput { + const public_memory_address = try self.vm.getPublicMemoryAddresses(); + defer public_memory_address.deinit(); + + var memory_segment_addresses = try self.getMemorySegmentAddresses(self.allocator); + defer memory_segment_addresses.deinit(); + + return air_input_public.PublicInput.new( + self.allocator, + self.relocated_memory.items, + self.layout.name, + public_memory_address.items, + memory_segment_addresses, + self.relocated_trace orelse return PublicInputError.EmptyTrace, + (try self.getPermRangeCheckLimits(self.allocator)) orelse return PublicInputError.NoRangeCheckLimits, + ); + } + + pub fn getMemorySegmentAddresses( + self: *Self, + allocator: std.mem.Allocator, + ) !std.StringHashMap(std.meta.Tuple(&.{ usize, usize })) { + const reloc_table = self + .vm + .relocation_table orelse return MemoryError.UnrelocatedMemory; + + const _relocate = (struct { + fn reloc(relocation_table: std.ArrayList(usize), segment: std.meta.Tuple(&.{ usize, usize })) !std.meta.Tuple(&.{ usize, usize }) { + const index, const stop_ptr_offset = segment; + const base = if (relocation_table.items.len <= index) return CairoVMError.RelocationNotFound else relocation_table.items[index]; + + return .{ base, base + stop_ptr_offset }; + } + }).reloc; + + var result = std.StringHashMap(std.meta.Tuple(&.{ usize, usize })).init(allocator); + errdefer result.deinit(); + + // TODO optimize contains? + const contains = (struct { + fn func(builtins: []BuiltinName, x: []const u8) bool { + for (builtins) |builtin| + if (builtin == std.meta.stringToEnum(BuiltinName, x)) return true; + + return false; + } + }).func; + + for (self.vm.builtin_runners.items) |*builtin| { + const base, const stop_ptr_ = builtin.getMemorySegmentAddresses(); + + const stop_ptr = if (contains(self.program.builtins.items, builtin.name())) stop_ptr_ orelse return RunnerError.NoStopPointer else stop_ptr_ orelse 0; + + try result.put(builtin.name(), try _relocate(reloc_table, .{ base, stop_ptr })); + } + + return result; + } + /// Retrieves the permanent range check limits from the CairoRunner instance. /// /// This function iterates through the builtin runners of the CairoRunner and gathers @@ -4067,7 +4192,7 @@ test "CairoRunner: initialize and run relocate trace with output builtin" { var hint_processor: HintProcessor = .{}; try cairo_runner.runUntilPC(end, &hint_processor); - try cairo_runner.relocate(); + try cairo_runner.relocate(true); // Perform assertions to check if memory relocation is correct. try expectEqualSlices( diff --git a/src/vm/types/builtins_instance_def.zig b/src/vm/types/builtins_instance_def.zig index 3d10047a..dbbf29ca 100644 --- a/src/vm/types/builtins_instance_def.zig +++ b/src/vm/types/builtins_instance_def.zig @@ -32,6 +32,11 @@ pub const BuiltinsInstanceDef = struct { /// /// If present, contains the instance definition for the 'range_check' builtin; otherwise, it's `null`. range_check: ?RangeCheckInstanceDef = null, + /// Represents the instance of the 'range_check96' builtin. + /// + /// If present, contains the instance definition for the 'range_check' builtin; otherwise, it's `null`. + range_check96: ?RangeCheckInstanceDef = null, + /// Represents the instance of the 'ECDSA' builtin. /// /// If present, contains the instance definition for the 'ECDSA' builtin; otherwise, it's `null`. @@ -87,6 +92,7 @@ pub const BuiltinsInstanceDef = struct { .ec_op = EcOpInstanceDef.init(1024), .keccak = KeccakInstanceDef.init(2048, state_rep_keccak), .poseidon = PoseidonInstanceDef.init(256), + .range_check96 = RangeCheckInstanceDef.init(8, 6), }; } diff --git a/src/vm/types/execution_scopes.zig b/src/vm/types/execution_scopes.zig index 9544012b..27970c11 100644 --- a/src/vm/types/execution_scopes.zig +++ b/src/vm/types/execution_scopes.zig @@ -301,7 +301,7 @@ pub const ExecutionScopes = struct { } /// Deinitializes the execution scope. - pub fn deinit(self: *Self) void { + pub fn deinit(self: *const Self) void { for (self.data.items) |*m| { var it = m.valueIterator(); diff --git a/src/vm/types/layout.zig b/src/vm/types/layout.zig index df618899..77c5f79c 100644 --- a/src/vm/types/layout.zig +++ b/src/vm/types/layout.zig @@ -33,6 +33,36 @@ const expectEqual = std.testing.expectEqual; const expectError = std.testing.expectError; const expectEqualSlices = std.testing.expectEqualSlices; +pub const LayoutName = enum { + plain, + small, + dex, + recursive, + starknet, + starknet_with_keccak, + recursive_large_output, + recursive_with_poseidon, + all_solidity, + all_cairo, + dynamic, + + pub fn toString(self: @This()) []const u8 { + return switch (self) { + .plain => "plain", + .small => "small", + .dex => "dex", + .recursive => "recursive", + .starknet => "starknet", + .starknet_with_keccak => "starknet_with_keccak", + .recursive_large_output => "recursive_large_output", + .recursive_with_poseidon => "recursive_with_poseidon", + .all_solidity => "all_solidity", + .all_cairo => "all_cairo", + .dynamic => "all_cairo", + }; + } +}; + /// Represents the layout configuration for Cairo programs. pub const CairoLayout = struct { const Self = @This(); diff --git a/src/vm/types/program.zig b/src/vm/types/program.zig index 8b1457bd..30efdb22 100644 --- a/src/vm/types/program.zig +++ b/src/vm/types/program.zig @@ -322,7 +322,7 @@ pub const SharedProgramData = struct { // Iterate through each instruction location. while (it.next()) |kv| { // Check if the parent_location_instruction exists. - if (instruction_locations.getPtr(kv.key_ptr.*).?.inst.parent_location_instruction) |*p| { + if (kv.value_ptr.inst.parent_location_instruction) |*p| { // Retrieve and remove the first element of the list. var it_list = p.popFirst(); diff --git a/src/vm/types/programjson.zig b/src/vm/types/programjson.zig index f6d9d12f..4b3f370b 100644 --- a/src/vm/types/programjson.zig +++ b/src/vm/types/programjson.zig @@ -25,6 +25,8 @@ pub const BuiltinName = enum { output, /// Represents the range check builtin. range_check, + /// Represents the range check 96 builtin. + range_check96, /// Represents the Pedersen builtin. pedersen, /// Represents the ECDSA builtin.