From cd5b958d198a699bcc282c171c9e9f273cab8311 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 17 Jun 2026 13:31:11 +0300 Subject: [PATCH] comptime compiler-API: Phase 1 foundation + Phase 2.1 weld plan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce the welded comptime `compiler` library (`#library "compiler"` + `abi(.zig) extern compiler`), per design/comptime-compiler-api.md, and unify `callconv(...)` into the new `abi(...)` annotation. abi(...) replaces callconv(...): - New ABI enum { default, c, zig, pure }; `abi(.c|.zig|.pure)` parses in the postfix slot before extern/export (and standalone). `kw_callconv` -> `kw_abi`. - Migrated 52 sx files, the call-convention-mismatch diagnostic, and docs (readme/specs) from `callconv(.c)` to `abi(.c)`. Phase 1 — welded compiler library (parse -> registry -> validation -> bridge): - `abi(.zig) extern compiler` parses on fn decls (carries abi/extern_lib) and struct decls (StructDecl.abi/extern_lib). - `#library "compiler"` is the comptime-only internal surface — never dlopen'd. - src/ir/compiler_lib.zig: the binding registry (the safety boundary). `Field` welded to StructInfo.Field with layout baked from the real Zig type (@offsetOf/@sizeOf); `findType`/`findFn`. Welded structs are layout-validated at registration (field set + total size) as a header checked against the impl. - Host-call bridge: a `fn abi(.zig) extern compiler` dispatches under the comptime interp to its registered Zig handler (intern/text_of round-trip), never dlsym. IR Function.compiler_welded; validated in declareFunction. - Comptime-only enforcement: a runtime call to a welded fn is a clean build-gating error (emitCall), not an undefined-symbol link failure. Phase 2.1 — byte-layout weld foundation: - Decision: full byte-layout weld (sx struct laid out byte-identically to the bound Zig type). Registered StructInfo (first non-natural / Zig-reordered layout). `computeWeldPlan` — pure offset-ordered element plan + padding + sx-field->LLVM-element remap; unit-tested. Emit/interp wiring is the next sub-step (2.2+, see current/CHECKPOINT-COMPILER-API.md). Examples: 0625/0626 (welded struct + fn round-trip), 1183/1184/1185 (layout-mismatch, unexported-fn, runtime-call diagnostics). --- current/CHECKPOINT-COMPILER-API.md | 273 ++++++++++++++++ design/comptime-compiler-api.md | 72 +++-- examples/0117-types-block-string-arg.sx | 6 +- examples/0206-generics-generic-into-block.sx | 6 +- examples/0625-comptime-weld-struct-field.sx | 24 ++ .../0626-comptime-weld-fn-intern-text-of.sx | 24 ++ ...iagnostics-callconv-mismatch-diagnostic.sx | 4 +- ...183-diagnostics-weld-struct-field-count.sx | 17 + .../1184-diagnostics-weld-fn-unexported.sx | 11 + .../1185-diagnostics-weld-fn-runtime-call.sx | 17 + examples/1200-ffi-callconv-c-callbacks.sx | 6 +- examples/1201-ffi-callconv-c-globals.sx | 6 +- examples/1202-ffi-cc-c-large-aggregate.sx | 4 +- ...03-ffi-callconv-c-fnptr-large-aggregate.sx | 10 +- .../1204-ffi-fnptr-cast-large-aggregate.sx | 6 +- examples/1214-ffi-06-callback.sx | 8 +- examples/1300-ffi-objc-roundtrip.sx | 4 +- examples/1301-ffi-objc-class.sx | 4 +- examples/1302-ffi-objc-block-noop.sx | 2 +- examples/1303-ffi-objc-block-capture.sx | 2 +- examples/1304-ffi-objc-block-multi-arg.sx | 4 +- examples/1305-ffi-objc-block-inline.sx | 2 +- .../1313-ffi-objc-class-alloc-roundtrip.sx | 2 +- .../1314-ffi-objc-class-dealloc-roundtrip.sx | 4 +- .../1316-ffi-objc-class-method-static-imp.sx | 2 +- .../1317-ffi-objc-class-level-constant.sx | 4 +- .../1318-ffi-objc-property-extern-class.sx | 4 +- examples/1332-ffi-objc-call-06-sret-return.sx | 4 +- .../1333-ffi-objc-call-07-fp-hfa-return.sx | 2 +- .../1334-ffi-objc-call-08-multi-keyword.sx | 2 +- examples/1337-ffi-objc-call-11-bool-return.sx | 4 +- .../1338-ffi-objc-call-12-rect-u64-returns.sx | 4 +- ...1339-ffi-objc-defined-class-01-instance.sx | 2 +- ...i-objc-defined-class-02-struct-encoding.sx | 2 +- examples/1341-ffi-objc-dsl-01-niladic.sx | 2 +- examples/1342-ffi-objc-dsl-02-one-arg.sx | 2 +- .../1343-ffi-objc-dsl-03-multi-keyword.sx | 2 +- .../1347-ffi-objc-dsl-07-mangling-table.sx | 2 +- examples/1349-ffi-objc-export-class.sx | 2 +- examples/1607-platform-uikit-app.sx | 2 +- examples/1608-platform-uikit-window.sx | 14 +- examples/1616-platform-ios-device-bundle.sx | 2 +- examples/1635-cfnptr-qsort.sx | 6 +- examples/1636-cfnptr-pthread-reentry.sx | 6 +- examples/1637-std-thread.sx | 2 +- .../1655-platform-asm-callback-into-sx.sx | 2 +- .../0114-types-build-block-convert.stdout | 8 +- .../0625-comptime-weld-struct-field.exit | 1 + .../0625-comptime-weld-struct-field.stderr | 1 + .../0625-comptime-weld-struct-field.stdout | 1 + .../0626-comptime-weld-fn-intern-text-of.exit | 1 + ...626-comptime-weld-fn-intern-text-of.stderr | 1 + ...626-comptime-weld-fn-intern-text-of.stdout | 1 + ...ostics-callconv-mismatch-diagnostic.stderr | 8 +- ...3-diagnostics-weld-struct-field-count.exit | 1 + ...diagnostics-weld-struct-field-count.stderr | 5 + ...diagnostics-weld-struct-field-count.stdout | 1 + .../1184-diagnostics-weld-fn-unexported.exit | 1 + ...1184-diagnostics-weld-fn-unexported.stderr | 5 + ...1184-diagnostics-weld-fn-unexported.stdout | 1 + ...1185-diagnostics-weld-fn-runtime-call.exit | 1 + ...85-diagnostics-weld-fn-runtime-call.stderr | 1 + ...85-diagnostics-weld-fn-runtime-call.stdout | 1 + .../1200-ffi-callconv-c-callbacks.stdout | 2 +- .../1228-ffi-extern-c-non-transitive.stdout | 1 + .../1231-ffi-extern-undeclared-lib.stdout | 1 + library/modules/ffi/objc.sx | 4 +- library/modules/ffi/objc_block.sx | 6 +- library/modules/ffi/opengl.sx | 108 +++---- library/modules/gpu/metal.sx | 82 ++--- library/modules/platform/android.sx | 4 +- library/modules/platform/sdl3.sx | 4 +- library/modules/platform/uikit.sx | 4 +- library/modules/std/thread.sx | 10 +- readme.md | 2 +- specs.md | 5 +- src/ast.zig | 52 +++- src/backend/llvm/abi.zig | 4 +- src/backend/llvm/ops.zig | 17 + src/ir/compiler_lib.test.zig | 177 +++++++++++ src/ir/compiler_lib.zig | 291 ++++++++++++++++++ src/ir/emit_llvm.zig | 2 +- src/ir/inst.zig | 8 +- src/ir/interp.zig | 10 + src/ir/ir.zig | 2 + src/ir/lower/closure.zig | 4 +- src/ir/lower/decl.zig | 37 ++- src/ir/lower/expr.zig | 6 +- src/ir/lower/ffi.zig | 2 +- src/ir/lower/nominal.zig | 54 +++- src/ir/lower/objc_class.zig | 4 +- src/ir/module.zig | 2 +- src/ir/type_resolver.zig | 8 +- src/lexer.zig | 6 +- src/lsp/server.zig | 2 +- src/main.zig | 5 + src/parser.test.zig | 145 +++++++++ src/parser.zig | 68 ++-- src/sema.zig | 14 +- src/token.zig | 4 +- 100 files changed, 1490 insertions(+), 298 deletions(-) create mode 100644 current/CHECKPOINT-COMPILER-API.md create mode 100644 examples/0625-comptime-weld-struct-field.sx create mode 100644 examples/0626-comptime-weld-fn-intern-text-of.sx create mode 100644 examples/1183-diagnostics-weld-struct-field-count.sx create mode 100644 examples/1184-diagnostics-weld-fn-unexported.sx create mode 100644 examples/1185-diagnostics-weld-fn-runtime-call.sx create mode 100644 examples/expected/0625-comptime-weld-struct-field.exit create mode 100644 examples/expected/0625-comptime-weld-struct-field.stderr create mode 100644 examples/expected/0625-comptime-weld-struct-field.stdout create mode 100644 examples/expected/0626-comptime-weld-fn-intern-text-of.exit create mode 100644 examples/expected/0626-comptime-weld-fn-intern-text-of.stderr create mode 100644 examples/expected/0626-comptime-weld-fn-intern-text-of.stdout create mode 100644 examples/expected/1183-diagnostics-weld-struct-field-count.exit create mode 100644 examples/expected/1183-diagnostics-weld-struct-field-count.stderr create mode 100644 examples/expected/1183-diagnostics-weld-struct-field-count.stdout create mode 100644 examples/expected/1184-diagnostics-weld-fn-unexported.exit create mode 100644 examples/expected/1184-diagnostics-weld-fn-unexported.stderr create mode 100644 examples/expected/1184-diagnostics-weld-fn-unexported.stdout create mode 100644 examples/expected/1185-diagnostics-weld-fn-runtime-call.exit create mode 100644 examples/expected/1185-diagnostics-weld-fn-runtime-call.stderr create mode 100644 examples/expected/1185-diagnostics-weld-fn-runtime-call.stdout create mode 100644 src/ir/compiler_lib.test.zig create mode 100644 src/ir/compiler_lib.zig diff --git a/current/CHECKPOINT-COMPILER-API.md b/current/CHECKPOINT-COMPILER-API.md new file mode 100644 index 00000000..a2ec2542 --- /dev/null +++ b/current/CHECKPOINT-COMPILER-API.md @@ -0,0 +1,273 @@ +# CHECKPOINT-COMPILER-API — comptime `compiler` library (`#library "compiler"` + `abi(.zig) extern`) + +Companion to the design-of-record +[../design/comptime-compiler-api.md](../design/comptime-compiler-api.md) (the plan ++ phased build order live there). This stream supersedes the metatype +`declare`/`define`/`type_info` `#builtin`s and the `#compiler` struct attribute +with ONE welded mechanism. Branch: `reify` (off `master`). Update after every step. + +## Last completed step +**Phase 2, sub-step 1 — the weld-plan layout math + `StructInfo` registered.** +The de-risked core of the byte-layout-override ("GEP") engine, pure + unit-tested, +no emit/interp wiring yet (suite trivially green). + +Decision (locked 2026-06-17): **full byte-layout weld** — a welded sx struct is +laid out byte-identically to the bound Zig type (Zig's `@offsetOf`, reordering + +padding included), so it passes to a Zig handler as raw memory with zero +marshalling. (The alternative — handlers reading interp `Value` aggregates +logically, no layout override — was rejected; welded types must also be usable as +runtime data, and the design wants the literal byte weld.) + +- Measured: Zig reorders `StructInfo` to `fields`@0, `name`@16, `nominal_id`@20, + `is_protocol`@24, size 32 — vs sx-natural `name`@0, `fields`@8, … So the override + is genuinely required (`Field`'s two-u32 natural layout was the easy case). +- `compiler_lib.zig`: registered `StructInfo` (`weldStruct`, the second + `bound_types` entry). Added `WeldElement` / `WeldPlan` + `computeWeldPlan(alloc, + fields, total)` — pure: orders fields by ascending byte offset, inserts padding + elements for gaps + the alignment tail, and builds the sx-field → LLVM-element + remap. This is what the LLVM type builder + struct-GEP sites will consume. +- Unit-tested (`compiler_lib.test.zig`): `Field` → identity plan (2 elems, no pad); + `StructInfo` → 5 elems `[fields@0, name@16, nominal_id@20, is_protocol@24, + pad@25..32]`, remap `[1,0,3,2]`. +- `zig build` + `zig build test` green. + +### Earlier — Phase 1 polish (comptime-only enforcement) +**A RUNTIME call to a `fn abi(.zig) extern compiler` is a clean build-gating error +instead of an undefined-symbol link failure.** +- `emitCall` (`src/backend/llvm/ops.zig`): when the callee is `compiler_welded` + AND the ENCLOSING function is not `is_comptime` (i.e. genuine runtime code, not a + `#run`/`::` initializer wrapper whose LLVM body is dead), print a clear + "comptime-only … cannot be called at runtime" error and set + `comptime_failed` (the driver halts before object/JIT emission). The enclosing + `is_comptime` guard is what keeps the legitimate `#run` use (example 0626) green. +- Corpus: `examples/1185-diagnostics-weld-fn-runtime-call.sx` (runtime `intern(…)` + → clean error, exit 1, no link failure). +- `zig build` + `zig build test` green (458 unit + 690 corpus). + +### Earlier — fifth sub-step (host-call bridge) +**A `fn abi(.zig) extern compiler` dispatches, under the comptime interpreter, to +its registered Zig handler instead of dlsym.** +- `compiler_lib.zig`: function registry — `BoundFn { sx_name, handler }`, + `bound_fns` = `intern(string)->StringId` + `text_of(StringId)->string` (the + string-pool round-trip), `findFn`, and `FnHandler` (`*Interpreter, []Value -> + Value`). `intern` mutates via `interp.mint orelse @constCast(&module.types)` + (the same mutable-table access the metatype mint path uses); `text_of` reads the + const pool. Imports `interp.zig` (the compiler_hooks↔interp cycle pattern). +- IR `Function` gained `compiler_welded: bool`. `declareFunction` + (`src/ir/lower/decl.zig`) sets it via `weldedCompilerFn`, which also VALIDATES: + the bound lib must be `compiler` and the name must be on the function-export + list — else a build-gating `.err` (no silent fall-through to dlsym). +- `interp.call()`: before the dlsym/extern path, a `compiler_welded` function + routes to `compiler_lib.findFn(name).handler(self, args)` (clean bail off the + export list). +- Corpus: `examples/0626-comptime-weld-fn-intern-text-of.sx` (`#run + text_of(intern("hello, compiler"))` folds to a string constant → prints it); + `examples/1184-diagnostics-weld-fn-unexported.sx` (unexported welded-fn name → + build error). `findFn` lookup unit-tested. +- **Runtime-call rejection is NOT yet clean** — welded fns are comptime-only; a + RUNTIME call would emit a reference to a non-existent extern symbol → a loud + LINK error (not silent, but not a tidy diagnostic). The examples call welded fns + only inside `#run`. A dedicated "comptime-only symbol" emit diagnostic is the + immediate follow-up. +- `zig build` + `zig build test` green (458 unit tests + 689 corpus). + +### Earlier — fourth sub-step (welded-struct layout validation) +**A `struct abi(.zig) extern compiler { … }` is validated against the binding +registry as a *header checked against the implementation*.** +- `compiler_lib.zig`: `validateStructLayout(bt, sx_fields, total)` — pure, returns + the first `LayoutMismatch` (field count / name / size / total) or null. Plus + `lib_name = "compiler"` and `SxField`. Unit-tested (faithful `Field` passes; + each drift flagged as the right variant). +- `registerStructDecl` (`src/ir/lower/nominal.zig`): for `sd.abi == .zig`, + `validateWeldedStruct` checks the bound lib is `compiler`, the name is on the + export list (`findType`), and the sx layout (field names + `typeSizeBytes` + + total) matches the welded type — emitting a build-gating `.err` (good span into + the struct body) on any failure. No silent reinterpretation. +- `#library "compiler"` is the comptime-only internal surface, NOT a dylib — + `src/main.zig`'s dlopen walker skips it (was emitting a spurious `libcompiler.so` + load warning). +- Corpus: `examples/0625-comptime-weld-struct-field.sx` (faithful `Field` welds, + validates, usable as data → `name=7 ty=3`); `examples/1183-diagnostics-weld- + struct-field-count.sx` (one-field `Field` → build-gating field-count diagnostic). +- **Offset-override / GEP emission for non-natural Zig layouts is NOT here** — it + isn't exercised by `Field` (two u32s = natural layout coincides with the weld). + It arrives with `StructInfo` in Phase 2 (slices/reordering), where the bound + offsets actually differ from the sx-natural ones. The validation already checks + per-field size + total, so a layout drift is caught even before the override + engine exists. +- `zig build` + `zig build test` green (456 unit tests + 687 corpus). + +### Earlier — third sub-step (binding registry) +**The binding registry (welded-type lookup, layout baked from the real Zig +type).** +- New `src/ir/compiler_lib.zig` — the `compiler` library's binding registry, the + curated safety boundary. `BoundType { sx_name, size, alignment, fields: + []FieldLayout{name, offset, size} }`; `weldStruct` bakes the layout from a real + Zig struct via `@sizeOf`/`@alignOf`/`@offsetOf` at compiler-build time (a + sx-field-count mismatch is a `@compileError`, never a silent truncation). + `bound_types` exports `Field` (welded to `types.TypeInfo.StructInfo.Field` — + two `u32`s); `findType(sx_name) ?*const BoundType` is the lookup the welded-decl + resolution path will consult (returns null off the export list — clean boundary, + no silent default). +- Registered in the barrel (`src/ir/ir.zig`): `compiler_lib` + `compiler_lib_tests`. +- Tests (`src/ir/compiler_lib.test.zig`): `findType("Field")` equals the real + `StructInfo.Field` `@sizeOf`/`@alignOf`/`@offsetOf` (8 bytes, two u32s at 0/4); + an unexported name returns null. Break-verified (a wrong size → suite red, + named `ir.compiler_lib.test...`). +- `zig build` + `zig build test` green (454 unit tests). + +### Earlier — second sub-step (struct-decl parse) +**`abi(.zig) extern ` PARSES on a STRUCT decl (parse-only, no semantics).** +- `ast.StructDecl` gained `abi: ABI` + `extern_lib: ?[]const u8` binding fields. +- `parseStructDecl` (`src/parser.zig`): after `struct` (and the `#compiler` + check), parse an optional `abi(...)` then optional `extern ` — same slot + order as fn decls — and thread them onto the node. Ordinary structs are + unperturbed (`parseOptionalAbi`/`parseOptionalExternExport` no-op when absent). +- Parser unit tests (`src/parser.test.zig`): `Field :: struct abi(.zig) extern + compiler { name: StringId; ty: Type; }` parses with `abi == .zig`, `extern_lib + == "compiler"`, field list intact; a plain struct leaves `abi == .default` / + `extern_lib == null`. Break-verified (a wrong-sentinel assert turns the suite + red, confirming the test runs). +- `zig build` + `zig build test` green. + +### Earlier — first sub-step (fn decls) + the syntax pivot +**`abi(.zig) extern ` PARSES on a fn decl (parse-only).** Plus the syntax +pivot it required. + +Syntax decision (locked 2026-06-17, supersedes the doc's original +`extern(.zig) ` single-qualifier form): the ABI/layout selector and the +linkage keyword are two orthogonal annotations. +- `abi(.x)` — ABI / calling-convention annotation in the slot **before** + `extern`/`export`. **Unified replacement for `callconv(...)`, which is removed.** + `ABI = { default, c, zig, pure }`: `.c` (C ABI), `.zig` (Zig-layout weld → the + `compiler` library), `.pure` (naked asm), `.default` (unannotated). Can appear + standalone (no extern) on any fn / fn-type / lambda. +- `extern ` — linkage keyword + binding source (named library). + +So a welded binding is `text_of :: (id: StringId) -> string abi(.zig) extern compiler;`. + +What landed: +- **AST** (`src/ast.zig`): `CallingConvention` → `ABI { default, c, zig, pure }`; + the `call_conv` field → `abi: ABI` on `FnDecl` / `Lambda` / `FunctionTypeExpr`. +- **Lexer/token** (`src/token.zig`, `src/lexer.zig`): `kw_callconv` → `kw_abi`, + keyword string `"callconv"` → `"abi"`. +- **Parser** (`src/parser.zig`): `parseOptionalCallConv` → `parseOptionalAbi` + (parses `abi(.c|.zig|.pure)`); wired in the fn-decl postfix slot (before + `extern`/`export`), the function-type-expr slot, and the lambda slot; + `isFunctionDef`/`hasFnBodyAfterArrow` recognise `kw_abi`. +- **AST→IR map** (`src/ir/type_resolver.zig`, `src/ir/lower/decl.zig`, `sema.zig`, + `closure.zig`): the AST `.abi == .c` reads kept their C-ABI meaning; the + function-type resolver maps `.zig`/`.pure` → IR `.default` (no fn-pointer-type + CC for those decl-level ABIs; neither occurs in a function-TYPE position yet). +- **CC-mismatch diagnostic** (`src/ir/lower/expr.zig`, `src/sema.zig`): the + user-facing text `callconv(.c)` → `abi(.c)`. +- **sx migration**: 52 `.sx` files `callconv(` → `abi(` (all were function-type + callback annotations — none in the fn-decl postfix slot, so no reordering). +- **Docs**: `readme.md`, `specs.md`, the design doc, snapshots (0114 / 1104 / + 1200) regenerated for the rename. +- **Tests**: parser unit tests in `src/parser.test.zig` — `abi(.zig) extern ` + on a fn decl (asserts `abi == .zig`, `extern_export == .extern_`, `extern_lib == + "compiler"`); bare `extern` leaves `abi == .default`; standalone `abi(.c)` / + `abi(.pure)`. lexer/sema tests updated. + +`zig build` + `zig build test` green (450/450 unit + 685 corpus). + +## Current state +- `compiler :: #library "compiler";` parses + is recognised as the comptime-only + internal surface (never dlopen'd). +- `abi(.zig) extern compiler` STRUCTS: layout-validated against the registry + (faithful → ok; drift → build-gating diagnostic). `Field` welds + usable. +- `abi(.zig) extern compiler` FUNCTIONS: dispatched under the comptime interp to + their registered Zig handler (`intern`/`text_of` round-trip works); unexported + names rejected at declaration. Comptime-only. +- A RUNTIME call to a welded fn is a clean build-gating error (comptime-only + enforcement at `emitCall`); the legitimate `#run`/`::` use stays green. +- The whole Phase 1 foundation (parse → registry → struct-layout validation → + function host-call bridge → comptime-only enforcement) is in place for the + two-u32 `Field` case + the two string readers. +- **Deferred**: offset-override / LLVM byte-offset GEP for non-natural layouts + (needed by `StructInfo`'s slice field, Phase 2). + +## Next step — Phase 2 decomposition (byte-layout weld for `StructInfo`) + +The weld plan (sub-step 1) is the pure layout math. The remaining sub-steps wire +it through emit + interp so a non-natural welded struct actually works. Each must +stay green; do ONE per session (the IR-stream split rule). + +- **2.2 — LLVM type honours the plan.** In `src/backend/llvm/types.zig` `.@"struct"` + case: if the struct's name is in `compiler_lib.findType`, build the LLVM struct + from `computeWeldPlan` — elements in offset order (real field types + `[N x i8]` + padding), and **assert** `LLVMOffsetOfElement(elem) == plan.elements[e].offset` + for every field element + `LLVMABISizeOfType == total_size` (the build-time + layout-equality assertion; mismatch = a loud emit failure). Cache the plan per + TypeId (the GEP sites + interp need the remap). Prove: a welded struct's LLVM + type has the Zig offsets (an emit-level test or an `.ir`/codegen check). +- **2.3 — field access honours the remap.** Every `struct_gep` / field load+store + for a welded struct maps the sx field index → `plan.sx_to_llvm[i]` before + `LLVMBuildStructGEP2` (`src/backend/llvm/ops.zig` — `emitFieldAccess` / + struct-literal init / the `field_ptr` paths). Prove with a REORDERED welded + struct used as runtime data: construct + read each field back correct. +- **2.4 — interp comptime layout.** The comptime interp represents structs as + `Value.aggregate` by logical index — fine for field access. The byte layout + matters at the handler boundary: serialize a welded-struct `Value` into + Zig-layout memory (via the plan's offsets) so a handler can take `*ZigType`, + and read a Zig-layout result back into a `Value`. (Or: keep handlers reading + `Value` aggregates logically — decide when wiring `register_struct`.) +- **2.5 — `register_struct` / `find_type` handlers.** Bind + `register_struct(StructInfo) -> Type` (guarded: dup field names, kind) + + `find_type(StringId) -> ?Type` over the host-call bridge, consuming a welded + `StructInfo`. Prove: build a struct programmatically + round-trip a source one. +- **2.6 — re-express `type_info`/`define` (struct) as sx** over `register_struct`/ + `find_type`; migrate `examples/0622`; delete the bespoke struct interp arms + (`defineStruct`/`reflectTypeInfo` struct path). Design build-order steps 2–3. + +Then Phase 3+: widen to enum/tuple (`EnumInfo`/`TaggedUnionInfo`/`TupleInfo`, +optional fields → sentinels), migrate `BuildOptions` to `abi(.zig) extern +compiler` (the `#compiler` registry re-homes under the `compiler` lib), delete +`#compiler`. + +## Known issues +- None for this stream. (Metatype's deferred enhancement is issue 0141 — comptime + `List` growth; orthogonal, see `current/CHECKPOINT-METATYPE.md`.) + +## Log +- **Phase 2.1 — weld-plan layout math + `StructInfo` registered.** Decision: + full byte-layout weld (not logical-field marshalling). `computeWeldPlan` + (offset-order elements + padding + sx→element remap), pure + unit-tested + against `Field` (identity) and `StructInfo` (reordered, remap `[1,0,3,2]`). + No emit/interp wiring yet. Build + suite green. +- **Phase 1 polish — comptime-only enforcement.** A runtime call to a welded fn is + a clean build-gating error (`emitCall` gate, guarded by enclosing-`is_comptime` + so `#run`/`::` uses stay green), not a link failure. Example 1185. Build + suite + green (458 unit, 690 corpus). +- **Phase 1.1 fifth sub-step — host-call bridge (welded functions).** + `compiler_lib` function registry (`intern`/`text_of`) + `findFn`; IR `Function` + `compiler_welded` flag set/validated in `declareFunction` (`weldedCompilerFn`); + `interp.call()` dispatches welded calls to the Zig handler. Examples 0626 (round- + trip) + 1184 (unexported-fn diagnostic); `findFn` unit-tested. Runtime-call clean + rejection deferred (loud link error today). Build + suite green (458 unit, 689 + corpus). +- **Phase 1.1 fourth sub-step — welded-struct layout validation.** + `validateStructLayout` (pure, unit-tested) + `validateWeldedStruct` wired into + `registerStructDecl`: a `struct abi(.zig) extern compiler` is validated against + the registry (lib == compiler, name exported, layout matches) with build-gating + diagnostics. `#library "compiler"` no longer dlopen'd. Examples 0625 (faithful + Field) + 1183 (field-count mismatch diagnostic). Offset-override/GEP deferred to + Phase 2 (not exercised by Field's natural layout). Build + suite green (456 unit, + 687 corpus). +- **Phase 1.1 third sub-step — binding registry.** New `src/ir/compiler_lib.zig`: + the `compiler` lib's welded-type registry; `Field` welded to + `StructInfo.Field` with layout baked from the real Zig type + (`@offsetOf`/`@sizeOf`/`@alignOf`); `findType` lookup proven by unit test + (+ null off the export list). Standalone island — not yet consumed by lowering. + Build + suite green (454 unit tests). Break-verified. +- **Phase 1.1 second sub-step — struct-decl binding parses.** `ast.StructDecl` + gained `abi` + `extern_lib`; `parseStructDecl` parses `abi(.zig) extern ` + after `struct`. Parser unit tests (welded `Field` + plain struct), break-verified. + Build + suite green. Parse-only sub-step (fns + structs) of Phase 1.1 complete. +- **Phase 1.1 first sub-step + `callconv`→`abi` unification.** Parsed `abi(.zig) + extern ` on fn decls; unified `callconv` into `abi(.c|.zig|.pure)` (removed + the `callconv` keyword), migrated 52 sx files + compiler diagnostics + docs + + snapshots. Build + suite green. The original design's `extern(.zig)` single + qualifier was split into `abi(.zig)` (ABI/layout, before extern) + `extern + ` (linkage + source) — recorded in the design doc's syntax-decision note. diff --git a/design/comptime-compiler-api.md b/design/comptime-compiler-api.md index 7c56d11c..695edad5 100644 --- a/design/comptime-compiler-api.md +++ b/design/comptime-compiler-api.md @@ -1,4 +1,4 @@ -# Comptime Compiler API — `#library "compiler"` + `extern(.zig)` +# Comptime Compiler API — `#library "compiler"` + `abi(.zig) extern` > **Status: design-of-record (not yet an active stream).** Captures a unified > mechanism for sx↔compiler binding that subsumes the metatype `declare`/`define` @@ -49,32 +49,44 @@ internal surface (Zig types + functions). Two defining properties: `is_comptime` boundary. (Welded *types* are still usable as plain runtime data; only the *functions* are comptime-gated.) -### `extern(.zig) ` — postfix attribute +### `abi(.zig)` + `extern ` — the binding surface -Slots where `#builtin` / `#compiler` go (postfix, after the return type for fns, -after `struct` for types), with the library handle following: +> **Syntax decision (2026-06-17, supersedes the original `extern(.zig) ` +> single-qualifier form).** The ABI/layout selector and the linkage keyword are +> two orthogonal things, so they are two annotations, not one fused qualifier: +> - `abi(.x)` — the ABI / calling-convention annotation, in the postfix slot +> **before** `extern`/`export`. It is the unified replacement for the old +> `callconv(...)` (which is removed): `ABI = { default, c, zig, pure }` — +> `.c` (C ABI / cdecl), `.zig` (Zig-layout weld → the `compiler` library), +> `.pure` (naked asm). `.default` = unannotated (ordinary sx convention). +> - `extern ` — the linkage keyword + binding source (the named library). + +`abi(...)` sits where `callconv(...)` went (after the return type for fns); the +`extern`/`export` keyword and the library handle follow. For welded types, the +same `abi(.zig)` + `extern ` pair sits after `struct`: ```sx // functions: -text_of :: (id: StringId) -> string extern(.zig) compiler; -intern :: (s: string) -> StringId extern(.zig) compiler; -register_type :: (info: StructInfo) -> Type extern(.zig) compiler; -find_type :: (name: StringId) -> ?Type extern(.zig) compiler; +text_of :: (id: StringId) -> string abi(.zig) extern compiler; +intern :: (s: string) -> StringId abi(.zig) extern compiler; +register_type :: (info: StructInfo) -> Type abi(.zig) extern compiler; +find_type :: (name: StringId) -> ?Type abi(.zig) extern compiler; // types (layout-welded to the lib's real Zig type): -Field :: struct extern(.zig) compiler { name: StringId; ty: Type; }; -StructInfo :: struct extern(.zig) compiler { +Field :: struct abi(.zig) extern compiler { name: StringId; ty: Type; }; +StructInfo :: struct abi(.zig) extern compiler { name: StringId; fields: []Field; is_protocol: bool; nominal_id: u32; }; ``` -`extern(.zig)` = "Zig ABI / Zig layout"; `` = the binding source. +`abi(.zig)` = "Zig ABI / Zig layout"; `extern compiler` = the linkage + binding +source. ### Layout welding — why it's exact, not brittle The sx compiler is itself a Zig program; `types.zig` is part of it. So at **compiler-build time** the real record's layout is available via -`@offsetOf` / `@sizeOf` / `@alignOf`. An `extern(.zig) compiler` struct is laid out +`@offsetOf` / `@sizeOf` / `@alignOf`. An `abi(.zig) extern compiler` struct is laid out to the bound Zig type's EXACT offsets (queried, not guessed), and the compiler ASSERTS the sx declaration matches the Zig type byte-for-byte (a mismatch is a build error — the sx side is a header checked against the implementation). Because @@ -134,8 +146,8 @@ instead of the user threading `declare`→forward-slot→`define`→eval-timing ## BuildOptions migration `BuildOptions :: struct #compiler { ... }` + `build_options() #compiler` → -`extern(.zig) compiler`: the setter/getter hook-methods become `extern(.zig) -compiler` functions (or methods on a welded/handle `BuildOptions`), backed by the +`abi(.zig) extern compiler`: the setter/getter hook-methods become `abi(.zig) +extern compiler` functions (or methods on a welded/handle `BuildOptions`), backed by the same `BuildConfig` state. The `compiler_hooks.zig` registry becomes the `compiler` lib's function/type registry. Net: the build DSL and the metatype API ride one mechanism. @@ -157,12 +169,17 @@ Foundation that ALREADY exists: - `extern` / `export` are keywords (`src/token.zig:46`, `kw_extern`/`kw_export`). New work for Phase 1: -- **Lexer/parser**: the `(.zig)` ABI qualifier on `extern`, and the trailing - `` identifier — `… extern(.zig) ` postfix on FN decls (after the - return type, beside `#builtin`/`#compiler` at `src/parser.zig:233`/`:315`) and - STRUCT decls (beside `struct #compiler`, `src/parser.zig:953`). -- **AST**: an abi/layout-binding field on `FnDecl` and the struct decl (`abi: - .c | .zig`, `lib: ?name`). +- **Lexer/parser**: the `abi(.zig)` annotation (a new `abi` keyword replacing + `callconv`; `ABI = { default, c, zig, pure }`) in the slot before `extern`, + followed by the `` handle — `… abi(.zig) extern ` postfix on FN decls + (after the return type, before `extern`) and STRUCT decls (beside + `struct #compiler`). **DONE (parse-only)** — `parseOptionalAbi` + (`src/parser.zig`) wired on fn decls AND struct decls, `ast.ABI`, parser unit + tests; the `callconv`→`abi` rename migrated 52 sx files + the compiler's + CC-mismatch diagnostic. +- **AST**: the `abi: ABI` field lives on `FnDecl` / `Lambda` / `FunctionTypeExpr` + (carries `.zig` for a welded fn); `StructDecl` gained `abi: ABI` + + `extern_lib: ?[]const u8`. **DONE.** - **Binding registry**: re-home / generalize `src/ir/compiler_hooks.zig` (today's `#compiler` registry) into the `compiler` lib's type+function registry, keyed by exported sx name → Zig type (`@offsetOf` layout) / Zig fn (host-call). @@ -174,12 +191,13 @@ New work for Phase 1: ## Build order (each phase keeps `zig build test` green) -1. **`extern(.zig)` + `#library` foundation** — parse the postfix attribute (the - `#library` decl already exists); a binding registry (sx name → Zig type/fn); - the layout engine honoring the bound type's `@offsetOf` offsets + LLVM emission - that hits them; **build-time layout-equality assertion**. Prove with `Field` - (two u32s). First testable sub-step: `extern(.zig) ` PARSES on a fn decl - (parser unit test), AST carries the binding — no semantics yet. +1. **`abi(.zig) extern ` + `#library` foundation** — parse the postfix + annotation (the `#library` decl already exists); a binding registry (sx name → + Zig type/fn); the layout engine honoring the bound type's `@offsetOf` offsets + + LLVM emission that hits them; **build-time layout-equality assertion**. Prove + with `Field` (two u32s). First testable sub-step **DONE**: `abi(.zig) extern + ` PARSES on a fn decl (parser unit test), AST carries the binding (`abi == + .zig`, `extern_lib`) — no semantics yet. 2. **Weld `StructInfo`** + `StringId` accessors (`intern`/`text_of`) over the host-call bridge. 3. **Re-express `type_info`/`define` (struct)** as sx over `register_struct`/ @@ -187,7 +205,7 @@ New work for Phase 1: 4. **Widen to enum/tuple** — weld `EnumInfo`/`TaggedUnionInfo`/`TupleInfo` (optional fields → sentinels: `backing_type` `.unresolved`, `explicit_values` len-0); migrate `examples/0619`/`0623`; delete the enum/tuple interp arms. -5. **Migrate `BuildOptions`** to `extern(.zig) compiler`. +5. **Migrate `BuildOptions`** to `abi(.zig) extern compiler`. 6. **Delete `#compiler`**; suite green. ## Risks / open questions diff --git a/examples/0117-types-block-string-arg.sx b/examples/0117-types-block-string-arg.sx index fe8676df..694ff96c 100644 --- a/examples/0117-types-block-string-arg.sx +++ b/examples/0117-types-block-string-arg.sx @@ -1,6 +1,6 @@ // Generic `Into(Block)` impl with a `string`-typed arg in the // closure signature. The block trampoline declares the param with -// callconv(.c); without the abi-collapse fix, sx `string` got +// abi(.c); without the abi-collapse fix, sx `string` got // silently collapsed to `ptr` (the libc `char *` heuristic) and // the caller's 16-byte `{ptr, len}` value mismatched the // trampoline's 8-byte `ptr` slot. Result: segfault inside the @@ -8,7 +8,7 @@ // // The fix lives in `abiCoerceParamTypeEx`: the `string`/`slice` → // `ptr` collapse only applies to `is_extern` extern decls (libc -// interop). sx-internal `callconv(.c)` keeps the full slice +// interop). sx-internal `abi(.c)` keeps the full slice // shape, which lands as `[2 x i64]` at the LLVM signature site // and matches the caller's two-register pass on AArch64. @@ -20,7 +20,7 @@ g_s: string = ""; main :: () -> i32 { cl := (s: string) => { g_s = s; }; b : Block = xx cl; - invoke_fn : (*Block, string) -> void callconv(.c) = xx b.invoke; + invoke_fn : (*Block, string) -> void abi(.c) = xx b.invoke; invoke_fn(@b, "hello"); if g_s.len == 0 { print("FAIL: empty\n"); return 1; } print("got: <{}>\n", g_s); diff --git a/examples/0206-generics-generic-into-block.sx b/examples/0206-generics-generic-into-block.sx index 5288996e..e7a82dbb 100644 --- a/examples/0206-generics-generic-into-block.sx +++ b/examples/0206-generics-generic-into-block.sx @@ -1,7 +1,7 @@ // FFI plan step 5.2 — generic `Into(Block) for Closure(..$args) -> // $R` impl. One impl in stdlib covers every closure shape; the // compiler monomorphises the impl body per call shape and emits a -// dedicated `__invoke` `callconv(.c)` trampoline + Block literal +// dedicated `__invoke` `abi(.c)` trampoline + Block literal // (via `#insert build_block_convert($args, $R);`). // // This test exercises a closure shape (`Closure(i64, i64) -> void`) @@ -13,7 +13,7 @@ // the per-shape trampoline ferries control back to the sx closure. // // The block is invoked directly through `b.invoke` (a typed -// `callconv(.c)` fn-pointer) — the same shape the Apple Block +// `abi(.c)` fn-pointer) — the same shape the Apple Block // runtime calls when a UIKit/Foundation API hands the block back // to its registered invoke. @@ -27,7 +27,7 @@ main :: () -> i32 { cl := (a: i64, b: i64) => { g_a = a; g_b = b; }; blk : Block = xx cl; - invoke_fn : (*Block, i64, i64) -> void callconv(.c) = xx blk.invoke; + invoke_fn : (*Block, i64, i64) -> void abi(.c) = xx blk.invoke; invoke_fn(@blk, 10, 20); if g_a != 10 { print("FAIL: g_a={}\n", g_a); return 1; } diff --git a/examples/0625-comptime-weld-struct-field.sx b/examples/0625-comptime-weld-struct-field.sx new file mode 100644 index 00000000..672c8bbb --- /dev/null +++ b/examples/0625-comptime-weld-struct-field.sx @@ -0,0 +1,24 @@ +// Comptime compiler API — a layout-welded struct binding. +// +// `Field :: struct abi(.zig) extern compiler { … }` binds the sx struct to the +// compiler's real internal Zig type (`StructInfo.Field`, two u32s) via the +// `compiler` library. The compiler validates the sx declaration against the +// welded type's layout at registration time (the sx side is a header checked +// against the implementation) — a faithful declaration validates clean and the +// struct is otherwise ordinary data. The `compiler` library is the comptime-only +// internal surface, so `#library "compiler"` is NOT dlopen'd. +// +// Phase 1 (foundation): the weld is layout-validated; field offsets coincide with +// the natural layout for `Field` (two u32s). Welded host-call functions land in a +// later phase. + +#import "modules/std.sx"; + +compiler :: #library "compiler"; + +Field :: struct abi(.zig) extern compiler { name: u32; ty: u32; } + +main :: () { + f := Field.{ name = 7, ty = 3 }; + print("name={} ty={}\n", f.name, f.ty); +} diff --git a/examples/0626-comptime-weld-fn-intern-text-of.sx b/examples/0626-comptime-weld-fn-intern-text-of.sx new file mode 100644 index 00000000..aec4b9f5 --- /dev/null +++ b/examples/0626-comptime-weld-fn-intern-text-of.sx @@ -0,0 +1,24 @@ +// Comptime compiler API — welded compiler FUNCTIONS over the host-call bridge. +// +// `intern` / `text_of` are bound to the `compiler` library via +// `abi(.zig) extern compiler`. They have no real symbol — under the comptime +// interpreter the call dispatches to the compiler's registered Zig handler +// (the string pool), never dlsym. Comptime-only: here they run inside `#run`, +// folding to a plain string constant the runtime `main` prints. +// +// Round-trip: `text_of(intern(s)) == s` — interning a string yields a handle, +// and resolving the handle gives the text back. + +#import "modules/std.sx"; + +compiler :: #library "compiler"; + +StringId :: u32; +intern :: (s: string) -> StringId abi(.zig) extern compiler; +text_of :: (id: StringId) -> string abi(.zig) extern compiler; + +greeting :: #run text_of(intern("hello, compiler")); + +main :: () { + print("{}\n", greeting); +} diff --git a/examples/1104-diagnostics-callconv-mismatch-diagnostic.sx b/examples/1104-diagnostics-callconv-mismatch-diagnostic.sx index 9b2eaa4e..bcded9e8 100644 --- a/examples/1104-diagnostics-callconv-mismatch-diagnostic.sx +++ b/examples/1104-diagnostics-callconv-mismatch-diagnostic.sx @@ -1,4 +1,4 @@ -// Passing a default-conv sx function as a callconv(.c) fn-pointer +// Passing a default-conv sx function as a abi(.c) fn-pointer // silently mismatches ABIs — historically that meant the C-side caller // supplied no `__sx_ctx` slot 0 and the sx-side body read garbage. // The compiler now rejects the coercion outright with a "call-convention @@ -9,6 +9,6 @@ sx_handler :: (arg: *void) -> *void { return arg; } main :: () -> i32 { - fp : (*void) -> *void callconv(.c) = sx_handler; + fp : (*void) -> *void abi(.c) = sx_handler; return 0; } diff --git a/examples/1183-diagnostics-weld-struct-field-count.sx b/examples/1183-diagnostics-weld-struct-field-count.sx new file mode 100644 index 00000000..bff0c34c --- /dev/null +++ b/examples/1183-diagnostics-weld-struct-field-count.sx @@ -0,0 +1,17 @@ +// Diagnostic: a layout-welded struct whose sx declaration does NOT faithfully +// mirror the compiler's real Zig type is a build error — the sx side is a header +// checked against the implementation, not a free reinterpretation. +// +// `Field` is two u32s (`name`, `ty`) in the compiler library; declaring it with a +// single field must be rejected at registration with a clear field-count message. + +#import "modules/std.sx"; + +compiler :: #library "compiler"; + +Field :: struct abi(.zig) extern compiler { name: u32; } + +main :: () { + f := Field.{ name = 1 }; + print("{}\n", f.name); +} diff --git a/examples/1184-diagnostics-weld-fn-unexported.sx b/examples/1184-diagnostics-weld-fn-unexported.sx new file mode 100644 index 00000000..71a32770 --- /dev/null +++ b/examples/1184-diagnostics-weld-fn-unexported.sx @@ -0,0 +1,11 @@ +// Diagnostic: a `fn abi(.zig) extern compiler` whose name is NOT on the compiler +// library's function-export list is a build error — the export list is the +// safety boundary, so an unbound name can't silently fall through to dlsym. + +#import "modules/std.sx"; + +compiler :: #library "compiler"; + +not_a_real_compiler_fn :: (x: i64) -> i64 abi(.zig) extern compiler; + +main :: () { print("unreached\n"); } diff --git a/examples/1185-diagnostics-weld-fn-runtime-call.sx b/examples/1185-diagnostics-weld-fn-runtime-call.sx new file mode 100644 index 00000000..347e5cdd --- /dev/null +++ b/examples/1185-diagnostics-weld-fn-runtime-call.sx @@ -0,0 +1,17 @@ +// Diagnostic: a welded `compiler`-library function is comptime-only — it has no +// runtime symbol (the comptime interpreter dispatches it to a Zig handler). +// Calling one from runtime code is a build error with a clear message, NOT an +// undefined-symbol link failure. (A comptime use — inside `#run` or a `::` — +// is fine; see examples/0626.) + +#import "modules/std.sx"; + +compiler :: #library "compiler"; + +StringId :: u32; +intern :: (s: string) -> StringId abi(.zig) extern compiler; + +main :: () { + id := intern("called at runtime"); + print("{}\n", id); +} diff --git a/examples/1200-ffi-callconv-c-callbacks.sx b/examples/1200-ffi-callconv-c-callbacks.sx index 7976e084..3e3957b6 100644 --- a/examples/1200-ffi-callconv-c-callbacks.sx +++ b/examples/1200-ffi-callconv-c-callbacks.sx @@ -1,16 +1,16 @@ -// `callconv(.c)` on function pointers passed to extern callbacks — ensures +// `abi(.c)` on function pointers passed to extern callbacks — ensures // the function uses C ABI so it can be safely invoked from `extern` // functions like SDL_AddEventWatch. #import "modules/std.sx"; // A function with C calling convention -add_c :: (a: i64, b: i64) -> i64 callconv(.c) { +add_c :: (a: i64, b: i64) -> i64 abi(.c) { a + b } main :: () { // Call it directly — should work like any other function result := add_c(10, 32); - print("callconv(.c): {}\n", result); + print("abi(.c): {}\n", result); } diff --git a/examples/1201-ffi-callconv-c-globals.sx b/examples/1201-ffi-callconv-c-globals.sx index f143e528..d140242d 100644 --- a/examples/1201-ffi-callconv-c-globals.sx +++ b/examples/1201-ffi-callconv-c-globals.sx @@ -1,4 +1,4 @@ -// `callconv(.c)` callbacks accessing struct methods on global pointers — +// `abi(.c)` callbacks accessing struct methods on global pointers — // regression coverage for prior data-corruption when the callback dispatches // through a global pointer to a method on the pointed-to struct. @@ -29,7 +29,7 @@ do_render :: () { print("wrapper: pw={}, ph={}, frame={}\n", g_pipe.pw, g_pipe.ph, g_pipe.frame); } -callback_inline :: (userdata: *void, code: i64) -> bool callconv(.c) { +callback_inline :: (userdata: *void, code: i64) -> bool abi(.c) { g_width = xx code; g_height = xx (code + 1); g_pipe.resize(xx g_width, xx g_height); @@ -38,7 +38,7 @@ callback_inline :: (userdata: *void, code: i64) -> bool callconv(.c) { true } -callback_wrapper :: (userdata: *void, code: i64) -> bool callconv(.c) { +callback_wrapper :: (userdata: *void, code: i64) -> bool abi(.c) { g_width = xx code; g_height = xx (code + 1); do_render(); diff --git a/examples/1202-ffi-cc-c-large-aggregate.sx b/examples/1202-ffi-cc-c-large-aggregate.sx index 11987863..3d0be910 100644 --- a/examples/1202-ffi-cc-c-large-aggregate.sx +++ b/examples/1202-ffi-cc-c-large-aggregate.sx @@ -1,6 +1,6 @@ // Regression test for issue-0025 path A. // -// sx functions declared with `callconv(.c)` that take a composite > 16 bytes +// sx functions declared with `abi(.c)` that take a composite > 16 bytes // by value must marshal the arg through `ptr byval()` per AAPCS64 / SysV // AArch64: the caller copies the struct to an alloca, passes the alloca // pointer with a `byval()` attribute, and the callee's entry block loads @@ -20,7 +20,7 @@ Wide :: struct { d: i64; } -accept_c :: (w: Wide) -> i64 callconv(.c) { +accept_c :: (w: Wide) -> i64 abi(.c) { w.a + w.b + w.c + w.d } diff --git a/examples/1203-ffi-callconv-c-fnptr-large-aggregate.sx b/examples/1203-ffi-callconv-c-fnptr-large-aggregate.sx index 3c07cdda..aff171a7 100644 --- a/examples/1203-ffi-callconv-c-fnptr-large-aggregate.sx +++ b/examples/1203-ffi-callconv-c-fnptr-large-aggregate.sx @@ -1,6 +1,6 @@ // Regression test for issue-0025 path B. // -// When a fn-pointer's type is spelled with `callconv(.c)`, the indirect +// When a fn-pointer's type is spelled with `abi(.c)`, the indirect // call must apply the same C-ABI byval coercion that direct C-ABI calls // do at the call site (path A): >16-byte non-HFA aggregates are passed // as `ptr byval()`. Without the fix, the indirect call site builds @@ -9,8 +9,8 @@ // ways that don't match the byval-attributed callee signature — the // callee then reads garbage out of the wrong machine-state slots. // -// The opt-in is the `callconv(.c)` on the fn-pointer type spelling. -// Pure-sx fn-pointer casts (no callconv suffix) keep their default +// The opt-in is the `abi(.c)` on the fn-pointer type spelling. +// Pure-sx fn-pointer casts (no abi suffix) keep their default // calling convention — verified by examples/87-fnptr-cast-large-aggregate.sx. #import "modules/std.sx"; @@ -22,7 +22,7 @@ Wide :: struct { d: i64; } -accept_c :: (w: Wide) -> i64 callconv(.c) { +accept_c :: (w: Wide) -> i64 abi(.c) { w.a + w.b + w.c + w.d } @@ -30,7 +30,7 @@ main :: () -> i32 { w := Wide.{ a = 1, b = 10, c = 100, d = 1000 }; if accept_c(w) != 1111 { return 1; } - fn_ptr : (Wide) -> i64 callconv(.c) = xx accept_c; + fn_ptr : (Wide) -> i64 abi(.c) = xx accept_c; if fn_ptr(w) != 1111 { return 2; } 0 diff --git a/examples/1204-ffi-fnptr-cast-large-aggregate.sx b/examples/1204-ffi-fnptr-cast-large-aggregate.sx index 65fd198c..ae60c5a9 100644 --- a/examples/1204-ffi-fnptr-cast-large-aggregate.sx +++ b/examples/1204-ffi-fnptr-cast-large-aggregate.sx @@ -1,10 +1,10 @@ -// Pure-sx fn-pointer cast: a function-pointer typed without `callconv(.c)` +// Pure-sx fn-pointer cast: a function-pointer typed without `abi(.c)` // keeps the default (sx) calling convention. Passing a >16-byte aggregate // through that pointer must not get the C-ABI byval coercion — the sx-CC // callee expects the struct as an SSA value, not as a `ptr byval()`. // -// Pair with examples/86-callconv-c-fnptr-large-aggregate.sx, which covers -// the opposite arm (fn-pointer typed `callconv(.c)` does get byval). +// Pair with examples/86-abi-c-fnptr-large-aggregate.sx, which covers +// the opposite arm (fn-pointer typed `abi(.c)` does get byval). #import "modules/std.sx"; diff --git a/examples/1214-ffi-06-callback.sx b/examples/1214-ffi-06-callback.sx index e951bed5..5ad596a6 100644 --- a/examples/1214-ffi-06-callback.sx +++ b/examples/1214-ffi-06-callback.sx @@ -16,19 +16,19 @@ #source "1214-ffi-06-callback.c"; }; -ffi_apply_callback :: (cb: (i32) -> i32 callconv(.c), value: i32) -> i32 extern; -ffi_apply_callback2 :: (cb: (*void, i32) -> i32 callconv(.c), ctx: *void, v: i32) -> i32 extern; +ffi_apply_callback :: (cb: (i32) -> i32 abi(.c), value: i32) -> i32 extern; +ffi_apply_callback2 :: (cb: (*void, i32) -> i32 abi(.c), ctx: *void, v: i32) -> i32 extern; g_callback_hits : i32 = 0; g_callback_sum : i32 = 0; -double_it :: (x: i32) -> i32 callconv(.c) { +double_it :: (x: i32) -> i32 abi(.c) { g_callback_hits += 1; g_callback_sum += x; x * 2 } -add_with_ctx :: (ctx: *void, v: i32) -> i32 callconv(.c) { +add_with_ctx :: (ctx: *void, v: i32) -> i32 abi(.c) { g_callback_hits += 1; // Pass a sentinel via ctx to prove the pointer arg also survives the // round-trip — read it back as an i32 through *i32. diff --git a/examples/1300-ffi-objc-roundtrip.sx b/examples/1300-ffi-objc-roundtrip.sx index a9a9b63d..896a3db5 100644 --- a/examples/1300-ffi-objc-roundtrip.sx +++ b/examples/1300-ffi-objc-roundtrip.sx @@ -18,10 +18,10 @@ main :: () -> i32 { sel_with_utf8 := sel_registerName("stringWithUTF8String:".ptr); sel_utf8 := sel_registerName("UTF8String".ptr); - msg_3 : (*void, *void, [*]u8) -> *void callconv(.c) = xx objc_msgSend; + msg_3 : (*void, *void, [*]u8) -> *void abi(.c) = xx objc_msgSend; ns_str := msg_3(ns_class, sel_with_utf8, "hi".ptr); - msg_2 : (*void, *void) -> [*]u8 callconv(.c) = xx objc_msgSend; + msg_2 : (*void, *void) -> [*]u8 abi(.c) = xx objc_msgSend; back := msg_2(ns_str, sel_utf8); return xx (back[0] + back[1]); // 'h' + 'i' = 104 + 105 = 209 diff --git a/examples/1301-ffi-objc-class.sx b/examples/1301-ffi-objc-class.sx index b13ca97d..11d9d5be 100644 --- a/examples/1301-ffi-objc-class.sx +++ b/examples/1301-ffi-objc-class.sx @@ -17,7 +17,7 @@ g_marker : i32 = 0; // IMP for `hello`. Must use C calling convention so `self` and `_cmd` land in // x0 and x1 the way the Obj-C runtime expects. -hello_imp :: (self: *void, _cmd: *void) callconv(.c) { +hello_imp :: (self: *void, _cmd: *void) abi(.c) { g_marker = 42; } @@ -34,7 +34,7 @@ main :: () -> i32 { if obj == xx 0 { return 2; } // [obj hello] - msg : (*void, *void) -> void callconv(.c) = xx objc_msgSend; + msg : (*void, *void) -> void abi(.c) = xx objc_msgSend; msg(obj, sel_hello); return g_marker; // 42 if hello_imp ran diff --git a/examples/1302-ffi-objc-block-noop.sx b/examples/1302-ffi-objc-block-noop.sx index b428e5d8..83dc0680 100644 --- a/examples/1302-ffi-objc-block-noop.sx +++ b/examples/1302-ffi-objc-block-noop.sx @@ -11,7 +11,7 @@ main :: () -> i32 { cl := () => { print("noop block ran\n"); }; b : Block = xx cl; - invoke_fn : (*Block) -> void callconv(.c) = xx b.invoke; + invoke_fn : (*Block) -> void abi(.c) = xx b.invoke; invoke_fn(@b); 0 } diff --git a/examples/1303-ffi-objc-block-capture.sx b/examples/1303-ffi-objc-block-capture.sx index 5194cf39..5079c7f6 100644 --- a/examples/1303-ffi-objc-block-capture.sx +++ b/examples/1303-ffi-objc-block-capture.sx @@ -11,7 +11,7 @@ main :: () -> i32 { y : i64 = 100; cl := () => { print("x + y = {}\n", x + y); }; b : Block = xx cl; - invoke_fn : (*Block) -> void callconv(.c) = xx b.invoke; + invoke_fn : (*Block) -> void abi(.c) = xx b.invoke; invoke_fn(@b); 0 } diff --git a/examples/1304-ffi-objc-block-multi-arg.sx b/examples/1304-ffi-objc-block-multi-arg.sx index 0e555dbd..595c8680 100644 --- a/examples/1304-ffi-objc-block-multi-arg.sx +++ b/examples/1304-ffi-objc-block-multi-arg.sx @@ -18,7 +18,7 @@ // Trampoline matching `void (^)(int, void*)` — the C ABI Apple's // runtime calls. Forwards through to the sx closure with the // standard `(__sx_ctx, env, ...args)` shape. -__block_invoke_void_i32_p :: (block_self: *Block, arg0: i32, arg1: *void) callconv(.c) { +__block_invoke_void_i32_p :: (block_self: *Block, arg0: i32, arg1: *void) abi(.c) { typed_fn : (*void, i32, *void) -> void = xx block_self.sx_fn; typed_fn(block_self.sx_env, arg0, arg1); } @@ -49,7 +49,7 @@ main :: () -> i32 { }; b : Block = xx cl; - invoke_fn : (*Block, i32, *void) -> void callconv(.c) = xx b.invoke; + invoke_fn : (*Block, i32, *void) -> void abi(.c) = xx b.invoke; sentinel: i32 = 42; invoke_fn(@b, 41, xx @sentinel); diff --git a/examples/1305-ffi-objc-block-inline.sx b/examples/1305-ffi-objc-block-inline.sx index 9626f097..d2d30135 100644 --- a/examples/1305-ffi-objc-block-inline.sx +++ b/examples/1305-ffi-objc-block-inline.sx @@ -6,7 +6,7 @@ #import "modules/ffi/objc_block.sx"; invoke_once :: (b: *Block) { - invoke_fn : (*Block) -> void callconv(.c) = xx b.invoke; + invoke_fn : (*Block) -> void abi(.c) = xx b.invoke; invoke_fn(b); } diff --git a/examples/1313-ffi-objc-class-alloc-roundtrip.sx b/examples/1313-ffi-objc-class-alloc-roundtrip.sx index 051ebb6d..f154438c 100644 --- a/examples/1313-ffi-objc-class-alloc-roundtrip.sx +++ b/examples/1313-ffi-objc-class-alloc-roundtrip.sx @@ -35,7 +35,7 @@ main :: () -> i32 { // [SxFoo alloc] — invokes the synthesized +alloc IMP. sel_alloc : SEL = sel_registerName("alloc".ptr); - msg_fn : (cls: *void, sel: *void) -> *void callconv(.c) = xx objc_msgSend; + msg_fn : (cls: *void, sel: *void) -> *void abi(.c) = xx objc_msgSend; instance : *void = msg_fn(cls, sel_alloc); if instance == null { print("FAIL: +alloc returned null\n"); return 1; } diff --git a/examples/1314-ffi-objc-class-dealloc-roundtrip.sx b/examples/1314-ffi-objc-class-dealloc-roundtrip.sx index ed16958b..530b9601 100644 --- a/examples/1314-ffi-objc-class-dealloc-roundtrip.sx +++ b/examples/1314-ffi-objc-class-dealloc-roundtrip.sx @@ -39,12 +39,12 @@ main :: () -> i32 { // alloc + release — synthesized -dealloc IMP fires inside. sel_alloc : SEL = sel_registerName("alloc".ptr); - alloc_fn : (cls: *void, sel: *void) -> *void callconv(.c) = xx objc_msgSend; + alloc_fn : (cls: *void, sel: *void) -> *void abi(.c) = xx objc_msgSend; instance : *void = alloc_fn(cls, sel_alloc); if instance == null { print("FAIL: +alloc returned null\n"); return 1; } sel_release : SEL = sel_registerName("release".ptr); - release_fn : (obj: *void, sel: *void) -> void callconv(.c) = xx objc_msgSend; + release_fn : (obj: *void, sel: *void) -> void abi(.c) = xx objc_msgSend; release_fn(instance, sel_release); // Run another cycle to confirm dealloc didn't corrupt runtime state. diff --git a/examples/1316-ffi-objc-class-method-static-imp.sx b/examples/1316-ffi-objc-class-method-static-imp.sx index 808b76e9..531b08c8 100644 --- a/examples/1316-ffi-objc-class-method-static-imp.sx +++ b/examples/1316-ffi-objc-class-method-static-imp.sx @@ -35,7 +35,7 @@ main :: () -> i32 { if method == null { print("FAIL: class method not on metaclass\n"); return 1; } // Invoke via objc_msgSend: [SxFoo answer] → 42. - msg_fn : (cls: *void, sel: *void) -> i32 callconv(.c) = xx objc_msgSend; + msg_fn : (cls: *void, sel: *void) -> i32 abi(.c) = xx objc_msgSend; result : i32 = msg_fn(cls, sel_answer); if result != 42 { print("FAIL: expected 42, got {}\n", result); return 1; } diff --git a/examples/1317-ffi-objc-class-level-constant.sx b/examples/1317-ffi-objc-class-level-constant.sx index 4bb0ff79..6bf5a8e3 100644 --- a/examples/1317-ffi-objc-class-level-constant.sx +++ b/examples/1317-ffi-objc-class-level-constant.sx @@ -38,13 +38,13 @@ main :: () -> i32 { // [SxThing answer] → 42 sel_answer : SEL = sel_registerName("answer".ptr); - msg_int : (cls: *void, sel: *void) -> i32 callconv(.c) = xx objc_msgSend; + msg_int : (cls: *void, sel: *void) -> i32 abi(.c) = xx objc_msgSend; r := msg_int(cls, sel_answer); if r != 42 { print("FAIL: answer expected 42, got {}\n", r); return 1; } // [SxThing seedClass] returns a non-null NSObject. sel_seed : SEL = sel_registerName("seedClass".ptr); - msg_ptr : (cls: *void, sel: *void) -> *void callconv(.c) = xx objc_msgSend; + msg_ptr : (cls: *void, sel: *void) -> *void abi(.c) = xx objc_msgSend; seed := msg_ptr(cls, sel_seed); if seed == null { print("FAIL: seedClass returned null\n"); return 1; } diff --git a/examples/1318-ffi-objc-property-extern-class.sx b/examples/1318-ffi-objc-property-extern-class.sx index 529eea00..d6c5d51a 100644 --- a/examples/1318-ffi-objc-property-extern-class.sx +++ b/examples/1318-ffi-objc-property-extern-class.sx @@ -23,10 +23,10 @@ // runtime ivar. Property dispatch should round-trip through them. g_probe_tag: i32 = 0; -probe_get_tag :: (self: *void, _cmd: *void) -> i32 callconv(.c) { +probe_get_tag :: (self: *void, _cmd: *void) -> i32 abi(.c) { return g_probe_tag; } -probe_set_tag :: (self: *void, _cmd: *void, v: i32) callconv(.c) { +probe_set_tag :: (self: *void, _cmd: *void, v: i32) abi(.c) { g_probe_tag = v; } diff --git a/examples/1332-ffi-objc-call-06-sret-return.sx b/examples/1332-ffi-objc-call-06-sret-return.sx index 8ac98088..1668f344 100644 --- a/examples/1332-ffi-objc-call-06-sret-return.sx +++ b/examples/1332-ffi-objc-call-06-sret-return.sx @@ -5,7 +5,7 @@ // through it and returns void. // // Register a runtime-built Obj-C class with a method that returns -// a fixed `Triple`. The IMP is a plain sx fn (callconv .c) — its +// a fixed `Triple`. The IMP is a plain sx fn (abi .c) — its // sret-shaped lowering already works (Phase 0.3 fix for plain // `extern` returns). The `#objc_call` dispatch side now produces // the matching call shape: `call void @objc_msgSend(ptr sret %slot, @@ -20,7 +20,7 @@ Triple :: struct { a: i64; b: i64; c: i64; } // IMP for the runtime-installed method. Obj-C convention: implicit // (self, _cmd) prefix, then declared args. Returns the value bytes. -triple_imp :: (self: *void, _cmd: *void) -> Triple callconv(.c) { +triple_imp :: (self: *void, _cmd: *void) -> Triple abi(.c) { Triple.{ a = 11, b = 22, c = 33 } } diff --git a/examples/1333-ffi-objc-call-07-fp-hfa-return.sx b/examples/1333-ffi-objc-call-07-fp-hfa-return.sx index d659dfa0..55279fe4 100644 --- a/examples/1333-ffi-objc-call-07-fp-hfa-return.sx +++ b/examples/1333-ffi-objc-call-07-fp-hfa-return.sx @@ -20,7 +20,7 @@ UIEdgeInsets :: struct { right: f64; } -insets_imp :: (self: *void, _cmd: *void) -> UIEdgeInsets callconv(.c) { +insets_imp :: (self: *void, _cmd: *void) -> UIEdgeInsets abi(.c) { UIEdgeInsets.{ top = 1.5, left = 2.5, bottom = 3.5, right = 4.5 } } diff --git a/examples/1334-ffi-objc-call-08-multi-keyword.sx b/examples/1334-ffi-objc-call-08-multi-keyword.sx index 3fb86e92..12b30aff 100644 --- a/examples/1334-ffi-objc-call-08-multi-keyword.sx +++ b/examples/1334-ffi-objc-call-08-multi-keyword.sx @@ -13,7 +13,7 @@ #import "modules/build.sx"; #import "modules/ffi/objc.sx"; -combine_imp :: (self: *void, _cmd: *void, a: i32, b: i32) -> i32 callconv(.c) { +combine_imp :: (self: *void, _cmd: *void, a: i32, b: i32) -> i32 abi(.c) { a * 100 + b } diff --git a/examples/1337-ffi-objc-call-11-bool-return.sx b/examples/1337-ffi-objc-call-11-bool-return.sx index 201242ed..aafbce09 100644 --- a/examples/1337-ffi-objc-call-11-bool-return.sx +++ b/examples/1337-ffi-objc-call-11-bool-return.sx @@ -13,8 +13,8 @@ #import "modules/build.sx"; #import "modules/ffi/objc.sx"; -yes_imp :: (self: *void, _cmd: *void) -> bool callconv(.c) { true } -no_imp :: (self: *void, _cmd: *void) -> bool callconv(.c) { false } +yes_imp :: (self: *void, _cmd: *void) -> bool abi(.c) { true } +no_imp :: (self: *void, _cmd: *void) -> bool abi(.c) { false } main :: () -> i32 { inline if OS == .macos { diff --git a/examples/1338-ffi-objc-call-12-rect-u64-returns.sx b/examples/1338-ffi-objc-call-12-rect-u64-returns.sx index 0e3d8eb2..da912ecc 100644 --- a/examples/1338-ffi-objc-call-12-rect-u64-returns.sx +++ b/examples/1338-ffi-objc-call-12-rect-u64-returns.sx @@ -20,11 +20,11 @@ CGRect :: struct { height: f64; } -rect_imp :: (self: *void, _cmd: *void) -> CGRect callconv(.c) { +rect_imp :: (self: *void, _cmd: *void) -> CGRect abi(.c) { CGRect.{ x = 10.5, y = 20.5, width = 30.5, height = 40.5 } } -u64_imp :: (self: *void, _cmd: *void) -> u64 callconv(.c) { +u64_imp :: (self: *void, _cmd: *void) -> u64 abi(.c) { // sx integer-literal parser rejects values ≥ 2^63 even when the // receiving type is u64, so the leading bit stays clear. 0x7FEDCBA987654321 diff --git a/examples/1339-ffi-objc-defined-class-01-instance.sx b/examples/1339-ffi-objc-defined-class-01-instance.sx index 555e379a..002b0de4 100644 --- a/examples/1339-ffi-objc-defined-class-01-instance.sx +++ b/examples/1339-ffi-objc-defined-class-01-instance.sx @@ -51,7 +51,7 @@ main :: () -> i32 { // release sel_release : SEL = sel_registerName("release".ptr); - release_fn : (obj: *void, sel: *void) -> void callconv(.c) = xx objc_msgSend; + release_fn : (obj: *void, sel: *void) -> void abi(.c) = xx objc_msgSend; release_fn(xx f, sel_release); } inline if OS != .macos { diff --git a/examples/1340-ffi-objc-defined-class-02-struct-encoding.sx b/examples/1340-ffi-objc-defined-class-02-struct-encoding.sx index bde8dde7..b561cea1 100644 --- a/examples/1340-ffi-objc-defined-class-02-struct-encoding.sx +++ b/examples/1340-ffi-objc-defined-class-02-struct-encoding.sx @@ -52,7 +52,7 @@ main :: () -> i32 { print("at: ({}, {})\n", p.x, p.y); // expected: at: (7.500000, 8.250000) sel_release : SEL = sel_registerName("release".ptr); - release_fn : (obj: *void, sel: *void) -> void callconv(.c) = xx objc_msgSend; + release_fn : (obj: *void, sel: *void) -> void abi(.c) = xx objc_msgSend; release_fn(xx m, sel_release); } inline if OS != .macos { diff --git a/examples/1341-ffi-objc-dsl-01-niladic.sx b/examples/1341-ffi-objc-dsl-01-niladic.sx index 89b57f10..b06b8099 100644 --- a/examples/1341-ffi-objc-dsl-01-niladic.sx +++ b/examples/1341-ffi-objc-dsl-01-niladic.sx @@ -20,7 +20,7 @@ SxProbeNiladic :: #objc_class("SxProbeNiladic") extern { length :: (self: *Self) -> i32; } -length_imp :: (self: *void, _cmd: *void) -> i32 callconv(.c) { +length_imp :: (self: *void, _cmd: *void) -> i32 abi(.c) { 42 } diff --git a/examples/1342-ffi-objc-dsl-02-one-arg.sx b/examples/1342-ffi-objc-dsl-02-one-arg.sx index 0d5f6947..2e290385 100644 --- a/examples/1342-ffi-objc-dsl-02-one-arg.sx +++ b/examples/1342-ffi-objc-dsl-02-one-arg.sx @@ -12,7 +12,7 @@ SxProbeOneArg :: #objc_class("SxProbeOneArg") extern { addObject :: (self: *Self, val: i32) -> i32; } -addObject_imp :: (self: *void, _cmd: *void, val: i32) -> i32 callconv(.c) { +addObject_imp :: (self: *void, _cmd: *void, val: i32) -> i32 abi(.c) { val * 2 } diff --git a/examples/1343-ffi-objc-dsl-03-multi-keyword.sx b/examples/1343-ffi-objc-dsl-03-multi-keyword.sx index daeaefc3..8b0814b6 100644 --- a/examples/1343-ffi-objc-dsl-03-multi-keyword.sx +++ b/examples/1343-ffi-objc-dsl-03-multi-keyword.sx @@ -11,7 +11,7 @@ SxProbeMultiKeyword :: #objc_class("SxProbeMultiKeyword") extern { combine_and :: (self: *Self, a: i32, b: i32) -> i32; } -combine_imp :: (self: *void, _cmd: *void, a: i32, b: i32) -> i32 callconv(.c) { +combine_imp :: (self: *void, _cmd: *void, a: i32, b: i32) -> i32 abi(.c) { a * 100 + b } diff --git a/examples/1347-ffi-objc-dsl-07-mangling-table.sx b/examples/1347-ffi-objc-dsl-07-mangling-table.sx index 114ee786..ebb777a0 100644 --- a/examples/1347-ffi-objc-dsl-07-mangling-table.sx +++ b/examples/1347-ffi-objc-dsl-07-mangling-table.sx @@ -27,7 +27,7 @@ SxManglingProbe :: #objc_class("SxManglingProbe") extern { custom_name :: (self: *Self) -> i32 #selector("actualSelectorName"); } -universal_imp :: (self: *void, _cmd: *void, a: i32, b: i32, c: i32, d: i32) -> i32 callconv(.c) { +universal_imp :: (self: *void, _cmd: *void, a: i32, b: i32, c: i32, d: i32) -> i32 abi(.c) { // Returns the arg count's witness; the test doesn't check return // values, only that dispatch succeeds for each selector shape. a + b + c + d diff --git a/examples/1349-ffi-objc-export-class.sx b/examples/1349-ffi-objc-export-class.sx index 6f188243..765dea1d 100644 --- a/examples/1349-ffi-objc-export-class.sx +++ b/examples/1349-ffi-objc-export-class.sx @@ -31,7 +31,7 @@ main :: () -> i32 { print("counter: {}\n", b.get()); // expected: 2 sel_release : SEL = sel_registerName("release".ptr); - release_fn : (obj: *void, sel: *void) -> void callconv(.c) = xx objc_msgSend; + release_fn : (obj: *void, sel: *void) -> void abi(.c) = xx objc_msgSend; release_fn(xx b, sel_release); } inline if OS != .macos { diff --git a/examples/1607-platform-uikit-app.sx b/examples/1607-platform-uikit-app.sx index b8230158..2dc00f6d 100644 --- a/examples/1607-platform-uikit-app.sx +++ b/examples/1607-platform-uikit-app.sx @@ -21,7 +21,7 @@ UIApplicationMain :: (argc: i32, argv: *void, principal_class: *NSString, delega // IMP for application:didFinishLaunchingWithOptions: // Obj-C: -(BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)opts // Type encoding: "c@:@@" -- BOOL (signed char), self, _cmd, id, id -did_finish_launching :: (self: *void, _cmd: *void, app: *void, opts: *void) -> u8 callconv(.c) { +did_finish_launching :: (self: *void, _cmd: *void, app: *void, opts: *void) -> u8 abi(.c) { NSLog(xx "[sx] application:didFinishLaunchingWithOptions: called\n"); return 1; // YES } diff --git a/examples/1608-platform-uikit-window.sx b/examples/1608-platform-uikit-window.sx index 052cd466..2f1d2348 100644 --- a/examples/1608-platform-uikit-window.sx +++ b/examples/1608-platform-uikit-window.sx @@ -25,14 +25,14 @@ g_window : *void = ---; // AppDelegate's `window` property. iOS queries this getter to discover the // app's key window; without it, the legacy code path creates its own empty // window and ignores anything we configure. -window_getter :: (self: *void, _cmd: *void) -> *void callconv(.c) { +window_getter :: (self: *void, _cmd: *void) -> *void abi(.c) { return g_window; } -window_setter :: (self: *void, _cmd: *void, w: *void) callconv(.c) { +window_setter :: (self: *void, _cmd: *void, w: *void) abi(.c) { g_window = w; } -did_finish_launching :: (self: *void, _cmd: *void, app: *void, opts: *void) -> u8 callconv(.c) { +did_finish_launching :: (self: *void, _cmd: *void, app: *void, opts: *void) -> u8 abi(.c) { UIWindow := objc_getClass("UIWindow".ptr); UIViewController := objc_getClass("UIViewController".ptr); UIColor := objc_getClass("UIColor".ptr); @@ -48,10 +48,10 @@ did_finish_launching :: (self: *void, _cmd: *void, app: *void, opts: *void) -> u sel_connected_scenes := sel_registerName("connectedScenes".ptr); sel_any_object := sel_registerName("anyObject".ptr); - msg_o : (*void, *void) -> *void callconv(.c) = xx objc_msgSend; - msg_v : (*void, *void) -> void callconv(.c) = xx objc_msgSend; - msg_oo : (*void, *void, *void) -> void callconv(.c) = xx objc_msgSend; - msg_ooo : (*void, *void, *void) -> *void callconv(.c) = xx objc_msgSend; + msg_o : (*void, *void) -> *void abi(.c) = xx objc_msgSend; + msg_v : (*void, *void) -> void abi(.c) = xx objc_msgSend; + msg_oo : (*void, *void, *void) -> void abi(.c) = xx objc_msgSend; + msg_ooo : (*void, *void, *void) -> *void abi(.c) = xx objc_msgSend; // Modern iOS path: get the connected windowScene, then construct the // window via initWithWindowScene: so UIKit auto-sizes it and attaches diff --git a/examples/1616-platform-ios-device-bundle.sx b/examples/1616-platform-ios-device-bundle.sx index 3a252b20..6228c1c7 100644 --- a/examples/1616-platform-ios-device-bundle.sx +++ b/examples/1616-platform-ios-device-bundle.sx @@ -39,7 +39,7 @@ configure :: () { // IMP for application:didFinishLaunchingWithOptions: // Obj-C: -(BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)opts // Type encoding: "c@:@@" -- BOOL (signed char), self, _cmd, id, id -did_finish_launching :: (self: *void, _cmd: *void, app: *void, opts: *void) -> u8 callconv(.c) { +did_finish_launching :: (self: *void, _cmd: *void, app: *void, opts: *void) -> u8 abi(.c) { NSLog(xx "[sx-device-probe] launched\n"); return 1; // YES } diff --git a/examples/1635-cfnptr-qsort.sx b/examples/1635-cfnptr-qsort.sx index 9cc8bdaf..24eb6e79 100644 --- a/examples/1635-cfnptr-qsort.sx +++ b/examples/1635-cfnptr-qsort.sx @@ -1,11 +1,11 @@ -// PLAN-HTTPZ C2: an sx `callconv(.c)` function passes by name as a +// PLAN-HTTPZ C2: an sx `abi(.c)` function passes by name as a // typed C function pointer — libc qsort drives an sx comparator. #import "modules/std.sx"; clib :: #library "c"; -qsort :: (base: [*]u8, nel: usize, width: usize, compar: (*void, *void) -> i32 callconv(.c)) extern clib; +qsort :: (base: [*]u8, nel: usize, width: usize, compar: (*void, *void) -> i32 abi(.c)) extern clib; -cmp_i32 :: (a: *void, b: *void) -> i32 callconv(.c) { +cmp_i32 :: (a: *void, b: *void) -> i32 abi(.c) { pa : *i32 = xx a; pb : *i32 = xx b; if pa.* < pb.* { return -1; } diff --git a/examples/1636-cfnptr-pthread-reentry.sx b/examples/1636-cfnptr-pthread-reentry.sx index 9179e820..384d74c9 100644 --- a/examples/1636-cfnptr-pthread-reentry.sx +++ b/examples/1636-cfnptr-pthread-reentry.sx @@ -1,14 +1,14 @@ // PLAN-HTTPZ C2: the C->sx re-entry contract. A real OS thread enters -// sx through a `callconv(.c)` entry (which has NO implicit context), +// sx through a `abi(.c)` entry (which has NO implicit context), // fabricates its own Context via `push Context.{ allocator = xx gpa }`, // and calls default-conv sx code that allocates through it. #import "modules/std.sx"; clib :: #library "c"; -pthread_create :: (thread: *usize, attr: *void, start: (*void) -> *void callconv(.c), arg: *void) -> i32 extern clib; +pthread_create :: (thread: *usize, attr: *void, start: (*void) -> *void abi(.c), arg: *void) -> i32 extern clib; pthread_join :: (thread: usize, retval: **void) -> i32 extern clib; -entry :: (arg: *void) -> *void callconv(.c) { +entry :: (arg: *void) -> *void abi(.c) { p : *i64 = xx arg; gpa := GPA.init(); push Context.{ allocator = xx gpa } { diff --git a/examples/1637-std-thread.sx b/examples/1637-std-thread.sx index 25e9bab7..78d8b3ca 100644 --- a/examples/1637-std-thread.sx +++ b/examples/1637-std-thread.sx @@ -11,7 +11,7 @@ Shared :: struct { } // Raw-thread entry: C->sx boundary, own fabricated context. -bump_entry :: (arg: *void) -> *void callconv(.c) { +bump_entry :: (arg: *void) -> *void abi(.c) { sh : *Shared = xx arg; gpa := GPA.init(); push Context.{ allocator = xx gpa } { diff --git a/examples/1655-platform-asm-callback-into-sx.sx b/examples/1655-platform-asm-callback-into-sx.sx index ff35d217..a4fb8249 100644 --- a/examples/1655-platform-asm-callback-into-sx.sx +++ b/examples/1655-platform-asm-callback-into-sx.sx @@ -2,7 +2,7 @@ // CALLS BACK into an sx function (`cb`) by its symbol, then returns. For the asm // `bl _cb` to resolve, the sx callback needs EXTERNAL LINKAGE and a stable C // symbol — that is exactly what `export` provides (it also implies the C ABI, so -// no hidden context parameter). `callconv(.c)` alone is NOT enough: it sets the +// no hidden context parameter). `abi(.c)` alone is NOT enough: it sets the // ABI but leaves the function `internal`, so it is dead-code-eliminated (nothing // in the IR references it — the `bl` is opaque to the optimizer) and `_cb` is // undefined at link. macOS gives `export "cb"` the symbol `_cb` (leading diff --git a/examples/expected/0114-types-build-block-convert.stdout b/examples/expected/0114-types-build-block-convert.stdout index f4d9e1ea..4463a09f 100644 --- a/examples/expected/0114-types-build-block-convert.stdout +++ b/examples/expected/0114-types-build-block-convert.stdout @@ -1,10 +1,10 @@ --- void / 0 args --- -__invoke :: (block_self: *Block) -> void callconv(.c) { typed_fn : (*void) -> void = xx block_self.sx_fn; typed_fn(block_self.sx_env); } return .{ isa = @_NSConcreteStackBlock, flags = 0, reserved = 0, invoke = xx @__invoke, descriptor = xx @__sx_block_descriptor, sx_env = self.env, sx_fn = self.fn_ptr, }; +__invoke :: (block_self: *Block) -> void abi(.c) { typed_fn : (*void) -> void = xx block_self.sx_fn; typed_fn(block_self.sx_env); } return .{ isa = @_NSConcreteStackBlock, flags = 0, reserved = 0, invoke = xx @__invoke, descriptor = xx @__sx_block_descriptor, sx_env = self.env, sx_fn = self.fn_ptr, }; --- void / bool --- -__invoke :: (block_self: *Block, arg0: bool) -> void callconv(.c) { typed_fn : (*void, bool) -> void = xx block_self.sx_fn; typed_fn(block_self.sx_env, arg0); } return .{ isa = @_NSConcreteStackBlock, flags = 0, reserved = 0, invoke = xx @__invoke, descriptor = xx @__sx_block_descriptor, sx_env = self.env, sx_fn = self.fn_ptr, }; +__invoke :: (block_self: *Block, arg0: bool) -> void abi(.c) { typed_fn : (*void, bool) -> void = xx block_self.sx_fn; typed_fn(block_self.sx_env, arg0); } return .{ isa = @_NSConcreteStackBlock, flags = 0, reserved = 0, invoke = xx @__invoke, descriptor = xx @__sx_block_descriptor, sx_env = self.env, sx_fn = self.fn_ptr, }; --- void / i64, string --- -__invoke :: (block_self: *Block, arg0: i64, arg1: string) -> void callconv(.c) { typed_fn : (*void, i64, string) -> void = xx block_self.sx_fn; typed_fn(block_self.sx_env, arg0, arg1); } return .{ isa = @_NSConcreteStackBlock, flags = 0, reserved = 0, invoke = xx @__invoke, descriptor = xx @__sx_block_descriptor, sx_env = self.env, sx_fn = self.fn_ptr, }; +__invoke :: (block_self: *Block, arg0: i64, arg1: string) -> void abi(.c) { typed_fn : (*void, i64, string) -> void = xx block_self.sx_fn; typed_fn(block_self.sx_env, arg0, arg1); } return .{ isa = @_NSConcreteStackBlock, flags = 0, reserved = 0, invoke = xx @__invoke, descriptor = xx @__sx_block_descriptor, sx_env = self.env, sx_fn = self.fn_ptr, }; --- i32 / f64 --- -__invoke :: (block_self: *Block, arg0: f64) -> i32 callconv(.c) { typed_fn : (*void, f64) -> i32 = xx block_self.sx_fn; return typed_fn(block_self.sx_env, arg0); } return .{ isa = @_NSConcreteStackBlock, flags = 0, reserved = 0, invoke = xx @__invoke, descriptor = xx @__sx_block_descriptor, sx_env = self.env, sx_fn = self.fn_ptr, }; +__invoke :: (block_self: *Block, arg0: f64) -> i32 abi(.c) { typed_fn : (*void, f64) -> i32 = xx block_self.sx_fn; return typed_fn(block_self.sx_env, arg0); } return .{ isa = @_NSConcreteStackBlock, flags = 0, reserved = 0, invoke = xx @__invoke, descriptor = xx @__sx_block_descriptor, sx_env = self.env, sx_fn = self.fn_ptr, }; --- build done --- rt diff --git a/examples/expected/0625-comptime-weld-struct-field.exit b/examples/expected/0625-comptime-weld-struct-field.exit new file mode 100644 index 00000000..573541ac --- /dev/null +++ b/examples/expected/0625-comptime-weld-struct-field.exit @@ -0,0 +1 @@ +0 diff --git a/examples/expected/0625-comptime-weld-struct-field.stderr b/examples/expected/0625-comptime-weld-struct-field.stderr new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/expected/0625-comptime-weld-struct-field.stderr @@ -0,0 +1 @@ + diff --git a/examples/expected/0625-comptime-weld-struct-field.stdout b/examples/expected/0625-comptime-weld-struct-field.stdout new file mode 100644 index 00000000..9c0e2d64 --- /dev/null +++ b/examples/expected/0625-comptime-weld-struct-field.stdout @@ -0,0 +1 @@ +name=7 ty=3 diff --git a/examples/expected/0626-comptime-weld-fn-intern-text-of.exit b/examples/expected/0626-comptime-weld-fn-intern-text-of.exit new file mode 100644 index 00000000..573541ac --- /dev/null +++ b/examples/expected/0626-comptime-weld-fn-intern-text-of.exit @@ -0,0 +1 @@ +0 diff --git a/examples/expected/0626-comptime-weld-fn-intern-text-of.stderr b/examples/expected/0626-comptime-weld-fn-intern-text-of.stderr new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/expected/0626-comptime-weld-fn-intern-text-of.stderr @@ -0,0 +1 @@ + diff --git a/examples/expected/0626-comptime-weld-fn-intern-text-of.stdout b/examples/expected/0626-comptime-weld-fn-intern-text-of.stdout new file mode 100644 index 00000000..27c025da --- /dev/null +++ b/examples/expected/0626-comptime-weld-fn-intern-text-of.stdout @@ -0,0 +1 @@ +hello, compiler diff --git a/examples/expected/1104-diagnostics-callconv-mismatch-diagnostic.stderr b/examples/expected/1104-diagnostics-callconv-mismatch-diagnostic.stderr index b6a2cec3..19ee3186 100644 --- a/examples/expected/1104-diagnostics-callconv-mismatch-diagnostic.stderr +++ b/examples/expected/1104-diagnostics-callconv-mismatch-diagnostic.stderr @@ -1,5 +1,5 @@ -error: call-convention mismatch: 'sx_handler' is declared with default sx convention but the target type expects callconv(.c) - --> examples/1104-diagnostics-callconv-mismatch-diagnostic.sx:12:42 +error: call-convention mismatch: 'sx_handler' is declared with default sx convention but the target type expects abi(.c) + --> examples/1104-diagnostics-callconv-mismatch-diagnostic.sx:12:37 | -12 | fp : (*void) -> *void callconv(.c) = sx_handler; - | ^^^^^^^^^^ +12 | fp : (*void) -> *void abi(.c) = sx_handler; + | ^^^^^^^^^^ diff --git a/examples/expected/1183-diagnostics-weld-struct-field-count.exit b/examples/expected/1183-diagnostics-weld-struct-field-count.exit new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/examples/expected/1183-diagnostics-weld-struct-field-count.exit @@ -0,0 +1 @@ +1 diff --git a/examples/expected/1183-diagnostics-weld-struct-field-count.stderr b/examples/expected/1183-diagnostics-weld-struct-field-count.stderr new file mode 100644 index 00000000..7c3aefbb --- /dev/null +++ b/examples/expected/1183-diagnostics-weld-struct-field-count.stderr @@ -0,0 +1,5 @@ +error: welded type 'Field' has 2 field(s) in the compiler library but the declaration has 1 + --> examples/1183-diagnostics-weld-struct-field-count.sx:12:51 + | +12 | Field :: struct abi(.zig) extern compiler { name: u32; } + | ^^^ diff --git a/examples/expected/1183-diagnostics-weld-struct-field-count.stdout b/examples/expected/1183-diagnostics-weld-struct-field-count.stdout new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/expected/1183-diagnostics-weld-struct-field-count.stdout @@ -0,0 +1 @@ + diff --git a/examples/expected/1184-diagnostics-weld-fn-unexported.exit b/examples/expected/1184-diagnostics-weld-fn-unexported.exit new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/examples/expected/1184-diagnostics-weld-fn-unexported.exit @@ -0,0 +1 @@ +1 diff --git a/examples/expected/1184-diagnostics-weld-fn-unexported.stderr b/examples/expected/1184-diagnostics-weld-fn-unexported.stderr new file mode 100644 index 00000000..76ca2b0c --- /dev/null +++ b/examples/expected/1184-diagnostics-weld-fn-unexported.stderr @@ -0,0 +1,5 @@ +error: 'not_a_real_compiler_fn' is not a function exported by the 'compiler' library + --> examples/1184-diagnostics-weld-fn-unexported.sx:9:1 + | + 9 | not_a_real_compiler_fn :: (x: i64) -> i64 abi(.zig) extern compiler; + | ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/examples/expected/1184-diagnostics-weld-fn-unexported.stdout b/examples/expected/1184-diagnostics-weld-fn-unexported.stdout new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/expected/1184-diagnostics-weld-fn-unexported.stdout @@ -0,0 +1 @@ + diff --git a/examples/expected/1185-diagnostics-weld-fn-runtime-call.exit b/examples/expected/1185-diagnostics-weld-fn-runtime-call.exit new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/examples/expected/1185-diagnostics-weld-fn-runtime-call.exit @@ -0,0 +1 @@ +1 diff --git a/examples/expected/1185-diagnostics-weld-fn-runtime-call.stderr b/examples/expected/1185-diagnostics-weld-fn-runtime-call.stderr new file mode 100644 index 00000000..318c356a --- /dev/null +++ b/examples/expected/1185-diagnostics-weld-fn-runtime-call.stderr @@ -0,0 +1 @@ +error: 'intern' is a comptime-only compiler-library function — it cannot be called at runtime (use it inside #run or a comptime '::') diff --git a/examples/expected/1185-diagnostics-weld-fn-runtime-call.stdout b/examples/expected/1185-diagnostics-weld-fn-runtime-call.stdout new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/expected/1185-diagnostics-weld-fn-runtime-call.stdout @@ -0,0 +1 @@ + diff --git a/examples/expected/1200-ffi-callconv-c-callbacks.stdout b/examples/expected/1200-ffi-callconv-c-callbacks.stdout index e1a9e663..79543ded 100644 --- a/examples/expected/1200-ffi-callconv-c-callbacks.stdout +++ b/examples/expected/1200-ffi-callconv-c-callbacks.stdout @@ -1 +1 @@ -callconv(.c): 42 +abi(.c): 42 diff --git a/examples/expected/1228-ffi-extern-c-non-transitive.stdout b/examples/expected/1228-ffi-extern-c-non-transitive.stdout index e69de29b..8b137891 100644 --- a/examples/expected/1228-ffi-extern-c-non-transitive.stdout +++ b/examples/expected/1228-ffi-extern-c-non-transitive.stdout @@ -0,0 +1 @@ + diff --git a/examples/expected/1231-ffi-extern-undeclared-lib.stdout b/examples/expected/1231-ffi-extern-undeclared-lib.stdout index e69de29b..8b137891 100644 --- a/examples/expected/1231-ffi-extern-undeclared-lib.stdout +++ b/examples/expected/1231-ffi-extern-undeclared-lib.stdout @@ -0,0 +1 @@ + diff --git a/library/modules/ffi/objc.sx b/library/modules/ffi/objc.sx index 54fcdbb8..d6dc2125 100644 --- a/library/modules/ffi/objc.sx +++ b/library/modules/ffi/objc.sx @@ -53,7 +53,7 @@ objc_msgSend :: (recv: *void, sel: *void) -> *void extern objc; // // IMPs (method implementations) are function pointers with the implicit // Obj-C method shape: `(self: *void, _cmd: *void, ...args) -> ret` with -// `callconv(.c)` so they land args in the standard C registers. +// `abi(.c)` so they land args in the standard C registers. // // Method type encoding strings follow Apple's runtime DSL: // v = void c = char/BOOL i = int l = long f = float d = double @@ -120,7 +120,7 @@ impl Into(*NSString) for string { convert :: (self: string) -> *NSString { cls := objc_getClass("NSString".ptr); sel := sel_registerName("stringWithUTF8String:".ptr); - msg : (*void, *void, [*]u8) -> *void callconv(.c) = xx objc_msgSend; + msg : (*void, *void, [*]u8) -> *void abi(.c) = xx objc_msgSend; return xx msg(cls, sel, self.ptr); } } diff --git a/library/modules/ffi/objc_block.sx b/library/modules/ffi/objc_block.sx index 99c11713..9afd4fbf 100644 --- a/library/modules/ffi/objc_block.sx +++ b/library/modules/ffi/objc_block.sx @@ -63,7 +63,7 @@ __sx_block_descriptor : BlockDescriptor = .{ // `$args` is the bound pack of arg types and `$R` is the bound // return type. The `#insert` evaluates `build_block_convert` at // comptime and substitutes the resulting source — a nested -// `callconv(.c)` trampoline + the Block literal that points its +// `abi(.c)` trampoline + the Block literal that points its // `invoke` slot at it. One impl in stdlib replaces every per- // signature hand-rolled `__block_invoke_*` + `Into(Block)` pair. impl Into(Block) for Closure(..$args) -> $R { @@ -77,7 +77,7 @@ impl Into(Block) for Closure(..$args) -> $R { // (`args`, a comptime `[]Type`) and the closure's return type // (`$ret`), emits source that: // -// 1. Declares a nested `__invoke` `callconv(.c)` trampoline whose +// 1. Declares a nested `__invoke` `abi(.c)` trampoline whose // signature matches the per-shape Apple Block ABI: first arg is // `block_self: *Block`, then the pack types verbatim. The body // reconstructs a typed fn-pointer from `block_self.sx_fn`, @@ -105,7 +105,7 @@ build_block_convert :: (args: []Type, $ret: Type) -> string { } code = concat(code, ") -> "); code = concat(code, ret_name); - code = concat(code, " callconv(.c) { typed_fn : (*void"); + code = concat(code, " abi(.c) { typed_fn : (*void"); i = 0; while i < args.len { code = concat(code, ", "); diff --git a/library/modules/ffi/opengl.sx b/library/modules/ffi/opengl.sx index 8077c1e7..792a3af7 100644 --- a/library/modules/ffi/opengl.sx +++ b/library/modules/ffi/opengl.sx @@ -25,39 +25,39 @@ GL_LINE :u32: 0x1B01; GL_FILL :u32: 0x1B02; // Function pointer variables (mutable, loaded at runtime) -glClearColor : (f32, f32, f32, f32) -> void callconv(.c) = ---; -glClear : (u32) -> void callconv(.c) = ---; -glEnable : (u32) -> void callconv(.c) = ---; -glDisable : (u32) -> void callconv(.c) = ---; -glViewport : (i32, i32, i32, i32) -> void callconv(.c) = ---; -glFlush : () -> void callconv(.c) = ---; -glDrawArrays : (u32, i32, i32) -> void callconv(.c) = ---; -glPolygonMode : (u32, u32) -> void callconv(.c) = ---; -glLineWidth : (f32) -> void callconv(.c) = ---; -glCreateShader : (u32) -> u32 callconv(.c) = ---; -glShaderSource : (u32, i32, *[:0]u8, *i32) -> void callconv(.c) = ---; -glCompileShader : (u32) -> void callconv(.c) = ---; -glGetShaderiv : (u32, u32, *i32) -> void callconv(.c) = ---; -glGetShaderInfoLog : (u32, i32, *i32, [*]u8) -> void callconv(.c) = ---; -glCreateProgram : () -> u32 callconv(.c) = ---; -glAttachShader : (u32, u32) -> void callconv(.c) = ---; -glLinkProgram : (u32) -> void callconv(.c) = ---; -glGetProgramiv : (u32, u32, *i32) -> void callconv(.c) = ---; -glGetProgramInfoLog : (u32, i32, *i32, [*]u8) -> void callconv(.c) = ---; -glUseProgram : (u32) -> void callconv(.c) = ---; -glDeleteShader : (u32) -> void callconv(.c) = ---; -glGenVertexArrays : (i32, *u32) -> void callconv(.c) = ---; -glGenBuffers : (i32, *u32) -> void callconv(.c) = ---; -glBindVertexArray : (u32) -> void callconv(.c) = ---; -glBindBuffer : (u32, u32) -> void callconv(.c) = ---; -glBufferData : (u32, isize, *void, u32) -> void callconv(.c) = ---; -glVertexAttribPointer : (u32, i32, u32, u8, i32, *void) -> void callconv(.c) = ---; -glEnableVertexAttribArray : (u32) -> void callconv(.c) = ---; -glGetUniformLocation : (u32, [*]u8) -> i32 callconv(.c) = ---; -glUniformMatrix4fv : (i32, i32, u8, [*]f32) -> void callconv(.c) = ---; -glUniform3f : (i32, f32, f32, f32) -> void callconv(.c) = ---; -glDepthFunc : (u32) -> void callconv(.c) = ---; -glUniform1f : (i32, f32) -> void callconv(.c) = ---; +glClearColor : (f32, f32, f32, f32) -> void abi(.c) = ---; +glClear : (u32) -> void abi(.c) = ---; +glEnable : (u32) -> void abi(.c) = ---; +glDisable : (u32) -> void abi(.c) = ---; +glViewport : (i32, i32, i32, i32) -> void abi(.c) = ---; +glFlush : () -> void abi(.c) = ---; +glDrawArrays : (u32, i32, i32) -> void abi(.c) = ---; +glPolygonMode : (u32, u32) -> void abi(.c) = ---; +glLineWidth : (f32) -> void abi(.c) = ---; +glCreateShader : (u32) -> u32 abi(.c) = ---; +glShaderSource : (u32, i32, *[:0]u8, *i32) -> void abi(.c) = ---; +glCompileShader : (u32) -> void abi(.c) = ---; +glGetShaderiv : (u32, u32, *i32) -> void abi(.c) = ---; +glGetShaderInfoLog : (u32, i32, *i32, [*]u8) -> void abi(.c) = ---; +glCreateProgram : () -> u32 abi(.c) = ---; +glAttachShader : (u32, u32) -> void abi(.c) = ---; +glLinkProgram : (u32) -> void abi(.c) = ---; +glGetProgramiv : (u32, u32, *i32) -> void abi(.c) = ---; +glGetProgramInfoLog : (u32, i32, *i32, [*]u8) -> void abi(.c) = ---; +glUseProgram : (u32) -> void abi(.c) = ---; +glDeleteShader : (u32) -> void abi(.c) = ---; +glGenVertexArrays : (i32, *u32) -> void abi(.c) = ---; +glGenBuffers : (i32, *u32) -> void abi(.c) = ---; +glBindVertexArray : (u32) -> void abi(.c) = ---; +glBindBuffer : (u32, u32) -> void abi(.c) = ---; +glBufferData : (u32, isize, *void, u32) -> void abi(.c) = ---; +glVertexAttribPointer : (u32, i32, u32, u8, i32, *void) -> void abi(.c) = ---; +glEnableVertexAttribArray : (u32) -> void abi(.c) = ---; +glGetUniformLocation : (u32, [*]u8) -> i32 abi(.c) = ---; +glUniformMatrix4fv : (i32, i32, u8, [*]f32) -> void abi(.c) = ---; +glUniform3f : (i32, f32, f32, f32) -> void abi(.c) = ---; +glDepthFunc : (u32) -> void abi(.c) = ---; +glUniform1f : (i32, f32) -> void abi(.c) = ---; GL_LESS :u32: 0x0201; GL_LEQUAL :u32: 0x0203; GL_SCISSOR_TEST :u32: 0x0C11; @@ -76,27 +76,27 @@ GL_RED :u32: 0x1903; GL_R8 :u32: 0x8229; GL_UNPACK_ALIGNMENT :u32: 0x0CF5; -glScissor : (i32, i32, i32, i32) -> void callconv(.c) = ---; -glBufferSubData : (u32, isize, isize, *void) -> void callconv(.c) = ---; -glGenTextures : (i32, *u32) -> void callconv(.c) = ---; -glBindTexture : (u32, u32) -> void callconv(.c) = ---; -glTexImage2D : (u32, i32, i32, i32, i32, i32, u32, u32, *void) -> void callconv(.c) = ---; -glTexParameteri : (u32, u32, i32) -> void callconv(.c) = ---; -glBlendFunc : (u32, u32) -> void callconv(.c) = ---; -glReadPixels : (i32, i32, i32, i32, u32, u32, *void) -> void callconv(.c) = ---; -glActiveTexture : (u32) -> void callconv(.c) = ---; -glUniform1i : (i32, i32) -> void callconv(.c) = ---; -glPixelStorei : (u32, i32) -> void callconv(.c) = ---; -glTexSubImage2D : (u32, i32, i32, i32, i32, i32, u32, u32, *void) -> void callconv(.c) = ---; -glDeleteTextures : (i32, *u32) -> void callconv(.c) = ---; +glScissor : (i32, i32, i32, i32) -> void abi(.c) = ---; +glBufferSubData : (u32, isize, isize, *void) -> void abi(.c) = ---; +glGenTextures : (i32, *u32) -> void abi(.c) = ---; +glBindTexture : (u32, u32) -> void abi(.c) = ---; +glTexImage2D : (u32, i32, i32, i32, i32, i32, u32, u32, *void) -> void abi(.c) = ---; +glTexParameteri : (u32, u32, i32) -> void abi(.c) = ---; +glBlendFunc : (u32, u32) -> void abi(.c) = ---; +glReadPixels : (i32, i32, i32, i32, u32, u32, *void) -> void abi(.c) = ---; +glActiveTexture : (u32) -> void abi(.c) = ---; +glUniform1i : (i32, i32) -> void abi(.c) = ---; +glPixelStorei : (u32, i32) -> void abi(.c) = ---; +glTexSubImage2D : (u32, i32, i32, i32, i32, i32, u32, u32, *void) -> void abi(.c) = ---; +glDeleteTextures : (i32, *u32) -> void abi(.c) = ---; -glGenFramebuffers : (i32, *u32) -> void callconv(.c) = ---; -glGenRenderbuffers : (i32, *u32) -> void callconv(.c) = ---; -glBindFramebuffer : (u32, u32) -> void callconv(.c) = ---; -glBindRenderbuffer : (u32, u32) -> void callconv(.c) = ---; -glFramebufferRenderbuffer : (u32, u32, u32, u32) -> void callconv(.c) = ---; -glGetRenderbufferParameteriv : (u32, u32, *i32) -> void callconv(.c) = ---; -glCheckFramebufferStatus : (u32) -> u32 callconv(.c) = ---; +glGenFramebuffers : (i32, *u32) -> void abi(.c) = ---; +glGenRenderbuffers : (i32, *u32) -> void abi(.c) = ---; +glBindFramebuffer : (u32, u32) -> void abi(.c) = ---; +glBindRenderbuffer : (u32, u32) -> void abi(.c) = ---; +glFramebufferRenderbuffer : (u32, u32, u32, u32) -> void abi(.c) = ---; +glGetRenderbufferParameteriv : (u32, u32, *i32) -> void abi(.c) = ---; +glCheckFramebufferStatus : (u32) -> u32 abi(.c) = ---; GL_TEXTURE_WRAP_S :u32: 0x2802; GL_TEXTURE_WRAP_T :u32: 0x2803; @@ -104,7 +104,7 @@ GL_CLAMP_TO_EDGE :u32: 0x812F; // Loader: call once after creating GL context // Pass in a proc loader (e.g. SDL_GL_GetProcAddress) -load_gl :: (get_proc: ([*]u8) -> *void callconv(.c)) { +load_gl :: (get_proc: ([*]u8) -> *void abi(.c)) { glClearColor = xx get_proc("glClearColor"); glClear = xx get_proc("glClear"); glEnable = xx get_proc("glEnable"); diff --git a/library/modules/gpu/metal.sx b/library/modules/gpu/metal.sx index ced720d9..0006b646 100644 --- a/library/modules/gpu/metal.sx +++ b/library/modules/gpu/metal.sx @@ -58,7 +58,7 @@ MTLClearColor :: struct { // MTLRegion is 48 bytes and MTLScissorRect is 32 bytes; both are passed // by value to the Obj-C runtime, which the compiler marshals as // `ptr byval()` via the C-ABI byval coercion. The fn-pointer cast -// must spell `callconv(.c)` so the indirect call applies that coercion. +// must spell `abi(.c)` so the indirect call applies that coercion. MTLOrigin :: struct { x: u64; y: u64; z: u64; } MTLSize :: struct { width: u64; height: u64; depth: u64; } MTLRegion :: struct { origin: MTLOrigin; size: MTLSize; } @@ -257,11 +257,11 @@ metal_init_ios :: (self: *MetalGPU) -> bool { if self.device == null { return false; } } - msg_oo : (*void, *void, *void) -> void callconv(.c) = xx objc_msgSend; - msg_ou : (*void, *void, u64) -> void callconv(.c) = xx objc_msgSend; - msg_ob : (*void, *void, u8) -> void callconv(.c) = xx objc_msgSend; - msg_osize : (*void, *void, CGSize) -> void callconv(.c) = xx objc_msgSend; - msg_o : (*void, *void) -> *void callconv(.c) = xx objc_msgSend; + msg_oo : (*void, *void, *void) -> void abi(.c) = xx objc_msgSend; + msg_ou : (*void, *void, u64) -> void abi(.c) = xx objc_msgSend; + msg_ob : (*void, *void, u8) -> void abi(.c) = xx objc_msgSend; + msg_osize : (*void, *void, CGSize) -> void abi(.c) = xx objc_msgSend; + msg_o : (*void, *void) -> *void abi(.c) = xx objc_msgSend; if self.queue == null { self.queue = msg_o(self.device, sel_registerName("newCommandQueue".ptr)); @@ -290,7 +290,7 @@ metal_resize_ios :: (self: *MetalGPU) { inline if OS != .ios { return; } if self.layer == null { return; } - msg_osize : (*void, *void, CGSize) -> void callconv(.c) = xx objc_msgSend; + msg_osize : (*void, *void, CGSize) -> void abi(.c) = xx objc_msgSend; size := CGSize.{ width = xx self.pixel_w, height = xx self.pixel_h }; msg_osize(self.layer, sel_registerName("setDrawableSize:".ptr), size); } @@ -301,12 +301,12 @@ metal_begin_frame_ios :: (self: *MetalGPU, clear: ClearColor) -> bool { if self.queue == null { return false; } if self.pixel_w <= 0 or self.pixel_h <= 0 { return false; } - msg_o : (*void, *void) -> *void callconv(.c) = xx objc_msgSend; - msg_oo : (*void, *void, *void) -> void callconv(.c) = xx objc_msgSend; - msg_oo_ret : (*void, *void, *void) -> *void callconv(.c) = xx objc_msgSend; - msg_ou : (*void, *void, u64) -> void callconv(.c) = xx objc_msgSend; - msg_ouret : (*void, *void, u64) -> *void callconv(.c) = xx objc_msgSend; - msg_oclear : (*void, *void, MTLClearColor) -> void callconv(.c) = xx objc_msgSend; + msg_o : (*void, *void) -> *void abi(.c) = xx objc_msgSend; + msg_oo : (*void, *void, *void) -> void abi(.c) = xx objc_msgSend; + msg_oo_ret : (*void, *void, *void) -> *void abi(.c) = xx objc_msgSend; + msg_ou : (*void, *void, u64) -> void abi(.c) = xx objc_msgSend; + msg_ouret : (*void, *void, u64) -> *void abi(.c) = xx objc_msgSend; + msg_oclear : (*void, *void, MTLClearColor) -> void abi(.c) = xx objc_msgSend; // drawable = [layer nextDrawable] self.drawable = msg_o(self.layer, sel_registerName("nextDrawable".ptr)); @@ -353,9 +353,9 @@ metal_end_frame_ios :: (self: *MetalGPU, target_time: f64) { if self.cmd_buffer == null { return; } if self.drawable == null { return; } - msg_v : (*void, *void) -> void callconv(.c) = xx objc_msgSend; - msg_oo : (*void, *void, *void) -> void callconv(.c) = xx objc_msgSend; - msg_ood : (*void, *void, *void, f64) -> void callconv(.c) = xx objc_msgSend; + msg_v : (*void, *void) -> void abi(.c) = xx objc_msgSend; + msg_oo : (*void, *void, *void) -> void abi(.c) = xx objc_msgSend; + msg_ood : (*void, *void, *void, f64) -> void abi(.c) = xx objc_msgSend; msg_v(self.encoder, sel_registerName("endEncoding".ptr)); @@ -386,15 +386,15 @@ metal_create_shader_ios :: (self: *MetalGPU, src: string) -> u32 { inline if OS != .ios { return 0; } if self.device == null { return 0; } - msg_o : (*void, *void) -> *void callconv(.c) = xx objc_msgSend; - msg_oo : (*void, *void, *void) -> void callconv(.c) = xx objc_msgSend; - msg_oo_r : (*void, *void, *NSString) -> *void callconv(.c) = xx objc_msgSend; - msg_ou : (*void, *void, u64) -> void callconv(.c) = xx objc_msgSend; - msg_ouret: (*void, *void, u64) -> *void callconv(.c) = xx objc_msgSend; - msg_ob : (*void, *void, u8) -> void callconv(.c) = xx objc_msgSend; + msg_o : (*void, *void) -> *void abi(.c) = xx objc_msgSend; + msg_oo : (*void, *void, *void) -> void abi(.c) = xx objc_msgSend; + msg_oo_r : (*void, *void, *NSString) -> *void abi(.c) = xx objc_msgSend; + msg_ou : (*void, *void, u64) -> void abi(.c) = xx objc_msgSend; + msg_ouret: (*void, *void, u64) -> *void abi(.c) = xx objc_msgSend; + msg_ob : (*void, *void, u8) -> void abi(.c) = xx objc_msgSend; // [device newLibraryWithSource:src options:nil error:&err] - msg_lib : (*void, *void, *NSString, *void, **void) -> *void callconv(.c) = xx objc_msgSend; + msg_lib : (*void, *void, *NSString, *void, **void) -> *void abi(.c) = xx objc_msgSend; err : *void = null; library := msg_lib(self.device, sel_registerName("newLibraryWithSource:options:error:".ptr), @@ -428,7 +428,7 @@ metal_create_shader_ios :: (self: *MetalGPU, src: string) -> u32 { msg_ou(att0, sel_registerName("setSourceAlphaBlendFactor:".ptr), MTL_BLEND_FACTOR_SRC_ALPHA); msg_ou(att0, sel_registerName("setDestinationAlphaBlendFactor:".ptr), MTL_BLEND_FACTOR_ONE_MINUS_SRC_A); - msg_pipe : (*void, *void, *void, **void) -> *void callconv(.c) = xx objc_msgSend; + msg_pipe : (*void, *void, *void, **void) -> *void abi(.c) = xx objc_msgSend; err2 : *void = null; state := msg_pipe(self.device, sel_registerName("newRenderPipelineStateWithDescriptor:error:".ptr), @@ -452,7 +452,7 @@ metal_create_buffer_ios :: (self: *MetalGPU, size_bytes: i64) -> u32 { if size_bytes <= 0 { return 0; } // MTLResourceStorageModeShared is the default (option value 0). - msg_buf : (*void, *void, u64, u64) -> *void callconv(.c) = xx objc_msgSend; + msg_buf : (*void, *void, u64, u64) -> *void abi(.c) = xx objc_msgSend; buf := msg_buf(self.device, sel_registerName("newBufferWithLength:options:".ptr), xx size_bytes, 0); @@ -469,7 +469,7 @@ metal_update_buffer_ios :: (self: *MetalGPU, handle: u32, data: *void, size_byte if data == null { return; } if size_bytes <= 0 { return; } - msg_o : (*void, *void) -> *void callconv(.c) = xx objc_msgSend; + msg_o : (*void, *void) -> *void abi(.c) = xx objc_msgSend; dst := msg_o(buf, sel_registerName("contents".ptr)); if dst == null { return; } memcpy(dst, data, size_bytes); @@ -483,7 +483,7 @@ metal_update_buffer_at_ios :: (self: *MetalGPU, handle: u32, data: *void, size_b if size_bytes <= 0 { return; } if byte_offset < 0 { return; } - msg_o : (*void, *void) -> *void callconv(.c) = xx objc_msgSend; + msg_o : (*void, *void) -> *void abi(.c) = xx objc_msgSend; base := msg_o(buf, sel_registerName("contents".ptr)); if base == null { return; } // Add byte_offset via integer arithmetic — `@dst[i]` on `[*]u8` @@ -530,7 +530,7 @@ metal_create_texture_ios :: (self: *MetalGPU, w: i32, h: i32, format: TextureFor // [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:width:height:mipmapped:] MTLTextureDescriptor := objc_getClass("MTLTextureDescriptor".ptr); - msg_desc : (*void, *void, u64, u64, u64, u8) -> *void callconv(.c) = xx objc_msgSend; + msg_desc : (*void, *void, u64, u64, u64, u8) -> *void abi(.c) = xx objc_msgSend; desc := msg_desc(MTLTextureDescriptor, sel_registerName("texture2DDescriptorWithPixelFormat:width:height:mipmapped:".ptr), pixel_format, xx w, xx h, 0); @@ -539,10 +539,10 @@ metal_create_texture_ios :: (self: *MetalGPU, w: i32, h: i32, format: TextureFor // Force shared storage so the CPU can keep writing pixels (atlas updates, // sprite uploads). On iOS-sim under Apple Silicon the convenience class // method's default storage isn't reliably shared for every format. - msg_ou_void : (*void, *void, u64) -> void callconv(.c) = xx objc_msgSend; + msg_ou_void : (*void, *void, u64) -> void abi(.c) = xx objc_msgSend; msg_ou_void(desc, sel_registerName("setStorageMode:".ptr), MTL_STORAGE_MODE_SHARED); - msg_oo : (*void, *void, *void) -> *void callconv(.c) = xx objc_msgSend; + msg_oo : (*void, *void, *void) -> *void abi(.c) = xx objc_msgSend; tex := msg_oo(self.device, sel_registerName("newTextureWithDescriptor:".ptr), desc); if tex == null { return 0; } @@ -575,7 +575,7 @@ metal_update_texture_region_ios :: (self: *MetalGPU, handle: u32, x: i32, y: i32 bytes_per_row : u64 = xx (slot.bytes_per_pixel * cast(u32) w); // [tex replaceRegion:region mipmapLevel:0 withBytes:pixels bytesPerRow:bytes_per_row] - msg_replace : (*void, *void, MTLRegion, u64, *void, u64) -> void callconv(.c) = xx objc_msgSend; + msg_replace : (*void, *void, MTLRegion, u64, *void, u64) -> void abi(.c) = xx objc_msgSend; msg_replace(slot.tex, sel_registerName("replaceRegion:mipmapLevel:withBytes:bytesPerRow:".ptr), region, 0, pixels, bytes_per_row); @@ -596,7 +596,7 @@ metal_destroy_shader_ios :: (self: *MetalGPU, handle: u32) { if h64 > self.shaders.len { return; } obj := self.shaders.items[handle - 1]; if obj == null { return; } - msg : (*void, *void) -> void callconv(.c) = xx objc_msgSend; + msg : (*void, *void) -> void abi(.c) = xx objc_msgSend; msg(obj, sel_registerName("release".ptr)); self.shaders.items[handle - 1] = null; } @@ -608,7 +608,7 @@ metal_destroy_buffer_ios :: (self: *MetalGPU, handle: u32) { if h64 > self.buffers.len { return; } obj := self.buffers.items[handle - 1]; if obj == null { return; } - msg : (*void, *void) -> void callconv(.c) = xx objc_msgSend; + msg : (*void, *void) -> void abi(.c) = xx objc_msgSend; msg(obj, sel_registerName("release".ptr)); self.buffers.items[handle - 1] = null; } @@ -620,7 +620,7 @@ metal_destroy_texture_ios :: (self: *MetalGPU, handle: u32) { if h64 > self.textures.len { return; } obj := self.textures.items[handle - 1].tex; if obj == null { return; } - msg : (*void, *void) -> void callconv(.c) = xx objc_msgSend; + msg : (*void, *void) -> void abi(.c) = xx objc_msgSend; msg(obj, sel_registerName("release".ptr)); self.textures.items[handle - 1].tex = null; self.textures.items[handle - 1].bytes_per_pixel = 0; @@ -633,7 +633,7 @@ metal_set_shader_ios :: (self: *MetalGPU, sh: u32) { if self.encoder == null { return; } state := metal_lookup_shader(self, sh); if state == null { return; } - msg : (*void, *void, *void) -> void callconv(.c) = xx objc_msgSend; + msg : (*void, *void, *void) -> void abi(.c) = xx objc_msgSend; msg(self.encoder, sel_registerName("setRenderPipelineState:".ptr), state); } @@ -643,7 +643,7 @@ metal_set_vertex_buffer_ios :: (self: *MetalGPU, h: u32) { buf := metal_lookup_buffer(self, h); if buf == null { return; } // [encoder setVertexBuffer:buf offset:0 atIndex:0] - msg : (*void, *void, *void, u64, u64) -> void callconv(.c) = xx objc_msgSend; + msg : (*void, *void, *void, u64, u64) -> void abi(.c) = xx objc_msgSend; msg(self.encoder, sel_registerName("setVertexBuffer:offset:atIndex:".ptr), buf, 0, 0); } @@ -656,7 +656,7 @@ metal_set_texture_ios :: (self: *MetalGPU, slot: u32, h: u32) { tex := self.textures.items[h - 1].tex; if tex == null { return; } // [encoder setFragmentTexture:tex atIndex:slot] - msg : (*void, *void, *void, u64) -> void callconv(.c) = xx objc_msgSend; + msg : (*void, *void, *void, u64) -> void abi(.c) = xx objc_msgSend; msg(self.encoder, sel_registerName("setFragmentTexture:atIndex:".ptr), tex, xx slot); } @@ -666,7 +666,7 @@ metal_set_vertex_constants_ios :: (self: *MetalGPU, slot: u32, data: *void, size if data == null { return; } if size_bytes <= 0 { return; } // [encoder setVertexBytes:data length:size_bytes atIndex:slot] - msg : (*void, *void, *void, u64, u64) -> void callconv(.c) = xx objc_msgSend; + msg : (*void, *void, *void, u64, u64) -> void abi(.c) = xx objc_msgSend; msg(self.encoder, sel_registerName("setVertexBytes:length:atIndex:".ptr), data, xx size_bytes, xx slot); } @@ -676,7 +676,7 @@ metal_set_scissor_ios :: (self: *MetalGPU, x: i32, y: i32, w: i32, h: i32) { if self.encoder == null { return; } rect : MTLScissorRect = .{ x = xx x, y = xx y, width = xx w, height = xx h }; // [encoder setScissorRect:rect] (MTLScissorRect is 32 bytes → ptr byval) - msg : (*void, *void, MTLScissorRect) -> void callconv(.c) = xx objc_msgSend; + msg : (*void, *void, MTLScissorRect) -> void abi(.c) = xx objc_msgSend; msg(self.encoder, sel_registerName("setScissorRect:".ptr), rect); } @@ -686,7 +686,7 @@ metal_disable_scissor_ios :: (self: *MetalGPU) { // Metal has no "disable scissor" — set the rect to cover the full // drawable so subsequent draws aren't clipped. rect : MTLScissorRect = .{ x = 0, y = 0, width = xx self.pixel_w, height = xx self.pixel_h }; - msg : (*void, *void, MTLScissorRect) -> void callconv(.c) = xx objc_msgSend; + msg : (*void, *void, MTLScissorRect) -> void abi(.c) = xx objc_msgSend; msg(self.encoder, sel_registerName("setScissorRect:".ptr), rect); } @@ -695,7 +695,7 @@ metal_draw_triangles_ios :: (self: *MetalGPU, vertex_offset: i32, vertex_count: if self.encoder == null { return; } if vertex_count <= 0 { return; } // [encoder drawPrimitives:.triangle vertexStart:offset vertexCount:count] - msg : (*void, *void, u64, u64, u64) -> void callconv(.c) = xx objc_msgSend; + msg : (*void, *void, u64, u64, u64) -> void abi(.c) = xx objc_msgSend; msg(self.encoder, sel_registerName("drawPrimitives:vertexStart:vertexCount:".ptr), MTL_PRIMITIVE_TYPE_TRIANGLE, xx vertex_offset, xx vertex_count); } diff --git a/library/modules/platform/android.sx b/library/modules/platform/android.sx index 3963e346..7187b603 100644 --- a/library/modules/platform/android.sx +++ b/library/modules/platform/android.sx @@ -85,7 +85,7 @@ ANativeWindow_setBuffersGeometry :: (w: *void, width: i32, height: i32, fmt: i32 AAssetManager_fromJava :: (env: *void, mgr: *void) -> *void extern; // pthread (link libpthread is built into bionic). -pthread_create :: (thread: *u64, attr: *void, start: (*void) -> *void callconv(.c), arg: *void) -> i32 extern; +pthread_create :: (thread: *u64, attr: *void, start: (*void) -> *void abi(.c), arg: *void) -> i32 extern; pthread_mutex_init :: (m: *void, attr: *void) -> i32 extern; pthread_mutex_lock :: (m: *void) -> i32 extern; pthread_mutex_unlock :: (m: *void) -> i32 extern; @@ -254,7 +254,7 @@ sx_android_start_render_thread :: (plat: *AndroidPlatform, entry_fn: () -> void) plat.render_thread_started = true; } -sx_android_render_thread_entry :: (arg: *void) -> *void callconv(.c) { +sx_android_render_thread_entry :: (arg: *void) -> *void abi(.c) { plat : *AndroidPlatform = xx arg; while plat.app_window == null and !plat.should_stop { usleep(1000); diff --git a/library/modules/platform/sdl3.sx b/library/modules/platform/sdl3.sx index 5dd0631c..c7ead443 100644 --- a/library/modules/platform/sdl3.sx +++ b/library/modules/platform/sdl3.sx @@ -229,7 +229,7 @@ impl Platform for SdlPlatform { // SDL fires the watch synchronously when events are added — including during // macOS's modal resize-drag, when SDL_PollEvent can't run. Re-invoking the // frame closure here keeps content rendering at the new size during the drag. -sdl_event_watch :: (userdata: *void, event: *SDL_Event) -> bool callconv(.c) { +sdl_event_watch :: (userdata: *void, event: *SDL_Event) -> bool abi(.c) { plat : *SdlPlatform = xx userdata; if event.* == { case .window_resized: (data) { @@ -245,7 +245,7 @@ sdl_event_watch :: (userdata: *void, event: *SDL_Event) -> bool callconv(.c) { true } -sdl_wasm_tick :: () callconv(.c) { +sdl_wasm_tick :: () abi(.c) { if g_sdl_plat == null { return; } if !g_sdl_plat.has_frame_closure { return; } fn := g_sdl_plat.frame_closure; diff --git a/library/modules/platform/uikit.sx b/library/modules/platform/uikit.sx index bd1900d6..a4904c3b 100644 --- a/library/modules/platform/uikit.sx +++ b/library/modules/platform/uikit.sx @@ -790,8 +790,8 @@ impl Platform for UIKitPlatform { } // dlsym(RTLD_DEFAULT, name) — Apple platforms. RTLD_DEFAULT is (void*)-2. -// callconv(.c) so this is callable from `load_gl`'s C-conv proc-loader slot. -ios_gl_proc :: (name: [*]u8) -> *void callconv(.c) { +// abi(.c) so this is callable from `load_gl`'s C-conv proc-loader slot. +ios_gl_proc :: (name: [*]u8) -> *void abi(.c) { rtld_default : *void = xx (0 - 2); dlsym(rtld_default, name) } diff --git a/library/modules/std/thread.sx b/library/modules/std/thread.sx index 6752da6d..40e29bf3 100644 --- a/library/modules/std/thread.sx +++ b/library/modules/std/thread.sx @@ -2,7 +2,7 @@ // pthreads (PLAN-HTTPZ S6). // // THE RE-ENTRY CONTRACT (pinned by examples/1636): a thread entry is a -// `callconv(.c)` function — it has NO implicit context — and enters +// `abi(.c)` function — it has NO implicit context — and enters // the sx world by fabricating one: `push Context.{ allocator = xx gpa }` // around the default-conv code it runs. Pool workers do exactly that, // each with its own malloc-backed GPA, so tasks allocate freely and @@ -26,7 +26,7 @@ tlib :: #library "c"; -pthread_create :: (thread: *usize, attr: *void, start: (*void) -> *void callconv(.c), arg: *void) -> i32 extern tlib; +pthread_create :: (thread: *usize, attr: *void, start: (*void) -> *void abi(.c), arg: *void) -> i32 extern tlib; pthread_join :: (thread: usize, retval: **void) -> i32 extern tlib; pthread_detach :: (thread: usize) -> i32 extern tlib; @@ -104,9 +104,9 @@ Cond :: struct { Thread :: struct { handle: usize = 0; - // `entry` is the C->sx boundary: callconv(.c), fabricates its own + // `entry` is the C->sx boundary: abi(.c), fabricates its own // Context before touching default-conv sx code (examples/1636). - spawn :: (entry: (*void) -> *void callconv(.c), arg: *void) -> (Thread, !ThreadErr) { + spawn :: (entry: (*void) -> *void abi(.c), arg: *void) -> (Thread, !ThreadErr) { t : Thread = .{}; if pthread_create(@t.handle, null, entry, arg) != 0 { raise error.Spawn; } return t; @@ -200,7 +200,7 @@ Pool :: struct { // The worker loop: C entry, own fabricated Context, then // pop-task/run-task until stop with an empty queue. -pool_worker :: (arg: *void) -> *void callconv(.c) { +pool_worker :: (arg: *void) -> *void abi(.c) { p : *Pool = xx arg; gpa := GPA.init(); push Context.{ allocator = xx gpa } { diff --git a/readme.md b/readme.md index adb64354..e2368507 100644 --- a/readme.md +++ b/readme.md @@ -427,7 +427,7 @@ write_fd :: (fd: i32, buf: [*]u8, count: u64) -> i64 extern libc "write"; `extern` / `export` are the keyword surface for C linkage. `extern` imports a symbol defined elsewhere; `export` is its dual — define a function in sx and -expose it under the C ABI so C can call back in. Both imply `callconv(.c)` and take +expose it under the C ABI so C can call back in. Both imply `abi(.c)` and take the same optional `[LIB] ["csym"]` rename tail; they also apply to data globals and to Obj-C / JNI runtime-class aggregates (postfix after the `#objc_class(…)` directive). ```sx diff --git a/specs.md b/specs.md index 11e2804b..8bf3ddbe 100644 --- a/specs.md +++ b/specs.md @@ -1194,8 +1194,9 @@ C linkage is expressed with the postfix `extern` (import) and `export` (define + expose) keywords. `extern` declares a symbol defined elsewhere — a C function or data global resolved at link time; `export` is its dual — **define** a symbol in sx and expose it under the C ABI so C (or asm, or another language) can call it. -Both imply `callconv(.c)`, carry external linkage, and suppress the implicit sx -context parameter. They are postfix modifiers, written where `callconv` would go. +Both imply `abi(.c)`, carry external linkage, and suppress the implicit sx +context parameter. They are postfix modifiers, written in the slot after the +`abi(...)` annotation. ```sx // Declare a named library constant diff --git a/src/ast.zig b/src/ast.zig index 9ced623f..520eac1f 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -122,14 +122,26 @@ pub const Root = struct { decls: []const *Node, }; -pub const CallingConvention = enum { default, c }; +/// ABI / calling-convention annotation written as the postfix `abi(.x)` form on a +/// function declaration, function-type literal, or lambda. Subsumes the old +/// `callconv(...)` spelling. +/// - `.default` — no annotation: the ordinary sx-internal convention (implicit +/// context, sx ABI). There is no surface spelling for `.default`; it is the +/// value when `abi(...)` is absent. +/// - `.c` — C ABI / cdecl, no implicit context (what `callconv(.c)` meant). +/// - `.zig` — welded to the real internal Zig type/fn: layout follows the bound +/// Zig type, functions dispatch over the comptime host-call bridge. The +/// `compiler` library (`design/comptime-compiler-api.md`) binds via `abi(.zig)`. +/// - `.pure` — a pure / naked function (inline asm body), no calling-convention +/// prologue/epilogue. +pub const ABI = enum { default, c, zig, pure }; -/// Linkage modifier written in the postfix slot after `callconv(...)`: -/// `name :: (sig) -> Ret [callconv(.x)] [extern | export] [;|{…}];` -/// `extern` = import (external linkage, C ABI, no sx ctx — `extern`'s role); -/// `export` = define + expose (body + external linkage + C ABI + no ctx). -/// Both imply `callconv(.c)`. Variants carry a trailing `_` to dodge the Zig -/// keywords. `.none` = no linkage modifier (the ordinary sx-internal decl). +/// Linkage modifier written in the postfix slot before `abi(...)`: +/// `name :: (sig) -> Ret [extern | export] [abi(.x)] [lib] [;|{…}];` +/// `extern` = import (external linkage, no sx ctx — `extern`'s role); +/// `export` = define + expose (body + external linkage + no ctx). +/// Variants carry a trailing `_` to dodge the Zig keywords. `.none` = no linkage +/// modifier (the ordinary sx-internal decl). pub const ExternExportModifier = enum { none, extern_, export_ }; pub const FnDecl = struct { @@ -139,10 +151,15 @@ pub const FnDecl = struct { body: *Node, type_params: []const StructTypeParam = &.{}, is_arrow: bool = false, - call_conv: CallingConvention = .default, - /// Postfix linkage modifier (`extern`/`export`) written after the - /// `callconv(...)` slot. `.none` for an ordinary sx-internal function. - /// Parsed in Phase 0.1; not consumed by the fn-decl path until Phase 1. + /// ABI / calling-convention annotation (`abi(.c)` / `abi(.zig)` / `abi(.pure)`) + /// in the postfix slot after `extern`/`export`. `.default` = unannotated. + /// `.zig` marks a function bound to the comptime `compiler` library — its + /// signature is welded to the real internal Zig fn and it dispatches over the + /// host-call bridge at comptime (consumed by the binding registry + host-call + /// bridge in later phases). + abi: ABI = .default, + /// Postfix linkage modifier (`extern`/`export`) written before the `abi(...)` + /// slot. `.none` for an ordinary sx-internal function. extern_export: ExternExportModifier = .none, /// Optional library reference + symbol-name override for an `extern`/`export` /// function, the optional library + symbol-name override. Both @@ -510,6 +527,15 @@ pub const StructDecl = struct { using_entries: []const UsingEntry = &.{}, methods: []const *Node = &.{}, // fn_decl nodes for struct methods constants: []const *Node = &.{}, // const_decl nodes for struct-level constants + /// ABI / layout annotation (`struct abi(.zig) extern { … }`). `.default` + /// for an ordinary struct. `.zig` marks a layout-welded binding to the named + /// `compiler` library's real Zig type — its field offsets are taken from the + /// bound Zig type (`@offsetOf`) and asserted equal at compiler-build time. + /// Parsed in Phase 1; consumed by the binding registry + layout engine later. + abi: ABI = .default, + /// The bound library handle for an `abi(.zig) extern ` welded struct + /// (e.g. `compiler`); null for an ordinary struct. + extern_lib: ?[]const u8 = null, /// True when the declared NAME was a backtick raw identifier /// (`` `i2 :: struct { … } ``) — exempt from the reserved-type-name decl /// check. A bare reserved-name decl still errors. @@ -533,7 +559,7 @@ pub const Lambda = struct { return_type: ?*Node, body: *Node, type_params: []const StructTypeParam = &.{}, - call_conv: CallingConvention = .default, + abi: ABI = .default, }; pub const TypeExpr = struct { @@ -805,7 +831,7 @@ pub const FunctionTypeExpr = struct { param_types: []const *Node, param_names: ?[]const ?[]const u8 = null, // optional documentation names return_type: ?*Node, // null = void return - call_conv: CallingConvention = .default, + abi: ABI = .default, }; pub const ClosureTypeExpr = struct { diff --git a/src/backend/llvm/abi.zig b/src/backend/llvm/abi.zig index 71dc09e9..2613b127 100644 --- a/src/backend/llvm/abi.zig +++ b/src/backend/llvm/abi.zig @@ -29,12 +29,12 @@ pub const AbiLowering = struct { /// `is_extern_c_api` knob. When true, sx `string` / `[]T` slices /// collapse to `ptr` — the libc convention where the user writes /// `string` to mean `char *` and the length is dropped. When - /// false (sx-internal `callconv(.c)` like block trampolines), the + /// false (sx-internal `abi(.c)` like block trampolines), the /// full slice shape is preserved and goes through the general /// struct-coerce path (16-byte slice → `[2 x i64]`, lands in two /// registers on AArch64 — the true C ABI for a 16-byte /// aggregate). Without the split, sx-to-sx calls through a - /// `(*Block, string) -> void callconv(.c)` fn-pointer mismatched + /// `(*Block, string) -> void abi(.c)` fn-pointer mismatched /// the caller's `{ptr, i64}` value against the trampoline's /// collapsed `ptr` param. pub fn abiCoerceParamTypeEx(self: AbiLowering, ir_ty: TypeId, llvm_ty: c.LLVMTypeRef, is_extern_c_api: bool) c.LLVMTypeRef { diff --git a/src/backend/llvm/ops.zig b/src/backend/llvm/ops.zig index 85eeb1e8..bfbdb787 100644 --- a/src/backend/llvm/ops.zig +++ b/src/backend/llvm/ops.zig @@ -1118,6 +1118,23 @@ pub const Ops = struct { pub fn emitCall(self: Ops, instruction: *const Inst, call_op: Call) void { // Evaluate comptime functions at compile time const callee_func = &self.e.ir_mod.functions.items[call_op.callee.index()]; + + // Welded `compiler`-library functions are comptime-only — they have no + // runtime symbol (the comptime interp dispatches them to a Zig handler). + // A welded call inside a RUNTIME function is illegal; surface a clean + // build-gating error instead of an undefined-symbol link failure. A + // welded call inside a COMPTIME function (a `#run` / `::` initializer + // wrapper, `is_comptime`) is fine — that body is interp-evaluated and its + // LLVM emission is dead, so skip the gate there. + const enclosing = &self.e.ir_mod.functions.items[self.e.current_func_idx]; + if (callee_func.compiler_welded and !enclosing.is_comptime) { + const fname = self.e.ir_mod.types.getString(callee_func.name); + std.debug.print("error: '{s}' is a comptime-only compiler-library function — it cannot be called at runtime (use it inside #run or a comptime '::')\n", .{fname}); + self.e.comptime_failed = true; + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + return; + } + if (callee_func.is_comptime and call_op.args.len == 0) { var interp_inst = Interpreter.init(self.e.ir_mod, self.e.alloc); interp_inst.build_config = &self.e.build_config; diff --git a/src/ir/compiler_lib.test.zig b/src/ir/compiler_lib.test.zig new file mode 100644 index 00000000..26ed194d --- /dev/null +++ b/src/ir/compiler_lib.test.zig @@ -0,0 +1,177 @@ +// Tests for the comptime `compiler` library's binding registry. + +const std = @import("std"); +const compiler_lib = @import("compiler_lib.zig"); +const types = @import("types.zig"); + +// Lock: `findType("Field")` resolves to the welded `StructInfo.Field` type, and +// its baked layout EQUALS the real Zig type's `@sizeOf`/`@alignOf`/`@offsetOf`. +// This is the foundation the layout sub-step builds on — the welded record's +// offsets come from the implementation, so they can't drift. +test "compiler_lib: Field welds to StructInfo.Field's real layout" { + const FieldZig = types.TypeInfo.StructInfo.Field; + + const bt = compiler_lib.findType("Field") orelse return error.MissingBoundType; + + try std.testing.expectEqualStrings("Field", bt.sx_name); + try std.testing.expectEqual(@sizeOf(FieldZig), bt.size); + try std.testing.expectEqual(@alignOf(FieldZig), bt.alignment); + + // Two u32 fields, in declaration order. + try std.testing.expectEqual(@as(usize, 2), bt.fields.len); + + try std.testing.expectEqualStrings("name", bt.fields[0].name); + try std.testing.expectEqual(@offsetOf(FieldZig, "name"), bt.fields[0].offset); + try std.testing.expectEqual(@as(usize, 4), bt.fields[0].size); + + try std.testing.expectEqualStrings("ty", bt.fields[1].name); + try std.testing.expectEqual(@offsetOf(FieldZig, "ty"), bt.fields[1].offset); + try std.testing.expectEqual(@as(usize, 4), bt.fields[1].size); + + // Sanity: the concrete shape the design calls out — two u32s, 8 bytes. + try std.testing.expectEqual(@as(usize, 8), bt.size); + try std.testing.expectEqual(@as(usize, 0), bt.fields[0].offset); + try std.testing.expectEqual(@as(usize, 4), bt.fields[1].offset); +} + +// Lock: a name NOT on the export list is unreachable — `findType` returns null +// (the safety boundary; the welded-decl path falls through to a clean error, +// never a silent default). +test "compiler_lib: unexported name returns null" { + try std.testing.expect(compiler_lib.findType("NotExported") == null); + try std.testing.expect(compiler_lib.findType("") == null); +} + +// Lock: a faithful sx header for `Field` validates clean (the natural two-u32 +// layout matches the welded type). +test "compiler_lib: validateStructLayout accepts a faithful Field header" { + const bt = compiler_lib.findType("Field").?; + const sx = [_]compiler_lib.SxField{ + .{ .name = "name", .size = 4 }, + .{ .name = "ty", .size = 4 }, + }; + try std.testing.expect(compiler_lib.validateStructLayout(bt, &sx, 8) == null); +} + +// Lock: every drift the assertion is meant to catch surfaces as the right +// `LayoutMismatch` variant (field count / name / size / total), and the first +// mismatch wins. +test "compiler_lib: validateStructLayout flags each kind of drift" { + const bt = compiler_lib.findType("Field").?; + + // Wrong field count (one field instead of two). + { + const sx = [_]compiler_lib.SxField{.{ .name = "name", .size = 4 }}; + const m = compiler_lib.validateStructLayout(bt, &sx, 4).?; + try std.testing.expect(m == .field_count); + try std.testing.expectEqual(@as(usize, 2), m.field_count.expected); + try std.testing.expectEqual(@as(usize, 1), m.field_count.got); + } + // Wrong field name (reorder / rename) at index 1. + { + const sx = [_]compiler_lib.SxField{ + .{ .name = "name", .size = 4 }, + .{ .name = "kind", .size = 4 }, + }; + const m = compiler_lib.validateStructLayout(bt, &sx, 8).?; + try std.testing.expect(m == .field_name); + try std.testing.expectEqual(@as(usize, 1), m.field_name.index); + try std.testing.expectEqualStrings("ty", m.field_name.expected); + try std.testing.expectEqualStrings("kind", m.field_name.got); + } + // Wrong field size (retype to an 8-byte field). + { + const sx = [_]compiler_lib.SxField{ + .{ .name = "name", .size = 4 }, + .{ .name = "ty", .size = 8 }, + }; + const m = compiler_lib.validateStructLayout(bt, &sx, 12).?; + try std.testing.expect(m == .field_size); + try std.testing.expectEqual(@as(usize, 1), m.field_size.index); + try std.testing.expectEqual(@as(usize, 4), m.field_size.expected); + try std.testing.expectEqual(@as(usize, 8), m.field_size.got); + } + // Right fields, wrong total (padding drift). + { + const sx = [_]compiler_lib.SxField{ + .{ .name = "name", .size = 4 }, + .{ .name = "ty", .size = 4 }, + }; + const m = compiler_lib.validateStructLayout(bt, &sx, 16).?; + try std.testing.expect(m == .total_size); + try std.testing.expectEqual(@as(usize, 8), m.total_size.expected); + try std.testing.expectEqual(@as(usize, 16), m.total_size.got); + } +} + +// Lock: `Field` (natural two-u32 layout) has the trivial weld plan — two field +// elements in declaration order, no padding, identity remap. +test "compiler_lib: weld plan for Field is the identity (no reorder, no pad)" { + const alloc = std.testing.allocator; + const bt = compiler_lib.findType("Field").?; + var plan = try compiler_lib.computeWeldPlan(alloc, bt.fields, bt.size); + defer plan.deinit(alloc); + + try std.testing.expectEqual(@as(usize, 8), plan.total_size); + try std.testing.expectEqual(@as(usize, 2), plan.elements.len); + // Identity remap. + try std.testing.expectEqual(@as(usize, 0), plan.sx_to_llvm[0]); + try std.testing.expectEqual(@as(usize, 1), plan.sx_to_llvm[1]); + // Both elements are real fields at 0 and 4. + try std.testing.expectEqual(@as(?usize, 0), plan.elements[0].sx_field); + try std.testing.expectEqual(@as(usize, 0), plan.elements[0].offset); + try std.testing.expectEqual(@as(?usize, 1), plan.elements[1].sx_field); + try std.testing.expectEqual(@as(usize, 4), plan.elements[1].offset); +} + +// Lock: `StructInfo` is the first NON-natural layout — Zig reorders it to +// (fields@0, name@16, nominal_id@20, is_protocol@24) with a 7-byte alignment +// tail. The plan must reproduce exactly that order + the sx→element remap, so the +// LLVM type built from it is byte-identical to the Zig type. sx declaration order +// is (name, fields, is_protocol, nominal_id) = sx indices 0,1,2,3. +test "compiler_lib: weld plan for StructInfo reorders + pads to the Zig layout" { + const alloc = std.testing.allocator; + const bt = compiler_lib.findType("StructInfo").?; + var plan = try compiler_lib.computeWeldPlan(alloc, bt.fields, bt.size); + defer plan.deinit(alloc); + + try std.testing.expectEqual(@as(usize, 32), plan.total_size); + + // Elements in ascending offset order: fields, name, nominal_id, is_protocol, + // then a trailing 7-byte pad (25 → 32). + try std.testing.expectEqual(@as(usize, 5), plan.elements.len); + + // elem 0: fields (sx index 1) @ 0, size 16 + try std.testing.expectEqual(@as(?usize, 1), plan.elements[0].sx_field); + try std.testing.expectEqual(@as(usize, 0), plan.elements[0].offset); + try std.testing.expectEqual(@as(usize, 16), plan.elements[0].size); + // elem 1: name (sx index 0) @ 16, size 4 + try std.testing.expectEqual(@as(?usize, 0), plan.elements[1].sx_field); + try std.testing.expectEqual(@as(usize, 16), plan.elements[1].offset); + // elem 2: nominal_id (sx index 3) @ 20, size 4 + try std.testing.expectEqual(@as(?usize, 3), plan.elements[2].sx_field); + try std.testing.expectEqual(@as(usize, 20), plan.elements[2].offset); + // elem 3: is_protocol (sx index 2) @ 24, size 1 + try std.testing.expectEqual(@as(?usize, 2), plan.elements[3].sx_field); + try std.testing.expectEqual(@as(usize, 24), plan.elements[3].offset); + // elem 4: trailing pad @ 25, size 7 + try std.testing.expectEqual(@as(?usize, null), plan.elements[4].sx_field); + try std.testing.expectEqual(@as(usize, 25), plan.elements[4].offset); + try std.testing.expectEqual(@as(usize, 7), plan.elements[4].size); + + // sx → element remap: name→1, fields→0, is_protocol→3, nominal_id→2. + try std.testing.expectEqual(@as(usize, 1), plan.sx_to_llvm[0]); + try std.testing.expectEqual(@as(usize, 0), plan.sx_to_llvm[1]); + try std.testing.expectEqual(@as(usize, 3), plan.sx_to_llvm[2]); + try std.testing.expectEqual(@as(usize, 2), plan.sx_to_llvm[3]); +} + +// Lock: the welded-function export list resolves the round-trip readers and +// rejects unexported names (the boundary the interp's dispatch consults). +test "compiler_lib: findFn resolves exported functions, rejects others" { + try std.testing.expect(compiler_lib.findFn("intern") != null); + try std.testing.expect(compiler_lib.findFn("text_of") != null); + try std.testing.expectEqualStrings("intern", compiler_lib.findFn("intern").?.sx_name); + try std.testing.expect(compiler_lib.findFn("not_exported") == null); + try std.testing.expect(compiler_lib.findFn("") == null); +} diff --git a/src/ir/compiler_lib.zig b/src/ir/compiler_lib.zig new file mode 100644 index 00000000..e59c990b --- /dev/null +++ b/src/ir/compiler_lib.zig @@ -0,0 +1,291 @@ +//! The comptime `compiler` library's binding registry — the curated surface of +//! the compiler's own types (layout-welded) and functions (host-call bridged) +//! reachable from comptime sx via `abi(.zig) extern compiler`. See +//! `design/comptime-compiler-api.md`. +//! +//! **This registry IS the safety boundary.** Only the entries registered here +//! are bindable from user comptime code; anything not on the export list is +//! unreachable. A welded `Name :: struct abi(.zig) extern compiler { … }` (or a +//! welded fn) resolves its layout/dispatch against this table, not the ordinary +//! extern-lib path. +//! +//! **Layout is welded, not guessed.** Because the sx compiler is itself a Zig +//! program, the real internal type's layout is available at compiler-build time: +//! each `BoundType` bakes `@sizeOf`/`@alignOf`/`@offsetOf` from the bound Zig +//! type. A `types.zig` change re-bakes the offsets on the next build, so both +//! sides move together. The sx-side `struct abi(.zig) …` declaration is then a +//! *header* checked against these offsets (the build-time layout-equality +//! assertion lands in the layout sub-step). + +const std = @import("std"); +const types = @import("types.zig"); +const interp_mod = @import("interp.zig"); +const Value = interp_mod.Value; +const Interpreter = interp_mod.Interpreter; +const InterpError = interp_mod.InterpError; +const StringId = types.StringId; + +/// One field of a welded type: its sx-visible name plus the byte offset + size +/// taken from the bound Zig type. +pub const FieldLayout = struct { + name: []const u8, + offset: usize, + size: usize, +}; + +/// A type exported by the `compiler` library, welded to a real internal Zig +/// type. `size`/`alignment`/`fields` are baked from that Zig type at +/// compiler-build time (so they cannot drift from the implementation). +pub const BoundType = struct { + /// The sx-side name a welded `struct abi(.zig) extern compiler` uses. + sx_name: []const u8, + size: usize, + alignment: usize, + fields: []const FieldLayout, +}; + +/// The real internal Zig type each welded export binds to. Kept as named +/// aliases so the binding sites read as a curated list. +const FieldZig = types.TypeInfo.StructInfo.Field; // { name: StringId, ty: TypeId } — two u32s +const StructInfoZig = types.TypeInfo.StructInfo; // { name, fields: []Field, is_protocol, nominal_id } — Zig-reordered + +/// Bake a `BoundType` from a real Zig struct type `T`. Field offsets/sizes come +/// from `@offsetOf`/`@sizeOf` on `T`; `sx_field_names` supplies the sx-visible +/// names positionally (must match `T`'s field order and count — a mismatch is a +/// compile error, never a silent truncation). +fn weldStruct( + comptime sx_name: []const u8, + comptime T: type, + comptime sx_field_names: []const []const u8, +) BoundType { + const zig_fields = @typeInfo(T).@"struct".fields; + if (zig_fields.len != sx_field_names.len) + @compileError("compiler-lib weld '" ++ sx_name ++ "': sx field count != Zig field count"); + comptime var layouts: [zig_fields.len]FieldLayout = undefined; + inline for (zig_fields, 0..) |zf, i| { + layouts[i] = .{ + .name = sx_field_names[i], + .offset = @offsetOf(T, zf.name), + .size = @sizeOf(zf.type), + }; + } + const frozen = layouts; + return .{ + .sx_name = sx_name, + .size = @sizeOf(T), + .alignment = @alignOf(T), + .fields = &frozen, + }; +} + +/// The welded-type export list. `Field` (two u32s, natural layout) proved the +/// weld in Phase 1; `StructInfo` (Phase 2) is the first NON-natural layout — +/// Zig reorders its fields (`fields`@0, `name`@16, `nominal_id`@20, +/// `is_protocol`@24), so it exercises the offset-override engine. `EnumInfo` / +/// `TaggedUnionInfo` / `TupleInfo` join later. +pub const bound_types = [_]BoundType{ + weldStruct("Field", FieldZig, &.{ "name", "ty" }), + weldStruct("StructInfo", StructInfoZig, &.{ "name", "fields", "is_protocol", "nominal_id" }), +}; + +/// Look up a welded type by its sx name. Returns null when the name is not on +/// the `compiler` library's export list (the lookup the welded-decl resolution +/// path consults instead of the ordinary extern-lib path). +pub fn findType(sx_name: []const u8) ?*const BoundType { + for (&bound_types) |*bt| { + if (std.mem.eql(u8, bt.sx_name, sx_name)) return bt; + } + return null; +} + +/// The name of the only welded library. A `struct abi(.zig) extern ` with a +/// different `` is rejected — `compiler` is the sole comptime weld source. +pub const lib_name = "compiler"; + +/// One field of an sx welded-struct declaration, as the lowering observed it: +/// the field's sx name plus the size the sx type system computed for its type. +pub const SxField = struct { + name: []const u8, + size: usize, +}; + +/// The first way an sx welded-struct declaration fails to faithfully mirror the +/// bound Zig type. The sx declaration is a *header* checked against the real +/// implementation, so any drift is a build error rather than a silent +/// reinterpretation. The caller renders the chosen variant into a diagnostic. +pub const LayoutMismatch = union(enum) { + /// The sx declaration has a different field count than the welded type. + field_count: struct { expected: usize, got: usize }, + /// Field `index` carries the wrong sx name (a weld is positional + by-name). + field_name: struct { index: usize, expected: []const u8, got: []const u8 }, + /// Field `index` (`name`) is a different size than the welded type's field. + field_size: struct { index: usize, name: []const u8, expected: usize, got: usize }, + /// The total struct size differs (padding / alignment drift). + total_size: struct { expected: usize, got: usize }, +}; + +/// Check an sx welded-struct declaration against the bound Zig type. Returns the +/// FIRST mismatch, or null if the sx declaration is a faithful header. Fields are +/// checked positionally + by name + by size, and the total size is compared — for +/// a natural (C-like) layout this catches a missing/extra field (count), a rename +/// or reorder (name), a retype (size), and padding drift (total). Explicit +/// per-field OFFSET overrides (for non-natural Zig layouts — slices, reordered or +/// `union(enum)` fields) arrive with `StructInfo` in Phase 2; `Field`'s two-u32 +/// natural layout needs none. +pub fn validateStructLayout( + bt: *const BoundType, + sx_fields: []const SxField, + sx_total_size: usize, +) ?LayoutMismatch { + if (sx_fields.len != bt.fields.len) + return .{ .field_count = .{ .expected = bt.fields.len, .got = sx_fields.len } }; + for (sx_fields, bt.fields, 0..) |sf, bf, i| { + if (!std.mem.eql(u8, sf.name, bf.name)) + return .{ .field_name = .{ .index = i, .expected = bf.name, .got = sf.name } }; + if (sf.size != bf.size) + return .{ .field_size = .{ .index = i, .name = bf.name, .expected = bf.size, .got = sf.size } }; + } + if (sx_total_size != bt.size) + return .{ .total_size = .{ .expected = bt.size, .got = sx_total_size } }; + return null; +} + +// ── Weld plan (byte-layout override) ──────────────────────────────────────── +// +// A welded struct must be laid out byte-identically to the bound Zig type, whose +// fields Zig may REORDER (and pad). The sx struct's natural layout generally +// won't match — so the compiler imposes the Zig layout: it builds the struct's +// LLVM type as the fields in ascending-OFFSET order, with explicit padding +// elements filling the gaps, and remaps each sx field index to its LLVM element +// index. `computeWeldPlan` is that pure layout math; the LLVM type builder + the +// struct-GEP / field-access sites consume the plan (later sub-steps), and the +// interp serializes comptime struct Values through the same offsets. + +/// One element of a welded struct's LLVM layout: either a real field (carrying +/// its sx field index) or a padding gap. Always in ascending `offset` order. +pub const WeldElement = struct { + /// The sx field index this element holds, or null for a padding gap. + sx_field: ?usize, + /// Byte offset of this element within the struct (the bound Zig offset). + offset: usize, + /// Byte width of this element (the field's size, or the gap width). + size: usize, +}; + +/// The byte-layout plan for a welded struct: its LLVM elements in offset order +/// (fields + padding) and the sx-field → LLVM-element-index remap. Owns its +/// slices — `deinit` with the same allocator passed to `computeWeldPlan`. +pub const WeldPlan = struct { + elements: []const WeldElement, + /// `sx_to_llvm[i]` is the index into `elements` of sx field `i`. + sx_to_llvm: []const usize, + total_size: usize, + + pub fn deinit(self: *WeldPlan, alloc: std.mem.Allocator) void { + alloc.free(self.elements); + alloc.free(self.sx_to_llvm); + } +}; + +/// Compute the byte-layout plan for a struct whose fields carry their bound Zig +/// offsets (`fields[i].offset`/`.size`, e.g. from a `BoundType`). `total_size` is +/// the bound Zig `@sizeOf`. The result lists LLVM elements in ascending-offset +/// order — real fields interleaved with padding gaps — plus the sx-field → +/// element-index remap that struct-GEP uses. Pure; allocates the result slices. +pub fn computeWeldPlan( + alloc: std.mem.Allocator, + fields: []const FieldLayout, + total_size: usize, +) !WeldPlan { + // Order the sx field indices by ascending byte offset (stable). + const order = try alloc.alloc(usize, fields.len); + defer alloc.free(order); + for (order, 0..) |*o, i| o.* = i; + std.sort.insertion(usize, order, fields, struct { + fn lessThan(fs: []const FieldLayout, a: usize, b: usize) bool { + return fs[a].offset < fs[b].offset; + } + }.lessThan); + + var elements = std.ArrayList(WeldElement).empty; + errdefer elements.deinit(alloc); + const sx_to_llvm = try alloc.alloc(usize, fields.len); + errdefer alloc.free(sx_to_llvm); + + var cursor: usize = 0; + for (order) |sx_i| { + const f = fields[sx_i]; + // Fill any gap before this field with a padding element. + if (f.offset > cursor) { + try elements.append(alloc, .{ .sx_field = null, .offset = cursor, .size = f.offset - cursor }); + } + sx_to_llvm[sx_i] = elements.items.len; + try elements.append(alloc, .{ .sx_field = sx_i, .offset = f.offset, .size = f.size }); + cursor = f.offset + f.size; + } + // Trailing padding up to the bound total size (alignment tail). + if (total_size > cursor) { + try elements.append(alloc, .{ .sx_field = null, .offset = cursor, .size = total_size - cursor }); + } + + return .{ + .elements = try elements.toOwnedSlice(alloc), + .sx_to_llvm = sx_to_llvm, + .total_size = total_size, + }; +} + +// ── Functions (comptime-only, host-call bridged) ──────────────────────────── + +/// A welded `compiler` function: dispatched under the comptime interpreter to its +/// Zig handler (never dlsym'd). The handler receives the interpreter (for the +/// string pool / type table) and the already-evaluated argument `Value`s, and +/// returns the result `Value`. +pub const FnHandler = *const fn (interp: *Interpreter, args: []const Value) InterpError!Value; + +pub const BoundFn = struct { + sx_name: []const u8, + handler: FnHandler, +}; + +/// The welded-function export list. Start small (Phase 1): the `StringId` +/// round-trip readers. `find_type` / the guarded `register_*` mutators join in +/// later phases. +pub const bound_fns = [_]BoundFn{ + .{ .sx_name = "intern", .handler = handleIntern }, + .{ .sx_name = "text_of", .handler = handleTextOf }, +}; + +/// Look up a welded function by its sx name. Returns null when the name is not on +/// the `compiler` library's function-export list. +pub fn findFn(sx_name: []const u8) ?*const BoundFn { + for (&bound_fns) |*bf| { + if (std.mem.eql(u8, bf.sx_name, sx_name)) return bf; + } + return null; +} + +/// The comptime type table to intern into: the host's mutable mint target when +/// set (the metatype-construction path), else the module's table reached through +/// a const-cast — the same access the interp's mint path uses (interp.zig). The +/// underlying table is genuinely mutable; the interp merely holds it `const`. +fn mintTable(interp: *Interpreter) *types.TypeTable { + return interp.mint orelse @constCast(&interp.module.types); +} + +/// `intern(s: string) -> StringId` — intern `s` into the compiler's string pool +/// and return its handle. The inverse of `text_of`. +fn handleIntern(interp: *Interpreter, args: []const Value) InterpError!Value { + if (args.len != 1 or args[0] != .string) return error.TypeError; + const id = mintTable(interp).internString(args[0].string); + return Value{ .int = @intFromEnum(id) }; +} + +/// `text_of(id: StringId) -> string` — resolve a string handle back to its text. +/// The inverse of `intern`. +fn handleTextOf(interp: *Interpreter, args: []const Value) InterpError!Value { + if (args.len != 1 or args[0] != .int) return error.TypeError; + if (args[0].int < 0 or args[0].int > std.math.maxInt(u32)) return error.TypeError; + const id: StringId = @enumFromInt(@as(u32, @intCast(args[0].int))); + return Value{ .string = interp.module.types.getString(id) }; +} diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 75b695c0..d447e7a3 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -1273,7 +1273,7 @@ pub const LLVMEmitter = struct { else if (needs_c_abi) self.abiCoerceParamTypeEx(func.ret, raw_ret_ty, func.is_extern) else raw_ret_ty; - // Build parameter types — apply C ABI coercion for extern/callconv(.c) functions. + // Build parameter types — apply C ABI coercion for extern/abi(.c) functions. // When uses_sret, prepend the sret pointer at index 0. const sret_offset: usize = if (uses_sret) 1 else 0; const param_count: c_uint = @intCast(func.params.len + sret_offset); diff --git a/src/ir/inst.zig b/src/ir/inst.zig index 407a964e..2e259bf2 100644 --- a/src/ir/inst.zig +++ b/src/ir/inst.zig @@ -575,8 +575,14 @@ pub const Function = struct { /// parameter that every default-conv sx function receives. Callers /// read this flag to decide whether to prepend their current /// `__sx_ctx` value to the args of a call. Extern decls and - /// `callconv(.c)` functions have it false. + /// `abi(.c)` functions have it false. has_implicit_ctx: bool = false, + /// True for a `fn abi(.zig) extern compiler` welded to the comptime + /// `compiler` library. Such a function has no real symbol — the comptime + /// interpreter dispatches it to its registered Zig handler + /// (`compiler_lib.findFn`) instead of dlsym. Comptime-only; a runtime call + /// has no backing symbol. See design/comptime-compiler-api.md. + compiler_welded: bool = false, pub const Param = struct { name: StringId, diff --git a/src/ir/interp.zig b/src/ir/interp.zig index 347a4285..a07bd4c5 100644 --- a/src/ir/interp.zig +++ b/src/ir/interp.zig @@ -162,6 +162,7 @@ pub const InterpError = error{ const compiler_hooks = @import("compiler_hooks.zig"); pub const BuildConfig = compiler_hooks.BuildConfig; +const compiler_lib = @import("compiler_lib.zig"); const host_ffi = @import("host_ffi.zig"); // ── Interpreter ───────────────────────────────────────────────────────── @@ -579,6 +580,15 @@ pub const Interpreter = struct { defer self.call_depth -= 1; const func = self.module.getFunction(func_id); + // Welded `compiler`-library function: dispatch to its registered Zig + // handler (comptime-only), never dlsym. The binding registry IS the + // safety boundary — a name not on the export list is a clean bail. + if (func.compiler_welded) { + const fname = self.module.types.getString(func.name); + const bf = compiler_lib.findFn(fname) orelse + return bailDetail("comptime compiler call: function not exported by the compiler library"); + return bf.handler(self, args); + } if (func.is_extern or func.blocks.items.len == 0) { // Dispatch to host libc via dlsym. Lets `#run` (and the // post-link bundler) call ordinary extern symbols like diff --git a/src/ir/ir.zig b/src/ir/ir.zig index fd39d883..c1241535 100644 --- a/src/ir/ir.zig +++ b/src/ir/ir.zig @@ -60,6 +60,7 @@ pub const ObjcLowering = ffi_objc.ObjcLowering; pub const ErrorFacts = error_analysis.ErrorFacts; pub const compiler_hooks = @import("compiler_hooks.zig"); +pub const compiler_lib = @import("compiler_lib.zig"); pub const emit_llvm = @import("emit_llvm.zig"); pub const LLVMEmitter = emit_llvm.LLVMEmitter; @@ -89,6 +90,7 @@ pub const type_bridge_tests = @import("type_bridge.test.zig"); pub const emit_llvm_tests = @import("emit_llvm.test.zig"); pub const jni_descriptor_tests = @import("jni_descriptor.test.zig"); pub const jni_java_emit_tests = @import("jni_java_emit.test.zig"); +pub const compiler_lib_tests = @import("compiler_lib.test.zig"); test { @import("std").testing.refAllDecls(@This()); diff --git a/src/ir/lower/closure.zig b/src/ir/lower/closure.zig index e79dea25..cc90b8f4 100644 --- a/src/ir/lower/closure.zig +++ b/src/ir/lower/closure.zig @@ -78,7 +78,7 @@ pub fn lowerLambda(self: *Lowering, lam: *const ast.Lambda) Ref { // Without implicit_ctx, env is slot 0 and user params follow. var params = std.ArrayList(Function.Param).empty; const env_ptr_ty = self.module.types.ptrTo(.void); - const lambda_wants_ctx = self.implicit_ctx_enabled and lam.call_conv != .c; + const lambda_wants_ctx = self.implicit_ctx_enabled and lam.abi != .c; if (lambda_wants_ctx) { params.append(self.alloc, .{ .name = self.module.types.internString("__sx_ctx"), @@ -168,7 +168,7 @@ pub fn lowerLambda(self: *Lowering, lam: *const ast.Lambda) Ref { }; const name_id = self.module.types.internString(name); const func_id = self.builder.beginFunction(name_id, params.items, ret_ty); - if (lam.call_conv == .c) { + if (lam.abi == .c) { self.module.getFunctionMut(func_id).call_conv = .c; } self.builder.currentFunc().has_implicit_ctx = lambda_wants_ctx; diff --git a/src/ir/lower/decl.zig b/src/ir/lower/decl.zig index 3a4198d2..5f64f765 100644 --- a/src/ir/lower/decl.zig +++ b/src/ir/lower/decl.zig @@ -10,6 +10,7 @@ const unescape = @import("../../unescape.zig"); const errors = @import("../../errors.zig"); const program_index_mod = @import("../program_index.zig"); const resolver_mod = @import("../resolver.zig"); +const compiler_lib = @import("../compiler_lib.zig"); const ProgramIndex = program_index_mod.ProgramIndex; const GlobalInfo = program_index_mod.GlobalInfo; const ModuleConstInfo = program_index_mod.ModuleConstInfo; @@ -497,7 +498,7 @@ pub fn detectContextDecl(decls: []const *const Node) bool { /// `__sx__impl` with the ctx param) lands in Step 4 proper. pub fn funcWantsImplicitCtx(self: *const Lowering, fd: *const ast.FnDecl) bool { if (!self.implicit_ctx_enabled) return false; - if (fd.call_conv == .c) return false; + if (fd.abi == .c) return false; // `extern` imports and `export` defines are external C symbols — // C ABI, no sx context (Phase 2, gap iv). if (fd.extern_export != .none) return false; @@ -2256,11 +2257,11 @@ pub fn declareFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8) } // `extern` declarations are external C symbols by definition — promote - // them to callconv(.c) when the user didn't write it explicitly. This keeps + // them to abi(.c) when the user didn't write it explicitly. This keeps // fn-ptr coercion type-safe: anything typed by name as `(args) -> ret` of an - // `extern` decl can be assigned to / passed as a `callconv(.c)` fn-pointer + // `extern` decl can be assigned to / passed as a `abi(.c)` fn-pointer // without a call-convention mismatch. - const cc: Function.CallingConvention = if (fd.call_conv == .c or is_extern_decl or fd.extern_export == .export_) .c else .default; + const cc: Function.CallingConvention = if (fd.abi == .c or is_extern_decl or fd.extern_export == .export_) .c else .default; // Symbol-name override: `extern … "csym"` / `export … "csym"` (fd.extern_name). // Declare under the C name and map the sx name → C name so call sites resolve @@ -2296,9 +2297,29 @@ pub fn declareFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8) func.source_file = self.current_source_file; func.is_variadic = is_variadic; func.has_implicit_ctx = wants_ctx; + if (weldedCompilerFn(self, fd, name)) func.compiler_welded = true; self.fn_decl_fids.put(fd, fid) catch {}; } +/// A `fn abi(.zig) extern ` binds the comptime `compiler` library. Validate +/// it (the bound lib must be `compiler`; the name must be on the function-export +/// list) and return whether it is a welded compiler function — the interpreter +/// dispatches such a call to its registered Zig handler instead of dlsym. Any +/// failure is a build-gating `.err` (never a silent fall-through to dlsym). +fn weldedCompilerFn(self: *Lowering, fd: *const ast.FnDecl, name: []const u8) bool { + if (fd.abi != .zig) return false; + const diags = self.diagnostics; + if (fd.extern_lib == null or !std.mem.eql(u8, fd.extern_lib.?, compiler_lib.lib_name)) { + if (diags) |d| d.addFmt(.err, fd.name_span, "abi(.zig) function '{s}' must bind the compiler library — write `extern {s}`", .{ name, compiler_lib.lib_name }); + return false; + } + if (compiler_lib.findFn(name) == null) { + if (diags) |d| d.addFmt(.err, fd.name_span, "'{s}' is not a function exported by the '{s}' library", .{ name, compiler_lib.lib_name }); + return false; + } + return true; +} + /// Register a namespaced import's OWN functions under their module-qualified /// name (`ns.fn`), giving each a UNIQUE FuncId in the function table. Two /// modules each exporting a top-level `parse` otherwise collide in the @@ -2553,7 +2574,7 @@ pub fn lowerFunctionBodyInto(self: *Lowering, fd: *const ast.FnDecl, fid: FuncId func.is_extern = false; // promote from extern stub to real function // `export` defines force external linkage + C ABI (Phase 2, gaps i+ii). func.linkage = if (isExportedEntryName(name) or fd.extern_export == .export_) .external else .internal; - if (fd.call_conv == .c or fd.extern_export == .export_) func.call_conv = .c; + if (fd.abi == .c or fd.extern_export == .export_) func.call_conv = .c; // Set inst_counter to param count (params occupy refs 0..N-1). IR params // = AST params + 1 if the function carries `__sx_ctx` at slot 0. const ctx_slots: usize = if (func.has_implicit_ctx) 1 else 0; @@ -2589,7 +2610,7 @@ pub fn lowerFunctionBodyInto(self: *Lowering, fd: *const ast.FnDecl, fid: FuncId scope.put(p.name, .{ .ref = slot, .ty = pty, .is_alloca = true }); } - // Inbound entry points + callconv(.c) sx functions: bind current_ctx_ref + // Inbound entry points + abi(.c) sx functions: bind current_ctx_ref // to the static default before any user code runs. if (!wants_ctx and self.implicit_ctx_enabled) { if (self.program_index.global_names.get("__sx_default_context")) |dctx_gi| { @@ -2695,7 +2716,7 @@ pub fn lowerFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8, i } // Set calling convention. `export` defines promote to C ABI (gap ii). - if (fd.call_conv == .c or fd.extern_export == .export_) { + if (fd.abi == .c or fd.extern_export == .export_) { self.builder.currentFunc().call_conv = .c; } @@ -2729,7 +2750,7 @@ pub fn lowerFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8, i scope.put(p.name, .{ .ref = slot, .ty = pty, .is_alloca = true }); } - // Inbound entry points + callconv(.c) sx functions: bind + // Inbound entry points + abi(.c) sx functions: bind // current_ctx_ref to &__sx_default_context. See companion comment // in `lowerFunction` for the same case. if (!wants_ctx_lf and self.implicit_ctx_enabled) { diff --git a/src/ir/lower/expr.zig b/src/ir/lower/expr.zig index 94aa021b..7b2aaa93 100644 --- a/src/ir/lower/expr.zig +++ b/src/ir/lower/expr.zig @@ -1894,7 +1894,7 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref { // Coercing a bare fn name to a fn-pointer // type — the call_conv must match. A // default-conv sx fn assigned to a - // callconv(.c) slot (e.g. passed to + // abi(.c) slot (e.g. passed to // pthread_create) would otherwise crash at // runtime when the C caller doesn't supply // the implicit __sx_ctx arg. @@ -1902,8 +1902,8 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref { const func_cc = self.module.functions.items[@intFromEnum(fid)].call_conv; if (func_cc != tt_info.function.call_conv) { if (self.diagnostics) |d| { - const want_cc = if (tt_info.function.call_conv == .c) "callconv(.c)" else "default sx convention"; - const have_cc = if (func_cc == .c) "callconv(.c)" else "default sx convention"; + const want_cc = if (tt_info.function.call_conv == .c) "abi(.c)" else "default sx convention"; + const have_cc = if (func_cc == .c) "abi(.c)" else "default sx convention"; d.addFmt(.err, node.span, "call-convention mismatch: '{s}' is declared with {s} but the target type expects {s}", .{ eff_fn_name, have_cc, want_cc }); } break :blk self.emitPlaceholder(eff_fn_name); diff --git a/src/ir/lower/ffi.zig b/src/ir/lower/ffi.zig index b68c71d3..22d9a5ca 100644 --- a/src/ir/lower/ffi.zig +++ b/src/ir/lower/ffi.zig @@ -1133,7 +1133,7 @@ pub fn synthesizeJniMainStub(self: *Lowering, fcd: *const ast.RuntimeClassDecl, // JNI native methods are C-callable entry points — install the // static default Context so `context.X` reads in the method body // resolve through `current_ctx_ref`. Mirror the same binding - // `lowerFunction` does for callconv(.c) / isExportedEntryName. + // `lowerFunction` does for abi(.c) / isExportedEntryName. const saved_ctx_ref_jni = self.current_ctx_ref; defer self.current_ctx_ref = saved_ctx_ref_jni; if (self.implicit_ctx_enabled) { diff --git a/src/ir/lower/nominal.zig b/src/ir/lower/nominal.zig index d527c84e..f6a3b835 100644 --- a/src/ir/lower/nominal.zig +++ b/src/ir/lower/nominal.zig @@ -6,6 +6,7 @@ const mod_mod = @import("../module.zig"); const type_bridge = @import("../type_bridge.zig"); const program_index_mod = @import("../program_index.zig"); const resolver_mod = @import("../resolver.zig"); +const compiler_lib = @import("../compiler_lib.zig"); const StructTemplate = program_index_mod.StructTemplate; const TemplateParam = program_index_mod.TemplateParam; @@ -673,7 +674,13 @@ pub fn registerStructDecl(self: *Lowering, sd: *const ast.StructDecl, source_fil // any forward-reference stub. Same-name structs in DIFFERENT sources get // distinct TypeIds instead of last-wins clobbering the first. const info: types.TypeInfo = .{ .@"struct" = .{ .name = name_id, .fields = fields.items } }; - _ = self.internNamedTypeDecl(decl_key, name_id, info, nominal_id); + const struct_tid = self.internNamedTypeDecl(decl_key, name_id, info, nominal_id); + + // Welded `struct abi(.zig) extern compiler { … }`: the sx declaration is a + // header checked against the compiler's real Zig type — validate the layout + // matches the binding registry (a mismatch is a build error). See + // design/comptime-compiler-api.md. + if (sd.abi == .zig) validateWeldedStruct(self, sd, struct_tid, fields.items); // Store field defaults for struct literal lowering if (sd.field_defaults.len > 0) { @@ -709,6 +716,51 @@ pub fn registerStructDecl(self: *Lowering, sd: *const ast.StructDecl, source_fil } } +/// Validate a welded `struct abi(.zig) extern { … }` against the `compiler` +/// library's binding registry: the bound library must be `compiler`, the name +/// must be on the export list, and the sx-declared layout must match the real Zig +/// type's (the sx side is a *header* checked against the implementation). Any +/// failure is a build-gating `.err` diagnostic — never a silent reinterpretation. +fn validateWeldedStruct(self: *Lowering, sd: *const ast.StructDecl, tid: TypeId, fields: []const types.TypeInfo.StructInfo.Field) void { + const diags = self.diagnostics orelse return; + const table = &self.module.types; + + // A span that points into the struct (its first field, else zero) — the decl + // has no name span of its own. + const span: ast.Span = if (sd.field_types.len > 0) sd.field_types[0].span else .{ .start = 0, .end = 0 }; + + // The bound library must be the sole welded source. + if (sd.extern_lib == null or !std.mem.eql(u8, sd.extern_lib.?, compiler_lib.lib_name)) { + diags.addFmt(.err, span, "abi(.zig) struct '{s}' must bind the compiler library — write `extern {s}`", .{ sd.name, compiler_lib.lib_name }); + return; + } + + // The name must be on the curated export list (the safety boundary). + const bt = compiler_lib.findType(sd.name) orelse { + diags.addFmt(.err, span, "'{s}' is not a type exported by the '{s}' library", .{ sd.name, compiler_lib.lib_name }); + return; + }; + + // Build the observed sx layout (field name + computed size) and total size. + var sx_fields = std.ArrayList(compiler_lib.SxField).empty; + defer sx_fields.deinit(self.alloc); + for (fields) |f| { + sx_fields.append(self.alloc, .{ + .name = table.getString(f.name), + .size = table.typeSizeBytes(f.ty), + }) catch return; + } + const total = table.typeSizeBytes(tid); + + const mismatch = compiler_lib.validateStructLayout(bt, sx_fields.items, total) orelse return; + switch (mismatch) { + .field_count => |m| diags.addFmt(.err, span, "welded type '{s}' has {d} field(s) in the compiler library but the declaration has {d}", .{ sd.name, m.expected, m.got }), + .field_name => |m| diags.addFmt(.err, span, "welded type '{s}' field {d} is named '{s}' in the compiler library, not '{s}'", .{ sd.name, m.index, m.expected, m.got }), + .field_size => |m| diags.addFmt(.err, span, "welded type '{s}' field '{s}' is {d} byte(s) in the compiler library but {d} as declared", .{ sd.name, m.name, m.expected, m.got }), + .total_size => |m| diags.addFmt(.err, span, "welded type '{s}' is {d} byte(s) in the compiler library but {d} as declared (padding/alignment mismatch)", .{ sd.name, m.expected, m.got }), + } +} + /// Register a top-level ENUM decl under a per-decl nominal identity (E6a) — /// the enum twin of `registerStructDecl`. A GENUINE same-name shadow already /// reserved its DISTINCT slot up-front in `scanDecls` (the first at id 0, the diff --git a/src/ir/lower/objc_class.zig b/src/ir/lower/objc_class.zig index 2587190c..9d00599e 100644 --- a/src/ir/lower/objc_class.zig +++ b/src/ir/lower/objc_class.zig @@ -255,7 +255,7 @@ pub fn lowerObjcPropertySetter(self: *Lowering, obj_expr: *const ast.Node, field } }, .void); } -/// Get a FuncId for an external C-callconv function. If a function +/// Get a FuncId for an external C-ABI function. If a function /// with this exported name already exists in the module (e.g. /// declared by stdlib `extern` decl), return it; otherwise /// declare it fresh with the given signature. @@ -290,7 +290,7 @@ pub fn ensureCRuntimeDecl(self: *Lowering, name: []const u8, param_tys: []const /// 2. Calls `object_getIvar(obj, ivar)` to get the `*State` /// state pointer. /// 3. Calls the sx body `@.(__sx_default_context, -/// state, ...user_args)` (default sx-callconv). +/// state, ...user_args)` (default sx convention). /// 4. Returns the result (or `ret void`). /// /// IMP name: `____imp`. emit_llvm's diff --git a/src/ir/module.zig b/src/ir/module.zig index f1ad9f60..c39ce6ed 100644 --- a/src/ir/module.zig +++ b/src/ir/module.zig @@ -91,7 +91,7 @@ pub const Module = struct { pub const ObjcDefinedMethodEntry = struct { sel: []const u8, // mangled Obj-C selector (`add:and:`) encoding: []const u8, // Apple-runtime type encoding (`v@:ii`) - imp_name: []const u8, // C-callconv trampoline symbol (`__Cls_method_imp`) + imp_name: []const u8, // C-ABI trampoline symbol (`__Cls_method_imp`) is_class: bool = false, // true ⇒ register on the metaclass (M2.1 class methods) }; diff --git a/src/ir/type_resolver.zig b/src/ir/type_resolver.zig index f65f3f0c..7efb6c21 100644 --- a/src/ir/type_resolver.zig +++ b/src/ir/type_resolver.zig @@ -224,9 +224,15 @@ pub const TypeResolver = struct { defer param_ids.deinit(table.alloc); for (ft.param_types) |pt| param_ids.append(table.alloc, inner.resolveInner(pt)) catch return .unresolved; const ret_ty = if (ft.return_type) |rt| inner.resolveInner(rt) else TypeId.void; - const cc: types.TypeInfo.CallConv = switch (ft.call_conv) { + const cc: types.TypeInfo.CallConv = switch (ft.abi) { .default => .default, .c => .c, + // `.zig` (compiler-lib weld) and `.pure` (naked asm) are + // decl-level ABIs with no function-pointer-type calling + // convention of their own; the IR function-type CC models only + // sx-default vs C. Neither occurs in a function-TYPE position in + // current usage — treated as sx-default here. + .zig, .pure => .default, }; break :blk table.functionTypeCC(param_ids.items, ret_ty, cc); }, diff --git a/src/lexer.zig b/src/lexer.zig index e2316f34..b86f1c2a 100644 --- a/src/lexer.zig +++ b/src/lexer.zig @@ -537,9 +537,9 @@ test "lex keywords" { } test "lex linkage keywords" { - // extern / export are keywords (FFI-linkage stream), lexed beside callconv. - var lex = Lexer.init("callconv extern export"); - const expected = [_]Tag{ .kw_callconv, .kw_extern, .kw_export }; + // extern / export are keywords (FFI-linkage stream), lexed beside abi. + var lex = Lexer.init("abi extern export"); + const expected = [_]Tag{ .kw_abi, .kw_extern, .kw_export }; for (expected) |exp| { try std.testing.expectEqual(exp, lex.next().tag); } diff --git a/src/lsp/server.zig b/src/lsp/server.zig index 52b8f89c..dda5e0b8 100644 --- a/src/lsp/server.zig +++ b/src/lsp/server.zig @@ -1682,7 +1682,7 @@ pub const Server = struct { .kw_protocol, .kw_impl, .kw_inline, - .kw_callconv, + .kw_abi, .kw_extern, .kw_export, .kw_asm, diff --git a/src/main.zig b/src/main.zig index b55f168a..cc36d987 100644 --- a/src/main.zig +++ b/src/main.zig @@ -909,6 +909,11 @@ fn extractLibraries(allocator: std.mem.Allocator, root: *const sx.ast.Node) ![]c for (decls) |d| { switch (d.data) { .library_decl => |ld| { + // The `compiler` library is the comptime-only internal + // surface (welded types / host-call functions), not a + // linkable dylib — never dlopen it. See + // design/comptime-compiler-api.md. + if (std.mem.eql(u8, ld.lib_name, sx.ir.compiler_lib.lib_name)) continue; if (s.contains(ld.lib_name)) continue; try s.put(ld.lib_name, {}); try l.append(a, ld.lib_name); diff --git a/src/parser.test.zig b/src/parser.test.zig index 94c14a2c..5e6e4c5c 100644 --- a/src/parser.test.zig +++ b/src/parser.test.zig @@ -78,3 +78,148 @@ test "parser: comptime type-metaprogramming surface parses" { try std.testing.expect(d.data.fn_decl.return_type != null); } } + +// Lock: the `compiler`-library binding surface PARSES — `name :: #library "x";` +// (already supported) plus the new postfix `abi(.zig)` annotation (in the slot +// before `extern`) followed by the library handle, on a function declaration. The +// AST must carry the binding: `abi == .zig`, `extern_export == .extern_`, and the +// library handle in `extern_lib`. No semantics yet — this is the first testable +// sub-step of Phase 1 (parse only). +test "parser: abi(.zig) extern binding parses on a fn decl" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const src = + \\compiler :: #library "compiler"; + \\text_of :: (id: StringId) -> string abi(.zig) extern compiler; + \\intern :: (s: string) -> StringId abi(.zig) extern compiler; + \\ + ; + var parser = Parser.init(alloc, src); + const root = try parser.parse(); + + try std.testing.expect(root.data == .root); + const decls = root.data.root.decls; + try std.testing.expectEqual(@as(usize, 3), decls.len); + + // The `#library` decl still parses to a `library_decl` node carrying the name. + try std.testing.expect(decls[0].data == .library_decl); + try std.testing.expectEqualStrings("compiler", decls[0].data.library_decl.name); + try std.testing.expectEqualStrings("compiler", decls[0].data.library_decl.lib_name); + + // The two `abi(.zig) extern compiler` fns: `.fn_decl` with the binding fields set. + for ([_][]const u8{ "text_of", "intern" }) |bn| { + var found: ?*const Node = null; + for (decls) |d| { + if (d.data.declName()) |n| { + if (std.mem.eql(u8, n, bn)) found = d; + } + } + const d = found orelse return error.MissingDecl; + try std.testing.expect(d.data == .fn_decl); + const fd = d.data.fn_decl; + try std.testing.expectEqual(ast.ABI.zig, fd.abi); + try std.testing.expectEqual(ast.ExternExportModifier.extern_, fd.extern_export); + try std.testing.expect(fd.extern_lib != null); + try std.testing.expectEqualStrings("compiler", fd.extern_lib.?); + // Bodyless extern import: synthesized empty block, no `#builtin`/`#compiler`. + try std.testing.expect(fd.body.data == .block); + } +} + +// Lock: a bare `extern` (no abi annotation) leaves `abi == .default` — the +// unannotated case is unchanged by the new `abi(...)` slot. +test "parser: bare extern leaves abi == .default" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const src = + \\puts :: (s: *u8) -> i32 extern; + \\ + ; + var parser = Parser.init(alloc, src); + const root = try parser.parse(); + const decls = root.data.root.decls; + try std.testing.expectEqual(@as(usize, 1), decls.len); + try std.testing.expect(decls[0].data == .fn_decl); + const fd = decls[0].data.fn_decl; + try std.testing.expectEqual(ast.ExternExportModifier.extern_, fd.extern_export); + try std.testing.expectEqual(ast.ABI.default, fd.abi); +} + +// Lock: `abi(.c)` parses standalone (no extern/export) in the postfix slot — the +// migrated spelling of the old `callconv(.c)` on an ordinary function pointer / +// fn decl. And `abi(.pure)` parses (naked-asm ABI). +test "parser: abi(.c) and abi(.pure) parse standalone" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const src = + \\cb :: () -> i64 abi(.c) { 0; } + \\nk :: () -> i64 abi(.pure) { 0; } + \\ + ; + var parser = Parser.init(alloc, src); + const root = try parser.parse(); + const decls = root.data.root.decls; + try std.testing.expectEqual(@as(usize, 2), decls.len); + try std.testing.expect(decls[0].data == .fn_decl); + try std.testing.expectEqual(ast.ABI.c, decls[0].data.fn_decl.abi); + try std.testing.expectEqual(ast.ExternExportModifier.none, decls[0].data.fn_decl.extern_export); + try std.testing.expect(decls[1].data == .fn_decl); + try std.testing.expectEqual(ast.ABI.pure, decls[1].data.fn_decl.abi); +} + +// Lock: the `compiler`-library binding PARSES on a STRUCT decl — `Name :: struct +// abi(.zig) extern { … }`. The AST struct_decl must carry `abi == .zig` and +// the library handle in `extern_lib`, with the field list intact. No semantics +// yet (parse-only) — this is the second testable sub-step of Phase 1. +test "parser: abi(.zig) extern binding parses on a struct decl" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const src = + \\compiler :: #library "compiler"; + \\Field :: struct abi(.zig) extern compiler { name: StringId; ty: Type; } + \\ + ; + var parser = Parser.init(alloc, src); + const root = try parser.parse(); + const decls = root.data.root.decls; + try std.testing.expectEqual(@as(usize, 2), decls.len); + + try std.testing.expect(decls[1].data == .struct_decl); + const sd = decls[1].data.struct_decl; + try std.testing.expectEqual(ast.ABI.zig, sd.abi); + try std.testing.expect(sd.extern_lib != null); + try std.testing.expectEqualStrings("compiler", sd.extern_lib.?); + // Field list survives the binding annotation. + try std.testing.expectEqual(@as(usize, 2), sd.field_names.len); + try std.testing.expectEqualStrings("name", sd.field_names[0]); + try std.testing.expectEqualStrings("ty", sd.field_names[1]); +} + +// Lock: an ordinary struct (no binding) leaves `abi == .default` / `extern_lib == +// null` — the new annotation slot doesn't perturb the common case. +test "parser: plain struct leaves abi == .default, extern_lib == null" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const src = + \\Point :: struct { x: i64; y: i64; } + \\ + ; + var parser = Parser.init(alloc, src); + const root = try parser.parse(); + const decls = root.data.root.decls; + try std.testing.expectEqual(@as(usize, 1), decls.len); + try std.testing.expect(decls[0].data == .struct_decl); + const sd = decls[0].data.struct_decl; + try std.testing.expectEqual(ast.ABI.default, sd.abi); + try std.testing.expect(sd.extern_lib == null); +} diff --git a/src/parser.zig b/src/parser.zig index e8a36d9a..3de73257 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -598,12 +598,12 @@ pub const Parser = struct { // '->' present: function type self.advance(); // skip '->' const return_type = try self.parseTypeExpr(); - const call_conv = try self.parseOptionalCallConv(); + const abi = try self.parseOptionalAbi(); return try self.createNode(start, .{ .function_type_expr = .{ .param_types = try param_types.toOwnedSlice(self.allocator), .param_names = if (has_names) try param_names.toOwnedSlice(self.allocator) else null, .return_type = return_type, - .call_conv = call_conv, + .abi = abi, } }); } // No '->': tuple type (even for single element). Keep field names @@ -959,6 +959,18 @@ pub const Parser = struct { self.advance(); } + // Optional welded-binding annotation: `struct abi(.zig) extern { … }`. + // `abi(...)` (the ABI/layout selector) sits before the `extern` linkage + // keyword, mirroring the fn-decl slot order; the library handle follows. + // Parse-only for now — no layout/registry semantics yet. + const struct_abi = try self.parseOptionalAbi(); + const struct_extern = self.parseOptionalExternExport(); + var struct_extern_lib: ?[]const u8 = null; + if (struct_extern != .none and self.current.tag == .identifier) { + struct_extern_lib = self.tokenSlice(self.current); + self.advance(); + } + // Optional type params: struct($N: u32, $T: Type) { ... } var type_params = std.ArrayList(ast.StructTypeParam).empty; if (self.current.tag == .l_paren) { @@ -1146,6 +1158,8 @@ pub const Parser = struct { .using_entries = try using_entries.toOwnedSlice(self.allocator), .methods = try methods.toOwnedSlice(self.allocator), .constants = try constants.toOwnedSlice(self.allocator), + .abi = struct_abi, + .extern_lib = struct_extern_lib, .is_raw = name_is_raw, } }); } @@ -1937,8 +1951,11 @@ pub const Parser = struct { return_type = try self.parseTypeExpr(); } - // Optional calling convention: callconv(.c) - const call_conv = try self.parseOptionalCallConv(); + // Optional ABI / calling-convention annotation: `abi(.c)` / `abi(.zig)` / + // `abi(.pure)`. Sits in the postfix slot BEFORE the `extern`/`export` + // linkage keyword (it is part of the function declaration). `abi(.zig)` + // marks a binding to the comptime `compiler` library. + const abi = try self.parseOptionalAbi(); // Optional postfix linkage modifier: `extern` (import) / `export` (define). const extern_export = self.parseOptionalExternExport(); @@ -2018,7 +2035,7 @@ pub const Parser = struct { .body = body, .type_params = type_params, .is_arrow = is_arrow, - .call_conv = call_conv, + .abi = abi, .extern_export = extern_export, .extern_lib = extern_lib, .extern_name = extern_name, @@ -3688,8 +3705,8 @@ pub const Parser = struct { return_type = try self.parseTypeExpr(); } - // Optional calling convention: callconv(.c) - const call_conv = try self.parseOptionalCallConv(); + // Optional ABI annotation: abi(.c) / abi(.zig) / abi(.pure) + const abi = try self.parseOptionalAbi(); // A closure is its own function boundary: clear the cleanup-body flags // so control-flow exits inside the closure body (`return` from the @@ -3719,7 +3736,7 @@ pub const Parser = struct { .return_type = return_type, .body = body, .type_params = type_params, - .call_conv = call_conv, + .abi = abi, } }); } @@ -3745,8 +3762,8 @@ pub const Parser = struct { // builtin marker) is a function-type literal, not a function def. if (tag == .arrow) return self.hasFnBodyAfterArrow(); // `kw_extern`/`kw_export`: a postfix linkage modifier (e.g. `f :: () extern;` - // with no return type) marks a fn decl just like `callconv`. - return tag == .l_brace or tag == .hash_builtin or tag == .hash_compiler or tag == .fat_arrow or tag == .kw_callconv or tag == .kw_extern or tag == .kw_export; + // with no return type) marks a fn decl just like `abi(...)`. + return tag == .l_brace or tag == .hash_builtin or tag == .hash_compiler or tag == .fat_arrow or tag == .kw_abi or tag == .kw_extern or tag == .kw_export; } fn hasFnBodyAfterArrow(self: *Parser) bool { @@ -3773,9 +3790,9 @@ pub const Parser = struct { if (self.current.tag == .fat_arrow) return true; if (self.current.tag == .l_brace) return true; if (self.current.tag == .hash_builtin or self.current.tag == .hash_compiler) return true; - if (self.current.tag == .kw_callconv) return true; + if (self.current.tag == .kw_abi) return true; // Postfix linkage modifier after the return type: `-> R extern;` / - // `-> R export { … }` (and `-> R callconv(.c) extern`). Marks a fn def. + // `-> R export { … }` (and `-> R abi(.c) extern`). Marks a fn def. if (self.current.tag == .kw_extern or self.current.tag == .kw_export) return true; // Inside a `struct #compiler` block, a `(...) -> Ret;` ending // with `;` after the return type is a `#compiler` method @@ -3806,25 +3823,32 @@ pub const Parser = struct { return false; } - fn parseOptionalCallConv(self: *Parser) anyerror!ast.CallingConvention { - if (self.current.tag != .kw_callconv) return .default; + /// Optional ABI / calling-convention annotation `abi(.c)` / `abi(.zig)` / + /// `abi(.pure)` in the postfix slot before `extern`/`export`. `.default` when + /// absent. Subsumes the old `callconv(...)` spelling. + fn parseOptionalAbi(self: *Parser) anyerror!ast.ABI { + if (self.current.tag != .kw_abi) return .default; self.advance(); try self.expect(.l_paren); try self.expect(.dot); if (self.current.tag != .identifier) - return self.fail("expected calling convention name after '.'"); - const cc_name = self.tokenSlice(self.current); - const cc: ast.CallingConvention = if (std.mem.eql(u8, cc_name, "c")) .c else return self.fail("unknown calling convention"); + return self.fail("expected ABI name ('.c', '.zig', or '.pure') after '.'"); + const abi_name = self.tokenSlice(self.current); + const abi: ast.ABI = if (std.mem.eql(u8, abi_name, "c")) + .c + else if (std.mem.eql(u8, abi_name, "zig")) + .zig + else if (std.mem.eql(u8, abi_name, "pure")) + .pure + else + return self.fail("unknown ABI (expected '.c', '.zig', or '.pure')"); self.advance(); try self.expect(.r_paren); - return cc; + return abi; } - /// Postfix linkage modifier in the slot after `callconv(...)`: + /// Postfix linkage modifier in the slot after `abi(...)`: /// `extern` (import) or `export` (define + expose), or `.none` if neither. - /// Mirrors `parseOptionalCallConv`. Bare-keyword today; the optional - /// `"csym"` symbol-name override lands in Phase 1.2/2.2. Defined here in - /// Phase 0.1 but NOT yet called from any decl path (wired in Phase 1.0). fn parseOptionalExternExport(self: *Parser) ast.ExternExportModifier { switch (self.current.tag) { .kw_extern => { diff --git a/src/sema.zig b/src/sema.zig index 0a119c32..254872b4 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -830,7 +830,7 @@ pub const Analyzer = struct { switch (node.data) { .fn_decl => |fd| { const saved_cc = self.in_c_conv; - self.in_c_conv = fd.call_conv == .c; + self.in_c_conv = fd.abi == .c; try self.pushScope(); try self.analyzeParams(fd.params); try self.analyzeNode(fd.body); @@ -852,7 +852,7 @@ pub const Analyzer = struct { if (mnode.data == .fn_decl) { const m = mnode.data.fn_decl; const saved_cc = self.in_c_conv; - self.in_c_conv = m.call_conv == .c; + self.in_c_conv = m.abi == .c; try self.pushScope(); try self.analyzeParams(m.params); try self.analyzeNode(m.body); @@ -979,7 +979,7 @@ pub const Analyzer = struct { try self.diagnostics.append(self.allocator, .{ .level = .warn, .span = span, - .message = "`context` is unavailable in a `callconv(.c)` function — the C ABI has no implicit context parameter; pass what you need explicitly", + .message = "`context` is unavailable in an `abi(.c)` function — the C ABI has no implicit context parameter; pass what you need explicitly", }); return; } @@ -1032,7 +1032,7 @@ pub const Analyzer = struct { }); } const saved_cc = self.in_c_conv; - self.in_c_conv = fd.call_conv == .c; + self.in_c_conv = fd.abi == .c; try self.pushScope(); try self.analyzeParams(fd.params); try self.analyzeNode(fd.body); @@ -2366,7 +2366,7 @@ test "sema: member references record fields, methods, and enum variants" { try std.testing.expect(red_use); } -test "sema: context in a callconv(.c) function reports a specific diagnostic" { +test "sema: context in an abi(.c) function reports a specific diagnostic" { const parser_mod = @import("parser.zig"); var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); @@ -2374,7 +2374,7 @@ test "sema: context in a callconv(.c) function reports a specific diagnostic" { const source = "Context :: struct { allocator: i64; data: i64; }" ++ - "cb :: () -> i64 callconv(.c) { context; 0; }" ++ + "cb :: () -> i64 abi(.c) { context; 0; }" ++ "ok :: () -> i64 { context; 0; }"; var parser = parser_mod.Parser.init(alloc, source); const root = try parser.parse(); @@ -2385,7 +2385,7 @@ test "sema: context in a callconv(.c) function reports a specific diagnostic" { var c_conv_diag = false; var undefined_diag = false; for (res.diagnostics) |d| { - if (std.mem.indexOf(u8, d.message, "callconv(.c)") != null) c_conv_diag = true; + if (std.mem.indexOf(u8, d.message, "abi(.c)") != null) c_conv_diag = true; if (std.mem.indexOf(u8, d.message, "undefined") != null) undefined_diag = true; } try std.testing.expect(c_conv_diag); // `cb` accesses context under the C ABI diff --git a/src/token.zig b/src/token.zig index 6674de79..29ef3dee 100644 --- a/src/token.zig +++ b/src/token.zig @@ -42,7 +42,7 @@ pub const Tag = enum { kw_impl, // impl kw_Self, // Self (in protocol declarations) kw_inline, // inline (compile-time if/for/while) - kw_callconv, // callconv (calling convention annotation) + kw_abi, // abi (ABI / calling-convention annotation: abi(.c)/abi(.zig)/abi(.pure)) kw_extern, // extern (import: external linkage, C ABI, no body) kw_export, // export (define + expose: external linkage, C ABI) kw_asm, // asm (inline assembly expression / global asm decl) @@ -281,7 +281,7 @@ pub const keywords = std.StaticStringMap(Tag).initComptime(.{ .{ "impl", .kw_impl }, .{ "Self", .kw_Self }, .{ "inline", .kw_inline }, - .{ "callconv", .kw_callconv }, + .{ "abi", .kw_abi }, .{ "extern", .kw_extern }, .{ "export", .kw_export }, // `asm` is a real keyword; `volatile` / `clobbers` stay OUT of this table