| ... | @@ -507,15 +507,223 @@ pub fn dupeZ(allocator: Allocator, comptime T: type, m: []const T) ![:0]T { | ... | @@ -507,15 +507,223 @@ pub fn dupeZ(allocator: Allocator, comptime T: type, m: []const T) ![:0]T { |
| 507 | return new_buf[0..m.len :0]; | 507 | return new_buf[0..m.len :0]; |
| 508 | } | 508 | } |
| 509 | | 509 | |
| | 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 | |
| 510 | /// Call `vtable.resize`, but caller guarantees that `new_len` <= `buf.len` meaning | 710 | /// Call `vtable.resize`, but caller guarantees that `new_len` <= `buf.len` meaning |
| 511 | /// than a `null` return value should be impossible. | 711 | /// than a `null` return value should be impossible. |
| 512 | /// This function allows a runtime `buf_align` value. Callers should generally prefer | 712 | /// This function allows a runtime `buf_align` value. Callers should generally prefer |
| 513 | /// to call `shrink` directly. | 713 | /// to call `shrink`. |
| 514 | pub fn shrinkBytes( | 714 | pub fn shrinkBytes( |
| 515 | self: Allocator, | 715 | self: Allocator, |
| | 716 | /// Must be the same as what was returned from most recent call to `allocFn` or `resizeFn`. |
| 516 | buf: []u8, | 717 | buf: []u8, |
| | 718 | /// Must be the same as what was passed to `allocFn`. |
| | 719 | /// Must be >= 1. |
| | 720 | /// Must be a power of 2. |
| 517 | buf_align: u29, | 721 | buf_align: u29, |
| | 722 | /// Must be >= 1. |
| 518 | new_len: usize, | 723 | 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` |
| 519 | len_align: u29, | 727 | len_align: u29, |
| 520 | return_address: usize, | 728 | return_address: usize, |
| 521 | ) usize { | 729 | ) usize { |