From 95c61311ca917a1bbae04738accd6202f7a0ebe2 Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Mon, 6 Jul 2026 21:24:49 +0200 Subject: [PATCH] allocator interface (first pass) --- LANGUAGE.md | 15 +- README.md | 7 +- TODO.md | 142 ++++++---- compiler/build.odin | 2 +- compiler/checker/checker.odin | 236 +++++++++++++++- compiler/hir/hir.odin | 1 + compiler/ir/ir.odin | 1 + compiler/lexer/lexer.odin | 2 + compiler/llvm/llvm.odin | 9 +- compiler/loader/loader.odin | 5 +- compiler/lower/lower.odin | 3 +- compiler/parser/parser.odin | 31 +- compiler/token/token.odin | 2 + compiler/translatec/translatec.odin | 8 +- compiler/types/types.odin | 69 ++++- compiler_tests.odin | 265 +++++++++++++++++- .../build/raylib/vendor/raylib/raylib.bro | 54 ++-- examples/programs/comptime_v1/main.bro | 4 +- examples/programs/mem_allocator/main.bro | 20 ++ ffi/c/stdio.bro | 24 +- ffi/c/stdlib.bro | 2 + std/mem/heap/heap.bro | 9 +- std/mem/mem.bro | 29 ++ testbed/game/raylib.bro | 54 ++-- 24 files changed, 833 insertions(+), 161 deletions(-) create mode 100644 examples/programs/mem_allocator/main.bro create mode 100644 ffi/c/stdlib.bro create mode 100644 std/mem/mem.bro diff --git a/LANGUAGE.md b/LANGUAGE.md index 5af1844..00a9635 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -18,18 +18,19 @@ roadmap and milestone history. ### scalar, aggregate, and pointer types -- exact-width integers, `isize`, `usize`, `f32`, `f64`, `bool`, `void`, and contextual `int`, `float`, and `range` constraints +- exact-width integers, `isize`, `usize`, `f32`, `f64`, `bool`, `void`, `anyopaque`, and contextual `int`, `float`, and `range` constraints - 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 - 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)` - 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-to-array `.len`, indexing, slicing, `.ptr` on slices and pointers-to-arrays, implicit address-taking for array-variable slices, and pointer/slice sentinel weakening +- `ptr_cast(T, ptr)` as a first-pass pointer-child retype that preserves optionality, pointer kind, mutability, and sentinel shape - UTF-8 string literals as immutable pointers to static zero-terminated byte arrays, plus raw backtick multiline strings - narrow immutable zero-terminated byte pointer/slice conversion to `*c_char` / `?*c_char` without general `u8`/`c_char` interchange - optionals with `none`, `orelse`, postfix `?`, conditional unwraps, guarded unwraps, and left-to-right short-circuiting multi-unwraps - nominal distinct types with exact backing construction, native enums with optional explicit integer backing, contextual enum literals, and imported C enums as target-backed integer aliases -- source-order native structs, defined/opaque `c_struct`, keyed record literals, native untagged unions, and native tagged unions `union(Enum)` / `union(enum)` +- source-order native structs, opaque nominal records with `Name :: opaque`, complete `c_struct { ... }`, keyed record literals, native untagged unions, and native tagged unions `union(Enum)` / `union(enum)` - void-payload tagged-union variants, anonymous struct payloads, contextual `.variant`, `.variant{payload}`, and `.variant{field = value}` construction - native sum composition with `A | B` for unbacked enums and tagged unions, using program-global `u16` variant ids - fallible channel types `T ! E`, where `E` is a native enum/tagged union or supported sum composition @@ -58,9 +59,9 @@ roadmap and milestone history. - comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values - bodyful `c_func` definitions and bodyless `c_func` declarations with exact external symbol names - concrete-only C signatures, C variadic declarations/calls, and C default argument promotions -- native function pointer values and types with `*func(...) R`, fallible `*func(...) R ! E`, optional `?*func(...) R`, and non-variadic native indirect calls +- native function pointer values and types with `@func(...) R`, fallible `@func(...) R ! E`, optional `?@func(...) R`, and non-variadic native indirect calls - Apple Silicon C ABI lowering for scalars, pointers, fixed-signature plain records/unions, small aggregates, homogeneous float aggregates, and indirect aggregate returns -- imported C typedefs, scalar constants, enum constants, fixed arrays, complete plain structs/unions, and pointers to opaque records +- imported C typedefs, scalar constants, enum constants, fixed arrays, complete plain structs/unions, C `void*` as nullable `anyopaque` pointers, and pointers to opaque records - imported external C object variables, including mutable variables and immutable object globals - object-like scalar and plain record/union macro constants - supported static inline C functions through generated external wrappers @@ -71,7 +72,8 @@ roadmap and milestone history. ### standard packages -- `std/mem/heap` v1 byte allocation over libc: `alloc(size usize) ?*mut u8` and `free(ptr ?*mut u8)` +- `std/mem` allocator contract over byte allocation: `Allocator` with `?*mut anyopaque` context, `heap`, `alloc(allocator, size, alignment) ?*mut u8`, and `free(allocator, ptr, size, alignment)` +- `std/mem/heap` legacy compatibility wrappers: `alloc(size usize) ?*mut u8` and `free(ptr ?*mut u8)` ### compiler behavior @@ -88,7 +90,8 @@ roadmap and milestone history. - tuples and native Brolang variadic functions - exporting Brolang functions to C and broader target-specific C ABI lowering - non-plain C record layouts such as bitfields, packed records, flexible arrays, qualified fields, and C variadic record arguments -- typed heap allocation, allocator parameters, arenas, pools, build-mode heap policy, and escaping-allocation diagnostics +- typed heap allocation helpers, arenas, pools, build-mode heap policy, and escaping-allocation diagnostics +- broader Zig-style pointer/result casts beyond V1 `ptr_cast(T, ptr)` - sum-type ABI/layout polish, including dynamic tag-width shrinking, all-void channel collapse, and cross-module global-id determinism - backed/C enum composition and must-consume fallible linting - result-to-argument type-demand propagation through function call boundaries diff --git a/README.md b/README.md index cf44e87..067453d 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,8 @@ main func() void { Header imports expose supported external functions, typedefs, C scalars, fixed arrays, complete plain structs and unions, function pointer typedefs, and -pointers to opaque records. Plain records can be constructed with keyed +pointers to opaque records. C `void*` imports as nullable `anyopaque` pointers. +Plain records can be constructed with keyed literals, accessed by field, and passed or returned by value through fixed C signatures on `aarch64-macos`. Unsupported or incomplete records remain pointer-only. Header imports never add linker inputs; implementations must still @@ -154,7 +155,7 @@ declares them. Imports beginning with `@` resolve from the project root: ```bro import "../math" other_math :: import "../math" -heap :: import "@std/mem/heap" +mem :: import "@std/mem" value :: math.sum(other_math.value, 1) ``` @@ -165,7 +166,7 @@ Current prototype features: - `#` comments - Immutable `::` bindings, typed mutable `=` locals/globals, and `_` sinks - Exact-width signed/unsigned integers, `f32`, `f64`, `isize`, `usize`, and loose integer-constrained `int` -- Target-dependent atomic `c_*` primitive types, `c_func`, and defined or opaque `c_struct` +- Target-dependent atomic `c_*` primitive types, `c_func`, complete `c_struct`, `opaque`, `anyopaque`, and V1 `ptr_cast(T, ptr)` - Arrays, sentinel arrays, single-item pointers, many-item pointers, sentinel many-item pointers, slices, sentinel slices, strings, character literals, optionals, and native structs - String literals as immutable pointers to static zero-terminated byte arrays - Pointer-preserving `.ptr`/`.len`, pointer-to-array indexing and slicing, postfix pointer dereference and optional unwrap, and keyed struct literals diff --git a/TODO.md b/TODO.md index 1fedf89..86efbfc 100644 --- a/TODO.md +++ b/TODO.md @@ -11,7 +11,7 @@ 1. interop type foundation (implemented) - unsigned integers, floats, and target-dependent c scalar types - atomic `c_*` primitive types remain distinct until target-aware lowering - - `c_func` and pointer-only `c_struct`; `c` remains an ordinary identifier + - `c_func`, complete `c_struct`, and pointer-only `opaque`; `c` remains an ordinary identifier - keep binding mutability (`::` / `=`) separate from element or pointee mutability (`mut`) - arrays and indexing - `[N]T`: array with `N` logical elements @@ -28,9 +28,9 @@ - character literals - optionals with trapping unwrap and fallback operations - native structs with compiler-controlled layout - - pointer-only `c_struct` support with target c layout + - complete `c_struct` support with target c layout and pointer-only `opaque` records - `Some :: c_struct { ... }`: defined c-layout struct - - `Some :: c_struct`: opaque c-layout struct + - `Some :: opaque`: incomplete nominal record - passing c structs by value was deferred until milestone 4.1 2. restricted c header imports (implemented) @@ -164,12 +164,19 @@ - added a native type-alias declaration `Name :: alias T` (parser/lexer/token surface; the `types.define_alias` / `.Alias` machinery already existed) so C typedefs and callback typedefs round-trip - - emits functions, complete/opaque structs (collapsing `typedef struct {...} Foo`), + - emits functions, complete structs and opaque records (collapsing `typedef struct {...} Foo`), typedef aliases, and scalar/aggregate/enum-member constants - C unions, external variables, static-inline functions, and unsupported declarations have no hand-writable spelling and are emitted as `# unsupported in bindings:` comments (functions that reference an un-spellable union therefore keep a dangling reference) +11.1. opaque, anyopaque, and pointer casts (implemented; v1) + - `Name :: opaque` is the incomplete nominal record spelling; bodyless `c_struct` is invalid + - `anyopaque` is the erased object type used behind pointers for C `void*` and allocator contexts + - C `void` function results remain `void`; C `void*` / `const void*` import and render as `?*mut anyopaque` / `?*anyopaque` + - `ptr_cast(T, ptr)` preserves pointer shape and only changes the child type in v1 + - future direction: generalize toward Zig-style arbitrary pointer-result casts once casts have a broader result-type story + 12. `undefined` as inspired by zig (implemented): - allow mutable local declarations with `undefined` - undefined values are assigned a poison value (0xaa...) @@ -620,10 +627,11 @@ parameters 25. dynamic heap allocation (implemented; v1) - - `std/mem/heap` is a tiny relative-importable package over libc `malloc`/`free` - - `heap.alloc(size usize) ?*mut u8` returns nullable mutable byte memory; callers use existing optional unwraps - - `heap.free(ptr ?*mut u8) void` forwards to C `free`, including `none` / null - - typed allocation, allocator parameters, arenas/pools, build-mode heap policy, and escaping-allocation diagnostics remain deferred + - `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` + - typed allocation helpers, arenas/pools, build-mode heap policy, and escaping-allocation diagnostics remain deferred 26. import from project "root" (implemented) - imports beginning with `@` resolve from the project root @@ -705,7 +713,7 @@ - comptime storage pointers/slices cannot materialize as runtime memory; escaped dead storage is rejected - bare concrete non-comptime function names are values; native function pointer - types use `*func(...) R` and fallible `*func(...) R ! E` + types use `@func(...) R` and fallible `@func(...) R ! E` - comptime-known native/bodyful `c_func` values can be called; bodyless/imported callbacks remain runtime-only - native function pointers are non-variadic v1; C variadic function pointers stay @@ -1323,8 +1331,25 @@ total :: $sum_loop(4) # implemented: mutable locals/loops/def ## A word on memory allocation -(NOTE THAT SYNTAX MAY NOT MATCH BROLANG EXACTLY AND SHOULD BE TAKEN WITH A GRAIN OF SALT - INSPIRATION ONLY) -(FURTHER, EXAMPLES ASSUME ARGUMENTS WITH DEFAULT VALUES AND COMPTIME POLYMORPHISM IN THE FORM OF GENERIC TYPE PARAMETERS - MONOMORPHISED) +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) +``` + +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. Memory allocation in Brolang is designed to be **explicit but not verbose**. We reject the dogma that global state is inherently evil — allocators are a cross-cutting concern that nearly every function needs, making them a perfect candidate for sensible defaults. @@ -1345,26 +1370,26 @@ Memory allocation in Brolang is designed to be **explicit but not verbose**. We ### The Default Heap Allocator -Brolang provides a **thread-local global heap allocator** that is: +Brolang provides a default heap allocator value: -* Determined at compile time by build mode +* Available as `mem.heap` +* Passed explicitly to `mem.alloc` and `mem.free` * **Immutable at runtime** — cannot be reconfigured ``` -import "std/mem/heap" +mem :: import "@std/mem" process func(input []u8) u64 { - # heap used for internal temporary work — does not escape - temp := heap.alloc(u8, size: input.len * 2) - defer heap.free(temp) + temp ?*mut u8 = mem.alloc(mem.heap, input.len * 2, 1) + defer mem.free(mem.heap, temp, input.len * 2, 1) # ... work with temp ... - return compute_hash(temp) # only the result escapes, not the allocation + return compute_hash(temp) } ``` -The behavior of `heap` depends on build mode: +Future build modes can choose different implementations behind `mem.heap` without changing the allocator contract: | Build Mode | Allocator Behavior | | -- | -- | @@ -1372,7 +1397,7 @@ The behavior of `heap` depends on build mode: | Release | Fast allocator, zero overhead | | ReleaseSafe | Bounds-checking allocator | -This is configured at compile time. You cannot change which allocator `heap` uses at runtime. This is intentional — it prevents bugs where memory allocated with one allocator is freed with another. +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. ### The Escaping Allocation Rule @@ -1381,42 +1406,41 @@ This is configured at compile time. You cannot change which allocator `heap` use This rule makes ownership transfer visible at the function signature level. The caller never needs to read the function's implementation to know whether heap cleanup is involved: ``` -import "std/mem" -import "std/mem/heap" +mem :: import "@std/mem" # Allocation escapes via return value — requires allocator -duplicate func(input []u8, allocator @mem.Allocator) []u8 { - result := allocator.alloc(u8, size: input.len) +duplicate func(input []u8, allocator mem.Allocator) ?*mut u8 { + result ?*mut u8 = mem.alloc(allocator, input.len, 1) mem.copy(result, input) return result # caller manages this memory } # Allocation escapes via mutable parameter — requires allocator -init func(obj: @mut MyStruct, allocator: @mem.Allocator) void { - obj.buffer = allocator.alloc(u8, size: 100) +init func(obj @mut MyStruct, allocator mem.Allocator) void { + obj.buffer = mem.alloc(allocator, 100, 1) # caller now knows heap memory was written into obj } # No allocation escapes — no allocator needed -process func(input: []u8) u64 { - temp := heap.alloc(u8, size: input.len) - defer heap.free(temp) +process func(input []u8) u64 { + temp ?*mut u8 = mem.alloc(mem.heap, input.len, 1) + defer mem.free(mem.heap, temp, input.len, 1) # ... work with temp ... return compute_hash(temp) } # No heap allocation at all — no allocator needed -reset func(obj: @mut MyStruct) void { +reset func(obj @mut MyStruct) void { obj.count = 0 } main func() void { - data := duplicate("hello", heap) - defer heap.free(data) + data := duplicate("hello", mem.heap) + defer mem.free(mem.heap, data, 5, 1) mut obj := MyStruct{ ... } - init(&obj, heap) - defer heap.free(obj.buffer) + init(&obj, mem.heap) + defer mem.free(mem.heap, obj.buffer, 100, 1) } ``` @@ -1424,9 +1448,9 @@ 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 `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 `mem.heap` directly and are freed before the function returns. No allocator parameter needed, no burden on the caller. -**The compiler enforces this rule.** If a function heap-allocates memory that escapes without accepting an allocator parameter, the compiler emits an error. +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. ### Why Immutable Defaults? @@ -1437,16 +1461,16 @@ Consider what would happen if you could reconfigure the default allocator: mem.heap_set(my_custom_heap) # Somewhere else in the codebase... -data := heap.alloc(u8, size: 100) +data := mem.alloc(mem.heap, 100, 1) # Later, someone changes it again... mem.heap_set(different_heap) # Now who frees `data`? With which allocator? -heap.free(data) # 💥 Wrong allocator - undefined behavior! +mem.free(mem.heap, data, 100, 1) # wrong allocator - undefined behavior ``` -This is "action at a distance" — the behavior of `heap.free()` depends on what some unrelated code did earlier. By making `heap` immutable, Brolang guarantees: +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: **Whatever you allocate with, you free with.** @@ -1454,15 +1478,16 @@ This is "action at a distance" — the behavior of `heap.free()` depends on what For specialized needs, you create explicit allocator instances. These are not global — you manage their lifetime and pass them where needed. +The examples in this section are future typed API sketches. The v1 allocator contract is still `mem.Allocator` plus byte-oriented `mem.alloc`/`mem.free`. + **Arena Allocator**: Fast bump allocation, bulk deallocation: ``` -import "std/mem" -import "std/mem/heap" +mem :: import "@std/mem" -process_file func(path: []u8, allocator: @mem.Allocator) !Data { +process_file func(path []u8, allocator mem.Allocator) !Data { # arena manages its own backing memory via heap - arena := mem.Arena.init(heap, capacity: mem.megabytes(1)) + arena := mem.Arena.init(mem.heap, capacity: mem.megabytes(1)) defer arena.deinit() # all temporary allocations from arena (fast bump allocation) @@ -1484,14 +1509,13 @@ process_file func(path: []u8, allocator: @mem.Allocator) !Data { **Pool Allocator**: O(1) fixed-size allocation, no fragmentation: ``` -import "std/mem" -import "std/mem/heap" +mem :: import "@std/mem" EntitySystem :: struct { pool: mem.Pool(Entity), } -init_entities func(allocator: @mem.Allocator) EntitySystem { +init_entities func(allocator mem.Allocator) EntitySystem { return EntitySystem{ pool = mem.Pool(Entity).init(allocator, capacity: 10_000), } @@ -1511,23 +1535,23 @@ despawn func(sys: @mut EntitySystem, entity: @Entity) void { As described in the escaping allocation rule, when a function heap-allocates memory that escapes its scope, it must accept an allocator parameter. The caller decides which allocator to use: ``` -import "std/mem" +mem :: import "@std/mem" # Function that uses caller's allocator -parse func(input: []u8, allocator: @mem.Allocator) !ParseResult { - buffer := allocator.alloc(u8, size: input.len) - defer allocator.free(buffer) +parse func(input []u8, allocator mem.Allocator) !ParseResult { + buffer := mem.alloc(allocator, input.len, 1) + defer mem.free(allocator, buffer, input.len, 1) # ... parse into buffer ... - result := allocator.alloc(ParseResult) # size defaults to 1 + result := mem.alloc(allocator, parse_result_size, parse_result_alignment) return result } # Caller decides which allocator to use main func() void { # use an arena for this parsing work - arena := mem.Arena.init(heap, capacity: mem.kilobytes(64)) + arena := mem.Arena.init(mem.heap, capacity: mem.kilobytes(64)) defer arena.deinit() result := parse(input, &arena) catch |err| { # handle error @@ -1546,15 +1570,15 @@ main func() void { | What | How | When to Use | | -- | -- | -- | -| `heap.alloc(T, size: n)` | Thread-local global | General purpose, 90% of cases | -| `heap.create(T)` | Thread-local global | Allocate single item | -| `allocator.alloc(T, size: n)` | Caller-provided | Escaping allocations (returned or written to caller's data) | -| `arena.alloc(T, size: n)` | Explicit instance | Temporary/scoped work, bulk free | -| `pool.alloc()` | Explicit instance | Many same-sized objects, O(1) | +| `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(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 `heap` satisfies the `Allocator` interface, so callers can pass `heap` as the allocator argument when they don't need a specialized allocator — which is most of the time. +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. -**The golden rule:** Allocate and free with the same allocator. The type system helps enforce this — memory from `heap` can only be freed with `heap`, memory from your arena can only be freed with that arena. The escaping allocation rule ensures the caller always knows which allocator was used. +**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. ### Compared to Other Languages diff --git a/compiler/build.odin b/compiler/build.odin index a57a328..ecb9f24 100644 --- a/compiler/build.odin +++ b/compiler/build.odin @@ -290,7 +290,7 @@ unwrap_coercions :: proc(m: ^hir.Module, id: hir.Expr_Id) -> hir.Expr_Id { cur := id for cur != hir.INVALID_EXPR && int(cur) < len(m.exprs) { #partial switch m.exprs[cur].kind { - case .Retype, .Weaken_Slice, .Weaken_Pointer, .Decay_Array_Pointer, .Slice_Ptr, + case .Retype, .Pointer_Cast, .Weaken_Slice, .Weaken_Pointer, .Decay_Array_Pointer, .Slice_Ptr, .Widen, .Sum_Widen, .Optional_Some, .C_Coerce, .Scalar_Cast: cur = m.exprs[cur].left case: diff --git a/compiler/checker/checker.odin b/compiler/checker/checker.odin index 078eab4..19648b9 100644 --- a/compiler/checker/checker.odin +++ b/compiler/checker/checker.odin @@ -180,6 +180,21 @@ type_label :: proc(checker: ^Checker, value: types.Type) -> string { return types.name(value) } +is_ptr_cast_call :: proc(checker: ^Checker, expr: ast.Expr) -> bool { + return expr.left == ast.INVALID_EXPR && + !symbol.is_valid(expr.qualifier) && + symbol_text(checker, expr.name) == "ptr_cast" +} + +valid_ptr_cast_child :: proc(checker: ^Checker, value: types.Type) -> bool { + return types.is_valid(value) && + !types.is_void(value) && + !types.is_anyopaque(value) && + !types.is_function(value, &checker.module.types) && + (types.is_runtime_value(value, &checker.module.types) || + types.is_opaque_struct(value, &checker.module.types)) +} + 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 @@ -958,7 +973,7 @@ function_pointer_type_for_template :: proc( defer delete(params, checker.allocator) function := checker.ast_module.functions[template] function_type := types.function(&checker.module.types, params, result, function.c_abi, function.variadic) - pointer_type := types.pointer(&checker.module.types, function_type, false, true) + pointer_type := types.pointer(&checker.module.types, function_type, false, false) spec := INVALID_SPEC if demanded == nil { if demand_spec { @@ -1970,6 +1985,42 @@ infer_expr :: proc( } continue } + if is_ptr_cast_call(checker, expr) { + if len(expr.args) != 2 { + last = types.INVALID + _ = pop(&stack) + continue + } + child, child_ok := resolve_type_argument(checker, expr.args[0], pkg, file) + operand := infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types) + result := types.INVALID + if child_ok && valid_ptr_cast_child(checker, child) { + result, _ = types.replace_pointer_child(&checker.module.types, operand, child) + } + last = result + _ = pop(&stack) + continue + } + if callee_type, handled := infer_qualified_value_field_type(checker, expr, locals, pkg, file); handled { + _, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types) + if !ok { + last = types.INVALID + _ = pop(&stack) + continue + } + stack[frame_index].left = function_type + stack[frame_index].args = make([]types.Type, len(expr.args), checker.allocator) + stack[frame_index].stage = 6 + if len(expr.args) > 0 { + append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION}) + } else if valid_callable_arity(function_item, 0) { + last = function_item.child + delete(stack[frame_index].args, checker.allocator) + stack[frame_index].args = nil + _ = pop(&stack) + } + continue + } target_pkg, available := expr_package(checker, expr, pkg, file) template := ast.INVALID_FUNCTION if available { @@ -3477,6 +3528,126 @@ find_struct_field :: proc(checker: ^Checker, struct_type: types.Type, name: symb return 0, {}, false } +field_type_from_value :: proc(checker: ^Checker, expr: ast.Expr, base_type: types.Type) -> types.Type { + store := &checker.module.types + field_name := symbol_text(checker, expr.name) + item, has_item := types.container(base_type, store) + if has_item && (item.kind == .Array || item.kind == .Slice) { + if field_name == "len" { + return types.USIZE + } + if field_name == "ptr" && + (item.kind == .Slice || types.is_pointer(base_type, store)) { + return container_pointer_type(store, item) + } + } + value_type := base_type + if types.is_pointer(value_type, store) { + value_type = types.child_type(value_type, store) + } + _, field, ok := find_struct_field(checker, value_type, expr.name) + return field.type if ok else types.INVALID +} + +infer_qualified_value_field_type :: proc( + checker: ^Checker, + expr: ast.Expr, + locals: []Infer_Local, + pkg: ast.Package_Id, + file: ast.File_Id, +) -> (types.Type, bool) { + if !symbol.is_valid(expr.qualifier) || + find_import(checker, file, expr.qualifier) != ast.INVALID_IMPORT { + return types.INVALID, false + } + base_type := find_infer_local(locals, expr.qualifier) + if !types.is_valid(base_type) { + if global := find_global(checker, expr.qualifier, pkg); global != ast.INVALID_GLOBAL { + base_type = checker.global_types[global] + } + } + if !types.is_valid(base_type) { + return types.INVALID, false + } + return field_type_from_value(checker, expr, base_type), true +} + +build_field_from_value :: proc( + checker: ^Checker, + expr: ast.Expr, + base: hir.Expr_Id, + base_type: types.Type, +) -> (hir.Expr_Id, bool) { + store := &checker.module.types + field_name := symbol_text(checker, expr.name) + item, has_item := types.container(base_type, store) + if has_item && (item.kind == .Array || item.kind == .Slice) { + if field_name == "len" { + return add_hir_expr(checker, hir.Expr{ + kind=.Length, span=expr.span, type=types.USIZE, left=base, + target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, + }), true + } + if field_name == "ptr" && + (item.kind == .Slice || types.is_pointer(base_type, store)) { + return add_hir_expr(checker, hir.Expr{ + kind=.Slice_Ptr, span=expr.span, + type=container_pointer_type(store, item), left=base, + target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, + }), true + } + if field_name == "ptr" && item.kind == .Array { + id := source.add(checker.diagnostics, expr.span, "arrays do not expose '.ptr'; take their address first") + return invalid_hir_expr(checker, expr.span, id), false + } + } + value_type := base_type + if types.is_pointer(value_type, store) { + value_type = types.child_type(value_type, store) + } + index, field, ok := find_struct_field(checker, value_type, expr.name) + if !ok { + id := source.addf(checker.diagnostics, expr.span, "unknown struct field '%s'", symbol_text(checker, expr.name)) + return invalid_hir_expr(checker, expr.span, id), false + } + if types.is_void(field.type) { + id := source.addf(checker.diagnostics, expr.span, "variant '%s' has no payload to read", symbol_text(checker, expr.name)) + return invalid_hir_expr(checker, expr.span, id), false + } + return add_hir_expr(checker, hir.Expr{ + kind=.Field, span=expr.span, type=field.type, integer=i64(index), left=base, + target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, + }), true +} + +build_qualified_value_field :: proc( + checker: ^Checker, + expr: ast.Expr, + locals: []Build_Local, + global_reads: ^[dynamic]hir.Global_Id, + pkg: ast.Package_Id, + file: ast.File_Id, +) -> (hir.Expr_Id, bool, bool) { + if !symbol.is_valid(expr.qualifier) || + find_import(checker, file, expr.qualifier, true) != ast.INVALID_IMPORT { + return hir.INVALID_EXPR, false, false + } + if local, ok := find_build_local(locals, expr.qualifier); ok { + base := add_hir_expr(checker, hir.Expr{ + kind=.Local, span=expr.span, type=local.type, target=hir.local_ref(local.id), + left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, + }) + value, ok := build_field_from_value(checker, expr, base, local.type) + return value, true, ok + } + if global := find_global(checker, expr.qualifier, pkg); global != ast.INVALID_GLOBAL { + base := build_global_reference(checker, global, expr.span, global_reads) + value, ok := build_field_from_value(checker, expr, base, checker.global_types[global]) + return value, true, ok + } + return hir.INVALID_EXPR, false, false +} + find_enum_member :: proc(checker: ^Checker, enum_type: types.Type, name: symbol.Id) -> (types.Enum_Member, bool) { for member in types.enum_members_for(&checker.module.types, enum_type) { if member.name == u32(name) { @@ -3581,7 +3752,15 @@ build_function_value :: proc( } defer delete(params, checker.allocator) function_type := types.function(&checker.module.types, params, result, function.c_abi, function.variadic) - pointer_type := types.pointer(&checker.module.types, function_type, false, true) + pointer_type := types.pointer(&checker.module.types, function_type, false, false) + expected_pointer := expected + if types.is_optional(expected_pointer, &checker.module.types) { + expected_pointer = types.child_type(expected_pointer, &checker.module.types) + } + if _, _, expected_function, ok := types.function_pointer(expected_pointer, &checker.module.types); ok && + types.equal(expected_function, function_type) { + pointer_type = expected_pointer + } spec := find_spec(checker, template, params) if spec == INVALID_SPEC { id := source.addf( @@ -4553,6 +4732,41 @@ build_expr :: proc( append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION}) 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)) + last = invalid_hir_expr(checker, expr.span, id) + _ = pop(&stack) + continue + } + 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, "ptr_cast target must be a type") + last = invalid_hir_expr(checker, expr.span, id) + _ = pop(&stack) + continue + } + if !valid_ptr_cast_child(checker, target) { + id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "ptr_cast target must be a sized runtime object type, got %s", type_label(checker, target)) + last = invalid_hir_expr(checker, expr.span, id) + _ = pop(&stack) + continue + } + stack[frame_index].target_type = target + stack[frame_index].stage = 9 + append(&stack, Build_Expr_Frame{expr=expr.args[1], expected=types.INVALID, template=ast.INVALID_FUNCTION}) + continue + } + if callee, handled, ok := build_qualified_value_field(checker, expr, locals, global_reads, pkg, file); handled { + if !ok { + last = callee + _ = pop(&stack) + continue + } + stack[frame_index].stage = 6 + last = callee + continue + } target_pkg, available := expr_package(checker, expr, pkg, file, true) if !available { id := add_package_resolution_diagnostic(checker, expr, file) @@ -4992,6 +5206,24 @@ build_expr :: proc( } _ = pop(&stack) } + if frame.stage == 9 { + result, ok := types.replace_pointer_child(&checker.module.types, checker.module.exprs[last].type, frame.target_type) + if !ok { + id := source.add(checker.diagnostics, expr.span, "ptr_cast operand must be a pointer or optional pointer") + last = invalid_hir_expr(checker, expr.span, id) + } else { + last = add_hir_expr(checker, hir.Expr{ + kind=.Pointer_Cast, + span=expr.span, + type=result, + left=last, + target=hir.INVALID_REF, + right=hir.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + } + _ = pop(&stack) + } } return last } diff --git a/compiler/hir/hir.odin b/compiler/hir/hir.odin index 97ad203..1c18368 100644 --- a/compiler/hir/hir.odin +++ b/compiler/hir/hir.odin @@ -103,6 +103,7 @@ Expr_Kind :: enum u8 { C_Vararg_Promote, Retype, Scalar_Cast, + Pointer_Cast, Weaken_Pointer, Weaken_Slice, Decay_Array_Pointer, diff --git a/compiler/ir/ir.odin b/compiler/ir/ir.odin index 0c39330..b3cf6f3 100644 --- a/compiler/ir/ir.odin +++ b/compiler/ir/ir.odin @@ -100,6 +100,7 @@ Opcode :: enum u8 { C_Vararg_Promote, Retype, Scalar_Cast, + Pointer_Cast, Weaken_Pointer, Weaken_Slice, Decay_Array_Pointer, diff --git a/compiler/lexer/lexer.odin b/compiler/lexer/lexer.odin index b4f8378..e3bab10 100644 --- a/compiler/lexer/lexer.odin +++ b/compiler/lexer/lexer.odin @@ -18,6 +18,7 @@ keyword_kind :: proc(text: string) -> token.Kind { case "c_func": return .Keyword_C_Func case "struct": return .Keyword_Struct case "c_struct": return .Keyword_C_Struct + case "opaque": return .Keyword_Opaque case "union": return .Keyword_Union case "enum": return .Keyword_Enum case "distinct": return .Keyword_Distinct @@ -44,6 +45,7 @@ keyword_kind :: proc(text: string) -> token.Kind { case "true": return .Keyword_True case "false": return .Keyword_False case "void": return .Keyword_Void + case "anyopaque": return .Keyword_Anyopaque case "bool": return .Keyword_Bool case "int": return .Keyword_Int case "float": return .Keyword_Float diff --git a/compiler/llvm/llvm.odin b/compiler/llvm/llvm.odin index 8ba9112..b9e6955 100644 --- a/compiler/llvm/llvm.odin +++ b/compiler/llvm/llvm.odin @@ -256,7 +256,7 @@ valid_value :: proc( .Load_Global, .Function_Address, .Address_Of, .Load, .Union_Tag, .Slice, .Length, .Slice_Ptr, .Fallible_Error, .Extract, .Select, .Unwrap, .Optional_Is_Some, .Optional_Value, .Orelse, - .Widen, .Sum_Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Scalar_Cast, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer, + .Widen, .Sum_Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Scalar_Cast, .Pointer_Cast, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer, .Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked, .Pointer_Add, .Not, .Compare, .Call: return true case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin, @@ -1511,6 +1511,13 @@ emit_instruction_stream :: proc( continue } fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr %%v%d, ptr null\n", instruction_index, instruction.a) + case .Pointer_Cast: + if !valid_instruction(instructions, instruction.a) || + !types.same_pointer_shape(instructions[instruction.a].type, instruction.type, &emitter.module.types) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid pointer cast operand") + continue + } + fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr %%v%d, ptr null\n", instruction_index, instruction.a) case .Weaken_Slice: if !valid_instruction(instructions, instruction.a) || !types.can_weaken_slice(instructions[instruction.a].type, instruction.type, &emitter.module.types) { diff --git a/compiler/loader/loader.odin b/compiler/loader/loader.odin index e7b67eb..fa68884 100644 --- a/compiler/loader/loader.odin +++ b/compiler/loader/loader.odin @@ -173,6 +173,9 @@ translate_c_type :: proc( case .C_Longdouble: translated = types.C_LONGDOUBLE case .Pointer: child := translate_c_type(state, result, item.child, pkg, record_mapping, type_mapping) + if child == types.VOID { + child = types.ANYOPAQUE + } if types.is_valid(child) { pointer := types.pointer(&state.module.type_store, child, item.mutable, true) translated = types.optional(&state.module.type_store, pointer) @@ -964,7 +967,7 @@ load_header :: proc(state: ^State, path: string, import_span: source.Span) -> as name = fmt.tprintf("__c_record_%d", len(state.record_types)) } record_type = types.named(&state.module.type_store, u32(pkg_id), u32(symbol.intern(state.symbols, name))) - _ = types.define_record(&state.module.type_store, record_type, nil, true, true, record.kind == .Union) + _ = types.define_record(&state.module.type_store, record_type, nil, false, true, record.kind == .Union) append(&state.record_identities, strings.clone(record.identity, state.allocator)) append(&state.record_types, record_type) } diff --git a/compiler/lower/lower.odin b/compiler/lower/lower.odin index cbade56..975c83b 100644 --- a/compiler/lower/lower.odin +++ b/compiler/lower/lower.odin @@ -665,7 +665,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id { }) } _ = pop(&stack) - case .Widen, .Sum_Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Scalar_Cast, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer: + case .Widen, .Sum_Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Scalar_Cast, .Pointer_Cast, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer: stack[frame_index].stage = 1 append(&stack, Lower_Expr_Frame{expr=expr.left}) case .Negate: @@ -727,6 +727,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id { case .C_Vararg_Promote: op = .C_Vararg_Promote case .Retype: op = .Retype case .Scalar_Cast: op = .Scalar_Cast + case .Pointer_Cast: op = .Pointer_Cast case: op = .Widen } last = append_instruction(state, ir.Instruction{ diff --git a/compiler/parser/parser.odin b/compiler/parser/parser.odin index 857b816..949881e 100644 --- a/compiler/parser/parser.odin +++ b/compiler/parser/parser.odin @@ -113,7 +113,8 @@ is_type_token :: proc(kind: token.Kind) -> bool { .Keyword_C_Short, .Keyword_C_Ushort, .Keyword_C_Int, .Keyword_C_Uint, .Keyword_C_Long, .Keyword_C_Ulong, .Keyword_C_Longlong, .Keyword_C_Ulonglong, .Keyword_C_Float, .Keyword_C_Double, .Keyword_C_Longdouble, - .Keyword_Void, .Keyword_Bool, .Keyword_Func, .Keyword_C_Func, .Identifier, .Question, .At, .Star, .Left_Bracket: + .Keyword_Void, .Keyword_Anyopaque, .Keyword_Bool, .Keyword_Func, .Keyword_C_Func, + .Identifier, .Question, .At, .Star, .Left_Bracket: return true } return false @@ -326,6 +327,9 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax { case .Keyword_Void: advance(parser) return types.VOID + case .Keyword_Anyopaque: + advance(parser) + return types.ANYOPAQUE case .Keyword_Bool: advance(parser) return types.BOOL @@ -600,7 +604,7 @@ parse_integer_magnitude :: proc(text: string) -> (u64, bool) { parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id { tok := current(parser) #partial switch tok.kind { - case .Keyword_Int, .Keyword_Float, .Keyword_Range, .Keyword_Void, .Keyword_Bool: + case .Keyword_Int, .Keyword_Float, .Keyword_Range, .Keyword_Void, .Keyword_Anyopaque, .Keyword_Bool: start := tok target := parse_type_atom(parser) return add_expr(parser, ast.Expr{ @@ -2214,6 +2218,13 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, is_unio ended_by_newline := current(parser).kind == .Newline skip_newlines(parser) if current(parser).kind != .Left_Brace { + if c_layout { + source.add(parser.diagnostics, start.span, "c_struct declarations require a body; use 'opaque' for incomplete types") + if !ended_by_newline { + _ = finish_statement(parser) + } + return + } if !c_layout { source.add( parser.diagnostics, start.span, @@ -2242,6 +2253,18 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, is_unio _ = finish_statement(parser) } +parse_opaque :: proc(parser: ^Parser, name: token.Token) { + start := advance(parser) + id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol)) + if !types.define_record(&parser.module.type_store, id, nil, false, true, false) { + source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name)) + } + if current(parser).kind == .Left_Brace { + source.add(parser.diagnostics, start.span, "opaque declarations do not have a body") + } + _ = finish_statement(parser) +} + // synthesize_union_tag builds the anonymous runtime discriminant enum for a tagged // union: one member per variant, valued by the program-global (name, payload-type) // ID. The declared tag enum, if any, remains only the validation surface. @@ -2573,6 +2596,10 @@ parse_top_level :: proc(parser: ^Parser) { parse_struct(parser, name, current(parser).kind == .Keyword_C_Struct) return } + if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Opaque { + parse_opaque(parser, name) + return + } if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Union { parse_struct(parser, name, false, is_union=true) return diff --git a/compiler/token/token.odin b/compiler/token/token.odin index dcbcb20..d0d172d 100644 --- a/compiler/token/token.odin +++ b/compiler/token/token.odin @@ -54,6 +54,7 @@ Kind :: enum u8 { Keyword_C_Func, Keyword_Struct, Keyword_C_Struct, + Keyword_Opaque, Keyword_Union, Keyword_Enum, Keyword_Distinct, @@ -80,6 +81,7 @@ Kind :: enum u8 { Keyword_True, Keyword_False, Keyword_Void, + Keyword_Anyopaque, Keyword_Bool, Keyword_Int, Keyword_Float, diff --git a/compiler/translatec/translatec.odin b/compiler/translatec/translatec.odin index 4c43c97..ef8073b 100644 --- a/compiler/translatec/translatec.odin +++ b/compiler/translatec/translatec.odin @@ -99,7 +99,11 @@ render_type :: proc(b: ^strings.Builder, result: ^cimport.Result, id: cimport.Ty if item.mutable { strings.write_string(b, "mut ") } - render_type(b, result, item.child, record_names) + if int(item.child) >= 0 && int(item.child) < len(result.types) && result.types[item.child].kind == .Void { + strings.write_string(b, "anyopaque") + } else { + render_type(b, result, item.child, record_names) + } case .Array: fmt.sbprintf(b, "[%d]", item.count) render_type(b, result, item.child, record_names) @@ -157,7 +161,7 @@ emit_records :: proc(b: ^strings.Builder, result: ^cimport.Result, record_names: } if !record.complete || len(record.reason) > 0 { // opaque / pointer-only struct - fmt.sbprintf(b, "%s :: c_struct\n", name) + fmt.sbprintf(b, "%s :: opaque\n", name) wrote = true continue } diff --git a/compiler/types/types.odin b/compiler/types/types.odin index 1ad747e..234422f 100644 --- a/compiler/types/types.odin +++ b/compiler/types/types.odin @@ -43,6 +43,7 @@ C_LONGDOUBLE :: Type(28) BOOL :: Type(29) FLOAT :: Type(30) RANGE :: Type(31) +ANYOPAQUE :: Type(32) DYNAMIC_START :: Type(64) @@ -56,6 +57,7 @@ Numeric_Category :: enum u8 { Kind :: enum u8 { Invalid, Void, + Anyopaque, Int_Constraint, Float_Constraint, Range_Constraint, @@ -555,6 +557,8 @@ kind :: proc(value: Type, store: ^Store = nil) -> Kind { return .Invalid case VOID: return .Void + case ANYOPAQUE: + return .Anyopaque case INT: return .Int_Constraint case FLOAT: @@ -595,6 +599,10 @@ is_void :: proc(value: Type) -> bool { return value == VOID } +is_anyopaque :: proc(value: Type) -> bool { + return value == ANYOPAQUE +} + is_bool :: proc(value: Type) -> bool { return value == BOOL } @@ -915,8 +923,19 @@ is_runtime_value :: proc(value: Type, store: ^Store, depth := 0) -> bool { if value_kind == .Scalar || value_kind == .Pointer { return true } - if value_kind == .Slice || value_kind == .Array || value_kind == .Range || value_kind == .Optional { - return !contains_c_struct_by_value(value, store) + if value_kind == .Slice || value_kind == .Array || value_kind == .Range { + item, ok := node(store, value) + return ok && is_runtime_value(item.child, store, depth+1) && !contains_c_struct_by_value(value, store) + } + if value_kind == .Optional { + item, ok := node(store, value) + if !ok { + return false + } + if is_pointer(item.child, store) { + return true + } + return is_runtime_value(item.child, store, depth+1) && !contains_c_struct_by_value(value, store) } if value_kind == .Struct || value_kind == .Union { item, ok := node(store, value) @@ -1204,6 +1223,42 @@ function_pointer :: proc(value: Type, store: ^Store) -> (pointer_item, function_ return pointer_node, function_node, pointer_node.child, true } +replace_pointer_child :: proc(store: ^Store, value, child: Type) -> (Type, bool) { + item, ok := node(store, value) + if !ok { + return INVALID, false + } + if item.kind == .Optional { + replaced, replaced_ok := replace_pointer_child(store, item.child, child) + if !replaced_ok || !is_pointer(replaced, store) { + return INVALID, false + } + return optional(store, replaced), true + } + if item.kind != .Pointer { + return INVALID, false + } + item.child = child + return intern(store, item), true +} + +same_pointer_shape :: proc(left, right: Type, store: ^Store) -> bool { + left_item, left_ok := node(store, left) + right_item, right_ok := node(store, right) + if !left_ok || !right_ok { + return false + } + if left_item.kind == .Optional || right_item.kind == .Optional { + return left_item.kind == .Optional && right_item.kind == .Optional && + same_pointer_shape(left_item.child, right_item.child, store) + } + return left_item.kind == .Pointer && right_item.kind == .Pointer && + left_item.many == right_item.many && + left_item.mutable == right_item.mutable && + left_item.has_sentinel == right_item.has_sentinel && + (!left_item.has_sentinel || left_item.sentinel == right_item.sentinel) +} + is_c_struct :: proc(value: Type, store: ^Store) -> bool { item, ok := node(store, value) return ok && (item.kind == .Struct || item.kind == .Union) && item.c_layout @@ -1318,6 +1373,10 @@ with_array_count :: proc(store: ^Store, value: Type, count: u64) -> Type { can_weaken_pointer :: proc(from, to: Type, store: ^Store) -> bool { from_node, from_ok := node(store, from) to_node, to_ok := node(store, to) + if from_ok && to_ok && (from_node.kind == .Optional || to_node.kind == .Optional) { + return from_node.kind == .Optional && to_node.kind == .Optional && + can_weaken_pointer(from_node.child, to_node.child, store) + } if !from_ok || !to_ok || from_node.kind != .Pointer || to_node.kind != .Pointer || from_node.many != to_node.many || (to_node.mutable && !from_node.mutable) { return false @@ -1327,9 +1386,12 @@ can_weaken_pointer :: proc(from, to: Type, store: ^Store) -> bool { return false } same_child := from_node.child == to_node.child + anyopaque_erasure := to_node.child == ANYOPAQUE && + (is_runtime_value(from_node.child, store) || + is_opaque_struct(from_node.child, store)) c_string := from_node.many && from_node.child == U8 && to_node.child == C_CHAR && from_node.has_sentinel && from_node.sentinel == 0 && !to_node.mutable - return same_child || c_string + return same_child || anyopaque_erasure || c_string } can_weaken_slice :: proc(from, to: Type, store: ^Store) -> bool { @@ -1594,6 +1656,7 @@ name :: proc(value: Type) -> string { switch value { case INVALID: return "" case VOID: return "void" + case ANYOPAQUE: return "anyopaque" case BOOL: return "bool" case INT: return "int" case FLOAT: return "float" diff --git a/compiler_tests.odin b/compiler_tests.odin index 4c116d0..2579acd 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -298,7 +298,7 @@ parser_accepts_native_function_pointer_types :: proc(t: ^testing.T) { text := `Error :: enum { bad } -take func(callback ?*func(value i32) i32, fallible *func() i32 ! Error) void +take func(callback ?@func(value i32) i32, fallible @func() i32 ! Error) void main func() void {} ` source_file := source.Source{path="test.bro", text=text} @@ -320,12 +320,12 @@ main func() void {} fallible, fallible_ok := types.node(&module.type_store, fallible_function.child) testing.expect_value(t, len(diagnostics.items), 0) testing.expect(t, optional_ok && optional.kind == .Optional) - testing.expect(t, pointer_ok && pointer.kind == .Pointer && pointer.many && !pointer.mutable) + testing.expect(t, pointer_ok && pointer.kind == .Pointer && !pointer.many && !pointer.mutable) testing.expect(t, function_ok && function.kind == .Function && !function.c_abi && !function.variadic) testing.expect(t, function.child == types.I32) testing.expect_value(t, len(params), 1) testing.expect(t, params[0].type == types.I32) - testing.expect(t, fallible_pointer_ok && fallible_pointer.kind == .Pointer) + testing.expect(t, fallible_pointer_ok && fallible_pointer.kind == .Pointer && !fallible_pointer.many) testing.expect(t, fallible_function_ok && fallible_function.kind == .Function && !fallible_function.c_abi) testing.expect(t, fallible_ok && fallible.kind == .Fallible && fallible.child == types.I32) } @@ -1526,11 +1526,12 @@ variadicness_is_part_of_c_function_signature_compatibility :: proc(t: ^testing.T } @(test) -c_structs_are_by_value_and_may_be_opaque :: proc(t: ^testing.T) { +c_structs_are_by_value_and_bodyless_c_struct_uses_opaque :: proc(t: ^testing.T) { text := `Defined :: c_struct { value c_int } -Opaque :: c_struct +Opaque :: opaque +Bodyless :: c_struct Empty :: c_struct {} Bad :: c_struct { values []i32 @@ -1555,18 +1556,117 @@ main func() void { defer hir.destroy_module(&hir_module) found_opaque := false + found_bodyless := false found_bad_layout := false found_empty := false for diagnostic in diagnostics.items { found_opaque = found_opaque || strings.contains(diagnostic.message, "cannot be passed by value") + found_bodyless = found_bodyless || strings.contains(diagnostic.message, "use 'opaque'") found_bad_layout = found_bad_layout || strings.contains(diagnostic.message, "C-layout-compatible") found_empty = found_empty || strings.contains(diagnostic.message, "at least one field") } testing.expect(t, found_opaque) + testing.expect(t, found_bodyless) testing.expect(t, found_bad_layout) testing.expect(t, found_empty) } +@(test) +opaque_anyopaque_and_ptr_cast_compile_and_lower :: proc(t: ^testing.T) { + text := `Handle :: opaque +take func(value ?*mut anyopaque) void {} +use_handle func(handle ?@mut Handle) void {} +main func() void { + values [2]mut u8 = [1, 2] + raw ?*mut anyopaque = (&values).ptr + bytes ?*mut u8 = ptr_cast(u8, raw) + take(bytes) + if bytes |p| { + p[1] = 5 + } + + one u8 = 1 + single ?@mut anyopaque = &one + typed ?@mut u8 = ptr_cast(u8, single) + if typed |p| { + p^ = 2 + } + + handle ?@mut Handle = none + use_handle(handle) +} +` + 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) + ir_module := lower.lower(&hir_module) + defer ir.destroy_module(&ir_module) + + hir_casts := 0 + for expr in hir_module.exprs { + hir_casts += 1 if expr.kind == .Pointer_Cast else 0 + } + ir_casts := 0 + for function in ir_module.functions { + for instruction in function.instructions { + ir_casts += 1 if instruction.op == .Pointer_Cast else 0 + } + } + + testing.expect_value(t, len(diagnostics.items), 0) + testing.expect_value(t, hir_casts, 2) + testing.expect_value(t, ir_casts, 2) +} + +@(test) +anyopaque_by_value_and_invalid_ptr_casts_are_rejected :: proc(t: ^testing.T) { + text := `Callback :: alias c_func() void +main func() void { + raw ?*mut anyopaque = none + value anyopaque = undefined + _ = ptr_cast(void, raw) + _ = ptr_cast(anyopaque, raw) + _ = ptr_cast(Callback, raw) + _ = ptr_cast(u8, 1) + _ = ptr_cast(1, raw) +} +` + 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) + + found_by_value := false + found_bad_target := false + found_bad_operand := false + found_target_type := false + for diagnostic in diagnostics.items { + found_by_value = found_by_value || strings.contains(diagnostic.message, "could not infer a concrete type") + found_bad_target = found_bad_target || strings.contains(diagnostic.message, "ptr_cast target must be a sized runtime object type") + found_bad_operand = found_bad_operand || strings.contains(diagnostic.message, "ptr_cast operand must be a pointer") + found_target_type = found_target_type || strings.contains(diagnostic.message, "ptr_cast target must be a type") + } + testing.expect(t, found_by_value) + testing.expect(t, found_bad_target) + testing.expect(t, found_bad_operand) + testing.expect(t, found_target_type) +} + @(test) aarch64_c_record_abi_classifies_fixed_parameters_and_results :: proc(t: ^testing.T) { text := `Small :: c_struct { @@ -2448,7 +2548,7 @@ main func() void { @(test) native_function_pointer_type_restrictions_are_diagnosed :: proc(t: ^testing.T) { text := `main func() void { - callback *func(...) void = undefined + callback @func(...) void = undefined } ` source_file := source.Source{path="test.bro", text=text} @@ -2812,6 +2912,140 @@ milestone_24_rejects_invalid_forms :: proc(t: ^testing.T) { } } +@(test) +field_function_pointer_calls_lower_as_indirect_calls :: proc(t: ^testing.T) { + text := `Callbacks :: struct { + call @func(value i32) i32 + value i32 +} +plus_one func(value i32) i32 { + return value + 1 +} +run func(callbacks Callbacks) i32 { + return callbacks.call(callbacks.value) +} +main func() i32 { + callbacks Callbacks = Callbacks { call = plus_one, value = 41 } + return run(callbacks) - 42 +} +` + 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) + ir_module := lower.lower(&hir_module) + defer ir.destroy_module(&ir_module) + + indirect_calls := 0 + for expr in hir_module.exprs { + if expr.kind == .Call && expr.left != hir.INVALID_EXPR { + indirect_calls += 1 + } + } + testing.expect_value(t, len(diagnostics.items), 0) + testing.expect(t, len(ir_module.functions) > 0) + testing.expect(t, indirect_calls > 0) +} + +@(test) +field_function_pointer_calls_reject_non_callable_fields :: proc(t: ^testing.T) { + text := `Box :: struct { + value i32 +} +main func() void { + box Box = Box { value = 1 } + box.value() +} +` + 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) + + found := false + for diagnostic in diagnostics.items { + found = found || strings.contains(diagnostic.message, "call target is not a function pointer") + } + testing.expect(t, found) +} + +@(test) +allocator_contract_compiles_and_runs :: proc(t: ^testing.T) { + output := "/tmp/brolang-test-mem-allocator" + defer _ = os.remove(output) + 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) +allocator_contract_heap_global_lowers :: 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/mem_allocator", &sources, &diagnostics, &symbols, context.allocator, context.allocator, cimport.Options{}, target.DEFAULT, ".") + defer ast.destroy_module(&ast_module) + testing.expect(t, loaded) + + hir_module := checker.check(&ast_module, &diagnostics, &symbols) + defer hir.destroy_module(&hir_module) + ir_module := lower.lower(&hir_module) + defer ir.destroy_module(&ir_module) + + found_heap := 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" { + continue + } + found_heap = 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) + if name == "context" { + optional_item, optional_ok := types.node(&hir_module.types, field.type) + if optional_ok && optional_item.kind == .Optional { + pointer_item, pointer_ok := types.node(&hir_module.types, optional_item.child) + found_anyopaque_context = pointer_ok && + pointer_item.kind == .Pointer && + pointer_item.mutable && + pointer_item.many && + pointer_item.child == types.ANYOPAQUE + } + } + found_alloc_callback = found_alloc_callback || name == "alloc" && callable && !callback_pointer.many + found_free_callback = found_free_callback || name == "free" && callable && !callback_pointer.many + } + } + + 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_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" @@ -8571,7 +8805,7 @@ main func() i32 { @(test) distinct_types_reject_implicit_conversions_operators_and_invalid_backings :: proc(t: ^testing.T) { - text := `Opaque :: c_struct + text := `Opaque :: opaque UserID :: distinct u32 OtherID :: distinct u32 BadInt :: distinct int @@ -8881,13 +9115,16 @@ translate_c_emits_native_bindings_and_round_trips :: proc(t: ^testing.T) { result := cimport.init_result(context.allocator) // type table: [0]=c_int, [1]=c_ulong, [2]=Record(Pair), - // [3]=Function(c_int)->c_int, [4]=Pointer->Function (callback) + // [3]=Function(c_int)->c_int, [4]=Pointer->Function (callback), + // [5]=void, [6]=Pointer->void append(&result.types, cimport.Type{kind = .C_Int, child = cimport.INVALID_TYPE}) append(&result.types, cimport.Type{kind = .C_Ulong, child = cimport.INVALID_TYPE}) append(&result.types, cimport.Type{kind = .Record, record = 0, child = cimport.INVALID_TYPE}) func_params := []cimport.Type_Id{cimport.Type_Id(0)} append(&result.types, cimport.Type{kind = .Function, params = func_params, child = cimport.Type_Id(0)}) append(&result.types, cimport.Type{kind = .Pointer, child = cimport.Type_Id(3)}) + append(&result.types, cimport.Type{kind = .Void, child = cimport.INVALID_TYPE}) + append(&result.types, cimport.Type{kind = .Pointer, child = cimport.Type_Id(5), mutable = true}) // record 0: Pair { left c_int; right c_int } pair_fields: [dynamic]cimport.Field @@ -8900,13 +9137,20 @@ translate_c_emits_native_bindings_and_round_trips :: proc(t: ^testing.T) { append(&choice_fields, cimport.Field{name = "tag", type = cimport.Type_Id(0)}) append(&result.records, cimport.Record{name = "Choice", fields = choice_fields, kind = .Union, complete = true}) - // typedef aliases: a scalar and a callback function pointer + // record 2: incomplete struct -> opaque + append(&result.records, cimport.Record{name = "Handle", kind = .Struct, complete = false}) + + // typedef aliases: a scalar, a callback function pointer, and a C void pointer append(&result.aliases, cimport.Alias{name = "Size", type = cimport.Type_Id(1)}) append(&result.aliases, cimport.Alias{name = "Mapper", type = cimport.Type_Id(4)}) + append(&result.aliases, cimport.Alias{name = "RawPtr", type = cimport.Type_Id(6)}) add_params := []cimport.Type_Id{cimport.Type_Id(0), cimport.Type_Id(0)} add_param_names := []string{"a", "b"} append(&result.functions, cimport.Function{name = "imported_add", params = add_params, param_names = add_param_names, result = cimport.Type_Id(0)}) + raw_params := []cimport.Type_Id{cimport.Type_Id(6)} + raw_param_names := []string{"ptr"} + append(&result.functions, cimport.Function{name = "consume_raw", params = raw_params, param_names = raw_param_names, result = cimport.Type_Id(5)}) append(&result.macros, cimport.Macro_Constant{ name = "MAX_LEN", @@ -8938,8 +9182,11 @@ translate_c_emits_native_bindings_and_round_trips :: proc(t: ^testing.T) { testing.expect(t, strings.contains(output, "Size :: alias c_ulong")) // Function-pointer types carry no parameter names, so the callback renders `_`. testing.expect(t, strings.contains(output, "Mapper :: alias ?*c_func(_ c_int) c_int")) + testing.expect(t, strings.contains(output, "RawPtr :: alias ?*mut anyopaque")) + testing.expect(t, strings.contains(output, "Handle :: opaque")) // Real C parameter names are used when present. testing.expect(t, strings.contains(output, "imported_add c_func(a c_int, b c_int) c_int")) + testing.expect(t, strings.contains(output, "consume_raw c_func(ptr ?*mut anyopaque) void")) testing.expect(t, strings.contains(output, "MAX_LEN c_int :: 256")) testing.expect(t, strings.contains(output, "# unsupported in bindings: C union 'Choice'")) testing.expect(t, strings.contains(output, "# unsupported in bindings: external variable 'some_global'")) diff --git a/examples/build/raylib/vendor/raylib/raylib.bro b/examples/build/raylib/vendor/raylib/raylib.bro index a3d6c53..2226484 100644 --- a/examples/build/raylib/vendor/raylib/raylib.bro +++ b/examples/build/raylib/vendor/raylib/raylib.bro @@ -46,7 +46,7 @@ Rectangle :: c_struct { height c_float } Image :: c_struct { - data ?*mut void + data ?*mut anyopaque width c_int height c_int mipmaps c_int @@ -179,10 +179,10 @@ Wave :: c_struct { sampleRate c_uint sampleSize c_uint channels c_uint - data ?*mut void + data ?*mut anyopaque } -rAudioBuffer :: c_struct -rAudioProcessor :: c_struct +rAudioBuffer :: opaque +rAudioProcessor :: opaque AudioStream :: c_struct { buffer ?*mut rAudioBuffer processor ?*mut rAudioProcessor @@ -199,7 +199,7 @@ Music :: c_struct { frameCount c_uint looping bool ctxType c_int - ctxData ?*mut void + ctxData ?*mut anyopaque } VrDeviceInfo :: c_struct { hResolution c_int @@ -268,10 +268,10 @@ CameraProjection :: alias c_uint NPatchLayout :: alias c_uint TraceLogCallback :: alias ?*c_func(_ c_int, _ ?*c_char, _ ?*mut c_char) void LoadFileDataCallback :: alias ?*c_func(_ ?*c_char, _ ?*mut c_int) ?*mut c_uchar -SaveFileDataCallback :: alias ?*c_func(_ ?*c_char, _ ?*mut void, _ c_int) bool +SaveFileDataCallback :: alias ?*c_func(_ ?*c_char, _ ?*mut anyopaque, _ c_int) bool LoadFileTextCallback :: alias ?*c_func(_ ?*c_char) ?*mut c_char SaveFileTextCallback :: alias ?*c_func(_ ?*c_char, _ ?*mut c_char) bool -AudioCallback :: alias ?*c_func(_ ?*mut void, _ c_uint) void +AudioCallback :: alias ?*c_func(_ ?*mut anyopaque, _ c_uint) void RAYLIB_VERSION_MAJOR c_int :: 5 RAYLIB_VERSION_MINOR c_int :: 5 @@ -634,7 +634,7 @@ SetWindowMaxSize c_func(width c_int, height c_int) void SetWindowSize c_func(width c_int, height c_int) void SetWindowOpacity c_func(opacity c_float) void SetWindowFocused c_func() void -GetWindowHandle c_func() ?*mut void +GetWindowHandle c_func() ?*mut anyopaque GetScreenWidth c_func() c_int GetScreenHeight c_func() c_int GetRenderWidth c_func() c_int @@ -685,8 +685,8 @@ LoadShaderFromMemory c_func(vsCode ?*c_char, fsCode ?*c_char) Shader IsShaderValid c_func(shader Shader) bool GetShaderLocation c_func(shader Shader, uniformName ?*c_char) c_int GetShaderLocationAttrib c_func(shader Shader, attribName ?*c_char) c_int -SetShaderValue c_func(shader Shader, locIndex c_int, value ?*void, uniformType c_int) void -SetShaderValueV c_func(shader Shader, locIndex c_int, value ?*void, uniformType c_int, count c_int) void +SetShaderValue c_func(shader Shader, locIndex c_int, value ?*anyopaque, uniformType c_int) void +SetShaderValueV c_func(shader Shader, locIndex c_int, value ?*anyopaque, uniformType c_int, count c_int) void SetShaderValueMatrix c_func(shader Shader, locIndex c_int, mat Matrix) void SetShaderValueTexture c_func(shader Shader, locIndex c_int, texture Texture) void UnloadShader c_func(shader Shader) void @@ -714,17 +714,17 @@ SetConfigFlags c_func(flags c_uint) void OpenURL c_func(url ?*c_char) void TraceLog c_func(logLevel c_int, text ?*c_char, ...) void SetTraceLogLevel c_func(logLevel c_int) void -MemAlloc c_func(size c_uint) ?*mut void -MemRealloc c_func(ptr ?*mut void, size c_uint) ?*mut void -MemFree c_func(ptr ?*mut void) void +MemAlloc c_func(size c_uint) ?*mut anyopaque +MemRealloc c_func(ptr ?*mut anyopaque, size c_uint) ?*mut anyopaque +MemFree c_func(ptr ?*mut anyopaque) void SetTraceLogCallback c_func(callback ?*c_func(_ c_int, _ ?*c_char, _ ?*mut c_char) void) void SetLoadFileDataCallback c_func(callback ?*c_func(_ ?*c_char, _ ?*mut c_int) ?*mut c_uchar) void -SetSaveFileDataCallback c_func(callback ?*c_func(_ ?*c_char, _ ?*mut void, _ c_int) bool) void +SetSaveFileDataCallback c_func(callback ?*c_func(_ ?*c_char, _ ?*mut anyopaque, _ c_int) bool) void SetLoadFileTextCallback c_func(callback ?*c_func(_ ?*c_char) ?*mut c_char) void SetSaveFileTextCallback c_func(callback ?*c_func(_ ?*c_char, _ ?*mut c_char) bool) void LoadFileData c_func(fileName ?*c_char, dataSize ?*mut c_int) ?*mut c_uchar UnloadFileData c_func(data ?*mut c_uchar) void -SaveFileData c_func(fileName ?*c_char, data ?*mut void, dataSize c_int) bool +SaveFileData c_func(fileName ?*c_char, data ?*mut anyopaque, dataSize c_int) bool ExportDataAsCode c_func(data ?*c_uchar, dataSize c_int, fileName ?*c_char) bool LoadFileText c_func(fileName ?*c_char) ?*mut c_char UnloadFileText c_func(text ?*mut c_char) void @@ -967,8 +967,8 @@ IsTextureValid c_func(texture Texture) bool UnloadTexture c_func(texture Texture) void IsRenderTextureValid c_func(target RenderTexture) bool UnloadRenderTexture c_func(target RenderTexture) void -UpdateTexture c_func(texture Texture, pixels ?*void) void -UpdateTextureRec c_func(texture Texture, rec Rectangle, pixels ?*void) void +UpdateTexture c_func(texture Texture, pixels ?*anyopaque) void +UpdateTextureRec c_func(texture Texture, rec Rectangle, pixels ?*anyopaque) void GenTextureMipmaps c_func(texture ?*mut Texture) void SetTextureFilter c_func(texture Texture, filter c_int) void SetTextureWrap c_func(texture Texture, wrap c_int) void @@ -992,8 +992,8 @@ ColorAlpha c_func(color Color, alpha c_float) Color ColorAlphaBlend c_func(dst Color, src Color, tint Color) Color ColorLerp c_func(color1 Color, color2 Color, factor c_float) Color GetColor c_func(hexValue c_uint) Color -GetPixelColor c_func(srcPtr ?*mut void, format c_int) Color -SetPixelColor c_func(dstPtr ?*mut void, color Color, format c_int) void +GetPixelColor c_func(srcPtr ?*mut anyopaque, format c_int) Color +SetPixelColor c_func(dstPtr ?*mut anyopaque, color Color, format c_int) void GetPixelDataSize c_func(width c_int, height c_int, format c_int) c_int GetFontDefault c_func() Font LoadFont c_func(fileName ?*c_char) Font @@ -1082,7 +1082,7 @@ DrawBillboard c_func(camera Camera3D, texture Texture, position Vector3, scale c DrawBillboardRec c_func(camera Camera3D, texture Texture, source Rectangle, position Vector3, size Vector2, tint Color) void DrawBillboardPro c_func(camera Camera3D, texture Texture, source Rectangle, position Vector3, up Vector3, size Vector2, origin Vector2, rotation c_float, tint Color) void UploadMesh c_func(mesh ?*mut Mesh, dynamic bool) void -UpdateMeshBuffer c_func(mesh Mesh, index c_int, data ?*void, dataSize c_int, offset c_int) void +UpdateMeshBuffer c_func(mesh Mesh, index c_int, data ?*anyopaque, dataSize c_int, offset c_int) void UnloadMesh c_func(mesh Mesh) void DrawMesh c_func(mesh Mesh, material Material, transform Matrix) void DrawMeshInstanced c_func(mesh Mesh, material Material, transforms ?*Matrix, instances c_int) void @@ -1133,7 +1133,7 @@ LoadSound c_func(fileName ?*c_char) Sound LoadSoundFromWave c_func(wave Wave) Sound LoadSoundAlias c_func(source Sound) Sound IsSoundValid c_func(sound Sound) bool -UpdateSound c_func(sound Sound, data ?*void, sampleCount c_int) void +UpdateSound c_func(sound Sound, data ?*anyopaque, sampleCount c_int) void UnloadWave c_func(wave Wave) void UnloadSound c_func(sound Sound) void UnloadSoundAlias c_func(_ Sound) void @@ -1171,7 +1171,7 @@ GetMusicTimePlayed c_func(music Music) c_float LoadAudioStream c_func(sampleRate c_uint, sampleSize c_uint, channels c_uint) AudioStream IsAudioStreamValid c_func(stream AudioStream) bool UnloadAudioStream c_func(stream AudioStream) void -UpdateAudioStream c_func(stream AudioStream, data ?*void, frameCount c_int) void +UpdateAudioStream c_func(stream AudioStream, data ?*anyopaque, frameCount c_int) void IsAudioStreamProcessed c_func(stream AudioStream) bool PlayAudioStream c_func(stream AudioStream) void PauseAudioStream c_func(stream AudioStream) void @@ -1182,11 +1182,11 @@ SetAudioStreamVolume c_func(stream AudioStream, volume c_float) void SetAudioStreamPitch c_func(stream AudioStream, pitch c_float) void SetAudioStreamPan c_func(stream AudioStream, pan c_float) void SetAudioStreamBufferSizeDefault c_func(size c_int) void -SetAudioStreamCallback c_func(stream AudioStream, callback ?*c_func(_ ?*mut void, _ c_uint) void) void -AttachAudioStreamProcessor c_func(stream AudioStream, processor ?*c_func(_ ?*mut void, _ c_uint) void) void -DetachAudioStreamProcessor c_func(stream AudioStream, processor ?*c_func(_ ?*mut void, _ c_uint) void) void -AttachAudioMixedProcessor c_func(processor ?*c_func(_ ?*mut void, _ c_uint) void) void -DetachAudioMixedProcessor c_func(processor ?*c_func(_ ?*mut void, _ c_uint) void) void +SetAudioStreamCallback c_func(stream AudioStream, callback ?*c_func(_ ?*mut anyopaque, _ c_uint) void) void +AttachAudioStreamProcessor c_func(stream AudioStream, processor ?*c_func(_ ?*mut anyopaque, _ c_uint) void) void +DetachAudioStreamProcessor c_func(stream AudioStream, processor ?*c_func(_ ?*mut anyopaque, _ c_uint) void) void +AttachAudioMixedProcessor c_func(processor ?*c_func(_ ?*mut anyopaque, _ c_uint) void) void +DetachAudioMixedProcessor c_func(processor ?*c_func(_ ?*mut anyopaque, _ c_uint) void) void # unsupported in bindings: RAYLIB_H — C macro has no replacement value # unsupported in bindings: _VA_LIST — C macro has no replacement value diff --git a/examples/programs/comptime_v1/main.bro b/examples/programs/comptime_v1/main.bro index a295de5..c42d086 100644 --- a/examples/programs/comptime_v1/main.bro +++ b/examples/programs/comptime_v1/main.bro @@ -78,11 +78,11 @@ increment func(value i32) i32 { return value + 1 } -call_native func(callback *func(value i32) i32, value i32) i32 { +call_native func(callback @func(value i32) i32, value i32) i32 { return callback(value) } -call_fallible func(callback *func(flag bool) i32 ! Error, flag bool) i32 ! Error { +call_fallible func(callback @func(flag bool) i32 ! Error, flag bool) i32 ! Error { return try callback(flag) } diff --git a/examples/programs/mem_allocator/main.bro b/examples/programs/mem_allocator/main.bro new file mode 100644 index 0000000..ce2eba4 --- /dev/null +++ b/examples/programs/mem_allocator/main.bro @@ -0,0 +1,20 @@ +mem :: import "@std/mem" + +main func() i32 { + memory ?*mut u8 = mem.alloc(mem.heap, 4, 1) + defer mem.free(mem.heap, memory, 4, 1) + + 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 +} diff --git a/ffi/c/stdio.bro b/ffi/c/stdio.bro index c072d5d..3f64bc9 100644 --- a/ffi/c/stdio.bro +++ b/ffi/c/stdio.bro @@ -2,8 +2,8 @@ # unsupported in bindings: C union '__mbstate_t' has no native spelling __darwin_pthread_handler_rec :: c_struct { - __routine ?*c_func(_ ?*mut void) void - __arg ?*mut void + __routine ?*c_func(_ ?*mut anyopaque) void + __arg ?*mut anyopaque __next ?*mut __darwin_pthread_handler_rec } _opaque_pthread_attr_t :: c_struct { @@ -47,7 +47,7 @@ __sbuf :: c_struct { _base ?*mut c_uchar _size c_int } -__sFILEX :: c_struct +__sFILEX :: opaque __sFILE :: c_struct { _p ?*mut c_uchar _r c_int @@ -56,11 +56,11 @@ __sFILE :: c_struct { _file c_short _bf __sbuf _lbfsize c_int - _cookie ?*mut void - _close ?*c_func(_ ?*mut void) c_int - _read ?*c_func(_ ?*mut void, _ ?*mut c_char, _ c_int) c_int - _seek ?*c_func(_ ?*mut void, _ c_longlong, _ c_int) c_longlong - _write ?*c_func(_ ?*mut void, _ ?*c_char, _ c_int) c_int + _cookie ?*mut anyopaque + _close ?*c_func(_ ?*mut anyopaque) c_int + _read ?*c_func(_ ?*mut anyopaque, _ ?*mut c_char, _ c_int) c_int + _seek ?*c_func(_ ?*mut anyopaque, _ c_longlong, _ c_int) c_longlong + _write ?*c_func(_ ?*mut anyopaque, _ ?*c_char, _ c_int) c_int _ub __sbuf _extra ?*mut __sFILEX _ur c_int @@ -196,13 +196,13 @@ fopen c_func(__filename ?*c_char, __mode ?*c_char) ?*mut __sFILE fprintf c_func(_ ?*mut __sFILE, _ ?*c_char, ...) c_int fputc c_func(_ c_int, _ ?*mut __sFILE) c_int fputs c_func(_ ?*c_char, _ ?*mut __sFILE) c_int -fread c_func(__ptr ?*mut void, __size c_ulong, __nitems c_ulong, __stream ?*mut __sFILE) c_ulong +fread c_func(__ptr ?*mut anyopaque, __size c_ulong, __nitems c_ulong, __stream ?*mut __sFILE) c_ulong freopen c_func(_ ?*c_char, _ ?*c_char, _ ?*mut __sFILE) ?*mut __sFILE fscanf c_func(_ ?*mut __sFILE, _ ?*c_char, ...) c_int fseek c_func(_ ?*mut __sFILE, _ c_long, _ c_int) c_int fsetpos c_func(_ ?*mut __sFILE, _ ?*c_longlong) c_int ftell c_func(_ ?*mut __sFILE) c_long -fwrite c_func(__ptr ?*void, __size c_ulong, __nitems c_ulong, __stream ?*mut __sFILE) c_ulong +fwrite c_func(__ptr ?*anyopaque, __size c_ulong, __nitems c_ulong, __stream ?*mut __sFILE) c_ulong getc c_func(_ ?*mut __sFILE) c_int getchar c_func() c_int gets c_func(_ ?*mut c_char) ?*mut c_char @@ -254,7 +254,7 @@ dprintf c_func(_ c_int, _ ?*c_char, ...) c_int vdprintf c_func(_ c_int, _ ?*c_char, _ ?*mut c_char) c_int getdelim c_func(__linep ?*mut ?*mut c_char, __linecapp ?*mut c_ulong, __delimiter c_int, __stream ?*mut __sFILE) c_long getline c_func(__linep ?*mut ?*mut c_char, __linecapp ?*mut c_ulong, __stream ?*mut __sFILE) c_long -fmemopen c_func(__buf ?*mut void, __size c_ulong, __mode ?*c_char) ?*mut __sFILE +fmemopen c_func(__buf ?*mut anyopaque, __size c_ulong, __mode ?*c_char) ?*mut __sFILE open_memstream c_func(__bufp ?*mut ?*mut c_char, __sizep ?*mut c_ulong) ?*mut __sFILE asprintf c_func(_ ?*mut ?*mut c_char, _ ?*c_char, ...) c_int ctermid_r c_func(_ ?*mut c_char) ?*mut c_char @@ -264,7 +264,7 @@ fpurge c_func(_ ?*mut __sFILE) c_int setbuffer c_func(_ ?*mut __sFILE, _ ?*mut c_char, __size c_int) void setlinebuf c_func(_ ?*mut __sFILE) c_int vasprintf c_func(_ ?*mut ?*mut c_char, _ ?*c_char, _ ?*mut c_char) c_int -funopen c_func(_ ?*void, _ ?*c_func(_ ?*mut void, _ ?*mut c_char, _ c_int) c_int, _ ?*c_func(_ ?*mut void, _ ?*c_char, _ c_int) c_int, _ ?*c_func(_ ?*mut void, _ c_longlong, _ c_int) c_longlong, _ ?*c_func(_ ?*mut void) c_int) ?*mut __sFILE +funopen c_func(_ ?*anyopaque, _ ?*c_func(_ ?*mut anyopaque, _ ?*mut c_char, _ c_int) c_int, _ ?*c_func(_ ?*mut anyopaque, _ ?*c_char, _ c_int) c_int, _ ?*c_func(_ ?*mut anyopaque, _ c_longlong, _ c_int) c_longlong, _ ?*c_func(_ ?*mut anyopaque) c_int) ?*mut __sFILE __snprintf_chk c_func(_ ?*mut c_char, __maxlen c_ulong, _ c_int, _ c_ulong, _ ?*c_char, ...) c_int __vsnprintf_chk c_func(_ ?*mut c_char, __maxlen c_ulong, _ c_int, _ c_ulong, _ ?*c_char, _ ?*mut c_char) c_int __sprintf_chk c_func(_ ?*mut c_char, _ c_int, _ c_ulong, _ ?*c_char, ...) c_int diff --git a/ffi/c/stdlib.bro b/ffi/c/stdlib.bro new file mode 100644 index 0000000..c7a616e --- /dev/null +++ b/ffi/c/stdlib.bro @@ -0,0 +1,2 @@ +malloc c_func(size usize) ?*mut anyopaque +free c_func(ptr ?*mut anyopaque) void diff --git a/std/mem/heap/heap.bro b/std/mem/heap/heap.bro index 445404b..5846b10 100644 --- a/std/mem/heap/heap.bro +++ b/std/mem/heap/heap.bro @@ -1,6 +1,9 @@ -malloc c_func(size usize) ?*mut u8 -free c_func(ptr ?*mut u8) void +mem :: import ".." alloc func(size usize) ?*mut u8 { - return malloc(size) + return mem.alloc(mem.heap, size, 1) +} + +free func(memory ?*mut u8) void { + mem.free(mem.heap, memory, 0, 1) } diff --git a/std/mem/mem.bro b/std/mem/mem.bro new file mode 100644 index 0000000..0b3d08c --- /dev/null +++ b/std/mem/mem.bro @@ -0,0 +1,29 @@ +c :: import "@ffi/c" + +Allocator :: struct { + context ?*mut anyopaque + alloc @func(context ?*mut anyopaque, size usize, alignment usize) ?*mut u8 + free @func(context ?*mut anyopaque, memory ?*mut u8, size usize, alignment usize) void +} + +heap Allocator :: Allocator { + context = none, + alloc = heap_alloc, + free = heap_free, +} + +heap_alloc func(context ?*mut anyopaque, size usize, alignment usize) ?*mut u8 { + return ptr_cast(u8, c.malloc(size)) +} + +heap_free func(context ?*mut anyopaque, memory ?*mut u8, size usize, alignment usize) void { + c.free(memory) +} + +alloc func(allocator Allocator, size usize, alignment usize) ?*mut u8 { + return allocator.alloc(allocator.context, size, alignment) +} + +free func(allocator Allocator, memory ?*mut u8, size usize, alignment usize) void { + allocator.free(allocator.context, memory, size, alignment) +} diff --git a/testbed/game/raylib.bro b/testbed/game/raylib.bro index a3d6c53..2226484 100644 --- a/testbed/game/raylib.bro +++ b/testbed/game/raylib.bro @@ -46,7 +46,7 @@ Rectangle :: c_struct { height c_float } Image :: c_struct { - data ?*mut void + data ?*mut anyopaque width c_int height c_int mipmaps c_int @@ -179,10 +179,10 @@ Wave :: c_struct { sampleRate c_uint sampleSize c_uint channels c_uint - data ?*mut void + data ?*mut anyopaque } -rAudioBuffer :: c_struct -rAudioProcessor :: c_struct +rAudioBuffer :: opaque +rAudioProcessor :: opaque AudioStream :: c_struct { buffer ?*mut rAudioBuffer processor ?*mut rAudioProcessor @@ -199,7 +199,7 @@ Music :: c_struct { frameCount c_uint looping bool ctxType c_int - ctxData ?*mut void + ctxData ?*mut anyopaque } VrDeviceInfo :: c_struct { hResolution c_int @@ -268,10 +268,10 @@ CameraProjection :: alias c_uint NPatchLayout :: alias c_uint TraceLogCallback :: alias ?*c_func(_ c_int, _ ?*c_char, _ ?*mut c_char) void LoadFileDataCallback :: alias ?*c_func(_ ?*c_char, _ ?*mut c_int) ?*mut c_uchar -SaveFileDataCallback :: alias ?*c_func(_ ?*c_char, _ ?*mut void, _ c_int) bool +SaveFileDataCallback :: alias ?*c_func(_ ?*c_char, _ ?*mut anyopaque, _ c_int) bool LoadFileTextCallback :: alias ?*c_func(_ ?*c_char) ?*mut c_char SaveFileTextCallback :: alias ?*c_func(_ ?*c_char, _ ?*mut c_char) bool -AudioCallback :: alias ?*c_func(_ ?*mut void, _ c_uint) void +AudioCallback :: alias ?*c_func(_ ?*mut anyopaque, _ c_uint) void RAYLIB_VERSION_MAJOR c_int :: 5 RAYLIB_VERSION_MINOR c_int :: 5 @@ -634,7 +634,7 @@ SetWindowMaxSize c_func(width c_int, height c_int) void SetWindowSize c_func(width c_int, height c_int) void SetWindowOpacity c_func(opacity c_float) void SetWindowFocused c_func() void -GetWindowHandle c_func() ?*mut void +GetWindowHandle c_func() ?*mut anyopaque GetScreenWidth c_func() c_int GetScreenHeight c_func() c_int GetRenderWidth c_func() c_int @@ -685,8 +685,8 @@ LoadShaderFromMemory c_func(vsCode ?*c_char, fsCode ?*c_char) Shader IsShaderValid c_func(shader Shader) bool GetShaderLocation c_func(shader Shader, uniformName ?*c_char) c_int GetShaderLocationAttrib c_func(shader Shader, attribName ?*c_char) c_int -SetShaderValue c_func(shader Shader, locIndex c_int, value ?*void, uniformType c_int) void -SetShaderValueV c_func(shader Shader, locIndex c_int, value ?*void, uniformType c_int, count c_int) void +SetShaderValue c_func(shader Shader, locIndex c_int, value ?*anyopaque, uniformType c_int) void +SetShaderValueV c_func(shader Shader, locIndex c_int, value ?*anyopaque, uniformType c_int, count c_int) void SetShaderValueMatrix c_func(shader Shader, locIndex c_int, mat Matrix) void SetShaderValueTexture c_func(shader Shader, locIndex c_int, texture Texture) void UnloadShader c_func(shader Shader) void @@ -714,17 +714,17 @@ SetConfigFlags c_func(flags c_uint) void OpenURL c_func(url ?*c_char) void TraceLog c_func(logLevel c_int, text ?*c_char, ...) void SetTraceLogLevel c_func(logLevel c_int) void -MemAlloc c_func(size c_uint) ?*mut void -MemRealloc c_func(ptr ?*mut void, size c_uint) ?*mut void -MemFree c_func(ptr ?*mut void) void +MemAlloc c_func(size c_uint) ?*mut anyopaque +MemRealloc c_func(ptr ?*mut anyopaque, size c_uint) ?*mut anyopaque +MemFree c_func(ptr ?*mut anyopaque) void SetTraceLogCallback c_func(callback ?*c_func(_ c_int, _ ?*c_char, _ ?*mut c_char) void) void SetLoadFileDataCallback c_func(callback ?*c_func(_ ?*c_char, _ ?*mut c_int) ?*mut c_uchar) void -SetSaveFileDataCallback c_func(callback ?*c_func(_ ?*c_char, _ ?*mut void, _ c_int) bool) void +SetSaveFileDataCallback c_func(callback ?*c_func(_ ?*c_char, _ ?*mut anyopaque, _ c_int) bool) void SetLoadFileTextCallback c_func(callback ?*c_func(_ ?*c_char) ?*mut c_char) void SetSaveFileTextCallback c_func(callback ?*c_func(_ ?*c_char, _ ?*mut c_char) bool) void LoadFileData c_func(fileName ?*c_char, dataSize ?*mut c_int) ?*mut c_uchar UnloadFileData c_func(data ?*mut c_uchar) void -SaveFileData c_func(fileName ?*c_char, data ?*mut void, dataSize c_int) bool +SaveFileData c_func(fileName ?*c_char, data ?*mut anyopaque, dataSize c_int) bool ExportDataAsCode c_func(data ?*c_uchar, dataSize c_int, fileName ?*c_char) bool LoadFileText c_func(fileName ?*c_char) ?*mut c_char UnloadFileText c_func(text ?*mut c_char) void @@ -967,8 +967,8 @@ IsTextureValid c_func(texture Texture) bool UnloadTexture c_func(texture Texture) void IsRenderTextureValid c_func(target RenderTexture) bool UnloadRenderTexture c_func(target RenderTexture) void -UpdateTexture c_func(texture Texture, pixels ?*void) void -UpdateTextureRec c_func(texture Texture, rec Rectangle, pixels ?*void) void +UpdateTexture c_func(texture Texture, pixels ?*anyopaque) void +UpdateTextureRec c_func(texture Texture, rec Rectangle, pixels ?*anyopaque) void GenTextureMipmaps c_func(texture ?*mut Texture) void SetTextureFilter c_func(texture Texture, filter c_int) void SetTextureWrap c_func(texture Texture, wrap c_int) void @@ -992,8 +992,8 @@ ColorAlpha c_func(color Color, alpha c_float) Color ColorAlphaBlend c_func(dst Color, src Color, tint Color) Color ColorLerp c_func(color1 Color, color2 Color, factor c_float) Color GetColor c_func(hexValue c_uint) Color -GetPixelColor c_func(srcPtr ?*mut void, format c_int) Color -SetPixelColor c_func(dstPtr ?*mut void, color Color, format c_int) void +GetPixelColor c_func(srcPtr ?*mut anyopaque, format c_int) Color +SetPixelColor c_func(dstPtr ?*mut anyopaque, color Color, format c_int) void GetPixelDataSize c_func(width c_int, height c_int, format c_int) c_int GetFontDefault c_func() Font LoadFont c_func(fileName ?*c_char) Font @@ -1082,7 +1082,7 @@ DrawBillboard c_func(camera Camera3D, texture Texture, position Vector3, scale c DrawBillboardRec c_func(camera Camera3D, texture Texture, source Rectangle, position Vector3, size Vector2, tint Color) void DrawBillboardPro c_func(camera Camera3D, texture Texture, source Rectangle, position Vector3, up Vector3, size Vector2, origin Vector2, rotation c_float, tint Color) void UploadMesh c_func(mesh ?*mut Mesh, dynamic bool) void -UpdateMeshBuffer c_func(mesh Mesh, index c_int, data ?*void, dataSize c_int, offset c_int) void +UpdateMeshBuffer c_func(mesh Mesh, index c_int, data ?*anyopaque, dataSize c_int, offset c_int) void UnloadMesh c_func(mesh Mesh) void DrawMesh c_func(mesh Mesh, material Material, transform Matrix) void DrawMeshInstanced c_func(mesh Mesh, material Material, transforms ?*Matrix, instances c_int) void @@ -1133,7 +1133,7 @@ LoadSound c_func(fileName ?*c_char) Sound LoadSoundFromWave c_func(wave Wave) Sound LoadSoundAlias c_func(source Sound) Sound IsSoundValid c_func(sound Sound) bool -UpdateSound c_func(sound Sound, data ?*void, sampleCount c_int) void +UpdateSound c_func(sound Sound, data ?*anyopaque, sampleCount c_int) void UnloadWave c_func(wave Wave) void UnloadSound c_func(sound Sound) void UnloadSoundAlias c_func(_ Sound) void @@ -1171,7 +1171,7 @@ GetMusicTimePlayed c_func(music Music) c_float LoadAudioStream c_func(sampleRate c_uint, sampleSize c_uint, channels c_uint) AudioStream IsAudioStreamValid c_func(stream AudioStream) bool UnloadAudioStream c_func(stream AudioStream) void -UpdateAudioStream c_func(stream AudioStream, data ?*void, frameCount c_int) void +UpdateAudioStream c_func(stream AudioStream, data ?*anyopaque, frameCount c_int) void IsAudioStreamProcessed c_func(stream AudioStream) bool PlayAudioStream c_func(stream AudioStream) void PauseAudioStream c_func(stream AudioStream) void @@ -1182,11 +1182,11 @@ SetAudioStreamVolume c_func(stream AudioStream, volume c_float) void SetAudioStreamPitch c_func(stream AudioStream, pitch c_float) void SetAudioStreamPan c_func(stream AudioStream, pan c_float) void SetAudioStreamBufferSizeDefault c_func(size c_int) void -SetAudioStreamCallback c_func(stream AudioStream, callback ?*c_func(_ ?*mut void, _ c_uint) void) void -AttachAudioStreamProcessor c_func(stream AudioStream, processor ?*c_func(_ ?*mut void, _ c_uint) void) void -DetachAudioStreamProcessor c_func(stream AudioStream, processor ?*c_func(_ ?*mut void, _ c_uint) void) void -AttachAudioMixedProcessor c_func(processor ?*c_func(_ ?*mut void, _ c_uint) void) void -DetachAudioMixedProcessor c_func(processor ?*c_func(_ ?*mut void, _ c_uint) void) void +SetAudioStreamCallback c_func(stream AudioStream, callback ?*c_func(_ ?*mut anyopaque, _ c_uint) void) void +AttachAudioStreamProcessor c_func(stream AudioStream, processor ?*c_func(_ ?*mut anyopaque, _ c_uint) void) void +DetachAudioStreamProcessor c_func(stream AudioStream, processor ?*c_func(_ ?*mut anyopaque, _ c_uint) void) void +AttachAudioMixedProcessor c_func(processor ?*c_func(_ ?*mut anyopaque, _ c_uint) void) void +DetachAudioMixedProcessor c_func(processor ?*c_func(_ ?*mut anyopaque, _ c_uint) void) void # unsupported in bindings: RAYLIB_H — C macro has no replacement value # unsupported in bindings: _VA_LIST — C macro has no replacement value