allocation related primitives
This commit is contained in:
@@ -1 +1,2 @@
|
||||
/build/
|
||||
.DS_Store
|
||||
|
||||
@@ -628,9 +628,8 @@
|
||||
|
||||
25. dynamic heap allocation (implemented; v1)
|
||||
- `std/mem` exposes a plain-data `Allocator` contract with `?*mut anyopaque` context, `alloc`, and `free` `@func` pointers
|
||||
- `mem.heap` is the default libc-backed allocator; `mem.alloc(mem.heap, size, alignment)` returns nullable mutable byte memory
|
||||
- `mem.free(mem.heap, ptr, size, alignment)` frees with the same allocator; size/alignment are part of the contract, though libc ignores them in v1
|
||||
- `std/mem/heap` remains as legacy compatibility wrappers over `std/mem`
|
||||
- `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
|
||||
- typed allocation helpers, arenas/pools, build-mode heap policy, and escaping-allocation diagnostics remain deferred
|
||||
|
||||
26. import from project "root" (implemented)
|
||||
@@ -732,6 +731,40 @@
|
||||
- deferred: build graph / steps / caching, multiple artifacts, computed paths
|
||||
(needs string building), struct field defaults to drop `&[]` on empty lists
|
||||
|
||||
29. disallow arbitrary integer division
|
||||
- take inspiration from zig
|
||||
- see also below for a word on unchecked casts
|
||||
- the user should be explicit about what they mean with integer division (e.g. `div`, `rem`)
|
||||
|
||||
## A word on unchecked casts
|
||||
|
||||
For casts that bypass safety checks, Honey provides builtin functions:
|
||||
|
||||
| Builtin | Purpose | Traps when... |
|
||||
| -- | -- | -- |
|
||||
| `truncate(x, T)` | Keep low bits, discard rest | Never |
|
||||
| `bitcast(x, T)` | Reinterpret bits, no cast | Sizes don't match (compile error) |
|
||||
| `ptrcast(p, T)` | Change pointer type | Gaining mutability (compile error) |
|
||||
|
||||
```honey
|
||||
# truncation
|
||||
a: u32 = 0xDEADBEEF
|
||||
b := truncate(a, u8) # b == 0xEF (low byte)
|
||||
|
||||
# bit reinterpretation
|
||||
n: i32 = -1
|
||||
m := bitcast(n, u32) # m == 0xFFFFFFFF (same bits)
|
||||
f: f32 = 3.14
|
||||
bits := bitcast(f, u32) # IEEE 754 representation
|
||||
|
||||
# pointer casts (element type, many ↔ single, pointer ↔ usize)
|
||||
buf: *u8 = get_buffer()
|
||||
ints := ptrcast(buf, *u32) # element type change
|
||||
single := ptrcast(buf, @u8) # many → single (restricting)
|
||||
addr := ptrcast(buf, usize) # pointer to integer
|
||||
ptr := ptrcast(addr, @u8) # integer to pointer
|
||||
```
|
||||
|
||||
## A word on multi-unwrap
|
||||
|
||||
Unwrap multiple optionals with `and`. This **short-circuits**: if the first optional is none, subsequent expressions are not evaluated.
|
||||
@@ -1336,17 +1369,8 @@ Current v1 is intentionally byte-oriented and plain data:
|
||||
```
|
||||
mem :: import "@std/mem"
|
||||
|
||||
bytes := mem.alloc(mem.heap, 128, 1)
|
||||
defer mem.free(mem.heap, bytes, 128, 1)
|
||||
```
|
||||
|
||||
`@std/mem` is the preferred allocator API. `@std/mem/heap` remains as legacy compatibility wrappers over the same heap allocator:
|
||||
|
||||
```
|
||||
heap :: import "@std/mem/heap"
|
||||
|
||||
bytes := heap.alloc(128)
|
||||
defer heap.free(bytes)
|
||||
bytes := mem.alloc(mem.c_allocator, 128, 1)
|
||||
defer mem.free(mem.c_allocator, bytes, 128, 1)
|
||||
```
|
||||
|
||||
Typed allocation helpers, arenas, pools, build-mode heap policy, and escaping-allocation diagnostics are future work. Older examples below are design sketches where noted, not committed syntax.
|
||||
@@ -1368,11 +1392,11 @@ Memory allocation in Brolang is designed to be **explicit but not verbose**. We
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### The Default Heap Allocator
|
||||
### The Default C Allocator
|
||||
|
||||
Brolang provides a default heap allocator value:
|
||||
Brolang provides a libc-backed allocator value:
|
||||
|
||||
* Available as `mem.heap`
|
||||
* Available as `mem.c_allocator`
|
||||
* Passed explicitly to `mem.alloc` and `mem.free`
|
||||
* **Immutable at runtime** — cannot be reconfigured
|
||||
|
||||
@@ -1380,8 +1404,8 @@ Brolang provides a default heap allocator value:
|
||||
mem :: import "@std/mem"
|
||||
|
||||
process func(input []u8) u64 {
|
||||
temp ?*mut u8 = mem.alloc(mem.heap, input.len * 2, 1)
|
||||
defer mem.free(mem.heap, temp, input.len * 2, 1)
|
||||
temp ?*mut u8 = mem.alloc(mem.c_allocator, input.len * 2, 1)
|
||||
defer mem.free(mem.c_allocator, temp, input.len * 2, 1)
|
||||
|
||||
# ... work with temp ...
|
||||
|
||||
@@ -1389,7 +1413,7 @@ process func(input []u8) u64 {
|
||||
}
|
||||
```
|
||||
|
||||
Future build modes can choose different implementations behind `mem.heap` without changing the allocator contract:
|
||||
Future build modes can add other allocator values without changing the allocator contract:
|
||||
|
||||
| Build Mode | Allocator Behavior |
|
||||
| -- | -- |
|
||||
@@ -1397,7 +1421,7 @@ Future build modes can choose different implementations behind `mem.heap` withou
|
||||
| Release | Fast allocator, zero overhead |
|
||||
| ReleaseSafe | Bounds-checking allocator |
|
||||
|
||||
That policy is configured at compile time. You cannot change which allocator `mem.heap` uses at runtime. This is intentional — it prevents bugs where memory allocated with one allocator is freed with another.
|
||||
Allocator policy is configured at compile time. You cannot change which allocator value an allocation used after the fact. This is intentional — it prevents bugs where memory allocated with one allocator is freed with another.
|
||||
|
||||
### The Escaping Allocation Rule
|
||||
|
||||
@@ -1423,8 +1447,8 @@ init func(obj @mut MyStruct, allocator mem.Allocator) void {
|
||||
|
||||
# No allocation escapes — no allocator needed
|
||||
process func(input []u8) u64 {
|
||||
temp ?*mut u8 = mem.alloc(mem.heap, input.len, 1)
|
||||
defer mem.free(mem.heap, temp, input.len, 1)
|
||||
temp ?*mut u8 = mem.alloc(mem.c_allocator, input.len, 1)
|
||||
defer mem.free(mem.c_allocator, temp, input.len, 1)
|
||||
# ... work with temp ...
|
||||
return compute_hash(temp)
|
||||
}
|
||||
@@ -1435,12 +1459,12 @@ reset func(obj @mut MyStruct) void {
|
||||
}
|
||||
|
||||
main func() void {
|
||||
data := duplicate("hello", mem.heap)
|
||||
defer mem.free(mem.heap, data, 5, 1)
|
||||
data := duplicate("hello", mem.c_allocator)
|
||||
defer mem.free(mem.c_allocator, data, 5, 1)
|
||||
|
||||
mut obj := MyStruct{ ... }
|
||||
init(&obj, mem.heap)
|
||||
defer mem.free(mem.heap, obj.buffer, 100, 1)
|
||||
init(&obj, mem.c_allocator)
|
||||
defer mem.free(mem.c_allocator, obj.buffer, 100, 1)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1448,7 +1472,7 @@ main func() void {
|
||||
|
||||
* Without the rule, a function like `init(obj: @mut MyStruct) void` is ambiguous — did it heap-allocate into `obj`, or just set some fields to stack/static data? The caller has no way to know without reading the implementation.
|
||||
* With the rule, the allocator parameter is a clear signal: "this function produces heap memory that outlives its scope, and you are responsible for cleaning it up."
|
||||
* Internal allocations (temporary buffers, scratch space) use `mem.heap` directly and are freed before the function returns. No allocator parameter needed, no burden on the caller.
|
||||
* Internal allocations (temporary buffers, scratch space) use an explicit allocator value directly and are freed before the function returns. No allocator parameter needed, no burden on the caller.
|
||||
|
||||
The compiler should eventually enforce this rule. If a function heap-allocates memory that escapes without accepting an allocator parameter, the compiler should emit an error.
|
||||
|
||||
@@ -1457,20 +1481,20 @@ The compiler should eventually enforce this rule. If a function heap-allocates m
|
||||
Consider what would happen if you could reconfigure the default allocator:
|
||||
|
||||
```
|
||||
# ❌ THIS IS NOT ALLOWED (and doesn't exist in Brolang)
|
||||
mem.heap_set(my_custom_heap)
|
||||
# This is not allowed and does not exist in Brolang.
|
||||
mem.default_allocator_set(my_custom_allocator)
|
||||
|
||||
# Somewhere else in the codebase...
|
||||
data := mem.alloc(mem.heap, 100, 1)
|
||||
data := mem.alloc(mem.default_allocator, 100, 1)
|
||||
|
||||
# Later, someone changes it again...
|
||||
mem.heap_set(different_heap)
|
||||
mem.default_allocator_set(different_allocator)
|
||||
|
||||
# Now who frees `data`? With which allocator?
|
||||
mem.free(mem.heap, data, 100, 1) # wrong allocator - undefined behavior
|
||||
mem.free(mem.default_allocator, data, 100, 1) # wrong allocator - undefined behavior
|
||||
```
|
||||
|
||||
This is "action at a distance" — the behavior of `mem.free(mem.heap, ...)` depends on what some unrelated code did earlier. By making `mem.heap` immutable, Brolang guarantees:
|
||||
This is "action at a distance" — the behavior of `mem.free(mem.default_allocator, ...)` depends on what some unrelated code did earlier. By making allocator values explicit and immutable, Brolang guarantees:
|
||||
|
||||
**Whatever you allocate with, you free with.**
|
||||
|
||||
@@ -1486,8 +1510,8 @@ The examples in this section are future typed API sketches. The v1 allocator con
|
||||
mem :: import "@std/mem"
|
||||
|
||||
process_file func(path []u8, allocator mem.Allocator) !Data {
|
||||
# arena manages its own backing memory via heap
|
||||
arena := mem.Arena.init(mem.heap, capacity: mem.megabytes(1))
|
||||
# arena manages its own backing memory via the supplied allocator
|
||||
arena := mem.Arena.init(mem.c_allocator, capacity: mem.megabytes(1))
|
||||
defer arena.deinit()
|
||||
|
||||
# all temporary allocations from arena (fast bump allocation)
|
||||
@@ -1551,7 +1575,7 @@ parse func(input []u8, allocator mem.Allocator) !ParseResult {
|
||||
# Caller decides which allocator to use
|
||||
main func() void {
|
||||
# use an arena for this parsing work
|
||||
arena := mem.Arena.init(mem.heap, capacity: mem.kilobytes(64))
|
||||
arena := mem.Arena.init(mem.c_allocator, capacity: mem.kilobytes(64))
|
||||
defer arena.deinit()
|
||||
result := parse(input, &arena) catch |err| {
|
||||
# handle error
|
||||
@@ -1570,13 +1594,12 @@ main func() void {
|
||||
|
||||
| What | How | When to Use |
|
||||
| -- | -- | -- |
|
||||
| `mem.alloc(mem.heap, n, a)` | Preferred heap allocator | General purpose byte allocation |
|
||||
| `mem.free(mem.heap, ptr, n, a)` | Preferred heap allocator | Free byte allocation with original size/alignment |
|
||||
| `heap.alloc(n)` / `heap.free(ptr)` | Legacy wrapper | Compatibility with older `@std/mem/heap` code |
|
||||
| `mem.alloc(mem.c_allocator, n, a)` | Libc-backed allocator | General purpose byte allocation |
|
||||
| `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 |
|
||||
|
||||
Note that `mem.heap` is a `mem.Allocator`, so callers can pass it as the allocator argument when they don't need a specialized allocator — which is most of the time.
|
||||
Note that `mem.c_allocator` is a `mem.Allocator`, so callers can pass it as the allocator argument when they don't need a specialized allocator — which is most of the time.
|
||||
|
||||
**The golden rule:** Allocate and free with the same allocator. In v1 this is explicit in the call sites; future diagnostics should use the escaping allocation rule to ensure the caller always knows which allocator was used.
|
||||
|
||||
|
||||
@@ -277,6 +277,26 @@ is_ptr_cast_call :: proc(checker: ^Checker, expr: ast.Expr) -> bool {
|
||||
symbol_text(checker, expr.name) == "ptr_cast"
|
||||
}
|
||||
|
||||
Layout_Builtin :: enum u8 {
|
||||
None,
|
||||
Size_Of,
|
||||
Align_Of,
|
||||
}
|
||||
|
||||
layout_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Layout_Builtin {
|
||||
if expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
|
||||
return .None
|
||||
}
|
||||
name := symbol_text(checker, expr.name)
|
||||
if name == "size_of" {
|
||||
return .Size_Of
|
||||
}
|
||||
if name == "align_of" {
|
||||
return .Align_Of
|
||||
}
|
||||
return .None
|
||||
}
|
||||
|
||||
valid_ptr_cast_child :: proc(checker: ^Checker, value: types.Type) -> bool {
|
||||
return types.is_valid(value) &&
|
||||
!types.is_void(value) &&
|
||||
@@ -286,6 +306,49 @@ valid_ptr_cast_child :: proc(checker: ^Checker, value: types.Type) -> bool {
|
||||
types.is_opaque_struct(value, &checker.module.types))
|
||||
}
|
||||
|
||||
valid_layout_type :: proc(checker: ^Checker, value: types.Type) -> bool {
|
||||
return types.is_runtime_value(value, &checker.module.types)
|
||||
}
|
||||
|
||||
layout_builtin_value :: proc(checker: ^Checker, kind: Layout_Builtin, value: types.Type) -> i128 {
|
||||
#partial switch kind {
|
||||
case .Size_Of:
|
||||
return i128(types.size(value, &checker.module.types, checker.target))
|
||||
case .Align_Of:
|
||||
return i128(types.alignment_of(value, &checker.module.types, checker.target))
|
||||
case:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
build_layout_builtin :: proc(
|
||||
checker: ^Checker,
|
||||
expr: ast.Expr,
|
||||
kind: Layout_Builtin,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
) -> hir.Expr_Id {
|
||||
if len(expr.args) != 1 {
|
||||
id := source.addf(checker.diagnostics, expr.span, "%s expects 1 argument, got %d", symbol_text(checker, expr.name), len(expr.args))
|
||||
return invalid_hir_expr(checker, expr.span, id, types.USIZE)
|
||||
}
|
||||
target, target_ok := resolve_type_argument(checker, expr.args[0], pkg, file)
|
||||
if !target_ok {
|
||||
id := source.add(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "layout target must be a type")
|
||||
return invalid_hir_expr(checker, expr.span, id, types.USIZE)
|
||||
}
|
||||
if !valid_layout_type(checker, target) {
|
||||
id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "layout target must be a sized runtime value type, got %s", type_label(checker, target))
|
||||
return invalid_hir_expr(checker, expr.span, id, types.USIZE)
|
||||
}
|
||||
return build_constant_expr(
|
||||
checker,
|
||||
expr,
|
||||
Constant{kind=.Value, value=layout_builtin_value(checker, kind, target)},
|
||||
types.USIZE,
|
||||
)
|
||||
}
|
||||
|
||||
is_type_metatype_syntax :: proc(checker: ^Checker, value: ast.Type_Syntax) -> bool {
|
||||
item, ok := types.node(&checker.module.types, value)
|
||||
return ok && item.name == u32(checker.type_symbol) && item.qualifier == 0
|
||||
@@ -1828,7 +1891,7 @@ infer_compound_expr :: proc(
|
||||
case .Slice:
|
||||
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||
item, ok := types.container(value, store)
|
||||
if !ok || item.kind == .Pointer {
|
||||
if !ok || (item.kind == .Pointer && expr.args[1] == ast.INVALID_EXPR) {
|
||||
return types.INVALID
|
||||
}
|
||||
for bound in expr.args {
|
||||
@@ -2098,6 +2161,11 @@ infer_expr :: proc(
|
||||
}
|
||||
continue
|
||||
}
|
||||
if builtin := layout_builtin_call(checker, expr); builtin != .None {
|
||||
last = types.USIZE
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if is_ptr_cast_call(checker, expr) {
|
||||
if len(expr.args) != 2 {
|
||||
last = types.INVALID
|
||||
@@ -3080,6 +3148,11 @@ infer_all :: proc(checker: ^Checker) {
|
||||
if global.external {
|
||||
continue
|
||||
}
|
||||
if global.expr != ast.INVALID_EXPR && int(global.expr) < len(checker.ast_module.exprs) &&
|
||||
layout_builtin_call(checker, checker.ast_module.exprs[global.expr]) != .None {
|
||||
checker.global_types[index] = types.USIZE
|
||||
continue
|
||||
}
|
||||
constant := eval_integer_constant_in_context(checker, global.expr, global.pkg, global.file)
|
||||
if constant.kind == .Value && fits_i64(constant.value) {
|
||||
checker.global_open_const[index] = true
|
||||
@@ -4227,8 +4300,12 @@ build_compound_expr :: proc(
|
||||
container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
container_type := checker.module.exprs[container].type
|
||||
item, ok := types.container(container_type, store)
|
||||
if !ok || item.kind == .Pointer {
|
||||
id := source.add(checker.diagnostics, expr.span, "slicing requires an array, slice, or pointer-to-array")
|
||||
if !ok {
|
||||
id := source.add(checker.diagnostics, expr.span, "slicing requires an array, slice, pointer-to-array, or many-item pointer")
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
if item.kind == .Pointer && expr.args[1] == ast.INVALID_EXPR {
|
||||
id := source.add(checker.diagnostics, expr.span, "many-item pointer slicing requires an explicit end bound")
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
bounds := make([]hir.Expr_Id, 2, checker.allocator)
|
||||
@@ -4881,6 +4958,11 @@ build_expr :: proc(
|
||||
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION})
|
||||
continue
|
||||
}
|
||||
if builtin := layout_builtin_call(checker, expr); builtin != .None {
|
||||
last = build_layout_builtin(checker, expr, builtin, pkg, file)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if is_ptr_cast_call(checker, expr) {
|
||||
if len(expr.args) != 2 {
|
||||
id := source.addf(checker.diagnostics, expr.span, "ptr_cast expects 2 arguments, got %d", len(expr.args))
|
||||
|
||||
@@ -1909,6 +1909,19 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
|
||||
}
|
||||
return ct_eval_template_call(state, ast.Function_Id(u32(state.values[callee].index)), expr.args, expr.span, expected, depth+1)
|
||||
}
|
||||
if builtin := layout_builtin_call(checker, expr); builtin != .None {
|
||||
if len(expr.args) != 1 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "%s expects 1 argument, got %d", symbol_text(checker, expr.name), len(expr.args))
|
||||
}
|
||||
target, target_ok := resolve_type_argument(checker, expr.args[0], state.pkg, state.file)
|
||||
if !target_ok {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, checker.ast_module.exprs[expr.args[0]].span, "layout target must be a type")
|
||||
}
|
||||
if !valid_layout_type(checker, target) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, checker.ast_module.exprs[expr.args[0]].span, "layout target must be a sized runtime value type, got %s", type_label(checker, target))
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=layout_builtin_value(checker, builtin, target)}), ct_flow(.Normal), true
|
||||
}
|
||||
target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false)
|
||||
if !available {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "unavailable function package")
|
||||
|
||||
+13
-3
@@ -1114,7 +1114,7 @@ emit_instruction_stream :: proc(
|
||||
}
|
||||
container := instructions[instruction.a]
|
||||
item, ok := types.container(container.type, &emitter.module.types)
|
||||
if !ok || (item.kind != .Array && item.kind != .Slice) {
|
||||
if !ok || (item.kind != .Array && item.kind != .Slice && item.kind != .Pointer) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container")
|
||||
continue
|
||||
}
|
||||
@@ -1132,6 +1132,12 @@ emit_instruction_stream :: proc(
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_len%d = extractvalue %s %%v%d, 1\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a)
|
||||
pointer_name = fmt.tprintf("%%slice_ptr%d", instruction_index)
|
||||
length_name = fmt.tprintf("%%slice_len%d", instruction_index)
|
||||
} else if item.kind == .Pointer {
|
||||
if len(instruction.args) <= 1 || instruction.args[1] == ir.INVALID_INSTRUCTION {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid many-item pointer slice")
|
||||
continue
|
||||
}
|
||||
length_name = "0"
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_bound_start%d = add i64 0, ", instruction_index)
|
||||
if len(instruction.args) > 0 && instruction.args[0] != ir.INVALID_INSTRUCTION {
|
||||
@@ -1150,8 +1156,12 @@ emit_instruction_stream :: proc(
|
||||
start_name := fmt.tprintf("%%slice_bound_start%d", instruction_index)
|
||||
end_name := fmt.tprintf("%%slice_bound_end%d", instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_order%d = icmp ule i64 %s, %s\n", instruction_index, start_name, end_name)
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_end_ok%d = icmp ule i64 %s, %s\n", instruction_index, end_name, length_name)
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_ok%d = and i1 %%slice_order%d, %%slice_end_ok%d\n", instruction_index, instruction_index, instruction_index)
|
||||
if item.kind == .Pointer {
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_ok%d = or i1 false, %%slice_order%d\n", instruction_index, instruction_index)
|
||||
} else {
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_end_ok%d = icmp ule i64 %s, %s\n", instruction_index, end_name, length_name)
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_ok%d = and i1 %%slice_order%d, %%slice_end_ok%d\n", instruction_index, instruction_index, instruction_index)
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " br i1 %%slice_ok%d, label %%slice_continue%d, label %%slice_trap%d\nslice_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
|
||||
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "slice bounds out of range")
|
||||
emit_trap_call(emitter, message)
|
||||
|
||||
@@ -653,6 +653,17 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Question, .At, .Star:
|
||||
start := tok
|
||||
target := parse_type_atom(parser)
|
||||
return add_expr(parser, ast.Expr{
|
||||
kind=.Type,
|
||||
span=span_from(start.span, previous(parser).span),
|
||||
type=target,
|
||||
left=ast.INVALID_EXPR,
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Integer:
|
||||
advance(parser)
|
||||
value, ok := parse_integer_magnitude(token_text(parser, tok))
|
||||
@@ -739,6 +750,18 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
||||
case .Keyword_Func:
|
||||
return parse_function_literal(parser)
|
||||
case .Left_Bracket:
|
||||
if starts_declared_type(parser) {
|
||||
start := tok
|
||||
target := parse_type_atom(parser)
|
||||
return add_expr(parser, ast.Expr{
|
||||
kind=.Type,
|
||||
span=span_from(start.span, previous(parser).span),
|
||||
type=target,
|
||||
left=ast.INVALID_EXPR,
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
return parse_array_literal(parser, nesting)
|
||||
case .Dot:
|
||||
start := advance(parser)
|
||||
|
||||
+165
-10
@@ -1294,6 +1294,160 @@ immutable_pointer_and_slice_bindings_preserve_mutable_pointees :: proc(t: ^testi
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
many_item_pointer_slices_compile_and_run :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-pointer-slices"
|
||||
main_path := "/tmp/brolang-test-pointer-slices/main.bro"
|
||||
output := "/tmp/brolang-test-pointer-slices-output"
|
||||
text := `main func() i32 {
|
||||
values [4]mut i32 = [3, 4, 5, 6]
|
||||
pointer *mut i32 :: (&values).ptr
|
||||
const_pointer *i32 :: pointer
|
||||
all []mut i32 :: pointer[..4]
|
||||
middle []mut i32 :: pointer[1..3]
|
||||
readonly []i32 :: const_pointer[..2]
|
||||
|
||||
if (all.len != 4) return 1
|
||||
if (middle.len != 2) return 2
|
||||
all[0] = 10
|
||||
if (readonly[0] != 10) return 3
|
||||
if (middle.ptr[0] != 4) return 4
|
||||
return 0
|
||||
}
|
||||
`
|
||||
_ = os2.remove_all(directory)
|
||||
defer _ = os2.remove_all(directory)
|
||||
defer _ = os.remove(output)
|
||||
testing.expect(t, os.make_directory(directory) == nil)
|
||||
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
|
||||
status := compiler_core.compile_package(directory, output)
|
||||
testing.expect_value(t, status, 0)
|
||||
state := run_executable(output)
|
||||
testing.expect_value(t, state.exit_code, 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
many_item_pointer_slices_require_end_bound :: proc(t: ^testing.T) {
|
||||
text := `main func() void {
|
||||
values [2]i32 = [1, 2]
|
||||
pointer *i32 :: (&values).ptr
|
||||
_ = pointer[..]
|
||||
_ = pointer[1..]
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
end_bound_errors := 0
|
||||
for diagnostic in diagnostics.items {
|
||||
if strings.contains(diagnostic.message, "many-item pointer slicing requires an explicit end bound") {
|
||||
end_bound_errors += 1
|
||||
}
|
||||
}
|
||||
testing.expect_value(t, end_bound_errors, 2)
|
||||
}
|
||||
|
||||
@(test)
|
||||
layout_builtins_compile_and_run :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-layout-builtins"
|
||||
main_path := "/tmp/brolang-test-layout-builtins/main.bro"
|
||||
output := "/tmp/brolang-test-layout-builtins-output"
|
||||
text := `Point :: struct {
|
||||
x i32
|
||||
y u8
|
||||
}
|
||||
|
||||
Opaque :: opaque
|
||||
Color :: enum {
|
||||
red
|
||||
blue
|
||||
}
|
||||
UserID :: distinct u32
|
||||
SIZE_GLOBAL :: size_of(i32)
|
||||
|
||||
needs_usize func(value usize) usize {
|
||||
return value
|
||||
}
|
||||
|
||||
buffer func($T type) [size_of(T)]u8 {
|
||||
data [size_of(T)]u8 = undefined
|
||||
return data
|
||||
}
|
||||
|
||||
main func() i32 {
|
||||
bytes [_]u8 :: buffer(i32)
|
||||
if (needs_usize(SIZE_GLOBAL) != 4) return 1
|
||||
if (bytes.len != 4) return 2
|
||||
if (size_of([3]u8) != 3) return 3
|
||||
if (size_of([]u8) != 16) return 4
|
||||
if (align_of([]u8) != 8) return 5
|
||||
if (size_of(*anyopaque) != 8) return 6
|
||||
if (size_of(?*i32) != 8) return 7
|
||||
if (size_of(*Opaque) != 8) return 8
|
||||
if (size_of(Color) != 2) return 9
|
||||
if (align_of(Color) != 2) return 10
|
||||
if (size_of(Point) != 8) return 11
|
||||
if (align_of(Point) != 4) return 12
|
||||
if (size_of(UserID) != 4) return 13
|
||||
if (align_of(UserID) != 4) return 14
|
||||
return 0
|
||||
}
|
||||
`
|
||||
_ = os2.remove_all(directory)
|
||||
defer _ = os2.remove_all(directory)
|
||||
defer _ = os.remove(output)
|
||||
testing.expect(t, os.make_directory(directory) == nil)
|
||||
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
|
||||
status := compiler_core.compile_package(directory, output)
|
||||
testing.expect_value(t, status, 0)
|
||||
state := run_executable(output)
|
||||
testing.expect_value(t, state.exit_code, 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
layout_builtins_reject_unsized_targets :: proc(t: ^testing.T) {
|
||||
text := `Opaque :: opaque
|
||||
Fn :: alias func() void
|
||||
|
||||
main func() void {
|
||||
_ = size_of(void)
|
||||
_ = align_of(anyopaque)
|
||||
_ = size_of(Fn)
|
||||
_ = size_of(Opaque)
|
||||
_ = align_of(1)
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
bad_layout_targets := 0
|
||||
target_type_error := false
|
||||
for diagnostic in diagnostics.items {
|
||||
bad_layout_targets += 1 if strings.contains(diagnostic.message, "layout target must be a sized runtime value type") else 0
|
||||
target_type_error = target_type_error || strings.contains(diagnostic.message, "layout target must be a type")
|
||||
}
|
||||
testing.expect_value(t, bad_layout_targets, 4)
|
||||
testing.expect(t, target_type_error)
|
||||
}
|
||||
|
||||
@(test)
|
||||
slicing_an_array_variable_takes_its_address_implicitly :: proc(t: ^testing.T) {
|
||||
// Milestone 10: `arr[a..b]` on an array variable slices without an explicit
|
||||
@@ -3109,7 +3263,7 @@ allocator_contract_compiles_and_runs :: proc(t: ^testing.T) {
|
||||
}
|
||||
|
||||
@(test)
|
||||
allocator_contract_heap_global_lowers :: proc(t: ^testing.T) {
|
||||
allocator_contract_c_allocator_global_lowers :: proc(t: ^testing.T) {
|
||||
sources := source.init_store()
|
||||
defer source.destroy_store(&sources)
|
||||
diagnostics := source.init_store_diagnostics(&sources)
|
||||
@@ -3125,15 +3279,15 @@ allocator_contract_heap_global_lowers :: proc(t: ^testing.T) {
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
|
||||
found_heap := false
|
||||
found_c_allocator := false
|
||||
found_anyopaque_context := false
|
||||
found_alloc_callback := false
|
||||
found_free_callback := false
|
||||
for global in hir_module.globals {
|
||||
if symbol.resolve(&symbols, global.name) != "heap" {
|
||||
if symbol.resolve(&symbols, global.name) != "c_allocator" {
|
||||
continue
|
||||
}
|
||||
found_heap = true
|
||||
found_c_allocator = true
|
||||
for field in types.fields_for(&hir_module.types, global.type) {
|
||||
name := symbol.resolve(&symbols, symbol.Id(field.name))
|
||||
callback_pointer, _, _, callable := types.function_pointer(field.type, &hir_module.types)
|
||||
@@ -3155,31 +3309,31 @@ allocator_contract_heap_global_lowers :: proc(t: ^testing.T) {
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect(t, len(ir_module.functions) > 0)
|
||||
testing.expect(t, found_heap)
|
||||
testing.expect(t, found_c_allocator)
|
||||
testing.expect(t, found_anyopaque_context)
|
||||
testing.expect(t, found_alloc_callback)
|
||||
testing.expect(t, found_free_callback)
|
||||
}
|
||||
|
||||
@(test)
|
||||
milestone_25_heap_compiles_and_runs :: proc(t: ^testing.T) {
|
||||
output := "/tmp/brolang-test-heap"
|
||||
milestone_25_c_allocator_compiles_and_runs :: proc(t: ^testing.T) {
|
||||
output := "/tmp/brolang-test-c-allocator"
|
||||
defer _ = os.remove(output)
|
||||
status := compiler_core.compile_package("examples/programs/heap", output, nil, target.DEFAULT, cimport.Options{}, ".")
|
||||
status := compiler_core.compile_package("examples/programs/mem_allocator", output, nil, target.DEFAULT, cimport.Options{}, ".")
|
||||
testing.expect_value(t, status, 0)
|
||||
state := run_executable(output)
|
||||
testing.expect_value(t, state.exit_code, 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
milestone_25_heap_emits_libc_alloc_declarations :: proc(t: ^testing.T) {
|
||||
milestone_25_c_allocator_emits_libc_alloc_declarations :: proc(t: ^testing.T) {
|
||||
sources := source.init_store()
|
||||
defer source.destroy_store(&sources)
|
||||
diagnostics := source.init_store_diagnostics(&sources)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
ast_module, loaded := loader.load("examples/programs/heap", &sources, &diagnostics, &symbols, context.allocator, context.allocator, cimport.Options{}, target.DEFAULT, ".")
|
||||
ast_module, loaded := loader.load("examples/programs/mem_allocator", &sources, &diagnostics, &symbols, context.allocator, context.allocator, cimport.Options{}, target.DEFAULT, ".")
|
||||
defer ast.destroy_module(&ast_module)
|
||||
testing.expect(t, loaded)
|
||||
|
||||
@@ -3192,6 +3346,7 @@ milestone_25_heap_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 i32 @posix_memalign(ptr, i64, i64)"))
|
||||
testing.expect(t, strings.contains(llvm_text, "declare void @free(ptr)"))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
heap :: import "@std/mem/heap"
|
||||
|
||||
main func() i32 {
|
||||
memory ?*mut u8 = heap.alloc(4)
|
||||
defer heap.free(memory)
|
||||
|
||||
if memory |bytes| {
|
||||
bytes[0] = 10
|
||||
bytes[1] = 20
|
||||
bytes[2] = bytes[0] + bytes[1]
|
||||
|
||||
if (bytes[2] != 30) {
|
||||
return 2
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
@@ -1,20 +1,214 @@
|
||||
mem :: import "@std/mem"
|
||||
|
||||
main func() i32 {
|
||||
memory ?*mut u8 = mem.alloc(mem.heap, 4, 1)
|
||||
defer mem.free(mem.heap, memory, 4, 1)
|
||||
TaskList :: struct {
|
||||
ids ?[]mut i32
|
||||
priorities ?[]mut i32
|
||||
durations ?[]mut i32
|
||||
len usize
|
||||
capacity usize
|
||||
allocator mem.Allocator
|
||||
}
|
||||
|
||||
if memory |bytes| {
|
||||
bytes[0] = 10
|
||||
bytes[1] = 20
|
||||
bytes[2] = bytes[0] + bytes[1]
|
||||
task_list_init func(allocator mem.Allocator) TaskList {
|
||||
return TaskList {
|
||||
ids = none,
|
||||
priorities = none,
|
||||
durations = none,
|
||||
len = 0,
|
||||
capacity = 0,
|
||||
allocator = allocator,
|
||||
}
|
||||
}
|
||||
|
||||
if (bytes[2] != 30) {
|
||||
return 2
|
||||
}
|
||||
alloc_i32s func(allocator mem.Allocator, count usize) ?[]mut i32 {
|
||||
raw ?*mut u8 = mem.alloc(allocator, count * size_of(i32), align_of(i32))
|
||||
if raw |bytes| {
|
||||
values *mut i32 = ptr_cast(i32, bytes)
|
||||
return values[..count]
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
return 0
|
||||
free_i32s func(allocator mem.Allocator, values ?[]mut i32) void {
|
||||
if values |slice| {
|
||||
mem.free(allocator, ptr_cast(u8, slice.ptr), slice.len * size_of(i32), align_of(i32))
|
||||
}
|
||||
}
|
||||
|
||||
task_list_reserve func(list @mut TaskList, capacity usize) bool {
|
||||
if capacity <= list.capacity {
|
||||
return true
|
||||
}
|
||||
|
||||
return 1
|
||||
new_ids ?[]mut i32 = alloc_i32s(list.allocator, capacity)
|
||||
new_priorities ?[]mut i32 = alloc_i32s(list.allocator, capacity)
|
||||
new_durations ?[]mut i32 = alloc_i32s(list.allocator, capacity)
|
||||
|
||||
if (new_ids and new_priorities and new_durations) |ids, priorities, durations| {
|
||||
if list.len > 0 {
|
||||
if (list.ids and list.priorities and list.durations) |old_ids, old_priorities, old_durations| {
|
||||
i usize = 0
|
||||
while i < list.len : i += 1 {
|
||||
ids[i] = old_ids[i]
|
||||
priorities[i] = old_priorities[i]
|
||||
durations[i] = old_durations[i]
|
||||
}
|
||||
} else {
|
||||
free_i32s(list.allocator, new_ids)
|
||||
free_i32s(list.allocator, new_priorities)
|
||||
free_i32s(list.allocator, new_durations)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
free_i32s(list.allocator, list.ids)
|
||||
free_i32s(list.allocator, list.priorities)
|
||||
free_i32s(list.allocator, list.durations)
|
||||
|
||||
list.ids = new_ids
|
||||
list.priorities = new_priorities
|
||||
list.durations = new_durations
|
||||
list.capacity = capacity
|
||||
return true
|
||||
}
|
||||
|
||||
free_i32s(list.allocator, new_ids)
|
||||
free_i32s(list.allocator, new_priorities)
|
||||
free_i32s(list.allocator, new_durations)
|
||||
return false
|
||||
}
|
||||
|
||||
task_list_push func(list @mut TaskList, id i32, priority i32, duration i32) bool {
|
||||
if list.len == list.capacity {
|
||||
new_capacity usize = 2
|
||||
if list.capacity != 0 {
|
||||
new_capacity = list.capacity * 2
|
||||
}
|
||||
if task_list_reserve(list, new_capacity) == false {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (list.ids and list.priorities and list.durations) |ids, priorities, durations| {
|
||||
index usize = list.len
|
||||
ids[index] = id
|
||||
priorities[index] = priority
|
||||
durations[index] = duration
|
||||
list.len += 1
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
task_score func(priority i32, duration i32) i32 {
|
||||
return priority * 10 - duration
|
||||
}
|
||||
|
||||
task_list_best_id func(list @mut TaskList) i32 {
|
||||
if list.len == 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
if (list.ids and list.priorities and list.durations) |ids, priorities, durations| {
|
||||
best_index usize = 0
|
||||
best_score i32 = task_score(priorities[0], durations[0])
|
||||
i usize = 1
|
||||
while i < list.len : i += 1 {
|
||||
score i32 = task_score(priorities[i], durations[i])
|
||||
if score > best_score {
|
||||
best_score = score
|
||||
best_index = i
|
||||
}
|
||||
}
|
||||
return ids[best_index]
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
task_list_total_duration func(list @mut TaskList) i32 {
|
||||
total i32 = 0
|
||||
if list.durations |durations| {
|
||||
i usize = 0
|
||||
while i < list.len : i += 1 {
|
||||
total += durations[i]
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
task_list_deinit func(list @mut TaskList) void {
|
||||
free_i32s(list.allocator, list.ids)
|
||||
free_i32s(list.allocator, list.priorities)
|
||||
free_i32s(list.allocator, list.durations)
|
||||
list.ids = none
|
||||
list.priorities = none
|
||||
list.durations = none
|
||||
list.len = 0
|
||||
list.capacity = 0
|
||||
}
|
||||
|
||||
main func() i32 {
|
||||
zero_alignment ?*mut u8 = mem.alloc(mem.c_allocator, 8, 0)
|
||||
if zero_alignment |memory| {
|
||||
mem.free(mem.c_allocator, memory, 8, 0)
|
||||
return 1
|
||||
}
|
||||
|
||||
bad_alignment ?*mut u8 = mem.alloc(mem.c_allocator, 8, 24)
|
||||
if bad_alignment |memory| {
|
||||
mem.free(mem.c_allocator, memory, 8, 24)
|
||||
return 2
|
||||
}
|
||||
|
||||
aligned ?*mut u8 = mem.alloc(mem.c_allocator, 64, 32)
|
||||
defer mem.free(mem.c_allocator, aligned, 64, 32)
|
||||
if aligned |bytes| {
|
||||
bytes[0] = 1
|
||||
bytes[63] = 2
|
||||
if bytes[0] + bytes[63] != 3 {
|
||||
return 4
|
||||
}
|
||||
} else {
|
||||
return 3
|
||||
}
|
||||
|
||||
tasks TaskList = task_list_init(mem.c_allocator)
|
||||
defer task_list_deinit(&tasks)
|
||||
|
||||
if task_list_push(&tasks, 101, 3, 5) == false {
|
||||
return 5
|
||||
}
|
||||
if tasks.capacity != 2 {
|
||||
return 6
|
||||
}
|
||||
|
||||
if task_list_push(&tasks, 202, 1, 4) == false {
|
||||
return 7
|
||||
}
|
||||
if tasks.capacity != 2 {
|
||||
return 8
|
||||
}
|
||||
|
||||
if task_list_push(&tasks, 303, 4, 8) == false {
|
||||
return 9
|
||||
}
|
||||
if tasks.capacity != 4 {
|
||||
return 10
|
||||
}
|
||||
|
||||
if tasks.len != 3 {
|
||||
return 11
|
||||
}
|
||||
|
||||
if task_list_best_id(&tasks) != 303 {
|
||||
return 12
|
||||
}
|
||||
|
||||
if task_list_total_duration(&tasks) != 17 {
|
||||
return 13
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,2 +1,3 @@
|
||||
malloc c_func(size usize) ?*mut anyopaque
|
||||
free c_func(ptr ?*mut anyopaque) void
|
||||
malloc c_func(__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
|
||||
|
||||
+44
-7
@@ -6,14 +6,51 @@ Allocator :: struct {
|
||||
free @func(context ?*mut anyopaque, memory ?*mut u8, size usize, alignment usize) void
|
||||
}
|
||||
|
||||
heap Allocator :: Allocator {
|
||||
_malloc_alignment usize :: 16 # ponytail: aarch64-macos libc malloc alignment assumption.
|
||||
|
||||
_power_of_two func(value usize) bool {
|
||||
if value == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
current usize = value
|
||||
while current > 1 {
|
||||
half usize = current / 2
|
||||
if half * 2 != current {
|
||||
return false
|
||||
}
|
||||
current = half
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
_c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||
if _power_of_two(alignment) == false {
|
||||
return none
|
||||
}
|
||||
|
||||
if alignment <= _malloc_alignment {
|
||||
return ptr_cast(u8, c.malloc(c_ulong(size)))
|
||||
}
|
||||
|
||||
memory [1]mut ?*mut anyopaque = [none]
|
||||
status c_int = c.posix_memalign((&memory).ptr, c_ulong(alignment), c_ulong(size))
|
||||
if status != 0 {
|
||||
return none
|
||||
}
|
||||
|
||||
return ptr_cast(u8, memory[0])
|
||||
}
|
||||
|
||||
_c_free func(_ ?*mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
|
||||
c.free(memory)
|
||||
}
|
||||
|
||||
c_allocator Allocator :: Allocator {
|
||||
context = none,
|
||||
alloc = func(_ ?*mut anyopaque, size usize, _ usize) ?*mut u8 {
|
||||
return ptr_cast(u8, c.malloc(size))
|
||||
},
|
||||
free = func(_ ?*mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
|
||||
c.free(memory)
|
||||
},
|
||||
alloc = _c_alloc,
|
||||
free = _c_free,
|
||||
}
|
||||
|
||||
alloc func(allocator Allocator, size usize, alignment usize) ?*mut u8 {
|
||||
|
||||
Reference in New Issue
Block a user