-
Notifications
You must be signed in to change notification settings - Fork 12
feat: model phase0 Validator as struct #232
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 8 commits
a5608d4
e6f36ae
59dcc5e
9a3b230
7c4b03d
f0738c7
ef21669
6f0c12b
49f2caf
640b420
91b030a
ef59c0b
8f64421
2ca264b
deab0b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,8 +34,8 @@ pub const Error = error{ | |
| /// | ||
| /// `[1, next_free]` | ||
| /// | ||
| /// If the high bit is not set, the next two bits determine the `node_type` | ||
| /// The following 29 bits are used for the `ref_count`. | ||
| /// If the high bit is not set, the next 3 bits determine the `node_type` | ||
| /// The following 28 bits are used for the `ref_count`. | ||
| /// | ||
| /// `[0, node_type, ref_count]` | ||
| pub const State = enum(u32) { | ||
|
|
@@ -45,14 +45,16 @@ pub const State = enum(u32) { | |
|
|
||
| pub const max_next_free = 0x7FFFFFFF; | ||
|
|
||
| // four types of nodes | ||
| const node_type = 0x60000000; | ||
| // five types of nodes, use 3 bits | ||
| const node_type = 0x70000000; | ||
| pub const zero: State = @enumFromInt(0x00000000); | ||
| pub const leaf: State = @enumFromInt(0x20000000); | ||
| pub const branch_lazy: State = @enumFromInt(0x40000000); | ||
| pub const branch_computed: State = @enumFromInt(0x60000000); | ||
| pub const leaf: State = @enumFromInt(0x10000000); | ||
| pub const branch_lazy: State = @enumFromInt(0x20000000); | ||
| pub const branch_computed: State = @enumFromInt(0x30000000); | ||
| pub const branch_struct_lazy: State = @enumFromInt(0x40000000); | ||
| pub const branch_struct_computed: State = @enumFromInt(0x50000000); | ||
|
|
||
| pub const max_ref_count = 0x1FFFFFFF; | ||
| pub const max_ref_count = 0x0FFFFFFF; | ||
|
|
||
| pub inline fn isFree(node: State) bool { | ||
| return @intFromEnum(node) & @intFromEnum(free) != 0; | ||
|
|
@@ -86,10 +88,26 @@ pub const State = enum(u32) { | |
| return @intFromEnum(node) & node_type == @intFromEnum(branch_computed); | ||
| } | ||
|
|
||
| pub inline fn isBranchStruct(node: State) bool { | ||
| return @intFromEnum(node) & @intFromEnum(branch_struct_lazy) != 0; | ||
| } | ||
|
|
||
| pub inline fn isBranchStructLazy(node: State) bool { | ||
|
twoeths marked this conversation as resolved.
|
||
| return @intFromEnum(node) & node_type == @intFromEnum(branch_struct_lazy); | ||
| } | ||
|
|
||
| pub inline fn isBranchStructComputed(node: State) bool { | ||
| return @intFromEnum(node) & node_type == @intFromEnum(branch_struct_computed); | ||
| } | ||
|
|
||
| pub inline fn setBranchComputed(node: *State) void { | ||
| node.* = @enumFromInt(@intFromEnum(node.*) | @intFromEnum(branch_computed)); | ||
| } | ||
|
|
||
| pub inline fn setBranchStructComputed(node: *State) void { | ||
| node.* = @enumFromInt(@intFromEnum(node.*) | @intFromEnum(branch_struct_computed)); | ||
| } | ||
|
|
||
| pub inline fn initRefCount(node: State) State { | ||
| return node; | ||
| } | ||
|
|
@@ -123,6 +141,12 @@ pub const Pool = struct { | |
| nodes: std.MultiArrayList(Node).Slice, | ||
| next_free_node: Id, | ||
|
|
||
| pub const BranchStructRef = struct { | ||
| ptr: *anyopaque, | ||
| get_root: *const fn (ptr: *const anyopaque, out: *[32]u8) void, | ||
| deinit: *const fn (ptr: *anyopaque, allocator: Allocator) void, | ||
| }; | ||
|
|
||
| pub const free_bit: u32 = 0x80000000; | ||
| pub const max_ref_count: u32 = 0x7FFFFFFF; | ||
|
|
||
|
|
@@ -246,6 +270,44 @@ pub const Pool = struct { | |
| return node_id; | ||
| } | ||
|
|
||
| /// The pool allocates and owns a clone of `ptr`; the caller retains ownership of its data. | ||
| pub fn createBranchStruct(self: *Pool, comptime T: type, ptr: *const T) Error!Id { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The style guide (rule 51) requires asserting all function arguments. The References
|
||
| const cloned = try T.init(self.allocator, ptr); | ||
| errdefer @constCast(cloned).deinit(self.allocator); | ||
|
|
||
| const branch_struct_ref = try self.allocator.create(BranchStructRef); | ||
| errdefer self.allocator.destroy(branch_struct_ref); | ||
|
|
||
| branch_struct_ref.* = .{ | ||
| .ptr = @ptrCast(@constCast(cloned)), | ||
| .get_root = struct { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Pointer split across Encoding a heap pointer into two Alternative: a Not blocking since the current approach works correctly on 64-bit, but worth considering for maintainability.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I agree if we go from the current/old design point of view. With the new approach of this PR, it changes semantic of node storage: it could be either left/right nodes or pointer to some data
good call on it. The only getLeft()/getRight() calls that affect is proof, which is addressed in 91b030a |
||
| fn call(ptr_erased: *const anyopaque, out: *[32]u8) void { | ||
| const typed_ptr: *const T = @ptrCast(@alignCast(ptr_erased)); | ||
| typed_ptr.getRoot(out); | ||
| } | ||
| }.call, | ||
| .deinit = struct { | ||
| fn call(ptr_erased: *anyopaque, allocator: Allocator) void { | ||
| const typed_ptr: *T = @ptrCast(@alignCast(ptr_erased)); | ||
| typed_ptr.deinit(allocator); | ||
| } | ||
| }.call, | ||
| }; | ||
|
|
||
| const node_id = try self.create(); | ||
| const ptr_usize = @intFromPtr(branch_struct_ref); | ||
| const right_ptr_value: u32 = @intCast(ptr_usize & 0xFFFFFFFF); | ||
| self.nodes.items(.right)[@intFromEnum(node_id)] = @enumFromInt(right_ptr_value); | ||
| if (comptime @sizeOf(usize) == 8) { | ||
| const left_ptr_value: u32 = @intCast(ptr_usize >> 32); | ||
| self.nodes.items(.left)[@intFromEnum(node_id)] = @enumFromInt(left_ptr_value); | ||
| } else { | ||
| self.nodes.items(.left)[@intFromEnum(node_id)] = @enumFromInt(0); | ||
| } | ||
| self.nodes.items(.state)[@intFromEnum(node_id)] = State.branch_struct_lazy.initRefCount(); | ||
| return node_id; | ||
| } | ||
|
|
||
| /// Allocates nodes into the pool. | ||
| /// | ||
| /// All nodes are allocated with refcount=0. | ||
|
|
@@ -333,6 +395,26 @@ pub const Pool = struct { | |
| _ = try states[@intFromEnum(node_id)].incRefCount(); | ||
| } | ||
|
|
||
| pub fn getStructPtr(self: *Pool, node_id: Id, comptime T: type) Error!*const T { | ||
| const state = self.nodes.items(.state)[@intFromEnum(node_id)]; | ||
| if (!state.isBranchStruct()) { | ||
| return Error.InvalidNode; | ||
| } | ||
| const struct_ref = self.getBranchStructRefUnsafe(node_id); | ||
| const ptr: *const T = @ptrCast(@alignCast(struct_ref.ptr)); | ||
| return ptr; | ||
| } | ||
|
|
||
| pub fn getBranchStructRefUnsafe(self: *Pool, node_id: Id) *BranchStructRef { | ||
| const left_ptr_value: u32 = @intFromEnum(self.nodes.items(.left)[@intFromEnum(node_id)]); | ||
| const right_ptr_value: u32 = @intFromEnum(self.nodes.items(.right)[@intFromEnum(node_id)]); | ||
| const ptr_int: usize = if (comptime @sizeOf(usize) == 8) | ||
| @as(u64, left_ptr_value) << 32 | @as(u64, right_ptr_value) | ||
| else | ||
| right_ptr_value; | ||
| return @ptrFromInt(ptr_int); | ||
| } | ||
|
|
||
| pub fn unref(self: *Pool, node_id: Id) void { | ||
| const states = self.nodes.items(.state); | ||
| const lefts = self.nodes.items(.left); | ||
|
|
@@ -381,6 +463,13 @@ pub const Pool = struct { | |
| } else { | ||
| current = null; | ||
| } | ||
|
|
||
| if (states[@intFromEnum(id)].isBranchStruct()) { | ||
| const struct_ref = self.getBranchStructRefUnsafe(id); | ||
| struct_ref.deinit(struct_ref.ptr, self.allocator); | ||
| self.allocator.destroy(struct_ref); | ||
| } | ||
|
|
||
| // Return the node to the free list | ||
| states[@intFromEnum(id)] = State.initNextFree(self.next_free_node); | ||
| self.next_free_node = id; | ||
|
|
@@ -396,7 +485,7 @@ pub const Id = enum(u32) { | |
|
|
||
| /// Returns true if navigation to the child node is not possible | ||
| pub inline fn noChild(node_id: Id, state: State) bool { | ||
| return state.isLeaf() or @intFromEnum(node_id) == 0; | ||
| return state.isLeaf() or state.isBranchStruct() or @intFromEnum(node_id) == 0; | ||
| } | ||
|
|
||
| /// Returns the root hash of the tree, computing any lazy branches as needed. | ||
|
|
@@ -410,6 +499,12 @@ pub const Id = enum(u32) { | |
| hashOne(hash, left, right); | ||
| state.setBranchComputed(); | ||
| } | ||
|
|
||
| if (state.isBranchStructLazy()) { | ||
| const struct_ref = pool.getBranchStructRefUnsafe(node_id); | ||
| struct_ref.get_root(struct_ref.ptr, hash); | ||
| state.setBranchStructComputed(); | ||
| } | ||
| return hash; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using the explicit helper functions
isBranchStructLazy()andisBranchStructComputed()makes the intent clearer and aligns better with the style guide's preference for simplicity and explicitness over subtle bitwise operations (rule 32).References