add realloc (and update c_allocator)

This commit is contained in:
2026-07-11 13:42:55 +02:00
parent 90869dcb4d
commit 6dd6b7ff54
6 changed files with 122 additions and 5 deletions
+1 -2
View File
@@ -72,8 +72,7 @@ roadmap and milestone history.
### standard packages
- `std/mem` allocator contract over byte allocation: `Allocator` with `?*mut anyopaque` context, `heap`, `alloc(allocator, size, alignment) ?*mut u8`, and `free(allocator, ptr, size, alignment)`
- `std/mem/heap` legacy compatibility wrappers: `alloc(size usize) ?*mut u8` and `free(ptr ?*mut u8)`
- `std/mem` allocator contract over byte allocation: `Allocator` with `?*mut anyopaque` context plus `alloc`, `realloc`, and `free`; failed nonzero reallocation preserves the original allocation, while zero size frees it
### compiler behavior
+5 -3
View File
@@ -627,9 +627,10 @@
parameters
25. dynamic heap allocation (implemented; v1)
- `std/mem` exposes a plain-data `Allocator` contract with `?*mut anyopaque` context, `alloc`, and `free` `@func` pointers
- `std/mem` exposes a plain-data `Allocator` contract with `?*mut anyopaque` context and `alloc`, `realloc`, and `free` `@func` pointers
- `mem.c_allocator` is the libc-backed allocator; `mem.alloc(mem.c_allocator, size, alignment)` returns nullable mutable byte memory
- `mem.free(mem.c_allocator, ptr, size, alignment)` frees with the same allocator; `malloc` handles default-aligned requests and `posix_memalign` handles larger power-of-two alignments
- `mem.realloc` preserves alignment and the original allocation on failure; zero size frees, and over-aligned blocks use allocate/copy/free
- typed allocation helpers, arenas/pools, build-mode heap policy, and escaping-allocation diagnostics remain deferred
26. import from project "root" (implemented)
@@ -718,7 +719,7 @@
- native function pointers are non-variadic v1; C variadic function pointers stay
under `*c_func(...) R`
28. brolang build system v0 shipped
28. brolang build system (implemented; v0 shipped)
- `brolang build [root]` reads a declarative `config` constant from
`root/build.bro` (importing `@std/build`'s `BuildConfig`) and compiles the
program package it names. The config is read from the checked HIR — build.bro
@@ -1397,7 +1398,7 @@ Memory allocation in Brolang is designed to be **explicit but not verbose**. We
Brolang provides a libc-backed allocator value:
* Available as `mem.c_allocator`
* Passed explicitly to `mem.alloc` and `mem.free`
* Passed explicitly to `mem.alloc`, `mem.realloc`, and `mem.free`
* **Immutable at runtime** — cannot be reconfigured
```
@@ -1595,6 +1596,7 @@ main func() void {
| What | How | When to Use |
| -- | -- | -- |
| `mem.alloc(mem.c_allocator, n, a)` | Libc-backed allocator | General purpose byte allocation |
| `mem.realloc(mem.c_allocator, ptr, old_n, new_n, a)` | Libc-backed allocator | Resize while preserving alignment and up to `min(old_n, new_n)` bytes |
| `mem.free(mem.c_allocator, ptr, n, a)` | Libc-backed allocator | Free byte allocation with original size/alignment |
| `mem.alloc(allocator, n, a)` | Caller-provided allocator | Escaping allocations (returned or written to caller's data) |
| typed helpers / arenas / pools | Future APIs | Higher-level allocation patterns |
+4
View File
@@ -3282,6 +3282,7 @@ allocator_contract_c_allocator_global_lowers :: proc(t: ^testing.T) {
found_c_allocator := false
found_anyopaque_context := false
found_alloc_callback := false
found_realloc_callback := false
found_free_callback := false
for global in hir_module.globals {
if symbol.resolve(&symbols, global.name) != "c_allocator" {
@@ -3303,6 +3304,7 @@ allocator_contract_c_allocator_global_lowers :: proc(t: ^testing.T) {
}
}
found_alloc_callback = found_alloc_callback || name == "alloc" && callable && !callback_pointer.many
found_realloc_callback = found_realloc_callback || name == "realloc" && callable && !callback_pointer.many
found_free_callback = found_free_callback || name == "free" && callable && !callback_pointer.many
}
}
@@ -3312,6 +3314,7 @@ allocator_contract_c_allocator_global_lowers :: proc(t: ^testing.T) {
testing.expect(t, found_c_allocator)
testing.expect(t, found_anyopaque_context)
testing.expect(t, found_alloc_callback)
testing.expect(t, found_realloc_callback)
testing.expect(t, found_free_callback)
}
@@ -3346,6 +3349,7 @@ milestone_25_c_allocator_emits_libc_alloc_declarations :: proc(t: ^testing.T) {
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, strings.contains(llvm_text, "declare ptr @malloc(i64)"))
testing.expect(t, strings.contains(llvm_text, "declare ptr @realloc(ptr, i64)"))
testing.expect(t, strings.contains(llvm_text, "declare i32 @posix_memalign(ptr, i64, i64)"))
testing.expect(t, strings.contains(llvm_text, "declare void @free(ptr)"))
}
+72
View File
@@ -150,6 +150,78 @@ task_list_deinit func(list @mut TaskList) void {
}
main func() i32 {
resized ?*mut u8 = mem.realloc(mem.c_allocator, none, 0, 4, 1)
if resized |bytes| {
bytes[0] = 10
bytes[1] = 20
bytes[2] = 30
bytes[3] = 40
} else {
return 20
}
grown ?*mut u8 = mem.realloc(mem.c_allocator, resized, 4, 8, 1)
if grown |bytes| {
resized = grown
if bytes[0] != 10 or bytes[1] != 20 or bytes[2] != 30 or bytes[3] != 40 {
mem.free(mem.c_allocator, grown, 8, 1)
return 21
}
} else {
mem.free(mem.c_allocator, resized, 4, 1)
return 22
}
shrunk ?*mut u8 = mem.realloc(mem.c_allocator, resized, 8, 2, 1)
if shrunk |bytes| {
resized = shrunk
if bytes[0] != 10 or bytes[1] != 20 {
mem.free(mem.c_allocator, shrunk, 2, 1)
return 23
}
} else {
mem.free(mem.c_allocator, resized, 8, 1)
return 24
}
invalid ?*mut u8 = mem.realloc(mem.c_allocator, resized, 2, 4, 24)
if invalid |memory| {
mem.free(mem.c_allocator, memory, 4, 24)
mem.free(mem.c_allocator, resized, 2, 1)
return 25
}
if resized |bytes| {
if bytes[0] != 10 or bytes[1] != 20 {
mem.free(mem.c_allocator, resized, 2, 1)
return 26
}
}
resized = mem.realloc(mem.c_allocator, resized, 2, 0, 1)
if resized |memory| {
mem.free(mem.c_allocator, memory, 0, 1)
return 27
}
over_aligned ?*mut u8 = mem.alloc(mem.c_allocator, 4, 32)
if over_aligned |bytes| {
bytes[0] = 11
bytes[1] = 22
} else {
return 28
}
over_aligned_grown ?*mut u8 = mem.realloc(mem.c_allocator, over_aligned, 4, 8, 32)
if over_aligned_grown |bytes| {
if bytes[0] != 11 or bytes[1] != 22 {
mem.free(mem.c_allocator, over_aligned_grown, 8, 32)
return 29
}
mem.free(mem.c_allocator, over_aligned_grown, 8, 32)
} else {
mem.free(mem.c_allocator, over_aligned, 4, 32)
return 30
}
zero_alignment ?*mut u8 = mem.alloc(mem.c_allocator, 8, 0)
if zero_alignment |memory| {
mem.free(mem.c_allocator, memory, 8, 0)
+1
View File
@@ -1,3 +1,4 @@
malloc c_func(__size c_ulong) ?*mut anyopaque
realloc c_func(__ptr ?*mut anyopaque, __size c_ulong) ?*mut anyopaque
free c_func(_ ?*mut anyopaque) void
posix_memalign c_func(__memptr ?*mut ?*mut anyopaque, __alignment c_ulong, __size c_ulong) c_int
+39
View File
@@ -3,6 +3,7 @@ c :: import "@ffi/c"
Allocator :: struct {
context ?*mut anyopaque
alloc @func(context ?*mut anyopaque, size usize, alignment usize) ?*mut u8
realloc @func(context ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8
free @func(context ?*mut anyopaque, memory ?*mut u8, size usize, alignment usize) void
}
@@ -43,6 +44,39 @@ _c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
return ptr_cast(u8, memory[0])
}
_c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
if _power_of_two(alignment) == false {
return none
}
if new_size == 0 {
c.free(memory)
return none
}
if memory |old_memory| {
if alignment <= _malloc_alignment {
return ptr_cast(u8, c.realloc(old_memory, c_ulong(new_size)))
}
new_memory ?*mut u8 = _c_alloc(none, new_size, alignment)
if new_memory |new_bytes| {
copy_size usize = old_size
if new_size < copy_size {
copy_size = new_size
}
i usize = 0
while i < copy_size : i += 1 {
new_bytes[i] = old_memory[i]
}
c.free(old_memory)
}
return new_memory
}
return _c_alloc(none, new_size, alignment)
}
_c_free func(_ ?*mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
c.free(memory)
}
@@ -50,6 +84,7 @@ _c_free func(_ ?*mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
c_allocator Allocator :: Allocator {
context = none,
alloc = _c_alloc,
realloc = _c_realloc,
free = _c_free,
}
@@ -57,6 +92,10 @@ alloc func(allocator Allocator, size usize, alignment usize) ?*mut u8 {
return allocator.alloc(allocator.context, size, alignment)
}
realloc func(allocator Allocator, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
return allocator.realloc(allocator.context, memory, old_size, new_size, alignment)
}
free func(allocator Allocator, memory ?*mut u8, size usize, alignment usize) void {
allocator.free(allocator.context, memory, size, alignment)
}