authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-09-10 14:28:13-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-09-10 14:28:13-07:00
logbfda12efcf2f6b4bc6803f520108b7ce05636965
tree99133f597a833d32522c42aba84782682f989c9b
parenta50c2a4eae66b772386c62e83f48007e98f9160c
parent6d4dbf05effa3afeb650aeea17683d5de4e6429c
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #24968 from ifreund/deque

std: add a Deque data structure

4 files changed, 455 insertions(+), 191 deletions(-)

lib/std/deque.zig created+433
...@@ -0,0 +1,433 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const Allocator = std.mem.Allocator;
4
5/// A contiguous, growable, double-ended queue.
6///
7/// Pushing/popping items from either end of the queue is O(1).
8pub fn Deque(comptime T: type) type {
9 return struct {
10 const Self = @This();
11
12 /// A ring buffer.
13 buffer: []T,
14 /// The index in buffer where the first item in the logical deque is stored.
15 head: usize,
16 /// The number of items stored in the logical deque.
17 len: usize,
18
19 /// A Deque containing no elements.
20 pub const empty: Self = .{
21 .buffer = &.{},
22 .head = 0,
23 .len = 0,
24 };
25
26 /// Initialize with capacity to hold `capacity` elements.
27 /// The resulting capacity will equal `capacity` exactly.
28 /// Deinitialize with `deinit`.
29 pub fn initCapacity(gpa: Allocator, capacity: usize) Allocator.Error!Self {
30 var deque: Self = .empty;
31 try deque.ensureTotalCapacityPrecise(gpa, capacity);
32 return deque;
33 }
34
35 /// Initialize with externally-managed memory. The buffer determines the
36 /// capacity and the deque is initially empty.
37 ///
38 /// When initialized this way, all functions that accept an Allocator
39 /// argument cause illegal behavior.
40 pub fn initBuffer(buffer: []T) Self {
41 return .{
42 .buffer = buffer,
43 .head = 0,
44 .len = 0,
45 };
46 }
47
48 /// Release all allocated memory.
49 pub fn deinit(deque: *Self, gpa: Allocator) void {
50 gpa.free(deque.buffer);
51 deque.* = undefined;
52 }
53
54 /// Modify the deque so that it can hold at least `new_capacity` items.
55 /// Implements super-linear growth to achieve amortized O(1) push/pop operations.
56 /// Invalidates element pointers if additional memory is needed.
57 pub fn ensureTotalCapacity(deque: *Self, gpa: Allocator, new_capacity: usize) Allocator.Error!void {
58 if (deque.buffer.len >= new_capacity) return;
59 return deque.ensureTotalCapacityPrecise(gpa, growCapacity(deque.buffer.len, new_capacity));
60 }
61
62 /// If the current capacity is less than `new_capacity`, this function will
63 /// modify the deque so that it can hold exactly `new_capacity` items.
64 /// Invalidates element pointers if additional memory is needed.
65 pub fn ensureTotalCapacityPrecise(deque: *Self, gpa: Allocator, new_capacity: usize) Allocator.Error!void {
66 if (deque.buffer.len >= new_capacity) return;
67 const old_buffer = deque.buffer;
68 if (gpa.remap(old_buffer, new_capacity)) |new_buffer| {
69 // If the items wrap around the end of the buffer we need to do
70 // a memcpy to prevent a gap after resizing the buffer.
71 if (deque.head > old_buffer.len - deque.len) {
72 // The gap splits the items in the deque into head and tail parts.
73 // Choose the shorter part to copy.
74 const head = new_buffer[deque.head..old_buffer.len];
75 const tail = new_buffer[0 .. deque.len - head.len];
76 if (head.len > tail.len and new_buffer.len - old_buffer.len > tail.len) {
77 @memcpy(new_buffer[old_buffer.len..][0..tail.len], tail);
78 } else {
79 // In this case overlap is possible if e.g. the capacity increase is 1
80 // and head.len is greater than 1.
81 deque.head = new_buffer.len - head.len;
82 @memmove(new_buffer[deque.head..][0..head.len], head);
83 }
84 }
85 deque.buffer = new_buffer;
86 } else {
87 const new_buffer = try gpa.alloc(T, new_capacity);
88 if (deque.head < old_buffer.len - deque.len) {
89 @memcpy(new_buffer[0..deque.len], old_buffer[deque.head..][0..deque.len]);
90 } else {
91 const head = old_buffer[deque.head..];
92 const tail = old_buffer[0 .. deque.len - head.len];
93 @memcpy(new_buffer[0..head.len], head);
94 @memcpy(new_buffer[head.len..][0..tail.len], tail);
95 }
96 deque.head = 0;
97 deque.buffer = new_buffer;
98 gpa.free(old_buffer);
99 }
100 }
101
102 /// Modify the deque so that it can hold at least `additional_count` **more** items.
103 /// Invalidates element pointers if additional memory is needed.
104 pub fn ensureUnusedCapacity(
105 deque: *Self,
106 gpa: Allocator,
107 additional_count: usize,
108 ) Allocator.Error!void {
109 return deque.ensureTotalCapacity(gpa, try addOrOom(deque.len, additional_count));
110 }
111
112 /// Add one item to the front of the deque.
113 ///
114 /// Invalidates element pointers if additional memory is needed.
115 pub fn pushFront(deque: *Self, gpa: Allocator, item: T) error{OutOfMemory}!void {
116 try deque.ensureUnusedCapacity(gpa, 1);
117 deque.pushFrontAssumeCapacity(item);
118 }
119
120 /// Add one item to the front of the deque.
121 ///
122 /// Never invalidates element pointers.
123 ///
124 /// If the deque lacks unused capacity for the additional item, returns
125 /// `error.OutOfMemory`.
126 pub fn pushFrontBounded(deque: *Self, item: T) error{OutOfMemory}!void {
127 if (deque.buffer.len - deque.len == 0) return error.OutOfMemory;
128 return deque.pushFrontAssumeCapacity(item);
129 }
130
131 /// Add one item to the front of the deque.
132 ///
133 /// Never invalidates element pointers.
134 ///
135 /// Asserts that the deque can hold one additional item.
136 pub fn pushFrontAssumeCapacity(deque: *Self, item: T) void {
137 assert(deque.len < deque.buffer.len);
138 if (deque.head == 0) {
139 deque.head = deque.buffer.len;
140 }
141 deque.head -= 1;
142 deque.buffer[deque.head] = item;
143 deque.len += 1;
144 }
145
146 /// Add one item to the back of the deque.
147 ///
148 /// Invalidates element pointers if additional memory is needed.
149 pub fn pushBack(deque: *Self, gpa: Allocator, item: T) error{OutOfMemory}!void {
150 try deque.ensureUnusedCapacity(gpa, 1);
151 deque.pushBackAssumeCapacity(item);
152 }
153
154 /// Add one item to the back of the deque.
155 ///
156 /// Never invalidates element pointers.
157 ///
158 /// If the deque lacks unused capacity for the additional item, returns
159 /// `error.OutOfMemory`.
160 pub fn pushBackBounded(deque: *Self, item: T) error{OutOfMemory}!void {
161 if (deque.buffer.len - deque.len == 0) return error.OutOfMemory;
162 deque.pushBackAssumeCapacity(item);
163 }
164
165 /// Add one item to the back of the deque.
166 ///
167 /// Never invalidates element pointers.
168 ///
169 /// Asserts that the deque can hold one additional item.
170 pub fn pushBackAssumeCapacity(deque: *Self, item: T) void {
171 assert(deque.len < deque.buffer.len);
172 const buffer_index = deque.bufferIndex(deque.len);
173 deque.buffer[buffer_index] = item;
174 deque.len += 1;
175 }
176
177 /// Return the first item in the deque or null if empty.
178 pub fn front(deque: *const Self) ?T {
179 if (deque.len == 0) return null;
180 return deque.buffer[deque.head];
181 }
182
183 /// Return the last item in the deque or null if empty.
184 pub fn back(deque: *const Self) ?T {
185 if (deque.len == 0) return null;
186 return deque.buffer[deque.bufferIndex(deque.len - 1)];
187 }
188
189 /// Return the item at the given index in the deque.
190 ///
191 /// The first item in the queue is at index 0.
192 ///
193 /// Asserts that the index is in-bounds.
194 pub fn at(deque: *const Self, index: usize) T {
195 assert(index < deque.len);
196 return deque.buffer[deque.bufferIndex(index)];
197 }
198
199 /// Remove and return the first item in the deque or null if empty.
200 pub fn popFront(deque: *Self) ?T {
201 if (deque.len == 0) return null;
202 const pop_index = deque.head;
203 deque.head = deque.bufferIndex(1);
204 deque.len -= 1;
205 return deque.buffer[pop_index];
206 }
207
208 /// Remove and return the last item in the deque or null if empty.
209 pub fn popBack(deque: *Self) ?T {
210 if (deque.len == 0) return null;
211 deque.len -= 1;
212 return deque.buffer[deque.bufferIndex(deque.len)];
213 }
214
215 pub const Iterator = struct {
216 deque: *const Self,
217 index: usize,
218
219 pub fn next(it: *Iterator) ?T {
220 if (it.index < it.deque.len) {
221 defer it.index += 1;
222 return it.deque.at(it.index);
223 } else {
224 return null;
225 }
226 }
227 };
228
229 /// Iterates over all items in the deque in order from front to back.
230 pub fn iterator(deque: *const Self) Iterator {
231 return .{ .deque = deque, .index = 0 };
232 }
233
234 /// Returns the index in `buffer` where the element at the given
235 /// index in the logical deque is stored.
236 fn bufferIndex(deque: *const Self, index: usize) usize {
237 // This function is written in this way to avoid overflow and
238 // expensive division.
239 const head_len = deque.buffer.len - deque.head;
240 if (index < head_len) {
241 return deque.head + index;
242 } else {
243 return index - head_len;
244 }
245 }
246
247 const init_capacity: comptime_int = @max(1, std.atomic.cache_line / @sizeOf(T));
248
249 /// Called when memory growth is necessary. Returns a capacity larger than
250 /// minimum that grows super-linearly.
251 fn growCapacity(current: usize, minimum: usize) usize {
252 var new = current;
253 while (true) {
254 new +|= new / 2 + init_capacity;
255 if (new >= minimum) return new;
256 }
257 }
258 };
259}
260
261/// Integer addition returning `error.OutOfMemory` on overflow.
262fn addOrOom(a: usize, b: usize) error{OutOfMemory}!usize {
263 const result, const overflow = @addWithOverflow(a, b);
264 if (overflow != 0) return error.OutOfMemory;
265 return result;
266}
267
268test "basic" {
269 const testing = std.testing;
270 const gpa = testing.allocator;
271
272 var q: Deque(u32) = .empty;
273 defer q.deinit(gpa);
274
275 try testing.expectEqual(null, q.popFront());
276 try testing.expectEqual(null, q.popBack());
277
278 try q.pushBack(gpa, 1);
279 try q.pushBack(gpa, 2);
280 try q.pushBack(gpa, 3);
281 try q.pushFront(gpa, 0);
282
283 try testing.expectEqual(0, q.popFront());
284 try testing.expectEqual(1, q.popFront());
285 try testing.expectEqual(3, q.popBack());
286 try testing.expectEqual(2, q.popFront());
287 try testing.expectEqual(null, q.popFront());
288 try testing.expectEqual(null, q.popBack());
289}
290
291test "buffer" {
292 const testing = std.testing;
293
294 var buffer: [4]u32 = undefined;
295 var q: Deque(u32) = .initBuffer(&buffer);
296
297 try testing.expectEqual(null, q.popFront());
298 try testing.expectEqual(null, q.popBack());
299
300 try q.pushBackBounded(1);
301 try q.pushBackBounded(2);
302 try q.pushBackBounded(3);
303 try q.pushFrontBounded(0);
304 try testing.expectError(error.OutOfMemory, q.pushBackBounded(4));
305
306 try testing.expectEqual(0, q.popFront());
307 try testing.expectEqual(1, q.popFront());
308 try testing.expectEqual(3, q.popBack());
309 try testing.expectEqual(2, q.popFront());
310 try testing.expectEqual(null, q.popFront());
311 try testing.expectEqual(null, q.popBack());
312}
313
314test "slow growth" {
315 const testing = std.testing;
316 const gpa = testing.allocator;
317
318 var q: Deque(i32) = .empty;
319 defer q.deinit(gpa);
320
321 try q.ensureTotalCapacityPrecise(gpa, 1);
322 q.pushBackAssumeCapacity(1);
323 try q.ensureTotalCapacityPrecise(gpa, 2);
324 q.pushFrontAssumeCapacity(0);
325 try q.ensureTotalCapacityPrecise(gpa, 3);
326 q.pushBackAssumeCapacity(2);
327 try q.ensureTotalCapacityPrecise(gpa, 5);
328 q.pushBackAssumeCapacity(3);
329 q.pushFrontAssumeCapacity(-1);
330 try q.ensureTotalCapacityPrecise(gpa, 6);
331 q.pushFrontAssumeCapacity(-2);
332
333 try testing.expectEqual(-2, q.popFront());
334 try testing.expectEqual(-1, q.popFront());
335 try testing.expectEqual(3, q.popBack());
336 try testing.expectEqual(0, q.popFront());
337 try testing.expectEqual(2, q.popBack());
338 try testing.expectEqual(1, q.popBack());
339 try testing.expectEqual(null, q.popFront());
340 try testing.expectEqual(null, q.popBack());
341}
342
343test "fuzz against ArrayList oracle" {
344 try std.testing.fuzz({}, fuzzAgainstArrayList, .{});
345}
346
347test "dumb fuzz against ArrayList oracle" {
348 const testing = std.testing;
349 const gpa = testing.allocator;
350
351 const input = try gpa.alloc(u8, 1024);
352 defer gpa.free(input);
353
354 var prng = std.Random.DefaultPrng.init(testing.random_seed);
355 prng.random().bytes(input);
356
357 try fuzzAgainstArrayList({}, input);
358}
359
360fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void {
361 const testing = std.testing;
362 const gpa = testing.allocator;
363
364 var q: Deque(u32) = .empty;
365 defer q.deinit(gpa);
366 var l: std.ArrayList(u32) = .empty;
367 defer l.deinit(gpa);
368
369 if (input.len < 2) return;
370
371 var prng = std.Random.DefaultPrng.init(input[0]);
372 const random = prng.random();
373
374 const Action = enum {
375 push_back,
376 push_front,
377 pop_back,
378 pop_front,
379 grow,
380 /// Sentinel to avoid hardcoding the cast below
381 max,
382 };
383 for (input[1..]) |byte| {
384 switch (@as(Action, @enumFromInt(byte % (@intFromEnum(Action.max))))) {
385 .push_back => {
386 const item = random.int(u8);
387 try testing.expectEqual(
388 l.appendBounded(item),
389 q.pushBackBounded(item),
390 );
391 },
392 .push_front => {
393 const item = random.int(u8);
394 try testing.expectEqual(
395 l.insertBounded(0, item),
396 q.pushFrontBounded(item),
397 );
398 },
399 .pop_back => {
400 try testing.expectEqual(l.pop(), q.popBack());
401 },
402 .pop_front => {
403 try testing.expectEqual(
404 if (l.items.len > 0) l.orderedRemove(0) else null,
405 q.popFront(),
406 );
407 },
408 // Growing by small, random, linear amounts seems to better test
409 // ensureTotalCapacityPrecise(), which is the most complex part
410 // of the Deque implementation.
411 .grow => {
412 const growth = random.int(u3);
413 try l.ensureTotalCapacityPrecise(gpa, l.items.len + growth);
414 try q.ensureTotalCapacityPrecise(gpa, q.len + growth);
415 },
416 .max => unreachable,
417 }
418 try testing.expectEqual(l.getLastOrNull(), q.back());
419 try testing.expectEqual(
420 if (l.items.len > 0) l.items[0] else null,
421 q.front(),
422 );
423 try testing.expectEqual(l.items.len, q.len);
424 try testing.expectEqual(l.capacity, q.buffer.len);
425 {
426 var it = q.iterator();
427 for (l.items) |item| {
428 try testing.expectEqual(item, it.next());
429 }
430 try testing.expectEqual(null, it.next());
431 }
432 }
433}
lib/std/std.zig+1
...@@ -10,6 +10,7 @@ pub const BufMap = @import("buf_map.zig").BufMap;...@@ -10,6 +10,7 @@ pub const BufMap = @import("buf_map.zig").BufMap;
10pub const BufSet = @import("buf_set.zig").BufSet;10pub const BufSet = @import("buf_set.zig").BufSet;
11pub const StaticStringMap = static_string_map.StaticStringMap;11pub const StaticStringMap = static_string_map.StaticStringMap;
12pub const StaticStringMapWithEql = static_string_map.StaticStringMapWithEql;12pub const StaticStringMapWithEql = static_string_map.StaticStringMapWithEql;
13pub const Deque = @import("deque.zig").Deque;
13pub const DoublyLinkedList = @import("DoublyLinkedList.zig");14pub const DoublyLinkedList = @import("DoublyLinkedList.zig");
14pub const DynLib = @import("dynamic_library.zig").DynLib;15pub const DynLib = @import("dynamic_library.zig").DynLib;
15pub const DynamicBitSet = bit_set.DynamicBitSet;16pub const DynamicBitSet = bit_set.DynamicBitSet;
src/Compilation.zig+21-22
...@@ -45,8 +45,6 @@ const Builtin = @import("Builtin.zig");...@@ -45,8 +45,6 @@ const Builtin = @import("Builtin.zig");
45const LlvmObject = @import("codegen/llvm.zig").Object;45const LlvmObject = @import("codegen/llvm.zig").Object;
46const dev = @import("dev.zig");46const dev = @import("dev.zig");
4747
48const DeprecatedLinearFifo = @import("deprecated.zig").LinearFifo;
49
50pub const Config = @import("Compilation/Config.zig");48pub const Config = @import("Compilation/Config.zig");
5149
52/// General-purpose allocator. Used for both temporary and long-term storage.50/// General-purpose allocator. Used for both temporary and long-term storage.
...@@ -124,20 +122,21 @@ work_queues: [...@@ -124,20 +122,21 @@ work_queues: [
124 }122 }
125 break :len len;123 break :len len;
126 }124 }
127]DeprecatedLinearFifo(Job),125]std.Deque(Job),
128126
129/// These jobs are to invoke the Clang compiler to create an object file, which127/// These jobs are to invoke the Clang compiler to create an object file, which
130/// gets linked with the Compilation.128/// gets linked with the Compilation.
131c_object_work_queue: DeprecatedLinearFifo(*CObject),129c_object_work_queue: std.Deque(*CObject),
132130
133/// These jobs are to invoke the RC compiler to create a compiled resource file (.res), which131/// These jobs are to invoke the RC compiler to create a compiled resource file (.res), which
134/// gets linked with the Compilation.132/// gets linked with the Compilation.
135win32_resource_work_queue: if (dev.env.supports(.win32_resource)) DeprecatedLinearFifo(*Win32Resource) else struct {133win32_resource_work_queue: if (dev.env.supports(.win32_resource)) std.Deque(*Win32Resource) else struct {
136 pub fn ensureUnusedCapacity(_: @This(), _: u0) error{}!void {}134 pub const empty: @This() = .{};
137 pub fn readItem(_: @This()) ?noreturn {135 pub fn ensureUnusedCapacity(_: @This(), _: Allocator, _: u0) error{}!void {}
136 pub fn popFront(_: @This()) ?noreturn {
138 return null;137 return null;
139 }138 }
140 pub fn deinit(_: @This()) void {}139 pub fn deinit(_: @This(), _: Allocator) void {}
141},140},
142141
143/// The ErrorMsg memory is owned by the `CObject`, using Compilation's general purpose allocator.142/// The ErrorMsg memory is owned by the `CObject`, using Compilation's general purpose allocator.
...@@ -2236,9 +2235,9 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options...@@ -2236,9 +2235,9 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options
2236 .root_mod = options.root_mod,2235 .root_mod = options.root_mod,
2237 .config = options.config,2236 .config = options.config,
2238 .dirs = options.dirs,2237 .dirs = options.dirs,
2239 .work_queues = @splat(.init(gpa)),2238 .work_queues = @splat(.empty),
2240 .c_object_work_queue = .init(gpa),2239 .c_object_work_queue = .empty,
2241 .win32_resource_work_queue = if (dev.env.supports(.win32_resource)) .init(gpa) else .{},2240 .win32_resource_work_queue = .empty,
2242 .c_source_files = options.c_source_files,2241 .c_source_files = options.c_source_files,
2243 .rc_source_files = options.rc_source_files,2242 .rc_source_files = options.rc_source_files,
2244 .cache_parent = cache,2243 .cache_parent = cache,
...@@ -2702,9 +2701,9 @@ pub fn destroy(comp: *Compilation) void {...@@ -2702,9 +2701,9 @@ pub fn destroy(comp: *Compilation) void {
2702 if (comp.zcu) |zcu| zcu.deinit();2701 if (comp.zcu) |zcu| zcu.deinit();
2703 comp.cache_use.deinit();2702 comp.cache_use.deinit();
27042703
2705 for (&comp.work_queues) |*work_queue| work_queue.deinit();2704 for (&comp.work_queues) |*work_queue| work_queue.deinit(gpa);
2706 comp.c_object_work_queue.deinit();2705 comp.c_object_work_queue.deinit(gpa);
2707 comp.win32_resource_work_queue.deinit();2706 comp.win32_resource_work_queue.deinit(gpa);
27082707
2709 for (comp.windows_libs.keys()) |windows_lib| gpa.free(windows_lib);2708 for (comp.windows_libs.keys()) |windows_lib| gpa.free(windows_lib);
2710 comp.windows_libs.deinit(gpa);2709 comp.windows_libs.deinit(gpa);
...@@ -3019,17 +3018,17 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE...@@ -3019,17 +3018,17 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
30193018
3020 // For compiling C objects, we rely on the cache hash system to avoid duplicating work.3019 // For compiling C objects, we rely on the cache hash system to avoid duplicating work.
3021 // Add a Job for each C object.3020 // Add a Job for each C object.
3022 try comp.c_object_work_queue.ensureUnusedCapacity(comp.c_object_table.count());3021 try comp.c_object_work_queue.ensureUnusedCapacity(gpa, comp.c_object_table.count());
3023 for (comp.c_object_table.keys()) |c_object| {3022 for (comp.c_object_table.keys()) |c_object| {
3024 comp.c_object_work_queue.writeItemAssumeCapacity(c_object);3023 comp.c_object_work_queue.pushBackAssumeCapacity(c_object);
3025 try comp.appendFileSystemInput(try .fromUnresolved(arena, comp.dirs, &.{c_object.src.src_path}));3024 try comp.appendFileSystemInput(try .fromUnresolved(arena, comp.dirs, &.{c_object.src.src_path}));
3026 }3025 }
30273026
3028 // For compiling Win32 resources, we rely on the cache hash system to avoid duplicating work.3027 // For compiling Win32 resources, we rely on the cache hash system to avoid duplicating work.
3029 // Add a Job for each Win32 resource file.3028 // Add a Job for each Win32 resource file.
3030 try comp.win32_resource_work_queue.ensureUnusedCapacity(comp.win32_resource_table.count());3029 try comp.win32_resource_work_queue.ensureUnusedCapacity(gpa, comp.win32_resource_table.count());
3031 for (comp.win32_resource_table.keys()) |win32_resource| {3030 for (comp.win32_resource_table.keys()) |win32_resource| {
3032 comp.win32_resource_work_queue.writeItemAssumeCapacity(win32_resource);3031 comp.win32_resource_work_queue.pushBackAssumeCapacity(win32_resource);
3033 switch (win32_resource.src) {3032 switch (win32_resource.src) {
3034 .rc => |f| {3033 .rc => |f| {
3035 try comp.appendFileSystemInput(try .fromUnresolved(arena, comp.dirs, &.{f.src_path}));3034 try comp.appendFileSystemInput(try .fromUnresolved(arena, comp.dirs, &.{f.src_path}));
...@@ -4871,14 +4870,14 @@ fn performAllTheWork(...@@ -4871,14 +4870,14 @@ fn performAllTheWork(
4871 }4870 }
4872 }4871 }
48734872
4874 while (comp.c_object_work_queue.readItem()) |c_object| {4873 while (comp.c_object_work_queue.popFront()) |c_object| {
4875 comp.link_task_queue.startPrelinkItem();4874 comp.link_task_queue.startPrelinkItem();
4876 comp.thread_pool.spawnWg(&comp.link_task_wait_group, workerUpdateCObject, .{4875 comp.thread_pool.spawnWg(&comp.link_task_wait_group, workerUpdateCObject, .{
4877 comp, c_object, main_progress_node,4876 comp, c_object, main_progress_node,
4878 });4877 });
4879 }4878 }
48804879
4881 while (comp.win32_resource_work_queue.readItem()) |win32_resource| {4880 while (comp.win32_resource_work_queue.popFront()) |win32_resource| {
4882 comp.link_task_queue.startPrelinkItem();4881 comp.link_task_queue.startPrelinkItem();
4883 comp.thread_pool.spawnWg(&comp.link_task_wait_group, workerUpdateWin32Resource, .{4882 comp.thread_pool.spawnWg(&comp.link_task_wait_group, workerUpdateWin32Resource, .{
4884 comp, win32_resource, main_progress_node,4883 comp, win32_resource, main_progress_node,
...@@ -4998,7 +4997,7 @@ fn performAllTheWork(...@@ -4998,7 +4997,7 @@ fn performAllTheWork(
4998 }4997 }
49994998
5000 work: while (true) {4999 work: while (true) {
5001 for (&comp.work_queues) |*work_queue| if (work_queue.readItem()) |job| {5000 for (&comp.work_queues) |*work_queue| if (work_queue.popFront()) |job| {
5002 try processOneJob(@intFromEnum(Zcu.PerThread.Id.main), comp, job);5001 try processOneJob(@intFromEnum(Zcu.PerThread.Id.main), comp, job);
5003 continue :work;5002 continue :work;
5004 };5003 };
...@@ -5027,7 +5026,7 @@ fn performAllTheWork(...@@ -5027,7 +5026,7 @@ fn performAllTheWork(
5027const JobError = Allocator.Error;5026const JobError = Allocator.Error;
50285027
5029pub fn queueJob(comp: *Compilation, job: Job) !void {5028pub fn queueJob(comp: *Compilation, job: Job) !void {
5030 try comp.work_queues[Job.stage(job)].writeItem(job);5029 try comp.work_queues[Job.stage(job)].pushBack(comp.gpa, job);
5031}5030}
50325031
5033pub fn queueJobs(comp: *Compilation, jobs: []const Job) !void {5032pub fn queueJobs(comp: *Compilation, jobs: []const Job) !void {
src/deprecated.zig deleted-169
...@@ -1,169 +0,0 @@
1//! Deprecated. Stop using this API
2
3const std = @import("std");
4const math = std.math;
5const mem = std.mem;
6const Allocator = mem.Allocator;
7const assert = std.debug.assert;
8const testing = std.testing;
9
10pub fn LinearFifo(comptime T: type) type {
11 return struct {
12 allocator: Allocator,
13 buf: []T,
14 head: usize,
15 count: usize,
16
17 const Self = @This();
18
19 pub fn init(allocator: Allocator) Self {
20 return .{
21 .allocator = allocator,
22 .buf = &.{},
23 .head = 0,
24 .count = 0,
25 };
26 }
27
28 pub fn deinit(self: *Self) void {
29 self.allocator.free(self.buf);
30 self.* = undefined;
31 }
32
33 pub fn realign(self: *Self) void {
34 if (self.buf.len - self.head >= self.count) {
35 mem.copyForwards(T, self.buf[0..self.count], self.buf[self.head..][0..self.count]);
36 self.head = 0;
37 } else {
38 var tmp: [4096 / 2 / @sizeOf(T)]T = undefined;
39
40 while (self.head != 0) {
41 const n = @min(self.head, tmp.len);
42 const m = self.buf.len - n;
43 @memcpy(tmp[0..n], self.buf[0..n]);
44 mem.copyForwards(T, self.buf[0..m], self.buf[n..][0..m]);
45 @memcpy(self.buf[m..][0..n], tmp[0..n]);
46 self.head -= n;
47 }
48 }
49 { // set unused area to undefined
50 const unused = mem.sliceAsBytes(self.buf[self.count..]);
51 @memset(unused, undefined);
52 }
53 }
54
55 /// Ensure that the buffer can fit at least `size` items
56 pub fn ensureTotalCapacity(self: *Self, size: usize) !void {
57 if (self.buf.len >= size) return;
58 self.realign();
59 const new_size = math.ceilPowerOfTwo(usize, size) catch return error.OutOfMemory;
60 self.buf = try self.allocator.realloc(self.buf, new_size);
61 }
62
63 /// Makes sure at least `size` items are unused
64 pub fn ensureUnusedCapacity(self: *Self, size: usize) error{OutOfMemory}!void {
65 if (self.writableLength() >= size) return;
66
67 return try self.ensureTotalCapacity(math.add(usize, self.count, size) catch return error.OutOfMemory);
68 }
69
70 /// Returns a writable slice from the 'read' end of the fifo
71 fn readableSliceMut(self: Self, offset: usize) []T {
72 if (offset > self.count) return &[_]T{};
73
74 var start = self.head + offset;
75 if (start >= self.buf.len) {
76 start -= self.buf.len;
77 return self.buf[start .. start + (self.count - offset)];
78 } else {
79 const end = @min(self.head + self.count, self.buf.len);
80 return self.buf[start..end];
81 }
82 }
83
84 /// Discard first `count` items in the fifo
85 pub fn discard(self: *Self, count: usize) void {
86 assert(count <= self.count);
87 { // set old range to undefined. Note: may be wrapped around
88 const slice = self.readableSliceMut(0);
89 if (slice.len >= count) {
90 const unused = mem.sliceAsBytes(slice[0..count]);
91 @memset(unused, undefined);
92 } else {
93 const unused = mem.sliceAsBytes(slice[0..]);
94 @memset(unused, undefined);
95 const unused2 = mem.sliceAsBytes(self.readableSliceMut(slice.len)[0 .. count - slice.len]);
96 @memset(unused2, undefined);
97 }
98 }
99 var head = self.head + count;
100 // Note it is safe to do a wrapping subtract as
101 // bitwise & with all 1s is a noop
102 head &= self.buf.len -% 1;
103 self.head = head;
104 self.count -= count;
105 }
106
107 /// Read the next item from the fifo
108 pub fn readItem(self: *Self) ?T {
109 if (self.count == 0) return null;
110
111 const c = self.buf[self.head];
112 self.discard(1);
113 return c;
114 }
115
116 /// Returns number of items available in fifo
117 pub fn writableLength(self: Self) usize {
118 return self.buf.len - self.count;
119 }
120
121 /// Returns the first section of writable buffer.
122 /// Note that this may be of length 0
123 pub fn writableSlice(self: Self, offset: usize) []T {
124 if (offset > self.buf.len) return &[_]T{};
125
126 const tail = self.head + offset + self.count;
127 if (tail < self.buf.len) {
128 return self.buf[tail..];
129 } else {
130 return self.buf[tail - self.buf.len ..][0 .. self.writableLength() - offset];
131 }
132 }
133
134 /// Update the tail location of the buffer (usually follows use of writable/writableWithSize)
135 pub fn update(self: *Self, count: usize) void {
136 assert(self.count + count <= self.buf.len);
137 self.count += count;
138 }
139
140 /// Appends the data in `src` to the fifo.
141 /// You must have ensured there is enough space.
142 pub fn writeAssumeCapacity(self: *Self, src: []const T) void {
143 assert(self.writableLength() >= src.len);
144
145 var src_left = src;
146 while (src_left.len > 0) {
147 const writable_slice = self.writableSlice(0);
148 assert(writable_slice.len != 0);
149 const n = @min(writable_slice.len, src_left.len);
150 @memcpy(writable_slice[0..n], src_left[0..n]);
151 self.update(n);
152 src_left = src_left[n..];
153 }
154 }
155
156 /// Write a single item to the fifo
157 pub fn writeItem(self: *Self, item: T) !void {
158 try self.ensureUnusedCapacity(1);
159 return self.writeItemAssumeCapacity(item);
160 }
161
162 pub fn writeItemAssumeCapacity(self: *Self, item: T) void {
163 var tail = self.head + self.count;
164 tail &= self.buf.len - 1;
165 self.buf[tail] = item;
166 self.update(1);
167 }
168 };
169}