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 {
88};
99
1010fn 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);
1214 var ip: usize = 0;
1315
1416 return vm: switch (code[ip]) {
1517 // Because all code after `continue` is unreachable, this branch does
1618 // not provide a result.
1719 .add => {
18 try stack.append(stack.pop().? + stack.pop().?);
20 try stack.appendBounded(stack.pop().? + stack.pop().?);
1921
2022 ip += 1;
2123 continue :vm code[ip];
2224 },
2325 .mul => {
24 try stack.append(stack.pop().? * stack.pop().?);
26 try stack.appendBounded(stack.pop().? * stack.pop().?);
2527
2628 ip += 1;
2729 continue :vm code[ip];
lib/docs/wasm/markdown/Parser.zig+40-26
......@@ -29,13 +29,14 @@ const Node = Document.Node;
2929const ExtraIndex = Document.ExtraIndex;
3030const ExtraData = Document.ExtraData;
3131const StringIndex = Document.StringIndex;
32const ArrayList = std.ArrayListUnmanaged;
3233
3334nodes: Node.List = .{},
34extra: std.ArrayListUnmanaged(u32) = .empty,
35scratch_extra: std.ArrayListUnmanaged(u32) = .empty,
36string_bytes: std.ArrayListUnmanaged(u8) = .empty,
37scratch_string: std.ArrayListUnmanaged(u8) = .empty,
38pending_blocks: std.ArrayListUnmanaged(Block) = .empty,
35extra: ArrayList(u32) = .empty,
36scratch_extra: ArrayList(u32) = .empty,
37string_bytes: ArrayList(u8) = .empty,
38scratch_string: ArrayList(u8) = .empty,
39pending_blocks: ArrayList(Block) = .empty,
3940allocator: Allocator,
4041
4142const Parser = @This();
......@@ -86,7 +87,8 @@ const Block = struct {
8687 continuation_indent: usize,
8788 },
8889 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,
9092 },
9193 heading: struct {
9294 /// Between 1 and 6, inclusive.
......@@ -354,7 +356,8 @@ const BlockStart = struct {
354356 continuation_indent: usize,
355357 },
356358 table_row: struct {
357 cells: std.BoundedArray([]const u8, max_table_columns),
359 cells_buffer: [max_table_columns][]const u8,
360 cells_len: usize,
358361 },
359362 heading: struct {
360363 /// Between 1 and 6, inclusive.
......@@ -422,7 +425,8 @@ fn appendBlockStart(p: *Parser, block_start: BlockStart) !void {
422425 try p.pending_blocks.append(p.allocator, .{
423426 .tag = .table,
424427 .data = .{ .table = .{
425 .column_alignments = .{},
428 .column_alignments_buffer = undefined,
429 .column_alignments_len = 0,
426430 } },
427431 .string_start = p.scratch_string.items.len,
428432 .extra_start = p.scratch_extra.items.len,
......@@ -431,15 +435,19 @@ fn appendBlockStart(p: *Parser, block_start: BlockStart) !void {
431435
432436 const current_row = p.scratch_extra.items.len - p.pending_blocks.getLast().extra_start;
433437 if (current_row <= 1) {
434 if (parseTableHeaderDelimiter(block_start.data.table_row.cells)) |alignments| {
435 p.pending_blocks.items[p.pending_blocks.items.len - 1].data.table.column_alignments = alignments;
438 var buffer: [max_table_columns]Node.TableCellAlignment = undefined;
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;
436444 if (current_row == 1) {
437445 // We need to go back and mark the header row and its column
438446 // alignments.
439447 const datas = p.nodes.items(.data);
440448 const header_data = datas[p.scratch_extra.getLast()];
441449 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;
443451 const cell_data = &datas[@intFromEnum(header_cell)].table_cell;
444452 cell_data.info.alignment = alignment;
445453 cell_data.info.header = true;
......@@ -480,8 +488,10 @@ fn appendBlockStart(p: *Parser, block_start: BlockStart) !void {
480488 // available in the BlockStart. We can immediately parse and append
481489 // these children now.
482490 const containing_table = p.pending_blocks.items[p.pending_blocks.items.len - 2];
483 const column_alignments = containing_table.data.table.column_alignments.slice();
484 for (block_start.data.table_row.cells.slice(), 0..) |cell_content, i| {
491 const table = &containing_table.data.table;
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| {
485495 const cell_children = try p.parseInlines(cell_content);
486496 const alignment = if (i < column_alignments.len) column_alignments[i] else .unset;
487497 const cell = try p.addNode(.{
......@@ -523,7 +533,8 @@ fn startBlock(p: *Parser, line: []const u8) !?BlockStart {
523533 return .{
524534 .tag = .table_row,
525535 .data = .{ .table_row = .{
526 .cells = table_row.cells,
536 .cells_buffer = table_row.cells_buffer,
537 .cells_len = table_row.cells_len,
527538 } },
528539 .rest = "",
529540 };
......@@ -606,7 +617,8 @@ fn startListItem(unindented_line: []const u8) ?ListItemStart {
606617}
607618
608619const TableRowStart = struct {
609 cells: std.BoundedArray([]const u8, max_table_columns),
620 cells_buffer: [max_table_columns][]const u8,
621 cells_len: usize,
610622};
611623
612624fn startTableRow(unindented_line: []const u8) ?TableRowStart {
......@@ -615,7 +627,8 @@ fn startTableRow(unindented_line: []const u8) ?TableRowStart {
615627 mem.endsWith(u8, unindented_line, "\\|") or
616628 !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);
619632 const table_row_content = unindented_line[1 .. unindented_line.len - 1];
620633 var cell_start: usize = 0;
621634 var i: usize = 0;
......@@ -623,7 +636,7 @@ fn startTableRow(unindented_line: []const u8) ?TableRowStart {
623636 switch (table_row_content[i]) {
624637 '\\' => i += 1,
625638 '|' => {
626 cells.append(table_row_content[cell_start..i]) catch return null;
639 cells.appendBounded(table_row_content[cell_start..i]) catch return null;
627640 cell_start = i + 1;
628641 },
629642 '`' => {
......@@ -641,20 +654,21 @@ fn startTableRow(unindented_line: []const u8) ?TableRowStart {
641654 else => {},
642655 }
643656 }
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 };
647660}
648661
649662fn parseTableHeaderDelimiter(
650 row_cells: std.BoundedArray([]const u8, max_table_columns),
651) ?std.BoundedArray(Node.TableCellAlignment, max_table_columns) {
652 var alignments: std.BoundedArray(Node.TableCellAlignment, max_table_columns) = .{};
653 for (row_cells.slice()) |content| {
663 row_cells: []const []const u8,
664 buffer: []Node.TableCellAlignment,
665) ?[]Node.TableCellAlignment {
666 var alignments: ArrayList(Node.TableCellAlignment) = .initBuffer(buffer);
667 for (row_cells) |content| {
654668 const alignment = parseTableHeaderDelimiterCell(content) orelse return null;
655669 alignments.appendAssumeCapacity(alignment);
656670 }
657 return alignments;
671 return alignments.items;
658672}
659673
660674fn parseTableHeaderDelimiterCell(content: []const u8) ?Node.TableCellAlignment {
......@@ -928,8 +942,8 @@ const InlineParser = struct {
928942 parent: *Parser,
929943 content: []const u8,
930944 pos: usize = 0,
931 pending_inlines: std.ArrayListUnmanaged(PendingInline) = .empty,
932 completed_inlines: std.ArrayListUnmanaged(CompletedInline) = .empty,
945 pending_inlines: ArrayList(PendingInline) = .empty,
946 completed_inlines: ArrayList(CompletedInline) = .empty,
933947
934948 const PendingInline = struct {
935949 tag: Tag,
lib/std/Io.zig-15
......@@ -231,21 +231,6 @@ pub fn GenericReader(
231231 return @errorCast(self.any().readBytesNoEof(num_bytes));
232232 }
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
249234 pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) NoEofError!T {
250235 return @errorCast(self.any().readInt(T, endian));
251236 }
lib/std/Io/DeprecatedReader.zig-27
......@@ -249,33 +249,6 @@ pub fn readBytesNoEof(self: Self, comptime num_bytes: usize) anyerror![num_bytes
249249 return bytes;
250250}
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
279252pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T {
280253 const bytes = try self.readBytesNoEof(@divExact(@typeInfo(T).int.bits, 8));
281254 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" {
349349
350350 try std.testing.expectError(error.StreamTooLong, reader.streamUntilDelimiter(writer, '!', 5));
351351}
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
657657
658658 /// Initialize with externally-managed memory. The buffer determines the
659659 /// capacity, and the length is set to zero.
660 ///
660661 /// When initialized this way, all functions that accept an Allocator
661662 /// argument cause illegal behavior.
662663 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: [
517517 var buffer: [0x100]u8 = undefined;
518518 const encoded = codecs.Encoder.encode(&buffer, expected_decoded);
519519 try testing.expectEqualSlices(u8, expected_encoded, encoded);
520
520 }
521 {
521522 // stream encode
522 var list = try std.BoundedArray(u8, 0x100).init(0);
523 try codecs.Encoder.encodeWriter(list.writer(), expected_decoded);
524 try testing.expectEqualSlices(u8, expected_encoded, list.slice());
525
523 var buffer: [0x100]u8 = undefined;
524 var writer: std.Io.Writer = .fixed(&buffer);
525 try codecs.Encoder.encodeWriter(&writer, expected_decoded);
526 try testing.expectEqualSlices(u8, expected_encoded, writer.buffered());
527 }
528 {
526529 // reader to writer encode
527 var stream = std.io.fixedBufferStream(expected_decoded);
528 list = try std.BoundedArray(u8, 0x100).init(0);
529 try codecs.Encoder.encodeFromReaderToWriter(list.writer(), stream.reader());
530 try testing.expectEqualSlices(u8, expected_encoded, list.slice());
530 var stream: std.Io.Reader = .fixed(expected_decoded);
531 var buffer: [0x100]u8 = undefined;
532 var writer: std.Io.Writer = .fixed(&buffer);
533 try codecs.Encoder.encodeFromReaderToWriter(&writer, &stream);
534 try testing.expectEqualSlices(u8, expected_encoded, writer.buffered());
531535 }
532536
533537 // 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;
99pub const AutoHashMap = hash_map.AutoHashMap;
1010pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged;
1111pub const BitStack = @import("BitStack.zig");
12pub const BoundedArray = @import("bounded_array.zig").BoundedArray;
13pub const BoundedArrayAligned = @import("bounded_array.zig").BoundedArrayAligned;
1412pub const Build = @import("Build.zig");
1513pub const BufMap = @import("buf_map.zig").BufMap;
1614pub 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
21032103 .local_zir_cache = local_zir_cache,
21042104 .error_limit = error_limit,
21052105 .llvm_object = null,
2106 .analysis_roots_buffer = undefined,
2107 .analysis_roots_len = 0,
21062108 };
21072109 try zcu.init(options.thread_pool.getIdCount());
21082110 break :blk zcu;
......@@ -2933,22 +2935,26 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
29332935 try comp.appendFileSystemInput(embed_file.path);
29342936 }
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
29402943 // Normally we rely on importing std to in turn import the root source file in the start code.
29412944 // However, the main module is distinct from the root module in tests, so that won't happen there.
29422945 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;
29442948 }
29452949
29462950 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;
29482953 }
29492954
29502955 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;
29522958 }
29532959 }
29542960
......@@ -4745,7 +4751,7 @@ fn performAllTheWork(
47454751 try zcu.flushRetryableFailures();
47464752
47474753 // It's analysis time! Queue up our initial analysis.
4748 for (zcu.analysis_roots.slice()) |mod| {
4754 for (zcu.analysisRoots()) |mod| {
47494755 try comp.queueJob(.{ .analyze_mod = mod });
47504756 }
47514757
src/Zcu.zig+8-3
......@@ -268,7 +268,8 @@ nav_val_analysis_queued: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, voi
268268
269269/// These are the modules which we initially queue for analysis in `Compilation.update`.
270270/// `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,
272273/// This is the cached result of `Zcu.resolveReferences`. It is computed on-demand, and
273274/// reset to `null` when any semantic analysis occurs (since this invalidates the data).
274275/// Allocated into `gpa`.
......@@ -4013,8 +4014,8 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv
40134014 // This is not a sufficient size, but a lower bound.
40144015 try result.ensureTotalCapacity(gpa, @intCast(zcu.reference_table.count()));
40154016
4016 try type_queue.ensureTotalCapacity(gpa, zcu.analysis_roots.len);
4017 for (zcu.analysis_roots.slice()) |mod| {
4017 try type_queue.ensureTotalCapacity(gpa, zcu.analysis_roots_len);
4018 for (zcu.analysisRoots()) |mod| {
40184019 const file = zcu.module_roots.get(mod).?.unwrap() orelse continue;
40194020 const root_ty = zcu.fileRootType(file);
40204021 if (root_ty == .none) continue;
......@@ -4202,6 +4203,10 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv
42024203 return result;
42034204}
42044205
4206pub fn analysisRoots(zcu: *Zcu) []*Package.Module {
4207 return zcu.analysis_roots_buffer[0..zcu.analysis_roots_len];
4208}
4209
42054210pub fn fileByIndex(zcu: *const Zcu, file_index: File.Index) *File {
42064211 return zcu.intern_pool.filePtr(file_index);
42074212}
src/Zcu/PerThread.zig+3-2
......@@ -2116,8 +2116,9 @@ pub fn computeAliveFiles(pt: Zcu.PerThread) Allocator.Error!bool {
21162116 // multi-threaded environment (where things like file indices could differ between compiler runs).
21172117
21182118 // The roots of our file liveness analysis will be the analysis roots.
2119 try zcu.alive_files.ensureTotalCapacity(gpa, zcu.analysis_roots.len);
2120 for (zcu.analysis_roots.slice()) |mod| {
2119 const analysis_roots = zcu.analysisRoots();
2120 try zcu.alive_files.ensureTotalCapacity(gpa, analysis_roots.len);
2121 for (analysis_roots) |mod| {
21212122 const file_index = zcu.module_roots.get(mod).?.unwrap() orelse continue;
21222123 const file = zcu.fileByIndex(file_index);
21232124