authorgravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-03-04 15:35:51+01:00
committergravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-03-06 10:09:05+01:00
log46c72ed970850af6ba0933b4bcd38b5764a3528e
tree5ffe136ec6934b428e32a8cd62a855b4b315669c
parentf09386cce9ad99d77978cee0d15ae7dd422ea50c

std.heap.ArenaAllocator: do not retry failed CAS in `resize`/`free`

If we use `@cmpxchgStrong` instead of `@cmpxchgWeak` to adjust the `end_index` in `resize` and `free`, the only reason the CAS can fail is that another thread has changed `end_index` in the meantime. If that's happened, the allocation we were trying to resize/free isn't the most recent allocation anymore and there's no point in retrying, so we can get rid of the loop.

1 files changed, 52 insertions(+), 64 deletions(-)

lib/std/heap/ArenaAllocator.zig+52-64
......@@ -539,91 +539,79 @@ fn alloc(ctx: *anyopaque, n: usize, alignment: Alignment, ret_addr: usize) ?[*]u
539539 }
540540}
541541
542fn resize(ctx: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, ret_addr: usize) bool {
542fn resize(ctx: *anyopaque, memory: []u8, alignment: Alignment, new_len: usize, ret_addr: usize) bool {
543543 const arena: *ArenaAllocator = @ptrCast(@alignCast(ctx));
544544 _ = alignment;
545545 _ = ret_addr;
546546
547 assert(buf.len > 0);
547 assert(memory.len > 0);
548548 assert(new_len > 0);
549 if (buf.len == new_len) return true;
550549
551550 const node = arena.loadFirstNode().?;
552 const cur_buf_ptr = @as([*]u8, @ptrCast(node)) + @sizeOf(Node);
553
554 var cur_end_index = @atomicLoad(usize, &node.end_index, .monotonic);
555 while (true) {
556 if (cur_buf_ptr + cur_end_index != buf.ptr + buf.len) {
557 // It's not the most recent allocation, so it cannot be expanded,
558 // but it's fine if they want to make it smaller.
559 return new_len <= buf.len;
560 }
551 const buf_ptr = @as([*]u8, @ptrCast(node)) + @sizeOf(Node);
561552
562 const new_end_index: usize = new_end_index: {
563 if (buf.len >= new_len) {
564 break :new_end_index cur_end_index - (buf.len - new_len);
565 }
566 const cur_buf_len: usize = node.loadBuf().len;
567 // Saturating arithmetic because `end_index` and `size` are not
568 // guaranteed to be in sync.
569 if (cur_buf_len -| cur_end_index >= new_len - buf.len) {
570 break :new_end_index cur_end_index + (new_len - buf.len);
571 }
572 return false;
573 };
574
575 cur_end_index = @cmpxchgWeak(
576 usize,
577 &node.end_index,
578 cur_end_index,
579 new_end_index,
580 .monotonic,
581 .monotonic,
582 ) orelse {
583 return true;
584 };
553 const cur_end_index = @atomicLoad(usize, &node.end_index, .monotonic);
554 if (buf_ptr + cur_end_index != memory.ptr + memory.len) {
555 // It's not the most recent allocation, so it cannot be expanded,
556 // but it's fine if they want to make it smaller.
557 return new_len <= memory.len;
585558 }
559
560 const new_end_index: usize = new_end_index: {
561 if (memory.len >= new_len) {
562 break :new_end_index cur_end_index - (memory.len - new_len);
563 }
564 const cur_buf_len: usize = node.loadBuf().len;
565 // Saturating arithmetic because `end_index` and `size` are not
566 // guaranteed to be in sync.
567 if (cur_buf_len -| cur_end_index >= new_len - memory.len) {
568 break :new_end_index cur_end_index + (new_len - memory.len);
569 }
570 return false;
571 };
572 assert(buf_ptr + new_end_index == memory.ptr + new_len);
573
574 return null == @cmpxchgStrong(
575 usize,
576 &node.end_index,
577 cur_end_index,
578 new_end_index,
579 .monotonic,
580 .monotonic,
581 );
586582}
587583
588fn remap(
589 context: *anyopaque,
590 memory: []u8,
591 alignment: Alignment,
592 new_len: usize,
593 return_address: usize,
594) ?[*]u8 {
595 return if (resize(context, memory, alignment, new_len, return_address)) memory.ptr else null;
584fn remap(ctx: *anyopaque, memory: []u8, alignment: Alignment, new_len: usize, ret_addr: usize) ?[*]u8 {
585 return if (resize(ctx, memory, alignment, new_len, ret_addr)) memory.ptr else null;
596586}
597587
598fn free(ctx: *anyopaque, buf: []u8, alignment: Alignment, ret_addr: usize) void {
588fn free(ctx: *anyopaque, memory: []u8, alignment: Alignment, ret_addr: usize) void {
599589 const arena: *ArenaAllocator = @ptrCast(@alignCast(ctx));
600590 _ = alignment;
601591 _ = ret_addr;
602592
603 assert(buf.len > 0);
593 assert(memory.len > 0);
604594
605595 const node = arena.loadFirstNode().?;
606 const cur_buf_ptr: [*]u8 = @as([*]u8, @ptrCast(node)) + @sizeOf(Node);
596 const buf_ptr = @as([*]u8, @ptrCast(node)) + @sizeOf(Node);
607597
608 var cur_end_index = @atomicLoad(usize, &node.end_index, .monotonic);
609 while (true) {
610 if (cur_buf_ptr + cur_end_index != buf.ptr + buf.len) {
611 // Not the most recent allocation; we cannot free it.
612 return;
613 }
614 const new_end_index = cur_end_index - buf.len;
615
616 cur_end_index = @cmpxchgWeak(
617 usize,
618 &node.end_index,
619 cur_end_index,
620 new_end_index,
621 .monotonic,
622 .monotonic,
623 ) orelse {
624 return;
625 };
598 const cur_end_index = @atomicLoad(usize, &node.end_index, .monotonic);
599 if (buf_ptr + cur_end_index != memory.ptr + memory.len) {
600 // Not the most recent allocation; we cannot free it.
601 return;
626602 }
603
604 const new_end_index = cur_end_index - memory.len;
605 assert(buf_ptr + new_end_index == memory.ptr);
606
607 _ = @cmpxchgStrong(
608 usize,
609 &node.end_index,
610 cur_end_index,
611 new_end_index,
612 .monotonic,
613 .monotonic,
614 );
627615}
628616
629617const std = @import("std");