From 37be346581a1b0bdab79ad00286d4e4cfbbf5333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Gr=C3=A9goire?= Date: Sat, 28 Mar 2026 13:56:25 -0400 Subject: [PATCH] feat(rtl): add bst and tagged pointer --- src/rtl/bst.zig | 101 +++++++++++++++++++++++++++++++++++++++++ src/rtl/root.zig | 2 + src/rtl/tagged_ptr.zig | 52 +++++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 src/rtl/bst.zig create mode 100644 src/rtl/tagged_ptr.zig diff --git a/src/rtl/bst.zig b/src/rtl/bst.zig new file mode 100644 index 0000000..f828d80 --- /dev/null +++ b/src/rtl/bst.zig @@ -0,0 +1,101 @@ +const rtl = @import("rtl"); +const std = @import("std"); + +pub const Node = struct { + left: *Node, + right: *Node, + parent: rtl.TaggedPtr(Node), +}; + +/// Base struct for binary search trees. +/// This should be used as a field in other types that are based on binary search trees, such as red-black trees or AVL trees. +pub fn BST(comptime cmp: fn (*Node, *Node) std.math.Order) type { + return struct { + const Self = @This(); + + root: *Node = undefined, + nil: Node = undefined, + + /// Initialize the BST. This must be called before using the tree. + pub fn init(self: *Self) void { + self.nil.left = &self.nil; + self.nil.right = &self.nil; + self.nil.parent = &self.nil; + self.root = &self.nil; + } + + /// Return whether or not the given node is the sentinel nil node. + pub fn is_nil(self: *Self, node: *Node) bool { + return node == &self.nil; + } + + /// Search for a node in the BST that matches `elem`, and return a pointer to it if found. + pub fn search(self: *Self, elem: *Node) ?*Node { + var node = self.root; + while (!self.is_nil(node)) { + const order = cmp(elem, node); + if (order == .eq) { + return node; + } else if (order == .lt) { + node = node.left; + } else { + node = node.right; + } + } + return null; + } + + /// Return a pointer to the minimum node in the subtree rooted at `node`. + pub fn minimum(self: *Self, node: *Node) *Node { + var current = node; + while (!self.is_nil(current.left)) { + current = current.left; + } + return current; + } + + /// Return a pointer to the maximum node in the subtree rooted at `node`. + pub fn maximum(self: *Self, node: *Node) *Node { + var current = node; + while (!self.is_nil(current.right)) { + current = current.right; + } + return current; + } + + /// Return a pointer to the successor of `node`, or nil if `node` is the maximum element in the tree. + pub fn successor(self: *Self, node: *Node) *Node { + if (!self.is_nil(node.right)) { + // Minimum of right subtree + return self.minimum(node.right); + } + + // Go up and find it. + var current = node; + var parent = current.parent.get_ptr(); + while (!self.is_nil(parent) and current == parent.right) { + current = parent; + parent = parent.parent.get_ptr(); + } + + if (parent == self.root) { + return &self.nil; + } + + return parent; + } + + /// Transplant the subtree rooted at `u` with the subtree rooted at `v`. This is used as a helper function. + pub fn transplant(self: *Self, u: *Node, v: *Node) void { + const u_parent = u.parent.get_ptr(); + if (self.is_nil(u_parent)) { + self.root = v; + } else if (u == u_parent.left) { + u_parent.left = v; + } else { + u_parent.right = v; + } + v.parent.set_ptr(u_parent); + } + }; +} diff --git a/src/rtl/root.zig b/src/rtl/root.zig index 216773c..71631d0 100644 --- a/src/rtl/root.zig +++ b/src/rtl/root.zig @@ -6,6 +6,8 @@ pub const barrier = @import("barrier.zig"); pub const cmdline = @import("cmdline.zig"); pub const pairing_heap = @import("pairing_heap.zig"); pub const PairingHeap = pairing_heap.PairingHeap; +pub const TaggedPtr = @import("tagged_ptr.zig").TaggedPtr; +pub const bst = @import("bst.zig"); test { std.testing.refAllDecls(@This()); diff --git a/src/rtl/tagged_ptr.zig b/src/rtl/tagged_ptr.zig new file mode 100644 index 0000000..b46529f --- /dev/null +++ b/src/rtl/tagged_ptr.zig @@ -0,0 +1,52 @@ +/// Struct used to wrap a pointer with a 2-bit tag in the lower bits. +/// This assumes that the pointer is at least 4-byte aligned. +pub fn TaggedPtr(comptime T: type) type { + return struct { + value: usize, + + const Self = @This(); + + const mask: usize = 0x3; + + pub fn init(ptr: *T, tag: u2) Self { + return Self{ .value = (@intFromPtr(ptr) & ~mask) | (@as(usize, tag) & mask) }; + } + + /// Return the pointer with the tag bits masked out. + pub fn get_ptr(self: *Self) *T { + return @ptrFromInt(self.value & ~mask); + } + + /// Set the tag bits to `tag`, while preserving the pointer. + pub fn set_tag(self: *Self, tag: u2) void { + self.value = (self.value & ~mask) | (@as(usize, tag) & mask); + } + + /// Return the tag bits. + pub fn get_tag(self: *Self) u2 { + return @as(u2, @truncate(self.value & mask)); + } + + /// Set the pointer to `ptr`, while preserving the tag bits. + pub fn set_ptr(self: *Self, ptr: *T) void { + self.value = (@intFromPtr(ptr) & ~mask) | (self.value & mask); + } + }; +} + +const std = @import("std"); + +test TaggedPtr { + var x: u32 = 0; + var tagged = TaggedPtr(u32).init(&x, 2); + + try std.testing.expectEqual(&x, tagged.get_ptr()); + try std.testing.expectEqual(2, tagged.get_tag()); + + tagged.set_tag(1); + try std.testing.expectEqual(1, tagged.get_tag()); + + var y: u32 = 0; + tagged.set_ptr(&y); + try std.testing.expectEqual(&y, tagged.get_ptr()); +}