From 698b77942879127dd1e0595ca750ba694073b5fb Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Tue, 30 Jan 2024 22:00:37 +0100 Subject: [PATCH] add unit tests and fix bugs on `runInstruction` (#339) add unit tests and fix bugs on runInstruction Co-authored-by: lanaivina <31368580+lana-shanghai@users.noreply.github.com> --- src/vm/core.zig | 133 +++++++------- src/vm/core_test.zig | 386 ++++++++++++++++++++++++++++++++++++--- src/vm/memory/memory.zig | 11 +- 3 files changed, 437 insertions(+), 93 deletions(-) diff --git a/src/vm/core.zig b/src/vm/core.zig index 829ada60..7384919f 100644 --- a/src/vm/core.zig +++ b/src/vm/core.zig @@ -43,22 +43,22 @@ pub const CairoVM = struct { /// The memory segment manager. segments: *segments.MemorySegmentManager, /// Whether the run is finished or not. - is_run_finished: bool, + is_run_finished: bool = false, /// VM trace trace_context: TraceContext, /// Whether the trace has been relocated - trace_relocated: bool, + trace_relocated: bool = false, /// Current Step - current_step: usize, + current_step: usize = 0, /// Rc limits - rc_limits: ?struct { i16, i16 }, + rc_limits: ?struct { isize, isize } = null, /// Relocation table - relocation_table: ?std.ArrayList(usize), + relocation_table: ?std.ArrayList(usize) = null, /// ArrayList containing instructions. May hold null elements. /// Used as an instruction cache within the CairoVM instance. instruction_cache: ArrayList(?Instruction), - relocated_memory: ?std.AutoHashMap(u32, Felt252), + relocated_memory: ?std.AutoHashMap(u32, Felt252) = null, // ************************************************************ // * MEMORY ALLOCATION AND DEALLOCATION * @@ -97,14 +97,8 @@ pub const CairoVM = struct { .run_context = run_context, .builtin_runners = builtin_runners, .segments = memory_segment_manager, - .is_run_finished = false, .trace_context = trace_context, - .trace_relocated = false, - .current_step = 0, - .rc_limits = null, - .relocation_table = null, .instruction_cache = instruction_cache, - .relocated_memory = null, }; } @@ -316,42 +310,75 @@ pub const CairoVM = struct { } } - /// Run a specific instruction. - // # Arguments - /// - `instruction`: The instruction to run. + /// Runs a specific instruction in the Cairo VM. + /// + /// This function executes a single instruction in the Cairo VM by fetching, decoding, and + /// executing the instruction. It updates the VM's registers, traces the instruction (if + /// tracing is enabled), computes and inserts operands into memory, and marks memory accesses. + /// + /// # Parameters + /// + /// - `self`: A mutable reference to the CairoVM instance. + /// - `allocator`: The allocator used for memory operations. + /// - `instruction`: A pointer to the instruction to run. + /// + /// # Errors + /// + /// This function may return an error of type `CairoVMError.InstructionEncodingError` if there + /// is an issue with encoding or decoding the instruction. + /// + /// # Safety + /// + /// This function assumes proper initialization of the CairoVM instance and must be called in + /// a controlled environment to ensure the correct execution of instructions and memory operations. pub fn runInstruction( self: *Self, allocator: Allocator, - instruction: *const instructions.Instruction, + instruction: *const Instruction, ) !void { + // Check if tracing is disabled and log the current state if not. if (!build_options.trace_disable) { - try self.trace_context.traceInstruction(.{ - .pc = self.run_context.pc.*, - .ap = self.run_context.ap.*, - .fp = self.run_context.fp.*, - }); + try self.trace_context.traceInstruction( + .{ + .pc = self.run_context.pc.*, + .ap = self.run_context.ap.*, + .fp = self.run_context.fp.*, + }, + ); } + // Compute operands for the instruction. const operands_result = try self.computeOperands(allocator, instruction); + + // Insert deduced operands into memory. try self.insertDeducedOperands(allocator, operands_result); + // Update registers based on the instruction and operands. try self.updateRegisters( instruction, operands_result, ); + // Constants for offset bit manipulation. const OFFSET_BITS: u32 = 16; - const off_0 = if (instruction.off_0 < 0) 0 else instruction.off_0 + (@as(i16, 1) << (OFFSET_BITS - 1)); - const off_1 = if (instruction.off_1 < 0) 0 else instruction.off_1 + (@as(i16, 1) << (OFFSET_BITS - 1)); - const off_2 = if (instruction.off_2 < 0) 0 else instruction.off_2 + (@as(i16, 1) << (OFFSET_BITS - 1)); + const off_0 = instruction.off_0 + (@as(isize, 1) << (OFFSET_BITS - 1)); + const off_1 = instruction.off_1 + (@as(isize, 1) << (OFFSET_BITS - 1)); + const off_2 = instruction.off_2 + (@as(isize, 1) << (OFFSET_BITS - 1)); + // Calculate and update relocation limits. const limits = self.rc_limits orelse .{ off_0, off_0 }; - self.rc_limits = .{ @min(limits[0], off_0, off_1, off_2), @max(limits[1], off_0, off_1, off_2) }; + self.rc_limits = .{ + @min(limits[0], off_0, off_1, off_2), + @max(limits[1], off_0, off_1, off_2), + }; + + // Mark memory accesses for the instruction. self.segments.memory.markAsAccessed(operands_result.dst_addr); self.segments.memory.markAsAccessed(operands_result.op_0_addr); self.segments.memory.markAsAccessed(operands_result.op_1_addr); + // Increment the current step counter. self.current_step += 1; } @@ -372,10 +399,10 @@ pub const CairoVM = struct { pub fn computeOperands( self: *Self, allocator: Allocator, - instruction: *const instructions.Instruction, + instruction: *const Instruction, ) !OperandsResult { // Create a default OperandsResult to store the computed operands. - var op_res = OperandsResult.default(); + var op_res = OperandsResult{}; op_res.res = null; // Compute the destination address of the instruction. @@ -462,7 +489,7 @@ pub const CairoVM = struct { self: *Self, allocator: Allocator, op_0_addr: Relocatable, - instruction: *const instructions.Instruction, + instruction: *const Instruction, dst: *const ?MaybeRelocatable, op1: *const ?MaybeRelocatable, ) !MaybeRelocatable { @@ -494,7 +521,7 @@ pub const CairoVM = struct { allocator: Allocator, op1_addr: Relocatable, res: *?MaybeRelocatable, - instruction: *const instructions.Instruction, + instruction: *const Instruction, dst_op: *const ?MaybeRelocatable, op0: *const ?MaybeRelocatable, ) !MaybeRelocatable { @@ -578,7 +605,7 @@ pub const CairoVM = struct { /// - `Tuple`: A tuple containing the deduced `op0` and `res`. pub fn deduceOp0( self: *Self, - inst: *const instructions.Instruction, + inst: *const Instruction, dst: *const ?MaybeRelocatable, op1: *const ?MaybeRelocatable, ) !Op0Result { @@ -617,7 +644,7 @@ pub const CairoVM = struct { /// - `operands`: The operands of the instruction. pub fn updatePc( self: *Self, - instruction: *const instructions.Instruction, + instruction: *const Instruction, operands: OperandsResult, ) !void { switch (instruction.pc_update) { @@ -665,7 +692,7 @@ pub const CairoVM = struct { /// - `operands`: The operands of the instruction. pub fn updateAp( self: *Self, - instruction: *const instructions.Instruction, + instruction: *const Instruction, operands: OperandsResult, ) !void { switch (instruction.ap_update) { @@ -694,7 +721,7 @@ pub const CairoVM = struct { /// - `operands`: The operands of the instruction. pub fn updateFp( self: *Self, - instruction: *const instructions.Instruction, + instruction: *const Instruction, operands: OperandsResult, ) !void { switch (instruction.fp_update) { @@ -709,7 +736,7 @@ pub const CairoVM = struct { .relocatable => |rel| { // Update the FP. // FP = DST. - self.run_context.fp.* = rel; + self.run_context.fp.* = Relocatable.init(1, rel.offset); }, .felt => |f| { // Update the FP. @@ -735,7 +762,7 @@ pub const CairoVM = struct { /// - Returns `void` on success, an error on failure. pub fn updateRegisters( self: *Self, - instruction: *const instructions.Instruction, + instruction: *const Instruction, operands: OperandsResult, ) !void { try self.updateFp(instruction, operands); @@ -1217,7 +1244,7 @@ pub fn computeRes( /// # Returns /// - `Tuple`: A tuple containing the deduced `op1` and `res`. pub fn deduceOp1( - inst: *const instructions.Instruction, + inst: *const Instruction, dst: *const ?MaybeRelocatable, op0: *const ?MaybeRelocatable, ) !Op1Result { @@ -1258,39 +1285,21 @@ pub const OperandsResult = struct { const Self = @This(); /// The destination operand value. - dst: MaybeRelocatable, + dst: MaybeRelocatable = MaybeRelocatable.fromFelt(Felt252.zero()), /// The result operand value. - res: ?MaybeRelocatable, + res: ?MaybeRelocatable = MaybeRelocatable.fromFelt(Felt252.zero()), /// The first operand value. - op_0: MaybeRelocatable, + op_0: MaybeRelocatable = MaybeRelocatable.fromFelt(Felt252.zero()), /// The second operand value. - op_1: MaybeRelocatable, + op_1: MaybeRelocatable = MaybeRelocatable.fromFelt(Felt252.zero()), /// The relocatable address of the destination operand. - dst_addr: Relocatable, + dst_addr: Relocatable = .{}, /// The relocatable address of the first operand. - op_0_addr: Relocatable, + op_0_addr: Relocatable = .{}, /// The relocatable address of the second operand. - op_1_addr: Relocatable, + op_1_addr: Relocatable = .{}, /// Indicator for deduced operands. - deduced_operands: u8, - - /// Returns a default instance of the OperandsResult struct with initial values set to zero. - /// - /// # Returns - /// - /// - An instance of OperandsResult with default values. - pub fn default() Self { - return .{ - .dst = MaybeRelocatable.fromInt(u64, 0), - .res = MaybeRelocatable.fromInt(u64, 0), - .op_0 = MaybeRelocatable.fromInt(u64, 0), - .op_1 = MaybeRelocatable.fromInt(u64, 0), - .dst_addr = .{}, - .op_0_addr = .{}, - .op_1_addr = .{}, - .deduced_operands = 0, - }; - } + deduced_operands: u8 = 0, /// Sets the flag indicating the destination operand was deduced. /// diff --git a/src/vm/core_test.zig b/src/vm/core_test.zig index 5da80a74..50bc8d9c 100644 --- a/src/vm/core_test.zig +++ b/src/vm/core_test.zig @@ -8,6 +8,7 @@ const starknet_felt = @import("../math/fields/starknet.zig"); const segments = @import("memory/segments.zig"); const memory = @import("memory/memory.zig"); const MemoryCell = memory.MemoryCell; +const Memory = memory.Memory; const relocatable = @import("memory/relocatable.zig"); const MaybeRelocatable = relocatable.MaybeRelocatable; const Relocatable = relocatable.Relocatable; @@ -87,7 +88,7 @@ test "update pc regular no imm" { // Test setup const allocator = std.testing.allocator; - const operands = OperandsResult.default(); + const operands = OperandsResult{}; // Create a new VM instance. var vm = try CairoVM.init(allocator, .{}); defer vm.deinit(); @@ -124,7 +125,7 @@ test "update pc regular no imm" { test "update pc regular with imm" { // Test setup const allocator = std.testing.allocator; - const operands = OperandsResult.default(); + const operands = OperandsResult{}; // Create a new VM instance. var vm = try CairoVM.init(allocator, .{}); defer vm.deinit(); @@ -161,7 +162,7 @@ test "update pc regular with imm" { test "update pc jump with operands res null" { // Test setup const allocator = std.testing.allocator; - var operands = OperandsResult.default(); + var operands = OperandsResult{}; operands.res = null; // Create a new VM instance. var vm = try CairoVM.init(allocator, .{}); @@ -192,7 +193,7 @@ test "update pc jump with operands res null" { test "update pc jump with operands res not relocatable" { // Test setup const allocator = std.testing.allocator; - var operands = OperandsResult.default(); + var operands = OperandsResult{}; operands.res = MaybeRelocatable.fromInt(u64, 0); // Create a new VM instance. var vm = try CairoVM.init(allocator, .{}); @@ -223,7 +224,7 @@ test "update pc jump with operands res not relocatable" { test "update pc jump with operands res relocatable" { // Test setup const allocator = std.testing.allocator; - var operands = OperandsResult.default(); + var operands = OperandsResult{}; operands.res = MaybeRelocatable.fromRelocatable(Relocatable.init( 0, 42, @@ -264,7 +265,7 @@ test "update pc jump with operands res relocatable" { test "update pc jump rel with operands res null" { // Test setup const allocator = std.testing.allocator; - var operands = OperandsResult.default(); + var operands = OperandsResult{}; operands.res = null; // Create a new VM instance. var vm = try CairoVM.init(allocator, .{}); @@ -295,7 +296,7 @@ test "update pc jump rel with operands res null" { test "update pc jump rel with operands res not felt" { // Test setup const allocator = std.testing.allocator; - var operands = OperandsResult.default(); + var operands = OperandsResult{}; operands.res = MaybeRelocatable.fromRelocatable(Relocatable.init( 0, 42, @@ -329,7 +330,7 @@ test "update pc jump rel with operands res not felt" { test "update pc jump rel with operands res felt" { // Test setup const allocator = std.testing.allocator; - var operands = OperandsResult.default(); + var operands = OperandsResult{}; operands.res = MaybeRelocatable.fromInt(u64, 42); // Create a new VM instance. var vm = try CairoVM.init(allocator, .{}); @@ -367,7 +368,7 @@ test "update pc jump rel with operands res felt" { test "update pc update jnz with operands dst zero" { // Test setup const allocator = std.testing.allocator; - var operands = OperandsResult.default(); + var operands = OperandsResult{}; operands.dst = MaybeRelocatable.fromInt(u64, 0); // Create a new VM instance. var vm = try CairoVM.init(allocator, .{}); @@ -405,7 +406,7 @@ test "update pc update jnz with operands dst zero" { test "update pc update jnz with operands dst not zero op1 not felt" { // Test setup const allocator = std.testing.allocator; - var operands = OperandsResult.default(); + var operands = OperandsResult{}; operands.dst = MaybeRelocatable.fromInt(u64, 1); operands.op_1 = MaybeRelocatable.fromRelocatable(Relocatable.init( 0, @@ -440,7 +441,7 @@ test "update pc update jnz with operands dst not zero op1 not felt" { test "update pc update jnz with operands dst not zero op1 felt" { // Test setup const allocator = std.testing.allocator; - var operands = OperandsResult.default(); + var operands = OperandsResult{}; operands.dst = MaybeRelocatable.fromInt(u64, 1); operands.op_1 = MaybeRelocatable.fromInt(u64, 42); // Create a new VM instance. @@ -479,7 +480,7 @@ test "update pc update jnz with operands dst not zero op1 felt" { test "CairoVM: updateAp using Add for AP update with null operands res" { // Test setup const allocator = std.testing.allocator; - var operands = OperandsResult.default(); + var operands = OperandsResult{}; operands.res = null; // Simulate unconstrained res // Create a new VM instance. var vm = try CairoVM.init(allocator, .{}); @@ -511,7 +512,7 @@ test "CairoVM: updateAp using Add for AP update with non-null operands result" { // Create an allocator for testing purposes. const allocator = std.testing.allocator; // Initialize operands result with a non-null result value. - var operands = OperandsResult.default(); + var operands = OperandsResult{}; operands.res = MaybeRelocatable.fromInt(u8, 10); // Create a new VM instance. @@ -547,7 +548,7 @@ test "CairoVM: updateAp using Add for AP update with non-null operands result" { test "CairoVM: updateAp using Add1 for AP update" { // Test setup const allocator = std.testing.allocator; - const operands = OperandsResult.default(); + const operands = OperandsResult{}; // Create a new VM instance. var vm = try CairoVM.init(allocator, .{}); defer vm.deinit(); @@ -585,7 +586,7 @@ test "CairoVM: updateAp using Add1 for AP update" { test "update ap add2" { // Test setup const allocator = std.testing.allocator; - const operands = OperandsResult.default(); + const operands = OperandsResult{}; // Create a new VM instance. var vm = try CairoVM.init(allocator, .{}); defer vm.deinit(); @@ -623,7 +624,7 @@ test "update ap add2" { test "update fp appplus2" { // Test setup const allocator = std.testing.allocator; - const operands = OperandsResult.default(); + const operands = OperandsResult{}; // Create a new VM instance. var vm = try CairoVM.init(allocator, .{}); defer vm.deinit(); @@ -661,7 +662,7 @@ test "update fp appplus2" { test "update fp dst relocatable" { // Test setup const allocator = std.testing.allocator; - var operands = OperandsResult.default(); + var operands = OperandsResult{}; operands.dst = MaybeRelocatable.fromRelocatable(Relocatable.init( 0, 42, @@ -703,7 +704,7 @@ test "update fp dst relocatable" { test "update fp dst felt" { // Test setup const allocator = std.testing.allocator; - var operands = OperandsResult.default(); + var operands = OperandsResult{}; operands.dst = MaybeRelocatable.fromInt(u64, 42); // Create a new VM instance. var vm = try CairoVM.init(allocator, .{}); @@ -2708,7 +2709,7 @@ test "CairoVM: core utility function for testing test" { test "CairoVM: OperandsResult set " { // Test setup - var operands = OperandsResult.default(); + var operands = OperandsResult{}; operands.setDst(true); // Test body @@ -2717,7 +2718,7 @@ test "CairoVM: OperandsResult set " { test "CairoVM: OperandsResult set Op1" { // Test setup - var operands = OperandsResult.default(); + var operands = OperandsResult{}; operands.setOp0(true); operands.setOp1(true); operands.setDst(true); @@ -2728,7 +2729,7 @@ test "CairoVM: OperandsResult set Op1" { test "CairoVM: OperandsResult deduced set and was functionality" { // Test setup - var operands = OperandsResult.default(); + var operands = OperandsResult{}; operands.setOp1(true); // Test body @@ -2761,7 +2762,7 @@ test "CairoVM: InserDeducedOperands should insert operands if set as deduced" { ); defer vm.segments.memory.deinitData(std.testing.allocator); - var test_operands = OperandsResult.default(); + var test_operands = OperandsResult{}; test_operands.dst_addr = dst_addr; test_operands.op_0_addr = op0_addr; test_operands.op_1_addr = op1_addr; @@ -2814,7 +2815,7 @@ test "CairoVM: InserDeducedOperands insert operands should not be inserted if no ); defer vm.segments.memory.deinitData(std.testing.allocator); - var test_operands = OperandsResult.default(); + var test_operands = OperandsResult{}; test_operands.dst_addr = dst_addr; test_operands.op_0_addr = op0_addr; test_operands.op_1_addr = op1_addr; @@ -3785,3 +3786,342 @@ test "CairoVM: verifyAutoDeductions for keccak builtin runner" { try expectEqual(void, @TypeOf(result)); } + +test "CairoVM: runInstruction without any insertion in the memory" { + // Program used: + // %builtins output + // from starkware.cairo.common.serialize import serialize_word + // func main{output_ptr: felt*}(): + // let a = 1 + // serialize_word(a) + // let b = 17 * a + // serialize_word(b) + // return() + // end + // Relocated Trace: + // [TraceEntry(pc=5, ap=18, fp=18), + // TraceEntry(pc=6, ap=19, fp=18), + // TraceEntry(pc=8, ap=20, fp=18), + // TraceEntry(pc=1, ap=22, fp=22), + // TraceEntry(pc=2, ap=22, fp=22), + // TraceEntry(pc=4, ap=23, fp=22), + // TraceEntry(pc=10, ap=23, fp=18), + + // Initialize CairoVM instance and defer its cleanup. + var vm = try CairoVM.init(std.testing.allocator, .{}); + defer vm.deinit(); + + // Set up memory with predefined data for the VM. + try vm.segments.memory.setUpMemory( + std.testing.allocator, + .{ + .{ .{ 0, 0 }, .{4612671182993129469} }, + .{ .{ 0, 1 }, .{5198983563776393216} }, + .{ .{ 0, 2 }, .{5198983563776393216} }, + .{ .{ 0, 3 }, .{2345108766317314046} }, + .{ .{ 0, 4 }, .{5191102247248822272} }, + .{ .{ 0, 5 }, .{5189976364521848832} }, + .{ .{ 0, 6 }, .{1} }, + .{ .{ 0, 7 }, .{1226245742482522112} }, + .{ .{ 0, 8 }, .{3618502788666131213697322783095070105623107215331596699973092056135872020474} }, + .{ .{ 0, 9 }, .{5189976364521848832} }, + .{ .{ 0, 10 }, .{17} }, + .{ .{ 0, 11 }, .{1226245742482522112} }, + .{ .{ 0, 12 }, .{3618502788666131213697322783095070105623107215331596699973092056135872020470} }, + .{ .{ 0, 13 }, .{2345108766317314046} }, + .{ .{ 1, 0 }, .{ 2, 0 } }, + .{ .{ 1, 1 }, .{ 3, 0 } }, + .{ .{ 1, 2 }, .{ 4, 0 } }, + .{ .{ 1, 3 }, .{ 2, 0 } }, + .{ .{ 1, 4 }, .{1} }, + .{ .{ 1, 5 }, .{ 1, 3 } }, + .{ .{ 1, 6 }, .{ 0, 9 } }, + .{ .{ 1, 7 }, .{ 2, 1 } }, + .{ .{ 1, 8 }, .{17} }, + .{ .{ 1, 9 }, .{ 1, 3 } }, + .{ .{ 1, 10 }, .{ 0, 13 } }, + .{ .{ 1, 11 }, .{ 2, 2 } }, + .{ .{ 2, 0 }, .{1} }, + .{ .{ 2, 1 }, .{17} }, + }, + ); + defer vm.segments.memory.deinitData(std.testing.allocator); + + // Mark all cells in memory as accessed except for one specific cell. + for (vm.segments.memory.data.items) |*d| { + for (d.items) |*cell| { + cell.*.?.is_accessed = true; + } + } + vm.segments.memory.data.items[1].items[1].?.is_accessed = false; + + // Add two memory segments to the VM. + _ = try vm.addMemorySegment(); + _ = try vm.addMemorySegment(); + + // Set relocation limits and initial register values. + vm.rc_limits = .{ 32764, 32769 }; + vm.run_context.pc.* = Relocatable.init(0, 13); + vm.run_context.ap.* = Relocatable.init(1, 12); + vm.run_context.fp.* = Relocatable.init(1, 3); + + // Ensure that the specific cell marked as not accessed is indeed not accessed. + try expect(!vm.segments.memory.data.items[1].items[1].?.is_accessed); + + // Ensure the current step counter is initialized to zero. + try expectEqual(@as(usize, 0), vm.current_step); + + // Execute a specific instruction with predefined properties. + try vm.runInstruction( + std.testing.allocator, + &.{ + .off_0 = -2, + .off_1 = -1, + .off_2 = -1, + .dst_reg = .FP, + .op_0_reg = .FP, + .op_1_addr = .FP, + .res_logic = .Op1, + .pc_update = .Jump, + .ap_update = .Regular, + .fp_update = .Dst, + .opcode = .Ret, + }, + ); + + // Ensure that relocation limits are correctly updated. + try expectEqual( + @as(?struct { isize, isize }, .{ 32764, 32769 }), + vm.rc_limits, + ); + + // Ensure that registers are updated as expected after the instruction execution. + try expectEqual(Relocatable.init(4, 0), vm.run_context.pc.*); + try expectEqual(Relocatable.init(1, 12), vm.run_context.ap.*); + try expectEqual(Relocatable.init(1, 0), vm.run_context.fp.*); + + // Ensure the current step counter is incremented. + try expectEqual(@as(usize, 1), vm.current_step); + + // Initialize an expected memory instance with the same data as the VM's memory. + var expected_memory = try Memory.init(std.testing.allocator); + defer expected_memory.deinit(); + + // Set a value into the memory. + try expected_memory.setUpMemory( + std.testing.allocator, + .{ + .{ .{ 0, 0 }, .{4612671182993129469} }, + .{ .{ 0, 1 }, .{5198983563776393216} }, + .{ .{ 0, 2 }, .{5198983563776393216} }, + .{ .{ 0, 3 }, .{2345108766317314046} }, + .{ .{ 0, 4 }, .{5191102247248822272} }, + .{ .{ 0, 5 }, .{5189976364521848832} }, + .{ .{ 0, 6 }, .{1} }, + .{ .{ 0, 7 }, .{1226245742482522112} }, + .{ .{ 0, 8 }, .{3618502788666131213697322783095070105623107215331596699973092056135872020474} }, + .{ .{ 0, 9 }, .{5189976364521848832} }, + .{ .{ 0, 10 }, .{17} }, + .{ .{ 0, 11 }, .{1226245742482522112} }, + .{ .{ 0, 12 }, .{3618502788666131213697322783095070105623107215331596699973092056135872020470} }, + .{ .{ 0, 13 }, .{2345108766317314046} }, + .{ .{ 1, 0 }, .{ 2, 0 } }, + .{ .{ 1, 1 }, .{ 3, 0 } }, + .{ .{ 1, 2 }, .{ 4, 0 } }, + .{ .{ 1, 3 }, .{ 2, 0 } }, + .{ .{ 1, 4 }, .{1} }, + .{ .{ 1, 5 }, .{ 1, 3 } }, + .{ .{ 1, 6 }, .{ 0, 9 } }, + .{ .{ 1, 7 }, .{ 2, 1 } }, + .{ .{ 1, 8 }, .{17} }, + .{ .{ 1, 9 }, .{ 1, 3 } }, + .{ .{ 1, 10 }, .{ 0, 13 } }, + .{ .{ 1, 11 }, .{ 2, 2 } }, + .{ .{ 2, 0 }, .{1} }, + .{ .{ 2, 1 }, .{17} }, + }, + ); + defer expected_memory.deinitData(std.testing.allocator); + + // Mark all cells in the expected memory as accessed. + for (expected_memory.data.items) |*d| { + for (d.items) |*cell| { + cell.*.?.is_accessed = true; + } + } + + // Compare each cell in VM's memory with the corresponding cell in the expected memory. + for (vm.segments.memory.data.items, 0..) |d, i| { + for (d.items, 0..) |cell, j| { + try expect(cell.?.eql(expected_memory.data.items[i].items[j].?)); + } + } +} + +test "CairoVM: runInstruction with Op0 being deduced" { + // Program used: + // %builtins output + // from starkware.cairo.common.serialize import serialize_word + // func main{output_ptr: felt*}(): + // let a = 1 + // serialize_word(a) + // let b = 17 * a + // serialize_word(b) + // return() + // end + // Relocated Trace: + // [TraceEntry(pc=5, ap=18, fp=18), + // TraceEntry(pc=6, ap=19, fp=18), + // TraceEntry(pc=8, ap=20, fp=18), + // TraceEntry(pc=1, ap=22, fp=22), + // TraceEntry(pc=2, ap=22, fp=22), + // TraceEntry(pc=4, ap=23, fp=22), + // TraceEntry(pc=10, ap=23, fp=18), + + // Initialize CairoVM instance and defer its cleanup. + var vm = try CairoVM.init(std.testing.allocator, .{}); + defer vm.deinit(); + + // Set up memory with predefined data for the VM. + try vm.segments.memory.setUpMemory( + std.testing.allocator, + .{ + .{ .{ 0, 0 }, .{4612671182993129469} }, + .{ .{ 0, 1 }, .{5198983563776393216} }, + .{ .{ 0, 2 }, .{1} }, + .{ .{ 0, 3 }, .{2345108766317314046} }, + .{ .{ 0, 4 }, .{5191102247248822272} }, + .{ .{ 0, 5 }, .{5189976364521848832} }, + .{ .{ 0, 6 }, .{1} }, + .{ .{ 0, 7 }, .{1226245742482522112} }, + .{ .{ 0, 8 }, .{3618502788666131213697322783095070105623107215331596699973092056135872020474} }, + .{ .{ 0, 9 }, .{5189976364521848832} }, + .{ .{ 0, 10 }, .{17} }, + .{ .{ 0, 11 }, .{1226245742482522112} }, + .{ .{ 0, 12 }, .{3618502788666131213697322783095070105623107215331596699973092056135872020470} }, + .{ .{ 0, 13 }, .{2345108766317314046} }, + .{ .{ 1, 0 }, .{ 2, 0 } }, + .{ .{ 1, 1 }, .{ 3, 0 } }, + .{ .{ 1, 2 }, .{ 4, 0 } }, + .{ .{ 1, 3 }, .{ 2, 0 } }, + .{ .{ 1, 4 }, .{1} }, + .{ .{ 1, 5 }, .{ 1, 3 } }, + .{ .{ 1, 6 }, .{ 0, 9 } }, + .{ .{ 1, 7 }, .{ 2, 1 } }, + .{ .{ 1, 8 }, .{17} }, + .{ .{ 2, 0 }, .{1} }, + }, + ); + defer vm.segments.memory.deinitData(std.testing.allocator); + + // Mark all cells in memory as accessed except for one specific cell. + for (vm.segments.memory.data.items) |*d| { + for (d.items) |*cell| { + cell.*.?.is_accessed = true; + } + } + // Mark a specific cell as not accessed. + vm.segments.memory.data.items[1].items[1].?.is_accessed = false; + + // Add two memory segments to the VM. + _ = try vm.addMemorySegment(); + _ = try vm.addMemorySegment(); + + // Set relocation limits and initial register values. + vm.rc_limits = .{ 32764, 32769 }; + vm.run_context.pc.* = Relocatable.init(0, 11); + vm.run_context.ap.* = Relocatable.init(1, 9); + vm.run_context.fp.* = Relocatable.init(1, 3); + + // Ensure that the specific cell marked as not accessed is indeed not accessed. + try expect(!vm.segments.memory.data.items[1].items[1].?.is_accessed); + + // Ensure the current step counter is initialized to zero. + try expectEqual(@as(usize, 0), vm.current_step); + + // Execute a specific instruction with Op0 being deduced. + try vm.runInstruction( + std.testing.allocator, + &.{ + .off_0 = 0, + .off_1 = 1, + .off_2 = 1, + .dst_reg = .AP, + .op_0_reg = .AP, + .op_1_addr = .Imm, + .res_logic = .Op1, + .pc_update = .JumpRel, + .ap_update = .Add2, + .fp_update = .APPlus2, + .opcode = .Call, + }, + ); + + // Ensure that relocation limits are correctly updated. + try expectEqual( + @as(?struct { isize, isize }, .{ 32764, 32769 }), + vm.rc_limits, + ); + + // Ensure that registers are updated as expected after the instruction execution. + try expectEqual(Relocatable.init(0, 0), vm.run_context.pc.*); + try expectEqual(Relocatable.init(1, 11), vm.run_context.ap.*); + try expectEqual(Relocatable.init(1, 11), vm.run_context.fp.*); + + // Ensure the current step counter is incremented. + try expectEqual(@as(usize, 1), vm.current_step); + + // Initialize an expected memory instance. + var expected_memory = try Memory.init(std.testing.allocator); + defer expected_memory.deinit(); + + // Set a value into the memory. + try expected_memory.setUpMemory( + std.testing.allocator, + .{ + .{ .{ 0, 0 }, .{4612671182993129469} }, + .{ .{ 0, 1 }, .{5198983563776393216} }, + .{ .{ 0, 2 }, .{1} }, + .{ .{ 0, 3 }, .{2345108766317314046} }, + .{ .{ 0, 4 }, .{5191102247248822272} }, + .{ .{ 0, 5 }, .{5189976364521848832} }, + .{ .{ 0, 6 }, .{1} }, + .{ .{ 0, 7 }, .{1226245742482522112} }, + .{ .{ 0, 8 }, .{3618502788666131213697322783095070105623107215331596699973092056135872020474} }, + .{ .{ 0, 9 }, .{5189976364521848832} }, + .{ .{ 0, 10 }, .{17} }, + .{ .{ 0, 11 }, .{1226245742482522112} }, + .{ .{ 0, 12 }, .{3618502788666131213697322783095070105623107215331596699973092056135872020470} }, + .{ .{ 0, 13 }, .{2345108766317314046} }, + .{ .{ 1, 0 }, .{ 2, 0 } }, + .{ .{ 1, 1 }, .{ 3, 0 } }, + .{ .{ 1, 2 }, .{ 4, 0 } }, + .{ .{ 1, 3 }, .{ 2, 0 } }, + .{ .{ 1, 4 }, .{1} }, + .{ .{ 1, 5 }, .{ 1, 3 } }, + .{ .{ 1, 6 }, .{ 0, 9 } }, + .{ .{ 1, 7 }, .{ 2, 1 } }, + .{ .{ 1, 8 }, .{17} }, + .{ .{ 1, 9 }, .{ 1, 3 } }, + .{ .{ 1, 10 }, .{ 0, 13 } }, + .{ .{ 2, 0 }, .{1} }, + }, + ); + defer expected_memory.deinitData(std.testing.allocator); + + // Mark all cells in the expected memory as accessed. + for (expected_memory.data.items) |*d| { + for (d.items) |*cell| { + cell.*.?.is_accessed = true; + } + } + // Mark a specific cell in the expected memory as not accessed. + expected_memory.data.items[1].items[1].?.is_accessed = false; + + // Compare each cell in VM's memory with the corresponding cell in the expected memory. + for (vm.segments.memory.data.items, 0..) |d, i| { + for (d.items, 0..) |cell, j| { + try expect(cell.?.eql(expected_memory.data.items[i].items[j].?)); + } + } +} diff --git a/src/vm/memory/memory.zig b/src/vm/memory/memory.zig index 01d345bd..0a1381df 100644 --- a/src/vm/memory/memory.zig +++ b/src/vm/memory/memory.zig @@ -29,7 +29,7 @@ pub const MemoryCell = struct { /// The index or relocation information of the memory segment. maybe_relocatable: MaybeRelocatable, /// Indicates whether the MemoryCell has been accessed. - is_accessed: bool, + is_accessed: bool = false, /// Creates a new MemoryCell. /// @@ -37,13 +37,8 @@ pub const MemoryCell = struct { /// - `maybe_relocatable`: The index or relocation information of the memory segment. /// # Returns /// A new MemoryCell. - pub fn init( - maybe_relocatable: MaybeRelocatable, - ) Self { - return .{ - .maybe_relocatable = maybe_relocatable, - .is_accessed = false, - }; + pub fn init(maybe_relocatable: MaybeRelocatable) Self { + return .{ .maybe_relocatable = maybe_relocatable }; } /// Checks equality between two MemoryCell instances.