comptime compiler-API: Phase 1 foundation + Phase 2.1 weld plan

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).
This commit is contained in:
agra
2026-06-17 13:31:11 +03:00
parent 3a9b508502
commit cd5b958d19
100 changed files with 1490 additions and 298 deletions

View File

@@ -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 <lib>` 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 <lib>` — 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 <lib>` 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) <lib>` 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 <lib>` — 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 <lib>`
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 23.
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 <lib>`
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 <lib>` 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
<lib>` (linkage + source) — recorded in the design doc's syntax-decision note.

View File

@@ -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 > **Status: design-of-record (not yet an active stream).** Captures a unified
> mechanism for sx↔compiler binding that subsumes the metatype `declare`/`define` > 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; `is_comptime` boundary. (Welded *types* are still usable as plain runtime data;
only the *functions* are comptime-gated.) only the *functions* are comptime-gated.)
### `extern(.zig) <lib>` — postfix attribute ### `abi(.zig)` + `extern <lib>` — the binding surface
Slots where `#builtin` / `#compiler` go (postfix, after the return type for fns, > **Syntax decision (2026-06-17, supersedes the original `extern(.zig) <lib>`
after `struct` for types), with the library handle following: > 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 <lib>` — 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 <lib>` pair sits after `struct`:
```sx ```sx
// functions: // functions:
text_of :: (id: StringId) -> string extern(.zig) compiler; text_of :: (id: StringId) -> string abi(.zig) extern compiler;
intern :: (s: string) -> StringId extern(.zig) compiler; intern :: (s: string) -> StringId abi(.zig) extern compiler;
register_type :: (info: StructInfo) -> Type extern(.zig) compiler; register_type :: (info: StructInfo) -> Type abi(.zig) extern compiler;
find_type :: (name: StringId) -> ?Type extern(.zig) compiler; find_type :: (name: StringId) -> ?Type abi(.zig) extern compiler;
// types (layout-welded to the lib's real Zig type): // types (layout-welded to the lib's real Zig type):
Field :: struct extern(.zig) compiler { name: StringId; ty: Type; }; Field :: struct abi(.zig) extern compiler { name: StringId; ty: Type; };
StructInfo :: struct extern(.zig) compiler { StructInfo :: struct abi(.zig) extern compiler {
name: StringId; fields: []Field; is_protocol: bool; nominal_id: u32; name: StringId; fields: []Field; is_protocol: bool; nominal_id: u32;
}; };
``` ```
`extern(.zig)` = "Zig ABI / Zig layout"; `<lib>` = the binding source. `abi(.zig)` = "Zig ABI / Zig layout"; `extern compiler` = the linkage + binding
source.
### Layout welding — why it's exact, not brittle ### Layout welding — why it's exact, not brittle
The sx compiler is itself a Zig program; `types.zig` is part of it. So at 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 **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 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 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 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 migration
`BuildOptions :: struct #compiler { ... }` + `build_options() #compiler` `BuildOptions :: struct #compiler { ... }` + `build_options() #compiler`
`extern(.zig) compiler`: the setter/getter hook-methods become `extern(.zig) `abi(.zig) extern compiler`: the setter/getter hook-methods become `abi(.zig)
compiler` functions (or methods on a welded/handle `BuildOptions`), backed by the extern compiler` functions (or methods on a welded/handle `BuildOptions`), backed by the
same `BuildConfig` state. The `compiler_hooks.zig` registry becomes the `compiler` 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 lib's function/type registry. Net: the build DSL and the metatype API ride one
mechanism. mechanism.
@@ -157,12 +169,17 @@ Foundation that ALREADY exists:
- `extern` / `export` are keywords (`src/token.zig:46`, `kw_extern`/`kw_export`). - `extern` / `export` are keywords (`src/token.zig:46`, `kw_extern`/`kw_export`).
New work for Phase 1: New work for Phase 1:
- **Lexer/parser**: the `(.zig)` ABI qualifier on `extern`, and the trailing - **Lexer/parser**: the `abi(.zig)` annotation (a new `abi` keyword replacing
`<lib>` identifier — `… extern(.zig) <lib>` postfix on FN decls (after the `callconv`; `ABI = { default, c, zig, pure }`) in the slot before `extern`,
return type, beside `#builtin`/`#compiler` at `src/parser.zig:233`/`:315`) and followed by the `<lib>` handle — `… abi(.zig) extern <lib>` postfix on FN decls
STRUCT decls (beside `struct #compiler`, `src/parser.zig:953`). (after the return type, before `extern`) and STRUCT decls (beside
- **AST**: an abi/layout-binding field on `FnDecl` and the struct decl (`abi: `struct #compiler`). **DONE (parse-only)**`parseOptionalAbi`
.c | .zig`, `lib: ?name`). (`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 - **Binding registry**: re-home / generalize `src/ir/compiler_hooks.zig` (today's
`#compiler` registry) into the `compiler` lib's type+function registry, keyed by `#compiler` registry) into the `compiler` lib's type+function registry, keyed by
exported sx name → Zig type (`@offsetOf` layout) / Zig fn (host-call). 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) ## Build order (each phase keeps `zig build test` green)
1. **`extern(.zig)` + `#library` foundation** — parse the postfix attribute (the 1. **`abi(.zig) extern <lib>` + `#library` foundation** — parse the postfix
`#library` decl already exists); a binding registry (sx name → Zig type/fn); annotation (the `#library` decl already exists); a binding registry (sx name →
the layout engine honoring the bound type's `@offsetOf` offsets + LLVM emission Zig type/fn); the layout engine honoring the bound type's `@offsetOf` offsets +
that hits them; **build-time layout-equality assertion**. Prove with `Field` LLVM emission that hits them; **build-time layout-equality assertion**. Prove
(two u32s). First testable sub-step: `extern(.zig) <lib>` PARSES on a fn decl with `Field` (two u32s). First testable sub-step **DONE**: `abi(.zig) extern
(parser unit test), AST carries the binding — no semantics yet. <lib>` 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 2. **Weld `StructInfo`** + `StringId` accessors (`intern`/`text_of`) over the
host-call bridge. host-call bridge.
3. **Re-express `type_info`/`define` (struct)** as sx over `register_struct`/ 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` 4. **Widen to enum/tuple** — weld `EnumInfo`/`TaggedUnionInfo`/`TupleInfo`
(optional fields → sentinels: `backing_type` `.unresolved`, `explicit_values` (optional fields → sentinels: `backing_type` `.unresolved`, `explicit_values`
len-0); migrate `examples/0619`/`0623`; delete the enum/tuple interp arms. 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. 6. **Delete `#compiler`**; suite green.
## Risks / open questions ## Risks / open questions

View File

@@ -1,6 +1,6 @@
// Generic `Into(Block)` impl with a `string`-typed arg in the // Generic `Into(Block)` impl with a `string`-typed arg in the
// closure signature. The block trampoline declares the param with // 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 // silently collapsed to `ptr` (the libc `char *` heuristic) and
// the caller's 16-byte `{ptr, len}` value mismatched the // the caller's 16-byte `{ptr, len}` value mismatched the
// trampoline's 8-byte `ptr` slot. Result: segfault inside the // trampoline's 8-byte `ptr` slot. Result: segfault inside the
@@ -8,7 +8,7 @@
// //
// The fix lives in `abiCoerceParamTypeEx`: the `string`/`slice` → // The fix lives in `abiCoerceParamTypeEx`: the `string`/`slice` →
// `ptr` collapse only applies to `is_extern` extern decls (libc // `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 // shape, which lands as `[2 x i64]` at the LLVM signature site
// and matches the caller's two-register pass on AArch64. // and matches the caller's two-register pass on AArch64.
@@ -20,7 +20,7 @@ g_s: string = "";
main :: () -> i32 { main :: () -> i32 {
cl := (s: string) => { g_s = s; }; cl := (s: string) => { g_s = s; };
b : Block = xx cl; 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"); invoke_fn(@b, "hello");
if g_s.len == 0 { print("FAIL: empty\n"); return 1; } if g_s.len == 0 { print("FAIL: empty\n"); return 1; }
print("got: <{}>\n", g_s); print("got: <{}>\n", g_s);

View File

@@ -1,7 +1,7 @@
// FFI plan step 5.2 — generic `Into(Block) for Closure(..$args) -> // FFI plan step 5.2 — generic `Into(Block) for Closure(..$args) ->
// $R` impl. One impl in stdlib covers every closure shape; the // $R` impl. One impl in stdlib covers every closure shape; the
// compiler monomorphises the impl body per call shape and emits a // 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);`). // (via `#insert build_block_convert($args, $R);`).
// //
// This test exercises a closure shape (`Closure(i64, i64) -> void`) // 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 per-shape trampoline ferries control back to the sx closure.
// //
// The block is invoked directly through `b.invoke` (a typed // 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 // runtime calls when a UIKit/Foundation API hands the block back
// to its registered invoke. // to its registered invoke.
@@ -27,7 +27,7 @@ main :: () -> i32 {
cl := (a: i64, b: i64) => { g_a = a; g_b = b; }; cl := (a: i64, b: i64) => { g_a = a; g_b = b; };
blk : Block = xx cl; 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); invoke_fn(@blk, 10, 20);
if g_a != 10 { print("FAIL: g_a={}\n", g_a); return 1; } if g_a != 10 { print("FAIL: g_a={}\n", g_a); return 1; }

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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 // silently mismatches ABIs — historically that meant the C-side caller
// supplied no `__sx_ctx` slot 0 and the sx-side body read garbage. // supplied no `__sx_ctx` slot 0 and the sx-side body read garbage.
// The compiler now rejects the coercion outright with a "call-convention // The compiler now rejects the coercion outright with a "call-convention
@@ -9,6 +9,6 @@
sx_handler :: (arg: *void) -> *void { return arg; } sx_handler :: (arg: *void) -> *void { return arg; }
main :: () -> i32 { main :: () -> i32 {
fp : (*void) -> *void callconv(.c) = sx_handler; fp : (*void) -> *void abi(.c) = sx_handler;
return 0; return 0;
} }

View File

@@ -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);
}

View File

@@ -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"); }

View File

@@ -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);
}

View File

@@ -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` // the function uses C ABI so it can be safely invoked from `extern`
// functions like SDL_AddEventWatch. // functions like SDL_AddEventWatch.
#import "modules/std.sx"; #import "modules/std.sx";
// A function with C calling convention // 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 a + b
} }
main :: () { main :: () {
// Call it directly — should work like any other function // Call it directly — should work like any other function
result := add_c(10, 32); result := add_c(10, 32);
print("callconv(.c): {}\n", result); print("abi(.c): {}\n", result);
} }

View File

@@ -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 // regression coverage for prior data-corruption when the callback dispatches
// through a global pointer to a method on the pointed-to struct. // 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); 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_width = xx code;
g_height = xx (code + 1); g_height = xx (code + 1);
g_pipe.resize(xx g_width, xx g_height); g_pipe.resize(xx g_width, xx g_height);
@@ -38,7 +38,7 @@ callback_inline :: (userdata: *void, code: i64) -> bool callconv(.c) {
true true
} }
callback_wrapper :: (userdata: *void, code: i64) -> bool callconv(.c) { callback_wrapper :: (userdata: *void, code: i64) -> bool abi(.c) {
g_width = xx code; g_width = xx code;
g_height = xx (code + 1); g_height = xx (code + 1);
do_render(); do_render();

View File

@@ -1,6 +1,6 @@
// Regression test for issue-0025 path A. // 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(<T>)` per AAPCS64 / SysV // by value must marshal the arg through `ptr byval(<T>)` per AAPCS64 / SysV
// AArch64: the caller copies the struct to an alloca, passes the alloca // AArch64: the caller copies the struct to an alloca, passes the alloca
// pointer with a `byval(<T>)` attribute, and the callee's entry block loads // pointer with a `byval(<T>)` attribute, and the callee's entry block loads
@@ -20,7 +20,7 @@ Wide :: struct {
d: i64; d: i64;
} }
accept_c :: (w: Wide) -> i64 callconv(.c) { accept_c :: (w: Wide) -> i64 abi(.c) {
w.a + w.b + w.c + w.d w.a + w.b + w.c + w.d
} }

View File

@@ -1,6 +1,6 @@
// Regression test for issue-0025 path B. // 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 // 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 // do at the call site (path A): >16-byte non-HFA aggregates are passed
// as `ptr byval(<T>)`. Without the fix, the indirect call site builds // as `ptr byval(<T>)`. Without the fix, the indirect call site builds
@@ -9,8 +9,8 @@
// ways that don't match the byval-attributed callee signature — the // ways that don't match the byval-attributed callee signature — the
// callee then reads garbage out of the wrong machine-state slots. // callee then reads garbage out of the wrong machine-state slots.
// //
// The opt-in is the `callconv(.c)` on the fn-pointer type spelling. // The opt-in is the `abi(.c)` on the fn-pointer type spelling.
// Pure-sx fn-pointer casts (no callconv suffix) keep their default // Pure-sx fn-pointer casts (no abi suffix) keep their default
// calling convention — verified by examples/87-fnptr-cast-large-aggregate.sx. // calling convention — verified by examples/87-fnptr-cast-large-aggregate.sx.
#import "modules/std.sx"; #import "modules/std.sx";
@@ -22,7 +22,7 @@ Wide :: struct {
d: i64; d: i64;
} }
accept_c :: (w: Wide) -> i64 callconv(.c) { accept_c :: (w: Wide) -> i64 abi(.c) {
w.a + w.b + w.c + w.d w.a + w.b + w.c + w.d
} }
@@ -30,7 +30,7 @@ main :: () -> i32 {
w := Wide.{ a = 1, b = 10, c = 100, d = 1000 }; w := Wide.{ a = 1, b = 10, c = 100, d = 1000 };
if accept_c(w) != 1111 { return 1; } 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; } if fn_ptr(w) != 1111 { return 2; }
0 0

View File

@@ -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 // 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 // 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(<T>)`. // callee expects the struct as an SSA value, not as a `ptr byval(<T>)`.
// //
// Pair with examples/86-callconv-c-fnptr-large-aggregate.sx, which covers // Pair with examples/86-abi-c-fnptr-large-aggregate.sx, which covers
// the opposite arm (fn-pointer typed `callconv(.c)` does get byval). // the opposite arm (fn-pointer typed `abi(.c)` does get byval).
#import "modules/std.sx"; #import "modules/std.sx";

View File

@@ -16,19 +16,19 @@
#source "1214-ffi-06-callback.c"; #source "1214-ffi-06-callback.c";
}; };
ffi_apply_callback :: (cb: (i32) -> i32 callconv(.c), value: i32) -> i32 extern; ffi_apply_callback :: (cb: (i32) -> i32 abi(.c), value: i32) -> i32 extern;
ffi_apply_callback2 :: (cb: (*void, i32) -> i32 callconv(.c), ctx: *void, v: 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_hits : i32 = 0;
g_callback_sum : 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_hits += 1;
g_callback_sum += x; g_callback_sum += x;
x * 2 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; g_callback_hits += 1;
// Pass a sentinel via ctx to prove the pointer arg also survives the // Pass a sentinel via ctx to prove the pointer arg also survives the
// round-trip — read it back as an i32 through *i32. // round-trip — read it back as an i32 through *i32.

View File

@@ -18,10 +18,10 @@ main :: () -> i32 {
sel_with_utf8 := sel_registerName("stringWithUTF8String:".ptr); sel_with_utf8 := sel_registerName("stringWithUTF8String:".ptr);
sel_utf8 := sel_registerName("UTF8String".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); 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); back := msg_2(ns_str, sel_utf8);
return xx (back[0] + back[1]); // 'h' + 'i' = 104 + 105 = 209 return xx (back[0] + back[1]); // 'h' + 'i' = 104 + 105 = 209

View File

@@ -17,7 +17,7 @@ g_marker : i32 = 0;
// IMP for `hello`. Must use C calling convention so `self` and `_cmd` land in // IMP for `hello`. Must use C calling convention so `self` and `_cmd` land in
// x0 and x1 the way the Obj-C runtime expects. // 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; g_marker = 42;
} }
@@ -34,7 +34,7 @@ main :: () -> i32 {
if obj == xx 0 { return 2; } if obj == xx 0 { return 2; }
// [obj hello] // [obj hello]
msg : (*void, *void) -> void callconv(.c) = xx objc_msgSend; msg : (*void, *void) -> void abi(.c) = xx objc_msgSend;
msg(obj, sel_hello); msg(obj, sel_hello);
return g_marker; // 42 if hello_imp ran return g_marker; // 42 if hello_imp ran

View File

@@ -11,7 +11,7 @@
main :: () -> i32 { main :: () -> i32 {
cl := () => { print("noop block ran\n"); }; cl := () => { print("noop block ran\n"); };
b : Block = xx cl; 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); invoke_fn(@b);
0 0
} }

View File

@@ -11,7 +11,7 @@ main :: () -> i32 {
y : i64 = 100; y : i64 = 100;
cl := () => { print("x + y = {}\n", x + y); }; cl := () => { print("x + y = {}\n", x + y); };
b : Block = xx cl; 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); invoke_fn(@b);
0 0
} }

View File

@@ -18,7 +18,7 @@
// Trampoline matching `void (^)(int, void*)` — the C ABI Apple's // Trampoline matching `void (^)(int, void*)` — the C ABI Apple's
// runtime calls. Forwards through to the sx closure with the // runtime calls. Forwards through to the sx closure with the
// standard `(__sx_ctx, env, ...args)` shape. // 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 : (*void, i32, *void) -> void = xx block_self.sx_fn;
typed_fn(block_self.sx_env, arg0, arg1); typed_fn(block_self.sx_env, arg0, arg1);
} }
@@ -49,7 +49,7 @@ main :: () -> i32 {
}; };
b : Block = xx cl; 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; sentinel: i32 = 42;
invoke_fn(@b, 41, xx @sentinel); invoke_fn(@b, 41, xx @sentinel);

View File

@@ -6,7 +6,7 @@
#import "modules/ffi/objc_block.sx"; #import "modules/ffi/objc_block.sx";
invoke_once :: (b: *Block) { 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); invoke_fn(b);
} }

View File

@@ -35,7 +35,7 @@ main :: () -> i32 {
// [SxFoo alloc] — invokes the synthesized +alloc IMP. // [SxFoo alloc] — invokes the synthesized +alloc IMP.
sel_alloc : SEL = sel_registerName("alloc".ptr); 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); instance : *void = msg_fn(cls, sel_alloc);
if instance == null { print("FAIL: +alloc returned null\n"); return 1; } if instance == null { print("FAIL: +alloc returned null\n"); return 1; }

View File

@@ -39,12 +39,12 @@ main :: () -> i32 {
// alloc + release — synthesized -dealloc IMP fires inside. // alloc + release — synthesized -dealloc IMP fires inside.
sel_alloc : SEL = sel_registerName("alloc".ptr); 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); instance : *void = alloc_fn(cls, sel_alloc);
if instance == null { print("FAIL: +alloc returned null\n"); return 1; } if instance == null { print("FAIL: +alloc returned null\n"); return 1; }
sel_release : SEL = sel_registerName("release".ptr); 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); release_fn(instance, sel_release);
// Run another cycle to confirm dealloc didn't corrupt runtime state. // Run another cycle to confirm dealloc didn't corrupt runtime state.

View File

@@ -35,7 +35,7 @@ main :: () -> i32 {
if method == null { print("FAIL: class method not on metaclass\n"); return 1; } if method == null { print("FAIL: class method not on metaclass\n"); return 1; }
// Invoke via objc_msgSend: [SxFoo answer] → 42. // 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); result : i32 = msg_fn(cls, sel_answer);
if result != 42 { print("FAIL: expected 42, got {}\n", result); return 1; } if result != 42 { print("FAIL: expected 42, got {}\n", result); return 1; }

View File

@@ -38,13 +38,13 @@ main :: () -> i32 {
// [SxThing answer] → 42 // [SxThing answer] → 42
sel_answer : SEL = sel_registerName("answer".ptr); 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); r := msg_int(cls, sel_answer);
if r != 42 { print("FAIL: answer expected 42, got {}\n", r); return 1; } if r != 42 { print("FAIL: answer expected 42, got {}\n", r); return 1; }
// [SxThing seedClass] returns a non-null NSObject. // [SxThing seedClass] returns a non-null NSObject.
sel_seed : SEL = sel_registerName("seedClass".ptr); 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); seed := msg_ptr(cls, sel_seed);
if seed == null { print("FAIL: seedClass returned null\n"); return 1; } if seed == null { print("FAIL: seedClass returned null\n"); return 1; }

View File

@@ -23,10 +23,10 @@
// runtime ivar. Property dispatch should round-trip through them. // runtime ivar. Property dispatch should round-trip through them.
g_probe_tag: i32 = 0; 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; 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; g_probe_tag = v;
} }

View File

@@ -5,7 +5,7 @@
// through it and returns void. // through it and returns void.
// //
// Register a runtime-built Obj-C class with a method that returns // 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 // sret-shaped lowering already works (Phase 0.3 fix for plain
// `extern` returns). The `#objc_call` dispatch side now produces // `extern` returns). The `#objc_call` dispatch side now produces
// the matching call shape: `call void @objc_msgSend(ptr sret %slot, // 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 // IMP for the runtime-installed method. Obj-C convention: implicit
// (self, _cmd) prefix, then declared args. Returns the value bytes. // (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 } Triple.{ a = 11, b = 22, c = 33 }
} }

View File

@@ -20,7 +20,7 @@ UIEdgeInsets :: struct {
right: f64; 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 } UIEdgeInsets.{ top = 1.5, left = 2.5, bottom = 3.5, right = 4.5 }
} }

View File

@@ -13,7 +13,7 @@
#import "modules/build.sx"; #import "modules/build.sx";
#import "modules/ffi/objc.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 a * 100 + b
} }

View File

@@ -13,8 +13,8 @@
#import "modules/build.sx"; #import "modules/build.sx";
#import "modules/ffi/objc.sx"; #import "modules/ffi/objc.sx";
yes_imp :: (self: *void, _cmd: *void) -> bool callconv(.c) { true } yes_imp :: (self: *void, _cmd: *void) -> bool abi(.c) { true }
no_imp :: (self: *void, _cmd: *void) -> bool callconv(.c) { false } no_imp :: (self: *void, _cmd: *void) -> bool abi(.c) { false }
main :: () -> i32 { main :: () -> i32 {
inline if OS == .macos { inline if OS == .macos {

View File

@@ -20,11 +20,11 @@ CGRect :: struct {
height: f64; 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 } 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 // sx integer-literal parser rejects values ≥ 2^63 even when the
// receiving type is u64, so the leading bit stays clear. // receiving type is u64, so the leading bit stays clear.
0x7FEDCBA987654321 0x7FEDCBA987654321

View File

@@ -51,7 +51,7 @@ main :: () -> i32 {
// release // release
sel_release : SEL = sel_registerName("release".ptr); 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); release_fn(xx f, sel_release);
} }
inline if OS != .macos { inline if OS != .macos {

View File

@@ -52,7 +52,7 @@ main :: () -> i32 {
print("at: ({}, {})\n", p.x, p.y); // expected: at: (7.500000, 8.250000) print("at: ({}, {})\n", p.x, p.y); // expected: at: (7.500000, 8.250000)
sel_release : SEL = sel_registerName("release".ptr); 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); release_fn(xx m, sel_release);
} }
inline if OS != .macos { inline if OS != .macos {

View File

@@ -20,7 +20,7 @@ SxProbeNiladic :: #objc_class("SxProbeNiladic") extern {
length :: (self: *Self) -> i32; length :: (self: *Self) -> i32;
} }
length_imp :: (self: *void, _cmd: *void) -> i32 callconv(.c) { length_imp :: (self: *void, _cmd: *void) -> i32 abi(.c) {
42 42
} }

View File

@@ -12,7 +12,7 @@ SxProbeOneArg :: #objc_class("SxProbeOneArg") extern {
addObject :: (self: *Self, val: i32) -> i32; 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 val * 2
} }

View File

@@ -11,7 +11,7 @@ SxProbeMultiKeyword :: #objc_class("SxProbeMultiKeyword") extern {
combine_and :: (self: *Self, a: i32, b: i32) -> i32; 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 a * 100 + b
} }

View File

@@ -27,7 +27,7 @@ SxManglingProbe :: #objc_class("SxManglingProbe") extern {
custom_name :: (self: *Self) -> i32 #selector("actualSelectorName"); 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 // Returns the arg count's witness; the test doesn't check return
// values, only that dispatch succeeds for each selector shape. // values, only that dispatch succeeds for each selector shape.
a + b + c + d a + b + c + d

View File

@@ -31,7 +31,7 @@ main :: () -> i32 {
print("counter: {}\n", b.get()); // expected: 2 print("counter: {}\n", b.get()); // expected: 2
sel_release : SEL = sel_registerName("release".ptr); 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); release_fn(xx b, sel_release);
} }
inline if OS != .macos { inline if OS != .macos {

View File

@@ -21,7 +21,7 @@ UIApplicationMain :: (argc: i32, argv: *void, principal_class: *NSString, delega
// IMP for application:didFinishLaunchingWithOptions: // IMP for application:didFinishLaunchingWithOptions:
// Obj-C: -(BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)opts // Obj-C: -(BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)opts
// Type encoding: "c@:@@" -- BOOL (signed char), self, _cmd, id, id // 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"); NSLog(xx "[sx] application:didFinishLaunchingWithOptions: called\n");
return 1; // YES return 1; // YES
} }

View File

@@ -25,14 +25,14 @@ g_window : *void = ---;
// AppDelegate's `window` property. iOS queries this getter to discover the // 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 // app's key window; without it, the legacy code path creates its own empty
// window and ignores anything we configure. // 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; 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; 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); UIWindow := objc_getClass("UIWindow".ptr);
UIViewController := objc_getClass("UIViewController".ptr); UIViewController := objc_getClass("UIViewController".ptr);
UIColor := objc_getClass("UIColor".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_connected_scenes := sel_registerName("connectedScenes".ptr);
sel_any_object := sel_registerName("anyObject".ptr); sel_any_object := sel_registerName("anyObject".ptr);
msg_o : (*void, *void) -> *void callconv(.c) = xx objc_msgSend; msg_o : (*void, *void) -> *void abi(.c) = xx objc_msgSend;
msg_v : (*void, *void) -> void callconv(.c) = xx objc_msgSend; msg_v : (*void, *void) -> void abi(.c) = xx objc_msgSend;
msg_oo : (*void, *void, *void) -> void callconv(.c) = xx objc_msgSend; msg_oo : (*void, *void, *void) -> void abi(.c) = xx objc_msgSend;
msg_ooo : (*void, *void, *void) -> *void callconv(.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 // Modern iOS path: get the connected windowScene, then construct the
// window via initWithWindowScene: so UIKit auto-sizes it and attaches // window via initWithWindowScene: so UIKit auto-sizes it and attaches

View File

@@ -39,7 +39,7 @@ configure :: () {
// IMP for application:didFinishLaunchingWithOptions: // IMP for application:didFinishLaunchingWithOptions:
// Obj-C: -(BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)opts // Obj-C: -(BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)opts
// Type encoding: "c@:@@" -- BOOL (signed char), self, _cmd, id, id // 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"); NSLog(xx "[sx-device-probe] launched\n");
return 1; // YES return 1; // YES
} }

View File

@@ -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. // typed C function pointer — libc qsort drives an sx comparator.
#import "modules/std.sx"; #import "modules/std.sx";
clib :: #library "c"; 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; pa : *i32 = xx a;
pb : *i32 = xx b; pb : *i32 = xx b;
if pa.* < pb.* { return -1; } if pa.* < pb.* { return -1; }

View File

@@ -1,14 +1,14 @@
// PLAN-HTTPZ C2: the C->sx re-entry contract. A real OS thread enters // 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 }`, // fabricates its own Context via `push Context.{ allocator = xx gpa }`,
// and calls default-conv sx code that allocates through it. // and calls default-conv sx code that allocates through it.
#import "modules/std.sx"; #import "modules/std.sx";
clib :: #library "c"; 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; 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; p : *i64 = xx arg;
gpa := GPA.init(); gpa := GPA.init();
push Context.{ allocator = xx gpa } { push Context.{ allocator = xx gpa } {

View File

@@ -11,7 +11,7 @@ Shared :: struct {
} }
// Raw-thread entry: C->sx boundary, own fabricated context. // 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; sh : *Shared = xx arg;
gpa := GPA.init(); gpa := GPA.init();
push Context.{ allocator = xx gpa } { push Context.{ allocator = xx gpa } {

View File

@@ -2,7 +2,7 @@
// CALLS BACK into an sx function (`cb`) by its symbol, then returns. For the asm // 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 // `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 // 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 // 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 // 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 // undefined at link. macOS gives `export "cb"` the symbol `_cb` (leading

View File

@@ -1,10 +1,10 @@
--- void / 0 args --- --- 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 --- --- 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 --- --- 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 --- --- 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 --- --- build done ---
rt rt

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
name=7 ty=3

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
hello, compiler

View File

@@ -1,5 +1,5 @@
error: call-convention mismatch: 'sx_handler' is declared with default sx convention but the target type expects callconv(.c) 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:42 --> 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;
| ^^^^^^^^^^ | ^^^^^^^^^^

View File

@@ -0,0 +1 @@
1

View File

@@ -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; }
| ^^^

View File

@@ -0,0 +1 @@
1

View File

@@ -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;
| ^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
1

View File

@@ -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 '::')

View File

@@ -1 +1 @@
callconv(.c): 42 abi(.c): 42

View File

@@ -53,7 +53,7 @@ objc_msgSend :: (recv: *void, sel: *void) -> *void extern objc;
// //
// IMPs (method implementations) are function pointers with the implicit // IMPs (method implementations) are function pointers with the implicit
// Obj-C method shape: `(self: *void, _cmd: *void, ...args) -> ret` with // 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: // Method type encoding strings follow Apple's runtime DSL:
// v = void c = char/BOOL i = int l = long f = float d = double // 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 { convert :: (self: string) -> *NSString {
cls := objc_getClass("NSString".ptr); cls := objc_getClass("NSString".ptr);
sel := sel_registerName("stringWithUTF8String:".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); return xx msg(cls, sel, self.ptr);
} }
} }

View File

@@ -63,7 +63,7 @@ __sx_block_descriptor : BlockDescriptor = .{
// `$args` is the bound pack of arg types and `$R` is the bound // `$args` is the bound pack of arg types and `$R` is the bound
// return type. The `#insert` evaluates `build_block_convert` at // return type. The `#insert` evaluates `build_block_convert` at
// comptime and substitutes the resulting source — a nested // 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- // `invoke` slot at it. One impl in stdlib replaces every per-
// signature hand-rolled `__block_invoke_*` + `Into(Block)` pair. // signature hand-rolled `__block_invoke_*` + `Into(Block)` pair.
impl Into(Block) for Closure(..$args) -> $R { 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 // (`args`, a comptime `[]Type`) and the closure's return type
// (`$ret`), emits source that: // (`$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 // signature matches the per-shape Apple Block ABI: first arg is
// `block_self: *Block`, then the pack types verbatim. The body // `block_self: *Block`, then the pack types verbatim. The body
// reconstructs a typed fn-pointer from `block_self.sx_fn`, // 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, ") -> ");
code = concat(code, ret_name); code = concat(code, ret_name);
code = concat(code, " callconv(.c) { typed_fn : (*void"); code = concat(code, " abi(.c) { typed_fn : (*void");
i = 0; i = 0;
while i < args.len { while i < args.len {
code = concat(code, ", "); code = concat(code, ", ");

View File

@@ -25,39 +25,39 @@ GL_LINE :u32: 0x1B01;
GL_FILL :u32: 0x1B02; GL_FILL :u32: 0x1B02;
// Function pointer variables (mutable, loaded at runtime) // Function pointer variables (mutable, loaded at runtime)
glClearColor : (f32, f32, f32, f32) -> void callconv(.c) = ---; glClearColor : (f32, f32, f32, f32) -> void abi(.c) = ---;
glClear : (u32) -> void callconv(.c) = ---; glClear : (u32) -> void abi(.c) = ---;
glEnable : (u32) -> void callconv(.c) = ---; glEnable : (u32) -> void abi(.c) = ---;
glDisable : (u32) -> void callconv(.c) = ---; glDisable : (u32) -> void abi(.c) = ---;
glViewport : (i32, i32, i32, i32) -> void callconv(.c) = ---; glViewport : (i32, i32, i32, i32) -> void abi(.c) = ---;
glFlush : () -> void callconv(.c) = ---; glFlush : () -> void abi(.c) = ---;
glDrawArrays : (u32, i32, i32) -> void callconv(.c) = ---; glDrawArrays : (u32, i32, i32) -> void abi(.c) = ---;
glPolygonMode : (u32, u32) -> void callconv(.c) = ---; glPolygonMode : (u32, u32) -> void abi(.c) = ---;
glLineWidth : (f32) -> void callconv(.c) = ---; glLineWidth : (f32) -> void abi(.c) = ---;
glCreateShader : (u32) -> u32 callconv(.c) = ---; glCreateShader : (u32) -> u32 abi(.c) = ---;
glShaderSource : (u32, i32, *[:0]u8, *i32) -> void callconv(.c) = ---; glShaderSource : (u32, i32, *[:0]u8, *i32) -> void abi(.c) = ---;
glCompileShader : (u32) -> void callconv(.c) = ---; glCompileShader : (u32) -> void abi(.c) = ---;
glGetShaderiv : (u32, u32, *i32) -> void callconv(.c) = ---; glGetShaderiv : (u32, u32, *i32) -> void abi(.c) = ---;
glGetShaderInfoLog : (u32, i32, *i32, [*]u8) -> void callconv(.c) = ---; glGetShaderInfoLog : (u32, i32, *i32, [*]u8) -> void abi(.c) = ---;
glCreateProgram : () -> u32 callconv(.c) = ---; glCreateProgram : () -> u32 abi(.c) = ---;
glAttachShader : (u32, u32) -> void callconv(.c) = ---; glAttachShader : (u32, u32) -> void abi(.c) = ---;
glLinkProgram : (u32) -> void callconv(.c) = ---; glLinkProgram : (u32) -> void abi(.c) = ---;
glGetProgramiv : (u32, u32, *i32) -> void callconv(.c) = ---; glGetProgramiv : (u32, u32, *i32) -> void abi(.c) = ---;
glGetProgramInfoLog : (u32, i32, *i32, [*]u8) -> void callconv(.c) = ---; glGetProgramInfoLog : (u32, i32, *i32, [*]u8) -> void abi(.c) = ---;
glUseProgram : (u32) -> void callconv(.c) = ---; glUseProgram : (u32) -> void abi(.c) = ---;
glDeleteShader : (u32) -> void callconv(.c) = ---; glDeleteShader : (u32) -> void abi(.c) = ---;
glGenVertexArrays : (i32, *u32) -> void callconv(.c) = ---; glGenVertexArrays : (i32, *u32) -> void abi(.c) = ---;
glGenBuffers : (i32, *u32) -> void callconv(.c) = ---; glGenBuffers : (i32, *u32) -> void abi(.c) = ---;
glBindVertexArray : (u32) -> void callconv(.c) = ---; glBindVertexArray : (u32) -> void abi(.c) = ---;
glBindBuffer : (u32, u32) -> void callconv(.c) = ---; glBindBuffer : (u32, u32) -> void abi(.c) = ---;
glBufferData : (u32, isize, *void, u32) -> void callconv(.c) = ---; glBufferData : (u32, isize, *void, u32) -> void abi(.c) = ---;
glVertexAttribPointer : (u32, i32, u32, u8, i32, *void) -> void callconv(.c) = ---; glVertexAttribPointer : (u32, i32, u32, u8, i32, *void) -> void abi(.c) = ---;
glEnableVertexAttribArray : (u32) -> void callconv(.c) = ---; glEnableVertexAttribArray : (u32) -> void abi(.c) = ---;
glGetUniformLocation : (u32, [*]u8) -> i32 callconv(.c) = ---; glGetUniformLocation : (u32, [*]u8) -> i32 abi(.c) = ---;
glUniformMatrix4fv : (i32, i32, u8, [*]f32) -> void callconv(.c) = ---; glUniformMatrix4fv : (i32, i32, u8, [*]f32) -> void abi(.c) = ---;
glUniform3f : (i32, f32, f32, f32) -> void callconv(.c) = ---; glUniform3f : (i32, f32, f32, f32) -> void abi(.c) = ---;
glDepthFunc : (u32) -> void callconv(.c) = ---; glDepthFunc : (u32) -> void abi(.c) = ---;
glUniform1f : (i32, f32) -> void callconv(.c) = ---; glUniform1f : (i32, f32) -> void abi(.c) = ---;
GL_LESS :u32: 0x0201; GL_LESS :u32: 0x0201;
GL_LEQUAL :u32: 0x0203; GL_LEQUAL :u32: 0x0203;
GL_SCISSOR_TEST :u32: 0x0C11; GL_SCISSOR_TEST :u32: 0x0C11;
@@ -76,27 +76,27 @@ GL_RED :u32: 0x1903;
GL_R8 :u32: 0x8229; GL_R8 :u32: 0x8229;
GL_UNPACK_ALIGNMENT :u32: 0x0CF5; GL_UNPACK_ALIGNMENT :u32: 0x0CF5;
glScissor : (i32, i32, i32, i32) -> void callconv(.c) = ---; glScissor : (i32, i32, i32, i32) -> void abi(.c) = ---;
glBufferSubData : (u32, isize, isize, *void) -> void callconv(.c) = ---; glBufferSubData : (u32, isize, isize, *void) -> void abi(.c) = ---;
glGenTextures : (i32, *u32) -> void callconv(.c) = ---; glGenTextures : (i32, *u32) -> void abi(.c) = ---;
glBindTexture : (u32, u32) -> void callconv(.c) = ---; glBindTexture : (u32, u32) -> void abi(.c) = ---;
glTexImage2D : (u32, i32, i32, i32, i32, i32, u32, u32, *void) -> void callconv(.c) = ---; glTexImage2D : (u32, i32, i32, i32, i32, i32, u32, u32, *void) -> void abi(.c) = ---;
glTexParameteri : (u32, u32, i32) -> void callconv(.c) = ---; glTexParameteri : (u32, u32, i32) -> void abi(.c) = ---;
glBlendFunc : (u32, u32) -> void callconv(.c) = ---; glBlendFunc : (u32, u32) -> void abi(.c) = ---;
glReadPixels : (i32, i32, i32, i32, u32, u32, *void) -> void callconv(.c) = ---; glReadPixels : (i32, i32, i32, i32, u32, u32, *void) -> void abi(.c) = ---;
glActiveTexture : (u32) -> void callconv(.c) = ---; glActiveTexture : (u32) -> void abi(.c) = ---;
glUniform1i : (i32, i32) -> void callconv(.c) = ---; glUniform1i : (i32, i32) -> void abi(.c) = ---;
glPixelStorei : (u32, i32) -> void callconv(.c) = ---; glPixelStorei : (u32, i32) -> void abi(.c) = ---;
glTexSubImage2D : (u32, i32, i32, i32, i32, i32, u32, u32, *void) -> void callconv(.c) = ---; glTexSubImage2D : (u32, i32, i32, i32, i32, i32, u32, u32, *void) -> void abi(.c) = ---;
glDeleteTextures : (i32, *u32) -> void callconv(.c) = ---; glDeleteTextures : (i32, *u32) -> void abi(.c) = ---;
glGenFramebuffers : (i32, *u32) -> void callconv(.c) = ---; glGenFramebuffers : (i32, *u32) -> void abi(.c) = ---;
glGenRenderbuffers : (i32, *u32) -> void callconv(.c) = ---; glGenRenderbuffers : (i32, *u32) -> void abi(.c) = ---;
glBindFramebuffer : (u32, u32) -> void callconv(.c) = ---; glBindFramebuffer : (u32, u32) -> void abi(.c) = ---;
glBindRenderbuffer : (u32, u32) -> void callconv(.c) = ---; glBindRenderbuffer : (u32, u32) -> void abi(.c) = ---;
glFramebufferRenderbuffer : (u32, u32, u32, u32) -> void callconv(.c) = ---; glFramebufferRenderbuffer : (u32, u32, u32, u32) -> void abi(.c) = ---;
glGetRenderbufferParameteriv : (u32, u32, *i32) -> void callconv(.c) = ---; glGetRenderbufferParameteriv : (u32, u32, *i32) -> void abi(.c) = ---;
glCheckFramebufferStatus : (u32) -> u32 callconv(.c) = ---; glCheckFramebufferStatus : (u32) -> u32 abi(.c) = ---;
GL_TEXTURE_WRAP_S :u32: 0x2802; GL_TEXTURE_WRAP_S :u32: 0x2802;
GL_TEXTURE_WRAP_T :u32: 0x2803; GL_TEXTURE_WRAP_T :u32: 0x2803;
@@ -104,7 +104,7 @@ GL_CLAMP_TO_EDGE :u32: 0x812F;
// Loader: call once after creating GL context // Loader: call once after creating GL context
// Pass in a proc loader (e.g. SDL_GL_GetProcAddress) // 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"); glClearColor = xx get_proc("glClearColor");
glClear = xx get_proc("glClear"); glClear = xx get_proc("glClear");
glEnable = xx get_proc("glEnable"); glEnable = xx get_proc("glEnable");

View File

@@ -58,7 +58,7 @@ MTLClearColor :: struct {
// MTLRegion is 48 bytes and MTLScissorRect is 32 bytes; both are passed // MTLRegion is 48 bytes and MTLScissorRect is 32 bytes; both are passed
// by value to the Obj-C runtime, which the compiler marshals as // by value to the Obj-C runtime, which the compiler marshals as
// `ptr byval(<T>)` via the C-ABI byval coercion. The fn-pointer cast // `ptr byval(<T>)` 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; } MTLOrigin :: struct { x: u64; y: u64; z: u64; }
MTLSize :: struct { width: u64; height: u64; depth: u64; } MTLSize :: struct { width: u64; height: u64; depth: u64; }
MTLRegion :: struct { origin: MTLOrigin; size: MTLSize; } MTLRegion :: struct { origin: MTLOrigin; size: MTLSize; }
@@ -257,11 +257,11 @@ metal_init_ios :: (self: *MetalGPU) -> bool {
if self.device == null { return false; } if self.device == null { return false; }
} }
msg_oo : (*void, *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 callconv(.c) = xx objc_msgSend; msg_ou : (*void, *void, u64) -> void abi(.c) = xx objc_msgSend;
msg_ob : (*void, *void, u8) -> void callconv(.c) = xx objc_msgSend; msg_ob : (*void, *void, u8) -> void abi(.c) = xx objc_msgSend;
msg_osize : (*void, *void, CGSize) -> void callconv(.c) = xx objc_msgSend; msg_osize : (*void, *void, CGSize) -> void abi(.c) = xx objc_msgSend;
msg_o : (*void, *void) -> *void callconv(.c) = xx objc_msgSend; msg_o : (*void, *void) -> *void abi(.c) = xx objc_msgSend;
if self.queue == null { if self.queue == null {
self.queue = msg_o(self.device, sel_registerName("newCommandQueue".ptr)); self.queue = msg_o(self.device, sel_registerName("newCommandQueue".ptr));
@@ -290,7 +290,7 @@ metal_resize_ios :: (self: *MetalGPU) {
inline if OS != .ios { return; } inline if OS != .ios { return; }
if self.layer == null { 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 }; size := CGSize.{ width = xx self.pixel_w, height = xx self.pixel_h };
msg_osize(self.layer, sel_registerName("setDrawableSize:".ptr), size); 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.queue == null { return false; }
if self.pixel_w <= 0 or self.pixel_h <= 0 { 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_o : (*void, *void) -> *void abi(.c) = xx objc_msgSend;
msg_oo : (*void, *void, *void) -> void callconv(.c) = xx objc_msgSend; msg_oo : (*void, *void, *void) -> void abi(.c) = xx objc_msgSend;
msg_oo_ret : (*void, *void, *void) -> *void callconv(.c) = xx objc_msgSend; msg_oo_ret : (*void, *void, *void) -> *void abi(.c) = xx objc_msgSend;
msg_ou : (*void, *void, u64) -> void callconv(.c) = xx objc_msgSend; msg_ou : (*void, *void, u64) -> void abi(.c) = xx objc_msgSend;
msg_ouret : (*void, *void, u64) -> *void callconv(.c) = xx objc_msgSend; msg_ouret : (*void, *void, u64) -> *void abi(.c) = xx objc_msgSend;
msg_oclear : (*void, *void, MTLClearColor) -> void callconv(.c) = xx objc_msgSend; msg_oclear : (*void, *void, MTLClearColor) -> void abi(.c) = xx objc_msgSend;
// drawable = [layer nextDrawable] // drawable = [layer nextDrawable]
self.drawable = msg_o(self.layer, sel_registerName("nextDrawable".ptr)); 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.cmd_buffer == null { return; }
if self.drawable == null { return; } if self.drawable == null { return; }
msg_v : (*void, *void) -> void callconv(.c) = xx objc_msgSend; msg_v : (*void, *void) -> void abi(.c) = xx objc_msgSend;
msg_oo : (*void, *void, *void) -> void callconv(.c) = xx objc_msgSend; msg_oo : (*void, *void, *void) -> void abi(.c) = xx objc_msgSend;
msg_ood : (*void, *void, *void, f64) -> void callconv(.c) = xx objc_msgSend; msg_ood : (*void, *void, *void, f64) -> void abi(.c) = xx objc_msgSend;
msg_v(self.encoder, sel_registerName("endEncoding".ptr)); 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; } inline if OS != .ios { return 0; }
if self.device == null { return 0; } if self.device == null { return 0; }
msg_o : (*void, *void) -> *void callconv(.c) = xx objc_msgSend; msg_o : (*void, *void) -> *void abi(.c) = xx objc_msgSend;
msg_oo : (*void, *void, *void) -> void callconv(.c) = xx objc_msgSend; msg_oo : (*void, *void, *void) -> void abi(.c) = xx objc_msgSend;
msg_oo_r : (*void, *void, *NSString) -> *void callconv(.c) = xx objc_msgSend; msg_oo_r : (*void, *void, *NSString) -> *void abi(.c) = xx objc_msgSend;
msg_ou : (*void, *void, u64) -> void callconv(.c) = xx objc_msgSend; msg_ou : (*void, *void, u64) -> void abi(.c) = xx objc_msgSend;
msg_ouret: (*void, *void, u64) -> *void callconv(.c) = xx objc_msgSend; msg_ouret: (*void, *void, u64) -> *void abi(.c) = xx objc_msgSend;
msg_ob : (*void, *void, u8) -> void callconv(.c) = xx objc_msgSend; msg_ob : (*void, *void, u8) -> void abi(.c) = xx objc_msgSend;
// [device newLibraryWithSource:src options:nil error:&err] // [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; err : *void = null;
library := msg_lib(self.device, library := msg_lib(self.device,
sel_registerName("newLibraryWithSource:options:error:".ptr), 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("setSourceAlphaBlendFactor:".ptr), MTL_BLEND_FACTOR_SRC_ALPHA);
msg_ou(att0, sel_registerName("setDestinationAlphaBlendFactor:".ptr), MTL_BLEND_FACTOR_ONE_MINUS_SRC_A); 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; err2 : *void = null;
state := msg_pipe(self.device, state := msg_pipe(self.device,
sel_registerName("newRenderPipelineStateWithDescriptor:error:".ptr), 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; } if size_bytes <= 0 { return 0; }
// MTLResourceStorageModeShared is the default (option value 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, buf := msg_buf(self.device,
sel_registerName("newBufferWithLength:options:".ptr), sel_registerName("newBufferWithLength:options:".ptr),
xx size_bytes, 0); 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 data == null { return; }
if size_bytes <= 0 { 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)); dst := msg_o(buf, sel_registerName("contents".ptr));
if dst == null { return; } if dst == null { return; }
memcpy(dst, data, size_bytes); 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 size_bytes <= 0 { return; }
if byte_offset < 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)); base := msg_o(buf, sel_registerName("contents".ptr));
if base == null { return; } if base == null { return; }
// Add byte_offset via integer arithmetic — `@dst[i]` on `[*]u8` // 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 texture2DDescriptorWithPixelFormat:width:height:mipmapped:]
MTLTextureDescriptor := objc_getClass("MTLTextureDescriptor".ptr); 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, desc := msg_desc(MTLTextureDescriptor,
sel_registerName("texture2DDescriptorWithPixelFormat:width:height:mipmapped:".ptr), sel_registerName("texture2DDescriptorWithPixelFormat:width:height:mipmapped:".ptr),
pixel_format, xx w, xx h, 0); 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, // Force shared storage so the CPU can keep writing pixels (atlas updates,
// sprite uploads). On iOS-sim under Apple Silicon the convenience class // sprite uploads). On iOS-sim under Apple Silicon the convenience class
// method's default storage isn't reliably shared for every format. // 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_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); tex := msg_oo(self.device, sel_registerName("newTextureWithDescriptor:".ptr), desc);
if tex == null { return 0; } 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); bytes_per_row : u64 = xx (slot.bytes_per_pixel * cast(u32) w);
// [tex replaceRegion:region mipmapLevel:0 withBytes:pixels bytesPerRow:bytes_per_row] // [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, msg_replace(slot.tex,
sel_registerName("replaceRegion:mipmapLevel:withBytes:bytesPerRow:".ptr), sel_registerName("replaceRegion:mipmapLevel:withBytes:bytesPerRow:".ptr),
region, 0, pixels, bytes_per_row); region, 0, pixels, bytes_per_row);
@@ -596,7 +596,7 @@ metal_destroy_shader_ios :: (self: *MetalGPU, handle: u32) {
if h64 > self.shaders.len { return; } if h64 > self.shaders.len { return; }
obj := self.shaders.items[handle - 1]; obj := self.shaders.items[handle - 1];
if obj == null { return; } 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)); msg(obj, sel_registerName("release".ptr));
self.shaders.items[handle - 1] = null; self.shaders.items[handle - 1] = null;
} }
@@ -608,7 +608,7 @@ metal_destroy_buffer_ios :: (self: *MetalGPU, handle: u32) {
if h64 > self.buffers.len { return; } if h64 > self.buffers.len { return; }
obj := self.buffers.items[handle - 1]; obj := self.buffers.items[handle - 1];
if obj == null { return; } 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)); msg(obj, sel_registerName("release".ptr));
self.buffers.items[handle - 1] = null; self.buffers.items[handle - 1] = null;
} }
@@ -620,7 +620,7 @@ metal_destroy_texture_ios :: (self: *MetalGPU, handle: u32) {
if h64 > self.textures.len { return; } if h64 > self.textures.len { return; }
obj := self.textures.items[handle - 1].tex; obj := self.textures.items[handle - 1].tex;
if obj == null { return; } 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)); msg(obj, sel_registerName("release".ptr));
self.textures.items[handle - 1].tex = null; self.textures.items[handle - 1].tex = null;
self.textures.items[handle - 1].bytes_per_pixel = 0; 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; } if self.encoder == null { return; }
state := metal_lookup_shader(self, sh); state := metal_lookup_shader(self, sh);
if state == null { return; } 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); 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); buf := metal_lookup_buffer(self, h);
if buf == null { return; } if buf == null { return; }
// [encoder setVertexBuffer:buf offset:0 atIndex:0] // [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); 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; tex := self.textures.items[h - 1].tex;
if tex == null { return; } if tex == null { return; }
// [encoder setFragmentTexture:tex atIndex:slot] // [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); 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 data == null { return; }
if size_bytes <= 0 { return; } if size_bytes <= 0 { return; }
// [encoder setVertexBytes:data length:size_bytes atIndex:slot] // [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), msg(self.encoder, sel_registerName("setVertexBytes:length:atIndex:".ptr),
data, xx size_bytes, xx slot); 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; } if self.encoder == null { return; }
rect : MTLScissorRect = .{ x = xx x, y = xx y, width = xx w, height = xx h }; rect : MTLScissorRect = .{ x = xx x, y = xx y, width = xx w, height = xx h };
// [encoder setScissorRect:rect] (MTLScissorRect is 32 bytes → ptr byval) // [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); 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 // Metal has no "disable scissor" — set the rect to cover the full
// drawable so subsequent draws aren't clipped. // drawable so subsequent draws aren't clipped.
rect : MTLScissorRect = .{ x = 0, y = 0, width = xx self.pixel_w, height = xx self.pixel_h }; 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); 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 self.encoder == null { return; }
if vertex_count <= 0 { return; } if vertex_count <= 0 { return; }
// [encoder drawPrimitives:.triangle vertexStart:offset vertexCount:count] // [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), msg(self.encoder, sel_registerName("drawPrimitives:vertexStart:vertexCount:".ptr),
MTL_PRIMITIVE_TYPE_TRIANGLE, xx vertex_offset, xx vertex_count); MTL_PRIMITIVE_TYPE_TRIANGLE, xx vertex_offset, xx vertex_count);
} }

View File

@@ -85,7 +85,7 @@ ANativeWindow_setBuffersGeometry :: (w: *void, width: i32, height: i32, fmt: i32
AAssetManager_fromJava :: (env: *void, mgr: *void) -> *void extern; AAssetManager_fromJava :: (env: *void, mgr: *void) -> *void extern;
// pthread (link libpthread is built into bionic). // 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_init :: (m: *void, attr: *void) -> i32 extern;
pthread_mutex_lock :: (m: *void) -> i32 extern; pthread_mutex_lock :: (m: *void) -> i32 extern;
pthread_mutex_unlock :: (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; 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; plat : *AndroidPlatform = xx arg;
while plat.app_window == null and !plat.should_stop { while plat.app_window == null and !plat.should_stop {
usleep(1000); usleep(1000);

View File

@@ -229,7 +229,7 @@ impl Platform for SdlPlatform {
// SDL fires the watch synchronously when events are added — including during // 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 // 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. // 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; plat : *SdlPlatform = xx userdata;
if event.* == { if event.* == {
case .window_resized: (data) { case .window_resized: (data) {
@@ -245,7 +245,7 @@ sdl_event_watch :: (userdata: *void, event: *SDL_Event) -> bool callconv(.c) {
true true
} }
sdl_wasm_tick :: () callconv(.c) { sdl_wasm_tick :: () abi(.c) {
if g_sdl_plat == null { return; } if g_sdl_plat == null { return; }
if !g_sdl_plat.has_frame_closure { return; } if !g_sdl_plat.has_frame_closure { return; }
fn := g_sdl_plat.frame_closure; fn := g_sdl_plat.frame_closure;

View File

@@ -790,8 +790,8 @@ impl Platform for UIKitPlatform {
} }
// dlsym(RTLD_DEFAULT, name) — Apple platforms. RTLD_DEFAULT is (void*)-2. // 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. // abi(.c) so this is callable from `load_gl`'s C-conv proc-loader slot.
ios_gl_proc :: (name: [*]u8) -> *void callconv(.c) { ios_gl_proc :: (name: [*]u8) -> *void abi(.c) {
rtld_default : *void = xx (0 - 2); rtld_default : *void = xx (0 - 2);
dlsym(rtld_default, name) dlsym(rtld_default, name)
} }

View File

@@ -2,7 +2,7 @@
// pthreads (PLAN-HTTPZ S6). // pthreads (PLAN-HTTPZ S6).
// //
// THE RE-ENTRY CONTRACT (pinned by examples/1636): a thread entry is a // 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 }` // the sx world by fabricating one: `push Context.{ allocator = xx gpa }`
// around the default-conv code it runs. Pool workers do exactly that, // around the default-conv code it runs. Pool workers do exactly that,
// each with its own malloc-backed GPA, so tasks allocate freely and // each with its own malloc-backed GPA, so tasks allocate freely and
@@ -26,7 +26,7 @@
tlib :: #library "c"; 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_join :: (thread: usize, retval: **void) -> i32 extern tlib;
pthread_detach :: (thread: usize) -> i32 extern tlib; pthread_detach :: (thread: usize) -> i32 extern tlib;
@@ -104,9 +104,9 @@ Cond :: struct {
Thread :: struct { Thread :: struct {
handle: usize = 0; 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). // 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 = .{}; t : Thread = .{};
if pthread_create(@t.handle, null, entry, arg) != 0 { raise error.Spawn; } if pthread_create(@t.handle, null, entry, arg) != 0 { raise error.Spawn; }
return t; return t;
@@ -200,7 +200,7 @@ Pool :: struct {
// The worker loop: C entry, own fabricated Context, then // The worker loop: C entry, own fabricated Context, then
// pop-task/run-task until stop with an empty queue. // 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; p : *Pool = xx arg;
gpa := GPA.init(); gpa := GPA.init();
push Context.{ allocator = xx gpa } { push Context.{ allocator = xx gpa } {

View File

@@ -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 `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 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 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). to Obj-C / JNI runtime-class aggregates (postfix after the `#objc_class(…)` directive).
```sx ```sx

View File

@@ -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 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 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. 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 Both imply `abi(.c)`, carry external linkage, and suppress the implicit sx
context parameter. They are postfix modifiers, written where `callconv` would go. context parameter. They are postfix modifiers, written in the slot after the
`abi(...)` annotation.
```sx ```sx
// Declare a named library constant // Declare a named library constant

View File

@@ -122,14 +122,26 @@ pub const Root = struct {
decls: []const *Node, 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(...)`: /// Linkage modifier written in the postfix slot before `abi(...)`:
/// `name :: (sig) -> Ret [callconv(.x)] [extern | export] [;|{…}];` /// `name :: (sig) -> Ret [extern | export] [abi(.x)] [lib] [;|{…}];`
/// `extern` = import (external linkage, C ABI, no sx ctx — `extern`'s role); /// `extern` = import (external linkage, no sx ctx — `extern`'s role);
/// `export` = define + expose (body + external linkage + C ABI + no ctx). /// `export` = define + expose (body + external linkage + no ctx).
/// Both imply `callconv(.c)`. Variants carry a trailing `_` to dodge the Zig /// Variants carry a trailing `_` to dodge the Zig keywords. `.none` = no linkage
/// keywords. `.none` = no linkage modifier (the ordinary sx-internal decl). /// modifier (the ordinary sx-internal decl).
pub const ExternExportModifier = enum { none, extern_, export_ }; pub const ExternExportModifier = enum { none, extern_, export_ };
pub const FnDecl = struct { pub const FnDecl = struct {
@@ -139,10 +151,15 @@ pub const FnDecl = struct {
body: *Node, body: *Node,
type_params: []const StructTypeParam = &.{}, type_params: []const StructTypeParam = &.{},
is_arrow: bool = false, is_arrow: bool = false,
call_conv: CallingConvention = .default, /// ABI / calling-convention annotation (`abi(.c)` / `abi(.zig)` / `abi(.pure)`)
/// Postfix linkage modifier (`extern`/`export`) written after the /// in the postfix slot after `extern`/`export`. `.default` = unannotated.
/// `callconv(...)` slot. `.none` for an ordinary sx-internal function. /// `.zig` marks a function bound to the comptime `compiler` library — its
/// Parsed in Phase 0.1; not consumed by the fn-decl path until Phase 1. /// 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, extern_export: ExternExportModifier = .none,
/// Optional library reference + symbol-name override for an `extern`/`export` /// Optional library reference + symbol-name override for an `extern`/`export`
/// function, the optional library + symbol-name override. Both /// function, the optional library + symbol-name override. Both
@@ -510,6 +527,15 @@ pub const StructDecl = struct {
using_entries: []const UsingEntry = &.{}, using_entries: []const UsingEntry = &.{},
methods: []const *Node = &.{}, // fn_decl nodes for struct methods methods: []const *Node = &.{}, // fn_decl nodes for struct methods
constants: []const *Node = &.{}, // const_decl nodes for struct-level constants constants: []const *Node = &.{}, // const_decl nodes for struct-level constants
/// ABI / layout annotation (`struct abi(.zig) extern <lib> { … }`). `.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 <lib>` 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 /// True when the declared NAME was a backtick raw identifier
/// (`` `i2 :: struct { … } ``) — exempt from the reserved-type-name decl /// (`` `i2 :: struct { … } ``) — exempt from the reserved-type-name decl
/// check. A bare reserved-name decl still errors. /// check. A bare reserved-name decl still errors.
@@ -533,7 +559,7 @@ pub const Lambda = struct {
return_type: ?*Node, return_type: ?*Node,
body: *Node, body: *Node,
type_params: []const StructTypeParam = &.{}, type_params: []const StructTypeParam = &.{},
call_conv: CallingConvention = .default, abi: ABI = .default,
}; };
pub const TypeExpr = struct { pub const TypeExpr = struct {
@@ -805,7 +831,7 @@ pub const FunctionTypeExpr = struct {
param_types: []const *Node, param_types: []const *Node,
param_names: ?[]const ?[]const u8 = null, // optional documentation names param_names: ?[]const ?[]const u8 = null, // optional documentation names
return_type: ?*Node, // null = void return return_type: ?*Node, // null = void return
call_conv: CallingConvention = .default, abi: ABI = .default,
}; };
pub const ClosureTypeExpr = struct { pub const ClosureTypeExpr = struct {

View File

@@ -29,12 +29,12 @@ pub const AbiLowering = struct {
/// `is_extern_c_api` knob. When true, sx `string` / `[]T` slices /// `is_extern_c_api` knob. When true, sx `string` / `[]T` slices
/// collapse to `ptr` — the libc convention where the user writes /// collapse to `ptr` — the libc convention where the user writes
/// `string` to mean `char *` and the length is dropped. When /// `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 /// full slice shape is preserved and goes through the general
/// struct-coerce path (16-byte slice → `[2 x i64]`, lands in two /// struct-coerce path (16-byte slice → `[2 x i64]`, lands in two
/// registers on AArch64 — the true C ABI for a 16-byte /// registers on AArch64 — the true C ABI for a 16-byte
/// aggregate). Without the split, sx-to-sx calls through a /// 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 /// the caller's `{ptr, i64}` value against the trampoline's
/// collapsed `ptr` param. /// collapsed `ptr` param.
pub fn abiCoerceParamTypeEx(self: AbiLowering, ir_ty: TypeId, llvm_ty: c.LLVMTypeRef, is_extern_c_api: bool) c.LLVMTypeRef { pub fn abiCoerceParamTypeEx(self: AbiLowering, ir_ty: TypeId, llvm_ty: c.LLVMTypeRef, is_extern_c_api: bool) c.LLVMTypeRef {

View File

@@ -1118,6 +1118,23 @@ pub const Ops = struct {
pub fn emitCall(self: Ops, instruction: *const Inst, call_op: Call) void { pub fn emitCall(self: Ops, instruction: *const Inst, call_op: Call) void {
// Evaluate comptime functions at compile time // Evaluate comptime functions at compile time
const callee_func = &self.e.ir_mod.functions.items[call_op.callee.index()]; 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) { if (callee_func.is_comptime and call_op.args.len == 0) {
var interp_inst = Interpreter.init(self.e.ir_mod, self.e.alloc); var interp_inst = Interpreter.init(self.e.ir_mod, self.e.alloc);
interp_inst.build_config = &self.e.build_config; interp_inst.build_config = &self.e.build_config;

View File

@@ -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);
}

291
src/ir/compiler_lib.zig Normal file
View File

@@ -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 <lib>` with a
/// different `<lib>` 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) };
}

View File

@@ -1273,7 +1273,7 @@ pub const LLVMEmitter = struct {
else if (needs_c_abi) self.abiCoerceParamTypeEx(func.ret, raw_ret_ty, func.is_extern) else if (needs_c_abi) self.abiCoerceParamTypeEx(func.ret, raw_ret_ty, func.is_extern)
else raw_ret_ty; 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. // When uses_sret, prepend the sret pointer at index 0.
const sret_offset: usize = if (uses_sret) 1 else 0; const sret_offset: usize = if (uses_sret) 1 else 0;
const param_count: c_uint = @intCast(func.params.len + sret_offset); const param_count: c_uint = @intCast(func.params.len + sret_offset);

View File

@@ -575,8 +575,14 @@ pub const Function = struct {
/// parameter that every default-conv sx function receives. Callers /// parameter that every default-conv sx function receives. Callers
/// read this flag to decide whether to prepend their current /// read this flag to decide whether to prepend their current
/// `__sx_ctx` value to the args of a call. Extern decls and /// `__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, 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 { pub const Param = struct {
name: StringId, name: StringId,

View File

@@ -162,6 +162,7 @@ pub const InterpError = error{
const compiler_hooks = @import("compiler_hooks.zig"); const compiler_hooks = @import("compiler_hooks.zig");
pub const BuildConfig = compiler_hooks.BuildConfig; pub const BuildConfig = compiler_hooks.BuildConfig;
const compiler_lib = @import("compiler_lib.zig");
const host_ffi = @import("host_ffi.zig"); const host_ffi = @import("host_ffi.zig");
// ── Interpreter ───────────────────────────────────────────────────────── // ── Interpreter ─────────────────────────────────────────────────────────
@@ -579,6 +580,15 @@ pub const Interpreter = struct {
defer self.call_depth -= 1; defer self.call_depth -= 1;
const func = self.module.getFunction(func_id); 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) { if (func.is_extern or func.blocks.items.len == 0) {
// Dispatch to host libc via dlsym. Lets `#run` (and the // Dispatch to host libc via dlsym. Lets `#run` (and the
// post-link bundler) call ordinary extern symbols like // post-link bundler) call ordinary extern symbols like

View File

@@ -60,6 +60,7 @@ pub const ObjcLowering = ffi_objc.ObjcLowering;
pub const ErrorFacts = error_analysis.ErrorFacts; pub const ErrorFacts = error_analysis.ErrorFacts;
pub const compiler_hooks = @import("compiler_hooks.zig"); 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 emit_llvm = @import("emit_llvm.zig");
pub const LLVMEmitter = emit_llvm.LLVMEmitter; 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 emit_llvm_tests = @import("emit_llvm.test.zig");
pub const jni_descriptor_tests = @import("jni_descriptor.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 jni_java_emit_tests = @import("jni_java_emit.test.zig");
pub const compiler_lib_tests = @import("compiler_lib.test.zig");
test { test {
@import("std").testing.refAllDecls(@This()); @import("std").testing.refAllDecls(@This());

View File

@@ -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. // Without implicit_ctx, env is slot 0 and user params follow.
var params = std.ArrayList(Function.Param).empty; var params = std.ArrayList(Function.Param).empty;
const env_ptr_ty = self.module.types.ptrTo(.void); 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) { if (lambda_wants_ctx) {
params.append(self.alloc, .{ params.append(self.alloc, .{
.name = self.module.types.internString("__sx_ctx"), .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 name_id = self.module.types.internString(name);
const func_id = self.builder.beginFunction(name_id, params.items, ret_ty); 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.module.getFunctionMut(func_id).call_conv = .c;
} }
self.builder.currentFunc().has_implicit_ctx = lambda_wants_ctx; self.builder.currentFunc().has_implicit_ctx = lambda_wants_ctx;

View File

@@ -10,6 +10,7 @@ const unescape = @import("../../unescape.zig");
const errors = @import("../../errors.zig"); const errors = @import("../../errors.zig");
const program_index_mod = @import("../program_index.zig"); const program_index_mod = @import("../program_index.zig");
const resolver_mod = @import("../resolver.zig"); const resolver_mod = @import("../resolver.zig");
const compiler_lib = @import("../compiler_lib.zig");
const ProgramIndex = program_index_mod.ProgramIndex; const ProgramIndex = program_index_mod.ProgramIndex;
const GlobalInfo = program_index_mod.GlobalInfo; const GlobalInfo = program_index_mod.GlobalInfo;
const ModuleConstInfo = program_index_mod.ModuleConstInfo; const ModuleConstInfo = program_index_mod.ModuleConstInfo;
@@ -497,7 +498,7 @@ pub fn detectContextDecl(decls: []const *const Node) bool {
/// `__sx_<name>_impl` with the ctx param) lands in Step 4 proper. /// `__sx_<name>_impl` with the ctx param) lands in Step 4 proper.
pub fn funcWantsImplicitCtx(self: *const Lowering, fd: *const ast.FnDecl) bool { pub fn funcWantsImplicitCtx(self: *const Lowering, fd: *const ast.FnDecl) bool {
if (!self.implicit_ctx_enabled) return false; 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 — // `extern` imports and `export` defines are external C symbols —
// C ABI, no sx context (Phase 2, gap iv). // C ABI, no sx context (Phase 2, gap iv).
if (fd.extern_export != .none) return false; 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 // `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 // 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. // 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). // 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 // 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.source_file = self.current_source_file;
func.is_variadic = is_variadic; func.is_variadic = is_variadic;
func.has_implicit_ctx = wants_ctx; func.has_implicit_ctx = wants_ctx;
if (weldedCompilerFn(self, fd, name)) func.compiler_welded = true;
self.fn_decl_fids.put(fd, fid) catch {}; self.fn_decl_fids.put(fd, fid) catch {};
} }
/// A `fn abi(.zig) extern <lib>` 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 /// Register a namespaced import's OWN functions under their module-qualified
/// name (`ns.fn`), giving each a UNIQUE FuncId in the function table. Two /// name (`ns.fn`), giving each a UNIQUE FuncId in the function table. Two
/// modules each exporting a top-level `parse` otherwise collide in the /// 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 func.is_extern = false; // promote from extern stub to real function
// `export` defines force external linkage + C ABI (Phase 2, gaps i+ii). // `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; 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 // 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. // = AST params + 1 if the function carries `__sx_ctx` at slot 0.
const ctx_slots: usize = if (func.has_implicit_ctx) 1 else 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 }); 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. // to the static default before any user code runs.
if (!wants_ctx and self.implicit_ctx_enabled) { if (!wants_ctx and self.implicit_ctx_enabled) {
if (self.program_index.global_names.get("__sx_default_context")) |dctx_gi| { 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). // 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; 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 }); 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 // current_ctx_ref to &__sx_default_context. See companion comment
// in `lowerFunction` for the same case. // in `lowerFunction` for the same case.
if (!wants_ctx_lf and self.implicit_ctx_enabled) { if (!wants_ctx_lf and self.implicit_ctx_enabled) {

View File

@@ -1894,7 +1894,7 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
// Coercing a bare fn name to a fn-pointer // Coercing a bare fn name to a fn-pointer
// type — the call_conv must match. A // type — the call_conv must match. A
// default-conv sx fn assigned to 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 // pthread_create) would otherwise crash at
// runtime when the C caller doesn't supply // runtime when the C caller doesn't supply
// the implicit __sx_ctx arg. // 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; const func_cc = self.module.functions.items[@intFromEnum(fid)].call_conv;
if (func_cc != tt_info.function.call_conv) { if (func_cc != tt_info.function.call_conv) {
if (self.diagnostics) |d| { if (self.diagnostics) |d| {
const want_cc = if (tt_info.function.call_conv == .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) "callconv(.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 }); 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); break :blk self.emitPlaceholder(eff_fn_name);

View File

@@ -1133,7 +1133,7 @@ pub fn synthesizeJniMainStub(self: *Lowering, fcd: *const ast.RuntimeClassDecl,
// JNI native methods are C-callable entry points — install the // JNI native methods are C-callable entry points — install the
// static default Context so `context.X` reads in the method body // static default Context so `context.X` reads in the method body
// resolve through `current_ctx_ref`. Mirror the same binding // 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; const saved_ctx_ref_jni = self.current_ctx_ref;
defer self.current_ctx_ref = saved_ctx_ref_jni; defer self.current_ctx_ref = saved_ctx_ref_jni;
if (self.implicit_ctx_enabled) { if (self.implicit_ctx_enabled) {

View File

@@ -6,6 +6,7 @@ const mod_mod = @import("../module.zig");
const type_bridge = @import("../type_bridge.zig"); const type_bridge = @import("../type_bridge.zig");
const program_index_mod = @import("../program_index.zig"); const program_index_mod = @import("../program_index.zig");
const resolver_mod = @import("../resolver.zig"); const resolver_mod = @import("../resolver.zig");
const compiler_lib = @import("../compiler_lib.zig");
const StructTemplate = program_index_mod.StructTemplate; const StructTemplate = program_index_mod.StructTemplate;
const TemplateParam = program_index_mod.TemplateParam; 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 // any forward-reference stub. Same-name structs in DIFFERENT sources get
// distinct TypeIds instead of last-wins clobbering the first. // distinct TypeIds instead of last-wins clobbering the first.
const info: types.TypeInfo = .{ .@"struct" = .{ .name = name_id, .fields = fields.items } }; 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 // Store field defaults for struct literal lowering
if (sd.field_defaults.len > 0) { 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 <lib> { … }` 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) — /// Register a top-level ENUM decl under a per-decl nominal identity (E6a) —
/// the enum twin of `registerStructDecl`. A GENUINE same-name shadow already /// 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 /// reserved its DISTINCT slot up-front in `scanDecls` (the first at id 0, the

View File

@@ -255,7 +255,7 @@ pub fn lowerObjcPropertySetter(self: *Lowering, obj_expr: *const ast.Node, field
} }, .void); } }, .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. /// with this exported name already exists in the module (e.g.
/// declared by stdlib `extern` decl), return it; otherwise /// declared by stdlib `extern` decl), return it; otherwise
/// declare it fresh with the given signature. /// 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 `*<Cls>State` /// 2. Calls `object_getIvar(obj, ivar)` to get the `*<Cls>State`
/// state pointer. /// state pointer.
/// 3. Calls the sx body `@<Cls>.<method>(__sx_default_context, /// 3. Calls the sx body `@<Cls>.<method>(__sx_default_context,
/// state, ...user_args)` (default sx-callconv). /// state, ...user_args)` (default sx convention).
/// 4. Returns the result (or `ret void`). /// 4. Returns the result (or `ret void`).
/// ///
/// IMP name: `__<ClassName>_<methodName>_imp`. emit_llvm's /// IMP name: `__<ClassName>_<methodName>_imp`. emit_llvm's

View File

@@ -91,7 +91,7 @@ pub const Module = struct {
pub const ObjcDefinedMethodEntry = struct { pub const ObjcDefinedMethodEntry = struct {
sel: []const u8, // mangled Obj-C selector (`add:and:`) sel: []const u8, // mangled Obj-C selector (`add:and:`)
encoding: []const u8, // Apple-runtime type encoding (`v@:ii`) 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) is_class: bool = false, // true ⇒ register on the metaclass (M2.1 class methods)
}; };

View File

@@ -224,9 +224,15 @@ pub const TypeResolver = struct {
defer param_ids.deinit(table.alloc); defer param_ids.deinit(table.alloc);
for (ft.param_types) |pt| param_ids.append(table.alloc, inner.resolveInner(pt)) catch return .unresolved; 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 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, .default => .default,
.c => .c, .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); break :blk table.functionTypeCC(param_ids.items, ret_ty, cc);
}, },

View File

@@ -537,9 +537,9 @@ test "lex keywords" {
} }
test "lex linkage keywords" { test "lex linkage keywords" {
// extern / export are keywords (FFI-linkage stream), lexed beside callconv. // extern / export are keywords (FFI-linkage stream), lexed beside abi.
var lex = Lexer.init("callconv extern export"); var lex = Lexer.init("abi extern export");
const expected = [_]Tag{ .kw_callconv, .kw_extern, .kw_export }; const expected = [_]Tag{ .kw_abi, .kw_extern, .kw_export };
for (expected) |exp| { for (expected) |exp| {
try std.testing.expectEqual(exp, lex.next().tag); try std.testing.expectEqual(exp, lex.next().tag);
} }

View File

@@ -1682,7 +1682,7 @@ pub const Server = struct {
.kw_protocol, .kw_protocol,
.kw_impl, .kw_impl,
.kw_inline, .kw_inline,
.kw_callconv, .kw_abi,
.kw_extern, .kw_extern,
.kw_export, .kw_export,
.kw_asm, .kw_asm,

View File

@@ -909,6 +909,11 @@ fn extractLibraries(allocator: std.mem.Allocator, root: *const sx.ast.Node) ![]c
for (decls) |d| { for (decls) |d| {
switch (d.data) { switch (d.data) {
.library_decl => |ld| { .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; if (s.contains(ld.lib_name)) continue;
try s.put(ld.lib_name, {}); try s.put(ld.lib_name, {});
try l.append(a, ld.lib_name); try l.append(a, ld.lib_name);

View File

@@ -78,3 +78,148 @@ test "parser: comptime type-metaprogramming surface parses" {
try std.testing.expect(d.data.fn_decl.return_type != null); 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 <lib> 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 <lib> { … }`. 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 <lib> 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);
}

View File

@@ -598,12 +598,12 @@ pub const Parser = struct {
// '->' present: function type // '->' present: function type
self.advance(); // skip '->' self.advance(); // skip '->'
const return_type = try self.parseTypeExpr(); 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 = .{ return try self.createNode(start, .{ .function_type_expr = .{
.param_types = try param_types.toOwnedSlice(self.allocator), .param_types = try param_types.toOwnedSlice(self.allocator),
.param_names = if (has_names) try param_names.toOwnedSlice(self.allocator) else null, .param_names = if (has_names) try param_names.toOwnedSlice(self.allocator) else null,
.return_type = return_type, .return_type = return_type,
.call_conv = call_conv, .abi = abi,
} }); } });
} }
// No '->': tuple type (even for single element). Keep field names // No '->': tuple type (even for single element). Keep field names
@@ -959,6 +959,18 @@ pub const Parser = struct {
self.advance(); self.advance();
} }
// Optional welded-binding annotation: `struct abi(.zig) extern <lib> { … }`.
// `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) { ... } // Optional type params: struct($N: u32, $T: Type) { ... }
var type_params = std.ArrayList(ast.StructTypeParam).empty; var type_params = std.ArrayList(ast.StructTypeParam).empty;
if (self.current.tag == .l_paren) { if (self.current.tag == .l_paren) {
@@ -1146,6 +1158,8 @@ pub const Parser = struct {
.using_entries = try using_entries.toOwnedSlice(self.allocator), .using_entries = try using_entries.toOwnedSlice(self.allocator),
.methods = try methods.toOwnedSlice(self.allocator), .methods = try methods.toOwnedSlice(self.allocator),
.constants = try constants.toOwnedSlice(self.allocator), .constants = try constants.toOwnedSlice(self.allocator),
.abi = struct_abi,
.extern_lib = struct_extern_lib,
.is_raw = name_is_raw, .is_raw = name_is_raw,
} }); } });
} }
@@ -1937,8 +1951,11 @@ pub const Parser = struct {
return_type = try self.parseTypeExpr(); return_type = try self.parseTypeExpr();
} }
// Optional calling convention: callconv(.c) // Optional ABI / calling-convention annotation: `abi(.c)` / `abi(.zig)` /
const call_conv = try self.parseOptionalCallConv(); // `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). // Optional postfix linkage modifier: `extern` (import) / `export` (define).
const extern_export = self.parseOptionalExternExport(); const extern_export = self.parseOptionalExternExport();
@@ -2018,7 +2035,7 @@ pub const Parser = struct {
.body = body, .body = body,
.type_params = type_params, .type_params = type_params,
.is_arrow = is_arrow, .is_arrow = is_arrow,
.call_conv = call_conv, .abi = abi,
.extern_export = extern_export, .extern_export = extern_export,
.extern_lib = extern_lib, .extern_lib = extern_lib,
.extern_name = extern_name, .extern_name = extern_name,
@@ -3688,8 +3705,8 @@ pub const Parser = struct {
return_type = try self.parseTypeExpr(); return_type = try self.parseTypeExpr();
} }
// Optional calling convention: callconv(.c) // Optional ABI annotation: abi(.c) / abi(.zig) / abi(.pure)
const call_conv = try self.parseOptionalCallConv(); const abi = try self.parseOptionalAbi();
// A closure is its own function boundary: clear the cleanup-body flags // A closure is its own function boundary: clear the cleanup-body flags
// so control-flow exits inside the closure body (`return` from the // so control-flow exits inside the closure body (`return` from the
@@ -3719,7 +3736,7 @@ pub const Parser = struct {
.return_type = return_type, .return_type = return_type,
.body = body, .body = body,
.type_params = type_params, .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. // builtin marker) is a function-type literal, not a function def.
if (tag == .arrow) return self.hasFnBodyAfterArrow(); if (tag == .arrow) return self.hasFnBodyAfterArrow();
// `kw_extern`/`kw_export`: a postfix linkage modifier (e.g. `f :: () extern;` // `kw_extern`/`kw_export`: a postfix linkage modifier (e.g. `f :: () extern;`
// with no return type) marks a fn decl just like `callconv`. // 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_callconv or tag == .kw_extern or tag == .kw_export; 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 { 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 == .fat_arrow) return true;
if (self.current.tag == .l_brace) 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 == .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;` / // 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; if (self.current.tag == .kw_extern or self.current.tag == .kw_export) return true;
// Inside a `struct #compiler` block, a `(...) -> Ret;` ending // Inside a `struct #compiler` block, a `(...) -> Ret;` ending
// with `;` after the return type is a `#compiler` method // with `;` after the return type is a `#compiler` method
@@ -3806,25 +3823,32 @@ pub const Parser = struct {
return false; return false;
} }
fn parseOptionalCallConv(self: *Parser) anyerror!ast.CallingConvention { /// Optional ABI / calling-convention annotation `abi(.c)` / `abi(.zig)` /
if (self.current.tag != .kw_callconv) return .default; /// `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(); self.advance();
try self.expect(.l_paren); try self.expect(.l_paren);
try self.expect(.dot); try self.expect(.dot);
if (self.current.tag != .identifier) if (self.current.tag != .identifier)
return self.fail("expected calling convention name after '.'"); return self.fail("expected ABI name ('.c', '.zig', or '.pure') after '.'");
const cc_name = self.tokenSlice(self.current); const abi_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"); 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(); self.advance();
try self.expect(.r_paren); 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. /// `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 { fn parseOptionalExternExport(self: *Parser) ast.ExternExportModifier {
switch (self.current.tag) { switch (self.current.tag) {
.kw_extern => { .kw_extern => {

View File

@@ -830,7 +830,7 @@ pub const Analyzer = struct {
switch (node.data) { switch (node.data) {
.fn_decl => |fd| { .fn_decl => |fd| {
const saved_cc = self.in_c_conv; 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.pushScope();
try self.analyzeParams(fd.params); try self.analyzeParams(fd.params);
try self.analyzeNode(fd.body); try self.analyzeNode(fd.body);
@@ -852,7 +852,7 @@ pub const Analyzer = struct {
if (mnode.data == .fn_decl) { if (mnode.data == .fn_decl) {
const m = mnode.data.fn_decl; const m = mnode.data.fn_decl;
const saved_cc = self.in_c_conv; 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.pushScope();
try self.analyzeParams(m.params); try self.analyzeParams(m.params);
try self.analyzeNode(m.body); try self.analyzeNode(m.body);
@@ -979,7 +979,7 @@ pub const Analyzer = struct {
try self.diagnostics.append(self.allocator, .{ try self.diagnostics.append(self.allocator, .{
.level = .warn, .level = .warn,
.span = span, .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; return;
} }
@@ -1032,7 +1032,7 @@ pub const Analyzer = struct {
}); });
} }
const saved_cc = self.in_c_conv; 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.pushScope();
try self.analyzeParams(fd.params); try self.analyzeParams(fd.params);
try self.analyzeNode(fd.body); 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); 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"); const parser_mod = @import("parser.zig");
var arena = std.heap.ArenaAllocator.init(std.testing.allocator); var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit(); defer arena.deinit();
@@ -2374,7 +2374,7 @@ test "sema: context in a callconv(.c) function reports a specific diagnostic" {
const source = const source =
"Context :: struct { allocator: i64; data: i64; }" ++ "Context :: struct { allocator: i64; data: i64; }" ++
"cb :: () -> i64 callconv(.c) { context; 0; }" ++ "cb :: () -> i64 abi(.c) { context; 0; }" ++
"ok :: () -> i64 { context; 0; }"; "ok :: () -> i64 { context; 0; }";
var parser = parser_mod.Parser.init(alloc, source); var parser = parser_mod.Parser.init(alloc, source);
const root = try parser.parse(); 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 c_conv_diag = false;
var undefined_diag = false; var undefined_diag = false;
for (res.diagnostics) |d| { 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; 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 try std.testing.expect(c_conv_diag); // `cb` accesses context under the C ABI

View File

@@ -42,7 +42,7 @@ pub const Tag = enum {
kw_impl, // impl kw_impl, // impl
kw_Self, // Self (in protocol declarations) kw_Self, // Self (in protocol declarations)
kw_inline, // inline (compile-time if/for/while) 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_extern, // extern (import: external linkage, C ABI, no body)
kw_export, // export (define + expose: external linkage, C ABI) kw_export, // export (define + expose: external linkage, C ABI)
kw_asm, // asm (inline assembly expression / global asm decl) kw_asm, // asm (inline assembly expression / global asm decl)
@@ -281,7 +281,7 @@ pub const keywords = std.StaticStringMap(Tag).initComptime(.{
.{ "impl", .kw_impl }, .{ "impl", .kw_impl },
.{ "Self", .kw_Self }, .{ "Self", .kw_Self },
.{ "inline", .kw_inline }, .{ "inline", .kw_inline },
.{ "callconv", .kw_callconv }, .{ "abi", .kw_abi },
.{ "extern", .kw_extern }, .{ "extern", .kw_extern },
.{ "export", .kw_export }, .{ "export", .kw_export },
// `asm` is a real keyword; `volatile` / `clobbers` stay OUT of this table // `asm` is a real keyword; `volatile` / `clobbers` stay OUT of this table