| ... | @@ -78,7 +78,7 @@ fn countListCapacity(first_node: ?*Node) usize { | ... | @@ -78,7 +78,7 @@ fn countListCapacity(first_node: ?*Node) usize { |
| 78 | while (it) |node| : (it = node.next) { | 78 | while (it) |node| : (it = node.next) { |
| 79 | // Compute the actually allocated size excluding the | 79 | // Compute the actually allocated size excluding the |
| 80 | // linked list node. | 80 | // linked list node. |
| 81 | capacity += node.size - @sizeOf(Node); | 81 | capacity += node.size.toInt() - @sizeOf(Node); |
| 82 | } | 82 | } |
| 83 | return capacity; | 83 | return capacity; |
| 84 | } | 84 | } |
| ... | @@ -164,7 +164,10 @@ pub fn reset(arena: *ArenaAllocator, mode: ResetMode) bool { | ... | @@ -164,7 +164,10 @@ pub fn reset(arena: *ArenaAllocator, mode: ResetMode) bool { |
| 164 | }; | 164 | }; |
| 165 | const allocated_slice = node.allocatedSliceUnsafe(); | 165 | const allocated_slice = node.allocatedSliceUnsafe(); |
| 166 | | 166 | |
| 167 | if (new_capacity == 0) { | 167 | // Align backwards to always stay below limit. |
| | 168 | const new_size = mem.alignBackward(usize, @sizeOf(Node) + new_capacity, 2); |
| | 169 | |
| | 170 | if (new_size == @sizeOf(Node)) { |
| 168 | arena.child_allocator.rawFree(allocated_slice, .of(Node), @returnAddress()); | 171 | arena.child_allocator.rawFree(allocated_slice, .of(Node), @returnAddress()); |
| 169 | first_node_ptr.* = null; | 172 | first_node_ptr.* = null; |
| 170 | continue; | 173 | continue; |
| ... | @@ -173,19 +176,17 @@ pub fn reset(arena: *ArenaAllocator, mode: ResetMode) bool { | ... | @@ -173,19 +176,17 @@ pub fn reset(arena: *ArenaAllocator, mode: ResetMode) bool { |
| 173 | node.end_index = 0; | 176 | node.end_index = 0; |
| 174 | first_node_ptr.* = node; | 177 | first_node_ptr.* = node; |
| 175 | | 178 | |
| 176 | const adjusted_capacity: usize = mem.alignForward(usize, new_capacity, 2); | 179 | if (allocated_slice.len == new_size) { |
| 177 | | | |
| 178 | if (allocated_slice.len - @sizeOf(Node) == adjusted_capacity) { | | |
| 179 | // perfect, no need to invoke the child_allocator | 180 | // perfect, no need to invoke the child_allocator |
| 180 | continue; | 181 | continue; |
| 181 | } | 182 | } |
| 182 | | 183 | |
| 183 | if (arena.child_allocator.rawResize(allocated_slice, .of(Node), adjusted_capacity, @returnAddress())) { | 184 | if (arena.child_allocator.rawResize(allocated_slice, .of(Node), new_size, @returnAddress())) { |
| 184 | // successful resize | 185 | // successful resize |
| 185 | node.size = adjusted_capacity; | 186 | node.size = .fromInt(new_size); |
| 186 | } else { | 187 | } else { |
| 187 | // manual realloc | 188 | // manual realloc |
| 188 | const new_ptr = arena.child_allocator.rawAlloc(adjusted_capacity, .of(Node), @returnAddress()) orelse { | 189 | const new_ptr = arena.child_allocator.rawAlloc(new_size, .of(Node), @returnAddress()) orelse { |
| 189 | // we failed to preheat the arena properly, signal this to the user. | 190 | // we failed to preheat the arena properly, signal this to the user. |
| 190 | ok = false; | 191 | ok = false; |
| 191 | continue; | 192 | continue; |
| ... | @@ -193,7 +194,7 @@ pub fn reset(arena: *ArenaAllocator, mode: ResetMode) bool { | ... | @@ -193,7 +194,7 @@ pub fn reset(arena: *ArenaAllocator, mode: ResetMode) bool { |
| 193 | arena.child_allocator.rawFree(allocated_slice, .of(Node), @returnAddress()); | 194 | arena.child_allocator.rawFree(allocated_slice, .of(Node), @returnAddress()); |
| 194 | const new_first_node: *Node = @ptrCast(@alignCast(new_ptr)); | 195 | const new_first_node: *Node = @ptrCast(@alignCast(new_ptr)); |
| 195 | new_first_node.* = .{ | 196 | new_first_node.* = .{ |
| 196 | .size = adjusted_capacity, | 197 | .size = .fromInt(new_size), |
| 197 | .end_index = 0, | 198 | .end_index = 0, |
| 198 | .next = null, | 199 | .next = null, |
| 199 | }; | 200 | }; |
| ... | @@ -213,51 +214,60 @@ pub fn reset(arena: *ArenaAllocator, mode: ResetMode) bool { | ... | @@ -213,51 +214,60 @@ pub fn reset(arena: *ArenaAllocator, mode: ResetMode) bool { |
| 213 | const Node = struct { | 214 | const Node = struct { |
| 214 | /// Only meant to be accessed indirectly via the methods supplied by this type, | 215 | /// Only meant to be accessed indirectly via the methods supplied by this type, |
| 215 | /// except if the node is owned by the thread accessing it. | 216 | /// except if the node is owned by the thread accessing it. |
| 216 | /// Must always be an even number to accomodate `resize_bit`. | 217 | /// Must always be an even number to accomodate `resize` bit. |
| 217 | size: usize, | 218 | size: Size, |
| 218 | /// Concurrent accesses to `end_index` can be monotonic since it is only ever | 219 | /// Concurrent accesses to `end_index` can be monotonic as long as its value |
| 219 | /// incremented in `alloc` and `resize` after being compared to `size`. | 220 | /// is compared to a version of `size` before using it to access memory. |
| 220 | /// Since `size` can only grow and never shrink, memory access depending on | 221 | /// Since `size` can only grow and never shrink, memory access depending on |
| 221 | /// `end_index` can never be OOB. | 222 | /// any `end_index` <= any `size` can never be OOB. |
| 222 | end_index: usize, | 223 | end_index: usize, |
| 223 | /// This field should only be accessed if the node is owned by the thread | 224 | /// This field should only be accessed if the node is owned by the thread |
| 224 | /// accessing it. | 225 | /// accessing it. |
| 225 | next: ?*Node, | 226 | next: ?*Node, |
| 226 | | 227 | |
| 227 | const resize_bit: usize = 1; | 228 | const Size = packed struct(usize) { |
| | 229 | resizing: bool, |
| | 230 | _: @Int(.unsigned, @bitSizeOf(usize) - 1) = 0, |
| 228 | | 231 | |
| 229 | fn loadEndIndex(node: *Node) usize { | 232 | fn fromInt(int: usize) Size { |
| 230 | return @atomicLoad(usize, &node.end_index, .monotonic); | 233 | assert(int >= @sizeOf(Node)); |
| 231 | } | 234 | const size: Size = @bitCast(int); |
| | 235 | assert(!size.resizing); |
| | 236 | return size; |
| | 237 | } |
| 232 | | 238 | |
| 233 | /// Returns `null` on success and previous value on failure. | 239 | fn toInt(size: Size) usize { |
| 234 | fn trySetEndIndex(node: *Node, from: usize, to: usize) ?usize { | 240 | var int = size; |
| 235 | assert(from != to); // check this before attempting to set `end_index`! | 241 | int.resizing = false; |
| 236 | return @cmpxchgWeak(usize, &node.end_index, from, to, .monotonic, .monotonic); | 242 | return @bitCast(int); |
| 237 | } | 243 | } |
| | 244 | |
| | 245 | comptime { |
| | 246 | assert(Size{ .resizing = true } == @as(Size, @bitCast(@as(usize, 1)))); |
| | 247 | } |
| | 248 | }; |
| 238 | | 249 | |
| 239 | fn loadBuf(node: *Node) []u8 { | 250 | fn loadBuf(node: *Node) []u8 { |
| 240 | // monotonic is fine since `size` can only ever grow, so the buffer returned | 251 | // monotonic is fine since `size` can only ever grow, so the buffer returned |
| 241 | // by this function is always valid memory. | 252 | // by this function is always valid memory. |
| 242 | const size = @atomicLoad(usize, &node.size, .monotonic); | 253 | const size = @atomicLoad(Size, &node.size, .monotonic); |
| 243 | return @as([*]u8, @ptrCast(node))[0 .. size & ~resize_bit][@sizeOf(Node)..]; | 254 | return @as([*]u8, @ptrCast(node))[0..size.toInt()][@sizeOf(Node)..]; |
| 244 | } | 255 | } |
| 245 | | 256 | |
| 246 | /// Returns allocated slice or `null` if node is already (being) resized. | 257 | /// Returns allocated slice or `null` if node is already (being) resized. |
| 247 | fn beginResize(node: *Node) ?[]u8 { | 258 | fn beginResize(node: *Node) ?[]u8 { |
| 248 | const size = @atomicRmw(usize, &node.size, .Or, resize_bit, .acquire); // syncs with release in `endResize` | 259 | const size = @atomicRmw(Size, &node.size, .Or, .{ .resizing = true }, .acquire); // syncs with release in `endResize` |
| 249 | if (size & resize_bit != 0) return null; | 260 | if (size.resizing) return null; |
| 250 | return @as([*]u8, @ptrCast(node))[0..size]; | 261 | return @as([*]u8, @ptrCast(node))[0..size.toInt()]; |
| 251 | } | 262 | } |
| 252 | | 263 | |
| 253 | fn endResize(node: *Node, size: usize) void { | 264 | fn endResize(node: *Node, size: usize) void { |
| 254 | assert(size & resize_bit == 0); | 265 | return @atomicStore(Size, &node.size, .fromInt(size), .release); // syncs with acquire in `beginResize` |
| 255 | return @atomicStore(usize, &node.size, size, .release); // syncs with acquire in `beginResize` | | |
| 256 | } | 266 | } |
| 257 | | 267 | |
| 258 | /// Not threadsafe. | 268 | /// Not threadsafe. |
| 259 | fn allocatedSliceUnsafe(node: *Node) []u8 { | 269 | fn allocatedSliceUnsafe(node: *Node) []u8 { |
| 260 | return @as([*]u8, @ptrCast(node))[0 .. node.size & ~resize_bit]; | 270 | return @as([*]u8, @ptrCast(node))[0..node.size.toInt()]; |
| 261 | } | 271 | } |
| 262 | }; | 272 | }; |
| 263 | | 273 | |
| ... | @@ -326,19 +336,22 @@ fn alloc(ctx: *anyopaque, n: usize, alignment: Alignment, ret_addr: usize) ?[*]u | ... | @@ -326,19 +336,22 @@ fn alloc(ctx: *anyopaque, n: usize, alignment: Alignment, ret_addr: usize) ?[*]u |
| 326 | retry: while (true) { | 336 | retry: while (true) { |
| 327 | const first_node: ?*Node, const prev_size: usize = first_node: { | 337 | const first_node: ?*Node, const prev_size: usize = first_node: { |
| 328 | const node = cur_first_node orelse break :first_node .{ null, 0 }; | 338 | const node = cur_first_node orelse break :first_node .{ null, 0 }; |
| 329 | var end_index = node.loadEndIndex(); | 339 | const buf = node.loadBuf(); |
| 330 | while (true) { | | |
| 331 | const buf = node.loadBuf(); | | |
| 332 | const aligned_index = alignedIndex(buf.ptr, end_index, alignment); | | |
| 333 | | 340 | |
| 334 | if (aligned_index + n > buf.len) { | 341 | // To avoid using a CAS loop in the hot path we atomically increase |
| 335 | break :first_node .{ node, buf.len }; | 342 | // `end_index` by a large enough amount to be able to always provide |
| 336 | } | 343 | // the required alignment within the reserved memory. To recover the |
| | 344 | // space this potentially wastes we try to subtract the 'overshoot' |
| | 345 | // with a single cmpxchg afterwards, which may fail. |
| 337 | | 346 | |
| 338 | end_index = node.trySetEndIndex(end_index, aligned_index + n) orelse { | 347 | const alignable = n + alignment.toByteUnits() - 1; |
| 339 | return buf[aligned_index..][0..n].ptr; | 348 | const end_index = @atomicRmw(usize, &node.end_index, .Add, alignable, .monotonic); |
| 340 | }; | 349 | const aligned_index = alignedIndex(buf.ptr, end_index, alignment); |
| 341 | } | 350 | assert(end_index + alignable >= aligned_index + n); |
| | 351 | _ = @cmpxchgStrong(usize, &node.end_index, end_index + alignable, aligned_index + n, .monotonic, .monotonic); |
| | 352 | |
| | 353 | if (aligned_index + n > buf.len) break :first_node .{ node, buf.len }; |
| | 354 | return buf[aligned_index..][0..n].ptr; |
| 342 | }; | 355 | }; |
| 343 | | 356 | |
| 344 | resize: { | 357 | resize: { |
| ... | @@ -352,7 +365,7 @@ fn alloc(ctx: *anyopaque, n: usize, alignment: Alignment, ret_addr: usize) ?[*]u | ... | @@ -352,7 +365,7 @@ fn alloc(ctx: *anyopaque, n: usize, alignment: Alignment, ret_addr: usize) ?[*]u |
| 352 | defer node.endResize(size); | 365 | defer node.endResize(size); |
| 353 | | 366 | |
| 354 | const buf = allocated_slice[@sizeOf(Node)..]; | 367 | const buf = allocated_slice[@sizeOf(Node)..]; |
| 355 | const end_index = node.loadEndIndex(); | 368 | const end_index = @atomicLoad(usize, &node.end_index, .monotonic); |
| 356 | const aligned_index = alignedIndex(buf.ptr, end_index, alignment); | 369 | const aligned_index = alignedIndex(buf.ptr, end_index, alignment); |
| 357 | const new_size = mem.alignForward(usize, @sizeOf(Node) + aligned_index + n, 2); | 370 | const new_size = mem.alignForward(usize, @sizeOf(Node) + aligned_index + n, 2); |
| 358 | | 371 | |
| ... | @@ -403,55 +416,59 @@ fn alloc(ctx: *anyopaque, n: usize, alignment: Alignment, ret_addr: usize) ?[*]u | ... | @@ -403,55 +416,59 @@ fn alloc(ctx: *anyopaque, n: usize, alignment: Alignment, ret_addr: usize) ?[*]u |
| 403 | } | 416 | } |
| 404 | } | 417 | } |
| 405 | | 418 | |
| 406 | var best_fit_prev: ?*Node = null; | 419 | const candidate: ?*Node, const prev: ?*Node = candidate: { |
| 407 | var best_fit: ?*Node = null; | 420 | var best_fit_prev: ?*Node = null; |
| 408 | var best_fit_diff: usize = std.math.maxInt(usize); | 421 | var best_fit: ?*Node = null; |
| 409 | | 422 | var best_fit_diff: usize = std.math.maxInt(usize); |
| 410 | var it_prev: ?*Node = null; | 423 | |
| 411 | var it = free_list; | 424 | var it_prev: ?*Node = null; |
| 412 | const candidate: ?*Node, const prev: ?*Node = find: while (it) |node| : ({ | 425 | var it = free_list; |
| 413 | it_prev = it; | 426 | while (it) |node| : ({ |
| 414 | it = node.next; | 427 | it_prev = it; |
| 415 | }) { | 428 | it = node.next; |
| 416 | last_free = node; | 429 | }) { |
| 417 | assert(node.size & Node.resize_bit == 0); | 430 | last_free = node; |
| 418 | const buf = node.allocatedSliceUnsafe()[@sizeOf(Node)..]; | 431 | assert(!node.size.resizing); |
| 419 | const aligned_index = alignedIndex(buf.ptr, 0, alignment); | 432 | const buf = node.allocatedSliceUnsafe()[@sizeOf(Node)..]; |
| 420 | if (buf.len < aligned_index + n) { | 433 | const aligned_index = alignedIndex(buf.ptr, 0, alignment); |
| | 434 | |
| | 435 | if (aligned_index + n <= buf.len) { |
| | 436 | break :candidate .{ node, it_prev }; |
| | 437 | } |
| | 438 | |
| 421 | const diff = aligned_index + n - buf.len; | 439 | const diff = aligned_index + n - buf.len; |
| 422 | if (diff <= best_fit_diff) { | 440 | if (diff <= best_fit_diff) { |
| 423 | best_fit_prev = it_prev; | 441 | best_fit_prev = it_prev; |
| 424 | best_fit = node; | 442 | best_fit = node; |
| 425 | best_fit_diff = diff; | 443 | best_fit_diff = diff; |
| 426 | } | 444 | } |
| 427 | continue :find; | 445 | } else { |
| 428 | } | 446 | // Ideally we want to use all nodes in `free_list` eventually, |
| 429 | break :find .{ node, it_prev }; | 447 | // so even if none fit we'll try to resize the one that was the |
| 430 | } else { | 448 | // closest to being large enough. |
| 431 | // Ideally we want to use all nodes in `free_list` eventually, | 449 | if (best_fit) |node| { |
| 432 | // so even if none fit we'll try to resize the one that was the | 450 | const allocated_slice = node.allocatedSliceUnsafe(); |
| 433 | // closest to being large enough. | 451 | const buf = allocated_slice[@sizeOf(Node)..]; |
| 434 | if (best_fit) |node| { | 452 | const aligned_index = alignedIndex(buf.ptr, 0, alignment); |
| 435 | const allocated_slice = node.allocatedSliceUnsafe(); | 453 | const new_size = mem.alignForward(usize, @sizeOf(Node) + aligned_index + n, 2); |
| 436 | const buf = allocated_slice[@sizeOf(Node)..]; | 454 | |
| 437 | const aligned_index = alignedIndex(buf.ptr, 0, alignment); | 455 | if (arena.child_allocator.rawResize(allocated_slice, .of(Node), new_size, @returnAddress())) { |
| 438 | const new_size = mem.alignForward(usize, @sizeOf(Node) + aligned_index + n, 2); | 456 | node.size = .fromInt(new_size); |
| 439 | | 457 | break :candidate .{ node, best_fit_prev }; |
| 440 | if (arena.child_allocator.rawResize(allocated_slice, .of(Node), new_size, @returnAddress())) { | 458 | } |
| 441 | node.size = new_size; | | |
| 442 | break :find .{ node, best_fit_prev }; | | |
| 443 | } | 459 | } |
| | 460 | break :from_free_list; |
| 444 | } | 461 | } |
| 445 | break :from_free_list; | | |
| 446 | }; | 462 | }; |
| 447 | | 463 | |
| 448 | it = last_free; | 464 | { |
| 449 | while (it) |node| : (it = node.next) { | 465 | var it = last_free; |
| 450 | last_free = node; | 466 | while (it) |node| : (it = node.next) { |
| | 467 | last_free = node; |
| | 468 | } |
| 451 | } | 469 | } |
| 452 | | 470 | |
| 453 | const node = candidate orelse break :from_free_list; | 471 | const node = candidate orelse break :from_free_list; |
| 454 | | | |
| 455 | const old_next = node.next; | 472 | const old_next = node.next; |
| 456 | | 473 | |
| 457 | const buf = node.allocatedSliceUnsafe()[@sizeOf(Node)..]; | 474 | const buf = node.allocatedSliceUnsafe()[@sizeOf(Node)..]; |
| ... | @@ -489,12 +506,11 @@ fn alloc(ctx: *anyopaque, n: usize, alignment: Alignment, ret_addr: usize) ?[*]u | ... | @@ -489,12 +506,11 @@ fn alloc(ctx: *anyopaque, n: usize, alignment: Alignment, ret_addr: usize) ?[*]u |
| 489 | const big_enough_size = prev_size + min_size + 16; | 506 | const big_enough_size = prev_size + min_size + 16; |
| 490 | break :size mem.alignForward(usize, big_enough_size + big_enough_size / 2, 2); | 507 | break :size mem.alignForward(usize, big_enough_size + big_enough_size / 2, 2); |
| 491 | }; | 508 | }; |
| 492 | assert(size & Node.resize_bit == 0); | | |
| 493 | const ptr = arena.child_allocator.rawAlloc(size, .of(Node), @returnAddress()) orelse | 509 | const ptr = arena.child_allocator.rawAlloc(size, .of(Node), @returnAddress()) orelse |
| 494 | return null; | 510 | return null; |
| 495 | const new_node: *Node = @ptrCast(@alignCast(ptr)); | 511 | const new_node: *Node = @ptrCast(@alignCast(ptr)); |
| 496 | new_node.* = .{ | 512 | new_node.* = .{ |
| 497 | .size = size, | 513 | .size = .fromInt(size), |
| 498 | .end_index = undefined, // set below | 514 | .end_index = undefined, // set below |
| 499 | .next = undefined, // set below | 515 | .next = undefined, // set below |
| 500 | }; | 516 | }; |
| ... | @@ -504,7 +520,7 @@ fn alloc(ctx: *anyopaque, n: usize, alignment: Alignment, ret_addr: usize) ?[*]u | ... | @@ -504,7 +520,7 @@ fn alloc(ctx: *anyopaque, n: usize, alignment: Alignment, ret_addr: usize) ?[*]u |
| 504 | | 520 | |
| 505 | const buf = new_node.allocatedSliceUnsafe()[@sizeOf(Node)..]; | 521 | const buf = new_node.allocatedSliceUnsafe()[@sizeOf(Node)..]; |
| 506 | const aligned_index = alignedIndex(buf.ptr, 0, alignment); | 522 | const aligned_index = alignedIndex(buf.ptr, 0, alignment); |
| 507 | assert(new_node.size >= @sizeOf(Node) + aligned_index + n); | 523 | assert(new_node.size.toInt() >= @sizeOf(Node) + aligned_index + n); |
| 508 | | 524 | |
| 509 | new_node.end_index = aligned_index + n; | 525 | new_node.end_index = aligned_index + n; |
| 510 | new_node.next = first_node; | 526 | new_node.next = first_node; |
| ... | @@ -533,7 +549,7 @@ fn resize(ctx: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, ret_ | ... | @@ -533,7 +549,7 @@ fn resize(ctx: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, ret_ |
| 533 | const node = arena.loadFirstNode().?; | 549 | const node = arena.loadFirstNode().?; |
| 534 | const cur_buf_ptr = @as([*]u8, @ptrCast(node)) + @sizeOf(Node); | 550 | const cur_buf_ptr = @as([*]u8, @ptrCast(node)) + @sizeOf(Node); |
| 535 | | 551 | |
| 536 | var cur_end_index = node.loadEndIndex(); | 552 | var cur_end_index = @atomicLoad(usize, &node.end_index, .monotonic); |
| 537 | while (true) { | 553 | while (true) { |
| 538 | if (cur_buf_ptr + cur_end_index != buf.ptr + buf.len) { | 554 | if (cur_buf_ptr + cur_end_index != buf.ptr + buf.len) { |
| 539 | // It's not the most recent allocation, so it cannot be expanded, | 555 | // It's not the most recent allocation, so it cannot be expanded, |
| ... | @@ -554,7 +570,14 @@ fn resize(ctx: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, ret_ | ... | @@ -554,7 +570,14 @@ fn resize(ctx: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, ret_ |
| 554 | return false; | 570 | return false; |
| 555 | }; | 571 | }; |
| 556 | | 572 | |
| 557 | cur_end_index = node.trySetEndIndex(cur_end_index, new_end_index) orelse { | 573 | cur_end_index = @cmpxchgWeak( |
| | 574 | usize, |
| | 575 | &node.end_index, |
| | 576 | cur_end_index, |
| | 577 | new_end_index, |
| | 578 | .monotonic, |
| | 579 | .monotonic, |
| | 580 | ) orelse { |
| 558 | return true; | 581 | return true; |
| 559 | }; | 582 | }; |
| 560 | } | 583 | } |
| ... | @@ -580,14 +603,22 @@ fn free(ctx: *anyopaque, buf: []u8, alignment: Alignment, ret_addr: usize) void | ... | @@ -580,14 +603,22 @@ fn free(ctx: *anyopaque, buf: []u8, alignment: Alignment, ret_addr: usize) void |
| 580 | const node = arena.loadFirstNode().?; | 603 | const node = arena.loadFirstNode().?; |
| 581 | const cur_buf_ptr: [*]u8 = @as([*]u8, @ptrCast(node)) + @sizeOf(Node); | 604 | const cur_buf_ptr: [*]u8 = @as([*]u8, @ptrCast(node)) + @sizeOf(Node); |
| 582 | | 605 | |
| 583 | var cur_end_index = node.loadEndIndex(); | 606 | var cur_end_index = @atomicLoad(usize, &node.end_index, .monotonic); |
| 584 | while (true) { | 607 | while (true) { |
| 585 | if (cur_buf_ptr + cur_end_index != buf.ptr + buf.len) { | 608 | if (cur_buf_ptr + cur_end_index != buf.ptr + buf.len) { |
| 586 | // Not the most recent allocation; we cannot free it. | 609 | // Not the most recent allocation; we cannot free it. |
| 587 | return; | 610 | return; |
| 588 | } | 611 | } |
| 589 | const new_end_index = cur_end_index - buf.len; | 612 | const new_end_index = cur_end_index - buf.len; |
| 590 | cur_end_index = node.trySetEndIndex(cur_end_index, new_end_index) orelse { | 613 | |
| | 614 | cur_end_index = @cmpxchgWeak( |
| | 615 | usize, |
| | 616 | &node.end_index, |
| | 617 | cur_end_index, |
| | 618 | new_end_index, |
| | 619 | .monotonic, |
| | 620 | .monotonic, |
| | 621 | ) orelse { |
| 591 | return; | 622 | return; |
| 592 | }; | 623 | }; |
| 593 | } | 624 | } |
| ... | @@ -637,6 +668,7 @@ test "reset while retaining a buffer" { | ... | @@ -637,6 +668,7 @@ test "reset while retaining a buffer" { |
| 637 | try std.testing.expect(arena_allocator.state.used_list.?.next != null); | 668 | try std.testing.expect(arena_allocator.state.used_list.?.next != null); |
| 638 | | 669 | |
| 639 | // This retains the first allocated buffer | 670 | // This retains the first allocated buffer |
| 640 | try std.testing.expect(arena_allocator.reset(.{ .retain_with_limit = 1 })); | 671 | try std.testing.expect(arena_allocator.reset(.{ .retain_with_limit = 2 })); |
| 641 | try std.testing.expect(arena_allocator.state.used_list.?.next == null); | 672 | try std.testing.expect(arena_allocator.state.used_list.?.next == null); |
| | 673 | try std.testing.expectEqual(2, arena_allocator.queryCapacity()); |
| 642 | } | 674 | } |