add realloc (and update c_allocator)
This commit is contained in:
+1
-2
@@ -72,8 +72,7 @@ roadmap and milestone history.
|
|||||||
|
|
||||||
### standard packages
|
### 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` 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
|
||||||
- `std/mem/heap` legacy compatibility wrappers: `alloc(size usize) ?*mut u8` and `free(ptr ?*mut u8)`
|
|
||||||
|
|
||||||
### compiler behavior
|
### compiler behavior
|
||||||
|
|
||||||
|
|||||||
@@ -627,9 +627,10 @@
|
|||||||
parameters
|
parameters
|
||||||
|
|
||||||
25. dynamic heap allocation (implemented; v1)
|
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.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.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
|
- typed allocation helpers, arenas/pools, build-mode heap policy, and escaping-allocation diagnostics remain deferred
|
||||||
|
|
||||||
26. import from project "root" (implemented)
|
26. import from project "root" (implemented)
|
||||||
@@ -718,7 +719,7 @@
|
|||||||
- native function pointers are non-variadic v1; C variadic function pointers stay
|
- native function pointers are non-variadic v1; C variadic function pointers stay
|
||||||
under `*c_func(...) R`
|
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
|
- `brolang build [root]` reads a declarative `config` constant from
|
||||||
`root/build.bro` (importing `@std/build`'s `BuildConfig`) and compiles the
|
`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
|
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:
|
Brolang provides a libc-backed allocator value:
|
||||||
|
|
||||||
* Available as `mem.c_allocator`
|
* 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
|
* **Immutable at runtime** — cannot be reconfigured
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -1595,6 +1596,7 @@ main func() void {
|
|||||||
| What | How | When to Use |
|
| What | How | When to Use |
|
||||||
| -- | -- | -- |
|
| -- | -- | -- |
|
||||||
| `mem.alloc(mem.c_allocator, n, a)` | Libc-backed allocator | General purpose byte allocation |
|
| `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.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) |
|
| `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 |
|
| typed helpers / arenas / pools | Future APIs | Higher-level allocation patterns |
|
||||||
|
|||||||
@@ -3282,6 +3282,7 @@ allocator_contract_c_allocator_global_lowers :: proc(t: ^testing.T) {
|
|||||||
found_c_allocator := false
|
found_c_allocator := false
|
||||||
found_anyopaque_context := false
|
found_anyopaque_context := false
|
||||||
found_alloc_callback := false
|
found_alloc_callback := false
|
||||||
|
found_realloc_callback := false
|
||||||
found_free_callback := false
|
found_free_callback := false
|
||||||
for global in hir_module.globals {
|
for global in hir_module.globals {
|
||||||
if symbol.resolve(&symbols, global.name) != "c_allocator" {
|
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_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
|
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_c_allocator)
|
||||||
testing.expect(t, found_anyopaque_context)
|
testing.expect(t, found_anyopaque_context)
|
||||||
testing.expect(t, found_alloc_callback)
|
testing.expect(t, found_alloc_callback)
|
||||||
|
testing.expect(t, found_realloc_callback)
|
||||||
testing.expect(t, found_free_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_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 @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 i32 @posix_memalign(ptr, i64, i64)"))
|
||||||
testing.expect(t, strings.contains(llvm_text, "declare void @free(ptr)"))
|
testing.expect(t, strings.contains(llvm_text, "declare void @free(ptr)"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,6 +150,78 @@ task_list_deinit func(list @mut TaskList) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
main func() i32 {
|
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)
|
zero_alignment ?*mut u8 = mem.alloc(mem.c_allocator, 8, 0)
|
||||||
if zero_alignment |memory| {
|
if zero_alignment |memory| {
|
||||||
mem.free(mem.c_allocator, memory, 8, 0)
|
mem.free(mem.c_allocator, memory, 8, 0)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
malloc c_func(__size c_ulong) ?*mut anyopaque
|
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
|
free c_func(_ ?*mut anyopaque) void
|
||||||
posix_memalign c_func(__memptr ?*mut ?*mut anyopaque, __alignment c_ulong, __size c_ulong) c_int
|
posix_memalign c_func(__memptr ?*mut ?*mut anyopaque, __alignment c_ulong, __size c_ulong) c_int
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ c :: import "@ffi/c"
|
|||||||
Allocator :: struct {
|
Allocator :: struct {
|
||||||
context ?*mut anyopaque
|
context ?*mut anyopaque
|
||||||
alloc @func(context ?*mut anyopaque, size usize, alignment usize) ?*mut u8
|
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
|
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])
|
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 func(_ ?*mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
|
||||||
c.free(memory)
|
c.free(memory)
|
||||||
}
|
}
|
||||||
@@ -50,6 +84,7 @@ _c_free func(_ ?*mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
|
|||||||
c_allocator Allocator :: Allocator {
|
c_allocator Allocator :: Allocator {
|
||||||
context = none,
|
context = none,
|
||||||
alloc = _c_alloc,
|
alloc = _c_alloc,
|
||||||
|
realloc = _c_realloc,
|
||||||
free = _c_free,
|
free = _c_free,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +92,10 @@ alloc func(allocator Allocator, size usize, alignment usize) ?*mut u8 {
|
|||||||
return allocator.alloc(allocator.context, size, alignment)
|
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 {
|
free func(allocator Allocator, memory ?*mut u8, size usize, alignment usize) void {
|
||||||
allocator.free(allocator.context, memory, size, alignment)
|
allocator.free(allocator.context, memory, size, alignment)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user