From fdc03ee7e026c8ecf7fb222ee64cc81b9b6d5823 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Thu, 29 Feb 2024 13:14:09 +0100 Subject: [PATCH] Add `getMemoryHoles` for Cairo runner (#410) * Add getMemoryHoles for Cairo runner * fix conflicts --- src/vm/core.zig | 2 +- src/vm/runners/cairo_runner.zig | 200 ++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 1 deletion(-) diff --git a/src/vm/core.zig b/src/vm/core.zig index 2a07dfe3..1cab923b 100644 --- a/src/vm/core.zig +++ b/src/vm/core.zig @@ -1317,7 +1317,7 @@ pub const CairoVM = struct { pub fn decodeCurrentInstruction(self: *const Self) !Instruction { const felt = try self.segments.memory.getFelt(self.run_context.getPC()); - const instruction = felt.intoU64() catch + const instruction = felt.intoU64() catch return CairoVMError.InvalidInstructionEncoding; return decoder.decodeInstructions(instruction); diff --git a/src/vm/runners/cairo_runner.zig b/src/vm/runners/cairo_runner.zig index 9da04920..39253ada 100644 --- a/src/vm/runners/cairo_runner.zig +++ b/src/vm/runners/cairo_runner.zig @@ -652,6 +652,37 @@ pub const CairoRunner = struct { return builtin_segment_info; } + /// Retrieves the number of memory holes in the CairoRunner's virtual machine (VM) segments. + /// + /// Memory holes are regions of memory that are unused or uninitialized. + /// This function calculates the number of memory holes based on the VM's segments + /// and the presence of certain built-in runners. + /// + /// # Arguments + /// + /// - `self`: A mutable reference to the CairoRunner instance. + /// + /// # Returns + /// + /// The number of memory holes in the VM segments. + /// + /// # Errors + /// + /// - Returns a `MemoryError` if any errors occur during the calculation. + pub fn getMemoryHoles(self: *Self) !usize { + // Check if the program has the output builtin + var has_output_builtin: bool = false; + for (self.program.builtins.items) |builtin| { + if (builtin == .output) has_output_builtin = true; + } + + // Calculate memory holes based on VM segments and built-in runners + return self.vm.segments.getMemoryHoles( + self.vm.builtin_runners.items.len, + has_output_builtin, + ); + } + /// Retrieves the permanent range check limits from the CairoRunner instance. /// /// This function iterates through the builtin runners of the CairoRunner and gathers @@ -1617,3 +1648,172 @@ test "CairoRunner: initial FP with a simple program" { // Verify that the initial function pointer (FP) is correct. try expectEqual(Relocatable.init(1, 2), cairo_runner.initial_fp); } + +test "CairoRunner: getMemoryHoles with missing segment used sizes" { + // Initialize a CairoRunner with an empty program, "plain" layout, and empty instructions. + var cairo_runner = try CairoRunner.init( + std.testing.allocator, + try Program.initDefault(std.testing.allocator, true), + "plain", + ArrayList(MaybeRelocatable).init(std.testing.allocator), + try CairoVM.init(std.testing.allocator, .{}), + false, + ); + + // Defer the deinitialization of the CairoRunner to ensure cleanup. + defer cairo_runner.deinit(std.testing.allocator); + + // Set up memory for the Cairo VM with missing segment used sizes. + // Allocator for memory allocation + try cairo_runner.vm.segments.memory.setUpMemory( + std.testing.allocator, + .{.{ .{ 0, 0 }, .{9} }}, + ); + // Defer memory cleanup + defer cairo_runner.vm.segments.memory.deinitData(std.testing.allocator); + + // Mark the memory as accessed (placeholder operation) + cairo_runner.vm.segments.memory.markAsAccessed(.{}); + + // Test that invoking `getMemoryHoles` function throws the expected error. + try expectError(MemoryError.MissingSegmentUsedSizes, cairo_runner.getMemoryHoles()); +} + +test "CairoRunner: getMemoryHoles empty" { + // Initialize a CairoRunner with an empty program, "plain" layout, and empty instructions. + var cairo_runner = try CairoRunner.init( + std.testing.allocator, + try Program.initDefault(std.testing.allocator, true), + "plain", + ArrayList(MaybeRelocatable).init(std.testing.allocator), + try CairoVM.init(std.testing.allocator, .{}), + false, + ); + + // Defer the deinitialization of the CairoRunner to ensure cleanup. + defer cairo_runner.deinit(std.testing.allocator); + + // Test that invoking `getMemoryHoles` function returns 0 when there are no memory holes. + try expectEqual(@as(usize, 0), try cairo_runner.getMemoryHoles()); +} + +test "CairoRunner: getMemoryHoles with segment used size" { + // Initialize a CairoRunner with an empty program, "plain" layout, and empty instructions. + // Allocator for memory allocation + var cairo_runner = try CairoRunner.init( + std.testing.allocator, + try Program.initDefault(std.testing.allocator, true), + "plain", + ArrayList(MaybeRelocatable).init(std.testing.allocator), + try CairoVM.init(std.testing.allocator, .{}), + false, + ); + + // Defer the deinitialization of the CairoRunner to ensure cleanup. + defer cairo_runner.deinit(std.testing.allocator); + + // Set up memory for the Cairo VM with specified segment used sizes. + // Allocator for memory allocation + try cairo_runner.vm.segments.memory.setUpMemory( + std.testing.allocator, + .{ + .{ .{ 0, 0 }, .{0} }, + .{ .{ 0, 2 }, .{0} }, + }, + ); + // Defer memory cleanup + defer cairo_runner.vm.segments.memory.deinitData(std.testing.allocator); + + // Specify segment used size for segment 0 + try cairo_runner.vm.segments.segment_used_sizes.put(0, 4); + + // Mark the specified memory cells as accessed (placeholder operation) + cairo_runner.vm.segments.memory.markAsAccessed(.{}); + cairo_runner.vm.segments.memory.markAsAccessed(Relocatable.init(0, 2)); + + // Test that invoking `getMemoryHoles` function returns the expected number of memory holes. + try expectEqual(@as(usize, 2), try cairo_runner.getMemoryHoles()); +} + +test "CairoRunner: getMemoryHoles with empty accesses" { + // Initialize a CairoRunner with an empty program, "plain" layout, and empty instructions. + // Allocator for memory allocation + var cairo_runner = try CairoRunner.init( + std.testing.allocator, + try Program.initDefault(std.testing.allocator, true), + "plain", + ArrayList(MaybeRelocatable).init(std.testing.allocator), + try CairoVM.init(std.testing.allocator, .{}), + false, + ); + + // Defer the deinitialization of the CairoRunner to ensure cleanup. + defer cairo_runner.deinit(std.testing.allocator); + + // Set up memory for the Cairo VM with specified memory cell accesses. + // Allocator for memory allocation + try cairo_runner.vm.segments.memory.setUpMemory( + std.testing.allocator, + .{ + .{ .{ 1, 0 }, .{0} }, + .{ .{ 1, 2 }, .{2} }, + }, + ); + // Defer memory cleanup + defer cairo_runner.vm.segments.memory.deinitData(std.testing.allocator); + + // Mark specified memory cells as accessed in segment 1 + cairo_runner.vm.segments.memory.markAsAccessed(Relocatable.init(1, 0)); + cairo_runner.vm.segments.memory.markAsAccessed(Relocatable.init(1, 2)); + + // Initialize an OutputBuiltinRunner for testing + var output_builtin: BuiltinRunner = .{ + .Output = OutputBuiltinRunner.init(std.testing.allocator, true), + }; + + // Initialize segments for the OutputBuiltinRunner + try output_builtin.initSegments(cairo_runner.vm.segments); + + // Append the OutputBuiltinRunner to the list of builtin runners in Cairo VM + try cairo_runner.vm.builtin_runners.append(output_builtin); + + // Specify segment used sizes for both segments + try cairo_runner.vm.segments.segment_used_sizes.put(0, 4); + try cairo_runner.vm.segments.segment_used_sizes.put(1, 4); + + // Test that invoking `getMemoryHoles` function returns the expected number of memory holes. + try expectEqual(@as(usize, 2), try cairo_runner.getMemoryHoles()); +} + +test "CairoRunner: getMemoryHoles basic test" { + // Initialize a CairoRunner with an empty program, "plain" layout, and empty instructions. + // Allocator for memory allocation + var cairo_runner = try CairoRunner.init( + std.testing.allocator, + try Program.initDefault(std.testing.allocator, true), + "plain", + ArrayList(MaybeRelocatable).init(std.testing.allocator), + try CairoVM.init(std.testing.allocator, .{}), + false, + ); + + // Defer the deinitialization of the CairoRunner to ensure cleanup. + defer cairo_runner.deinit(std.testing.allocator); + + // Initialize an OutputBuiltinRunner for testing + var output_builtin: BuiltinRunner = .{ + .Output = OutputBuiltinRunner.init(std.testing.allocator, true), + }; + + // Initialize segments for the OutputBuiltinRunner + try output_builtin.initSegments(cairo_runner.vm.segments); + + // Append the OutputBuiltinRunner to the list of builtin runners in Cairo VM + try cairo_runner.vm.builtin_runners.append(output_builtin); + + // Specify segment used sizes for segment 0 + try cairo_runner.vm.segments.segment_used_sizes.put(0, 4); + + // Test that invoking `getMemoryHoles` function returns 0 as there are no memory holes. + try expectEqual(@as(usize, 0), try cairo_runner.getMemoryHoles()); +}