authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-03 14:11:38-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-03 14:55:04-07:00
log1639fcea43549853f1fded32aa1d711d21771e1c
tree0c053fb3b858757e49ae234f7167c65fe310a1e7
parente9220525e836810425e925722d1beaba9bfe9d91

de-genericify SinglyLinkedList

by making it always intrusive, we make it a more broadly useful API, and avoid binary bloat.

6 files changed, 207 insertions(+), 202 deletions(-)

CMakeLists.txt-1
......@@ -444,7 +444,6 @@ set(ZIG_STAGE2_SOURCES
444444 lib/std/json.zig
445445 lib/std/json/stringify.zig
446446 lib/std/leb128.zig
447 lib/std/linked_list.zig
448447 lib/std/log.zig
449448 lib/std/macho.zig
450449 lib/std/math.zig
lib/std/SinglyLinkedList.zig created+166
......@@ -0,0 +1,166 @@
1//! A singly-linked list is headed by a single forward pointer. The elements
2//! are singly-linked for minimum space and pointer manipulation overhead at
3//! the expense of O(n) removal for arbitrary elements. New elements can be
4//! added to the list after an existing element or at the head of the list.
5//!
6//! A singly-linked list may only be traversed in the forward direction.
7//!
8//! Singly-linked lists are useful under these conditions:
9//! * Ability to preallocate elements / requirement of infallibility for
10//! insertion.
11//! * Ability to allocate elements intrusively along with other data.
12//! * Homogenous elements.
13
14const std = @import("std.zig");
15const debug = std.debug;
16const assert = debug.assert;
17const testing = std.testing;
18const SinglyLinkedList = @This();
19
20first: ?*Node = null,
21
22/// This struct contains only a next pointer and not any data payload. The
23/// intended usage is to embed it intrusively into another data structure and
24/// access the data with `@fieldParentPtr`.
25pub const Node = struct {
26 next: ?*Node = null,
27
28 pub fn insertAfter(node: *Node, new_node: *Node) void {
29 new_node.next = node.next;
30 node.next = new_node;
31 }
32
33 /// Remove the node after the one provided, returning it.
34 pub fn removeNext(node: *Node) ?*Node {
35 const next_node = node.next orelse return null;
36 node.next = next_node.next;
37 return next_node;
38 }
39
40 /// Iterate over the singly-linked list from this node, until the final
41 /// node is found.
42 ///
43 /// This operation is O(N). Instead of calling this function, consider
44 /// using a different data structure.
45 pub fn findLast(node: *Node) *Node {
46 var it = node;
47 while (true) {
48 it = it.next orelse return it;
49 }
50 }
51
52 /// Iterate over each next node, returning the count of all nodes except
53 /// the starting one.
54 ///
55 /// This operation is O(N). Instead of calling this function, consider
56 /// using a different data structure.
57 pub fn countChildren(node: *const Node) usize {
58 var count: usize = 0;
59 var it: ?*const Node = node.next;
60 while (it) |n| : (it = n.next) {
61 count += 1;
62 }
63 return count;
64 }
65
66 /// Reverse the list starting from this node in-place.
67 ///
68 /// This operation is O(N). Instead of calling this function, consider
69 /// using a different data structure.
70 pub fn reverse(indirect: *?*Node) void {
71 if (indirect.* == null) {
72 return;
73 }
74 var current: *Node = indirect.*.?;
75 while (current.next) |next| {
76 current.next = next.next;
77 next.next = indirect.*;
78 indirect.* = next;
79 }
80 }
81};
82
83pub fn prepend(list: *SinglyLinkedList, new_node: *Node) void {
84 new_node.next = list.first;
85 list.first = new_node;
86}
87
88pub fn remove(list: *SinglyLinkedList, node: *Node) void {
89 if (list.first == node) {
90 list.first = node.next;
91 } else {
92 var current_elm = list.first.?;
93 while (current_elm.next != node) {
94 current_elm = current_elm.next.?;
95 }
96 current_elm.next = node.next;
97 }
98}
99
100/// Remove and return the first node in the list.
101pub fn popFirst(list: *SinglyLinkedList) ?*Node {
102 const first = list.first orelse return null;
103 list.first = first.next;
104 return first;
105}
106
107/// Iterate over all nodes, returning the count.
108///
109/// This operation is O(N). Consider tracking the length separately rather than
110/// computing it.
111pub fn len(list: SinglyLinkedList) usize {
112 if (list.first) |n| {
113 return 1 + n.countChildren();
114 } else {
115 return 0;
116 }
117}
118
119test "basics" {
120 const L = struct {
121 data: u32,
122 node: SinglyLinkedList.Node = .{},
123 };
124 var list: SinglyLinkedList = .{};
125
126 try testing.expect(list.len() == 0);
127
128 var one: L = .{ .data = 1 };
129 var two: L = .{ .data = 2 };
130 var three: L = .{ .data = 3 };
131 var four: L = .{ .data = 4 };
132 var five: L = .{ .data = 5 };
133
134 list.prepend(&two.node); // {2}
135 two.node.insertAfter(&five.node); // {2, 5}
136 list.prepend(&one.node); // {1, 2, 5}
137 two.node.insertAfter(&three.node); // {1, 2, 3, 5}
138 three.node.insertAfter(&four.node); // {1, 2, 3, 4, 5}
139
140 try testing.expect(list.len() == 5);
141
142 // Traverse forwards.
143 {
144 var it = list.first;
145 var index: u32 = 1;
146 while (it) |node| : (it = node.next) {
147 const l: *L = @fieldParentPtr("node", node);
148 try testing.expect(l.data == index);
149 index += 1;
150 }
151 }
152
153 _ = list.popFirst(); // {2, 3, 4, 5}
154 _ = list.remove(&five.node); // {2, 3, 4}
155 _ = two.node.removeNext(); // {2, 4}
156
157 try testing.expect(@as(*L, @fieldParentPtr("node", list.first.?)).data == 2);
158 try testing.expect(@as(*L, @fieldParentPtr("node", list.first.?.next.?)).data == 4);
159 try testing.expect(list.first.?.next.?.next == null);
160
161 SinglyLinkedList.Node.reverse(&list.first);
162
163 try testing.expect(@as(*L, @fieldParentPtr("node", list.first.?)).data == 4);
164 try testing.expect(@as(*L, @fieldParentPtr("node", list.first.?.next.?)).data == 2);
165 try testing.expect(list.first.?.next.?.next == null);
166}
lib/std/Thread/Pool.zig+15-16
......@@ -5,7 +5,7 @@ const WaitGroup = @import("WaitGroup.zig");
55
66mutex: std.Thread.Mutex = .{},
77cond: std.Thread.Condition = .{},
8run_queue: RunQueue = .{},
8run_queue: std.SinglyLinkedList = .{},
99is_running: bool = true,
1010allocator: std.mem.Allocator,
1111threads: if (builtin.single_threaded) [0]std.Thread else []std.Thread,
......@@ -16,9 +16,9 @@ ids: if (builtin.single_threaded) struct {
1616 }
1717} else std.AutoArrayHashMapUnmanaged(std.Thread.Id, void),
1818
19const RunQueue = std.SinglyLinkedList(Runnable);
2019const Runnable = struct {
2120 runFn: RunProto,
21 node: std.SinglyLinkedList.Node = .{},
2222};
2323
2424const RunProto = *const fn (*Runnable, id: ?usize) void;
......@@ -110,12 +110,11 @@ pub fn spawnWg(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, args
110110 const Closure = struct {
111111 arguments: Args,
112112 pool: *Pool,
113 run_node: RunQueue.Node = .{ .data = .{ .runFn = runFn } },
113 runnable: Runnable = .{ .runFn = runFn },
114114 wait_group: *WaitGroup,
115115
116116 fn runFn(runnable: *Runnable, _: ?usize) void {
117 const run_node: *RunQueue.Node = @fieldParentPtr("data", runnable);
118 const closure: *@This() = @alignCast(@fieldParentPtr("run_node", run_node));
117 const closure: *@This() = @alignCast(@fieldParentPtr("runnable", runnable));
119118 @call(.auto, func, closure.arguments);
120119 closure.wait_group.finish();
121120
......@@ -143,7 +142,7 @@ pub fn spawnWg(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, args
143142 .wait_group = wait_group,
144143 };
145144
146 pool.run_queue.prepend(&closure.run_node);
145 pool.run_queue.prepend(&closure.runnable.node);
147146 pool.mutex.unlock();
148147 }
149148
......@@ -173,12 +172,11 @@ pub fn spawnWgId(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, ar
173172 const Closure = struct {
174173 arguments: Args,
175174 pool: *Pool,
176 run_node: RunQueue.Node = .{ .data = .{ .runFn = runFn } },
175 runnable: Runnable = .{ .runFn = runFn },
177176 wait_group: *WaitGroup,
178177
179178 fn runFn(runnable: *Runnable, id: ?usize) void {
180 const run_node: *RunQueue.Node = @fieldParentPtr("data", runnable);
181 const closure: *@This() = @alignCast(@fieldParentPtr("run_node", run_node));
179 const closure: *@This() = @alignCast(@fieldParentPtr("runnable", runnable));
182180 @call(.auto, func, .{id.?} ++ closure.arguments);
183181 closure.wait_group.finish();
184182
......@@ -207,7 +205,7 @@ pub fn spawnWgId(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, ar
207205 .wait_group = wait_group,
208206 };
209207
210 pool.run_queue.prepend(&closure.run_node);
208 pool.run_queue.prepend(&closure.runnable.node);
211209 pool.mutex.unlock();
212210 }
213211
......@@ -225,11 +223,10 @@ pub fn spawn(pool: *Pool, comptime func: anytype, args: anytype) !void {
225223 const Closure = struct {
226224 arguments: Args,
227225 pool: *Pool,
228 run_node: RunQueue.Node = .{ .data = .{ .runFn = runFn } },
226 runnable: Runnable = .{ .runFn = runFn },
229227
230228 fn runFn(runnable: *Runnable, _: ?usize) void {
231 const run_node: *RunQueue.Node = @fieldParentPtr("data", runnable);
232 const closure: *@This() = @alignCast(@fieldParentPtr("run_node", run_node));
229 const closure: *@This() = @alignCast(@fieldParentPtr("runnable", runnable));
233230 @call(.auto, func, closure.arguments);
234231
235232 // The thread pool's allocator is protected by the mutex.
......@@ -251,7 +248,7 @@ pub fn spawn(pool: *Pool, comptime func: anytype, args: anytype) !void {
251248 .pool = pool,
252249 };
253250
254 pool.run_queue.prepend(&closure.run_node);
251 pool.run_queue.prepend(&closure.runnable.node);
255252 }
256253
257254 // Notify waiting threads outside the lock to try and keep the critical section small.
......@@ -292,7 +289,8 @@ fn worker(pool: *Pool) void {
292289 pool.mutex.unlock();
293290 defer pool.mutex.lock();
294291
295 run_node.data.runFn(&run_node.data, id);
292 const runnable: *Runnable = @fieldParentPtr("node", run_node);
293 runnable.runFn(runnable, id);
296294 }
297295
298296 // Stop executing instead of waiting if the thread pool is no longer running.
......@@ -312,7 +310,8 @@ pub fn waitAndWork(pool: *Pool, wait_group: *WaitGroup) void {
312310 if (pool.run_queue.popFirst()) |run_node| {
313311 id = id orelse pool.ids.getIndex(std.Thread.getCurrentId());
314312 pool.mutex.unlock();
315 run_node.data.runFn(&run_node.data, id);
313 const runnable: *Runnable = @fieldParentPtr("node", run_node);
314 runnable.runFn(runnable, id);
316315 continue;
317316 }
318317
lib/std/heap/arena_allocator.zig+25-16
......@@ -14,7 +14,7 @@ pub const ArenaAllocator = struct {
1414 /// Inner state of ArenaAllocator. Can be stored rather than the entire ArenaAllocator
1515 /// as a memory-saving optimization.
1616 pub const State = struct {
17 buffer_list: std.SinglyLinkedList(usize) = .{},
17 buffer_list: std.SinglyLinkedList = .{},
1818 end_index: usize = 0,
1919
2020 pub fn promote(self: State, child_allocator: Allocator) ArenaAllocator {
......@@ -37,7 +37,10 @@ pub const ArenaAllocator = struct {
3737 };
3838 }
3939
40 const BufNode = std.SinglyLinkedList(usize).Node;
40 const BufNode = struct {
41 data: usize,
42 node: std.SinglyLinkedList.Node = .{},
43 };
4144 const BufNode_alignment: mem.Alignment = .fromByteUnits(@alignOf(BufNode));
4245
4346 pub fn init(child_allocator: Allocator) ArenaAllocator {
......@@ -51,7 +54,8 @@ pub const ArenaAllocator = struct {
5154 while (it) |node| {
5255 // this has to occur before the free because the free frees node
5356 const next_it = node.next;
54 const alloc_buf = @as([*]u8, @ptrCast(node))[0..node.data];
57 const buf_node: *BufNode = @fieldParentPtr("node", node);
58 const alloc_buf = @as([*]u8, @ptrCast(buf_node))[0..buf_node.data];
5559 self.child_allocator.rawFree(alloc_buf, BufNode_alignment, @returnAddress());
5660 it = next_it;
5761 }
......@@ -78,7 +82,8 @@ pub const ArenaAllocator = struct {
7882 while (it) |node| : (it = node.next) {
7983 // Compute the actually allocated size excluding the
8084 // linked list node.
81 size += node.data - @sizeOf(BufNode);
85 const buf_node: *BufNode = @fieldParentPtr("node", node);
86 size += buf_node.data - @sizeOf(BufNode);
8287 }
8388 return size;
8489 }
......@@ -130,7 +135,8 @@ pub const ArenaAllocator = struct {
130135 const next_it = node.next;
131136 if (next_it == null)
132137 break node;
133 const alloc_buf = @as([*]u8, @ptrCast(node))[0..node.data];
138 const buf_node: *BufNode = @fieldParentPtr("node", node);
139 const alloc_buf = @as([*]u8, @ptrCast(buf_node))[0..buf_node.data];
134140 self.child_allocator.rawFree(alloc_buf, BufNode_alignment, @returnAddress());
135141 it = next_it;
136142 } else null;
......@@ -140,12 +146,13 @@ pub const ArenaAllocator = struct {
140146 if (maybe_first_node) |first_node| {
141147 self.state.buffer_list.first = first_node;
142148 // perfect, no need to invoke the child_allocator
143 if (first_node.data == total_size)
149 const first_buf_node: *BufNode = @fieldParentPtr("node", first_node);
150 if (first_buf_node.data == total_size)
144151 return true;
145 const first_alloc_buf = @as([*]u8, @ptrCast(first_node))[0..first_node.data];
152 const first_alloc_buf = @as([*]u8, @ptrCast(first_buf_node))[0..first_buf_node.data];
146153 if (self.child_allocator.rawResize(first_alloc_buf, BufNode_alignment, total_size, @returnAddress())) {
147154 // successful resize
148 first_node.data = total_size;
155 first_buf_node.data = total_size;
149156 } else {
150157 // manual realloc
151158 const new_ptr = self.child_allocator.rawAlloc(total_size, BufNode_alignment, @returnAddress()) orelse {
......@@ -153,9 +160,9 @@ pub const ArenaAllocator = struct {
153160 return false;
154161 };
155162 self.child_allocator.rawFree(first_alloc_buf, BufNode_alignment, @returnAddress());
156 const node: *BufNode = @ptrCast(@alignCast(new_ptr));
157 node.* = .{ .data = total_size };
158 self.state.buffer_list.first = node;
163 const buf_node: *BufNode = @ptrCast(@alignCast(new_ptr));
164 buf_node.* = .{ .data = total_size };
165 self.state.buffer_list.first = &buf_node.node;
159166 }
160167 }
161168 return true;
......@@ -169,7 +176,7 @@ pub const ArenaAllocator = struct {
169176 return null;
170177 const buf_node: *BufNode = @ptrCast(@alignCast(ptr));
171178 buf_node.* = .{ .data = len };
172 self.state.buffer_list.prepend(buf_node);
179 self.state.buffer_list.prepend(&buf_node.node);
173180 self.state.end_index = 0;
174181 return buf_node;
175182 }
......@@ -179,8 +186,8 @@ pub const ArenaAllocator = struct {
179186 _ = ra;
180187
181188 const ptr_align = alignment.toByteUnits();
182 var cur_node = if (self.state.buffer_list.first) |first_node|
183 first_node
189 var cur_node: *BufNode = if (self.state.buffer_list.first) |first_node|
190 @fieldParentPtr("node", first_node)
184191 else
185192 (self.createNode(0, n + ptr_align) orelse return null);
186193 while (true) {
......@@ -213,7 +220,8 @@ pub const ArenaAllocator = struct {
213220 _ = ret_addr;
214221
215222 const cur_node = self.state.buffer_list.first orelse return false;
216 const cur_buf = @as([*]u8, @ptrCast(cur_node))[@sizeOf(BufNode)..cur_node.data];
223 const cur_buf_node: *BufNode = @fieldParentPtr("node", cur_node);
224 const cur_buf = @as([*]u8, @ptrCast(cur_buf_node))[@sizeOf(BufNode)..cur_buf_node.data];
217225 if (@intFromPtr(cur_buf.ptr) + self.state.end_index != @intFromPtr(buf.ptr) + buf.len) {
218226 // It's not the most recent allocation, so it cannot be expanded,
219227 // but it's fine if they want to make it smaller.
......@@ -248,7 +256,8 @@ pub const ArenaAllocator = struct {
248256 const self: *ArenaAllocator = @ptrCast(@alignCast(ctx));
249257
250258 const cur_node = self.state.buffer_list.first orelse return;
251 const cur_buf = @as([*]u8, @ptrCast(cur_node))[@sizeOf(BufNode)..cur_node.data];
259 const cur_buf_node: *BufNode = @fieldParentPtr("node", cur_node);
260 const cur_buf = @as([*]u8, @ptrCast(cur_buf_node))[@sizeOf(BufNode)..cur_buf_node.data];
252261
253262 if (@intFromPtr(cur_buf.ptr) + self.state.end_index == @intFromPtr(buf.ptr) + buf.len) {
254263 self.state.end_index -= buf.len;
lib/std/linked_list.zig-168
......@@ -3,174 +3,6 @@ const debug = std.debug;
33const assert = debug.assert;
44const testing = std.testing;
55
6/// A singly-linked list is headed by a single forward pointer. The elements
7/// are singly-linked for minimum space and pointer manipulation overhead at
8/// the expense of O(n) removal for arbitrary elements. New elements can be
9/// added to the list after an existing element or at the head of the list.
10/// A singly-linked list may only be traversed in the forward direction.
11/// Singly-linked lists are ideal for applications with large datasets and
12/// few or no removals or for implementing a LIFO queue.
13pub fn SinglyLinkedList(comptime T: type) type {
14 return struct {
15 const Self = @This();
16
17 /// Node inside the linked list wrapping the actual data.
18 pub const Node = struct {
19 next: ?*Node = null,
20 data: T,
21
22 pub const Data = T;
23
24 /// Insert a new node after the current one.
25 ///
26 /// Arguments:
27 /// new_node: Pointer to the new node to insert.
28 pub fn insertAfter(node: *Node, new_node: *Node) void {
29 new_node.next = node.next;
30 node.next = new_node;
31 }
32
33 /// Remove a node from the list.
34 ///
35 /// Arguments:
36 /// node: Pointer to the node to be removed.
37 /// Returns:
38 /// node removed
39 pub fn removeNext(node: *Node) ?*Node {
40 const next_node = node.next orelse return null;
41 node.next = next_node.next;
42 return next_node;
43 }
44
45 /// Iterate over the singly-linked list from this node, until the final node is found.
46 /// This operation is O(N).
47 pub fn findLast(node: *Node) *Node {
48 var it = node;
49 while (true) {
50 it = it.next orelse return it;
51 }
52 }
53
54 /// Iterate over each next node, returning the count of all nodes except the starting one.
55 /// This operation is O(N).
56 pub fn countChildren(node: *const Node) usize {
57 var count: usize = 0;
58 var it: ?*const Node = node.next;
59 while (it) |n| : (it = n.next) {
60 count += 1;
61 }
62 return count;
63 }
64
65 /// Reverse the list starting from this node in-place.
66 /// This operation is O(N).
67 pub fn reverse(indirect: *?*Node) void {
68 if (indirect.* == null) {
69 return;
70 }
71 var current: *Node = indirect.*.?;
72 while (current.next) |next| {
73 current.next = next.next;
74 next.next = indirect.*;
75 indirect.* = next;
76 }
77 }
78 };
79
80 first: ?*Node = null,
81
82 /// Insert a new node at the head.
83 ///
84 /// Arguments:
85 /// new_node: Pointer to the new node to insert.
86 pub fn prepend(list: *Self, new_node: *Node) void {
87 new_node.next = list.first;
88 list.first = new_node;
89 }
90
91 /// Remove a node from the list.
92 ///
93 /// Arguments:
94 /// node: Pointer to the node to be removed.
95 pub fn remove(list: *Self, node: *Node) void {
96 if (list.first == node) {
97 list.first = node.next;
98 } else {
99 var current_elm = list.first.?;
100 while (current_elm.next != node) {
101 current_elm = current_elm.next.?;
102 }
103 current_elm.next = node.next;
104 }
105 }
106
107 /// Remove and return the first node in the list.
108 ///
109 /// Returns:
110 /// A pointer to the first node in the list.
111 pub fn popFirst(list: *Self) ?*Node {
112 const first = list.first orelse return null;
113 list.first = first.next;
114 return first;
115 }
116
117 /// Iterate over all nodes, returning the count.
118 /// This operation is O(N).
119 pub fn len(list: Self) usize {
120 if (list.first) |n| {
121 return 1 + n.countChildren();
122 } else {
123 return 0;
124 }
125 }
126 };
127}
128
129test "basic SinglyLinkedList test" {
130 const L = SinglyLinkedList(u32);
131 var list = L{};
132
133 try testing.expect(list.len() == 0);
134
135 var one = L.Node{ .data = 1 };
136 var two = L.Node{ .data = 2 };
137 var three = L.Node{ .data = 3 };
138 var four = L.Node{ .data = 4 };
139 var five = L.Node{ .data = 5 };
140
141 list.prepend(&two); // {2}
142 two.insertAfter(&five); // {2, 5}
143 list.prepend(&one); // {1, 2, 5}
144 two.insertAfter(&three); // {1, 2, 3, 5}
145 three.insertAfter(&four); // {1, 2, 3, 4, 5}
146
147 try testing.expect(list.len() == 5);
148
149 // Traverse forwards.
150 {
151 var it = list.first;
152 var index: u32 = 1;
153 while (it) |node| : (it = node.next) {
154 try testing.expect(node.data == index);
155 index += 1;
156 }
157 }
158
159 _ = list.popFirst(); // {2, 3, 4, 5}
160 _ = list.remove(&five); // {2, 3, 4}
161 _ = two.removeNext(); // {2, 4}
162
163 try testing.expect(list.first.?.data == 2);
164 try testing.expect(list.first.?.next.?.data == 4);
165 try testing.expect(list.first.?.next.?.next == null);
166
167 L.Node.reverse(&list.first);
168
169 try testing.expect(list.first.?.data == 4);
170 try testing.expect(list.first.?.next.?.data == 2);
171 try testing.expect(list.first.?.next.?.next == null);
172}
173
1746/// A doubly-linked list has a pair of pointers to both the head and
1757/// tail of the list. List elements have pointers to both the previous
1768/// and next elements in the sequence. The list can be traversed both
lib/std/std.zig+1-1
......@@ -33,7 +33,7 @@ pub const Random = @import("Random.zig");
3333pub const RingBuffer = @import("RingBuffer.zig");
3434pub const SegmentedList = @import("segmented_list.zig").SegmentedList;
3535pub const SemanticVersion = @import("SemanticVersion.zig");
36pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;
36pub const SinglyLinkedList = @import("SinglyLinkedList.zig");
3737pub const StaticBitSet = bit_set.StaticBitSet;
3838pub const StringHashMap = hash_map.StringHashMap;
3939pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged;