typed alloc

This commit is contained in:
2026-07-12 00:29:22 +02:00
parent 220b1c6e82
commit fbbbfa454c
6 changed files with 328 additions and 66 deletions
+2 -1
View File
@@ -23,6 +23,7 @@ roadmap and milestone history.
- target-dependent C scalar primitives from `c_char` through `c_longdouble`, kept semantically distinct from native scalars - target-dependent C scalar primitives from `c_char` through `c_longdouble`, kept semantically distinct from native scalars
- contextual integer/float/character literals, backward type-demand inference through names and arithmetic, and compile-time folding for numeric constant expressions - contextual integer/float/character literals, backward type-demand inference through names and arithmetic, and compile-time folding for numeric constant expressions
- strict numeric conversion by default, widening where valid, C scalar coercions at C boundaries, and explicit scalar keyword casts such as `i32(x)` / `c_float(x)` - strict numeric conversion by default, widening where valid, C scalar coercions at C boundaries, and explicit scalar keyword casts such as `i32(x)` / `c_float(x)`
- compile-time `min_value(T)` and `max_value(T)` bounds for concrete native and C integer scalar types
- arrays `[N]T`, inferred-count arrays `[_]T`, sentinel arrays `[N;S]T`, compile-time expression array counts, slices `[]T` / `[;S]T`, single-item pointers `@T`, many-item pointers `*T`, and sentinel many-item pointers `[*;S]T` - arrays `[N]T`, inferred-count arrays `[_]T`, sentinel arrays `[N;S]T`, compile-time expression array counts, slices `[]T` / `[;S]T`, single-item pointers `@T`, many-item pointers `*T`, and sentinel many-item pointers `[*;S]T`
- pointer mutability via `mut`, optional pointers as nullable pointers, pointer arithmetic for many-item pointers, postfix dereference `^`, and trapping optional unwrap `?` - pointer mutability via `mut`, optional pointers as nullable pointers, pointer arithmetic for many-item pointers, postfix dereference `^`, and trapping optional unwrap `?`
- pointer-to-array `.len`, indexing, slicing, `.ptr` on slices and pointers-to-arrays, implicit address-taking for array-variable slices, and pointer/slice sentinel weakening - pointer-to-array `.len`, indexing, slicing, `.ptr` on slices and pointers-to-arrays, implicit address-taking for array-variable slices, and pointer/slice sentinel weakening
@@ -73,7 +74,7 @@ roadmap and milestone history.
### standard packages ### standard packages
- `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` allocator contract with a context pointer plus shared `AllocatorVTable`, raw byte operations `raw_alloc` / `raw_realloc` / `raw_free`, and fallible typed `alloc(T, allocator, count)`; failed nonzero raw reallocation preserves the original allocation, while zero size frees it
### compiler behavior ### compiler behavior
+53 -14
View File
@@ -281,13 +281,15 @@ is_ptr_cast_call :: proc(checker: ^Checker, expr: ast.Expr) -> bool {
symbol_text(checker, expr.name) == "ptr_cast" symbol_text(checker, expr.name) == "ptr_cast"
} }
Layout_Builtin :: enum u8 { Type_Builtin :: enum u8 {
None, None,
Size_Of, Size_Of,
Align_Of, Align_Of,
Min_Value,
Max_Value,
} }
layout_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Layout_Builtin { type_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Type_Builtin {
if expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) { if expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
return .None return .None
} }
@@ -298,6 +300,12 @@ layout_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Layout_Builtin
if name == "align_of" { if name == "align_of" {
return .Align_Of return .Align_Of
} }
if name == "min_value" {
return .Min_Value
}
if name == "max_value" {
return .Max_Value
}
return .None return .None
} }
@@ -314,21 +322,30 @@ valid_layout_type :: proc(checker: ^Checker, value: types.Type) -> bool {
return types.is_runtime_value(value, &checker.module.types) return types.is_runtime_value(value, &checker.module.types)
} }
layout_builtin_value :: proc(checker: ^Checker, kind: Layout_Builtin, value: types.Type) -> i128 { type_builtin_value :: proc(checker: ^Checker, kind: Type_Builtin, value: types.Type) -> i128 {
#partial switch kind { #partial switch kind {
case .Size_Of: case .Size_Of:
return i128(types.size(value, &checker.module.types, checker.target)) return i128(types.size(value, &checker.module.types, checker.target))
case .Align_Of: case .Align_Of:
return i128(types.alignment_of(value, &checker.module.types, checker.target)) return i128(types.alignment_of(value, &checker.module.types, checker.target))
case .Min_Value:
if types.is_unsigned(value, checker.target) {
return 0
}
return -(i128(1) << u32(types.bits(value, checker.target)-1))
case .Max_Value:
bit_count := types.bits(value, checker.target)
sign_bit_count := 1 if types.is_signed(value, checker.target) else 0
return (i128(1) << u32(bit_count-sign_bit_count))-1
case: case:
return 0 return 0
} }
} }
build_layout_builtin :: proc( build_type_builtin :: proc(
checker: ^Checker, checker: ^Checker,
expr: ast.Expr, expr: ast.Expr,
kind: Layout_Builtin, kind: Type_Builtin,
pkg: ast.Package_Id, pkg: ast.Package_Id,
file: ast.File_Id, file: ast.File_Id,
) -> hir.Expr_Id { ) -> hir.Expr_Id {
@@ -338,18 +355,24 @@ build_layout_builtin :: proc(
} }
target, target_ok := resolve_type_argument(checker, expr.args[0], pkg, file) target, target_ok := resolve_type_argument(checker, expr.args[0], pkg, file)
if !target_ok { if !target_ok {
id := source.add(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "layout target must be a type") label := "layout" if kind == .Size_Of || kind == .Align_Of else "integer bound"
id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "%s target must be a type", label)
return invalid_hir_expr(checker, expr.span, id, types.USIZE) return invalid_hir_expr(checker, expr.span, id, types.USIZE)
} }
if !valid_layout_type(checker, target) { if (kind == .Size_Of || kind == .Align_Of) && !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)) 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 invalid_hir_expr(checker, expr.span, id, types.USIZE)
} }
if (kind == .Min_Value || kind == .Max_Value) && !types.is_concrete_integer(target) {
id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "integer bound target must be a concrete integer type, got %s", type_label(checker, target))
return invalid_hir_expr(checker, expr.span, id, types.USIZE)
}
result_type := types.USIZE if kind == .Size_Of || kind == .Align_Of else target
return build_constant_expr( return build_constant_expr(
checker, checker,
expr, expr,
Constant{kind=.Value, value=layout_builtin_value(checker, kind, target)}, Constant{kind=.Value, value=type_builtin_value(checker, kind, target)},
types.USIZE, result_type,
) )
} }
@@ -2201,8 +2224,15 @@ infer_expr :: proc(
} }
continue continue
} }
if builtin := layout_builtin_call(checker, expr); builtin != .None { if builtin := type_builtin_call(checker, expr); builtin != .None {
if builtin == .Size_Of || builtin == .Align_Of {
last = types.USIZE last = types.USIZE
} else if len(expr.args) == 1 {
target, ok := resolve_type_argument(checker, expr.args[0], pkg, file)
last = target if ok && types.is_concrete_integer(target) else types.INVALID
} else {
last = types.INVALID
}
_ = pop(&stack) _ = pop(&stack)
continue continue
} }
@@ -3188,11 +3218,20 @@ infer_all :: proc(checker: ^Checker) {
if global.external { if global.external {
continue continue
} }
if global.expr != ast.INVALID_EXPR && int(global.expr) < len(checker.ast_module.exprs) && 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 { expr := checker.ast_module.exprs[global.expr]
if builtin := type_builtin_call(checker, expr); builtin != .None {
if builtin == .Size_Of || builtin == .Align_Of {
checker.global_types[index] = types.USIZE checker.global_types[index] = types.USIZE
} else if len(expr.args) == 1 {
target, ok := resolve_type_argument(checker, expr.args[0], global.pkg, global.file)
if ok && types.is_concrete_integer(target) {
checker.global_types[index] = target
}
}
continue continue
} }
}
constant := eval_integer_constant_in_context(checker, global.expr, global.pkg, global.file) constant := eval_integer_constant_in_context(checker, global.expr, global.pkg, global.file)
if constant.kind == .Value && fits_i64(constant.value) { if constant.kind == .Value && fits_i64(constant.value) {
checker.global_open_const[index] = true checker.global_open_const[index] = true
@@ -4998,8 +5037,8 @@ build_expr :: proc(
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION}) append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION})
continue continue
} }
if builtin := layout_builtin_call(checker, expr); builtin != .None { if builtin := type_builtin_call(checker, expr); builtin != .None {
last = build_layout_builtin(checker, expr, builtin, pkg, file) last = build_type_builtin(checker, expr, builtin, pkg, file)
_ = pop(&stack) _ = pop(&stack)
continue continue
} }
+9 -4
View File
@@ -1909,18 +1909,23 @@ 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) 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 builtin := type_builtin_call(checker, expr); builtin != .None {
if len(expr.args) != 1 { 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)) 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) target, target_ok := resolve_type_argument(checker, expr.args[0], state.pkg, state.file)
if !target_ok { 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") label := "layout" if builtin == .Size_Of || builtin == .Align_Of else "integer bound"
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, checker.ast_module.exprs[expr.args[0]].span, "%s target must be a type", label)
} }
if !valid_layout_type(checker, target) { if (builtin == .Size_Of || builtin == .Align_Of) && !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 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 if (builtin == .Min_Value || builtin == .Max_Value) && !types.is_concrete_integer(target) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, checker.ast_module.exprs[expr.args[0]].span, "integer bound target must be a concrete integer type, got %s", type_label(checker, target))
}
result_type := types.USIZE if builtin == .Size_Of || builtin == .Align_Of else target
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=type_builtin_value(checker, builtin, target)}), ct_flow(.Normal), true
} }
target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false) target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false)
if !available { if !available {
+91 -4
View File
@@ -1413,6 +1413,83 @@ main func() i32 {
testing.expect_value(t, state.exit_code, 0) testing.expect_value(t, state.exit_code, 0)
} }
@(test)
integer_bound_builtins_compile_and_run :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-integer-bounds"
main_path := "/tmp/brolang-test-integer-bounds/main.bro"
output := "/tmp/brolang-test-integer-bounds-output"
text := `MAX_U64 u64 :: max_value(u64)
maximum func($T type) T {
return max_value(T)
}
main func() i32 {
if (min_value(i8) != -128) return 1
if (max_value(i8) != 127) return 2
if (min_value(u8) != 0) return 3
if (max_value(u8) != 255) return 4
if (min_value(isize) != -9223372036854775808) return 5
if (max_value(usize) != 18446744073709551615) return 6
if (MAX_U64 != 18446744073709551615) return 7
if (maximum(u16) != 65535) return 8
if (min_value(c_int) != -2147483648) return 9
if (max_value(c_ulong) != 18446744073709551615) return 10
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)
integer_bound_builtins_reject_invalid_targets :: proc(t: ^testing.T) {
text := `Named :: distinct u8
Choice :: enum { one }
main func() void {
_ = min_value()
_ = max_value(u8, u16)
_ = min_value(1)
_ = max_value(int)
_ = max_value(f32)
_ = max_value(bool)
_ = max_value(Named)
_ = max_value(Choice)
}
`
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_arity := 0
bad_type := false
bad_target := 0
for diagnostic in diagnostics.items {
bad_arity += 1 if strings.contains(diagnostic.message, "expects 1 argument") else 0
bad_type = bad_type || strings.contains(diagnostic.message, "integer bound target must be a type")
bad_target += 1 if strings.contains(diagnostic.message, "integer bound target must be a concrete integer type") else 0
}
testing.expect_value(t, bad_arity, 2)
testing.expect(t, bad_type)
testing.expect_value(t, bad_target, 5)
}
@(test) @(test)
layout_builtins_reject_unsized_targets :: proc(t: ^testing.T) { layout_builtins_reject_unsized_targets :: proc(t: ^testing.T) {
text := `Opaque :: opaque text := `Opaque :: opaque
@@ -3281,6 +3358,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_vtable_pointer := false
found_alloc_callback := false found_alloc_callback := false
found_realloc_callback := false found_realloc_callback := false
found_free_callback := false found_free_callback := false
@@ -3291,7 +3369,6 @@ allocator_contract_c_allocator_global_lowers :: proc(t: ^testing.T) {
found_c_allocator = true found_c_allocator = true
for field in types.fields_for(&hir_module.types, global.type) { for field in types.fields_for(&hir_module.types, global.type) {
name := symbol.resolve(&symbols, symbol.Id(field.name)) name := symbol.resolve(&symbols, symbol.Id(field.name))
callback_pointer, _, _, callable := types.function_pointer(field.type, &hir_module.types)
if name == "context" { if name == "context" {
optional_item, optional_ok := types.node(&hir_module.types, field.type) optional_item, optional_ok := types.node(&hir_module.types, field.type)
if optional_ok && optional_item.kind == .Optional { if optional_ok && optional_item.kind == .Optional {
@@ -3302,10 +3379,19 @@ allocator_contract_c_allocator_global_lowers :: proc(t: ^testing.T) {
pointer_item.many && pointer_item.many &&
pointer_item.child == types.ANYOPAQUE pointer_item.child == types.ANYOPAQUE
} }
} else if name == "vtable" {
pointer_item, pointer_ok := types.node(&hir_module.types, field.type)
found_vtable_pointer = pointer_ok && pointer_item.kind == .Pointer && !pointer_item.mutable && !pointer_item.many
if found_vtable_pointer {
for callback in types.fields_for(&hir_module.types, pointer_item.child) {
callback_name := symbol.resolve(&symbols, symbol.Id(callback.name))
callback_pointer, _, _, callable := types.function_pointer(callback.type, &hir_module.types)
found_alloc_callback = found_alloc_callback || callback_name == "alloc" && callable && !callback_pointer.many
found_realloc_callback = found_realloc_callback || callback_name == "realloc" && callable && !callback_pointer.many
found_free_callback = found_free_callback || callback_name == "free" && 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
} }
} }
@@ -3313,6 +3399,7 @@ allocator_contract_c_allocator_global_lowers :: proc(t: ^testing.T) {
testing.expect(t, len(ir_module.functions) > 0) testing.expect(t, len(ir_module.functions) > 0)
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_vtable_pointer)
testing.expect(t, found_alloc_callback) testing.expect(t, found_alloc_callback)
testing.expect(t, found_realloc_callback) testing.expect(t, found_realloc_callback)
testing.expect(t, found_free_callback) testing.expect(t, found_free_callback)
+120 -30
View File
@@ -1,5 +1,32 @@
mem :: import "@std/mem" mem :: import "@std/mem"
_probe_count func(context ?*mut anyopaque) void {
if context |raw| {
counts *mut usize :: ptr_cast(usize, raw)
counts[0] += 1
}
}
_probe_alloc func(context ?*mut anyopaque, _ usize, _ usize) ?*mut u8 {
_probe_count(context)
return none
}
_probe_realloc func(context ?*mut anyopaque, _ ?*mut u8, _ usize, _ usize, _ usize) ?*mut u8 {
_probe_count(context)
return none
}
_probe_free func(context ?*mut anyopaque, _ ?*mut u8, _ usize, _ usize) void {
_probe_count(context)
}
_probe_vtable mem.AllocatorVTable :: mem.AllocatorVTable {
alloc = _probe_alloc,
realloc = _probe_realloc,
free = _probe_free,
}
TaskList :: struct { TaskList :: struct {
ids ?[]mut i32 ids ?[]mut i32
priorities ?[]mut i32 priorities ?[]mut i32
@@ -21,17 +48,19 @@ task_list_init func(allocator mem.Allocator) TaskList {
} }
alloc_i32s func(allocator mem.Allocator, count usize) ?[]mut i32 { alloc_i32s func(allocator mem.Allocator, count usize) ?[]mut i32 {
raw ?*mut u8 = mem.alloc(allocator, count * size_of(i32), align_of(i32)) fallback [1]mut i32 = undefined
if raw |bytes| { failed bool = false
values *mut i32 = ptr_cast(i32, bytes) values []mut i32 = mem.alloc(i32, allocator, count) catch |_| {
return values[..count] failed = true
yield (&fallback).ptr[..0]
} }
return none if (failed) return none
return values
} }
free_i32s func(allocator mem.Allocator, values ?[]mut i32) void { free_i32s func(allocator mem.Allocator, values ?[]mut i32) void {
if values |slice| { if values |slice| {
mem.free(allocator, ptr_cast(u8, slice.ptr), slice.len * size_of(i32), align_of(i32)) mem.raw_free(allocator, ptr_cast(u8, slice.ptr), slice.len * size_of(i32), align_of(i32))
} }
} }
@@ -150,7 +179,68 @@ 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 (size_of(mem.Allocator) != 16) return 31
first_calls [1]mut usize = [0]
second_calls [1]mut usize = [0]
first_allocator mem.Allocator :: mem.Allocator {
context = (&first_calls).ptr,
vtable = &_probe_vtable,
}
second_allocator mem.Allocator :: mem.Allocator {
context = (&second_calls).ptr,
vtable = &_probe_vtable,
}
_ = mem.raw_alloc(first_allocator, 1, 1)
_ = mem.raw_realloc(first_allocator, none, 0, 1, 1)
mem.raw_free(first_allocator, none, 0, 1)
_ = mem.raw_alloc(second_allocator, 1, 1)
if (first_calls[0] != 3 or second_calls[0] != 1) return 32
i32_fallback [1]mut i32 = undefined
empty_failed bool = false
empty []mut i32 = mem.alloc(i32, first_allocator, 0) catch |_| {
empty_failed = true
yield (&i32_fallback).ptr[..0]
}
if (empty_failed or empty.len != 0 or first_calls[0] != 3) return 34
zero_sized_fallback [1]mut [0]u8 = undefined
zero_sized_failed bool = false
zero_sized []mut [0]u8 = mem.alloc([0]u8, first_allocator, 3) catch |_| {
zero_sized_failed = true
yield (&zero_sized_fallback).ptr[..0]
}
if (zero_sized_failed or zero_sized.len != 3 or first_calls[0] != 3) return 36
_ = zero_sized[2]
u64_fallback [1]mut u64 = undefined
overflow_fallback_failed bool = false
overflow_fallback []mut u64 = mem.alloc(u64, first_allocator, 0) catch |_| {
overflow_fallback_failed = true
yield (&u64_fallback).ptr[..0]
}
if (overflow_fallback_failed) return 37
overflow_failed bool = false
_ = mem.alloc(u64, first_allocator, max_value(usize)) catch |_| {
overflow_failed = true
yield overflow_fallback
}
if (overflow_failed == false or first_calls[0] != 3) return 40
typed_failed bool = false
typed []mut i32 = mem.alloc(i32, mem.c_allocator, 4) catch |_| {
typed_failed = true
yield (&i32_fallback).ptr[..0]
}
if (typed_failed) return 38
defer mem.raw_free(mem.c_allocator, ptr_cast(u8, typed.ptr), typed.len * size_of(i32), align_of(i32))
typed[0] = 10
typed[3] = 20
if (typed[0] + typed[3] != 30) return 39
resized ?*mut u8 = mem.raw_realloc(mem.c_allocator, none, 0, 4, 1)
if resized |bytes| { if resized |bytes| {
bytes[0] = 10 bytes[0] = 10
bytes[1] = 20 bytes[1] = 20
@@ -160,82 +250,82 @@ main func() i32 {
return 20 return 20
} }
grown ?*mut u8 = mem.realloc(mem.c_allocator, resized, 4, 8, 1) grown ?*mut u8 = mem.raw_realloc(mem.c_allocator, resized, 4, 8, 1)
if grown |bytes| { if grown |bytes| {
resized = grown resized = grown
if bytes[0] != 10 or bytes[1] != 20 or bytes[2] != 30 or bytes[3] != 40 { if bytes[0] != 10 or bytes[1] != 20 or bytes[2] != 30 or bytes[3] != 40 {
mem.free(mem.c_allocator, grown, 8, 1) mem.raw_free(mem.c_allocator, grown, 8, 1)
return 21 return 21
} }
} else { } else {
mem.free(mem.c_allocator, resized, 4, 1) mem.raw_free(mem.c_allocator, resized, 4, 1)
return 22 return 22
} }
shrunk ?*mut u8 = mem.realloc(mem.c_allocator, resized, 8, 2, 1) shrunk ?*mut u8 = mem.raw_realloc(mem.c_allocator, resized, 8, 2, 1)
if shrunk |bytes| { if shrunk |bytes| {
resized = shrunk resized = shrunk
if bytes[0] != 10 or bytes[1] != 20 { if bytes[0] != 10 or bytes[1] != 20 {
mem.free(mem.c_allocator, shrunk, 2, 1) mem.raw_free(mem.c_allocator, shrunk, 2, 1)
return 23 return 23
} }
} else { } else {
mem.free(mem.c_allocator, resized, 8, 1) mem.raw_free(mem.c_allocator, resized, 8, 1)
return 24 return 24
} }
invalid ?*mut u8 = mem.realloc(mem.c_allocator, resized, 2, 4, 24) invalid ?*mut u8 = mem.raw_realloc(mem.c_allocator, resized, 2, 4, 24)
if invalid |memory| { if invalid |memory| {
mem.free(mem.c_allocator, memory, 4, 24) mem.raw_free(mem.c_allocator, memory, 4, 24)
mem.free(mem.c_allocator, resized, 2, 1) mem.raw_free(mem.c_allocator, resized, 2, 1)
return 25 return 25
} }
if resized |bytes| { if resized |bytes| {
if bytes[0] != 10 or bytes[1] != 20 { if bytes[0] != 10 or bytes[1] != 20 {
mem.free(mem.c_allocator, resized, 2, 1) mem.raw_free(mem.c_allocator, resized, 2, 1)
return 26 return 26
} }
} }
resized = mem.realloc(mem.c_allocator, resized, 2, 0, 1) resized = mem.raw_realloc(mem.c_allocator, resized, 2, 0, 1)
if resized |memory| { if resized |memory| {
mem.free(mem.c_allocator, memory, 0, 1) mem.raw_free(mem.c_allocator, memory, 0, 1)
return 27 return 27
} }
over_aligned ?*mut u8 = mem.alloc(mem.c_allocator, 4, 32) over_aligned ?*mut u8 = mem.raw_alloc(mem.c_allocator, 4, 32)
if over_aligned |bytes| { if over_aligned |bytes| {
bytes[0] = 11 bytes[0] = 11
bytes[1] = 22 bytes[1] = 22
} else { } else {
return 28 return 28
} }
over_aligned_grown ?*mut u8 = mem.realloc(mem.c_allocator, over_aligned, 4, 8, 32) over_aligned_grown ?*mut u8 = mem.raw_realloc(mem.c_allocator, over_aligned, 4, 8, 32)
if over_aligned_grown |bytes| { if over_aligned_grown |bytes| {
if bytes[0] != 11 or bytes[1] != 22 { if bytes[0] != 11 or bytes[1] != 22 {
mem.free(mem.c_allocator, over_aligned_grown, 8, 32) mem.raw_free(mem.c_allocator, over_aligned_grown, 8, 32)
return 29 return 29
} }
mem.free(mem.c_allocator, over_aligned_grown, 8, 32) mem.raw_free(mem.c_allocator, over_aligned_grown, 8, 32)
} else { } else {
mem.free(mem.c_allocator, over_aligned, 4, 32) mem.raw_free(mem.c_allocator, over_aligned, 4, 32)
return 30 return 30
} }
zero_alignment ?*mut u8 = mem.alloc(mem.c_allocator, 8, 0) zero_alignment ?*mut u8 = mem.raw_alloc(mem.c_allocator, 8, 0)
if zero_alignment |memory| { if zero_alignment |memory| {
mem.free(mem.c_allocator, memory, 8, 0) mem.raw_free(mem.c_allocator, memory, 8, 0)
return 1 return 1
} }
bad_alignment ?*mut u8 = mem.alloc(mem.c_allocator, 8, 24) bad_alignment ?*mut u8 = mem.raw_alloc(mem.c_allocator, 8, 24)
if bad_alignment |memory| { if bad_alignment |memory| {
mem.free(mem.c_allocator, memory, 8, 24) mem.raw_free(mem.c_allocator, memory, 8, 24)
return 2 return 2
} }
aligned ?*mut u8 = mem.alloc(mem.c_allocator, 64, 32) aligned ?*mut u8 = mem.raw_alloc(mem.c_allocator, 64, 32)
defer mem.free(mem.c_allocator, aligned, 64, 32) defer mem.raw_free(mem.c_allocator, aligned, 64, 32)
if aligned |bytes| { if aligned |bytes| {
bytes[0] = 1 bytes[0] = 1
bytes[63] = 2 bytes[63] = 2
+50 -10
View File
@@ -1,22 +1,58 @@
c :: import "@ffi/c" c :: import "@ffi/c"
Allocator :: struct { AllocatorVTable :: struct {
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 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
} }
alloc func(allocator Allocator, size usize, alignment usize) ?*mut u8 { Allocator :: struct {
return allocator.alloc(allocator.context, size, alignment) context ?*mut anyopaque
vtable @AllocatorVTable
} }
realloc func(allocator Allocator, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 { AllocError :: enum {
return allocator.realloc(allocator.context, memory, old_size, new_size, alignment) out_of_memory
} }
free func(allocator Allocator, memory ?*mut u8, size usize, alignment usize) void { raw_alloc func(allocator Allocator, size usize, alignment usize) ?*mut u8 {
allocator.free(allocator.context, memory, size, alignment) return allocator.vtable.alloc(allocator.context, size, alignment)
}
raw_realloc func(allocator Allocator, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
return allocator.vtable.realloc(allocator.context, memory, old_size, new_size, alignment)
}
raw_free func(allocator Allocator, memory ?*mut u8, size usize, alignment usize) void {
allocator.vtable.free(allocator.context, memory, size, alignment)
}
_empty_storage [1]mut u64 = [0]
_empty_slice func($T type, count usize) []mut T {
pointer *mut T :: ptr_cast(T, (&_empty_storage).ptr)
return pointer[..count]
}
alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
if count == 0 {
return _empty_slice(T, 0)
}
element_size usize :: size_of(T)
if element_size == 0 {
return _empty_slice(T, count)
}
if count > max_value(usize) / element_size {
return .out_of_memory
}
memory ?*mut u8 = raw_alloc(allocator, count * element_size, align_of(T))
if memory |bytes| {
pointer *mut T :: ptr_cast(T, bytes)
return pointer[..count]
}
return .out_of_memory
} }
_malloc_alignment usize :: 16 # ponytail: aarch64-macos libc malloc alignment assumption. _malloc_alignment usize :: 16 # ponytail: aarch64-macos libc malloc alignment assumption.
@@ -93,9 +129,13 @@ _c_free func(_ ?*mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
c.free(memory) c.free(memory)
} }
c_allocator Allocator :: Allocator { _c_vtable AllocatorVTable :: AllocatorVTable {
context = none,
alloc = _c_alloc, alloc = _c_alloc,
realloc = _c_realloc, realloc = _c_realloc,
free = _c_free, free = _c_free,
} }
c_allocator Allocator :: Allocator {
context = none,
vtable = &_c_vtable,
}