Skip to content

Commit 16b7535

Browse files
authored
Allocator: allocBytes and reallocBytes (#10352)
Closes #10348
1 parent 80b21ce commit 16b7535

File tree

1 file changed

+209
-1
lines changed

1 file changed

+209
-1
lines changed

lib/std/mem/Allocator.zig

Lines changed: 209 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,15 +507,223 @@ pub fn dupeZ(allocator: Allocator, comptime T: type, m: []const T) ![:0]T {
507507
return new_buf[0..m.len :0];
508508
}
509509

510+
/// This function allows a runtime `alignment` value. Callers should generally prefer
511+
/// to call the `alloc*` functions.
512+
pub fn allocBytes(
513+
self: Allocator,
514+
/// Must be >= 1.
515+
/// Must be a power of 2.
516+
/// Returned slice's pointer will have this alignment.
517+
alignment: u29,
518+
byte_count: usize,
519+
/// 0 indicates the length of the slice returned MUST match `byte_count` exactly
520+
/// non-zero means the length of the returned slice must be aligned by `len_align`
521+
/// `byte_count` must be aligned by `len_align`
522+
len_align: u29,
523+
return_address: usize,
524+
) Error![]u8 {
525+
const new_mem = try self.rawAlloc(byte_count, alignment, len_align, return_address);
526+
// TODO: https://github.com/ziglang/zig/issues/4298
527+
@memset(new_mem.ptr, undefined, new_mem.len);
528+
return new_mem;
529+
}
530+
531+
test "allocBytes" {
532+
const number_of_bytes: usize = 10;
533+
var runtime_alignment: u29 = 2;
534+
535+
{
536+
const new_mem = try std.testing.allocator.allocBytes(runtime_alignment, number_of_bytes, 0, @returnAddress());
537+
defer std.testing.allocator.free(new_mem);
538+
539+
try std.testing.expectEqual(number_of_bytes, new_mem.len);
540+
try std.testing.expect(mem.isAligned(@ptrToInt(new_mem.ptr), runtime_alignment));
541+
}
542+
543+
runtime_alignment = 8;
544+
545+
{
546+
const new_mem = try std.testing.allocator.allocBytes(runtime_alignment, number_of_bytes, 0, @returnAddress());
547+
defer std.testing.allocator.free(new_mem);
548+
549+
try std.testing.expectEqual(number_of_bytes, new_mem.len);
550+
try std.testing.expect(mem.isAligned(@ptrToInt(new_mem.ptr), runtime_alignment));
551+
}
552+
}
553+
554+
test "allocBytes non-zero len_align" {
555+
const number_of_bytes: usize = 10;
556+
var runtime_alignment: u29 = 1;
557+
var len_align: u29 = 2;
558+
559+
{
560+
const new_mem = try std.testing.allocator.allocBytes(runtime_alignment, number_of_bytes, len_align, @returnAddress());
561+
defer std.testing.allocator.free(new_mem);
562+
563+
try std.testing.expect(new_mem.len >= number_of_bytes);
564+
try std.testing.expect(new_mem.len % len_align == 0);
565+
try std.testing.expect(mem.isAligned(@ptrToInt(new_mem.ptr), runtime_alignment));
566+
}
567+
568+
runtime_alignment = 16;
569+
len_align = 5;
570+
571+
{
572+
const new_mem = try std.testing.allocator.allocBytes(runtime_alignment, number_of_bytes, len_align, @returnAddress());
573+
defer std.testing.allocator.free(new_mem);
574+
575+
try std.testing.expect(new_mem.len >= number_of_bytes);
576+
try std.testing.expect(new_mem.len % len_align == 0);
577+
try std.testing.expect(mem.isAligned(@ptrToInt(new_mem.ptr), runtime_alignment));
578+
}
579+
}
580+
581+
/// Realloc is used to modify the size or alignment of an existing allocation,
582+
/// as well as to provide the allocator with an opportunity to move an allocation
583+
/// to a better location.
584+
/// The returned slice will have its pointer aligned at least to `new_alignment` bytes.
585+
///
586+
/// This function allows a runtime `alignment` value. Callers should generally prefer
587+
/// to call the `realloc*` functions.
588+
///
589+
/// If the size/alignment is greater than the previous allocation, and the requested new
590+
/// allocation could not be granted this function returns `error.OutOfMemory`.
591+
/// When the size/alignment is less than or equal to the previous allocation,
592+
/// this function returns `error.OutOfMemory` when the allocator decides the client
593+
/// would be better off keeping the extra alignment/size.
594+
/// Clients will call `resizeFn` when they require the allocator to track a new alignment/size,
595+
/// and so this function should only return success when the allocator considers
596+
/// the reallocation desirable from the allocator's perspective.
597+
///
598+
/// As an example, `std.ArrayList` tracks a "capacity", and therefore can handle
599+
/// reallocation failure, even when `new_n` <= `old_mem.len`. A `FixedBufferAllocator`
600+
/// would always return `error.OutOfMemory` for `reallocFn` when the size/alignment
601+
/// is less than or equal to the old allocation, because it cannot reclaim the memory,
602+
/// and thus the `std.ArrayList` would be better off retaining its capacity.
603+
pub fn reallocBytes(
604+
self: Allocator,
605+
/// Must be the same as what was returned from most recent call to `allocFn` or `resizeFn`.
606+
/// If `old_mem.len == 0` then this is a new allocation and `new_byte_count` must be >= 1.
607+
old_mem: []u8,
608+
/// If `old_mem.len == 0` then this is `undefined`, otherwise:
609+
/// Must be the same as what was passed to `allocFn`.
610+
/// Must be >= 1.
611+
/// Must be a power of 2.
612+
old_alignment: u29,
613+
/// If `new_byte_count` is 0 then this is a free and it is required that `old_mem.len != 0`.
614+
new_byte_count: usize,
615+
/// Must be >= 1.
616+
/// Must be a power of 2.
617+
/// Returned slice's pointer will have this alignment.
618+
new_alignment: u29,
619+
/// 0 indicates the length of the slice returned MUST match `new_byte_count` exactly
620+
/// non-zero means the length of the returned slice must be aligned by `len_align`
621+
/// `new_byte_count` must be aligned by `len_align`
622+
len_align: u29,
623+
return_address: usize,
624+
) Error![]u8 {
625+
if (old_mem.len == 0) {
626+
return self.allocBytes(new_alignment, new_byte_count, len_align, return_address);
627+
}
628+
if (new_byte_count == 0) {
629+
// TODO https://github.com/ziglang/zig/issues/4298
630+
@memset(old_mem.ptr, undefined, old_mem.len);
631+
self.rawFree(old_mem, old_alignment, return_address);
632+
return &[0]u8{};
633+
}
634+
635+
if (mem.isAligned(@ptrToInt(old_mem.ptr), new_alignment)) {
636+
if (new_byte_count <= old_mem.len) {
637+
const shrunk_len = self.shrinkBytes(old_mem, old_alignment, new_byte_count, len_align, return_address);
638+
return old_mem.ptr[0..shrunk_len];
639+
}
640+
641+
if (self.rawResize(old_mem, old_alignment, new_byte_count, len_align, return_address)) |resized_len| {
642+
assert(resized_len >= new_byte_count);
643+
// TODO: https://github.com/ziglang/zig/issues/4298
644+
@memset(old_mem.ptr + new_byte_count, undefined, resized_len - new_byte_count);
645+
return old_mem.ptr[0..resized_len];
646+
}
647+
}
648+
649+
if (new_byte_count <= old_mem.len and new_alignment <= old_alignment) {
650+
return error.OutOfMemory;
651+
}
652+
653+
const new_mem = try self.rawAlloc(new_byte_count, new_alignment, len_align, return_address);
654+
@memcpy(new_mem.ptr, old_mem.ptr, math.min(new_byte_count, old_mem.len));
655+
656+
// TODO https://github.com/ziglang/zig/issues/4298
657+
@memset(old_mem.ptr, undefined, old_mem.len);
658+
self.rawFree(old_mem, old_alignment, return_address);
659+
660+
return new_mem;
661+
}
662+
663+
test "reallocBytes" {
664+
var new_mem: []u8 = &.{};
665+
666+
var new_byte_count: usize = 16;
667+
var runtime_alignment: u29 = 4;
668+
669+
// `new_mem.len == 0`, this is a new allocation
670+
{
671+
new_mem = try std.testing.allocator.reallocBytes(new_mem, undefined, new_byte_count, runtime_alignment, 0, @returnAddress());
672+
try std.testing.expectEqual(new_byte_count, new_mem.len);
673+
try std.testing.expect(mem.isAligned(@ptrToInt(new_mem.ptr), runtime_alignment));
674+
}
675+
676+
// `new_byte_count < new_mem.len`, this is a shrink, alignment is unmodified
677+
new_byte_count = 14;
678+
{
679+
new_mem = try std.testing.allocator.reallocBytes(new_mem, runtime_alignment, new_byte_count, runtime_alignment, 0, @returnAddress());
680+
try std.testing.expectEqual(new_byte_count, new_mem.len);
681+
try std.testing.expect(mem.isAligned(@ptrToInt(new_mem.ptr), runtime_alignment));
682+
}
683+
684+
// `new_byte_count < new_mem.len`, this is a shrink, alignment is decreased from 4 to 2
685+
runtime_alignment = 2;
686+
new_byte_count = 12;
687+
{
688+
new_mem = try std.testing.allocator.reallocBytes(new_mem, 4, new_byte_count, runtime_alignment, 0, @returnAddress());
689+
try std.testing.expectEqual(new_byte_count, new_mem.len);
690+
try std.testing.expect(mem.isAligned(@ptrToInt(new_mem.ptr), runtime_alignment));
691+
}
692+
693+
// `new_byte_count > new_mem.len`, this is a growth, alignment is increased from 2 to 8
694+
runtime_alignment = 8;
695+
new_byte_count = 32;
696+
{
697+
new_mem = try std.testing.allocator.reallocBytes(new_mem, 2, new_byte_count, runtime_alignment, 0, @returnAddress());
698+
try std.testing.expectEqual(new_byte_count, new_mem.len);
699+
try std.testing.expect(mem.isAligned(@ptrToInt(new_mem.ptr), runtime_alignment));
700+
}
701+
702+
// `new_byte_count == 0`, this is a free
703+
new_byte_count = 0;
704+
{
705+
new_mem = try std.testing.allocator.reallocBytes(new_mem, runtime_alignment, new_byte_count, runtime_alignment, 0, @returnAddress());
706+
try std.testing.expectEqual(new_byte_count, new_mem.len);
707+
}
708+
}
709+
510710
/// Call `vtable.resize`, but caller guarantees that `new_len` <= `buf.len` meaning
511711
/// than a `null` return value should be impossible.
512712
/// This function allows a runtime `buf_align` value. Callers should generally prefer
513-
/// to call `shrink` directly.
713+
/// to call `shrink`.
514714
pub fn shrinkBytes(
515715
self: Allocator,
716+
/// Must be the same as what was returned from most recent call to `allocFn` or `resizeFn`.
516717
buf: []u8,
718+
/// Must be the same as what was passed to `allocFn`.
719+
/// Must be >= 1.
720+
/// Must be a power of 2.
517721
buf_align: u29,
722+
/// Must be >= 1.
518723
new_len: usize,
724+
/// 0 indicates the length of the slice returned MUST match `new_len` exactly
725+
/// non-zero means the length of the returned slice must be aligned by `len_align`
726+
/// `new_len` must be aligned by `len_align`
519727
len_align: u29,
520728
return_address: usize,
521729
) usize {

0 commit comments

Comments
 (0)