typed alloc
This commit is contained in:
+2
-1
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
last = types.USIZE
|
if builtin == .Size_Of || builtin == .Align_Of {
|
||||||
|
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,10 +3218,19 @@ 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]
|
||||||
checker.global_types[index] = types.USIZE
|
if builtin := type_builtin_call(checker, expr); builtin != .None {
|
||||||
continue
|
if builtin == .Size_Of || builtin == .Align_Of {
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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) {
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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,
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user