authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-04 23:51:45-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-05 09:56:02-07:00
log196e36bbb27b0f0ebd7cd7a866b85f477b3662fb
tree04efce99fc296278385acbd0a27acdb52d916358
parentc47ec4f3d7a6bf79be3adcffa33aa51bcc26ed0b

std: remove BoundedArray

This use case is handled by ArrayListUnmanaged via the "...Bounded" method variants, and it's more optimal to share machine code, versus generating multiple versions of each function for differing array lengths.

12 files changed, 82 insertions(+), 526 deletions(-)

doc/langref/test_switch_dispatch_loop.zig+5-3
...@@ -8,20 +8,22 @@ const Instruction = enum {...@@ -8,20 +8,22 @@ const Instruction = enum {
8};8};
99
10fn evaluate(initial_stack: []const i32, code: []const Instruction) !i32 {10fn evaluate(initial_stack: []const i32, code: []const Instruction) !i32 {
11 var stack = try std.BoundedArray(i32, 8).fromSlice(initial_stack);11 var buffer: [8]i32 = undefined;
12 var stack = std.ArrayListUnmanaged(i32).initBuffer(&buffer);
13 try stack.appendSliceBounded(initial_stack);
12 var ip: usize = 0;14 var ip: usize = 0;
1315
14 return vm: switch (code[ip]) {16 return vm: switch (code[ip]) {
15 // Because all code after `continue` is unreachable, this branch does17 // Because all code after `continue` is unreachable, this branch does
16 // not provide a result.18 // not provide a result.
17 .add => {19 .add => {
18 try stack.append(stack.pop().? + stack.pop().?);20 try stack.appendBounded(stack.pop().? + stack.pop().?);
1921
20 ip += 1;22 ip += 1;
21 continue :vm code[ip];23 continue :vm code[ip];
22 },24 },
23 .mul => {25 .mul => {
24 try stack.append(stack.pop().? * stack.pop().?);26 try stack.appendBounded(stack.pop().? * stack.pop().?);
2527
26 ip += 1;28 ip += 1;
27 continue :vm code[ip];29 continue :vm code[ip];
lib/docs/wasm/markdown/Parser.zig+40-26
...@@ -29,13 +29,14 @@ const Node = Document.Node;...@@ -29,13 +29,14 @@ const Node = Document.Node;
29const ExtraIndex = Document.ExtraIndex;29const ExtraIndex = Document.ExtraIndex;
30const ExtraData = Document.ExtraData;30const ExtraData = Document.ExtraData;
31const StringIndex = Document.StringIndex;31const StringIndex = Document.StringIndex;
32const ArrayList = std.ArrayListUnmanaged;
3233
33nodes: Node.List = .{},34nodes: Node.List = .{},
34extra: std.ArrayListUnmanaged(u32) = .empty,35extra: ArrayList(u32) = .empty,
35scratch_extra: std.ArrayListUnmanaged(u32) = .empty,36scratch_extra: ArrayList(u32) = .empty,
36string_bytes: std.ArrayListUnmanaged(u8) = .empty,37string_bytes: ArrayList(u8) = .empty,
37scratch_string: std.ArrayListUnmanaged(u8) = .empty,38scratch_string: ArrayList(u8) = .empty,
38pending_blocks: std.ArrayListUnmanaged(Block) = .empty,39pending_blocks: ArrayList(Block) = .empty,
39allocator: Allocator,40allocator: Allocator,
4041
41const Parser = @This();42const Parser = @This();
...@@ -86,7 +87,8 @@ const Block = struct {...@@ -86,7 +87,8 @@ const Block = struct {
86 continuation_indent: usize,87 continuation_indent: usize,
87 },88 },
88 table: struct {89 table: struct {
89 column_alignments: std.BoundedArray(Node.TableCellAlignment, max_table_columns) = .{},90 column_alignments_buffer: [max_table_columns]Node.TableCellAlignment,
91 column_alignments_len: usize,
90 },92 },
91 heading: struct {93 heading: struct {
92 /// Between 1 and 6, inclusive.94 /// Between 1 and 6, inclusive.
...@@ -354,7 +356,8 @@ const BlockStart = struct {...@@ -354,7 +356,8 @@ const BlockStart = struct {
354 continuation_indent: usize,356 continuation_indent: usize,
355 },357 },
356 table_row: struct {358 table_row: struct {
357 cells: std.BoundedArray([]const u8, max_table_columns),359 cells_buffer: [max_table_columns][]const u8,
360 cells_len: usize,
358 },361 },
359 heading: struct {362 heading: struct {
360 /// Between 1 and 6, inclusive.363 /// Between 1 and 6, inclusive.
...@@ -422,7 +425,8 @@ fn appendBlockStart(p: *Parser, block_start: BlockStart) !void {...@@ -422,7 +425,8 @@ fn appendBlockStart(p: *Parser, block_start: BlockStart) !void {
422 try p.pending_blocks.append(p.allocator, .{425 try p.pending_blocks.append(p.allocator, .{
423 .tag = .table,426 .tag = .table,
424 .data = .{ .table = .{427 .data = .{ .table = .{
425 .column_alignments = .{},428 .column_alignments_buffer = undefined,
429 .column_alignments_len = 0,
426 } },430 } },
427 .string_start = p.scratch_string.items.len,431 .string_start = p.scratch_string.items.len,
428 .extra_start = p.scratch_extra.items.len,432 .extra_start = p.scratch_extra.items.len,
...@@ -431,15 +435,19 @@ fn appendBlockStart(p: *Parser, block_start: BlockStart) !void {...@@ -431,15 +435,19 @@ fn appendBlockStart(p: *Parser, block_start: BlockStart) !void {
431435
432 const current_row = p.scratch_extra.items.len - p.pending_blocks.getLast().extra_start;436 const current_row = p.scratch_extra.items.len - p.pending_blocks.getLast().extra_start;
433 if (current_row <= 1) {437 if (current_row <= 1) {
434 if (parseTableHeaderDelimiter(block_start.data.table_row.cells)) |alignments| {438 var buffer: [max_table_columns]Node.TableCellAlignment = undefined;
435 p.pending_blocks.items[p.pending_blocks.items.len - 1].data.table.column_alignments = alignments;439 const table_row = &block_start.data.table_row;
440 if (parseTableHeaderDelimiter(table_row.cells_buffer[0..table_row.cells_len], &buffer)) |alignments| {
441 const table = &p.pending_blocks.items[p.pending_blocks.items.len - 1].data.table;
442 @memcpy(table.column_alignments_buffer[0..alignments.len], alignments);
443 table.column_alignments_len = alignments.len;
436 if (current_row == 1) {444 if (current_row == 1) {
437 // We need to go back and mark the header row and its column445 // We need to go back and mark the header row and its column
438 // alignments.446 // alignments.
439 const datas = p.nodes.items(.data);447 const datas = p.nodes.items(.data);
440 const header_data = datas[p.scratch_extra.getLast()];448 const header_data = datas[p.scratch_extra.getLast()];
441 for (p.extraChildren(header_data.container.children), 0..) |header_cell, i| {449 for (p.extraChildren(header_data.container.children), 0..) |header_cell, i| {
442 const alignment = if (i < alignments.len) alignments.buffer[i] else .unset;450 const alignment = if (i < alignments.len) alignments[i] else .unset;
443 const cell_data = &datas[@intFromEnum(header_cell)].table_cell;451 const cell_data = &datas[@intFromEnum(header_cell)].table_cell;
444 cell_data.info.alignment = alignment;452 cell_data.info.alignment = alignment;
445 cell_data.info.header = true;453 cell_data.info.header = true;
...@@ -480,8 +488,10 @@ fn appendBlockStart(p: *Parser, block_start: BlockStart) !void {...@@ -480,8 +488,10 @@ fn appendBlockStart(p: *Parser, block_start: BlockStart) !void {
480 // available in the BlockStart. We can immediately parse and append488 // available in the BlockStart. We can immediately parse and append
481 // these children now.489 // these children now.
482 const containing_table = p.pending_blocks.items[p.pending_blocks.items.len - 2];490 const containing_table = p.pending_blocks.items[p.pending_blocks.items.len - 2];
483 const column_alignments = containing_table.data.table.column_alignments.slice();491 const table = &containing_table.data.table;
484 for (block_start.data.table_row.cells.slice(), 0..) |cell_content, i| {492 const column_alignments = table.column_alignments_buffer[0..table.column_alignments_len];
493 const table_row = &block_start.data.table_row;
494 for (table_row.cells_buffer[0..table_row.cells_len], 0..) |cell_content, i| {
485 const cell_children = try p.parseInlines(cell_content);495 const cell_children = try p.parseInlines(cell_content);
486 const alignment = if (i < column_alignments.len) column_alignments[i] else .unset;496 const alignment = if (i < column_alignments.len) column_alignments[i] else .unset;
487 const cell = try p.addNode(.{497 const cell = try p.addNode(.{
...@@ -523,7 +533,8 @@ fn startBlock(p: *Parser, line: []const u8) !?BlockStart {...@@ -523,7 +533,8 @@ fn startBlock(p: *Parser, line: []const u8) !?BlockStart {
523 return .{533 return .{
524 .tag = .table_row,534 .tag = .table_row,
525 .data = .{ .table_row = .{535 .data = .{ .table_row = .{
526 .cells = table_row.cells,536 .cells_buffer = table_row.cells_buffer,
537 .cells_len = table_row.cells_len,
527 } },538 } },
528 .rest = "",539 .rest = "",
529 };540 };
...@@ -606,7 +617,8 @@ fn startListItem(unindented_line: []const u8) ?ListItemStart {...@@ -606,7 +617,8 @@ fn startListItem(unindented_line: []const u8) ?ListItemStart {
606}617}
607618
608const TableRowStart = struct {619const TableRowStart = struct {
609 cells: std.BoundedArray([]const u8, max_table_columns),620 cells_buffer: [max_table_columns][]const u8,
621 cells_len: usize,
610};622};
611623
612fn startTableRow(unindented_line: []const u8) ?TableRowStart {624fn startTableRow(unindented_line: []const u8) ?TableRowStart {
...@@ -615,7 +627,8 @@ fn startTableRow(unindented_line: []const u8) ?TableRowStart {...@@ -615,7 +627,8 @@ fn startTableRow(unindented_line: []const u8) ?TableRowStart {
615 mem.endsWith(u8, unindented_line, "\\|") or627 mem.endsWith(u8, unindented_line, "\\|") or
616 !mem.endsWith(u8, unindented_line, "|")) return null;628 !mem.endsWith(u8, unindented_line, "|")) return null;
617629
618 var cells: std.BoundedArray([]const u8, max_table_columns) = .{};630 var cells_buffer: [max_table_columns][]const u8 = undefined;
631 var cells: ArrayList([]const u8) = .initBuffer(&cells_buffer);
619 const table_row_content = unindented_line[1 .. unindented_line.len - 1];632 const table_row_content = unindented_line[1 .. unindented_line.len - 1];
620 var cell_start: usize = 0;633 var cell_start: usize = 0;
621 var i: usize = 0;634 var i: usize = 0;
...@@ -623,7 +636,7 @@ fn startTableRow(unindented_line: []const u8) ?TableRowStart {...@@ -623,7 +636,7 @@ fn startTableRow(unindented_line: []const u8) ?TableRowStart {
623 switch (table_row_content[i]) {636 switch (table_row_content[i]) {
624 '\\' => i += 1,637 '\\' => i += 1,
625 '|' => {638 '|' => {
626 cells.append(table_row_content[cell_start..i]) catch return null;639 cells.appendBounded(table_row_content[cell_start..i]) catch return null;
627 cell_start = i + 1;640 cell_start = i + 1;
628 },641 },
629 '`' => {642 '`' => {
...@@ -641,20 +654,21 @@ fn startTableRow(unindented_line: []const u8) ?TableRowStart {...@@ -641,20 +654,21 @@ fn startTableRow(unindented_line: []const u8) ?TableRowStart {
641 else => {},654 else => {},
642 }655 }
643 }656 }
644 cells.append(table_row_content[cell_start..]) catch return null;657 cells.appendBounded(table_row_content[cell_start..]) catch return null;
645658
646 return .{ .cells = cells };659 return .{ .cells_buffer = cells_buffer, .cells_len = cells.items.len };
647}660}
648661
649fn parseTableHeaderDelimiter(662fn parseTableHeaderDelimiter(
650 row_cells: std.BoundedArray([]const u8, max_table_columns),663 row_cells: []const []const u8,
651) ?std.BoundedArray(Node.TableCellAlignment, max_table_columns) {664 buffer: []Node.TableCellAlignment,
652 var alignments: std.BoundedArray(Node.TableCellAlignment, max_table_columns) = .{};665) ?[]Node.TableCellAlignment {
653 for (row_cells.slice()) |content| {666 var alignments: ArrayList(Node.TableCellAlignment) = .initBuffer(buffer);
667 for (row_cells) |content| {
654 const alignment = parseTableHeaderDelimiterCell(content) orelse return null;668 const alignment = parseTableHeaderDelimiterCell(content) orelse return null;
655 alignments.appendAssumeCapacity(alignment);669 alignments.appendAssumeCapacity(alignment);
656 }670 }
657 return alignments;671 return alignments.items;
658}672}
659673
660fn parseTableHeaderDelimiterCell(content: []const u8) ?Node.TableCellAlignment {674fn parseTableHeaderDelimiterCell(content: []const u8) ?Node.TableCellAlignment {
...@@ -928,8 +942,8 @@ const InlineParser = struct {...@@ -928,8 +942,8 @@ const InlineParser = struct {
928 parent: *Parser,942 parent: *Parser,
929 content: []const u8,943 content: []const u8,
930 pos: usize = 0,944 pos: usize = 0,
931 pending_inlines: std.ArrayListUnmanaged(PendingInline) = .empty,945 pending_inlines: ArrayList(PendingInline) = .empty,
932 completed_inlines: std.ArrayListUnmanaged(CompletedInline) = .empty,946 completed_inlines: ArrayList(CompletedInline) = .empty,
933947
934 const PendingInline = struct {948 const PendingInline = struct {
935 tag: Tag,949 tag: Tag,
lib/std/Io.zig-15
...@@ -231,21 +231,6 @@ pub fn GenericReader(...@@ -231,21 +231,6 @@ pub fn GenericReader(
231 return @errorCast(self.any().readBytesNoEof(num_bytes));231 return @errorCast(self.any().readBytesNoEof(num_bytes));
232 }232 }
233233
234 pub inline fn readIntoBoundedBytes(
235 self: Self,
236 comptime num_bytes: usize,
237 bounded: *std.BoundedArray(u8, num_bytes),
238 ) Error!void {
239 return @errorCast(self.any().readIntoBoundedBytes(num_bytes, bounded));
240 }
241
242 pub inline fn readBoundedBytes(
243 self: Self,
244 comptime num_bytes: usize,
245 ) Error!std.BoundedArray(u8, num_bytes) {
246 return @errorCast(self.any().readBoundedBytes(num_bytes));
247 }
248
249 pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) NoEofError!T {234 pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) NoEofError!T {
250 return @errorCast(self.any().readInt(T, endian));235 return @errorCast(self.any().readInt(T, endian));
251 }236 }
lib/std/Io/DeprecatedReader.zig-27
...@@ -249,33 +249,6 @@ pub fn readBytesNoEof(self: Self, comptime num_bytes: usize) anyerror![num_bytes...@@ -249,33 +249,6 @@ pub fn readBytesNoEof(self: Self, comptime num_bytes: usize) anyerror![num_bytes
249 return bytes;249 return bytes;
250}250}
251251
252/// Reads bytes until `bounded.len` is equal to `num_bytes`,
253/// or the stream ends.
254///
255/// * it is assumed that `num_bytes` will not exceed `bounded.capacity()`
256pub fn readIntoBoundedBytes(
257 self: Self,
258 comptime num_bytes: usize,
259 bounded: *std.BoundedArray(u8, num_bytes),
260) anyerror!void {
261 while (bounded.len < num_bytes) {
262 // get at most the number of bytes free in the bounded array
263 const bytes_read = try self.read(bounded.unusedCapacitySlice());
264 if (bytes_read == 0) return;
265
266 // bytes_read will never be larger than @TypeOf(bounded.len)
267 // due to `self.read` being bounded by `bounded.unusedCapacitySlice()`
268 bounded.len += @as(@TypeOf(bounded.len), @intCast(bytes_read));
269 }
270}
271
272/// Reads at most `num_bytes` and returns as a bounded array.
273pub fn readBoundedBytes(self: Self, comptime num_bytes: usize) anyerror!std.BoundedArray(u8, num_bytes) {
274 var result = std.BoundedArray(u8, num_bytes){};
275 try self.readIntoBoundedBytes(num_bytes, &result);
276 return result;
277}
278
279pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T {252pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T {
280 const bytes = try self.readBytesNoEof(@divExact(@typeInfo(T).int.bits, 8));253 const bytes = try self.readBytesNoEof(@divExact(@typeInfo(T).int.bits, 8));
281 return mem.readInt(T, &bytes, endian);254 return mem.readInt(T, &bytes, endian);
lib/std/Io/Reader/test.zig-21
...@@ -349,24 +349,3 @@ test "streamUntilDelimiter writes all bytes without delimiter to the output" {...@@ -349,24 +349,3 @@ test "streamUntilDelimiter writes all bytes without delimiter to the output" {
349349
350 try std.testing.expectError(error.StreamTooLong, reader.streamUntilDelimiter(writer, '!', 5));350 try std.testing.expectError(error.StreamTooLong, reader.streamUntilDelimiter(writer, '!', 5));
351}351}
352
353test "readBoundedBytes correctly reads into a new bounded array" {
354 const test_string = "abcdefg";
355 var fis = std.io.fixedBufferStream(test_string);
356 const reader = fis.reader();
357
358 var array = try reader.readBoundedBytes(10000);
359 try testing.expectEqualStrings(array.slice(), test_string);
360}
361
362test "readIntoBoundedBytes correctly reads into a provided bounded array" {
363 const test_string = "abcdefg";
364 var fis = std.io.fixedBufferStream(test_string);
365 const reader = fis.reader();
366
367 var bounded_array = std.BoundedArray(u8, 10000){};
368
369 // compile time error if the size is not the same at the provided `bounded.capacity()`
370 try reader.readIntoBoundedBytes(10000, &bounded_array);
371 try testing.expectEqualStrings(bounded_array.slice(), test_string);
372}
lib/std/array_list.zig+1
...@@ -657,6 +657,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -657,6 +657,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
657657
658 /// Initialize with externally-managed memory. The buffer determines the658 /// Initialize with externally-managed memory. The buffer determines the
659 /// capacity, and the length is set to zero.659 /// capacity, and the length is set to zero.
660 ///
660 /// When initialized this way, all functions that accept an Allocator661 /// When initialized this way, all functions that accept an Allocator
661 /// argument cause illegal behavior.662 /// argument cause illegal behavior.
662 pub fn initBuffer(buffer: Slice) Self {663 pub fn initBuffer(buffer: Slice) Self {
lib/std/base64.zig+13-9
...@@ -517,17 +517,21 @@ fn testAllApis(codecs: Codecs, expected_decoded: []const u8, expected_encoded: [...@@ -517,17 +517,21 @@ fn testAllApis(codecs: Codecs, expected_decoded: []const u8, expected_encoded: [
517 var buffer: [0x100]u8 = undefined;517 var buffer: [0x100]u8 = undefined;
518 const encoded = codecs.Encoder.encode(&buffer, expected_decoded);518 const encoded = codecs.Encoder.encode(&buffer, expected_decoded);
519 try testing.expectEqualSlices(u8, expected_encoded, encoded);519 try testing.expectEqualSlices(u8, expected_encoded, encoded);
520520 }
521 {
521 // stream encode522 // stream encode
522 var list = try std.BoundedArray(u8, 0x100).init(0);523 var buffer: [0x100]u8 = undefined;
523 try codecs.Encoder.encodeWriter(list.writer(), expected_decoded);524 var writer: std.Io.Writer = .fixed(&buffer);
524 try testing.expectEqualSlices(u8, expected_encoded, list.slice());525 try codecs.Encoder.encodeWriter(&writer, expected_decoded);
525526 try testing.expectEqualSlices(u8, expected_encoded, writer.buffered());
527 }
528 {
526 // reader to writer encode529 // reader to writer encode
527 var stream = std.io.fixedBufferStream(expected_decoded);530 var stream: std.Io.Reader = .fixed(expected_decoded);
528 list = try std.BoundedArray(u8, 0x100).init(0);531 var buffer: [0x100]u8 = undefined;
529 try codecs.Encoder.encodeFromReaderToWriter(list.writer(), stream.reader());532 var writer: std.Io.Writer = .fixed(&buffer);
530 try testing.expectEqualSlices(u8, expected_encoded, list.slice());533 try codecs.Encoder.encodeFromReaderToWriter(&writer, &stream);
534 try testing.expectEqualSlices(u8, expected_encoded, writer.buffered());
531 }535 }
532536
533 // Base64Decoder537 // Base64Decoder
lib/std/bounded_array.zig deleted-412
...@@ -1,412 +0,0 @@
1const std = @import("std.zig");
2const assert = std.debug.assert;
3const mem = std.mem;
4const testing = std.testing;
5const Alignment = std.mem.Alignment;
6
7/// A structure with an array and a length, that can be used as a slice.
8///
9/// Useful to pass around small arrays whose exact size is only known at
10/// runtime, but whose maximum size is known at comptime, without requiring
11/// an `Allocator`.
12///
13/// ```zig
14/// var actual_size = 32;
15/// var a = try BoundedArray(u8, 64).init(actual_size);
16/// var slice = a.slice(); // a slice of the 64-byte array
17/// var a_clone = a; // creates a copy - the structure doesn't use any internal pointers
18/// ```
19pub fn BoundedArray(comptime T: type, comptime buffer_capacity: usize) type {
20 return BoundedArrayAligned(T, .of(T), buffer_capacity);
21}
22
23/// A structure with an array, length and alignment, that can be used as a
24/// slice.
25///
26/// Useful to pass around small explicitly-aligned arrays whose exact size is
27/// only known at runtime, but whose maximum size is known at comptime, without
28/// requiring an `Allocator`.
29/// ```zig
30// var a = try BoundedArrayAligned(u8, 16, 2).init(0);
31// try a.append(255);
32// try a.append(255);
33// const b = @ptrCast(*const [1]u16, a.constSlice().ptr);
34// try testing.expectEqual(@as(u16, 65535), b[0]);
35/// ```
36pub fn BoundedArrayAligned(
37 comptime T: type,
38 comptime alignment: Alignment,
39 comptime buffer_capacity: usize,
40) type {
41 return struct {
42 const Self = @This();
43 buffer: [buffer_capacity]T align(alignment.toByteUnits()) = undefined,
44 len: usize = 0,
45
46 /// Set the actual length of the slice.
47 /// Returns error.Overflow if it exceeds the length of the backing array.
48 pub fn init(len: usize) error{Overflow}!Self {
49 if (len > buffer_capacity) return error.Overflow;
50 return Self{ .len = len };
51 }
52
53 /// View the internal array as a slice whose size was previously set.
54 pub fn slice(self: anytype) switch (@TypeOf(&self.buffer)) {
55 *align(alignment.toByteUnits()) [buffer_capacity]T => []align(alignment.toByteUnits()) T,
56 *align(alignment.toByteUnits()) const [buffer_capacity]T => []align(alignment.toByteUnits()) const T,
57 else => unreachable,
58 } {
59 return self.buffer[0..self.len];
60 }
61
62 /// View the internal array as a constant slice whose size was previously set.
63 pub fn constSlice(self: *const Self) []align(alignment.toByteUnits()) const T {
64 return self.slice();
65 }
66
67 /// Adjust the slice's length to `len`.
68 /// Does not initialize added items if any.
69 pub fn resize(self: *Self, len: usize) error{Overflow}!void {
70 if (len > buffer_capacity) return error.Overflow;
71 self.len = len;
72 }
73
74 /// Remove all elements from the slice.
75 pub fn clear(self: *Self) void {
76 self.len = 0;
77 }
78
79 /// Copy the content of an existing slice.
80 pub fn fromSlice(m: []const T) error{Overflow}!Self {
81 var list = try init(m.len);
82 @memcpy(list.slice(), m);
83 return list;
84 }
85
86 /// Return the element at index `i` of the slice.
87 pub fn get(self: Self, i: usize) T {
88 return self.constSlice()[i];
89 }
90
91 /// Set the value of the element at index `i` of the slice.
92 pub fn set(self: *Self, i: usize, item: T) void {
93 self.slice()[i] = item;
94 }
95
96 /// Return the maximum length of a slice.
97 pub fn capacity(self: Self) usize {
98 return self.buffer.len;
99 }
100
101 /// Check that the slice can hold at least `additional_count` items.
102 pub fn ensureUnusedCapacity(self: Self, additional_count: usize) error{Overflow}!void {
103 if (self.len + additional_count > buffer_capacity) {
104 return error.Overflow;
105 }
106 }
107
108 /// Increase length by 1, returning a pointer to the new item.
109 pub fn addOne(self: *Self) error{Overflow}!*T {
110 try self.ensureUnusedCapacity(1);
111 return self.addOneAssumeCapacity();
112 }
113
114 /// Increase length by 1, returning pointer to the new item.
115 /// Asserts that there is space for the new item.
116 pub fn addOneAssumeCapacity(self: *Self) *T {
117 assert(self.len < buffer_capacity);
118 self.len += 1;
119 return &self.slice()[self.len - 1];
120 }
121
122 /// Resize the slice, adding `n` new elements, which have `undefined` values.
123 /// The return value is a pointer to the array of uninitialized elements.
124 pub fn addManyAsArray(self: *Self, comptime n: usize) error{Overflow}!*align(alignment.toByteUnits()) [n]T {
125 const prev_len = self.len;
126 try self.resize(self.len + n);
127 return self.slice()[prev_len..][0..n];
128 }
129
130 /// Resize the slice, adding `n` new elements, which have `undefined` values.
131 /// The return value is a slice pointing to the uninitialized elements.
132 pub fn addManyAsSlice(self: *Self, n: usize) error{Overflow}![]align(alignment.toByteUnits()) T {
133 const prev_len = self.len;
134 try self.resize(self.len + n);
135 return self.slice()[prev_len..][0..n];
136 }
137
138 /// Remove and return the last element from the slice, or return `null` if the slice is empty.
139 pub fn pop(self: *Self) ?T {
140 if (self.len == 0) return null;
141 const item = self.get(self.len - 1);
142 self.len -= 1;
143 return item;
144 }
145
146 /// Return a slice of only the extra capacity after items.
147 /// This can be useful for writing directly into it.
148 /// Note that such an operation must be followed up with a
149 /// call to `resize()`
150 pub fn unusedCapacitySlice(self: *Self) []align(alignment.toByteUnits()) T {
151 return self.buffer[self.len..];
152 }
153
154 /// Insert `item` at index `i` by moving `slice[n .. slice.len]` to make room.
155 /// This operation is O(N).
156 pub fn insert(
157 self: *Self,
158 i: usize,
159 item: T,
160 ) error{Overflow}!void {
161 if (i > self.len) {
162 return error.Overflow;
163 }
164 _ = try self.addOne();
165 var s = self.slice();
166 mem.copyBackwards(T, s[i + 1 .. s.len], s[i .. s.len - 1]);
167 self.buffer[i] = item;
168 }
169
170 /// Insert slice `items` at index `i` by moving `slice[i .. slice.len]` to make room.
171 /// This operation is O(N).
172 pub fn insertSlice(self: *Self, i: usize, items: []const T) error{Overflow}!void {
173 try self.ensureUnusedCapacity(items.len);
174 self.len += items.len;
175 mem.copyBackwards(T, self.slice()[i + items.len .. self.len], self.constSlice()[i .. self.len - items.len]);
176 @memcpy(self.slice()[i..][0..items.len], items);
177 }
178
179 /// Replace range of elements `slice[start..][0..len]` with `new_items`.
180 /// Grows slice if `len < new_items.len`.
181 /// Shrinks slice if `len > new_items.len`.
182 pub fn replaceRange(
183 self: *Self,
184 start: usize,
185 len: usize,
186 new_items: []const T,
187 ) error{Overflow}!void {
188 const after_range = start + len;
189 var range = self.slice()[start..after_range];
190
191 if (range.len == new_items.len) {
192 @memcpy(range[0..new_items.len], new_items);
193 } else if (range.len < new_items.len) {
194 const first = new_items[0..range.len];
195 const rest = new_items[range.len..];
196 @memcpy(range[0..first.len], first);
197 try self.insertSlice(after_range, rest);
198 } else {
199 @memcpy(range[0..new_items.len], new_items);
200 const after_subrange = start + new_items.len;
201 for (self.constSlice()[after_range..], 0..) |item, i| {
202 self.slice()[after_subrange..][i] = item;
203 }
204 self.len -= len - new_items.len;
205 }
206 }
207
208 /// Extend the slice by 1 element.
209 pub fn append(self: *Self, item: T) error{Overflow}!void {
210 const new_item_ptr = try self.addOne();
211 new_item_ptr.* = item;
212 }
213
214 /// Extend the slice by 1 element, asserting the capacity is already
215 /// enough to store the new item.
216 pub fn appendAssumeCapacity(self: *Self, item: T) void {
217 const new_item_ptr = self.addOneAssumeCapacity();
218 new_item_ptr.* = item;
219 }
220
221 /// Remove the element at index `i`, shift elements after index
222 /// `i` forward, and return the removed element.
223 /// Asserts the slice has at least one item.
224 /// This operation is O(N).
225 pub fn orderedRemove(self: *Self, i: usize) T {
226 const newlen = self.len - 1;
227 if (newlen == i) return self.pop().?;
228 const old_item = self.get(i);
229 for (self.slice()[i..newlen], 0..) |*b, j| b.* = self.get(i + 1 + j);
230 self.set(newlen, undefined);
231 self.len = newlen;
232 return old_item;
233 }
234
235 /// Remove the element at the specified index and return it.
236 /// The empty slot is filled from the end of the slice.
237 /// This operation is O(1).
238 pub fn swapRemove(self: *Self, i: usize) T {
239 if (self.len - 1 == i) return self.pop().?;
240 const old_item = self.get(i);
241 self.set(i, self.pop().?);
242 return old_item;
243 }
244
245 /// Append the slice of items to the slice.
246 pub fn appendSlice(self: *Self, items: []const T) error{Overflow}!void {
247 try self.ensureUnusedCapacity(items.len);
248 self.appendSliceAssumeCapacity(items);
249 }
250
251 /// Append the slice of items to the slice, asserting the capacity is already
252 /// enough to store the new items.
253 pub fn appendSliceAssumeCapacity(self: *Self, items: []const T) void {
254 const old_len = self.len;
255 self.len += items.len;
256 @memcpy(self.slice()[old_len..][0..items.len], items);
257 }
258
259 /// Append a value to the slice `n` times.
260 /// Allocates more memory as necessary.
261 pub fn appendNTimes(self: *Self, value: T, n: usize) error{Overflow}!void {
262 const old_len = self.len;
263 try self.resize(old_len + n);
264 @memset(self.slice()[old_len..self.len], value);
265 }
266
267 /// Append a value to the slice `n` times.
268 /// Asserts the capacity is enough.
269 pub fn appendNTimesAssumeCapacity(self: *Self, value: T, n: usize) void {
270 const old_len = self.len;
271 self.len += n;
272 assert(self.len <= buffer_capacity);
273 @memset(self.slice()[old_len..self.len], value);
274 }
275
276 pub const Writer = if (T != u8)
277 @compileError("The Writer interface is only defined for BoundedArray(u8, ...) " ++
278 "but the given type is BoundedArray(" ++ @typeName(T) ++ ", ...)")
279 else
280 std.io.GenericWriter(*Self, error{Overflow}, appendWrite);
281
282 /// Initializes a writer which will write into the array.
283 pub fn writer(self: *Self) Writer {
284 return .{ .context = self };
285 }
286
287 /// Same as `appendSlice` except it returns the number of bytes written, which is always the same
288 /// as `m.len`. The purpose of this function existing is to match `std.io.GenericWriter` API.
289 fn appendWrite(self: *Self, m: []const u8) error{Overflow}!usize {
290 try self.appendSlice(m);
291 return m.len;
292 }
293 };
294}
295
296test BoundedArray {
297 var a = try BoundedArray(u8, 64).init(32);
298
299 try testing.expectEqual(a.capacity(), 64);
300 try testing.expectEqual(a.slice().len, 32);
301 try testing.expectEqual(a.constSlice().len, 32);
302
303 try a.resize(48);
304 try testing.expectEqual(a.len, 48);
305
306 const x = [_]u8{1} ** 10;
307 a = try BoundedArray(u8, 64).fromSlice(&x);
308 try testing.expectEqualSlices(u8, &x, a.constSlice());
309
310 var a2 = a;
311 try testing.expectEqualSlices(u8, a.constSlice(), a2.constSlice());
312 a2.set(0, 0);
313 try testing.expect(a.get(0) != a2.get(0));
314
315 try testing.expectError(error.Overflow, a.resize(100));
316 try testing.expectError(error.Overflow, BoundedArray(u8, x.len - 1).fromSlice(&x));
317
318 try a.resize(0);
319 try a.ensureUnusedCapacity(a.capacity());
320 (try a.addOne()).* = 0;
321 try a.ensureUnusedCapacity(a.capacity() - 1);
322 try testing.expectEqual(a.len, 1);
323
324 const uninitialized = try a.addManyAsArray(4);
325 try testing.expectEqual(uninitialized.len, 4);
326 try testing.expectEqual(a.len, 5);
327
328 try a.append(0xff);
329 try testing.expectEqual(a.len, 6);
330 try testing.expectEqual(a.pop(), 0xff);
331
332 a.appendAssumeCapacity(0xff);
333 try testing.expectEqual(a.len, 6);
334 try testing.expectEqual(a.pop(), 0xff);
335
336 try a.resize(1);
337 try testing.expectEqual(a.pop(), 0);
338 try testing.expectEqual(a.pop(), null);
339 var unused = a.unusedCapacitySlice();
340 @memset(unused[0..8], 2);
341 unused[8] = 3;
342 unused[9] = 4;
343 try testing.expectEqual(unused.len, a.capacity());
344 try a.resize(10);
345
346 try a.insert(5, 0xaa);
347 try testing.expectEqual(a.len, 11);
348 try testing.expectEqual(a.get(5), 0xaa);
349 try testing.expectEqual(a.get(9), 3);
350 try testing.expectEqual(a.get(10), 4);
351
352 try a.insert(11, 0xbb);
353 try testing.expectEqual(a.len, 12);
354 try testing.expectEqual(a.pop(), 0xbb);
355
356 try a.appendSlice(&x);
357 try testing.expectEqual(a.len, 11 + x.len);
358
359 try a.appendNTimes(0xbb, 5);
360 try testing.expectEqual(a.len, 11 + x.len + 5);
361 try testing.expectEqual(a.pop(), 0xbb);
362
363 a.appendNTimesAssumeCapacity(0xcc, 5);
364 try testing.expectEqual(a.len, 11 + x.len + 5 - 1 + 5);
365 try testing.expectEqual(a.pop(), 0xcc);
366
367 try testing.expectEqual(a.len, 29);
368 try a.replaceRange(1, 20, &x);
369 try testing.expectEqual(a.len, 29 + x.len - 20);
370
371 try a.insertSlice(0, &x);
372 try testing.expectEqual(a.len, 29 + x.len - 20 + x.len);
373
374 try a.replaceRange(1, 5, &x);
375 try testing.expectEqual(a.len, 29 + x.len - 20 + x.len + x.len - 5);
376
377 try a.append(10);
378 try testing.expectEqual(a.pop(), 10);
379
380 try a.append(20);
381 const removed = a.orderedRemove(5);
382 try testing.expectEqual(removed, 1);
383 try testing.expectEqual(a.len, 34);
384
385 a.set(0, 0xdd);
386 a.set(a.len - 1, 0xee);
387 const swapped = a.swapRemove(0);
388 try testing.expectEqual(swapped, 0xdd);
389 try testing.expectEqual(a.get(0), 0xee);
390
391 const added_slice = try a.addManyAsSlice(3);
392 try testing.expectEqual(added_slice.len, 3);
393 try testing.expectEqual(a.len, 36);
394
395 while (a.pop()) |_| {}
396 const w = a.writer();
397 const s = "hello, this is a test string";
398 try w.writeAll(s);
399 try testing.expectEqualStrings(s, a.constSlice());
400}
401
402test "BoundedArrayAligned" {
403 var a = try BoundedArrayAligned(u8, .@"16", 4).init(0);
404 try a.append(0);
405 try a.append(0);
406 try a.append(255);
407 try a.append(255);
408
409 const b = @as(*const [2]u16, @ptrCast(a.constSlice().ptr));
410 try testing.expectEqual(@as(u16, 0), b[0]);
411 try testing.expectEqual(@as(u16, 65535), b[1]);
412}
lib/std/std.zig-2
...@@ -9,8 +9,6 @@ pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged;...@@ -9,8 +9,6 @@ pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged;
9pub const AutoHashMap = hash_map.AutoHashMap;9pub const AutoHashMap = hash_map.AutoHashMap;
10pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged;10pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged;
11pub const BitStack = @import("BitStack.zig");11pub const BitStack = @import("BitStack.zig");
12pub const BoundedArray = @import("bounded_array.zig").BoundedArray;
13pub const BoundedArrayAligned = @import("bounded_array.zig").BoundedArrayAligned;
14pub const Build = @import("Build.zig");12pub const Build = @import("Build.zig");
15pub const BufMap = @import("buf_map.zig").BufMap;13pub const BufMap = @import("buf_map.zig").BufMap;
16pub const BufSet = @import("buf_set.zig").BufSet;14pub const BufSet = @import("buf_set.zig").BufSet;
src/Compilation.zig+12-6
...@@ -2103,6 +2103,8 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -2103,6 +2103,8 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
2103 .local_zir_cache = local_zir_cache,2103 .local_zir_cache = local_zir_cache,
2104 .error_limit = error_limit,2104 .error_limit = error_limit,
2105 .llvm_object = null,2105 .llvm_object = null,
2106 .analysis_roots_buffer = undefined,
2107 .analysis_roots_len = 0,
2106 };2108 };
2107 try zcu.init(options.thread_pool.getIdCount());2109 try zcu.init(options.thread_pool.getIdCount());
2108 break :blk zcu;2110 break :blk zcu;
...@@ -2933,22 +2935,26 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {...@@ -2933,22 +2935,26 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
2933 try comp.appendFileSystemInput(embed_file.path);2935 try comp.appendFileSystemInput(embed_file.path);
2934 }2936 }
29352937
2936 zcu.analysis_roots.clear();2938 zcu.analysis_roots_len = 0;
29372939
2938 zcu.analysis_roots.appendAssumeCapacity(zcu.std_mod);2940 zcu.analysis_roots_buffer[zcu.analysis_roots_len] = zcu.std_mod;
2941 zcu.analysis_roots_len += 1;
29392942
2940 // Normally we rely on importing std to in turn import the root source file in the start code.2943 // Normally we rely on importing std to in turn import the root source file in the start code.
2941 // However, the main module is distinct from the root module in tests, so that won't happen there.2944 // However, the main module is distinct from the root module in tests, so that won't happen there.
2942 if (comp.config.is_test and zcu.main_mod != zcu.std_mod) {2945 if (comp.config.is_test and zcu.main_mod != zcu.std_mod) {
2943 zcu.analysis_roots.appendAssumeCapacity(zcu.main_mod);2946 zcu.analysis_roots_buffer[zcu.analysis_roots_len] = zcu.main_mod;
2947 zcu.analysis_roots_len += 1;
2944 }2948 }
29452949
2946 if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| {2950 if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| {
2947 zcu.analysis_roots.appendAssumeCapacity(compiler_rt_mod);2951 zcu.analysis_roots_buffer[zcu.analysis_roots_len] = compiler_rt_mod;
2952 zcu.analysis_roots_len += 1;
2948 }2953 }
29492954
2950 if (zcu.root_mod.deps.get("ubsan_rt")) |ubsan_rt_mod| {2955 if (zcu.root_mod.deps.get("ubsan_rt")) |ubsan_rt_mod| {
2951 zcu.analysis_roots.appendAssumeCapacity(ubsan_rt_mod);2956 zcu.analysis_roots_buffer[zcu.analysis_roots_len] = ubsan_rt_mod;
2957 zcu.analysis_roots_len += 1;
2952 }2958 }
2953 }2959 }
29542960
...@@ -4745,7 +4751,7 @@ fn performAllTheWork(...@@ -4745,7 +4751,7 @@ fn performAllTheWork(
4745 try zcu.flushRetryableFailures();4751 try zcu.flushRetryableFailures();
47464752
4747 // It's analysis time! Queue up our initial analysis.4753 // It's analysis time! Queue up our initial analysis.
4748 for (zcu.analysis_roots.slice()) |mod| {4754 for (zcu.analysisRoots()) |mod| {
4749 try comp.queueJob(.{ .analyze_mod = mod });4755 try comp.queueJob(.{ .analyze_mod = mod });
4750 }4756 }
47514757
src/Zcu.zig+8-3
...@@ -268,7 +268,8 @@ nav_val_analysis_queued: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, voi...@@ -268,7 +268,8 @@ nav_val_analysis_queued: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, voi
268268
269/// These are the modules which we initially queue for analysis in `Compilation.update`.269/// These are the modules which we initially queue for analysis in `Compilation.update`.
270/// `resolveReferences` will use these as the root of its reachability traversal.270/// `resolveReferences` will use these as the root of its reachability traversal.
271analysis_roots: std.BoundedArray(*Package.Module, 4) = .{},271analysis_roots_buffer: [4]*Package.Module,
272analysis_roots_len: usize = 0,
272/// This is the cached result of `Zcu.resolveReferences`. It is computed on-demand, and273/// This is the cached result of `Zcu.resolveReferences`. It is computed on-demand, and
273/// reset to `null` when any semantic analysis occurs (since this invalidates the data).274/// reset to `null` when any semantic analysis occurs (since this invalidates the data).
274/// Allocated into `gpa`.275/// Allocated into `gpa`.
...@@ -4013,8 +4014,8 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv...@@ -4013,8 +4014,8 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv
4013 // This is not a sufficient size, but a lower bound.4014 // This is not a sufficient size, but a lower bound.
4014 try result.ensureTotalCapacity(gpa, @intCast(zcu.reference_table.count()));4015 try result.ensureTotalCapacity(gpa, @intCast(zcu.reference_table.count()));
40154016
4016 try type_queue.ensureTotalCapacity(gpa, zcu.analysis_roots.len);4017 try type_queue.ensureTotalCapacity(gpa, zcu.analysis_roots_len);
4017 for (zcu.analysis_roots.slice()) |mod| {4018 for (zcu.analysisRoots()) |mod| {
4018 const file = zcu.module_roots.get(mod).?.unwrap() orelse continue;4019 const file = zcu.module_roots.get(mod).?.unwrap() orelse continue;
4019 const root_ty = zcu.fileRootType(file);4020 const root_ty = zcu.fileRootType(file);
4020 if (root_ty == .none) continue;4021 if (root_ty == .none) continue;
...@@ -4202,6 +4203,10 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv...@@ -4202,6 +4203,10 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv
4202 return result;4203 return result;
4203}4204}
42044205
4206pub fn analysisRoots(zcu: *Zcu) []*Package.Module {
4207 return zcu.analysis_roots_buffer[0..zcu.analysis_roots_len];
4208}
4209
4205pub fn fileByIndex(zcu: *const Zcu, file_index: File.Index) *File {4210pub fn fileByIndex(zcu: *const Zcu, file_index: File.Index) *File {
4206 return zcu.intern_pool.filePtr(file_index);4211 return zcu.intern_pool.filePtr(file_index);
4207}4212}
src/Zcu/PerThread.zig+3-2
...@@ -2116,8 +2116,9 @@ pub fn computeAliveFiles(pt: Zcu.PerThread) Allocator.Error!bool {...@@ -2116,8 +2116,9 @@ pub fn computeAliveFiles(pt: Zcu.PerThread) Allocator.Error!bool {
2116 // multi-threaded environment (where things like file indices could differ between compiler runs).2116 // multi-threaded environment (where things like file indices could differ between compiler runs).
21172117
2118 // The roots of our file liveness analysis will be the analysis roots.2118 // The roots of our file liveness analysis will be the analysis roots.
2119 try zcu.alive_files.ensureTotalCapacity(gpa, zcu.analysis_roots.len);2119 const analysis_roots = zcu.analysisRoots();
2120 for (zcu.analysis_roots.slice()) |mod| {2120 try zcu.alive_files.ensureTotalCapacity(gpa, analysis_roots.len);
2121 for (analysis_roots) |mod| {
2121 const file_index = zcu.module_roots.get(mod).?.unwrap() orelse continue;2122 const file_index = zcu.module_roots.get(mod).?.unwrap() orelse continue;
2122 const file = zcu.fileByIndex(file_index);2123 const file = zcu.fileByIndex(file_index);
21232124