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 = .{
368368 .vtable = &BrkAllocator.vtable,
369369};
370370
371/// Returns a `StackFallbackAllocator` allocating using either a
372/// `FixedBufferAllocator` on an array of size `size` and falling back to
373/// `fallback_allocator` if that fails.
374pub fn stackFallback(comptime size: usize, fallback_allocator: Allocator) StackFallbackAllocator(size) {
375 return StackFallbackAllocator(size){
376 .buffer = undefined,
377 .fallback_allocator = fallback_allocator,
378 .fixed_buffer_allocator = undefined,
379 };
380}
371/// An allocator that attempts to allocate from the given buffer, falling back to
372/// `fallback_allocator` if this fails.
373pub const StackFallbackAllocator = struct {
374 const Self = @This();
375
376 fallback_allocator: Allocator,
377 fixed_buffer_allocator: FixedBufferAllocator,
378
379 pub fn init(buf: []u8, fallback_allocator: Allocator) Self {
380 return .{
381 .fallback_allocator = fallback_allocator,
382 .fixed_buffer_allocator = .init(buf),
383 };
384 }
381385
382/// An allocator that attempts to allocate using a
383/// `FixedBufferAllocator` using an array of size `size`. If the
384/// allocation fails, it will fall back to using
385/// `fallback_allocator`. Easily created with `stackFallback`.
386pub fn StackFallbackAllocator(comptime size: usize) type {
387 return struct {
388 const Self = @This();
389
390 buffer: [size]u8,
391 fallback_allocator: Allocator,
392 fixed_buffer_allocator: FixedBufferAllocator,
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 }
386 pub fn allocator(self: *Self) Allocator {
387 return .{
388 .ptr = self,
389 .vtable = &.{
390 .alloc = alloc,
391 .resize = resize,
392 .remap = remap,
393 .free = free,
394 },
395 };
396 }
414397
415 /// Unlike most std allocators `StackFallbackAllocator` modifies
416 /// its internal state before returning an implementation of
417 /// the`Allocator` interface and therefore also doesn't use
418 /// the usual `.allocator()` method.
419 pub const allocator = @compileError("use 'const allocator = stackFallback(N).get();' instead");
420
421 fn alloc(
422 ctx: *anyopaque,
423 len: usize,
424 alignment: Alignment,
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 }
398 fn alloc(
399 ctx: *anyopaque,
400 len: usize,
401 alignment: Alignment,
402 ra: usize,
403 ) ?[*]u8 {
404 const self: *Self = @ptrCast(@alignCast(ctx));
405 return FixedBufferAllocator.alloc(&self.fixed_buffer_allocator, len, alignment, ra) orelse
406 return self.fallback_allocator.rawAlloc(len, alignment, ra);
407 }
431408
432 fn resize(
433 ctx: *anyopaque,
434 buf: []u8,
435 alignment: Alignment,
436 new_len: usize,
437 ra: usize,
438 ) bool {
439 const self: *Self = @ptrCast(@alignCast(ctx));
440 if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) {
441 return FixedBufferAllocator.resize(&self.fixed_buffer_allocator, buf, alignment, new_len, ra);
442 } else {
443 return self.fallback_allocator.rawResize(buf, alignment, new_len, ra);
444 }
409 fn resize(
410 ctx: *anyopaque,
411 buf: []u8,
412 alignment: Alignment,
413 new_len: usize,
414 ra: usize,
415 ) bool {
416 const self: *Self = @ptrCast(@alignCast(ctx));
417 if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) {
418 return FixedBufferAllocator.resize(&self.fixed_buffer_allocator, buf, alignment, new_len, ra);
419 } else {
420 return self.fallback_allocator.rawResize(buf, alignment, new_len, ra);
445421 }
422 }
446423
447 fn remap(
448 context: *anyopaque,
449 memory: []u8,
450 alignment: Alignment,
451 new_len: usize,
452 return_address: usize,
453 ) ?[*]u8 {
454 const self: *Self = @ptrCast(@alignCast(context));
455 if (self.fixed_buffer_allocator.ownsPtr(memory.ptr)) {
456 return FixedBufferAllocator.remap(&self.fixed_buffer_allocator, memory, alignment, new_len, return_address);
457 } else {
458 return self.fallback_allocator.rawRemap(memory, alignment, new_len, return_address);
459 }
424 fn remap(
425 context: *anyopaque,
426 memory: []u8,
427 alignment: Alignment,
428 new_len: usize,
429 return_address: usize,
430 ) ?[*]u8 {
431 const self: *Self = @ptrCast(@alignCast(context));
432 if (self.fixed_buffer_allocator.ownsPtr(memory.ptr)) {
433 return FixedBufferAllocator.remap(&self.fixed_buffer_allocator, memory, alignment, new_len, return_address);
434 } else {
435 return self.fallback_allocator.rawRemap(memory, alignment, new_len, return_address);
460436 }
437 }
461438
462 fn free(
463 ctx: *anyopaque,
464 buf: []u8,
465 alignment: Alignment,
466 ra: usize,
467 ) void {
468 const self: *Self = @ptrCast(@alignCast(ctx));
469 if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) {
470 return FixedBufferAllocator.free(&self.fixed_buffer_allocator, buf, alignment, ra);
471 } else {
472 return self.fallback_allocator.rawFree(buf, alignment, ra);
473 }
439 fn free(
440 ctx: *anyopaque,
441 buf: []u8,
442 alignment: Alignment,
443 ra: usize,
444 ) void {
445 const self: *Self = @ptrCast(@alignCast(ctx));
446 if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) {
447 return FixedBufferAllocator.free(&self.fixed_buffer_allocator, buf, alignment, ra);
448 } else {
449 return self.fallback_allocator.rawFree(buf, alignment, ra);
474450 }
475 };
476}
451 }
452};
477453
478454test c_allocator {
479455 if (builtin.link_libc) {
......@@ -525,22 +501,23 @@ test ArenaAllocator {
525501 try testAllocatorAlignedShrink(allocator);
526502}
527503
528test "StackFallbackAllocator" {
504test StackFallbackAllocator {
505 var buf: [4096]u8 = undefined;
529506 {
530 var stack_allocator = stackFallback(4096, std.testing.allocator);
531 try testAllocator(stack_allocator.get());
507 var stack_allocator: StackFallbackAllocator = .init(&buf, std.testing.allocator);
508 try testAllocator(stack_allocator.allocator());
532509 }
533510 {
534 var stack_allocator = stackFallback(4096, std.testing.allocator);
535 try testAllocatorAligned(stack_allocator.get());
511 var stack_allocator: StackFallbackAllocator = .init(&buf, std.testing.allocator);
512 try testAllocatorAligned(stack_allocator.allocator());
536513 }
537514 {
538 var stack_allocator = stackFallback(4096, std.testing.allocator);
539 try testAllocatorLargeAlignment(stack_allocator.get());
515 var stack_allocator: StackFallbackAllocator = .init(&buf, std.testing.allocator);
516 try testAllocatorLargeAlignment(stack_allocator.allocator());
540517 }
541518 {
542 var stack_allocator = stackFallback(4096, std.testing.allocator);
543 try testAllocatorAlignedShrink(stack_allocator.get());
519 var stack_allocator: StackFallbackAllocator = .init(&buf, std.testing.allocator);
520 try testAllocatorAlignedShrink(stack_allocator.allocator());
544521 }
545522}
546523