authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-05-28 14:01:01-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:29-07:00
log21df0010012b9a428f678a11586a50f24e54cf6f
tree7324b6cbf6f37ababd26392621f3ed70eb7ebb75
parent3650cd3e8efd730fb6305bdefbbf8053c352bb0c

std: remove fifo

bad API

7 files changed, 595 insertions(+), 596 deletions(-)

CMakeLists.txt-1
......@@ -434,7 +434,6 @@ set(ZIG_STAGE2_SOURCES
434434 lib/std/dwarf/OP.zig
435435 lib/std/dwarf/TAG.zig
436436 lib/std/elf.zig
437 lib/std/fifo.zig
438437 lib/std/fmt.zig
439438 lib/std/fmt/parse_float.zig
440439 lib/std/fs.zig
lib/compiler/resinator/compile.zig+1-3
......@@ -1269,9 +1269,7 @@ pub const Compiler = struct {
12691269 pub fn writeResourceDataNoPadding(writer: anytype, data_reader: anytype, data_size: u32) !void {
12701270 var limited_reader = std.io.limitedReader(data_reader, data_size);
12711271
1272 const FifoBuffer = std.fifo.LinearFifo(u8, .{ .Static = 4096 });
1273 var fifo = FifoBuffer.init();
1274 try fifo.pump(limited_reader.reader(), writer);
1272 try limited_reader.reader().readRemaining(writer);
12751273 }
12761274
12771275 pub fn writeResourceData(writer: anytype, data_reader: anytype, data_size: u32) !void {
lib/std/fifo.zig deleted-587
......@@ -1,587 +0,0 @@
1// FIFO of fixed size items
2// Usually used for e.g. byte buffers
3
4const std = @import("std");
5const math = std.math;
6const mem = std.mem;
7const Allocator = mem.Allocator;
8const assert = std.debug.assert;
9const testing = std.testing;
10
11pub const LinearFifoBufferType = union(enum) {
12 /// The buffer is internal to the fifo; it is of the specified size.
13 Static: usize,
14
15 /// The buffer is passed as a slice to the initialiser.
16 Slice,
17
18 /// The buffer is managed dynamically using a `mem.Allocator`.
19 Dynamic,
20};
21
22pub fn LinearFifo(
23 comptime T: type,
24 comptime buffer_type: LinearFifoBufferType,
25) type {
26 const autoalign = false;
27
28 const powers_of_two = switch (buffer_type) {
29 .Static => std.math.isPowerOfTwo(buffer_type.Static),
30 .Slice => false, // Any size slice could be passed in
31 .Dynamic => true, // This could be configurable in future
32 };
33
34 return struct {
35 allocator: if (buffer_type == .Dynamic) Allocator else void,
36 buf: if (buffer_type == .Static) [buffer_type.Static]T else []T,
37 head: usize,
38 count: usize,
39
40 const Self = @This();
41
42 // Type of Self argument for slice operations.
43 // If buffer is inline (Static) then we need to ensure we haven't
44 // returned a slice into a copy on the stack
45 const SliceSelfArg = if (buffer_type == .Static) *Self else Self;
46
47 pub const init = switch (buffer_type) {
48 .Static => initStatic,
49 .Slice => initSlice,
50 .Dynamic => initDynamic,
51 };
52
53 fn initStatic() Self {
54 comptime assert(buffer_type == .Static);
55 return .{
56 .allocator = {},
57 .buf = undefined,
58 .head = 0,
59 .count = 0,
60 };
61 }
62
63 fn initSlice(buf: []T) Self {
64 comptime assert(buffer_type == .Slice);
65 return .{
66 .allocator = {},
67 .buf = buf,
68 .head = 0,
69 .count = 0,
70 };
71 }
72
73 fn initDynamic(allocator: Allocator) Self {
74 comptime assert(buffer_type == .Dynamic);
75 return .{
76 .allocator = allocator,
77 .buf = &.{},
78 .head = 0,
79 .count = 0,
80 };
81 }
82
83 pub fn deinit(self: Self) void {
84 if (buffer_type == .Dynamic) self.allocator.free(self.buf);
85 }
86
87 pub fn realign(self: *Self) void {
88 if (self.buf.len - self.head >= self.count) {
89 mem.copyForwards(T, self.buf[0..self.count], self.buf[self.head..][0..self.count]);
90 self.head = 0;
91 } else {
92 var tmp: [4096 / 2 / @sizeOf(T)]T = undefined;
93
94 while (self.head != 0) {
95 const n = @min(self.head, tmp.len);
96 const m = self.buf.len - n;
97 @memcpy(tmp[0..n], self.buf[0..n]);
98 mem.copyForwards(T, self.buf[0..m], self.buf[n..][0..m]);
99 @memcpy(self.buf[m..][0..n], tmp[0..n]);
100 self.head -= n;
101 }
102 }
103 { // set unused area to undefined
104 const unused = mem.sliceAsBytes(self.buf[self.count..]);
105 @memset(unused, undefined);
106 }
107 }
108
109 /// Reduce allocated capacity to `size`.
110 pub fn shrink(self: *Self, size: usize) void {
111 assert(size >= self.count);
112 if (buffer_type == .Dynamic) {
113 self.realign();
114 self.buf = self.allocator.realloc(self.buf, size) catch |e| switch (e) {
115 error.OutOfMemory => return, // no problem, capacity is still correct then.
116 };
117 }
118 }
119
120 /// Ensure that the buffer can fit at least `size` items
121 pub fn ensureTotalCapacity(self: *Self, size: usize) !void {
122 if (self.buf.len >= size) return;
123 if (buffer_type == .Dynamic) {
124 self.realign();
125 const new_size = if (powers_of_two) math.ceilPowerOfTwo(usize, size) catch return error.OutOfMemory else size;
126 self.buf = try self.allocator.realloc(self.buf, new_size);
127 } else {
128 return error.OutOfMemory;
129 }
130 }
131
132 /// Makes sure at least `size` items are unused
133 pub fn ensureUnusedCapacity(self: *Self, size: usize) error{OutOfMemory}!void {
134 if (self.writableLength() >= size) return;
135
136 return try self.ensureTotalCapacity(math.add(usize, self.count, size) catch return error.OutOfMemory);
137 }
138
139 /// Returns number of items currently in fifo
140 pub fn readableLength(self: Self) usize {
141 return self.count;
142 }
143
144 /// Returns a writable slice from the 'read' end of the fifo
145 fn readableSliceMut(self: SliceSelfArg, offset: usize) []T {
146 if (offset > self.count) return &[_]T{};
147
148 var start = self.head + offset;
149 if (start >= self.buf.len) {
150 start -= self.buf.len;
151 return self.buf[start .. start + (self.count - offset)];
152 } else {
153 const end = @min(self.head + self.count, self.buf.len);
154 return self.buf[start..end];
155 }
156 }
157
158 /// Returns a readable slice from `offset`
159 pub fn readableSlice(self: SliceSelfArg, offset: usize) []const T {
160 return self.readableSliceMut(offset);
161 }
162
163 pub fn readableSliceOfLen(self: *Self, len: usize) []const T {
164 assert(len <= self.count);
165 const buf = self.readableSlice(0);
166 if (buf.len >= len) {
167 return buf[0..len];
168 } else {
169 self.realign();
170 return self.readableSlice(0)[0..len];
171 }
172 }
173
174 /// Discard first `count` items in the fifo
175 pub fn discard(self: *Self, count: usize) void {
176 assert(count <= self.count);
177 { // set old range to undefined. Note: may be wrapped around
178 const slice = self.readableSliceMut(0);
179 if (slice.len >= count) {
180 const unused = mem.sliceAsBytes(slice[0..count]);
181 @memset(unused, undefined);
182 } else {
183 const unused = mem.sliceAsBytes(slice[0..]);
184 @memset(unused, undefined);
185 const unused2 = mem.sliceAsBytes(self.readableSliceMut(slice.len)[0 .. count - slice.len]);
186 @memset(unused2, undefined);
187 }
188 }
189 if (autoalign and self.count == count) {
190 self.head = 0;
191 self.count = 0;
192 } else {
193 var head = self.head + count;
194 if (powers_of_two) {
195 // Note it is safe to do a wrapping subtract as
196 // bitwise & with all 1s is a noop
197 head &= self.buf.len -% 1;
198 } else {
199 head %= self.buf.len;
200 }
201 self.head = head;
202 self.count -= count;
203 }
204 }
205
206 /// Read the next item from the fifo
207 pub fn readItem(self: *Self) ?T {
208 if (self.count == 0) return null;
209
210 const c = self.buf[self.head];
211 self.discard(1);
212 return c;
213 }
214
215 /// Read data from the fifo into `dst`, returns number of items copied.
216 pub fn read(self: *Self, dst: []T) usize {
217 var dst_left = dst;
218
219 while (dst_left.len > 0) {
220 const slice = self.readableSlice(0);
221 if (slice.len == 0) break;
222 const n = @min(slice.len, dst_left.len);
223 @memcpy(dst_left[0..n], slice[0..n]);
224 self.discard(n);
225 dst_left = dst_left[n..];
226 }
227
228 return dst.len - dst_left.len;
229 }
230
231 /// Same as `read` except it returns an error union
232 /// The purpose of this function existing is to match `std.io.Reader` API.
233 fn readFn(self: *Self, dest: []u8) error{}!usize {
234 return self.read(dest);
235 }
236
237 pub fn reader(self: *Self) std.io.Reader {
238 return .{
239 .context = self,
240 .vtable = &.{
241 .read = &readerRead,
242 .readVec = &readerReadVec,
243 .discard = &readerDiscard,
244 },
245 };
246 }
247 fn readerRead(
248 ctx: ?*anyopaque,
249 bw: *std.io.BufferedWriter,
250 limit: std.io.Limit,
251 ) std.io.Reader.RwError!usize {
252 const fifo: *Self = @alignCast(@ptrCast(ctx));
253 _ = fifo;
254 _ = bw;
255 _ = limit;
256 @panic("TODO");
257 }
258 fn readerReadVec(ctx: ?*anyopaque, data: []const []u8) std.io.Reader.Error!usize {
259 const fifo: *Self = @alignCast(@ptrCast(ctx));
260 _ = fifo;
261 _ = data;
262 @panic("TODO");
263 }
264 fn readerDiscard(ctx: ?*anyopaque, limit: std.io.Limit) std.io.Reader.Error!usize {
265 const fifo: *Self = @alignCast(@ptrCast(ctx));
266 _ = fifo;
267 _ = limit;
268 @panic("TODO");
269 }
270
271 /// Returns number of items available in fifo
272 pub fn writableLength(self: Self) usize {
273 return self.buf.len - self.count;
274 }
275
276 /// Returns the first section of writable buffer.
277 /// Note that this may be of length 0
278 pub fn writableSlice(self: SliceSelfArg, offset: usize) []T {
279 if (offset > self.buf.len) return &[_]T{};
280
281 const tail = self.head + offset + self.count;
282 if (tail < self.buf.len) {
283 return self.buf[tail..];
284 } else {
285 return self.buf[tail - self.buf.len ..][0 .. self.writableLength() - offset];
286 }
287 }
288
289 /// Returns a writable buffer of at least `size` items, allocating memory as needed.
290 /// Use `fifo.update` once you've written data to it.
291 pub fn writableWithSize(self: *Self, size: usize) ![]T {
292 try self.ensureUnusedCapacity(size);
293
294 // try to avoid realigning buffer
295 var slice = self.writableSlice(0);
296 if (slice.len < size) {
297 self.realign();
298 slice = self.writableSlice(0);
299 }
300 return slice;
301 }
302
303 /// Update the tail location of the buffer (usually follows use of writable/writableWithSize)
304 pub fn update(self: *Self, count: usize) void {
305 assert(self.count + count <= self.buf.len);
306 self.count += count;
307 }
308
309 /// Appends the data in `src` to the fifo.
310 /// You must have ensured there is enough space.
311 pub fn writeAssumeCapacity(self: *Self, src: []const T) void {
312 assert(self.writableLength() >= src.len);
313
314 var src_left = src;
315 while (src_left.len > 0) {
316 const writable_slice = self.writableSlice(0);
317 assert(writable_slice.len != 0);
318 const n = @min(writable_slice.len, src_left.len);
319 @memcpy(writable_slice[0..n], src_left[0..n]);
320 self.update(n);
321 src_left = src_left[n..];
322 }
323 }
324
325 /// Write a single item to the fifo
326 pub fn writeItem(self: *Self, item: T) !void {
327 try self.ensureUnusedCapacity(1);
328 return self.writeItemAssumeCapacity(item);
329 }
330
331 pub fn writeItemAssumeCapacity(self: *Self, item: T) void {
332 var tail = self.head + self.count;
333 if (powers_of_two) {
334 tail &= self.buf.len - 1;
335 } else {
336 tail %= self.buf.len;
337 }
338 self.buf[tail] = item;
339 self.update(1);
340 }
341
342 /// Appends the data in `src` to the fifo.
343 /// Allocates more memory as necessary
344 pub fn write(self: *Self, src: []const T) !void {
345 try self.ensureUnusedCapacity(src.len);
346
347 return self.writeAssumeCapacity(src);
348 }
349
350 /// Same as `write` except it returns the number of bytes written, which is always the same
351 /// as `bytes.len`. The purpose of this function existing is to match `std.io.Writer` API.
352 fn appendWrite(self: *Self, bytes: []const u8) error{OutOfMemory}!usize {
353 try self.write(bytes);
354 return bytes.len;
355 }
356
357 pub fn writer(fifo: *Self) std.io.Writer {
358 return .{
359 .context = fifo,
360 .vtable = &.{
361 .writeSplat = writerWriteSplat,
362 .writeFile = writerWriteFile,
363 },
364 };
365 }
366 fn writerWriteSplat(ctx: ?*anyopaque, data: []const []const u8, splat: usize) std.io.Writer.Error!usize {
367 const fifo: *Self = @alignCast(@ptrCast(ctx));
368 _ = fifo;
369 _ = data;
370 _ = splat;
371 @panic("TODO");
372 }
373 fn writerWriteFile(
374 ctx: ?*anyopaque,
375 file: std.fs.File,
376 offset: std.io.Writer.Offset,
377 limit: std.io.Writer.Limit,
378 headers_and_trailers: []const []const u8,
379 headers_len: usize,
380 ) std.io.Writer.Error!usize {
381 const fifo: *Self = @alignCast(@ptrCast(ctx));
382 _ = fifo;
383 _ = file;
384 _ = offset;
385 _ = limit;
386 _ = headers_and_trailers;
387 _ = headers_len;
388 @panic("TODO");
389 }
390
391 /// Make `count` items available before the current read location
392 fn rewind(self: *Self, count: usize) void {
393 assert(self.writableLength() >= count);
394
395 var head = self.head + (self.buf.len - count);
396 if (powers_of_two) {
397 head &= self.buf.len - 1;
398 } else {
399 head %= self.buf.len;
400 }
401 self.head = head;
402 self.count += count;
403 }
404
405 /// Place data back into the read stream
406 pub fn unget(self: *Self, src: []const T) !void {
407 try self.ensureUnusedCapacity(src.len);
408
409 self.rewind(src.len);
410
411 const slice = self.readableSliceMut(0);
412 if (src.len < slice.len) {
413 @memcpy(slice[0..src.len], src);
414 } else {
415 @memcpy(slice, src[0..slice.len]);
416 const slice2 = self.readableSliceMut(slice.len);
417 @memcpy(slice2[0 .. src.len - slice.len], src[slice.len..]);
418 }
419 }
420
421 /// Returns the item at `offset`.
422 /// Asserts offset is within bounds.
423 pub fn peekItem(self: Self, offset: usize) T {
424 assert(offset < self.count);
425
426 var index = self.head + offset;
427 if (powers_of_two) {
428 index &= self.buf.len - 1;
429 } else {
430 index %= self.buf.len;
431 }
432 return self.buf[index];
433 }
434
435 pub fn toOwnedSlice(self: *Self) Allocator.Error![]T {
436 if (self.head != 0) self.realign();
437 assert(self.head == 0);
438 assert(self.count <= self.buf.len);
439 const allocator = self.allocator;
440 if (allocator.resize(self.buf, self.count)) {
441 const result = self.buf[0..self.count];
442 self.* = Self.init(allocator);
443 return result;
444 }
445 const new_memory = try allocator.dupe(T, self.buf[0..self.count]);
446 allocator.free(self.buf);
447 self.* = Self.init(allocator);
448 return new_memory;
449 }
450 };
451}
452
453test "LinearFifo(u8, .Dynamic) discard(0) from empty buffer should not error on overflow" {
454 var fifo = LinearFifo(u8, .Dynamic).init(testing.allocator);
455 defer fifo.deinit();
456
457 // If overflow is not explicitly allowed this will crash in debug / safe mode
458 fifo.discard(0);
459}
460
461test "LinearFifo(u8, .Dynamic)" {
462 var fifo = LinearFifo(u8, .Dynamic).init(testing.allocator);
463 defer fifo.deinit();
464
465 try fifo.write("HELLO");
466 try testing.expectEqual(@as(usize, 5), fifo.readableLength());
467 try testing.expectEqualSlices(u8, "HELLO", fifo.readableSlice(0));
468
469 {
470 var i: usize = 0;
471 while (i < 5) : (i += 1) {
472 try fifo.write(&[_]u8{fifo.peekItem(i)});
473 }
474 try testing.expectEqual(@as(usize, 10), fifo.readableLength());
475 try testing.expectEqualSlices(u8, "HELLOHELLO", fifo.readableSlice(0));
476 }
477
478 {
479 try testing.expectEqual(@as(u8, 'H'), fifo.readItem().?);
480 try testing.expectEqual(@as(u8, 'E'), fifo.readItem().?);
481 try testing.expectEqual(@as(u8, 'L'), fifo.readItem().?);
482 try testing.expectEqual(@as(u8, 'L'), fifo.readItem().?);
483 try testing.expectEqual(@as(u8, 'O'), fifo.readItem().?);
484 }
485 try testing.expectEqual(@as(usize, 5), fifo.readableLength());
486
487 { // Writes that wrap around
488 try testing.expectEqual(@as(usize, 11), fifo.writableLength());
489 try testing.expectEqual(@as(usize, 6), fifo.writableSlice(0).len);
490 fifo.writeAssumeCapacity("6<chars<11");
491 try testing.expectEqualSlices(u8, "HELLO6<char", fifo.readableSlice(0));
492 try testing.expectEqualSlices(u8, "s<11", fifo.readableSlice(11));
493 try testing.expectEqualSlices(u8, "11", fifo.readableSlice(13));
494 try testing.expectEqualSlices(u8, "", fifo.readableSlice(15));
495 fifo.discard(11);
496 try testing.expectEqualSlices(u8, "s<11", fifo.readableSlice(0));
497 fifo.discard(4);
498 try testing.expectEqual(@as(usize, 0), fifo.readableLength());
499 }
500
501 {
502 const buf = try fifo.writableWithSize(12);
503 try testing.expectEqual(@as(usize, 12), buf.len);
504 var i: u8 = 0;
505 while (i < 10) : (i += 1) {
506 buf[i] = i + 'a';
507 }
508 fifo.update(10);
509 try testing.expectEqualSlices(u8, "abcdefghij", fifo.readableSlice(0));
510 }
511
512 {
513 try fifo.unget("prependedstring");
514 var result: [30]u8 = undefined;
515 try testing.expectEqualSlices(u8, "prependedstringabcdefghij", result[0..fifo.read(&result)]);
516 try fifo.unget("b");
517 try fifo.unget("a");
518 try testing.expectEqualSlices(u8, "ab", result[0..fifo.read(&result)]);
519 }
520
521 fifo.shrink(0);
522
523 {
524 try fifo.writer().print("{s}, {s}!", .{ "Hello", "World" });
525 var result: [30]u8 = undefined;
526 try testing.expectEqualSlices(u8, "Hello, World!", result[0..fifo.read(&result)]);
527 try testing.expectEqual(@as(usize, 0), fifo.readableLength());
528 }
529
530 {
531 try fifo.writer().writeAll("This is a test");
532 var result: [30]u8 = undefined;
533 try testing.expectEqualSlices(u8, "This", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?);
534 try testing.expectEqualSlices(u8, "is", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?);
535 try testing.expectEqualSlices(u8, "a", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?);
536 try testing.expectEqualSlices(u8, "test", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?);
537 }
538
539 {
540 try fifo.ensureTotalCapacity(1);
541 var in_fbs = std.io.fixedBufferStream("pump test");
542 var out_buf: [50]u8 = undefined;
543 var out_fbs = std.io.fixedBufferStream(&out_buf);
544 try fifo.pump(in_fbs.reader(), out_fbs.writer());
545 try testing.expectEqualSlices(u8, in_fbs.buffer, out_fbs.getWritten());
546 }
547}
548
549test LinearFifo {
550 inline for ([_]type{ u1, u8, u16, u64 }) |T| {
551 inline for ([_]LinearFifoBufferType{ LinearFifoBufferType{ .Static = 32 }, .Slice, .Dynamic }) |bt| {
552 const FifoType = LinearFifo(T, bt);
553 var buf: if (bt == .Slice) [32]T else void = undefined;
554 var fifo = switch (bt) {
555 .Static => FifoType.init(),
556 .Slice => FifoType.init(buf[0..]),
557 .Dynamic => FifoType.init(testing.allocator),
558 };
559 defer fifo.deinit();
560
561 try fifo.write(&[_]T{ 0, 1, 1, 0, 1 });
562 try testing.expectEqual(@as(usize, 5), fifo.readableLength());
563
564 {
565 try testing.expectEqual(@as(T, 0), fifo.readItem().?);
566 try testing.expectEqual(@as(T, 1), fifo.readItem().?);
567 try testing.expectEqual(@as(T, 1), fifo.readItem().?);
568 try testing.expectEqual(@as(T, 0), fifo.readItem().?);
569 try testing.expectEqual(@as(T, 1), fifo.readItem().?);
570 try testing.expectEqual(@as(usize, 0), fifo.readableLength());
571 }
572
573 {
574 try fifo.writeItem(1);
575 try fifo.writeItem(1);
576 try fifo.writeItem(1);
577 try testing.expectEqual(@as(usize, 3), fifo.readableLength());
578 }
579
580 {
581 var readBuf: [3]T = undefined;
582 const n = fifo.read(&readBuf);
583 try testing.expectEqual(@as(usize, 3), n); // NOTE: It should be the number of items.
584 }
585 }
586 }
587}
lib/std/std.zig-1
......@@ -58,7 +58,6 @@ pub const debug = @import("debug.zig");
5858pub const dwarf = @import("dwarf.zig");
5959pub const elf = @import("elf.zig");
6060pub const enums = @import("enums.zig");
61pub const fifo = @import("fifo.zig");
6261pub const fmt = @import("fmt.zig");
6362pub const fs = @import("fs.zig");
6463pub const gpu = @import("gpu.zig");
src/Compilation.zig+5-3
......@@ -44,6 +44,8 @@ const Builtin = @import("Builtin.zig");
4444const LlvmObject = @import("codegen/llvm.zig").Object;
4545const dev = @import("dev.zig");
4646
47const DeprecatedLinearFifo = @import("deprecated.zig").LinearFifo;
48
4749pub const Config = @import("Compilation/Config.zig");
4850
4951/// General-purpose allocator. Used for both temporary and long-term storage.
......@@ -121,15 +123,15 @@ work_queues: [
121123 }
122124 break :len len;
123125 }
124]std.fifo.LinearFifo(Job, .Dynamic),
126]DeprecatedLinearFifo(Job),
125127
126128/// These jobs are to invoke the Clang compiler to create an object file, which
127129/// gets linked with the Compilation.
128c_object_work_queue: std.fifo.LinearFifo(*CObject, .Dynamic),
130c_object_work_queue: DeprecatedLinearFifo(*CObject),
129131
130132/// These jobs are to invoke the RC compiler to create a compiled resource file (.res), which
131133/// gets linked with the Compilation.
132win32_resource_work_queue: if (dev.env.supports(.win32_resource)) std.fifo.LinearFifo(*Win32Resource, .Dynamic) else struct {
134win32_resource_work_queue: if (dev.env.supports(.win32_resource)) DeprecatedLinearFifo(*Win32Resource) else struct {
133135 pub fn ensureUnusedCapacity(_: @This(), _: u0) error{}!void {}
134136 pub fn readItem(_: @This()) ?noreturn {
135137 return null;
src/deprecated.zig created+587
......@@ -0,0 +1,587 @@
1// FIFO of fixed size items
2// Usually used for e.g. byte buffers
3
4const std = @import("std");
5const math = std.math;
6const mem = std.mem;
7const Allocator = mem.Allocator;
8const assert = std.debug.assert;
9const testing = std.testing;
10
11pub const LinearFifoBufferType = union(enum) {
12 /// The buffer is internal to the fifo; it is of the specified size.
13 Static: usize,
14
15 /// The buffer is passed as a slice to the initialiser.
16 Slice,
17
18 /// The buffer is managed dynamically using a `mem.Allocator`.
19 Dynamic,
20};
21
22pub fn LinearFifo(
23 comptime T: type,
24 comptime buffer_type: LinearFifoBufferType,
25) type {
26 const autoalign = false;
27
28 const powers_of_two = switch (buffer_type) {
29 .Static => std.math.isPowerOfTwo(buffer_type.Static),
30 .Slice => false, // Any size slice could be passed in
31 .Dynamic => true, // This could be configurable in future
32 };
33
34 return struct {
35 allocator: if (buffer_type == .Dynamic) Allocator else void,
36 buf: if (buffer_type == .Static) [buffer_type.Static]T else []T,
37 head: usize,
38 count: usize,
39
40 const Self = @This();
41
42 // Type of Self argument for slice operations.
43 // If buffer is inline (Static) then we need to ensure we haven't
44 // returned a slice into a copy on the stack
45 const SliceSelfArg = if (buffer_type == .Static) *Self else Self;
46
47 pub const init = switch (buffer_type) {
48 .Static => initStatic,
49 .Slice => initSlice,
50 .Dynamic => initDynamic,
51 };
52
53 fn initStatic() Self {
54 comptime assert(buffer_type == .Static);
55 return .{
56 .allocator = {},
57 .buf = undefined,
58 .head = 0,
59 .count = 0,
60 };
61 }
62
63 fn initSlice(buf: []T) Self {
64 comptime assert(buffer_type == .Slice);
65 return .{
66 .allocator = {},
67 .buf = buf,
68 .head = 0,
69 .count = 0,
70 };
71 }
72
73 fn initDynamic(allocator: Allocator) Self {
74 comptime assert(buffer_type == .Dynamic);
75 return .{
76 .allocator = allocator,
77 .buf = &.{},
78 .head = 0,
79 .count = 0,
80 };
81 }
82
83 pub fn deinit(self: Self) void {
84 if (buffer_type == .Dynamic) self.allocator.free(self.buf);
85 }
86
87 pub fn realign(self: *Self) void {
88 if (self.buf.len - self.head >= self.count) {
89 mem.copyForwards(T, self.buf[0..self.count], self.buf[self.head..][0..self.count]);
90 self.head = 0;
91 } else {
92 var tmp: [4096 / 2 / @sizeOf(T)]T = undefined;
93
94 while (self.head != 0) {
95 const n = @min(self.head, tmp.len);
96 const m = self.buf.len - n;
97 @memcpy(tmp[0..n], self.buf[0..n]);
98 mem.copyForwards(T, self.buf[0..m], self.buf[n..][0..m]);
99 @memcpy(self.buf[m..][0..n], tmp[0..n]);
100 self.head -= n;
101 }
102 }
103 { // set unused area to undefined
104 const unused = mem.sliceAsBytes(self.buf[self.count..]);
105 @memset(unused, undefined);
106 }
107 }
108
109 /// Reduce allocated capacity to `size`.
110 pub fn shrink(self: *Self, size: usize) void {
111 assert(size >= self.count);
112 if (buffer_type == .Dynamic) {
113 self.realign();
114 self.buf = self.allocator.realloc(self.buf, size) catch |e| switch (e) {
115 error.OutOfMemory => return, // no problem, capacity is still correct then.
116 };
117 }
118 }
119
120 /// Ensure that the buffer can fit at least `size` items
121 pub fn ensureTotalCapacity(self: *Self, size: usize) !void {
122 if (self.buf.len >= size) return;
123 if (buffer_type == .Dynamic) {
124 self.realign();
125 const new_size = if (powers_of_two) math.ceilPowerOfTwo(usize, size) catch return error.OutOfMemory else size;
126 self.buf = try self.allocator.realloc(self.buf, new_size);
127 } else {
128 return error.OutOfMemory;
129 }
130 }
131
132 /// Makes sure at least `size` items are unused
133 pub fn ensureUnusedCapacity(self: *Self, size: usize) error{OutOfMemory}!void {
134 if (self.writableLength() >= size) return;
135
136 return try self.ensureTotalCapacity(math.add(usize, self.count, size) catch return error.OutOfMemory);
137 }
138
139 /// Returns number of items currently in fifo
140 pub fn readableLength(self: Self) usize {
141 return self.count;
142 }
143
144 /// Returns a writable slice from the 'read' end of the fifo
145 fn readableSliceMut(self: SliceSelfArg, offset: usize) []T {
146 if (offset > self.count) return &[_]T{};
147
148 var start = self.head + offset;
149 if (start >= self.buf.len) {
150 start -= self.buf.len;
151 return self.buf[start .. start + (self.count - offset)];
152 } else {
153 const end = @min(self.head + self.count, self.buf.len);
154 return self.buf[start..end];
155 }
156 }
157
158 /// Returns a readable slice from `offset`
159 pub fn readableSlice(self: SliceSelfArg, offset: usize) []const T {
160 return self.readableSliceMut(offset);
161 }
162
163 pub fn readableSliceOfLen(self: *Self, len: usize) []const T {
164 assert(len <= self.count);
165 const buf = self.readableSlice(0);
166 if (buf.len >= len) {
167 return buf[0..len];
168 } else {
169 self.realign();
170 return self.readableSlice(0)[0..len];
171 }
172 }
173
174 /// Discard first `count` items in the fifo
175 pub fn discard(self: *Self, count: usize) void {
176 assert(count <= self.count);
177 { // set old range to undefined. Note: may be wrapped around
178 const slice = self.readableSliceMut(0);
179 if (slice.len >= count) {
180 const unused = mem.sliceAsBytes(slice[0..count]);
181 @memset(unused, undefined);
182 } else {
183 const unused = mem.sliceAsBytes(slice[0..]);
184 @memset(unused, undefined);
185 const unused2 = mem.sliceAsBytes(self.readableSliceMut(slice.len)[0 .. count - slice.len]);
186 @memset(unused2, undefined);
187 }
188 }
189 if (autoalign and self.count == count) {
190 self.head = 0;
191 self.count = 0;
192 } else {
193 var head = self.head + count;
194 if (powers_of_two) {
195 // Note it is safe to do a wrapping subtract as
196 // bitwise & with all 1s is a noop
197 head &= self.buf.len -% 1;
198 } else {
199 head %= self.buf.len;
200 }
201 self.head = head;
202 self.count -= count;
203 }
204 }
205
206 /// Read the next item from the fifo
207 pub fn readItem(self: *Self) ?T {
208 if (self.count == 0) return null;
209
210 const c = self.buf[self.head];
211 self.discard(1);
212 return c;
213 }
214
215 /// Read data from the fifo into `dst`, returns number of items copied.
216 pub fn read(self: *Self, dst: []T) usize {
217 var dst_left = dst;
218
219 while (dst_left.len > 0) {
220 const slice = self.readableSlice(0);
221 if (slice.len == 0) break;
222 const n = @min(slice.len, dst_left.len);
223 @memcpy(dst_left[0..n], slice[0..n]);
224 self.discard(n);
225 dst_left = dst_left[n..];
226 }
227
228 return dst.len - dst_left.len;
229 }
230
231 /// Same as `read` except it returns an error union
232 /// The purpose of this function existing is to match `std.io.Reader` API.
233 fn readFn(self: *Self, dest: []u8) error{}!usize {
234 return self.read(dest);
235 }
236
237 pub fn reader(self: *Self) std.io.Reader {
238 return .{
239 .context = self,
240 .vtable = &.{
241 .read = &readerRead,
242 .readVec = &readerReadVec,
243 .discard = &readerDiscard,
244 },
245 };
246 }
247 fn readerRead(
248 ctx: ?*anyopaque,
249 bw: *std.io.BufferedWriter,
250 limit: std.io.Limit,
251 ) std.io.Reader.RwError!usize {
252 const fifo: *Self = @alignCast(@ptrCast(ctx));
253 _ = fifo;
254 _ = bw;
255 _ = limit;
256 @panic("TODO");
257 }
258 fn readerReadVec(ctx: ?*anyopaque, data: []const []u8) std.io.Reader.Error!usize {
259 const fifo: *Self = @alignCast(@ptrCast(ctx));
260 _ = fifo;
261 _ = data;
262 @panic("TODO");
263 }
264 fn readerDiscard(ctx: ?*anyopaque, limit: std.io.Limit) std.io.Reader.Error!usize {
265 const fifo: *Self = @alignCast(@ptrCast(ctx));
266 _ = fifo;
267 _ = limit;
268 @panic("TODO");
269 }
270
271 /// Returns number of items available in fifo
272 pub fn writableLength(self: Self) usize {
273 return self.buf.len - self.count;
274 }
275
276 /// Returns the first section of writable buffer.
277 /// Note that this may be of length 0
278 pub fn writableSlice(self: SliceSelfArg, offset: usize) []T {
279 if (offset > self.buf.len) return &[_]T{};
280
281 const tail = self.head + offset + self.count;
282 if (tail < self.buf.len) {
283 return self.buf[tail..];
284 } else {
285 return self.buf[tail - self.buf.len ..][0 .. self.writableLength() - offset];
286 }
287 }
288
289 /// Returns a writable buffer of at least `size` items, allocating memory as needed.
290 /// Use `fifo.update` once you've written data to it.
291 pub fn writableWithSize(self: *Self, size: usize) ![]T {
292 try self.ensureUnusedCapacity(size);
293
294 // try to avoid realigning buffer
295 var slice = self.writableSlice(0);
296 if (slice.len < size) {
297 self.realign();
298 slice = self.writableSlice(0);
299 }
300 return slice;
301 }
302
303 /// Update the tail location of the buffer (usually follows use of writable/writableWithSize)
304 pub fn update(self: *Self, count: usize) void {
305 assert(self.count + count <= self.buf.len);
306 self.count += count;
307 }
308
309 /// Appends the data in `src` to the fifo.
310 /// You must have ensured there is enough space.
311 pub fn writeAssumeCapacity(self: *Self, src: []const T) void {
312 assert(self.writableLength() >= src.len);
313
314 var src_left = src;
315 while (src_left.len > 0) {
316 const writable_slice = self.writableSlice(0);
317 assert(writable_slice.len != 0);
318 const n = @min(writable_slice.len, src_left.len);
319 @memcpy(writable_slice[0..n], src_left[0..n]);
320 self.update(n);
321 src_left = src_left[n..];
322 }
323 }
324
325 /// Write a single item to the fifo
326 pub fn writeItem(self: *Self, item: T) !void {
327 try self.ensureUnusedCapacity(1);
328 return self.writeItemAssumeCapacity(item);
329 }
330
331 pub fn writeItemAssumeCapacity(self: *Self, item: T) void {
332 var tail = self.head + self.count;
333 if (powers_of_two) {
334 tail &= self.buf.len - 1;
335 } else {
336 tail %= self.buf.len;
337 }
338 self.buf[tail] = item;
339 self.update(1);
340 }
341
342 /// Appends the data in `src` to the fifo.
343 /// Allocates more memory as necessary
344 pub fn write(self: *Self, src: []const T) !void {
345 try self.ensureUnusedCapacity(src.len);
346
347 return self.writeAssumeCapacity(src);
348 }
349
350 /// Same as `write` except it returns the number of bytes written, which is always the same
351 /// as `bytes.len`. The purpose of this function existing is to match `std.io.Writer` API.
352 fn appendWrite(self: *Self, bytes: []const u8) error{OutOfMemory}!usize {
353 try self.write(bytes);
354 return bytes.len;
355 }
356
357 pub fn writer(fifo: *Self) std.io.Writer {
358 return .{
359 .context = fifo,
360 .vtable = &.{
361 .writeSplat = writerWriteSplat,
362 .writeFile = writerWriteFile,
363 },
364 };
365 }
366 fn writerWriteSplat(ctx: ?*anyopaque, data: []const []const u8, splat: usize) std.io.Writer.Error!usize {
367 const fifo: *Self = @alignCast(@ptrCast(ctx));
368 _ = fifo;
369 _ = data;
370 _ = splat;
371 @panic("TODO");
372 }
373 fn writerWriteFile(
374 ctx: ?*anyopaque,
375 file: std.fs.File,
376 offset: std.io.Writer.Offset,
377 limit: std.io.Writer.Limit,
378 headers_and_trailers: []const []const u8,
379 headers_len: usize,
380 ) std.io.Writer.Error!usize {
381 const fifo: *Self = @alignCast(@ptrCast(ctx));
382 _ = fifo;
383 _ = file;
384 _ = offset;
385 _ = limit;
386 _ = headers_and_trailers;
387 _ = headers_len;
388 @panic("TODO");
389 }
390
391 /// Make `count` items available before the current read location
392 fn rewind(self: *Self, count: usize) void {
393 assert(self.writableLength() >= count);
394
395 var head = self.head + (self.buf.len - count);
396 if (powers_of_two) {
397 head &= self.buf.len - 1;
398 } else {
399 head %= self.buf.len;
400 }
401 self.head = head;
402 self.count += count;
403 }
404
405 /// Place data back into the read stream
406 pub fn unget(self: *Self, src: []const T) !void {
407 try self.ensureUnusedCapacity(src.len);
408
409 self.rewind(src.len);
410
411 const slice = self.readableSliceMut(0);
412 if (src.len < slice.len) {
413 @memcpy(slice[0..src.len], src);
414 } else {
415 @memcpy(slice, src[0..slice.len]);
416 const slice2 = self.readableSliceMut(slice.len);
417 @memcpy(slice2[0 .. src.len - slice.len], src[slice.len..]);
418 }
419 }
420
421 /// Returns the item at `offset`.
422 /// Asserts offset is within bounds.
423 pub fn peekItem(self: Self, offset: usize) T {
424 assert(offset < self.count);
425
426 var index = self.head + offset;
427 if (powers_of_two) {
428 index &= self.buf.len - 1;
429 } else {
430 index %= self.buf.len;
431 }
432 return self.buf[index];
433 }
434
435 pub fn toOwnedSlice(self: *Self) Allocator.Error![]T {
436 if (self.head != 0) self.realign();
437 assert(self.head == 0);
438 assert(self.count <= self.buf.len);
439 const allocator = self.allocator;
440 if (allocator.resize(self.buf, self.count)) {
441 const result = self.buf[0..self.count];
442 self.* = Self.init(allocator);
443 return result;
444 }
445 const new_memory = try allocator.dupe(T, self.buf[0..self.count]);
446 allocator.free(self.buf);
447 self.* = Self.init(allocator);
448 return new_memory;
449 }
450 };
451}
452
453test "LinearFifo(u8, .Dynamic) discard(0) from empty buffer should not error on overflow" {
454 var fifo = LinearFifo(u8, .Dynamic).init(testing.allocator);
455 defer fifo.deinit();
456
457 // If overflow is not explicitly allowed this will crash in debug / safe mode
458 fifo.discard(0);
459}
460
461test "LinearFifo(u8, .Dynamic)" {
462 var fifo = LinearFifo(u8, .Dynamic).init(testing.allocator);
463 defer fifo.deinit();
464
465 try fifo.write("HELLO");
466 try testing.expectEqual(@as(usize, 5), fifo.readableLength());
467 try testing.expectEqualSlices(u8, "HELLO", fifo.readableSlice(0));
468
469 {
470 var i: usize = 0;
471 while (i < 5) : (i += 1) {
472 try fifo.write(&[_]u8{fifo.peekItem(i)});
473 }
474 try testing.expectEqual(@as(usize, 10), fifo.readableLength());
475 try testing.expectEqualSlices(u8, "HELLOHELLO", fifo.readableSlice(0));
476 }
477
478 {
479 try testing.expectEqual(@as(u8, 'H'), fifo.readItem().?);
480 try testing.expectEqual(@as(u8, 'E'), fifo.readItem().?);
481 try testing.expectEqual(@as(u8, 'L'), fifo.readItem().?);
482 try testing.expectEqual(@as(u8, 'L'), fifo.readItem().?);
483 try testing.expectEqual(@as(u8, 'O'), fifo.readItem().?);
484 }
485 try testing.expectEqual(@as(usize, 5), fifo.readableLength());
486
487 { // Writes that wrap around
488 try testing.expectEqual(@as(usize, 11), fifo.writableLength());
489 try testing.expectEqual(@as(usize, 6), fifo.writableSlice(0).len);
490 fifo.writeAssumeCapacity("6<chars<11");
491 try testing.expectEqualSlices(u8, "HELLO6<char", fifo.readableSlice(0));
492 try testing.expectEqualSlices(u8, "s<11", fifo.readableSlice(11));
493 try testing.expectEqualSlices(u8, "11", fifo.readableSlice(13));
494 try testing.expectEqualSlices(u8, "", fifo.readableSlice(15));
495 fifo.discard(11);
496 try testing.expectEqualSlices(u8, "s<11", fifo.readableSlice(0));
497 fifo.discard(4);
498 try testing.expectEqual(@as(usize, 0), fifo.readableLength());
499 }
500
501 {
502 const buf = try fifo.writableWithSize(12);
503 try testing.expectEqual(@as(usize, 12), buf.len);
504 var i: u8 = 0;
505 while (i < 10) : (i += 1) {
506 buf[i] = i + 'a';
507 }
508 fifo.update(10);
509 try testing.expectEqualSlices(u8, "abcdefghij", fifo.readableSlice(0));
510 }
511
512 {
513 try fifo.unget("prependedstring");
514 var result: [30]u8 = undefined;
515 try testing.expectEqualSlices(u8, "prependedstringabcdefghij", result[0..fifo.read(&result)]);
516 try fifo.unget("b");
517 try fifo.unget("a");
518 try testing.expectEqualSlices(u8, "ab", result[0..fifo.read(&result)]);
519 }
520
521 fifo.shrink(0);
522
523 {
524 try fifo.writer().print("{s}, {s}!", .{ "Hello", "World" });
525 var result: [30]u8 = undefined;
526 try testing.expectEqualSlices(u8, "Hello, World!", result[0..fifo.read(&result)]);
527 try testing.expectEqual(@as(usize, 0), fifo.readableLength());
528 }
529
530 {
531 try fifo.writer().writeAll("This is a test");
532 var result: [30]u8 = undefined;
533 try testing.expectEqualSlices(u8, "This", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?);
534 try testing.expectEqualSlices(u8, "is", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?);
535 try testing.expectEqualSlices(u8, "a", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?);
536 try testing.expectEqualSlices(u8, "test", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?);
537 }
538
539 {
540 try fifo.ensureTotalCapacity(1);
541 var in_fbs = std.io.fixedBufferStream("pump test");
542 var out_buf: [50]u8 = undefined;
543 var out_fbs = std.io.fixedBufferStream(&out_buf);
544 try fifo.pump(in_fbs.reader(), out_fbs.writer());
545 try testing.expectEqualSlices(u8, in_fbs.buffer, out_fbs.getWritten());
546 }
547}
548
549test LinearFifo {
550 inline for ([_]type{ u1, u8, u16, u64 }) |T| {
551 inline for ([_]LinearFifoBufferType{ LinearFifoBufferType{ .Static = 32 }, .Slice, .Dynamic }) |bt| {
552 const FifoType = LinearFifo(T, bt);
553 var buf: if (bt == .Slice) [32]T else void = undefined;
554 var fifo = switch (bt) {
555 .Static => FifoType.init(),
556 .Slice => FifoType.init(buf[0..]),
557 .Dynamic => FifoType.init(testing.allocator),
558 };
559 defer fifo.deinit();
560
561 try fifo.write(&[_]T{ 0, 1, 1, 0, 1 });
562 try testing.expectEqual(@as(usize, 5), fifo.readableLength());
563
564 {
565 try testing.expectEqual(@as(T, 0), fifo.readItem().?);
566 try testing.expectEqual(@as(T, 1), fifo.readItem().?);
567 try testing.expectEqual(@as(T, 1), fifo.readItem().?);
568 try testing.expectEqual(@as(T, 0), fifo.readItem().?);
569 try testing.expectEqual(@as(T, 1), fifo.readItem().?);
570 try testing.expectEqual(@as(usize, 0), fifo.readableLength());
571 }
572
573 {
574 try fifo.writeItem(1);
575 try fifo.writeItem(1);
576 try fifo.writeItem(1);
577 try testing.expectEqual(@as(usize, 3), fifo.readableLength());
578 }
579
580 {
581 var readBuf: [3]T = undefined;
582 const n = fifo.read(&readBuf);
583 try testing.expectEqual(@as(usize, 3), n); // NOTE: It should be the number of items.
584 }
585 }
586 }
587}
src/link/MachO/dyld_info/Trie.zig+2-1
......@@ -138,7 +138,7 @@ fn finalize(self: *Trie, allocator: Allocator) !void {
138138 defer ordered_nodes.deinit();
139139 try ordered_nodes.ensureTotalCapacityPrecise(self.nodes.items(.is_terminal).len);
140140
141 var fifo = std.fifo.LinearFifo(Node.Index, .Dynamic).init(allocator);
141 var fifo = DeprecatedLinearFifo(Node.Index).init(allocator);
142142 defer fifo.deinit();
143143
144144 try fifo.writeItem(self.root.?);
......@@ -409,6 +409,7 @@ const mem = std.mem;
409409const std = @import("std");
410410const testing = std.testing;
411411const trace = @import("../../../tracy.zig").trace;
412const DeprecatedLinearFifo = @import("../../../deprecated.zig").LinearFifo;
412413
413414const Allocator = mem.Allocator;
414415const MachO = @import("../../MachO.zig");