authorgravatar for mason@gamesbymason.comMason Remaley <mason@gamesbymason.com> 2026-04-13 02:55:07-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-18 14:51:49-07:00
loga4d8e9608e0cb2704bad00b2f87af078c6d4ed59
treeda7d65897712988d3bd6b76ad00e6cb1a72e4652
parent73ecc6333fcc57dce51a5ca93e0740fe6a3346cf

Reworks the stack fallback allocator

The previous approach had a few downsides: 1. You couldn't set the alignment of the internal buffer. Many callers in the standard library trying to use this for a small vec style optimization worked around this by setting the alignment for the struct itself, this ends up very verbose and also assumes a specific layout for the struct which isn't guaranteed. 2. It was generic over the size of the buffer. This type is used a lot in std with various sizes. 3. It has an awkward API where you had to call get which mutated the type unlike all other allocators, and then had a runtime check to make sure you didn't get this wrong. The new approach resolves all of these issues by just taking the buf as an argument. This is particularly amenable to smallvec style optimizations: you can just declare the buf as an array of the item you want to allocate to get the exact minimum size.

1 files changed, 84 insertions(+), 107 deletions(-)

lib/std/heap.zig+84-107
...@@ -368,112 +368,88 @@ pub const brk_allocator: Allocator = .{...@@ -368,112 +368,88 @@ pub const brk_allocator: Allocator = .{
368 .vtable = &BrkAllocator.vtable,368 .vtable = &BrkAllocator.vtable,
369};369};
370370
371/// Returns a `StackFallbackAllocator` allocating using either a371/// An allocator that attempts to allocate from the given buffer, falling back to
372/// `FixedBufferAllocator` on an array of size `size` and falling back to372/// `fallback_allocator` if this fails.
373/// `fallback_allocator` if that fails.373pub const StackFallbackAllocator = struct {
374pub fn stackFallback(comptime size: usize, fallback_allocator: Allocator) StackFallbackAllocator(size) {374 const Self = @This();
375 return StackFallbackAllocator(size){375
376 .buffer = undefined,376 fallback_allocator: Allocator,
377 .fallback_allocator = fallback_allocator,377 fixed_buffer_allocator: FixedBufferAllocator,
378 .fixed_buffer_allocator = undefined,378
379 };379 pub fn init(buf: []u8, fallback_allocator: Allocator) Self {
380}380 return .{
381 .fallback_allocator = fallback_allocator,
382 .fixed_buffer_allocator = .init(buf),
383 };
384 }
381385
382/// An allocator that attempts to allocate using a386 pub fn allocator(self: *Self) Allocator {
383/// `FixedBufferAllocator` using an array of size `size`. If the387 return .{
384/// allocation fails, it will fall back to using388 .ptr = self,
385/// `fallback_allocator`. Easily created with `stackFallback`.389 .vtable = &.{
386pub fn StackFallbackAllocator(comptime size: usize) type {390 .alloc = alloc,
387 return struct {391 .resize = resize,
388 const Self = @This();392 .remap = remap,
389393 .free = free,
390 buffer: [size]u8,394 },
391 fallback_allocator: Allocator,395 };
392 fixed_buffer_allocator: FixedBufferAllocator,396 }
393 get_called: if (std.debug.runtime_safety) bool else void =
394 if (std.debug.runtime_safety) false else {},
395
396 /// This function both fetches a `Allocator` interface to this
397 /// allocator *and* resets the internal buffer allocator.
398 pub fn get(self: *Self) Allocator {
399 if (std.debug.runtime_safety) {
400 assert(!self.get_called); // `get` called multiple times; instead use `const allocator = stackFallback(N).get();`
401 self.get_called = true;
402 }
403 self.fixed_buffer_allocator = FixedBufferAllocator.init(self.buffer[0..]);
404 return .{
405 .ptr = self,
406 .vtable = &.{
407 .alloc = alloc,
408 .resize = resize,
409 .remap = remap,
410 .free = free,
411 },
412 };
413 }
414397
415 /// Unlike most std allocators `StackFallbackAllocator` modifies398 fn alloc(
416 /// its internal state before returning an implementation of399 ctx: *anyopaque,
417 /// the`Allocator` interface and therefore also doesn't use400 len: usize,
418 /// the usual `.allocator()` method.401 alignment: Alignment,
419 pub const allocator = @compileError("use 'const allocator = stackFallback(N).get();' instead");402 ra: usize,
420403 ) ?[*]u8 {
421 fn alloc(404 const self: *Self = @ptrCast(@alignCast(ctx));
422 ctx: *anyopaque,405 return FixedBufferAllocator.alloc(&self.fixed_buffer_allocator, len, alignment, ra) orelse
423 len: usize,406 return self.fallback_allocator.rawAlloc(len, alignment, ra);
424 alignment: Alignment,407 }
425 ra: usize,
426 ) ?[*]u8 {
427 const self: *Self = @ptrCast(@alignCast(ctx));
428 return FixedBufferAllocator.alloc(&self.fixed_buffer_allocator, len, alignment, ra) orelse
429 return self.fallback_allocator.rawAlloc(len, alignment, ra);
430 }
431408
432 fn resize(409 fn resize(
433 ctx: *anyopaque,410 ctx: *anyopaque,
434 buf: []u8,411 buf: []u8,
435 alignment: Alignment,412 alignment: Alignment,
436 new_len: usize,413 new_len: usize,
437 ra: usize,414 ra: usize,
438 ) bool {415 ) bool {
439 const self: *Self = @ptrCast(@alignCast(ctx));416 const self: *Self = @ptrCast(@alignCast(ctx));
440 if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) {417 if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) {
441 return FixedBufferAllocator.resize(&self.fixed_buffer_allocator, buf, alignment, new_len, ra);418 return FixedBufferAllocator.resize(&self.fixed_buffer_allocator, buf, alignment, new_len, ra);
442 } else {419 } else {
443 return self.fallback_allocator.rawResize(buf, alignment, new_len, ra);420 return self.fallback_allocator.rawResize(buf, alignment, new_len, ra);
444 }
445 }421 }
422 }
446423
447 fn remap(424 fn remap(
448 context: *anyopaque,425 context: *anyopaque,
449 memory: []u8,426 memory: []u8,
450 alignment: Alignment,427 alignment: Alignment,
451 new_len: usize,428 new_len: usize,
452 return_address: usize,429 return_address: usize,
453 ) ?[*]u8 {430 ) ?[*]u8 {
454 const self: *Self = @ptrCast(@alignCast(context));431 const self: *Self = @ptrCast(@alignCast(context));
455 if (self.fixed_buffer_allocator.ownsPtr(memory.ptr)) {432 if (self.fixed_buffer_allocator.ownsPtr(memory.ptr)) {
456 return FixedBufferAllocator.remap(&self.fixed_buffer_allocator, memory, alignment, new_len, return_address);433 return FixedBufferAllocator.remap(&self.fixed_buffer_allocator, memory, alignment, new_len, return_address);
457 } else {434 } else {
458 return self.fallback_allocator.rawRemap(memory, alignment, new_len, return_address);435 return self.fallback_allocator.rawRemap(memory, alignment, new_len, return_address);
459 }
460 }436 }
437 }
461438
462 fn free(439 fn free(
463 ctx: *anyopaque,440 ctx: *anyopaque,
464 buf: []u8,441 buf: []u8,
465 alignment: Alignment,442 alignment: Alignment,
466 ra: usize,443 ra: usize,
467 ) void {444 ) void {
468 const self: *Self = @ptrCast(@alignCast(ctx));445 const self: *Self = @ptrCast(@alignCast(ctx));
469 if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) {446 if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) {
470 return FixedBufferAllocator.free(&self.fixed_buffer_allocator, buf, alignment, ra);447 return FixedBufferAllocator.free(&self.fixed_buffer_allocator, buf, alignment, ra);
471 } else {448 } else {
472 return self.fallback_allocator.rawFree(buf, alignment, ra);449 return self.fallback_allocator.rawFree(buf, alignment, ra);
473 }
474 }450 }
475 };451 }
476}452};
477453
478test c_allocator {454test c_allocator {
479 if (builtin.link_libc) {455 if (builtin.link_libc) {
...@@ -525,22 +501,23 @@ test ArenaAllocator {...@@ -525,22 +501,23 @@ test ArenaAllocator {
525 try testAllocatorAlignedShrink(allocator);501 try testAllocatorAlignedShrink(allocator);
526}502}
527503
528test "StackFallbackAllocator" {504test StackFallbackAllocator {
505 var buf: [4096]u8 = undefined;
529 {506 {
530 var stack_allocator = stackFallback(4096, std.testing.allocator);507 var stack_allocator: StackFallbackAllocator = .init(&buf, std.testing.allocator);
531 try testAllocator(stack_allocator.get());508 try testAllocator(stack_allocator.allocator());
532 }509 }
533 {510 {
534 var stack_allocator = stackFallback(4096, std.testing.allocator);511 var stack_allocator: StackFallbackAllocator = .init(&buf, std.testing.allocator);
535 try testAllocatorAligned(stack_allocator.get());512 try testAllocatorAligned(stack_allocator.allocator());
536 }513 }
537 {514 {
538 var stack_allocator = stackFallback(4096, std.testing.allocator);515 var stack_allocator: StackFallbackAllocator = .init(&buf, std.testing.allocator);
539 try testAllocatorLargeAlignment(stack_allocator.get());516 try testAllocatorLargeAlignment(stack_allocator.allocator());
540 }517 }
541 {518 {
542 var stack_allocator = stackFallback(4096, std.testing.allocator);519 var stack_allocator: StackFallbackAllocator = .init(&buf, std.testing.allocator);
543 try testAllocatorAlignedShrink(stack_allocator.get());520 try testAllocatorAlignedShrink(stack_allocator.allocator());
544 }521 }
545}522}
546523