authorgravatar for leecannon@leecannon.xyzLee Cannon <leecannon@leecannon.xyz> 2021-12-19 06:58:13+00:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-12-19 01:58:13-05:00
log16b753549702e2fc662491a468dd3323e8a51042
treeac4531ce5ad53ebd85ac6a321d82fc155084cfad
parent80b21cef5af29b7473332f2f902a64caba687dc8
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Allocator: `allocBytes` and `reallocBytes` (#10352)

Closes #10348

1 files changed, 209 insertions(+), 1 deletions(-)

lib/std/mem/Allocator.zig+209-1
......@@ -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.
512pub 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
531test "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
554test "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.
603pub 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
663test "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`.
514714pub 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 {