Skip to content

Commit

Permalink
feat: add util functions to the MemorySegmentManager structure (kee…
Browse files Browse the repository at this point in the history
…p-starknet-strange#113)

* add computeEffectiveSize util and unit tests

* add getSegmentSize util and unit tests

* add isValidMemoryValue util and unit tests

* clean up imports
  • Loading branch information
tcoratger authored Nov 8, 2023
1 parent 0904c68 commit d63738c
Show file tree
Hide file tree
Showing 3 changed files with 270 additions and 18 deletions.
2 changes: 1 addition & 1 deletion src/vm/builtins/builtin_runner/range_check.zig
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ pub const RangeCheckBuiltinRunner = struct {
/// The number of used cells as a `u32`, or `MemoryError.MissingSegmentUsedSizes` if
/// the size is not available.
pub fn getUsedCells(self: *const Self, segments: *MemorySegmentManager) !u32 {
return segments.get_segment_used_size(
return segments.getSegmentUsedSize(
@intCast(self.base),
) orelse MemoryError.MissingSegmentUsedSizes;
}
Expand Down
8 changes: 4 additions & 4 deletions src/vm/memory/memory.zig
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ pub const Memory = struct {
/// The allocator used to allocate the memory.
allocator: Allocator,
// The data in the memory.
data: std.HashMap(
data: std.ArrayHashMap(
Relocatable,
MaybeRelocatable,
std.hash_map.AutoContext(Relocatable),
std.hash_map.default_max_load_percentage,
std.array_hash_map.AutoContext(Relocatable),
true,
),
// The number of segments in the memory.
num_segments: u32,
Expand Down Expand Up @@ -65,7 +65,7 @@ pub const Memory = struct {

memory.* = Self{
.allocator = allocator,
.data = std.AutoHashMap(
.data = std.AutoArrayHashMap(
Relocatable,
MaybeRelocatable,
).init(allocator),
Expand Down
278 changes: 265 additions & 13 deletions src/vm/memory/segments.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const expectEqualSlices = std.testing.expectEqualSlices;
const expectError = std.testing.expectError;

// Local imports.
const Memory = @import("memory.zig").Memory;
const relocatable = @import("relocatable.zig");
const starknet_felt = @import("../../math/fields/starknet.zig");
const Relocatable = @import("relocatable.zig").Relocatable;
const MaybeRelocatable = @import("relocatable.zig").MaybeRelocatable;
const Felt252 = @import("../../math/fields/starknet.zig").Felt252;

// MemorySegmentManager manages the list of memory segments.
// Also holds metadata useful for the relocation process of
Expand All @@ -21,11 +25,11 @@ pub const MemorySegmentManager = struct {
/// The allocator used to allocate the memory.
allocator: Allocator,
// The size of the used segments.
segment_used_sizes: std.HashMap(
segment_used_sizes: std.ArrayHashMap(
u32,
u32,
std.hash_map.AutoContext(u32),
std.hash_map.default_max_load_percentage,
std.array_hash_map.AutoContext(u32),
false,
),
// The size of the segments.
segment_sizes: std.HashMap(
Expand Down Expand Up @@ -65,7 +69,7 @@ pub const MemorySegmentManager = struct {
// Initialize the values of the MemorySegmentManager struct.
segment_manager.* = .{
.allocator = allocator,
.segment_used_sizes = std.AutoHashMap(
.segment_used_sizes = std.AutoArrayHashMap(
u32,
u32,
).init(allocator),
Expand Down Expand Up @@ -98,9 +102,9 @@ pub const MemorySegmentManager = struct {
// ************************************************************

// Adds a memory segment and returns the first address of the new segment.
pub fn addSegment(self: *Self) relocatable.Relocatable {
pub fn addSegment(self: *Self) Relocatable {
// Create the relocatable address for the new segment.
const relocatable_address = relocatable.Relocatable{
const relocatable_address = Relocatable{
.segment_index = self.memory.num_segments,
.offset = 0,
};
Expand All @@ -110,16 +114,85 @@ pub const MemorySegmentManager = struct {

return relocatable_address;
}

/// Retrieves the size of a memory segment by its index if available, else returns null.
///
/// # Parameters
/// - `index` (u32): The index of the memory segment.
///
/// # Returns
/// A `u32` representing the size of the segment or null if not computed.
pub fn get_segment_used_size(self: *Self, index: u32) ?u32 {
pub fn getSegmentUsedSize(self: *Self, index: u32) ?u32 {
return self.segment_used_sizes.get(index) orelse null;
}

/// Retrieves the number of memory segments.
///
/// # Returns
///
/// The number of memory segments as a `usize`.
pub fn numSegments(self: *Self) usize {
return self.memory.data.count();
}

/// Computes and returns the effective size of memory segments.
///
/// This function iterates through memory segments, calculates their effective sizes, and
/// updates the segment sizes map accordingly.
///
/// # Returns
///
/// An `AutoArrayHashMap` representing the computed effective sizes of memory segments.
pub fn computeEffectiveSize(self: *Self) !std.AutoArrayHashMap(u32, u32) {
for (self.memory.data.keys()) |item| {
const offset = self.segment_used_sizes.get(@intCast(item.segment_index));
if (offset == null or offset.? < item.offset + 1) {
try self.segment_used_sizes.put(
@intCast(item.segment_index),
@intCast(item.offset + 1),
);
}
}
return self.segment_used_sizes;
}

/// Retrieves the size of a memory segment by its index if available, else computes it.
///
/// This function attempts to retrieve the size of a memory segment by its index. If the size
/// is not available in the segment sizes map, it calculates the effective size and returns it.
///
/// # Parameters
///
/// - `index` (u32): The index of the memory segment.
///
/// # Returns
///
/// A `u32` representing the size of the segment or a computed effective size if not available.
pub fn getSegmentSize(self: *Self, index: u32) ?u32 {
return self.segment_sizes.get(index) orelse self.getSegmentUsedSize(index);
}

/// Checks if a memory value is valid within the MemorySegmentManager.
///
/// This function validates whether a given memory value is within the bounds
/// of the memory segments managed by the MemorySegmentManager.
///
/// # Parameters
///
/// - `value` (*MaybeRelocatable): The memory value to validate.
///
/// # Returns
///
/// A boolean value indicating the validity of the memory value.
pub fn isValidMemoryValue(self: *Self, value: *MaybeRelocatable) bool {
return switch (value.*) {
.felt => true,
.relocatable => |item| @as(
usize,
@intCast(item.segment_index),
) < self.segment_used_sizes.count(),
};
}
};

// ************************************************************
Expand All @@ -142,7 +215,7 @@ test "memory segment manager" {

// Check if the relocatable address is correct.
try expectEqual(
relocatable.Relocatable{
Relocatable{
.segment_index = 0,
.offset = 0,
},
Expand All @@ -157,7 +230,7 @@ test "memory segment manager" {

// Check if the relocatable address is correct.
try expectEqual(
relocatable.Relocatable{
Relocatable{
.segment_index = 1,
.offset = 0,
},
Expand All @@ -182,13 +255,13 @@ test "set get integer value in segment memory" {
_ = memory_segment_manager.addSegment();
_ = memory_segment_manager.addSegment();

const address = relocatable.Relocatable.new(
const address = Relocatable.new(
0,
0,
);
const value = relocatable.fromFelt(starknet_felt.Felt252.fromInteger(42));
const value = relocatable.fromFelt(Felt252.fromInteger(42));

const wrong_address = relocatable.Relocatable.new(0, 1);
const wrong_address = Relocatable.new(0, 1);

_ = try memory_segment_manager.memory.set(address, value);

Expand All @@ -203,3 +276,182 @@ test "set get integer value in segment memory" {

try expect(expected_value.eq(actual_value));
}

test "MemorySegmentManager: getSegmentUsedSize should return the size of a memory segment by its index if available" {
var memory_segment_manager = try MemorySegmentManager.init(std.testing.allocator);
defer memory_segment_manager.deinit();
try memory_segment_manager.segment_used_sizes.put(10, 4);
try expectEqual(
@as(u32, @intCast(4)),
memory_segment_manager.getSegmentUsedSize(10).?,
);
}

test "MemorySegmentManager: getSegmentUsedSize should return null if index not available" {
var memory_segment_manager = try MemorySegmentManager.init(std.testing.allocator);
defer memory_segment_manager.deinit();
try expectEqual(
@as(?u32, null),
memory_segment_manager.getSegmentUsedSize(10),
);
}

test "MemorySegmentManager: numSegments should return the number of segments in the real memory" {
var memory_segment_manager = try MemorySegmentManager.init(std.testing.allocator);
defer memory_segment_manager.deinit();
try memory_segment_manager.memory.data.put(Relocatable.new(0, 1), .{ .felt = Felt252.fromInteger(10) });
try memory_segment_manager.memory.data.put(Relocatable.new(1, 1), .{ .felt = Felt252.fromInteger(10) });
try memory_segment_manager.memory.data.put(Relocatable.new(2, 1), .{ .felt = Felt252.fromInteger(10) });
try memory_segment_manager.memory.data.put(Relocatable.new(3, 1), .{ .felt = Felt252.fromInteger(10) });
try expectEqual(
@as(usize, 4),
memory_segment_manager.numSegments(),
);
}

test "MemorySegmentManager: computeEffectiveSize for one segment memory" {
var memory_segment_manager = try MemorySegmentManager.init(std.testing.allocator);
defer memory_segment_manager.deinit();
try memory_segment_manager.memory.data.put(Relocatable.new(0, 0), .{ .felt = Felt252.fromInteger(1) });
try memory_segment_manager.memory.data.put(Relocatable.new(0, 1), .{ .felt = Felt252.fromInteger(1) });
try memory_segment_manager.memory.data.put(Relocatable.new(0, 2), .{ .felt = Felt252.fromInteger(1) });

var actual = try memory_segment_manager.computeEffectiveSize();

try expectEqual(@as(usize, 1), actual.count());
try expectEqual(@as(u32, 3), actual.get(0).?);
}

test "MemorySegmentManager: computeEffectiveSize for one segment memory with gap" {
var memory_segment_manager = try MemorySegmentManager.init(std.testing.allocator);
defer memory_segment_manager.deinit();
_ = memory_segment_manager.addSegment();
try memory_segment_manager.memory.data.put(Relocatable.new(0, 6), .{ .felt = Felt252.fromInteger(1) });

var actual = try memory_segment_manager.computeEffectiveSize();

try expectEqual(@as(usize, 1), actual.count());
try expectEqual(@as(u32, 7), actual.get(0).?);
}

test "MemorySegmentManager: computeEffectiveSize for one segment memory with gaps" {
var memory_segment_manager = try MemorySegmentManager.init(std.testing.allocator);
defer memory_segment_manager.deinit();
try memory_segment_manager.memory.data.put(Relocatable.new(0, 3), .{ .felt = Felt252.fromInteger(1) });
try memory_segment_manager.memory.data.put(Relocatable.new(0, 4), .{ .felt = Felt252.fromInteger(1) });
try memory_segment_manager.memory.data.put(Relocatable.new(0, 7), .{ .felt = Felt252.fromInteger(1) });
try memory_segment_manager.memory.data.put(Relocatable.new(0, 9), .{ .felt = Felt252.fromInteger(1) });

var actual = try memory_segment_manager.computeEffectiveSize();

try expectEqual(@as(usize, 1), actual.count());
try expectEqual(@as(u32, 10), actual.get(0).?);
}

test "MemorySegmentManager: computeEffectiveSize for three segment memory" {
var memory_segment_manager = try MemorySegmentManager.init(std.testing.allocator);
defer memory_segment_manager.deinit();
try memory_segment_manager.memory.data.put(Relocatable.new(0, 0), .{ .felt = Felt252.fromInteger(1) });
try memory_segment_manager.memory.data.put(Relocatable.new(0, 1), .{ .felt = Felt252.fromInteger(1) });
try memory_segment_manager.memory.data.put(Relocatable.new(0, 2), .{ .felt = Felt252.fromInteger(1) });

try memory_segment_manager.memory.data.put(Relocatable.new(1, 0), .{ .felt = Felt252.fromInteger(1) });
try memory_segment_manager.memory.data.put(Relocatable.new(1, 1), .{ .felt = Felt252.fromInteger(1) });
try memory_segment_manager.memory.data.put(Relocatable.new(1, 2), .{ .felt = Felt252.fromInteger(1) });

try memory_segment_manager.memory.data.put(Relocatable.new(2, 0), .{ .felt = Felt252.fromInteger(1) });
try memory_segment_manager.memory.data.put(Relocatable.new(2, 1), .{ .felt = Felt252.fromInteger(1) });
try memory_segment_manager.memory.data.put(Relocatable.new(2, 2), .{ .felt = Felt252.fromInteger(1) });

var actual = try memory_segment_manager.computeEffectiveSize();

try expectEqual(@as(usize, 3), actual.count());
try expectEqual(@as(u32, 3), actual.get(0).?);
try expectEqual(@as(u32, 3), actual.get(1).?);
try expectEqual(@as(u32, 3), actual.get(2).?);
}

test "MemorySegmentManager: computeEffectiveSize for three segment memory with gaps" {
var memory_segment_manager = try MemorySegmentManager.init(std.testing.allocator);
defer memory_segment_manager.deinit();
try memory_segment_manager.memory.data.put(Relocatable.new(0, 2), .{ .felt = Felt252.fromInteger(1) });
try memory_segment_manager.memory.data.put(Relocatable.new(0, 5), .{ .felt = Felt252.fromInteger(1) });
try memory_segment_manager.memory.data.put(Relocatable.new(0, 7), .{ .felt = Felt252.fromInteger(1) });

try memory_segment_manager.memory.data.put(Relocatable.new(1, 1), .{ .felt = Felt252.fromInteger(1) });

try memory_segment_manager.memory.data.put(Relocatable.new(2, 2), .{ .felt = Felt252.fromInteger(1) });
try memory_segment_manager.memory.data.put(Relocatable.new(2, 4), .{ .felt = Felt252.fromInteger(1) });
try memory_segment_manager.memory.data.put(Relocatable.new(2, 7), .{ .felt = Felt252.fromInteger(1) });

var actual = try memory_segment_manager.computeEffectiveSize();

try expectEqual(@as(usize, 3), actual.count());
try expectEqual(@as(u32, 8), actual.get(0).?);
try expectEqual(@as(u32, 2), actual.get(1).?);
try expectEqual(@as(u32, 8), actual.get(2).?);
}

test "MemorySegmentManager: getSegmentUsedSize after computeEffectiveSize" {
var memory_segment_manager = try MemorySegmentManager.init(std.testing.allocator);
defer memory_segment_manager.deinit();
try memory_segment_manager.memory.data.put(Relocatable.new(0, 2), .{ .felt = Felt252.fromInteger(1) });
try memory_segment_manager.memory.data.put(Relocatable.new(0, 5), .{ .felt = Felt252.fromInteger(1) });
try memory_segment_manager.memory.data.put(Relocatable.new(0, 7), .{ .felt = Felt252.fromInteger(1) });

try memory_segment_manager.memory.data.put(Relocatable.new(1, 1), .{ .felt = Felt252.fromInteger(1) });

try memory_segment_manager.memory.data.put(Relocatable.new(2, 2), .{ .felt = Felt252.fromInteger(1) });
try memory_segment_manager.memory.data.put(Relocatable.new(2, 4), .{ .felt = Felt252.fromInteger(1) });
try memory_segment_manager.memory.data.put(Relocatable.new(2, 7), .{ .felt = Felt252.fromInteger(1) });

_ = try memory_segment_manager.computeEffectiveSize();

try expectEqual(@as(usize, 3), memory_segment_manager.segment_used_sizes.count());
try expectEqual(@as(u32, 8), memory_segment_manager.segment_used_sizes.get(0).?);
try expectEqual(@as(u32, 2), memory_segment_manager.segment_used_sizes.get(1).?);
try expectEqual(@as(u32, 8), memory_segment_manager.segment_used_sizes.get(2).?);
}

test "MemorySegmentManager: getSegmentSize should return the size of the segment if contained in segment_sizes" {
var memory_segment_manager = try MemorySegmentManager.init(std.testing.allocator);
defer memory_segment_manager.deinit();
try memory_segment_manager.segment_sizes.put(10, 105);
try expectEqual(@as(u32, 105), memory_segment_manager.getSegmentSize(10).?);
}

test "MemorySegmentManager: getSegmentSize should return the size of the segment via getSegmentUsedSize if not contained in segment_sizes" {
var memory_segment_manager = try MemorySegmentManager.init(std.testing.allocator);
defer memory_segment_manager.deinit();
try memory_segment_manager.segment_used_sizes.put(3, 6);
try expectEqual(@as(u32, 6), memory_segment_manager.getSegmentSize(3).?);
}

test "MemorySegmentManager: getSegmentSize should return null if missing segment" {
var memory_segment_manager = try MemorySegmentManager.init(std.testing.allocator);
defer memory_segment_manager.deinit();
try expectEqual(@as(?u32, null), memory_segment_manager.getSegmentSize(3));
}

test "MemorySegmentManager: isValidMemoryValue should return true if Felt" {
var memory_segment_manager = try MemorySegmentManager.init(std.testing.allocator);
defer memory_segment_manager.deinit();
var value: MaybeRelocatable = .{ .felt = Felt252.zero() };
try expect(memory_segment_manager.isValidMemoryValue(&value));
}

test "MemorySegmentManager: isValidMemoryValue should return false if invalid segment" {
var memory_segment_manager = try MemorySegmentManager.init(std.testing.allocator);
defer memory_segment_manager.deinit();
try memory_segment_manager.segment_used_sizes.put(0, 10);
var value: MaybeRelocatable = .{ .relocatable = Relocatable.new(1, 1) };
try expect(!memory_segment_manager.isValidMemoryValue(&value));
}

test "MemorySegmentManager: isValidMemoryValue should return true if valid segment" {
var memory_segment_manager = try MemorySegmentManager.init(std.testing.allocator);
defer memory_segment_manager.deinit();
try memory_segment_manager.segment_used_sizes.put(0, 10);
var value: MaybeRelocatable = .{ .relocatable = Relocatable.new(0, 5) };
try expect(memory_segment_manager.isValidMemoryValue(&value));
}

0 comments on commit d63738c

Please sign in to comment.