Skip to content

Commit

Permalink
Feat/check range check usage (keep-starknet-strange#412)
Browse files Browse the repository at this point in the history
* builtin runner methods

* ,

merge conflict

* temp code

* change back imports

* fix merge conflicts

* fmt after merge
  • Loading branch information
StringNick authored Feb 29, 2024
1 parent fdc03ee commit e2a3dc5
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 22 deletions.
103 changes: 86 additions & 17 deletions src/vm/builtins/builtin_runner/builtin_runner.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ const Allocator = std.mem.Allocator;
const Tuple = std.meta.Tuple;

const MemorySegmentManager = @import("../../memory/segments.zig").MemorySegmentManager;
const CairoVM = @import("../../../vm/core.zig").CairoVM;
const MemoryError = @import("../../../vm/error.zig").MemoryError;
const InsufficientAllocatedCellsError = @import("../../../vm/error.zig").InsufficientAllocatedCellsError;
const BitwiseBuiltinRunner = @import("./bitwise.zig").BitwiseBuiltinRunner;
const EcOpBuiltinRunner = @import("./ec_op.zig").EcOpBuiltinRunner;
const HashBuiltinRunner = @import("./hash.zig").HashBuiltinRunner;
const KeccakBuiltinRunner = @import("./keccak.zig").KeccakBuiltinRunner;
const OutputBuiltinRunner = @import("./output.zig").OutputBuiltinRunner;
const PoseidonBuiltinRunner = @import("./poseidon.zig").PoseidonBuiltinRunner;
const RangeCheckBuiltinRunner = @import("./range_check.zig").RangeCheckBuiltinRunner;
const SegmentArenaBuiltinRunner = @import("./segment_arena.zig").SegmentArenaBuiltinRunner;
const SignatureBuiltinRunner = @import("./signature.zig").SignatureBuiltinRunner;
const BitwiseBuiltinRunner = @import("bitwise.zig").BitwiseBuiltinRunner;
const EcOpBuiltinRunner = @import("ec_op.zig").EcOpBuiltinRunner;
const HashBuiltinRunner = @import("hash.zig").HashBuiltinRunner;
const KeccakBuiltinRunner = @import("keccak.zig").KeccakBuiltinRunner;
const OutputBuiltinRunner = @import("output.zig").OutputBuiltinRunner;
const PoseidonBuiltinRunner = @import("poseidon.zig").PoseidonBuiltinRunner;
const RangeCheckBuiltinRunner = @import("range_check.zig").RangeCheckBuiltinRunner;
const SegmentArenaBuiltinRunner = @import("segment_arena.zig").SegmentArenaBuiltinRunner;
const SignatureBuiltinRunner = @import("signature.zig").SignatureBuiltinRunner;

const InsufficientAllocatedCellsError = @import("../../error.zig").InsufficientAllocatedCellsError;
const MemoryError = @import("../../error.zig").MemoryError;
const CairoVM = @import("../../core.zig").CairoVM;
const Relocatable = @import("../../memory/relocatable.zig").Relocatable;
const MaybeRelocatable = @import("../../memory/relocatable.zig").MaybeRelocatable;
const Memory = @import("../../memory/memory.zig").Memory;
Expand Down Expand Up @@ -317,7 +318,7 @@ pub const BuiltinRunner = union(BuiltinName) {
/// # Returns
///
/// The total number of used memory cells as a `usize`, or an error if calculation fails.
pub fn getUsedCells(self: *Self, segments: *MemorySegmentManager) !usize {
pub fn getUsedCells(self: *const Self, segments: *MemorySegmentManager) !usize {
return switch (self.*) {
.SegmentArena => 0,
inline else => |*builtin| try builtin.getUsedCells(segments),
Expand All @@ -342,7 +343,7 @@ pub const BuiltinRunner = union(BuiltinName) {
///
/// Returns an error if any error occurs during the computation.
///
pub fn getAllocatedMemoryUnits(self: *Self, vm: *CairoVM) !usize {
pub fn getAllocatedMemoryUnits(self: *const Self, vm: *CairoVM) !usize {
switch (self.*) {
// For Output and SegmentArena built-in runners, return 0 as they do not allocate memory units
.Output, .SegmentArena => return 0,
Expand Down Expand Up @@ -401,7 +402,7 @@ pub const BuiltinRunner = union(BuiltinName) {
///
/// This function is used to determine the usage and allocation status of memory cells associated
/// with the specific type of built-in runner.
pub fn getUsedCellsAndAllocatedSize(self: *Self, vm: *CairoVM) !Tuple(&.{ usize, ?usize }) {
pub fn getUsedCellsAndAllocatedSize(self: *const Self, vm: *CairoVM) !Tuple(&.{ usize, ?usize }) {
switch (self.*) {
// For output and segment arena built-in runners
.Output, .SegmentArena => {
Expand Down Expand Up @@ -442,7 +443,7 @@ pub const BuiltinRunner = union(BuiltinName) {
///
/// This function is used to determine the usage of permanent range check units associated
/// with the specific type of built-in runner.
pub fn getUsedPermRangeCheckUnits(self: *Self, vm: *CairoVM) !usize {
pub fn getUsedPermRangeCheckUnits(self: *const Self, vm: *CairoVM) !usize {
return switch (self.*) {
// For range check built-in runners
.RangeCheck => |range_check| {
Expand Down Expand Up @@ -519,7 +520,7 @@ pub const BuiltinRunner = union(BuiltinName) {
/// # Returns
///
/// A `usize` representing the number of instances per component for the built-in runner.
pub fn getInstancesPerComponent(self: *Self) u32 {
pub fn getInstancesPerComponent(self: *const Self) u32 {
return switch (self.*) {
.SegmentArena, .Output => 1,
inline else => |*builtin| builtin.instances_per_component,
Expand Down Expand Up @@ -945,6 +946,26 @@ test "BuiltinRunner: getMemoryAccesses with missing segment used sizes" {
);
}

test "BuiltinRunner: getAllocataedMemoryUnits Output" {
var builtin = BuiltinRunner{
.Output = OutputBuiltinRunner.init(std.testing.allocator, true),
};
defer builtin.deinit();

var vm = try CairoVM.init(
std.testing.allocator,
.{},
);
// Ensure Cairo VM is properly deallocated at the end of the test
defer vm.deinit();

// Expecting an error of type MemoryError.MissingSegmentUsedSizes
try expectError(
MemoryError.MissingSegmentUsedSizes,
builtin.getMemoryAccesses(std.testing.allocator, &vm),
);
}

test "BuiltinRunner: getMemoryAccesses with empty access" {
// Initialize Cairo VM
var vm = try CairoVM.init(
Expand All @@ -969,6 +990,54 @@ test "BuiltinRunner: getMemoryAccesses with empty access" {
try expect(actual.items.len == 0);
}

test "BuiltinRunner: getAllocataedMemoryUnits Keccak" {
var def = try KeccakInstanceDef.initDefault(std.testing.allocator);
defer def.deinit();

var builtin = BuiltinRunner{
.Keccak = try KeccakBuiltinRunner.init(std.testing.allocator, &def, true),
};
defer builtin.deinit();

var vm = try CairoVM.init(
std.testing.allocator,
.{},
);
defer vm.deinit();
vm.current_step = 32768;
try std.testing.expectEqual(256, try builtin.getAllocatedMemoryUnits(&vm));
}

test "BuiltinRunner: getAllocataedMemoryUnits Bitwise" {
var builtin = BuiltinRunner{
.Bitwise = BitwiseBuiltinRunner.init(&.{}, true),
};
defer builtin.deinit();

var vm = try CairoVM.init(
std.testing.allocator,
.{},
);
defer vm.deinit();
vm.current_step = 256;
try std.testing.expectEqual(5, try builtin.getAllocatedMemoryUnits(&vm));
}

test "BuiltinRunner: getAllocataedMemoryUnits RangeCheck" {
var builtin = BuiltinRunner{
.RangeCheck = RangeCheckBuiltinRunner.init(8, 8, true),
};
defer builtin.deinit();

var vm = try CairoVM.init(
std.testing.allocator,
.{},
);
defer vm.deinit();
vm.current_step = 8;
try std.testing.expectEqual(1, try builtin.getAllocatedMemoryUnits(&vm));
}

test "BuiltinRunner: getMemoryAccesses with real data" {
// Initialize Cairo VM
var vm = try CairoVM.init(std.testing.allocator, .{});
Expand Down
2 changes: 1 addition & 1 deletion src/vm/builtins/builtin_runner/ec_op.zig
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ pub const EcOpBuiltinRunner = struct {
///
/// The number of used cells as a `u32`, or `MemoryError.MissingSegmentUsedSizes` if
/// the size is not available.
pub fn getUsedCells(self: *Self, segments: *MemorySegmentManager) !u32 {
pub fn getUsedCells(self: *const Self, segments: *MemorySegmentManager) !u32 {
return segments.getSegmentUsedSize(
@intCast(self.base),
) orelse MemoryError.MissingSegmentUsedSizes;
Expand Down
2 changes: 1 addition & 1 deletion src/vm/builtins/builtin_runner/output.zig
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ pub const OutputBuiltinRunner = struct {
///
/// The count of used memory cells associated with the OutputBuiltinRunner's base address.
/// If the information is unavailable, it returns MemoryError.MissingSegmentUsedSizes.
pub fn getUsedCells(self: *Self, segments: *MemorySegmentManager) !u32 {
pub fn getUsedCells(self: *const Self, segments: *MemorySegmentManager) !u32 {
return segments.getSegmentUsedSize(
@intCast(self.base),
) orelse MemoryError.MissingSegmentUsedSizes;
Expand Down
8 changes: 8 additions & 0 deletions src/vm/error.zig
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,14 @@ pub const HintError = error{
};

pub const InsufficientAllocatedCellsError = error{
// Number of steps must be at least for some builtin
MinStepNotReached,
// The builtin used cells but the capacity is wrong
BuiltinCells,
// There are only cells to fill the range checks holes, but potentially are required.
RangeCheckUnits,
// There are only cells to fill the diluted check holes, but potentially are required
DilutedCells,
// There are only cells to fill the memory address holes, but are required.
MemoryAddresses,
};
2 changes: 1 addition & 1 deletion src/vm/run_context.zig
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ pub const RunContext = struct {

return if (instruction.off_2 < 0)
// Convert i16 to u64 safely and then negate
try base_addr.subUint(@intCast(-instruction.off_2))
try base_addr.subUint(@abs(instruction.off_2))
else
// Convert i16 to u64 safely
return try base_addr.addUint(@intCast(instruction.off_2));
Expand Down
65 changes: 63 additions & 2 deletions src/vm/runners/cairo_runner.zig
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const Attribute = @import("../types/programjson.zig").Attribute;
const ReferenceManager = @import("../types/programjson.zig").ReferenceManager;
const CairoRunnerError = @import("../error.zig").CairoRunnerError;
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 trace_context = @import("../trace_context.zig");
Expand Down Expand Up @@ -652,6 +653,22 @@ pub const CairoRunner = struct {
return builtin_segment_info;
}


/// Checks that there are enough trace cells to fill the entire range check
/// range.
pub fn checkRangeCheckUsage(self: *Self, allocator: Allocator, vm: *CairoVM) !void {
const rc_min_max = (try self.getPermRangeCheckLimits(allocator)) orelse return;
var rc_units_used_by_builtins: usize = 0;

for (vm.builtin_runners.items) |runner|
rc_units_used_by_builtins = rc_units_used_by_builtins + try runner.getUsedPermRangeCheckUnits(vm);

const unused_rc_units = (@as(usize, self.layout.rc_units) - 3) * vm.current_step - rc_units_used_by_builtins;

if (unused_rc_units < @as(usize, @intCast(rc_min_max[1] - rc_min_max[0])))
return InsufficientAllocatedCellsError.RangeCheckUnits;
}

/// 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.
Expand Down Expand Up @@ -683,6 +700,7 @@ pub const CairoRunner = struct {
);
}


/// Retrieves the permanent range check limits from the CairoRunner instance.
///
/// This function iterates through the builtin runners of the CairoRunner and gathers
Expand Down Expand Up @@ -1424,6 +1442,51 @@ test "CairoRunner: getPermRangeCheckLimits with null range limit" {
);
}

test "CairoRunner: checkRangeCheckUsage perm range limits none" {
// Initialize a CairoRunner with an empty program, "plain" layout, and 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);
try cairo_runner.checkRangeCheckUsage(std.testing.allocator, &cairo_runner.vm);
}

test "CairoRunner: checkRangeCheckUsage without builtins" {
// Initialize a CairoRunner with an empty program, "plain" layout, and 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,
);
cairo_runner.vm.current_step = 10000;

var segm = std.ArrayListUnmanaged(?MemoryCell){};
try segm.append(std.testing.allocator, MemoryCell.init(MaybeRelocatable.fromFelt(Felt252.fromInt(u256, 0x80FF80000530))));

try cairo_runner.vm.segments.memory.data.append(segm);

// Defer the deinitialization of the CairoRunner to ensure cleanup.
defer cairo_runner.deinit(std.testing.allocator);
defer cairo_runner.vm.segments.memory.deinitData(std.testing.allocator);

try cairo_runner.checkRangeCheckUsage(std.testing.allocator, &cairo_runner.vm);
}
test "CairoRunner: get constants" {
// Initialize a default program with built-ins enabled using the testing allocator.
var program = try Program.initDefault(std.testing.allocator, true);
Expand All @@ -1445,7 +1508,6 @@ test "CairoRunner: get constants" {
),
false,
);

// Defer the deinitialization of the CairoRunner object to ensure cleanup after the test.
defer cairo_runner.deinit(std.testing.allocator);

Expand Down Expand Up @@ -1477,7 +1539,6 @@ test "CairoRunner: initBuiltins missing builtins allow missing" {
),
false,
);

// Defer the deinitialization of the CairoRunner to ensure cleanup.
defer cairo_runner.deinit(std.testing.allocator);

Expand Down

0 comments on commit e2a3dc5

Please sign in to comment.