Skip to content

Commit

Permalink
Range Check builtin (keep-starknet-strange#100)
Browse files Browse the repository at this point in the history
* range check builtin

* range check runner

* memory validation rule

* range check and validation rule

* fixes

* format

* fixed

* some fixes

* try

---------

Co-authored-by: lanaivina <[email protected]>
  • Loading branch information
oxlime and lana-shanghai authored Nov 7, 2023
1 parent b119ba4 commit 747dc4a
Show file tree
Hide file tree
Showing 14 changed files with 362 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/vm/builtins/builtin_runner/bitwise.zig
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub const BitwiseBuiltinRunner = struct {
/// # Returns
///
/// The base value as a `usize`.
pub fn get_base(self: *const Self) usize {
pub fn getBase(self: *const Self) usize {
return self.base;
}
};
18 changes: 9 additions & 9 deletions src/vm/builtins/builtin_runner/builtin_runner.zig
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ pub const BuiltinRunner = union(enum) {
/// The base value as a `usize`.
pub fn base(self: *const Self) usize {
return switch (self.*) {
.Bitwise => |*bitwise| bitwise.get_base(),
.EcOp => |*ec| ec.get_base(),
.Hash => |*hash| hash.get_base(),
.Output => |*output| output.get_base(),
.RangeCheck => |*range_check| range_check.get_base(),
.Keccak => |*keccak| keccak.get_base(),
.Signature => |*signature| signature.get_base(),
.Poseidon => |*poseidon| poseidon.get_base(),
.SegmentArena => |*segment_arena| segment_arena.get_base(),
.Bitwise => |*bitwise| bitwise.getBase(),
.EcOp => |*ec| ec.getBase(),
.Hash => |*hash| hash.getBase(),
.Output => |*output| output.getBase(),
.RangeCheck => |*range_check| range_check.getBase(),
.Keccak => |*keccak| keccak.getBase(),
.Signature => |*signature| signature.getBase(),
.Poseidon => |*poseidon| poseidon.getBase(),
.SegmentArena => |*segment_arena| segment_arena.getBase(),
};
}
};
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 @@ -66,7 +66,7 @@ pub const EcOpBuiltinRunner = struct {
/// # Returns
///
/// The base value as a `usize`.
pub fn get_base(self: *const Self) usize {
pub fn getBase(self: *const Self) usize {
return self.base;
}
};
2 changes: 1 addition & 1 deletion src/vm/builtins/builtin_runner/hash.zig
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub const HashBuiltinRunner = struct {
/// # Returns
///
/// The base value as a `usize`.
pub fn get_base(self: *const Self) usize {
pub fn getBase(self: *const Self) usize {
return self.base;
}
};
2 changes: 1 addition & 1 deletion src/vm/builtins/builtin_runner/keccak.zig
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ pub const KeccakBuiltinRunner = struct {
/// # Returns
///
/// The base value as a `usize`.
pub fn get_base(self: *const Self) usize {
pub fn getBase(self: *const Self) usize {
return self.base;
}
};
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 @@ -33,7 +33,7 @@ pub const OutputBuiltinRunner = struct {
/// # Returns
///
/// The base value as a `usize`.
pub fn get_base(self: *const Self) usize {
pub fn getBase(self: *const Self) usize {
return self.base;
}
};
2 changes: 1 addition & 1 deletion src/vm/builtins/builtin_runner/poseidon.zig
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ pub const PoseidonBuiltinRunner = struct {
/// # Returns
///
/// The base value as a `usize`.
pub fn get_base(self: *const Self) usize {
pub fn getBase(self: *const Self) usize {
return self.base;
}
};
249 changes: 243 additions & 6 deletions src/vm/builtins/builtin_runner/range_check.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;

const Felt252 = @import("../../../math/fields/starknet.zig").Felt252;
const MemorySegmentManager = @import("../../memory/segments.zig").MemorySegmentManager;
const relocatable = @import("../../memory/relocatable.zig");
const Error = @import("../../error.zig");
const validation_rule = @import("../../memory/memory.zig").validation_rule;
const Memory = @import("../../memory/memory.zig").Memory;
const Field = @import("../../../math/fields/starknet.zig").Field;
const range_check_instance_def = @import("../../types/range_check_instance_def.zig");

const CELLS_PER_RANGE_CHECK = range_check_instance_def.CELLS_PER_RANGE_CHECK;
const Relocatable = relocatable.Relocatable;
const MaybeRelocatable = relocatable.MaybeRelocatable;
const MemoryError = Error.MemoryError;
const RunnerError = Error.RunnerError;

const N_PARTS: u64 = 8;
const INNER_RC_BOUND_SHIFT: u64 = 16;
const INNER_RC_BOUND_MASK: u64 = @as(u64, @intCast(u16.MAX));

/// Range check built-in runner
pub const RangeCheckBuiltinRunner = struct {
const Self = @This();
Expand Down Expand Up @@ -43,26 +63,243 @@ pub const RangeCheckBuiltinRunner = struct {
n_parts: u32,
included: bool,
) Self {
const bound: Felt252 = Felt252.one().saturating_shl(16 * n_parts);
const _bound: ?Felt252 = if (n_parts != 0 and bound.isZero()) null else bound;

return .{
.ratio = ratio,
.base = 0,
.stop_ptr = null,
.cell_per_instance = range_check_instance_def.CELLS_PER_RANGE_CHECK,
.n_input_cells = range_check_instance_def.CELLS_PER_RANGE_CHECK,
// TODO: implement shl logic: https://github.com/lambdaclass/cairo-vm/blob/e6171d66a64146acc16d5512766ae91ae044f297/vm/src/vm/runners/builtin_runner/range_check.rs#L48-L53
._bound = null,
.cells_per_instance = CELLS_PER_RANGE_CHECK,
.n_input_cells = CELLS_PER_RANGE_CHECK,
._bound = _bound,
.included = included,
.n_parts = n_parts,
.instances_per_component = 1,
};
}

/// Get the base value of this range check runner.
/// Get the base value of this Range Check runner.
///
/// # Returns
///
/// The base value as a `usize`.
pub fn get_base(self: *const Self) usize {
pub fn getBase(self: *const Self) usize {
return self.base;
}

/// Get the ratio value of this Range Check runner.
///
/// # Returns
///
/// The ratio value as an `u32`.
pub fn getRatio(self: *const Self) ?u32 {
return self.ratio;
}

/// Initializes memory segments and sets the base value for the Range Check runner.
///
/// This function adds a memory segment using the provided `segments` manager and
/// sets the `base` value to the index of the new segment.
///
/// # Parameters
/// - `segments`: A pointer to the `MemorySegmentManager` for segment management.
///
/// # Modifies
/// - `self`: Updates the `base` value to the new segment's index.
pub fn initializeSegments(self: *Self, segments: *MemorySegmentManager) void {
self.base = @intCast(segments.addSegment().segment_index);
}

/// Initializes and returns an `ArrayList` of `MaybeRelocatable` values.
///
/// If the range check runner is included, it appends a `Relocatable` element to the `ArrayList`
/// with the base value. Otherwise, it returns an empty `ArrayList`.
///
/// # Parameters
/// - `allocator`: An allocator for initializing the `ArrayList`.
///
/// # Returns
/// An `ArrayList` of `MaybeRelocatable` values.
pub fn initialStack(self: *Self, allocator: Allocator) !ArrayList(MaybeRelocatable) {
var result = ArrayList(MaybeRelocatable).init(allocator);
if (self.included) {
try result.append(.{
.relocatable = Relocatable.new(
@intCast(self.base),
0,
),
});
return result;
}
return result;
}

/// Get the number of used cells associated with this Range Check runner.
///
/// # Parameters
///
/// - `segments`: A pointer to a `MemorySegmentManager` for segment size information.
///
/// # Returns
///
/// 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(
@intCast(self.base),
) orelse MemoryError.MissingSegmentUsedSizes;
}

/// Calculates the number of used instances for the Range Check runner.
///
/// This function computes the number of used instances based on the available
/// used cells and the number of cells per instance. It performs a ceiling division
/// to ensure that any remaining cells are counted as an additional instance.
///
/// # Parameters
/// - `segments`: A pointer to the `MemorySegmentManager` for segment information.
///
/// # Returns
/// The number of used instances as a `usize`.
pub fn getUsedInstances(self: *Self, segments: *MemorySegmentManager) !usize {
return std.math.divCeil(
usize,
try self.getUsedCells(segments),
@intCast(self.cells_per_instance),
);
}

/// Retrieves memory segment addresses as a tuple.
///
/// Returns a tuple containing the `base` and `stop_ptr` addresses associated
/// with the Range Check runner's memory segments. The `stop_ptr` may be `null`.
///
/// # Returns
/// A tuple of `usize` and `?usize` addresses.
pub fn getMemorySegmentAddresses(self: *Self) std.meta.Tuple(&.{
usize,
?usize,
}) {
return .{
self.base,
self.stop_ptr,
};
}

/// Calculate the final stack.
///
/// This function calculates the final stack pointer for the Range Check runner, based on the provided `segments`, `pointer`, and `self` settings. If the runner is included,
/// it verifies the stop pointer for consistency and sets it. Otherwise, it sets the stop pointer to zero.
///
/// # Parameters
///
/// - `segments`: A pointer to the `MemorySegmentManager` for segment management.
/// - `pointer`: A `Relocatable` pointer to the current stack pointer.
///
/// # Returns
///
/// A `Relocatable` pointer to the final stack pointer, or an error code if the
/// verification fails.
pub fn finalStack(
self: *Self,
segments: *MemorySegmentManager,
pointer: Relocatable,
) !Relocatable {
if (self.included) {
const stop_pointer_addr = pointer.subUint(
@intCast(1),
) catch return RunnerError.NoStopPointer;
const stop_pointer = try (segments.memory.get(
stop_pointer_addr,
) catch return RunnerError.NoStopPointer).tryIntoRelocatable();
if (@as(
isize,
@intCast(self.base),
) != stop_pointer.segment_index) {
return RunnerError.InvalidStopPointerIndex;
}
const stop_ptr = stop_pointer.offset;

if (stop_ptr != try self.getUsedInstances(segments) * @as(
usize,
@intCast(self.cells_per_instance),
)) {
return RunnerError.InvalidStopPointer;
}
self.stop_ptr = stop_ptr;
return stop_pointer_addr;
}

self.stop_ptr = 0;
return pointer;
}

/// Creates Validation Rules ArrayList
///
/// # Parameters
///
/// - `memory`: A `Memory` pointer of validation rules segment index.
/// - `address`: A `Relocatable` pointer to the validation rule.
///
/// # Returns
///
/// An `ArrayList(Relocatable)` containing the rules address
/// verification fails.
pub fn rangeCheckValidationRule(memory: *Memory, address: Relocatable, allocator: Allocator) !std.ArrayList(Relocatable) {
var result = ArrayList(Relocatable).init(allocator);
const num = (memory.get(address) catch {
return null;
}).tryIntoFelt() catch {
return RunnerError.BuiltinExpectedInteger;
};

if (num.Mask <= N_PARTS * INNER_RC_BOUND_SHIFT) {
return try result.append(address);
} else {
return try result.append(Error.MemoryOutOfBounds);
}
}

/// Creates Validation Rule in Memory
///
/// # Parameters
///
/// - `memory`: A `Memory` pointer of validation rules segment index.
///
/// # Modifies
///
/// - `memory`: Adds validation rule to `memory`.
pub fn addValidationRule(self: *const Self, memory: *Memory) void {
memory.addValidationRule(self.base.segment_index, rangeCheckValidationRule);
}
};

test "initialize segments for range check" {

// given
var builtin = RangeCheckBuiltinRunner.new(8, 8, true);
var allocator = std.testing.allocator;
var mem = try MemorySegmentManager.init(allocator);
defer mem.deinit();

// assert
try std.testing.expectEqual(
builtin.base,
0,
);
}

test "used instances" {

// given
var builtin = RangeCheckBuiltinRunner.new(10, 12, true);

var memory_segment_manager = try MemorySegmentManager.init(std.testing.allocator);
defer memory_segment_manager.deinit();
try memory_segment_manager.segment_used_sizes.put(0, 1);
try std.testing.expectEqual(
@as(usize, @intCast(1)),
try builtin.getUsedInstances(memory_segment_manager),
);
}
2 changes: 1 addition & 1 deletion src/vm/builtins/builtin_runner/segment_arena.zig
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub const SegmentArenaBuiltinRunner = struct {
/// # Returns
///
/// The base segment index as a `usize`.
pub fn get_base(self: *const Self) usize {
pub fn getBase(self: *const Self) usize {
return @as(
usize,
@intCast(self.base.segment_index),
Expand Down
2 changes: 1 addition & 1 deletion src/vm/builtins/builtin_runner/signature.zig
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub const SignatureBuiltinRunner = struct {
/// # Returns
///
/// The base value as a `usize`.
pub fn get_base(self: *const Self) usize {
pub fn getBase(self: *const Self) usize {
return self.base;
}
};
11 changes: 11 additions & 0 deletions src/vm/error.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,14 @@ pub const CairoVMError = error{
TypeMismatchNotRelocatable,
ValueTooLarge,
};

pub const MemoryError = error{
MissingSegmentUsedSizes,
};

pub const RunnerError = error{
NoStopPointer,
InvalidStopPointerIndex,
InvalidStopPointer,
BuiltinExpectedInteger,
};
Loading

0 comments on commit 747dc4a

Please sign in to comment.