authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-04 17:48:06-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-04-04 17:48:06-04:00
log8acedfd5baabab705946ad097746f9183ef62420
treec3c88949d11035d9435da7e278a2dd05b3b68596
parent84c9cee502ff71a432fa65ff5c63fac95cb1c47e
parent810f70ef42fa013dc31b13445dd2910a43f8a0f7
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #23459 from ziglang/linked-lists

de-genericify linked lists

9 files changed, 553 insertions(+), 543 deletions(-)

CMakeLists.txt-1
...@@ -444,7 +444,6 @@ set(ZIG_STAGE2_SOURCES...@@ -444,7 +444,6 @@ set(ZIG_STAGE2_SOURCES
444 lib/std/json.zig444 lib/std/json.zig
445 lib/std/json/stringify.zig445 lib/std/json/stringify.zig
446 lib/std/leb128.zig446 lib/std/leb128.zig
447 lib/std/linked_list.zig
448 lib/std/log.zig447 lib/std/log.zig
449 lib/std/macho.zig448 lib/std/macho.zig
450 lib/std/math.zig449 lib/std/math.zig
lib/std/DoublyLinkedList.zig created+284
...@@ -0,0 +1,284 @@
1//! A doubly-linked list has a pair of pointers to both the head and
2//! tail of the list. List elements have pointers to both the previous
3//! and next elements in the sequence. The list can be traversed both
4//! forward and backward. Some operations that take linear O(n) time
5//! with a singly-linked list can be done without traversal in constant
6//! O(1) time with a doubly-linked list:
7//!
8//! * Removing an element.
9//! * Inserting a new element before an existing element.
10//! * Pushing or popping an element from the end of the list.
11
12const std = @import("std.zig");
13const debug = std.debug;
14const assert = debug.assert;
15const testing = std.testing;
16const DoublyLinkedList = @This();
17
18first: ?*Node = null,
19last: ?*Node = null,
20
21/// This struct contains only the prev and next pointers and not any data
22/// payload. The intended usage is to embed it intrusively into another data
23/// structure and access the data with `@fieldParentPtr`.
24pub const Node = struct {
25 prev: ?*Node = null,
26 next: ?*Node = null,
27};
28
29pub fn insertAfter(list: *DoublyLinkedList, existing_node: *Node, new_node: *Node) void {
30 new_node.prev = existing_node;
31 if (existing_node.next) |next_node| {
32 // Intermediate node.
33 new_node.next = next_node;
34 next_node.prev = new_node;
35 } else {
36 // Last element of the list.
37 new_node.next = null;
38 list.last = new_node;
39 }
40 existing_node.next = new_node;
41}
42
43pub fn insertBefore(list: *DoublyLinkedList, existing_node: *Node, new_node: *Node) void {
44 new_node.next = existing_node;
45 if (existing_node.prev) |prev_node| {
46 // Intermediate node.
47 new_node.prev = prev_node;
48 prev_node.next = new_node;
49 } else {
50 // First element of the list.
51 new_node.prev = null;
52 list.first = new_node;
53 }
54 existing_node.prev = new_node;
55}
56
57/// Concatenate list2 onto the end of list1, removing all entries from the former.
58///
59/// Arguments:
60/// list1: the list to concatenate onto
61/// list2: the list to be concatenated
62pub fn concatByMoving(list1: *DoublyLinkedList, list2: *DoublyLinkedList) void {
63 const l2_first = list2.first orelse return;
64 if (list1.last) |l1_last| {
65 l1_last.next = list2.first;
66 l2_first.prev = list1.last;
67 } else {
68 // list1 was empty
69 list1.first = list2.first;
70 }
71 list1.last = list2.last;
72 list2.first = null;
73 list2.last = null;
74}
75
76/// Insert a new node at the end of the list.
77///
78/// Arguments:
79/// new_node: Pointer to the new node to insert.
80pub fn append(list: *DoublyLinkedList, new_node: *Node) void {
81 if (list.last) |last| {
82 // Insert after last.
83 list.insertAfter(last, new_node);
84 } else {
85 // Empty list.
86 list.prepend(new_node);
87 }
88}
89
90/// Insert a new node at the beginning of the list.
91///
92/// Arguments:
93/// new_node: Pointer to the new node to insert.
94pub fn prepend(list: *DoublyLinkedList, new_node: *Node) void {
95 if (list.first) |first| {
96 // Insert before first.
97 list.insertBefore(first, new_node);
98 } else {
99 // Empty list.
100 list.first = new_node;
101 list.last = new_node;
102 new_node.prev = null;
103 new_node.next = null;
104 }
105}
106
107/// Remove a node from the list.
108///
109/// Arguments:
110/// node: Pointer to the node to be removed.
111pub fn remove(list: *DoublyLinkedList, node: *Node) void {
112 if (node.prev) |prev_node| {
113 // Intermediate node.
114 prev_node.next = node.next;
115 } else {
116 // First element of the list.
117 list.first = node.next;
118 }
119
120 if (node.next) |next_node| {
121 // Intermediate node.
122 next_node.prev = node.prev;
123 } else {
124 // Last element of the list.
125 list.last = node.prev;
126 }
127}
128
129/// Remove and return the last node in the list.
130///
131/// Returns:
132/// A pointer to the last node in the list.
133pub fn pop(list: *DoublyLinkedList) ?*Node {
134 const last = list.last orelse return null;
135 list.remove(last);
136 return last;
137}
138
139/// Remove and return the first node in the list.
140///
141/// Returns:
142/// A pointer to the first node in the list.
143pub fn popFirst(list: *DoublyLinkedList) ?*Node {
144 const first = list.first orelse return null;
145 list.remove(first);
146 return first;
147}
148
149/// Iterate over all nodes, returning the count.
150///
151/// This operation is O(N). Consider tracking the length separately rather than
152/// computing it.
153pub fn len(list: DoublyLinkedList) usize {
154 var count: usize = 0;
155 var it: ?*const Node = list.first;
156 while (it) |n| : (it = n.next) count += 1;
157 return count;
158}
159
160test "basics" {
161 const L = struct {
162 data: u32,
163 node: DoublyLinkedList.Node = .{},
164 };
165 var list: DoublyLinkedList = .{};
166
167 var one: L = .{ .data = 1 };
168 var two: L = .{ .data = 2 };
169 var three: L = .{ .data = 3 };
170 var four: L = .{ .data = 4 };
171 var five: L = .{ .data = 5 };
172
173 list.append(&two.node); // {2}
174 list.append(&five.node); // {2, 5}
175 list.prepend(&one.node); // {1, 2, 5}
176 list.insertBefore(&five.node, &four.node); // {1, 2, 4, 5}
177 list.insertAfter(&two.node, &three.node); // {1, 2, 3, 4, 5}
178
179 // Traverse forwards.
180 {
181 var it = list.first;
182 var index: u32 = 1;
183 while (it) |node| : (it = node.next) {
184 const l: *L = @fieldParentPtr("node", node);
185 try testing.expect(l.data == index);
186 index += 1;
187 }
188 }
189
190 // Traverse backwards.
191 {
192 var it = list.last;
193 var index: u32 = 1;
194 while (it) |node| : (it = node.prev) {
195 const l: *L = @fieldParentPtr("node", node);
196 try testing.expect(l.data == (6 - index));
197 index += 1;
198 }
199 }
200
201 _ = list.popFirst(); // {2, 3, 4, 5}
202 _ = list.pop(); // {2, 3, 4}
203 list.remove(&three.node); // {2, 4}
204
205 try testing.expect(@as(*L, @fieldParentPtr("node", list.first.?)).data == 2);
206 try testing.expect(@as(*L, @fieldParentPtr("node", list.last.?)).data == 4);
207 try testing.expect(list.len() == 2);
208}
209
210test "concatenation" {
211 const L = struct {
212 data: u32,
213 node: DoublyLinkedList.Node = .{},
214 };
215 var list1: DoublyLinkedList = .{};
216 var list2: DoublyLinkedList = .{};
217
218 var one: L = .{ .data = 1 };
219 var two: L = .{ .data = 2 };
220 var three: L = .{ .data = 3 };
221 var four: L = .{ .data = 4 };
222 var five: L = .{ .data = 5 };
223
224 list1.append(&one.node);
225 list1.append(&two.node);
226 list2.append(&three.node);
227 list2.append(&four.node);
228 list2.append(&five.node);
229
230 list1.concatByMoving(&list2);
231
232 try testing.expect(list1.last == &five.node);
233 try testing.expect(list1.len() == 5);
234 try testing.expect(list2.first == null);
235 try testing.expect(list2.last == null);
236 try testing.expect(list2.len() == 0);
237
238 // Traverse forwards.
239 {
240 var it = list1.first;
241 var index: u32 = 1;
242 while (it) |node| : (it = node.next) {
243 const l: *L = @fieldParentPtr("node", node);
244 try testing.expect(l.data == index);
245 index += 1;
246 }
247 }
248
249 // Traverse backwards.
250 {
251 var it = list1.last;
252 var index: u32 = 1;
253 while (it) |node| : (it = node.prev) {
254 const l: *L = @fieldParentPtr("node", node);
255 try testing.expect(l.data == (6 - index));
256 index += 1;
257 }
258 }
259
260 // Swap them back, this verifies that concatenating to an empty list works.
261 list2.concatByMoving(&list1);
262
263 // Traverse forwards.
264 {
265 var it = list2.first;
266 var index: u32 = 1;
267 while (it) |node| : (it = node.next) {
268 const l: *L = @fieldParentPtr("node", node);
269 try testing.expect(l.data == index);
270 index += 1;
271 }
272 }
273
274 // Traverse backwards.
275 {
276 var it = list2.last;
277 var index: u32 = 1;
278 while (it) |node| : (it = node.prev) {
279 const l: *L = @fieldParentPtr("node", node);
280 try testing.expect(l.data == (6 - index));
281 index += 1;
282 }
283 }
284}
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");...@@ -5,7 +5,7 @@ const WaitGroup = @import("WaitGroup.zig");
55
6mutex: std.Thread.Mutex = .{},6mutex: std.Thread.Mutex = .{},
7cond: std.Thread.Condition = .{},7cond: std.Thread.Condition = .{},
8run_queue: RunQueue = .{},8run_queue: std.SinglyLinkedList = .{},
9is_running: bool = true,9is_running: bool = true,
10allocator: std.mem.Allocator,10allocator: std.mem.Allocator,
11threads: if (builtin.single_threaded) [0]std.Thread else []std.Thread,11threads: if (builtin.single_threaded) [0]std.Thread else []std.Thread,
...@@ -16,9 +16,9 @@ ids: if (builtin.single_threaded) struct {...@@ -16,9 +16,9 @@ ids: if (builtin.single_threaded) struct {
16 }16 }
17} else std.AutoArrayHashMapUnmanaged(std.Thread.Id, void),17} else std.AutoArrayHashMapUnmanaged(std.Thread.Id, void),
1818
19const RunQueue = std.SinglyLinkedList(Runnable);
20const Runnable = struct {19const Runnable = struct {
21 runFn: RunProto,20 runFn: RunProto,
21 node: std.SinglyLinkedList.Node = .{},
22};22};
2323
24const RunProto = *const fn (*Runnable, id: ?usize) void;24const RunProto = *const fn (*Runnable, id: ?usize) void;
...@@ -110,12 +110,11 @@ pub fn spawnWg(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, args...@@ -110,12 +110,11 @@ pub fn spawnWg(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, args
110 const Closure = struct {110 const Closure = struct {
111 arguments: Args,111 arguments: Args,
112 pool: *Pool,112 pool: *Pool,
113 run_node: RunQueue.Node = .{ .data = .{ .runFn = runFn } },113 runnable: Runnable = .{ .runFn = runFn },
114 wait_group: *WaitGroup,114 wait_group: *WaitGroup,
115115
116 fn runFn(runnable: *Runnable, _: ?usize) void {116 fn runFn(runnable: *Runnable, _: ?usize) void {
117 const run_node: *RunQueue.Node = @fieldParentPtr("data", runnable);117 const closure: *@This() = @alignCast(@fieldParentPtr("runnable", runnable));
118 const closure: *@This() = @alignCast(@fieldParentPtr("run_node", run_node));
119 @call(.auto, func, closure.arguments);118 @call(.auto, func, closure.arguments);
120 closure.wait_group.finish();119 closure.wait_group.finish();
121120
...@@ -143,7 +142,7 @@ pub fn spawnWg(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, args...@@ -143,7 +142,7 @@ pub fn spawnWg(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, args
143 .wait_group = wait_group,142 .wait_group = wait_group,
144 };143 };
145144
146 pool.run_queue.prepend(&closure.run_node);145 pool.run_queue.prepend(&closure.runnable.node);
147 pool.mutex.unlock();146 pool.mutex.unlock();
148 }147 }
149148
...@@ -173,12 +172,11 @@ pub fn spawnWgId(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, ar...@@ -173,12 +172,11 @@ pub fn spawnWgId(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, ar
173 const Closure = struct {172 const Closure = struct {
174 arguments: Args,173 arguments: Args,
175 pool: *Pool,174 pool: *Pool,
176 run_node: RunQueue.Node = .{ .data = .{ .runFn = runFn } },175 runnable: Runnable = .{ .runFn = runFn },
177 wait_group: *WaitGroup,176 wait_group: *WaitGroup,
178177
179 fn runFn(runnable: *Runnable, id: ?usize) void {178 fn runFn(runnable: *Runnable, id: ?usize) void {
180 const run_node: *RunQueue.Node = @fieldParentPtr("data", runnable);179 const closure: *@This() = @alignCast(@fieldParentPtr("runnable", runnable));
181 const closure: *@This() = @alignCast(@fieldParentPtr("run_node", run_node));
182 @call(.auto, func, .{id.?} ++ closure.arguments);180 @call(.auto, func, .{id.?} ++ closure.arguments);
183 closure.wait_group.finish();181 closure.wait_group.finish();
184182
...@@ -207,7 +205,7 @@ pub fn spawnWgId(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, ar...@@ -207,7 +205,7 @@ pub fn spawnWgId(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, ar
207 .wait_group = wait_group,205 .wait_group = wait_group,
208 };206 };
209207
210 pool.run_queue.prepend(&closure.run_node);208 pool.run_queue.prepend(&closure.runnable.node);
211 pool.mutex.unlock();209 pool.mutex.unlock();
212 }210 }
213211
...@@ -225,11 +223,10 @@ pub fn spawn(pool: *Pool, comptime func: anytype, args: anytype) !void {...@@ -225,11 +223,10 @@ pub fn spawn(pool: *Pool, comptime func: anytype, args: anytype) !void {
225 const Closure = struct {223 const Closure = struct {
226 arguments: Args,224 arguments: Args,
227 pool: *Pool,225 pool: *Pool,
228 run_node: RunQueue.Node = .{ .data = .{ .runFn = runFn } },226 runnable: Runnable = .{ .runFn = runFn },
229227
230 fn runFn(runnable: *Runnable, _: ?usize) void {228 fn runFn(runnable: *Runnable, _: ?usize) void {
231 const run_node: *RunQueue.Node = @fieldParentPtr("data", runnable);229 const closure: *@This() = @alignCast(@fieldParentPtr("runnable", runnable));
232 const closure: *@This() = @alignCast(@fieldParentPtr("run_node", run_node));
233 @call(.auto, func, closure.arguments);230 @call(.auto, func, closure.arguments);
234231
235 // The thread pool's allocator is protected by the mutex.232 // 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 {...@@ -251,7 +248,7 @@ pub fn spawn(pool: *Pool, comptime func: anytype, args: anytype) !void {
251 .pool = pool,248 .pool = pool,
252 };249 };
253250
254 pool.run_queue.prepend(&closure.run_node);251 pool.run_queue.prepend(&closure.runnable.node);
255 }252 }
256253
257 // Notify waiting threads outside the lock to try and keep the critical section small.254 // Notify waiting threads outside the lock to try and keep the critical section small.
...@@ -292,7 +289,8 @@ fn worker(pool: *Pool) void {...@@ -292,7 +289,8 @@ fn worker(pool: *Pool) void {
292 pool.mutex.unlock();289 pool.mutex.unlock();
293 defer pool.mutex.lock();290 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);
296 }294 }
297295
298 // Stop executing instead of waiting if the thread pool is no longer running.296 // 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 {...@@ -312,7 +310,8 @@ pub fn waitAndWork(pool: *Pool, wait_group: *WaitGroup) void {
312 if (pool.run_queue.popFirst()) |run_node| {310 if (pool.run_queue.popFirst()) |run_node| {
313 id = id orelse pool.ids.getIndex(std.Thread.getCurrentId());311 id = id orelse pool.ids.getIndex(std.Thread.getCurrentId());
314 pool.mutex.unlock();312 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);
316 continue;315 continue;
317 }316 }
318317
lib/std/heap/arena_allocator.zig+25-16
...@@ -14,7 +14,7 @@ pub const ArenaAllocator = struct {...@@ -14,7 +14,7 @@ pub const ArenaAllocator = struct {
14 /// Inner state of ArenaAllocator. Can be stored rather than the entire ArenaAllocator14 /// Inner state of ArenaAllocator. Can be stored rather than the entire ArenaAllocator
15 /// as a memory-saving optimization.15 /// as a memory-saving optimization.
16 pub const State = struct {16 pub const State = struct {
17 buffer_list: std.SinglyLinkedList(usize) = .{},17 buffer_list: std.SinglyLinkedList = .{},
18 end_index: usize = 0,18 end_index: usize = 0,
1919
20 pub fn promote(self: State, child_allocator: Allocator) ArenaAllocator {20 pub fn promote(self: State, child_allocator: Allocator) ArenaAllocator {
...@@ -37,7 +37,10 @@ pub const ArenaAllocator = struct {...@@ -37,7 +37,10 @@ pub const ArenaAllocator = struct {
37 };37 };
38 }38 }
3939
40 const BufNode = std.SinglyLinkedList(usize).Node;40 const BufNode = struct {
41 data: usize,
42 node: std.SinglyLinkedList.Node = .{},
43 };
41 const BufNode_alignment: mem.Alignment = .fromByteUnits(@alignOf(BufNode));44 const BufNode_alignment: mem.Alignment = .fromByteUnits(@alignOf(BufNode));
4245
43 pub fn init(child_allocator: Allocator) ArenaAllocator {46 pub fn init(child_allocator: Allocator) ArenaAllocator {
...@@ -51,7 +54,8 @@ pub const ArenaAllocator = struct {...@@ -51,7 +54,8 @@ pub const ArenaAllocator = struct {
51 while (it) |node| {54 while (it) |node| {
52 // this has to occur before the free because the free frees node55 // this has to occur before the free because the free frees node
53 const next_it = node.next;56 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];
55 self.child_allocator.rawFree(alloc_buf, BufNode_alignment, @returnAddress());59 self.child_allocator.rawFree(alloc_buf, BufNode_alignment, @returnAddress());
56 it = next_it;60 it = next_it;
57 }61 }
...@@ -78,7 +82,8 @@ pub const ArenaAllocator = struct {...@@ -78,7 +82,8 @@ pub const ArenaAllocator = struct {
78 while (it) |node| : (it = node.next) {82 while (it) |node| : (it = node.next) {
79 // Compute the actually allocated size excluding the83 // Compute the actually allocated size excluding the
80 // linked list node.84 // linked list node.
81 size += node.data - @sizeOf(BufNode);85 const buf_node: *BufNode = @fieldParentPtr("node", node);
86 size += buf_node.data - @sizeOf(BufNode);
82 }87 }
83 return size;88 return size;
84 }89 }
...@@ -130,7 +135,8 @@ pub const ArenaAllocator = struct {...@@ -130,7 +135,8 @@ pub const ArenaAllocator = struct {
130 const next_it = node.next;135 const next_it = node.next;
131 if (next_it == null)136 if (next_it == null)
132 break node;137 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];
134 self.child_allocator.rawFree(alloc_buf, BufNode_alignment, @returnAddress());140 self.child_allocator.rawFree(alloc_buf, BufNode_alignment, @returnAddress());
135 it = next_it;141 it = next_it;
136 } else null;142 } else null;
...@@ -140,12 +146,13 @@ pub const ArenaAllocator = struct {...@@ -140,12 +146,13 @@ pub const ArenaAllocator = struct {
140 if (maybe_first_node) |first_node| {146 if (maybe_first_node) |first_node| {
141 self.state.buffer_list.first = first_node;147 self.state.buffer_list.first = first_node;
142 // perfect, no need to invoke the child_allocator148 // 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)
144 return true;151 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];
146 if (self.child_allocator.rawResize(first_alloc_buf, BufNode_alignment, total_size, @returnAddress())) {153 if (self.child_allocator.rawResize(first_alloc_buf, BufNode_alignment, total_size, @returnAddress())) {
147 // successful resize154 // successful resize
148 first_node.data = total_size;155 first_buf_node.data = total_size;
149 } else {156 } else {
150 // manual realloc157 // manual realloc
151 const new_ptr = self.child_allocator.rawAlloc(total_size, BufNode_alignment, @returnAddress()) orelse {158 const new_ptr = self.child_allocator.rawAlloc(total_size, BufNode_alignment, @returnAddress()) orelse {
...@@ -153,9 +160,9 @@ pub const ArenaAllocator = struct {...@@ -153,9 +160,9 @@ pub const ArenaAllocator = struct {
153 return false;160 return false;
154 };161 };
155 self.child_allocator.rawFree(first_alloc_buf, BufNode_alignment, @returnAddress());162 self.child_allocator.rawFree(first_alloc_buf, BufNode_alignment, @returnAddress());
156 const node: *BufNode = @ptrCast(@alignCast(new_ptr));163 const buf_node: *BufNode = @ptrCast(@alignCast(new_ptr));
157 node.* = .{ .data = total_size };164 buf_node.* = .{ .data = total_size };
158 self.state.buffer_list.first = node;165 self.state.buffer_list.first = &buf_node.node;
159 }166 }
160 }167 }
161 return true;168 return true;
...@@ -169,7 +176,7 @@ pub const ArenaAllocator = struct {...@@ -169,7 +176,7 @@ pub const ArenaAllocator = struct {
169 return null;176 return null;
170 const buf_node: *BufNode = @ptrCast(@alignCast(ptr));177 const buf_node: *BufNode = @ptrCast(@alignCast(ptr));
171 buf_node.* = .{ .data = len };178 buf_node.* = .{ .data = len };
172 self.state.buffer_list.prepend(buf_node);179 self.state.buffer_list.prepend(&buf_node.node);
173 self.state.end_index = 0;180 self.state.end_index = 0;
174 return buf_node;181 return buf_node;
175 }182 }
...@@ -179,8 +186,8 @@ pub const ArenaAllocator = struct {...@@ -179,8 +186,8 @@ pub const ArenaAllocator = struct {
179 _ = ra;186 _ = ra;
180187
181 const ptr_align = alignment.toByteUnits();188 const ptr_align = alignment.toByteUnits();
182 var cur_node = if (self.state.buffer_list.first) |first_node|189 var cur_node: *BufNode = if (self.state.buffer_list.first) |first_node|
183 first_node190 @fieldParentPtr("node", first_node)
184 else191 else
185 (self.createNode(0, n + ptr_align) orelse return null);192 (self.createNode(0, n + ptr_align) orelse return null);
186 while (true) {193 while (true) {
...@@ -213,7 +220,8 @@ pub const ArenaAllocator = struct {...@@ -213,7 +220,8 @@ pub const ArenaAllocator = struct {
213 _ = ret_addr;220 _ = ret_addr;
214221
215 const cur_node = self.state.buffer_list.first orelse return false;222 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];
217 if (@intFromPtr(cur_buf.ptr) + self.state.end_index != @intFromPtr(buf.ptr) + buf.len) {225 if (@intFromPtr(cur_buf.ptr) + self.state.end_index != @intFromPtr(buf.ptr) + buf.len) {
218 // It's not the most recent allocation, so it cannot be expanded,226 // It's not the most recent allocation, so it cannot be expanded,
219 // but it's fine if they want to make it smaller.227 // but it's fine if they want to make it smaller.
...@@ -248,7 +256,8 @@ pub const ArenaAllocator = struct {...@@ -248,7 +256,8 @@ pub const ArenaAllocator = struct {
248 const self: *ArenaAllocator = @ptrCast(@alignCast(ctx));256 const self: *ArenaAllocator = @ptrCast(@alignCast(ctx));
249257
250 const cur_node = self.state.buffer_list.first orelse return;258 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
253 if (@intFromPtr(cur_buf.ptr) + self.state.end_index == @intFromPtr(buf.ptr) + buf.len) {262 if (@intFromPtr(cur_buf.ptr) + self.state.end_index == @intFromPtr(buf.ptr) + buf.len) {
254 self.state.end_index -= buf.len;263 self.state.end_index -= buf.len;
lib/std/http/Client.zig+44-43
...@@ -46,9 +46,9 @@ https_proxy: ?*Proxy = null,...@@ -46,9 +46,9 @@ https_proxy: ?*Proxy = null,
46pub const ConnectionPool = struct {46pub const ConnectionPool = struct {
47 mutex: std.Thread.Mutex = .{},47 mutex: std.Thread.Mutex = .{},
48 /// Open connections that are currently in use.48 /// Open connections that are currently in use.
49 used: Queue = .{},49 used: std.DoublyLinkedList = .{},
50 /// Open connections that are not currently in use.50 /// Open connections that are not currently in use.
51 free: Queue = .{},51 free: std.DoublyLinkedList = .{},
52 free_len: usize = 0,52 free_len: usize = 0,
53 free_size: usize = 32,53 free_size: usize = 32,
5454
...@@ -59,9 +59,6 @@ pub const ConnectionPool = struct {...@@ -59,9 +59,6 @@ pub const ConnectionPool = struct {
59 protocol: Connection.Protocol,59 protocol: Connection.Protocol,
60 };60 };
6161
62 const Queue = std.DoublyLinkedList(Connection);
63 pub const Node = Queue.Node;
64
65 /// Finds and acquires a connection from the connection pool matching the criteria. This function is threadsafe.62 /// Finds and acquires a connection from the connection pool matching the criteria. This function is threadsafe.
66 /// If no connection is found, null is returned.63 /// If no connection is found, null is returned.
67 pub fn findConnection(pool: *ConnectionPool, criteria: Criteria) ?*Connection {64 pub fn findConnection(pool: *ConnectionPool, criteria: Criteria) ?*Connection {
...@@ -70,33 +67,34 @@ pub const ConnectionPool = struct {...@@ -70,33 +67,34 @@ pub const ConnectionPool = struct {
7067
71 var next = pool.free.last;68 var next = pool.free.last;
72 while (next) |node| : (next = node.prev) {69 while (next) |node| : (next = node.prev) {
73 if (node.data.protocol != criteria.protocol) continue;70 const connection: *Connection = @fieldParentPtr("pool_node", node);
74 if (node.data.port != criteria.port) continue;71 if (connection.protocol != criteria.protocol) continue;
72 if (connection.port != criteria.port) continue;
7573
76 // Domain names are case-insensitive (RFC 5890, Section 2.3.2.4)74 // Domain names are case-insensitive (RFC 5890, Section 2.3.2.4)
77 if (!std.ascii.eqlIgnoreCase(node.data.host, criteria.host)) continue;75 if (!std.ascii.eqlIgnoreCase(connection.host, criteria.host)) continue;
7876
79 pool.acquireUnsafe(node);77 pool.acquireUnsafe(connection);
80 return &node.data;78 return connection;
81 }79 }
8280
83 return null;81 return null;
84 }82 }
8583
86 /// Acquires an existing connection from the connection pool. This function is not threadsafe.84 /// Acquires an existing connection from the connection pool. This function is not threadsafe.
87 pub fn acquireUnsafe(pool: *ConnectionPool, node: *Node) void {85 pub fn acquireUnsafe(pool: *ConnectionPool, connection: *Connection) void {
88 pool.free.remove(node);86 pool.free.remove(&connection.pool_node);
89 pool.free_len -= 1;87 pool.free_len -= 1;
9088
91 pool.used.append(node);89 pool.used.append(&connection.pool_node);
92 }90 }
9391
94 /// Acquires an existing connection from the connection pool. This function is threadsafe.92 /// Acquires an existing connection from the connection pool. This function is threadsafe.
95 pub fn acquire(pool: *ConnectionPool, node: *Node) void {93 pub fn acquire(pool: *ConnectionPool, connection: *Connection) void {
96 pool.mutex.lock();94 pool.mutex.lock();
97 defer pool.mutex.unlock();95 defer pool.mutex.unlock();
9896
99 return pool.acquireUnsafe(node);97 return pool.acquireUnsafe(connection);
100 }98 }
10199
102 /// Tries to release a connection back to the connection pool. This function is threadsafe.100 /// Tries to release a connection back to the connection pool. This function is threadsafe.
...@@ -108,38 +106,37 @@ pub const ConnectionPool = struct {...@@ -108,38 +106,37 @@ pub const ConnectionPool = struct {
108 pool.mutex.lock();106 pool.mutex.lock();
109 defer pool.mutex.unlock();107 defer pool.mutex.unlock();
110108
111 const node: *Node = @fieldParentPtr("data", connection);109 pool.used.remove(&connection.pool_node);
112
113 pool.used.remove(node);
114110
115 if (node.data.closing or pool.free_size == 0) {111 if (connection.closing or pool.free_size == 0) {
116 node.data.close(allocator);112 connection.close(allocator);
117 return allocator.destroy(node);113 return allocator.destroy(connection);
118 }114 }
119115
120 if (pool.free_len >= pool.free_size) {116 if (pool.free_len >= pool.free_size) {
121 const popped = pool.free.popFirst() orelse unreachable;117 const popped: *Connection = @fieldParentPtr("pool_node", pool.free.popFirst().?);
122 pool.free_len -= 1;118 pool.free_len -= 1;
123119
124 popped.data.close(allocator);120 popped.close(allocator);
125 allocator.destroy(popped);121 allocator.destroy(popped);
126 }122 }
127123
128 if (node.data.proxied) {124 if (connection.proxied) {
129 pool.free.prepend(node); // proxied connections go to the end of the queue, always try direct connections first125 // proxied connections go to the end of the queue, always try direct connections first
126 pool.free.prepend(&connection.pool_node);
130 } else {127 } else {
131 pool.free.append(node);128 pool.free.append(&connection.pool_node);
132 }129 }
133130
134 pool.free_len += 1;131 pool.free_len += 1;
135 }132 }
136133
137 /// Adds a newly created node to the pool of used connections. This function is threadsafe.134 /// Adds a newly created node to the pool of used connections. This function is threadsafe.
138 pub fn addUsed(pool: *ConnectionPool, node: *Node) void {135 pub fn addUsed(pool: *ConnectionPool, connection: *Connection) void {
139 pool.mutex.lock();136 pool.mutex.lock();
140 defer pool.mutex.unlock();137 defer pool.mutex.unlock();
141138
142 pool.used.append(node);139 pool.used.append(&connection.pool_node);
143 }140 }
144141
145 /// Resizes the connection pool. This function is threadsafe.142 /// Resizes the connection pool. This function is threadsafe.
...@@ -170,18 +167,18 @@ pub const ConnectionPool = struct {...@@ -170,18 +167,18 @@ pub const ConnectionPool = struct {
170167
171 var next = pool.free.first;168 var next = pool.free.first;
172 while (next) |node| {169 while (next) |node| {
173 defer allocator.destroy(node);170 const connection: *Connection = @fieldParentPtr("pool_node", node);
174 next = node.next;171 next = node.next;
175172 connection.close(allocator);
176 node.data.close(allocator);173 allocator.destroy(connection);
177 }174 }
178175
179 next = pool.used.first;176 next = pool.used.first;
180 while (next) |node| {177 while (next) |node| {
181 defer allocator.destroy(node);178 const connection: *Connection = @fieldParentPtr("pool_node", node);
182 next = node.next;179 next = node.next;
183180 connection.close(allocator);
184 node.data.close(allocator);181 allocator.destroy(node);
185 }182 }
186183
187 pool.* = undefined;184 pool.* = undefined;
...@@ -194,6 +191,9 @@ pub const Connection = struct {...@@ -194,6 +191,9 @@ pub const Connection = struct {
194 /// undefined unless protocol is tls.191 /// undefined unless protocol is tls.
195 tls_client: if (!disable_tls) *std.crypto.tls.Client else void,192 tls_client: if (!disable_tls) *std.crypto.tls.Client else void,
196193
194 /// Entry in `ConnectionPool.used` or `ConnectionPool.free`.
195 pool_node: std.DoublyLinkedList.Node,
196
197 /// The protocol that this connection is using.197 /// The protocol that this connection is using.
198 protocol: Protocol,198 protocol: Protocol,
199199
...@@ -1326,9 +1326,8 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec...@@ -1326,9 +1326,8 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
1326 if (disable_tls and protocol == .tls)1326 if (disable_tls and protocol == .tls)
1327 return error.TlsInitializationFailed;1327 return error.TlsInitializationFailed;
13281328
1329 const conn = try client.allocator.create(ConnectionPool.Node);1329 const conn = try client.allocator.create(Connection);
1330 errdefer client.allocator.destroy(conn);1330 errdefer client.allocator.destroy(conn);
1331 conn.* = .{ .data = undefined };
13321331
1333 const stream = net.tcpConnectToHost(client.allocator, host, port) catch |err| switch (err) {1332 const stream = net.tcpConnectToHost(client.allocator, host, port) catch |err| switch (err) {
1334 error.ConnectionRefused => return error.ConnectionRefused,1333 error.ConnectionRefused => return error.ConnectionRefused,
...@@ -1343,21 +1342,23 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec...@@ -1343,21 +1342,23 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
1343 };1342 };
1344 errdefer stream.close();1343 errdefer stream.close();
13451344
1346 conn.data = .{1345 conn.* = .{
1347 .stream = stream,1346 .stream = stream,
1348 .tls_client = undefined,1347 .tls_client = undefined,
13491348
1350 .protocol = protocol,1349 .protocol = protocol,
1351 .host = try client.allocator.dupe(u8, host),1350 .host = try client.allocator.dupe(u8, host),
1352 .port = port,1351 .port = port,
1352
1353 .pool_node = .{},
1353 };1354 };
1354 errdefer client.allocator.free(conn.data.host);1355 errdefer client.allocator.free(conn.host);
13551356
1356 if (protocol == .tls) {1357 if (protocol == .tls) {
1357 if (disable_tls) unreachable;1358 if (disable_tls) unreachable;
13581359
1359 conn.data.tls_client = try client.allocator.create(std.crypto.tls.Client);1360 conn.tls_client = try client.allocator.create(std.crypto.tls.Client);
1360 errdefer client.allocator.destroy(conn.data.tls_client);1361 errdefer client.allocator.destroy(conn.tls_client);
13611362
1362 const ssl_key_log_file: ?std.fs.File = if (std.options.http_enable_ssl_key_log_file) ssl_key_log_file: {1363 const ssl_key_log_file: ?std.fs.File = if (std.options.http_enable_ssl_key_log_file) ssl_key_log_file: {
1363 const ssl_key_log_path = std.process.getEnvVarOwned(client.allocator, "SSLKEYLOGFILE") catch |err| switch (err) {1364 const ssl_key_log_path = std.process.getEnvVarOwned(client.allocator, "SSLKEYLOGFILE") catch |err| switch (err) {
...@@ -1375,19 +1376,19 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec...@@ -1375,19 +1376,19 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
1375 } else null;1376 } else null;
1376 errdefer if (ssl_key_log_file) |key_log_file| key_log_file.close();1377 errdefer if (ssl_key_log_file) |key_log_file| key_log_file.close();
13771378
1378 conn.data.tls_client.* = std.crypto.tls.Client.init(stream, .{1379 conn.tls_client.* = std.crypto.tls.Client.init(stream, .{
1379 .host = .{ .explicit = host },1380 .host = .{ .explicit = host },
1380 .ca = .{ .bundle = client.ca_bundle },1381 .ca = .{ .bundle = client.ca_bundle },
1381 .ssl_key_log_file = ssl_key_log_file,1382 .ssl_key_log_file = ssl_key_log_file,
1382 }) catch return error.TlsInitializationFailed;1383 }) catch return error.TlsInitializationFailed;
1383 // This is appropriate for HTTPS because the HTTP headers contain1384 // This is appropriate for HTTPS because the HTTP headers contain
1384 // the content length which is used to detect truncation attacks.1385 // the content length which is used to detect truncation attacks.
1385 conn.data.tls_client.allow_truncation_attacks = true;1386 conn.tls_client.allow_truncation_attacks = true;
1386 }1387 }
13871388
1388 client.connection_pool.addUsed(conn);1389 client.connection_pool.addUsed(conn);
13891390
1390 return &conn.data;1391 return conn;
1391}1392}
13921393
1393pub const ConnectUnixError = Allocator.Error || std.posix.SocketError || error{NameTooLong} || std.posix.ConnectError;1394pub const ConnectUnixError = Allocator.Error || std.posix.SocketError || error{NameTooLong} || std.posix.ConnectError;
lib/std/linked_list.zig deleted-455
...@@ -1,455 +0,0 @@
1const std = @import("std.zig");
2const debug = std.debug;
3const assert = debug.assert;
4const testing = std.testing;
5
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
174/// A doubly-linked list has a pair of pointers to both the head and
175/// tail of the list. List elements have pointers to both the previous
176/// and next elements in the sequence. The list can be traversed both
177/// forward and backward. Some operations that take linear O(n) time
178/// with a singly-linked list can be done without traversal in constant
179/// O(1) time with a doubly-linked list:
180///
181/// - Removing an element.
182/// - Inserting a new element before an existing element.
183/// - Pushing or popping an element from the end of the list.
184pub fn DoublyLinkedList(comptime T: type) type {
185 return struct {
186 const Self = @This();
187
188 /// Node inside the linked list wrapping the actual data.
189 pub const Node = struct {
190 prev: ?*Node = null,
191 next: ?*Node = null,
192 data: T,
193 };
194
195 first: ?*Node = null,
196 last: ?*Node = null,
197 len: usize = 0,
198
199 /// Insert a new node after an existing one.
200 ///
201 /// Arguments:
202 /// node: Pointer to a node in the list.
203 /// new_node: Pointer to the new node to insert.
204 pub fn insertAfter(list: *Self, node: *Node, new_node: *Node) void {
205 new_node.prev = node;
206 if (node.next) |next_node| {
207 // Intermediate node.
208 new_node.next = next_node;
209 next_node.prev = new_node;
210 } else {
211 // Last element of the list.
212 new_node.next = null;
213 list.last = new_node;
214 }
215 node.next = new_node;
216
217 list.len += 1;
218 }
219
220 /// Insert a new node before an existing one.
221 ///
222 /// Arguments:
223 /// node: Pointer to a node in the list.
224 /// new_node: Pointer to the new node to insert.
225 pub fn insertBefore(list: *Self, node: *Node, new_node: *Node) void {
226 new_node.next = node;
227 if (node.prev) |prev_node| {
228 // Intermediate node.
229 new_node.prev = prev_node;
230 prev_node.next = new_node;
231 } else {
232 // First element of the list.
233 new_node.prev = null;
234 list.first = new_node;
235 }
236 node.prev = new_node;
237
238 list.len += 1;
239 }
240
241 /// Concatenate list2 onto the end of list1, removing all entries from the former.
242 ///
243 /// Arguments:
244 /// list1: the list to concatenate onto
245 /// list2: the list to be concatenated
246 pub fn concatByMoving(list1: *Self, list2: *Self) void {
247 const l2_first = list2.first orelse return;
248 if (list1.last) |l1_last| {
249 l1_last.next = list2.first;
250 l2_first.prev = list1.last;
251 list1.len += list2.len;
252 } else {
253 // list1 was empty
254 list1.first = list2.first;
255 list1.len = list2.len;
256 }
257 list1.last = list2.last;
258 list2.first = null;
259 list2.last = null;
260 list2.len = 0;
261 }
262
263 /// Insert a new node at the end of the list.
264 ///
265 /// Arguments:
266 /// new_node: Pointer to the new node to insert.
267 pub fn append(list: *Self, new_node: *Node) void {
268 if (list.last) |last| {
269 // Insert after last.
270 list.insertAfter(last, new_node);
271 } else {
272 // Empty list.
273 list.prepend(new_node);
274 }
275 }
276
277 /// Insert a new node at the beginning of the list.
278 ///
279 /// Arguments:
280 /// new_node: Pointer to the new node to insert.
281 pub fn prepend(list: *Self, new_node: *Node) void {
282 if (list.first) |first| {
283 // Insert before first.
284 list.insertBefore(first, new_node);
285 } else {
286 // Empty list.
287 list.first = new_node;
288 list.last = new_node;
289 new_node.prev = null;
290 new_node.next = null;
291
292 list.len = 1;
293 }
294 }
295
296 /// Remove a node from the list.
297 ///
298 /// Arguments:
299 /// node: Pointer to the node to be removed.
300 pub fn remove(list: *Self, node: *Node) void {
301 if (node.prev) |prev_node| {
302 // Intermediate node.
303 prev_node.next = node.next;
304 } else {
305 // First element of the list.
306 list.first = node.next;
307 }
308
309 if (node.next) |next_node| {
310 // Intermediate node.
311 next_node.prev = node.prev;
312 } else {
313 // Last element of the list.
314 list.last = node.prev;
315 }
316
317 list.len -= 1;
318 assert(list.len == 0 or (list.first != null and list.last != null));
319 }
320
321 /// Remove and return the last node in the list.
322 ///
323 /// Returns:
324 /// A pointer to the last node in the list.
325 pub fn pop(list: *Self) ?*Node {
326 const last = list.last orelse return null;
327 list.remove(last);
328 return last;
329 }
330
331 /// Remove and return the first node in the list.
332 ///
333 /// Returns:
334 /// A pointer to the first node in the list.
335 pub fn popFirst(list: *Self) ?*Node {
336 const first = list.first orelse return null;
337 list.remove(first);
338 return first;
339 }
340 };
341}
342
343test "basic DoublyLinkedList test" {
344 const L = DoublyLinkedList(u32);
345 var list = L{};
346
347 var one = L.Node{ .data = 1 };
348 var two = L.Node{ .data = 2 };
349 var three = L.Node{ .data = 3 };
350 var four = L.Node{ .data = 4 };
351 var five = L.Node{ .data = 5 };
352
353 list.append(&two); // {2}
354 list.append(&five); // {2, 5}
355 list.prepend(&one); // {1, 2, 5}
356 list.insertBefore(&five, &four); // {1, 2, 4, 5}
357 list.insertAfter(&two, &three); // {1, 2, 3, 4, 5}
358
359 // Traverse forwards.
360 {
361 var it = list.first;
362 var index: u32 = 1;
363 while (it) |node| : (it = node.next) {
364 try testing.expect(node.data == index);
365 index += 1;
366 }
367 }
368
369 // Traverse backwards.
370 {
371 var it = list.last;
372 var index: u32 = 1;
373 while (it) |node| : (it = node.prev) {
374 try testing.expect(node.data == (6 - index));
375 index += 1;
376 }
377 }
378
379 _ = list.popFirst(); // {2, 3, 4, 5}
380 _ = list.pop(); // {2, 3, 4}
381 list.remove(&three); // {2, 4}
382
383 try testing.expect(list.first.?.data == 2);
384 try testing.expect(list.last.?.data == 4);
385 try testing.expect(list.len == 2);
386}
387
388test "DoublyLinkedList concatenation" {
389 const L = DoublyLinkedList(u32);
390 var list1 = L{};
391 var list2 = L{};
392
393 var one = L.Node{ .data = 1 };
394 var two = L.Node{ .data = 2 };
395 var three = L.Node{ .data = 3 };
396 var four = L.Node{ .data = 4 };
397 var five = L.Node{ .data = 5 };
398
399 list1.append(&one);
400 list1.append(&two);
401 list2.append(&three);
402 list2.append(&four);
403 list2.append(&five);
404
405 list1.concatByMoving(&list2);
406
407 try testing.expect(list1.last == &five);
408 try testing.expect(list1.len == 5);
409 try testing.expect(list2.first == null);
410 try testing.expect(list2.last == null);
411 try testing.expect(list2.len == 0);
412
413 // Traverse forwards.
414 {
415 var it = list1.first;
416 var index: u32 = 1;
417 while (it) |node| : (it = node.next) {
418 try testing.expect(node.data == index);
419 index += 1;
420 }
421 }
422
423 // Traverse backwards.
424 {
425 var it = list1.last;
426 var index: u32 = 1;
427 while (it) |node| : (it = node.prev) {
428 try testing.expect(node.data == (6 - index));
429 index += 1;
430 }
431 }
432
433 // Swap them back, this verifies that concatenating to an empty list works.
434 list2.concatByMoving(&list1);
435
436 // Traverse forwards.
437 {
438 var it = list2.first;
439 var index: u32 = 1;
440 while (it) |node| : (it = node.next) {
441 try testing.expect(node.data == index);
442 index += 1;
443 }
444 }
445
446 // Traverse backwards.
447 {
448 var it = list2.last;
449 var index: u32 = 1;
450 while (it) |node| : (it = node.prev) {
451 try testing.expect(node.data == (6 - index));
452 index += 1;
453 }
454 }
455}
lib/std/std.zig+2-2
...@@ -16,7 +16,7 @@ pub const BufMap = @import("buf_map.zig").BufMap;...@@ -16,7 +16,7 @@ pub const BufMap = @import("buf_map.zig").BufMap;
16pub const BufSet = @import("buf_set.zig").BufSet;16pub const BufSet = @import("buf_set.zig").BufSet;
17pub const StaticStringMap = static_string_map.StaticStringMap;17pub const StaticStringMap = static_string_map.StaticStringMap;
18pub const StaticStringMapWithEql = static_string_map.StaticStringMapWithEql;18pub const StaticStringMapWithEql = static_string_map.StaticStringMapWithEql;
19pub const DoublyLinkedList = @import("linked_list.zig").DoublyLinkedList;19pub const DoublyLinkedList = @import("DoublyLinkedList.zig");
20pub const DynLib = @import("dynamic_library.zig").DynLib;20pub const DynLib = @import("dynamic_library.zig").DynLib;
21pub const DynamicBitSet = bit_set.DynamicBitSet;21pub const DynamicBitSet = bit_set.DynamicBitSet;
22pub const DynamicBitSetUnmanaged = bit_set.DynamicBitSetUnmanaged;22pub const DynamicBitSetUnmanaged = bit_set.DynamicBitSetUnmanaged;
...@@ -33,7 +33,7 @@ pub const Random = @import("Random.zig");...@@ -33,7 +33,7 @@ pub const Random = @import("Random.zig");
33pub const RingBuffer = @import("RingBuffer.zig");33pub const RingBuffer = @import("RingBuffer.zig");
34pub const SegmentedList = @import("segmented_list.zig").SegmentedList;34pub const SegmentedList = @import("segmented_list.zig").SegmentedList;
35pub const SemanticVersion = @import("SemanticVersion.zig");35pub const SemanticVersion = @import("SemanticVersion.zig");
36pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;36pub const SinglyLinkedList = @import("SinglyLinkedList.zig");
37pub const StaticBitSet = bit_set.StaticBitSet;37pub const StaticBitSet = bit_set.StaticBitSet;
38pub const StringHashMap = hash_map.StringHashMap;38pub const StringHashMap = hash_map.StringHashMap;
39pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged;39pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged;
src/Package/Fetch/git.zig+17-10
...@@ -473,14 +473,18 @@ const Object = struct {...@@ -473,14 +473,18 @@ const Object = struct {
473/// objects remaining in the cache will be freed when the cache itself is freed.473/// objects remaining in the cache will be freed when the cache itself is freed.
474const ObjectCache = struct {474const ObjectCache = struct {
475 objects: std.AutoHashMapUnmanaged(u64, CacheEntry) = .empty,475 objects: std.AutoHashMapUnmanaged(u64, CacheEntry) = .empty,
476 lru_nodes: LruList = .{},476 lru_nodes: std.DoublyLinkedList = .{},
477 lru_nodes_len: usize = 0,
477 byte_size: usize = 0,478 byte_size: usize = 0,
478479
479 const max_byte_size = 128 * 1024 * 1024; // 128MiB480 const max_byte_size = 128 * 1024 * 1024; // 128MiB
480 /// A list of offsets stored in the cache, with the most recently used481 /// A list of offsets stored in the cache, with the most recently used
481 /// entries at the end.482 /// entries at the end.
482 const LruList = std.DoublyLinkedList(u64);483 const LruListNode = struct {
483 const CacheEntry = struct { object: Object, lru_node: *LruList.Node };484 data: u64,
485 node: std.DoublyLinkedList.Node,
486 };
487 const CacheEntry = struct { object: Object, lru_node: *LruListNode };
484488
485 fn deinit(cache: *ObjectCache, allocator: Allocator) void {489 fn deinit(cache: *ObjectCache, allocator: Allocator) void {
486 var object_iterator = cache.objects.iterator();490 var object_iterator = cache.objects.iterator();
...@@ -496,8 +500,8 @@ const ObjectCache = struct {...@@ -496,8 +500,8 @@ const ObjectCache = struct {
496 /// position if it is present.500 /// position if it is present.
497 fn get(cache: *ObjectCache, offset: u64) ?Object {501 fn get(cache: *ObjectCache, offset: u64) ?Object {
498 if (cache.objects.get(offset)) |entry| {502 if (cache.objects.get(offset)) |entry| {
499 cache.lru_nodes.remove(entry.lru_node);503 cache.lru_nodes.remove(&entry.lru_node.node);
500 cache.lru_nodes.append(entry.lru_node);504 cache.lru_nodes.append(&entry.lru_node.node);
501 return entry.object;505 return entry.object;
502 } else {506 } else {
503 return null;507 return null;
...@@ -510,26 +514,29 @@ const ObjectCache = struct {...@@ -510,26 +514,29 @@ const ObjectCache = struct {
510 /// will not be evicted before the next call to `put` or `deinit` even if514 /// will not be evicted before the next call to `put` or `deinit` even if
511 /// it exceeds the maximum cache size.515 /// it exceeds the maximum cache size.
512 fn put(cache: *ObjectCache, allocator: Allocator, offset: u64, object: Object) !void {516 fn put(cache: *ObjectCache, allocator: Allocator, offset: u64, object: Object) !void {
513 const lru_node = try allocator.create(LruList.Node);517 const lru_node = try allocator.create(LruListNode);
514 errdefer allocator.destroy(lru_node);518 errdefer allocator.destroy(lru_node);
515 lru_node.data = offset;519 lru_node.data = offset;
516520
517 const gop = try cache.objects.getOrPut(allocator, offset);521 const gop = try cache.objects.getOrPut(allocator, offset);
518 if (gop.found_existing) {522 if (gop.found_existing) {
519 cache.byte_size -= gop.value_ptr.object.data.len;523 cache.byte_size -= gop.value_ptr.object.data.len;
520 cache.lru_nodes.remove(gop.value_ptr.lru_node);524 cache.lru_nodes.remove(&gop.value_ptr.lru_node.node);
525 cache.lru_nodes_len -= 1;
521 allocator.destroy(gop.value_ptr.lru_node);526 allocator.destroy(gop.value_ptr.lru_node);
522 allocator.free(gop.value_ptr.object.data);527 allocator.free(gop.value_ptr.object.data);
523 }528 }
524 gop.value_ptr.* = .{ .object = object, .lru_node = lru_node };529 gop.value_ptr.* = .{ .object = object, .lru_node = lru_node };
525 cache.byte_size += object.data.len;530 cache.byte_size += object.data.len;
526 cache.lru_nodes.append(lru_node);531 cache.lru_nodes.append(&lru_node.node);
532 cache.lru_nodes_len += 1;
527533
528 while (cache.byte_size > max_byte_size and cache.lru_nodes.len > 1) {534 while (cache.byte_size > max_byte_size and cache.lru_nodes_len > 1) {
529 // The > 1 check is to make sure that we don't evict the most535 // The > 1 check is to make sure that we don't evict the most
530 // recently added node, even if it by itself happens to exceed the536 // recently added node, even if it by itself happens to exceed the
531 // maximum size of the cache.537 // maximum size of the cache.
532 const evict_node = cache.lru_nodes.popFirst().?;538 const evict_node: *LruListNode = @alignCast(@fieldParentPtr("node", cache.lru_nodes.popFirst().?));
539 cache.lru_nodes_len -= 1;
533 const evict_offset = evict_node.data;540 const evict_offset = evict_node.data;
534 allocator.destroy(evict_node);541 allocator.destroy(evict_node);
535 const evict_object = cache.objects.get(evict_offset).?.object;542 const evict_object = cache.objects.get(evict_offset).?.object;