Refactor core:mem/tlsf, add free_all support.

TODO: Allow the TLSF allocator to add additional pools when it would ordinarily OOM
      by calling its backing allocator.
This commit is contained in:
Jeroen van Rijn
2025-04-14 17:13:27 +02:00
parent 0e9cd0fb6a
commit 7088284ff4
3 changed files with 569 additions and 445 deletions
+102
View File
@@ -1,8 +1,10 @@
package test_core_mem
import "core:mem"
import "core:mem/tlsf"
import "core:mem/virtual"
import "core:testing"
import "core:slice"
@test
test_tlsf_bitscan :: proc(t: ^testing.T) {
@@ -54,3 +56,103 @@ test_align_bumping_block_limit :: proc(t: ^testing.T) {
testing.expect_value(t, err, nil)
testing.expect(t, len(data) == 896)
}
@(test)
tlsf_test_overlap_and_zero :: proc(t: ^testing.T) {
default_allocator := context.allocator
alloc: tlsf.Allocator
defer tlsf.destroy(&alloc)
NUM_ALLOCATIONS :: 1_000
BACKING_SIZE :: NUM_ALLOCATIONS * (1_000 + size_of(uintptr))
if err := tlsf.init_from_allocator(&alloc, default_allocator, BACKING_SIZE); err != .None {
testing.fail_now(t, "TLSF init error")
}
context.allocator = tlsf.allocator(&alloc)
allocations := make([dynamic][]byte, 0, NUM_ALLOCATIONS, default_allocator)
defer delete(allocations)
err: mem.Allocator_Error
s: []byte
for size := 1; err == .None && size <= NUM_ALLOCATIONS; size += 1 {
s, err = make([]byte, size)
append(&allocations, s)
}
slice.sort_by(allocations[:len(allocations)], proc(a, b: []byte) -> bool {
return uintptr(raw_data(a)) < uintptr(raw_data((b)))
})
for i in 0..<len(allocations) - 1 {
fail_if_allocations_overlap(t, allocations[i], allocations[i + 1])
fail_if_not_zeroed(t, allocations[i])
}
}
@(test)
tlsf_test_free_all :: proc(t: ^testing.T) {
default_allocator := context.allocator
alloc: tlsf.Allocator
defer tlsf.destroy(&alloc)
NUM_ALLOCATIONS :: 10
ALLOCATION_SIZE :: mem.Megabyte
BACKING_SIZE :: NUM_ALLOCATIONS * (ALLOCATION_SIZE + size_of(uintptr))
if init_err := tlsf.init_from_allocator(&alloc, default_allocator, BACKING_SIZE); init_err != .None {
testing.fail_now(t, "TLSF init error")
}
context.allocator = tlsf.allocator(&alloc)
allocations: [2][dynamic][]byte
allocations[0] = make([dynamic][]byte, 0, NUM_ALLOCATIONS, default_allocator) // After `init`
allocations[1] = make([dynamic][]byte, 0, NUM_ALLOCATIONS, default_allocator) // After `free_all`
defer {
delete(allocations[0])
delete(allocations[1])
}
for {
s := make([]byte, ALLOCATION_SIZE) or_break
append(&allocations[0], s)
}
testing.expect(t, len(allocations[0]) >= 10)
free_all(tlsf.allocator(&alloc))
for {
s := make([]byte, ALLOCATION_SIZE) or_break
append(&allocations[1], s)
}
testing.expect(t, len(allocations[1]) >= 10)
for i in 0..<len(allocations[0]) {
s0, s1 := allocations[0][i], allocations[1][i]
assert(raw_data(s0) == raw_data((s1)))
assert(len(s0) == len((s1)))
}
}
fail_if_not_zeroed :: proc(t: ^testing.T, a: []byte) {
for b in a {
if b != 0 {
testing.fail_now(t, "Allocation wasn't zeroed")
}
}
}
fail_if_allocations_overlap :: proc(t: ^testing.T, a, b: []byte) {
a, b := a, b
a_start := uintptr(raw_data(a))
a_end := a_start + uintptr(len(a))
b_start := uintptr(raw_data(b))
b_end := b_start + uintptr(len(b))
if a_end >= b_end && b_end >= a_start {
testing.fail_now(t, "Allocations overlapped")
}
}