diff --git a/lib/compiler/aro/aro/Compilation.zig b/lib/compiler/aro/aro/Compilation.zig index d25154248e48cb68a2a07cca639d13942c61ec81..932b09a088a70e91a7838d5e75ad3be0ba174833 100644 --- a/lib/compiler/aro/aro/Compilation.zig +++ b/lib/compiler/aro/aro/Compilation.zig @@ -1601,6 +1601,7 @@ pub fn addSourceFromOwnedBuffer(comp: *Compilation, path: []const u8, buf: []u8, var list: std.ArrayList(u8) = .{ .items = contents[0..i], .capacity = contents.len, + .pointer_stability = .{}, }; contents = try list.toOwnedSlice(comp.gpa); } diff --git a/lib/std/Io/Writer.zig b/lib/std/Io/Writer.zig index b2d8f3767d4b585071e9f578de433e9406c071fe..17cff637912fbab283e38ac9e4022c9b1c84e918 100644 --- a/lib/std/Io/Writer.zig +++ b/lib/std/Io/Writer.zig @@ -2475,6 +2475,7 @@ pub fn unreachableRebase(w: *Writer, preserve: usize, capacity: usize) Error!voi pub fn fromArrayList(array_list: *ArrayList(u8)) Writer { defer array_list.* = .empty; + array_list.pointer_stability.assertUnlocked(); return .{ .vtable = &.{ .drain = fixedDrain, @@ -2490,6 +2491,7 @@ pub fn toArrayList(w: *Writer) ArrayList(u8) { const result: ArrayList(u8) = .{ .items = w.buffer[0..w.end], .capacity = w.buffer.len, + .pointer_stability = .{}, }; w.buffer = &.{}; w.end = 0; @@ -2739,6 +2741,7 @@ pub const Allocating = struct { const result: std.array_list.Aligned(u8, alignment) = .{ .items = @alignCast(w.buffer[0..w.end]), .capacity = w.buffer.len, + .pointer_stability = .{}, }; w.buffer = &.{}; w.end = 0; diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index 6e9a3d528660db3d2c202ef84146f46db32ee444..58058b285af276504fe0212080fd0983c290e50f 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -26,14 +26,17 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type /// /// Pointers to elements in this slice are invalidated by various /// functions of this ArrayList in accordance with the respective - /// documentation. In all cases, "invalidated" means that the memory - /// has been passed to this allocator's resize or free function. + /// documentation. + /// An invalidated pointer may point either to valid or freed memory. items: Slice, /// How many T values this list can hold without allocating /// additional memory. capacity: usize, allocator: Allocator, + /// Used to detect memory safety violations. + pointer_stability: debug.SafetyLock, + pub const Slice = if (alignment) |a| ([]align(a.toByteUnits()) T) else []T; pub fn SentinelSlice(comptime s: T) type { @@ -46,6 +49,7 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type .items = &[_]T{}, .capacity = 0, .allocator = gpa, + .pointer_stability = .{}, }; } @@ -60,11 +64,29 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type /// Release all allocated memory. pub fn deinit(self: Self) void { + self.pointer_stability.assertUnlocked(); if (@sizeOf(T) > 0) { self.allocator.free(self.allocatedSlice()); } } + /// Puts the array list into a state where any method call that would + /// cause an existing value pointer to become invalidated will + /// instead trigger an assertion. + /// + /// An additional call to `lockPointers` in such state also triggers an + /// assertion. + /// + /// `unlockPointers` returns the array list to the previous state. + pub fn lockPointers(self: *Self) void { + self.pointer_stability.lock(); + } + + /// Undoes a call to `lockPointers`. + pub fn unlockPointers(self: *Self) void { + self.pointer_stability.unlock(); + } + /// ArrayList takes ownership of the passed in slice. The slice must have been /// allocated with `gpa`. /// Deinitialize with `deinit` or use `toOwnedSlice`. @@ -73,6 +95,7 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type .items = slice, .capacity = slice.len, .allocator = gpa, + .pointer_stability = .{}, }; } @@ -84,6 +107,7 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type .items = slice, .capacity = slice.len + 1, .allocator = gpa, + .pointer_stability = .{}, }; } @@ -91,14 +115,20 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type /// of this ArrayList. Empties this ArrayList. pub fn moveToUnmanaged(self: *Self) Aligned(T, alignment) { const allocator = self.allocator; - const result: Aligned(T, alignment) = .{ .items = self.items, .capacity = self.capacity }; + const result: Aligned(T, alignment) = .{ + .items = self.items, + .capacity = self.capacity, + .pointer_stability = self.pointer_stability, + }; self.* = init(allocator); return result; } /// The caller owns the returned memory. Empties this ArrayList. /// Its capacity is cleared, making `deinit` safe but unnecessary to call. + /// May invalidate element pointers if remapping memory cannot be done in place. pub fn toOwnedSlice(self: *Self) Allocator.Error!Slice { + self.pointer_stability.assertUnlocked(); const allocator = self.allocator; const old_memory = self.allocatedSlice(); @@ -114,6 +144,7 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type } /// The caller owns the returned memory. Empties this ArrayList. + /// May invalidate element pointers if remapping memory cannot be done in place. pub fn toOwnedSliceSentinel(self: *Self, comptime sentinel: T) Allocator.Error!SentinelSlice(sentinel) { // This addition can never overflow because `self.items` can never occupy the whole address space try self.ensureTotalCapacityPrecise(self.items.len + 1); @@ -129,28 +160,31 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type return cloned; } - /// Insert `item` at index `i`. Moves `list[i .. list.len]` to higher indices to make room. - /// If `i` is equal to the length of the list this operation is equivalent to append. + /// Insert `item` at index `index`. Moves `list[index .. list.len]` to higher indices to make room. + /// If `index` is equal to the length of the list this operation is equivalent to append. /// This operation is O(N). /// Invalidates element pointers if additional memory is needed. + /// Invalidates pre-existing pointers to elements at and after `index`. /// Asserts that the index is in bounds or equal to the length. - pub fn insert(self: *Self, i: usize, item: T) Allocator.Error!void { - const dst = try self.addManyAt(i, 1); + pub fn insert(self: *Self, index: usize, item: T) Allocator.Error!void { + self.pointer_stability.assertUnlocked(); + const dst = try self.addManyAt(index, 1); dst[0] = item; } - /// Insert `item` at index `i`. Moves `list[i .. list.len]` to higher indices to make room. - /// If `i` is equal to the length of the list this operation is + /// Insert `item` at index `index`. Moves `list[index .. list.len]` to higher indices to make room. + /// If `index` is equal to the length of the list this operation is /// equivalent to appendAssumeCapacity. /// This operation is O(N). + /// Invalidates pre-existing pointers to elements at and after `index`. /// Asserts that there is enough capacity for the new item. /// Asserts that the index is in bounds or equal to the length. - pub fn insertAssumeCapacity(self: *Self, i: usize, item: T) void { + pub fn insertAssumeCapacity(self: *Self, index: usize, item: T) void { + self.pointer_stability.assertUnlocked(); assert(self.items.len < self.capacity); self.items.len += 1; - - @memmove(self.items[i + 1 .. self.items.len], self.items[i .. self.items.len - 1]); - self.items[i] = item; + @memmove(self.items[index + 1 .. self.items.len], self.items[index .. self.items.len - 1]); + self.items[index] = item; } /// Add `count` new elements at position `index`, which have @@ -163,6 +197,7 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type /// Asserts that the index is in bounds or equal to the length. pub fn addManyAt(self: *Self, index: usize, count: usize) Allocator.Error![]T { const new_len = try addOrOom(self.items.len, count); + self.pointer_stability.assertUnlocked(); if (self.capacity >= new_len) return addManyAtAssumeCapacity(self, index, count); @@ -198,11 +233,11 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type /// `undefined` values. Returns a slice pointing to the newly allocated /// elements, which becomes invalid after various `ArrayList` /// operations. + /// Invalidates pre-existing pointers to elements at and after `index`. /// Asserts that there is enough capacity for the new elements. - /// Invalidates pre-existing pointers to elements at and after `index`, but - /// does not invalidate any before that. /// Asserts that the index is in bounds or equal to the length. pub fn addManyAtAssumeCapacity(self: *Self, index: usize, count: usize) []T { + self.pointer_stability.assertUnlocked(); const new_len = self.items.len + count; assert(self.capacity >= new_len); const to_move = self.items[index..]; @@ -213,7 +248,7 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type return result; } - /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room. + /// Insert slice `items` at index `index` by moving `list[index .. list.len]` to make room. /// This operation is O(N). /// Invalidates pre-existing pointers to elements at and after `index`. /// Invalidates all pre-existing element pointers if capacity must be @@ -229,7 +264,9 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type } /// Grows or shrinks the list as necessary. - /// Invalidates element pointers if additional capacity is allocated. + /// Invalidates element pointers if additional capacity is allocated, + /// Invalidates pointers to elements at and above index `start + len` + /// when `len` and `new_items.len` are unequal. /// Asserts that the range is in bounds. pub fn replaceRange(self: *Self, start: usize, len: usize, new_items: []const T) Allocator.Error!void { var unmanaged = self.moveToUnmanaged(); @@ -238,7 +275,8 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type } /// Grows or shrinks the list as necessary. - /// Never invalidates element pointers. + /// Invalidates pointers to elements at and above index `start + len` + /// when `len` and `new_items.len` are unequal. /// Asserts the capacity is enough for additional items. pub fn replaceRangeAssumeCapacity(self: *Self, start: usize, len: usize, new_items: []const T) void { var unmanaged = self.moveToUnmanaged(); @@ -275,10 +313,12 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type /// Removes the element at the specified index and returns it. /// The empty slot is filled from the end of the list. + /// Invalidates pointers to the end of the list. /// This operation is O(1). /// This may not preserve item order. Use `orderedRemove` if you need to preserve order. /// Asserts that the index is in bounds. pub fn swapRemove(self: *Self, i: usize) T { + self.pointer_stability.assertUnlocked(); const val = self.items[i]; self.items[i] = self.items[self.items.len - 1]; self.items[self.items.len - 1] = undefined; @@ -328,6 +368,8 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type @memcpy(self.items[old_len..][0..items.len], items); } + /// Prints a formatted string into this list. + /// Invalidates element pointers if additional memory is needed. pub fn print(self: *Self, comptime fmt: []const u8, args: anytype) error{OutOfMemory}!void { const gpa = self.allocator; var unmanaged = self.moveToUnmanaged(); @@ -379,6 +421,7 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type /// Invalidates element pointers for the elements `items[new_len..]`. /// Asserts that the new length is less than or equal to the previous length. pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void { + self.pointer_stability.assertUnlocked(); assert(new_len <= self.items.len); @memset(self.items[new_len..], undefined); self.items.len = new_len; @@ -387,12 +430,14 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type /// Reduce length to 0. /// Invalidates all element pointers. pub fn clearRetainingCapacity(self: *Self) void { + self.pointer_stability.assertUnlocked(); @memset(self.items, undefined); self.items.len = 0; } /// Invalidates all element pointers. pub fn clearAndFree(self: *Self) void { + self.pointer_stability.assertUnlocked(); self.allocator.free(self.allocatedSlice()); self.items.len = 0; self.capacity = 0; @@ -424,9 +469,9 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type } if (self.capacity >= new_capacity) return; - + self.pointer_stability.assertUnlocked(); // Here we avoid copying allocated but unused bytes by - // attempting a resize in place, and falling back to allocating + // attempting a remap, and falling back to allocating // a new buffer and doing our own copy. With a realloc() call, // the allocator implementation would pointlessly copy our // extra capacity. @@ -457,7 +502,8 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type } /// Increase length by 1, returning pointer to the new item. - /// The returned pointer becomes invalid when the list resized. + /// Invalidates element pointers if additional memory is needed. + /// The returned pointer may be invalidated by further operations to this list. pub fn addOne(self: *Self) Allocator.Error!*T { // This can never overflow because `self.items` can never occupy the whole address space const newlen = self.items.len + 1; @@ -466,7 +512,7 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type } /// Increase length by 1, returning pointer to the new item. - /// The returned pointer becomes invalid when the list is resized. + /// The returned pointer may be invalidated by further operations to this list. /// Never invalidates element pointers. /// Asserts that the list can hold one additional item. pub fn addOneAssumeCapacity(self: *Self) *T { @@ -477,8 +523,9 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type /// Resize the array, adding `n` new elements, which have `undefined` values. /// The return value is an array pointing to the newly allocated elements. - /// The returned pointer becomes invalid when the list is resized. + /// The returned pointer may be invalidated by further operations to this list. /// Resizes list if `self.capacity` is not large enough. + /// Invalidates element pointers if additional memory is needed. pub fn addManyAsArray(self: *Self, comptime n: usize) Allocator.Error!*[n]T { const prev_len = self.items.len; try self.resize(try addOrOom(self.items.len, n)); @@ -488,7 +535,7 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type /// Resize the array, adding `n` new elements, which have `undefined` values. /// The return value is an array pointing to the newly allocated elements. /// Never invalidates element pointers. - /// The returned pointer becomes invalid when the list is resized. + /// The returned pointer may be invalidated by further operations to this list. /// Asserts that the list can hold the additional items. pub fn addManyAsArrayAssumeCapacity(self: *Self, comptime n: usize) *[n]T { assert(self.items.len + n <= self.capacity); @@ -499,8 +546,9 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type /// Resize the array, adding `n` new elements, which have `undefined` values. /// The return value is a slice pointing to the newly allocated elements. - /// The returned pointer becomes invalid when the list is resized. + /// The returned pointer may be invalidated by further operations to this list. /// Resizes list if `self.capacity` is not large enough. + /// Invalidates element pointers if additional memory is needed. pub fn addManyAsSlice(self: *Self, n: usize) Allocator.Error![]T { const prev_len = self.items.len; try self.resize(try addOrOom(self.items.len, n)); @@ -510,7 +558,7 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type /// Resize the array, adding `n` new elements, which have `undefined` values. /// The return value is a slice pointing to the newly allocated elements. /// Never invalidates element pointers. - /// The returned pointer becomes invalid when the list is resized. + /// The returned pointer may be invalidated by further operations to this list. /// Asserts that the list can hold the additional items. pub fn addManyAsSliceAssumeCapacity(self: *Self, n: usize) []T { assert(self.items.len + n <= self.capacity); @@ -520,9 +568,10 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type } /// Remove and return the last element from the list, or return `null` if list is empty. - /// Invalidates element pointers to the removed element, if any. + /// Invalidates element pointers to the removed element. pub fn pop(self: *Self) ?T { if (self.items.len == 0) return null; + self.pointer_stability.assertUnlocked(); const val = self.items[self.items.len - 1]; self.items[self.items.len - 1] = undefined; self.items.len -= 1; @@ -531,6 +580,7 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type /// Returns a slice of all the items plus the extra capacity, whose memory /// contents are `undefined`. + /// The returned pointer may be invalidated by further operations to this list. pub fn allocatedSlice(self: Self) Slice { // `items.len` is the length, not the capacity. return self.items.ptr[0..self.capacity]; @@ -540,6 +590,7 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type /// This can be useful for writing directly into an ArrayList. /// Note that such an operation must be followed up with a direct /// modification of `self.items.len`. + /// The returned pointer may be invalidated by further operations to this list. pub fn unusedCapacitySlice(self: Self) []T { return self.allocatedSlice()[self.items.len..]; } @@ -554,6 +605,7 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type /// Returns the last element from the list, or `null` if the list is /// empty. + /// Never invalidates element pointers. pub fn last(self: Self) ?T { if (self.items.len == 0) return null; return self.items[self.items.len - 1]; @@ -561,6 +613,7 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type /// Returns a pointer to the last element from the list, or `null` if /// the list is empty. + /// The returned pointer may be invalidated by further operations to this list. pub fn lastPtr(self: Self) ?*T { if (self.items.len == 0) return null; return &self.items[self.items.len - 1]; @@ -590,17 +643,21 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// /// Pointers to elements in this slice are invalidated by various /// functions of this ArrayList in accordance with the respective - /// documentation. In all cases, "invalidated" means that the memory - /// has been passed to an allocator's resize or free function. + /// documentation. + /// An invalidated pointer may point either to valid or freed memory. items: Slice, /// How many T values this list can hold without allocating /// additional memory. capacity: usize, + /// Used to detect memory safety violations. + pointer_stability: debug.SafetyLock, + /// An ArrayList containing no elements. pub const empty: Self = .{ .items = &.{}, .capacity = 0, + .pointer_stability = .{}, }; pub const Slice = if (alignment) |a| ([]align(a.toByteUnits()) T) else []T; @@ -626,19 +683,43 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { return .{ .items = buffer[0..0], .capacity = buffer.len, + .pointer_stability = .{}, }; } /// Release all allocated memory. pub fn deinit(self: *Self, gpa: Allocator) void { + self.pointer_stability.assertUnlocked(); gpa.free(self.allocatedSlice()); self.* = undefined; } + /// Puts the unmanaged array list into a state where any method call that would + /// cause an existing value pointer to become invalidated will + /// instead trigger an assertion. + /// + /// An additional call to `lockPointers` in such state also triggers an + /// assertion. + /// + /// `unlockPointers` returns the unmanaged array list to the previous state. + pub fn lockPointers(self: *Self) void { + self.pointer_stability.lock(); + } + + /// Undoes a call to `lockPointers`. + pub fn unlockPointers(self: *Self) void { + self.pointer_stability.unlock(); + } + /// Convert this list into an analogous memory-managed one. /// The returned list has ownership of the underlying memory. pub fn toManaged(self: *Self, gpa: Allocator) AlignedManaged(T, alignment) { - return .{ .items = self.items, .capacity = self.capacity, .allocator = gpa }; + return .{ + .items = self.items, + .capacity = self.capacity, + .allocator = gpa, + .pointer_stability = self.pointer_stability, + }; } /// ArrayList takes ownership of the passed in slice. @@ -647,6 +728,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { return Self{ .items = slice, .capacity = slice.len, + .pointer_stability = .{}, }; } @@ -656,13 +738,16 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { return Self{ .items = slice, .capacity = slice.len + 1, + .pointer_stability = .{}, }; } /// The caller owns the returned memory. Empties this ArrayList. /// Its capacity is cleared, making deinit() safe but unnecessary to call. + /// May invalidate element pointers. pub fn toOwnedSlice(self: *Self, gpa: Allocator) Allocator.Error!Slice { const old_memory = self.allocatedSlice(); + self.pointer_stability.assertUnlocked(); if (gpa.remap(old_memory, self.items.len)) |new_items| { self.* = .empty; return new_items; @@ -675,7 +760,9 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { } /// The caller owns the returned memory. ArrayList becomes empty. + /// May invalidate element pointers. pub fn toOwnedSliceSentinel(self: *Self, gpa: Allocator, comptime sentinel: T) Allocator.Error!SentinelSlice(sentinel) { + self.pointer_stability.assertUnlocked(); // This addition can never overflow because `self.items` can never occupy the whole address space. try self.ensureTotalCapacityPrecise(gpa, self.items.len + 1); self.appendAssumeCapacity(sentinel); @@ -688,6 +775,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// Its capacity is cleared, making deinit() safe but unnecessary to call. /// /// Asserts what the capacity is equal to the length. + /// Never invalidates element pointers. pub fn toOwnedSliceAssert(self: *Self) Slice { assert(self.items.len == self.capacity); const items = self.items; @@ -697,6 +785,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// The caller owns the returned memory. ArrayList becomes empty. /// Asserts what the capacity is equal to the length + 1. + /// Never invalidates element pointers. pub fn toOwnedSliceSentinelAssert(self: *Self, comptime sentinel: T) SentinelSlice(sentinel) { std.debug.assert(self.items.len + 1 == self.capacity); self.appendAssumeCapacity(sentinel); @@ -711,43 +800,41 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { return cloned; } - /// Insert `item` at index `i`. Moves `list[i .. list.len]` to higher indices to make room. - /// If `i` is equal to the length of the list this operation is equivalent to append. + /// Insert `item` at index `index`. Moves `list[index .. list.len]` to higher indices to make room. + /// If `index` is equal to the length of the list this operation is equivalent to append. /// This operation is O(N). /// Invalidates element pointers if additional memory is needed. + /// Invalidates pre-existing pointers to elements at and after `index`. /// Asserts that the index is in bounds or equal to the length. - pub fn insert(self: *Self, gpa: Allocator, i: usize, item: T) Allocator.Error!void { - const dst = try self.addManyAt(gpa, i, 1); + pub fn insert(self: *Self, gpa: Allocator, index: usize, item: T) Allocator.Error!void { + self.pointer_stability.assertUnlocked(); + const dst = try self.addManyAt(gpa, index, 1); dst[0] = item; } - /// Insert `item` at index `i`. Moves `list[i .. list.len]` to higher indices to make room. - /// - /// If `i` is equal to the length of the list this operation is equivalent to append. - /// + /// Insert `item` at index `index`. Moves `list[index .. list.len]` to higher indices to make room. + /// If `index` is equal to the length of the list this operation is + /// equivalent to appendAssumeCapacity. /// This operation is O(N). - /// + /// Invalidates pre-existing pointers to elements at and after `index`. /// Asserts that the list has capacity for one additional item. - /// /// Asserts that the index is in bounds or equal to the length. - pub fn insertAssumeCapacity(self: *Self, i: usize, item: T) void { + pub fn insertAssumeCapacity(self: *Self, index: usize, item: T) void { + self.pointer_stability.assertUnlocked(); assert(self.items.len < self.capacity); self.items.len += 1; - - @memmove(self.items[i + 1 .. self.items.len], self.items[i .. self.items.len - 1]); - self.items[i] = item; + @memmove(self.items[index + 1 .. self.items.len], self.items[index .. self.items.len - 1]); + self.items[index] = item; } - /// Insert `item` at index `i`, moving `list[i .. list.len]` to higher indices to make room. - /// - /// If `i` is equal to the length of the list this operation is equivalent to append. - /// + /// Insert `item` at index `index`. Moves `list[index .. list.len]` to higher indices to make room. + /// If `index` is equal to the length of the list this operation is + /// equivalent to appendAssumeCapacity. /// This operation is O(N). - /// + /// Invalidates pre-existing pointers to elements at and after `index`. + /// Asserts that the index is in bounds or equal to the length. /// If the list lacks unused capacity for the additional item, returns /// `error.OutOfMemory`. - /// - /// Asserts that the index is in bounds or equal to the length. pub fn insertBounded(self: *Self, i: usize, item: T) error{OutOfMemory}!void { if (self.capacity - self.items.len == 0) return error.OutOfMemory; return insertAssumeCapacity(self, i, item); @@ -767,20 +854,48 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { index: usize, count: usize, ) Allocator.Error![]T { - var managed = self.toManaged(gpa); - defer self.* = managed.moveToUnmanaged(); - return managed.addManyAt(index, count); + const new_len = try addOrOom(self.items.len, count); + self.pointer_stability.assertUnlocked(); + + if (self.capacity >= new_len) + return addManyAtAssumeCapacity(self, index, count); + + // Here we avoid copying allocated but unused bytes by + // attempting a resize in place, and falling back to allocating + // a new buffer and doing our own copy. With a realloc() call, + // the allocator implementation would pointlessly copy our + // extra capacity. + const new_capacity = Aligned(T, alignment).growCapacity(new_len); + const old_memory = self.allocatedSlice(); + if (gpa.remap(old_memory, new_capacity)) |new_memory| { + self.items.ptr = new_memory.ptr; + self.capacity = new_memory.len; + return addManyAtAssumeCapacity(self, index, count); + } + + // Make a new allocation, avoiding `ensureTotalCapacity` in order + // to avoid extra memory copies. + const new_memory = try gpa.alignedAlloc(T, alignment, new_capacity); + const to_move = self.items[index..]; + @memcpy(new_memory[0..index], self.items[0..index]); + @memcpy(new_memory[index + count ..][0..to_move.len], to_move); + gpa.free(old_memory); + self.items = new_memory[0..new_len]; + self.capacity = new_memory.len; + // The inserted elements at `new_memory[index..][0..count]` have + // already been set to `undefined` by memory allocation. + return new_memory[index..][0..count]; } /// Add `count` new elements at position `index`, which have /// `undefined` values. Returns a slice pointing to the newly allocated /// elements, which becomes invalid after various `ArrayList` /// operations. - /// Invalidates pre-existing pointers to elements at and after `index`, but - /// does not invalidate any before that. + /// Invalidates pre-existing pointers to elements at and after `index`. /// Asserts that the list has capacity for the additional items. /// Asserts that the index is in bounds or equal to the length. pub fn addManyAtAssumeCapacity(self: *Self, index: usize, count: usize) []T { + self.pointer_stability.assertUnlocked(); const new_len = self.items.len + count; assert(self.capacity >= new_len); const to_move = self.items[index..]; @@ -795,20 +910,16 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// `undefined` values, returning a slice pointing to the newly /// allocated elements, which becomes invalid after various `ArrayList` /// operations. - /// - /// Invalidates pre-existing pointers to elements at and after `index`, but - /// does not invalidate any before that. - /// + /// Invalidates pre-existing pointers to elements at and after `index`. /// If the list lacks unused capacity for the additional items, returns /// `error.OutOfMemory`. - /// /// Asserts that the index is in bounds or equal to the length. pub fn addManyAtBounded(self: *Self, index: usize, count: usize) error{OutOfMemory}![]T { if (self.capacity - self.items.len < count) return error.OutOfMemory; return addManyAtAssumeCapacity(self, index, count); } - /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room. + /// Insert slice `items` at index `index` by moving `list[index .. list.len]` to make room. /// This operation is O(N). /// Invalidates pre-existing pointers to elements at and after `index`. /// Invalidates all pre-existing element pointers if capacity must be @@ -828,7 +939,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { @memcpy(dst, items); } - /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room. + /// Insert slice `items` at index `index` by moving `list[index .. list.len]` to make room. /// This operation is O(N). /// Invalidates pre-existing pointers to elements at and after `index`. /// Asserts that the list has capacity for the additional items. @@ -842,7 +953,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { @memcpy(dst, items); } - /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room. + /// Insert slice `items` at index `index` by moving `list[index .. list.len]` to make room. /// This operation is O(N). /// Invalidates pre-existing pointers to elements at and after `index`. /// If the list lacks unused capacity for the additional items, returns @@ -858,7 +969,9 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { } /// Grows or shrinks the list as necessary. - /// Invalidates element pointers if additional capacity is allocated. + /// Invalidates element pointers if additional capacity is allocated, + /// Invalidates pointers to elements at and above index `start + len` + /// when `len` and `new_items.len` are unequal. /// Asserts that the range is in bounds. pub fn replaceRange( self: *Self, @@ -872,9 +985,8 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { } /// Grows or shrinks the list as necessary. - /// - /// Never invalidates element pointers. - /// + /// Invalidates pointers to elements at and above index `start + len` + /// when `len` and `new_items.len` are unequal. /// Asserts the capacity is enough for additional items. pub fn replaceRangeAssumeCapacity( self: *Self, @@ -883,7 +995,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { new_items: []const T, ) void { std.debug.assert(self.capacity - self.items.len >= new_items.len -| len); - + self.pointer_stability.assertUnlocked(); const tail = self.items[start + len ..]; const vacated = self.items[self.items.len - (len -| new_items.len) ..]; self.items.len = self.items.len - len + new_items.len; @@ -892,10 +1004,8 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { @memset(vacated, undefined); } - /// Grows or shrinks the list as necessary. - /// - /// Never invalidates element pointers. - /// + /// Invalidates pointers to elements at and above index `start + len` + /// when `len` and `new_items.len` are unequal. /// If the unused capacity is insufficient for additional items, /// returns `error.OutOfMemory`. pub fn replaceRangeBounded( @@ -958,6 +1068,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// /// Invalidates element pointers beyond the first deleted index. pub fn orderedRemoveMany(self: *Self, sorted_indexes: []const usize) void { + self.pointer_stability.assertUnlocked(); if (sorted_indexes.len == 0) return; var shift: usize = 1; for (sorted_indexes[0 .. sorted_indexes.len - 1], sorted_indexes[1..]) |removed, end| { @@ -980,6 +1091,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// This operation is O(1). /// Asserts that the index is in bounds. pub fn swapRemove(self: *Self, i: usize) T { + self.pointer_stability.assertUnlocked(); const val = self.items[i]; self.items[i] = self.items[self.items.len - 1]; self.items[self.items.len - 1] = undefined; @@ -996,7 +1108,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { } /// Append the slice of items to the list. - /// + /// Never invalidates element pointers. /// Asserts that the list can hold the additional items. pub fn appendSliceAssumeCapacity(self: *Self, items: []const T) void { const old_len = self.items.len; @@ -1007,7 +1119,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { } /// Append the slice of items to the list. - /// + /// Never invalidates element pointers. /// If the list lacks unused capacity for the additional items, returns `error.OutOfMemory`. pub fn appendSliceBounded(self: *Self, items: []const T) error{OutOfMemory}!void { if (self.capacity - self.items.len < items.len) return error.OutOfMemory; @@ -1027,7 +1139,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// /// Intended to be used only when `appendSliceAssumeCapacity` would be /// a compile error. - /// + /// Never invalidates element pointers. /// Asserts that the list can hold the additional items. pub fn appendUnalignedSliceAssumeCapacity(self: *Self, items: []align(1) const T) void { const old_len = self.items.len; @@ -1041,7 +1153,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// /// Intended to be used only when `appendSliceAssumeCapacity` would be /// a compile error. - /// + /// Never invalidates element pointers. /// If the list lacks unused capacity for the additional items, returns /// `error.OutOfMemory`. pub fn appendUnalignedSliceBounded(self: *Self, items: []align(1) const T) error{OutOfMemory}!void { @@ -1049,6 +1161,8 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { return appendUnalignedSliceAssumeCapacity(self, items); } + /// Prints a formatted string into this list. + /// Invalidates element pointers if additional memory is needed. pub fn print(self: *Self, gpa: Allocator, comptime fmt: []const u8, args: anytype) error{OutOfMemory}!void { comptime assert(T == u8); try self.ensureUnusedCapacity(gpa, fmt.len); @@ -1059,6 +1173,9 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { }; } + /// Prints a formatted string into this list. + /// Asserts that there is enough capacity for the write. + /// Never invalidates element pointers. pub fn printAssumeCapacity(self: *Self, comptime fmt: []const u8, args: anytype) void { comptime assert(T == u8); var w: std.Io.Writer = .fixed(self.unusedCapacitySlice()); @@ -1066,6 +1183,9 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { self.items.len += w.end; } + /// Prints a formatted string into this list. + /// Returns error.OutOfMemory if additional capacity is needed for the write. + /// Never invalidates element pointers. pub fn printBounded(self: *Self, comptime fmt: []const u8, args: anytype) error{OutOfMemory}!void { comptime assert(T == u8); var w: std.Io.Writer = .fixed(self.unusedCapacitySlice()); @@ -1141,6 +1261,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// Asserts that the new length is less than or equal to the previous length. /// If succeds capacity is guaranteed to be equal to the length. pub fn shrinkAndFreePrecise(self: *Self, gpa: Allocator, new_len: usize) Allocator.Error!void { + self.pointer_stability.assertUnlocked(); assert(new_len <= self.items.len); if (@sizeOf(T) == 0) { @@ -1194,6 +1315,8 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// Keeps capacity the same. /// Asserts that the new length is less than or equal to the previous length. pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void { + self.pointer_stability.assertUnlocked(); + assert(new_len <= self.items.len); @memset(self.items[new_len..], undefined); self.items.len = new_len; @@ -1202,12 +1325,14 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// Reduce length to 0. /// Invalidates all element pointers. pub fn clearRetainingCapacity(self: *Self) void { + self.pointer_stability.assertUnlocked(); @memset(self.items, undefined); self.items.len = 0; } /// Invalidates all element pointers. pub fn clearAndFree(self: *Self, gpa: Allocator) void { + self.pointer_stability.assertUnlocked(); gpa.free(self.allocatedSlice()); self.items.len = 0; self.capacity = 0; @@ -1225,6 +1350,8 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// modify the array so that it can hold exactly `new_capacity` items. /// Invalidates element pointers if additional memory is needed. pub fn ensureTotalCapacityPrecise(self: *Self, gpa: Allocator, new_capacity: usize) Allocator.Error!void { + self.pointer_stability.assertUnlocked(); + if (@sizeOf(T) == 0) { self.capacity = math.maxInt(usize); return; @@ -1268,7 +1395,8 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { } /// Increase length by 1, returning pointer to the new item. - /// The returned element pointer becomes invalid when the list is resized. + /// Invalidates element pointers if additional memory is needed. + /// The returned pointer may be invalidated by further operations to this list. pub fn addOne(self: *Self, gpa: Allocator) Allocator.Error!*T { // This can never overflow because `self.items` can never occupy the whole address space const newlen = self.items.len + 1; @@ -1277,11 +1405,8 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { } /// Increase length by 1, returning pointer to the new item. - /// /// Never invalidates element pointers. - /// - /// The returned element pointer becomes invalid when the list is resized. - /// + /// The returned pointer may be invalidated by further operations to this list. /// Asserts that the list can hold one additional item. pub fn addOneAssumeCapacity(self: *Self) *T { assert(self.items.len < self.capacity); @@ -1291,11 +1416,8 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { } /// Increase length by 1, returning pointer to the new item. - /// /// Never invalidates element pointers. - /// - /// The returned element pointer becomes invalid when the list is resized. - /// + /// The returned pointer may be invalidated by further operations to this list. /// If the list lacks unused capacity for the additional item, returns `error.OutOfMemory`. pub fn addOneBounded(self: *Self) error{OutOfMemory}!*T { if (self.capacity - self.items.len < 1) return error.OutOfMemory; @@ -1303,8 +1425,9 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { } /// Resize the array, adding `n` new elements, which have `undefined` values. + /// Invalidates element pointers if additional memory is required. /// The return value is an array pointing to the newly allocated elements. - /// The returned pointer becomes invalid when the list is resized. + /// The returned pointer may be invalidated by further operations to this list. pub fn addManyAsArray(self: *Self, gpa: Allocator, comptime n: usize) Allocator.Error!*[n]T { const prev_len = self.items.len; try self.resize(gpa, try addOrOom(self.items.len, n)); @@ -1312,13 +1435,9 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { } /// Resize the array, adding `n` new elements, which have `undefined` values. - /// /// The return value is an array pointing to the newly allocated elements. - /// /// Never invalidates element pointers. - /// - /// The returned pointer becomes invalid when the list is resized. - /// + /// The returned pointer may be invalidated by further operations to this list. /// Asserts that the list can hold the additional items. pub fn addManyAsArrayAssumeCapacity(self: *Self, comptime n: usize) *[n]T { assert(self.items.len + n <= self.capacity); @@ -1328,13 +1447,9 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { } /// Resize the array, adding `n` new elements, which have `undefined` values. - /// /// The return value is an array pointing to the newly allocated elements. - /// /// Never invalidates element pointers. - /// - /// The returned pointer becomes invalid when the list is resized. - /// + /// The returned pointer may be invalidated by further operations to this list. /// If the list lacks unused capacity for the additional items, returns /// `error.OutOfMemory`. pub fn addManyAsArrayBounded(self: *Self, comptime n: usize) error{OutOfMemory}!*[n]T { @@ -1344,7 +1459,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// Resize the array, adding `n` new elements, which have `undefined` values. /// The return value is a slice pointing to the newly allocated elements. - /// The returned pointer becomes invalid when the list is resized. + /// The returned pointer may be invalidated by further operations to this list. /// Resizes list if `self.capacity` is not large enough. pub fn addManyAsSlice(self: *Self, gpa: Allocator, n: usize) Allocator.Error![]T { const prev_len = self.items.len; @@ -1354,10 +1469,8 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// Resizes the array, adding `n` new elements, which have `undefined` /// values, returning a slice pointing to the newly allocated elements. - /// - /// Never invalidates element pointers. The returned pointer becomes - /// invalid when the list is resized. - /// + /// Never invalidates element pointers. + /// The returned pointer may be invalidated by further operations to this list. /// Asserts that the list can hold the additional items. pub fn addManyAsSliceAssumeCapacity(self: *Self, n: usize) []T { assert(self.items.len + n <= self.capacity); @@ -1368,10 +1481,8 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// Resizes the array, adding `n` new elements, which have `undefined` /// values, returning a slice pointing to the newly allocated elements. - /// - /// Never invalidates element pointers. The returned pointer becomes - /// invalid when the list is resized. - /// + /// Never invalidates element pointers. + /// The returned pointer may be invalidated by further operations to this list. /// If the list lacks unused capacity for the additional items, returns /// `error.OutOfMemory`. pub fn addManyAsSliceBounded(self: *Self, n: usize) error{OutOfMemory}![]T { @@ -1384,6 +1495,8 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// Invalidates pointers to last element. pub fn pop(self: *Self) ?T { if (self.items.len == 0) return null; + self.pointer_stability.assertUnlocked(); + const val = self.items[self.items.len - 1]; self.items[self.items.len - 1] = undefined; self.items.len -= 1; @@ -1392,6 +1505,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// Returns a slice of all the items plus the extra capacity, whose memory /// contents are `undefined`. + /// The returned pointer may be invalidated by further operations to this list. pub fn allocatedSlice(self: Self) Slice { return self.items.ptr[0..self.capacity]; } @@ -1400,6 +1514,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// This can be useful for writing directly into an ArrayList. /// Note that such an operation must be followed up with a direct /// modification of `self.items.len`. + /// The returned pointer may be invalidated by further operations to this list. pub fn unusedCapacitySlice(self: Self) []T { return self.allocatedSlice()[self.items.len..]; } @@ -1421,6 +1536,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// Returns a pointer to the last element from the list, or `null` if /// the list is empty. + /// The returned pointer may be invalidated by further operations to this list. pub fn lastPtr(self: Self) ?*T { if (self.items.len == 0) return null; return &self.items[self.items.len - 1]; @@ -2432,6 +2548,7 @@ test "return OutOfMemory when capacity would exceed maximum usize integer value" var list: ArrayList(u32) = .{ .items = undefined, .capacity = math.maxInt(usize) - 1, + .pointer_stability = .{}, }; list.items.len = math.maxInt(usize) - 1; @@ -2450,6 +2567,7 @@ test "return OutOfMemory when capacity would exceed maximum usize integer value" .items = undefined, .capacity = math.maxInt(usize) - 1, .allocator = a, + .pointer_stability = .{}, }; list.items.len = math.maxInt(usize) - 1;