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:
273
current/CHECKPOINT-COMPILER-API.md
Normal file
273
current/CHECKPOINT-COMPILER-API.md
Normal 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 2–3.
|
||||
|
||||
Then Phase 3+: widen to enum/tuple (`EnumInfo`/`TaggedUnionInfo`/`TupleInfo`,
|
||||
optional fields → sentinels), migrate `BuildOptions` to `abi(.zig) extern
|
||||
compiler` (the `#compiler` registry re-homes under the `compiler` lib), delete
|
||||
`#compiler`.
|
||||
|
||||
## Known issues
|
||||
- None for this stream. (Metatype's deferred enhancement is issue 0141 — comptime
|
||||
`List` growth; orthogonal, see `current/CHECKPOINT-METATYPE.md`.)
|
||||
|
||||
## Log
|
||||
- **Phase 2.1 — weld-plan layout math + `StructInfo` registered.** Decision:
|
||||
full byte-layout weld (not logical-field marshalling). `computeWeldPlan`
|
||||
(offset-order elements + padding + sx→element remap), pure + unit-tested
|
||||
against `Field` (identity) and `StructInfo` (reordered, remap `[1,0,3,2]`).
|
||||
No emit/interp wiring yet. Build + suite green.
|
||||
- **Phase 1 polish — comptime-only enforcement.** A runtime call to a welded fn is
|
||||
a clean build-gating error (`emitCall` gate, guarded by enclosing-`is_comptime`
|
||||
so `#run`/`::` uses stay green), not a link failure. Example 1185. Build + suite
|
||||
green (458 unit, 690 corpus).
|
||||
- **Phase 1.1 fifth sub-step — host-call bridge (welded functions).**
|
||||
`compiler_lib` function registry (`intern`/`text_of`) + `findFn`; IR `Function`
|
||||
`compiler_welded` flag set/validated in `declareFunction` (`weldedCompilerFn`);
|
||||
`interp.call()` dispatches welded calls to the Zig handler. Examples 0626 (round-
|
||||
trip) + 1184 (unexported-fn diagnostic); `findFn` unit-tested. Runtime-call clean
|
||||
rejection deferred (loud link error today). Build + suite green (458 unit, 689
|
||||
corpus).
|
||||
- **Phase 1.1 fourth sub-step — welded-struct layout validation.**
|
||||
`validateStructLayout` (pure, unit-tested) + `validateWeldedStruct` wired into
|
||||
`registerStructDecl`: a `struct abi(.zig) extern compiler` is validated against
|
||||
the registry (lib == compiler, name exported, layout matches) with build-gating
|
||||
diagnostics. `#library "compiler"` no longer dlopen'd. Examples 0625 (faithful
|
||||
Field) + 1183 (field-count mismatch diagnostic). Offset-override/GEP deferred to
|
||||
Phase 2 (not exercised by Field's natural layout). Build + suite green (456 unit,
|
||||
687 corpus).
|
||||
- **Phase 1.1 third sub-step — binding registry.** New `src/ir/compiler_lib.zig`:
|
||||
the `compiler` lib's welded-type registry; `Field` welded to
|
||||
`StructInfo.Field` with layout baked from the real Zig type
|
||||
(`@offsetOf`/`@sizeOf`/`@alignOf`); `findType` lookup proven by unit test
|
||||
(+ null off the export list). Standalone island — not yet consumed by lowering.
|
||||
Build + suite green (454 unit tests). Break-verified.
|
||||
- **Phase 1.1 second sub-step — struct-decl binding parses.** `ast.StructDecl`
|
||||
gained `abi` + `extern_lib`; `parseStructDecl` parses `abi(.zig) extern <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.
|
||||
@@ -1,4 +1,4 @@
|
||||
# Comptime Compiler API — `#library "compiler"` + `extern(.zig)`
|
||||
# Comptime Compiler API — `#library "compiler"` + `abi(.zig) extern`
|
||||
|
||||
> **Status: design-of-record (not yet an active stream).** Captures a unified
|
||||
> mechanism for sx↔compiler binding that subsumes the metatype `declare`/`define`
|
||||
@@ -49,32 +49,44 @@ internal surface (Zig types + functions). Two defining properties:
|
||||
`is_comptime` boundary. (Welded *types* are still usable as plain runtime data;
|
||||
only the *functions* are comptime-gated.)
|
||||
|
||||
### `extern(.zig) <lib>` — postfix attribute
|
||||
### `abi(.zig)` + `extern <lib>` — the binding surface
|
||||
|
||||
Slots where `#builtin` / `#compiler` go (postfix, after the return type for fns,
|
||||
after `struct` for types), with the library handle following:
|
||||
> **Syntax decision (2026-06-17, supersedes the original `extern(.zig) <lib>`
|
||||
> 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
|
||||
// functions:
|
||||
text_of :: (id: StringId) -> string extern(.zig) compiler;
|
||||
intern :: (s: string) -> StringId extern(.zig) compiler;
|
||||
register_type :: (info: StructInfo) -> Type extern(.zig) compiler;
|
||||
find_type :: (name: StringId) -> ?Type extern(.zig) compiler;
|
||||
text_of :: (id: StringId) -> string abi(.zig) extern compiler;
|
||||
intern :: (s: string) -> StringId abi(.zig) extern compiler;
|
||||
register_type :: (info: StructInfo) -> Type abi(.zig) extern compiler;
|
||||
find_type :: (name: StringId) -> ?Type abi(.zig) extern compiler;
|
||||
|
||||
// types (layout-welded to the lib's real Zig type):
|
||||
Field :: struct extern(.zig) compiler { name: StringId; ty: Type; };
|
||||
StructInfo :: struct extern(.zig) compiler {
|
||||
Field :: struct abi(.zig) extern compiler { name: StringId; ty: Type; };
|
||||
StructInfo :: struct abi(.zig) extern compiler {
|
||||
name: StringId; fields: []Field; is_protocol: bool; nominal_id: u32;
|
||||
};
|
||||
```
|
||||
|
||||
`extern(.zig)` = "Zig ABI / Zig layout"; `<lib>` = the binding source.
|
||||
`abi(.zig)` = "Zig ABI / Zig layout"; `extern compiler` = the linkage + binding
|
||||
source.
|
||||
|
||||
### Layout welding — why it's exact, not brittle
|
||||
|
||||
The sx compiler is itself a Zig program; `types.zig` is part of it. So at
|
||||
**compiler-build time** the real record's layout is available via
|
||||
`@offsetOf` / `@sizeOf` / `@alignOf`. An `extern(.zig) compiler` struct is laid out
|
||||
`@offsetOf` / `@sizeOf` / `@alignOf`. An `abi(.zig) extern compiler` struct is laid out
|
||||
to the bound Zig type's EXACT offsets (queried, not guessed), and the compiler
|
||||
ASSERTS the sx declaration matches the Zig type byte-for-byte (a mismatch is a
|
||||
build error — the sx side is a header checked against the implementation). Because
|
||||
@@ -134,8 +146,8 @@ instead of the user threading `declare`→forward-slot→`define`→eval-timing
|
||||
## BuildOptions migration
|
||||
|
||||
`BuildOptions :: struct #compiler { ... }` + `build_options() #compiler` →
|
||||
`extern(.zig) compiler`: the setter/getter hook-methods become `extern(.zig)
|
||||
compiler` functions (or methods on a welded/handle `BuildOptions`), backed by the
|
||||
`abi(.zig) extern compiler`: the setter/getter hook-methods become `abi(.zig)
|
||||
extern compiler` functions (or methods on a welded/handle `BuildOptions`), backed by the
|
||||
same `BuildConfig` state. The `compiler_hooks.zig` registry becomes the `compiler`
|
||||
lib's function/type registry. Net: the build DSL and the metatype API ride one
|
||||
mechanism.
|
||||
@@ -157,12 +169,17 @@ Foundation that ALREADY exists:
|
||||
- `extern` / `export` are keywords (`src/token.zig:46`, `kw_extern`/`kw_export`).
|
||||
|
||||
New work for Phase 1:
|
||||
- **Lexer/parser**: the `(.zig)` ABI qualifier on `extern`, and the trailing
|
||||
`<lib>` identifier — `… extern(.zig) <lib>` postfix on FN decls (after the
|
||||
return type, beside `#builtin`/`#compiler` at `src/parser.zig:233`/`:315`) and
|
||||
STRUCT decls (beside `struct #compiler`, `src/parser.zig:953`).
|
||||
- **AST**: an abi/layout-binding field on `FnDecl` and the struct decl (`abi:
|
||||
.c | .zig`, `lib: ?name`).
|
||||
- **Lexer/parser**: the `abi(.zig)` annotation (a new `abi` keyword replacing
|
||||
`callconv`; `ABI = { default, c, zig, pure }`) in the slot before `extern`,
|
||||
followed by the `<lib>` handle — `… abi(.zig) extern <lib>` postfix on FN decls
|
||||
(after the return type, before `extern`) and STRUCT decls (beside
|
||||
`struct #compiler`). **DONE (parse-only)** — `parseOptionalAbi`
|
||||
(`src/parser.zig`) wired on fn decls AND struct decls, `ast.ABI`, parser unit
|
||||
tests; the `callconv`→`abi` rename migrated 52 sx files + the compiler's
|
||||
CC-mismatch diagnostic.
|
||||
- **AST**: the `abi: ABI` field lives on `FnDecl` / `Lambda` / `FunctionTypeExpr`
|
||||
(carries `.zig` for a welded fn); `StructDecl` gained `abi: ABI` +
|
||||
`extern_lib: ?[]const u8`. **DONE.**
|
||||
- **Binding registry**: re-home / generalize `src/ir/compiler_hooks.zig` (today's
|
||||
`#compiler` registry) into the `compiler` lib's type+function registry, keyed by
|
||||
exported sx name → Zig type (`@offsetOf` layout) / Zig fn (host-call).
|
||||
@@ -174,12 +191,13 @@ New work for Phase 1:
|
||||
|
||||
## Build order (each phase keeps `zig build test` green)
|
||||
|
||||
1. **`extern(.zig)` + `#library` foundation** — parse the postfix attribute (the
|
||||
`#library` decl already exists); a binding registry (sx name → Zig type/fn);
|
||||
the layout engine honoring the bound type's `@offsetOf` offsets + LLVM emission
|
||||
that hits them; **build-time layout-equality assertion**. Prove with `Field`
|
||||
(two u32s). First testable sub-step: `extern(.zig) <lib>` PARSES on a fn decl
|
||||
(parser unit test), AST carries the binding — no semantics yet.
|
||||
1. **`abi(.zig) extern <lib>` + `#library` foundation** — parse the postfix
|
||||
annotation (the `#library` decl already exists); a binding registry (sx name →
|
||||
Zig type/fn); the layout engine honoring the bound type's `@offsetOf` offsets +
|
||||
LLVM emission that hits them; **build-time layout-equality assertion**. Prove
|
||||
with `Field` (two u32s). First testable sub-step **DONE**: `abi(.zig) extern
|
||||
<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
|
||||
host-call bridge.
|
||||
3. **Re-express `type_info`/`define` (struct)** as sx over `register_struct`/
|
||||
@@ -187,7 +205,7 @@ New work for Phase 1:
|
||||
4. **Widen to enum/tuple** — weld `EnumInfo`/`TaggedUnionInfo`/`TupleInfo`
|
||||
(optional fields → sentinels: `backing_type` `.unresolved`, `explicit_values`
|
||||
len-0); migrate `examples/0619`/`0623`; delete the enum/tuple interp arms.
|
||||
5. **Migrate `BuildOptions`** to `extern(.zig) compiler`.
|
||||
5. **Migrate `BuildOptions`** to `abi(.zig) extern compiler`.
|
||||
6. **Delete `#compiler`**; suite green.
|
||||
|
||||
## Risks / open questions
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Generic `Into(Block)` impl with a `string`-typed arg in the
|
||||
// closure signature. The block trampoline declares the param with
|
||||
// callconv(.c); without the abi-collapse fix, sx `string` got
|
||||
// abi(.c); without the abi-collapse fix, sx `string` got
|
||||
// silently collapsed to `ptr` (the libc `char *` heuristic) and
|
||||
// the caller's 16-byte `{ptr, len}` value mismatched the
|
||||
// trampoline's 8-byte `ptr` slot. Result: segfault inside the
|
||||
@@ -8,7 +8,7 @@
|
||||
//
|
||||
// The fix lives in `abiCoerceParamTypeEx`: the `string`/`slice` →
|
||||
// `ptr` collapse only applies to `is_extern` extern decls (libc
|
||||
// interop). sx-internal `callconv(.c)` keeps the full slice
|
||||
// interop). sx-internal `abi(.c)` keeps the full slice
|
||||
// shape, which lands as `[2 x i64]` at the LLVM signature site
|
||||
// and matches the caller's two-register pass on AArch64.
|
||||
|
||||
@@ -20,7 +20,7 @@ g_s: string = "";
|
||||
main :: () -> i32 {
|
||||
cl := (s: string) => { g_s = s; };
|
||||
b : Block = xx cl;
|
||||
invoke_fn : (*Block, string) -> void callconv(.c) = xx b.invoke;
|
||||
invoke_fn : (*Block, string) -> void abi(.c) = xx b.invoke;
|
||||
invoke_fn(@b, "hello");
|
||||
if g_s.len == 0 { print("FAIL: empty\n"); return 1; }
|
||||
print("got: <{}>\n", g_s);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// FFI plan step 5.2 — generic `Into(Block) for Closure(..$args) ->
|
||||
// $R` impl. One impl in stdlib covers every closure shape; the
|
||||
// compiler monomorphises the impl body per call shape and emits a
|
||||
// dedicated `__invoke` `callconv(.c)` trampoline + Block literal
|
||||
// dedicated `__invoke` `abi(.c)` trampoline + Block literal
|
||||
// (via `#insert build_block_convert($args, $R);`).
|
||||
//
|
||||
// This test exercises a closure shape (`Closure(i64, i64) -> void`)
|
||||
@@ -13,7 +13,7 @@
|
||||
// the per-shape trampoline ferries control back to the sx closure.
|
||||
//
|
||||
// The block is invoked directly through `b.invoke` (a typed
|
||||
// `callconv(.c)` fn-pointer) — the same shape the Apple Block
|
||||
// `abi(.c)` fn-pointer) — the same shape the Apple Block
|
||||
// runtime calls when a UIKit/Foundation API hands the block back
|
||||
// to its registered invoke.
|
||||
|
||||
@@ -27,7 +27,7 @@ main :: () -> i32 {
|
||||
cl := (a: i64, b: i64) => { g_a = a; g_b = b; };
|
||||
blk : Block = xx cl;
|
||||
|
||||
invoke_fn : (*Block, i64, i64) -> void callconv(.c) = xx blk.invoke;
|
||||
invoke_fn : (*Block, i64, i64) -> void abi(.c) = xx blk.invoke;
|
||||
invoke_fn(@blk, 10, 20);
|
||||
|
||||
if g_a != 10 { print("FAIL: g_a={}\n", g_a); return 1; }
|
||||
|
||||
24
examples/0625-comptime-weld-struct-field.sx
Normal file
24
examples/0625-comptime-weld-struct-field.sx
Normal 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);
|
||||
}
|
||||
24
examples/0626-comptime-weld-fn-intern-text-of.sx
Normal file
24
examples/0626-comptime-weld-fn-intern-text-of.sx
Normal 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);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Passing a default-conv sx function as a callconv(.c) fn-pointer
|
||||
// Passing a default-conv sx function as a abi(.c) fn-pointer
|
||||
// silently mismatches ABIs — historically that meant the C-side caller
|
||||
// supplied no `__sx_ctx` slot 0 and the sx-side body read garbage.
|
||||
// The compiler now rejects the coercion outright with a "call-convention
|
||||
@@ -9,6 +9,6 @@
|
||||
sx_handler :: (arg: *void) -> *void { return arg; }
|
||||
|
||||
main :: () -> i32 {
|
||||
fp : (*void) -> *void callconv(.c) = sx_handler;
|
||||
fp : (*void) -> *void abi(.c) = sx_handler;
|
||||
return 0;
|
||||
}
|
||||
|
||||
17
examples/1183-diagnostics-weld-struct-field-count.sx
Normal file
17
examples/1183-diagnostics-weld-struct-field-count.sx
Normal 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);
|
||||
}
|
||||
11
examples/1184-diagnostics-weld-fn-unexported.sx
Normal file
11
examples/1184-diagnostics-weld-fn-unexported.sx
Normal 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"); }
|
||||
17
examples/1185-diagnostics-weld-fn-runtime-call.sx
Normal file
17
examples/1185-diagnostics-weld-fn-runtime-call.sx
Normal 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);
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
// `callconv(.c)` on function pointers passed to extern callbacks — ensures
|
||||
// `abi(.c)` on function pointers passed to extern callbacks — ensures
|
||||
// the function uses C ABI so it can be safely invoked from `extern`
|
||||
// functions like SDL_AddEventWatch.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
// A function with C calling convention
|
||||
add_c :: (a: i64, b: i64) -> i64 callconv(.c) {
|
||||
add_c :: (a: i64, b: i64) -> i64 abi(.c) {
|
||||
a + b
|
||||
}
|
||||
|
||||
main :: () {
|
||||
// Call it directly — should work like any other function
|
||||
result := add_c(10, 32);
|
||||
print("callconv(.c): {}\n", result);
|
||||
print("abi(.c): {}\n", result);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// `callconv(.c)` callbacks accessing struct methods on global pointers —
|
||||
// `abi(.c)` callbacks accessing struct methods on global pointers —
|
||||
// regression coverage for prior data-corruption when the callback dispatches
|
||||
// through a global pointer to a method on the pointed-to struct.
|
||||
|
||||
@@ -29,7 +29,7 @@ do_render :: () {
|
||||
print("wrapper: pw={}, ph={}, frame={}\n", g_pipe.pw, g_pipe.ph, g_pipe.frame);
|
||||
}
|
||||
|
||||
callback_inline :: (userdata: *void, code: i64) -> bool callconv(.c) {
|
||||
callback_inline :: (userdata: *void, code: i64) -> bool abi(.c) {
|
||||
g_width = xx code;
|
||||
g_height = xx (code + 1);
|
||||
g_pipe.resize(xx g_width, xx g_height);
|
||||
@@ -38,7 +38,7 @@ callback_inline :: (userdata: *void, code: i64) -> bool callconv(.c) {
|
||||
true
|
||||
}
|
||||
|
||||
callback_wrapper :: (userdata: *void, code: i64) -> bool callconv(.c) {
|
||||
callback_wrapper :: (userdata: *void, code: i64) -> bool abi(.c) {
|
||||
g_width = xx code;
|
||||
g_height = xx (code + 1);
|
||||
do_render();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Regression test for issue-0025 path A.
|
||||
//
|
||||
// sx functions declared with `callconv(.c)` that take a composite > 16 bytes
|
||||
// sx functions declared with `abi(.c)` that take a composite > 16 bytes
|
||||
// by value must marshal the arg through `ptr byval(<T>)` per AAPCS64 / SysV
|
||||
// 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
|
||||
@@ -20,7 +20,7 @@ Wide :: struct {
|
||||
d: i64;
|
||||
}
|
||||
|
||||
accept_c :: (w: Wide) -> i64 callconv(.c) {
|
||||
accept_c :: (w: Wide) -> i64 abi(.c) {
|
||||
w.a + w.b + w.c + w.d
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Regression test for issue-0025 path B.
|
||||
//
|
||||
// When a fn-pointer's type is spelled with `callconv(.c)`, the indirect
|
||||
// When a fn-pointer's type is spelled with `abi(.c)`, the indirect
|
||||
// call must apply the same C-ABI byval coercion that direct C-ABI calls
|
||||
// do at the call site (path A): >16-byte non-HFA aggregates are passed
|
||||
// as `ptr byval(<T>)`. Without the fix, the indirect call site builds
|
||||
@@ -9,8 +9,8 @@
|
||||
// ways that don't match the byval-attributed callee signature — the
|
||||
// callee then reads garbage out of the wrong machine-state slots.
|
||||
//
|
||||
// The opt-in is the `callconv(.c)` on the fn-pointer type spelling.
|
||||
// Pure-sx fn-pointer casts (no callconv suffix) keep their default
|
||||
// The opt-in is the `abi(.c)` on the fn-pointer type spelling.
|
||||
// Pure-sx fn-pointer casts (no abi suffix) keep their default
|
||||
// calling convention — verified by examples/87-fnptr-cast-large-aggregate.sx.
|
||||
|
||||
#import "modules/std.sx";
|
||||
@@ -22,7 +22,7 @@ Wide :: struct {
|
||||
d: i64;
|
||||
}
|
||||
|
||||
accept_c :: (w: Wide) -> i64 callconv(.c) {
|
||||
accept_c :: (w: Wide) -> i64 abi(.c) {
|
||||
w.a + w.b + w.c + w.d
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ main :: () -> i32 {
|
||||
w := Wide.{ a = 1, b = 10, c = 100, d = 1000 };
|
||||
if accept_c(w) != 1111 { return 1; }
|
||||
|
||||
fn_ptr : (Wide) -> i64 callconv(.c) = xx accept_c;
|
||||
fn_ptr : (Wide) -> i64 abi(.c) = xx accept_c;
|
||||
if fn_ptr(w) != 1111 { return 2; }
|
||||
|
||||
0
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Pure-sx fn-pointer cast: a function-pointer typed without `callconv(.c)`
|
||||
// Pure-sx fn-pointer cast: a function-pointer typed without `abi(.c)`
|
||||
// keeps the default (sx) calling convention. Passing a >16-byte aggregate
|
||||
// through that pointer must not get the C-ABI byval coercion — the sx-CC
|
||||
// callee expects the struct as an SSA value, not as a `ptr byval(<T>)`.
|
||||
//
|
||||
// Pair with examples/86-callconv-c-fnptr-large-aggregate.sx, which covers
|
||||
// the opposite arm (fn-pointer typed `callconv(.c)` does get byval).
|
||||
// Pair with examples/86-abi-c-fnptr-large-aggregate.sx, which covers
|
||||
// the opposite arm (fn-pointer typed `abi(.c)` does get byval).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
|
||||
@@ -16,19 +16,19 @@
|
||||
#source "1214-ffi-06-callback.c";
|
||||
};
|
||||
|
||||
ffi_apply_callback :: (cb: (i32) -> i32 callconv(.c), value: i32) -> i32 extern;
|
||||
ffi_apply_callback2 :: (cb: (*void, i32) -> i32 callconv(.c), ctx: *void, v: i32) -> i32 extern;
|
||||
ffi_apply_callback :: (cb: (i32) -> i32 abi(.c), value: i32) -> i32 extern;
|
||||
ffi_apply_callback2 :: (cb: (*void, i32) -> i32 abi(.c), ctx: *void, v: i32) -> i32 extern;
|
||||
|
||||
g_callback_hits : i32 = 0;
|
||||
g_callback_sum : i32 = 0;
|
||||
|
||||
double_it :: (x: i32) -> i32 callconv(.c) {
|
||||
double_it :: (x: i32) -> i32 abi(.c) {
|
||||
g_callback_hits += 1;
|
||||
g_callback_sum += x;
|
||||
x * 2
|
||||
}
|
||||
|
||||
add_with_ctx :: (ctx: *void, v: i32) -> i32 callconv(.c) {
|
||||
add_with_ctx :: (ctx: *void, v: i32) -> i32 abi(.c) {
|
||||
g_callback_hits += 1;
|
||||
// Pass a sentinel via ctx to prove the pointer arg also survives the
|
||||
// round-trip — read it back as an i32 through *i32.
|
||||
|
||||
@@ -18,10 +18,10 @@ main :: () -> i32 {
|
||||
sel_with_utf8 := sel_registerName("stringWithUTF8String:".ptr);
|
||||
sel_utf8 := sel_registerName("UTF8String".ptr);
|
||||
|
||||
msg_3 : (*void, *void, [*]u8) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg_3 : (*void, *void, [*]u8) -> *void abi(.c) = xx objc_msgSend;
|
||||
ns_str := msg_3(ns_class, sel_with_utf8, "hi".ptr);
|
||||
|
||||
msg_2 : (*void, *void) -> [*]u8 callconv(.c) = xx objc_msgSend;
|
||||
msg_2 : (*void, *void) -> [*]u8 abi(.c) = xx objc_msgSend;
|
||||
back := msg_2(ns_str, sel_utf8);
|
||||
|
||||
return xx (back[0] + back[1]); // 'h' + 'i' = 104 + 105 = 209
|
||||
|
||||
@@ -17,7 +17,7 @@ g_marker : i32 = 0;
|
||||
|
||||
// IMP for `hello`. Must use C calling convention so `self` and `_cmd` land in
|
||||
// x0 and x1 the way the Obj-C runtime expects.
|
||||
hello_imp :: (self: *void, _cmd: *void) callconv(.c) {
|
||||
hello_imp :: (self: *void, _cmd: *void) abi(.c) {
|
||||
g_marker = 42;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ main :: () -> i32 {
|
||||
if obj == xx 0 { return 2; }
|
||||
|
||||
// [obj hello]
|
||||
msg : (*void, *void) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg : (*void, *void) -> void abi(.c) = xx objc_msgSend;
|
||||
msg(obj, sel_hello);
|
||||
|
||||
return g_marker; // 42 if hello_imp ran
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
main :: () -> i32 {
|
||||
cl := () => { print("noop block ran\n"); };
|
||||
b : Block = xx cl;
|
||||
invoke_fn : (*Block) -> void callconv(.c) = xx b.invoke;
|
||||
invoke_fn : (*Block) -> void abi(.c) = xx b.invoke;
|
||||
invoke_fn(@b);
|
||||
0
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ main :: () -> i32 {
|
||||
y : i64 = 100;
|
||||
cl := () => { print("x + y = {}\n", x + y); };
|
||||
b : Block = xx cl;
|
||||
invoke_fn : (*Block) -> void callconv(.c) = xx b.invoke;
|
||||
invoke_fn : (*Block) -> void abi(.c) = xx b.invoke;
|
||||
invoke_fn(@b);
|
||||
0
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
// Trampoline matching `void (^)(int, void*)` — the C ABI Apple's
|
||||
// runtime calls. Forwards through to the sx closure with the
|
||||
// standard `(__sx_ctx, env, ...args)` shape.
|
||||
__block_invoke_void_i32_p :: (block_self: *Block, arg0: i32, arg1: *void) callconv(.c) {
|
||||
__block_invoke_void_i32_p :: (block_self: *Block, arg0: i32, arg1: *void) abi(.c) {
|
||||
typed_fn : (*void, i32, *void) -> void = xx block_self.sx_fn;
|
||||
typed_fn(block_self.sx_env, arg0, arg1);
|
||||
}
|
||||
@@ -49,7 +49,7 @@ main :: () -> i32 {
|
||||
};
|
||||
b : Block = xx cl;
|
||||
|
||||
invoke_fn : (*Block, i32, *void) -> void callconv(.c) = xx b.invoke;
|
||||
invoke_fn : (*Block, i32, *void) -> void abi(.c) = xx b.invoke;
|
||||
sentinel: i32 = 42;
|
||||
invoke_fn(@b, 41, xx @sentinel);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#import "modules/ffi/objc_block.sx";
|
||||
|
||||
invoke_once :: (b: *Block) {
|
||||
invoke_fn : (*Block) -> void callconv(.c) = xx b.invoke;
|
||||
invoke_fn : (*Block) -> void abi(.c) = xx b.invoke;
|
||||
invoke_fn(b);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ main :: () -> i32 {
|
||||
|
||||
// [SxFoo alloc] — invokes the synthesized +alloc IMP.
|
||||
sel_alloc : SEL = sel_registerName("alloc".ptr);
|
||||
msg_fn : (cls: *void, sel: *void) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg_fn : (cls: *void, sel: *void) -> *void abi(.c) = xx objc_msgSend;
|
||||
instance : *void = msg_fn(cls, sel_alloc);
|
||||
if instance == null { print("FAIL: +alloc returned null\n"); return 1; }
|
||||
|
||||
|
||||
@@ -39,12 +39,12 @@ main :: () -> i32 {
|
||||
|
||||
// alloc + release — synthesized -dealloc IMP fires inside.
|
||||
sel_alloc : SEL = sel_registerName("alloc".ptr);
|
||||
alloc_fn : (cls: *void, sel: *void) -> *void callconv(.c) = xx objc_msgSend;
|
||||
alloc_fn : (cls: *void, sel: *void) -> *void abi(.c) = xx objc_msgSend;
|
||||
instance : *void = alloc_fn(cls, sel_alloc);
|
||||
if instance == null { print("FAIL: +alloc returned null\n"); return 1; }
|
||||
|
||||
sel_release : SEL = sel_registerName("release".ptr);
|
||||
release_fn : (obj: *void, sel: *void) -> void callconv(.c) = xx objc_msgSend;
|
||||
release_fn : (obj: *void, sel: *void) -> void abi(.c) = xx objc_msgSend;
|
||||
release_fn(instance, sel_release);
|
||||
|
||||
// Run another cycle to confirm dealloc didn't corrupt runtime state.
|
||||
|
||||
@@ -35,7 +35,7 @@ main :: () -> i32 {
|
||||
if method == null { print("FAIL: class method not on metaclass\n"); return 1; }
|
||||
|
||||
// Invoke via objc_msgSend: [SxFoo answer] → 42.
|
||||
msg_fn : (cls: *void, sel: *void) -> i32 callconv(.c) = xx objc_msgSend;
|
||||
msg_fn : (cls: *void, sel: *void) -> i32 abi(.c) = xx objc_msgSend;
|
||||
result : i32 = msg_fn(cls, sel_answer);
|
||||
if result != 42 { print("FAIL: expected 42, got {}\n", result); return 1; }
|
||||
|
||||
|
||||
@@ -38,13 +38,13 @@ main :: () -> i32 {
|
||||
|
||||
// [SxThing answer] → 42
|
||||
sel_answer : SEL = sel_registerName("answer".ptr);
|
||||
msg_int : (cls: *void, sel: *void) -> i32 callconv(.c) = xx objc_msgSend;
|
||||
msg_int : (cls: *void, sel: *void) -> i32 abi(.c) = xx objc_msgSend;
|
||||
r := msg_int(cls, sel_answer);
|
||||
if r != 42 { print("FAIL: answer expected 42, got {}\n", r); return 1; }
|
||||
|
||||
// [SxThing seedClass] returns a non-null NSObject.
|
||||
sel_seed : SEL = sel_registerName("seedClass".ptr);
|
||||
msg_ptr : (cls: *void, sel: *void) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg_ptr : (cls: *void, sel: *void) -> *void abi(.c) = xx objc_msgSend;
|
||||
seed := msg_ptr(cls, sel_seed);
|
||||
if seed == null { print("FAIL: seedClass returned null\n"); return 1; }
|
||||
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
// runtime ivar. Property dispatch should round-trip through them.
|
||||
g_probe_tag: i32 = 0;
|
||||
|
||||
probe_get_tag :: (self: *void, _cmd: *void) -> i32 callconv(.c) {
|
||||
probe_get_tag :: (self: *void, _cmd: *void) -> i32 abi(.c) {
|
||||
return g_probe_tag;
|
||||
}
|
||||
probe_set_tag :: (self: *void, _cmd: *void, v: i32) callconv(.c) {
|
||||
probe_set_tag :: (self: *void, _cmd: *void, v: i32) abi(.c) {
|
||||
g_probe_tag = v;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// through it and returns void.
|
||||
//
|
||||
// Register a runtime-built Obj-C class with a method that returns
|
||||
// a fixed `Triple`. The IMP is a plain sx fn (callconv .c) — its
|
||||
// a fixed `Triple`. The IMP is a plain sx fn (abi .c) — its
|
||||
// sret-shaped lowering already works (Phase 0.3 fix for plain
|
||||
// `extern` returns). The `#objc_call` dispatch side now produces
|
||||
// the matching call shape: `call void @objc_msgSend(ptr sret %slot,
|
||||
@@ -20,7 +20,7 @@ Triple :: struct { a: i64; b: i64; c: i64; }
|
||||
|
||||
// IMP for the runtime-installed method. Obj-C convention: implicit
|
||||
// (self, _cmd) prefix, then declared args. Returns the value bytes.
|
||||
triple_imp :: (self: *void, _cmd: *void) -> Triple callconv(.c) {
|
||||
triple_imp :: (self: *void, _cmd: *void) -> Triple abi(.c) {
|
||||
Triple.{ a = 11, b = 22, c = 33 }
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ UIEdgeInsets :: struct {
|
||||
right: f64;
|
||||
}
|
||||
|
||||
insets_imp :: (self: *void, _cmd: *void) -> UIEdgeInsets callconv(.c) {
|
||||
insets_imp :: (self: *void, _cmd: *void) -> UIEdgeInsets abi(.c) {
|
||||
UIEdgeInsets.{ top = 1.5, left = 2.5, bottom = 3.5, right = 4.5 }
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#import "modules/build.sx";
|
||||
#import "modules/ffi/objc.sx";
|
||||
|
||||
combine_imp :: (self: *void, _cmd: *void, a: i32, b: i32) -> i32 callconv(.c) {
|
||||
combine_imp :: (self: *void, _cmd: *void, a: i32, b: i32) -> i32 abi(.c) {
|
||||
a * 100 + b
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
#import "modules/build.sx";
|
||||
#import "modules/ffi/objc.sx";
|
||||
|
||||
yes_imp :: (self: *void, _cmd: *void) -> bool callconv(.c) { true }
|
||||
no_imp :: (self: *void, _cmd: *void) -> bool callconv(.c) { false }
|
||||
yes_imp :: (self: *void, _cmd: *void) -> bool abi(.c) { true }
|
||||
no_imp :: (self: *void, _cmd: *void) -> bool abi(.c) { false }
|
||||
|
||||
main :: () -> i32 {
|
||||
inline if OS == .macos {
|
||||
|
||||
@@ -20,11 +20,11 @@ CGRect :: struct {
|
||||
height: f64;
|
||||
}
|
||||
|
||||
rect_imp :: (self: *void, _cmd: *void) -> CGRect callconv(.c) {
|
||||
rect_imp :: (self: *void, _cmd: *void) -> CGRect abi(.c) {
|
||||
CGRect.{ x = 10.5, y = 20.5, width = 30.5, height = 40.5 }
|
||||
}
|
||||
|
||||
u64_imp :: (self: *void, _cmd: *void) -> u64 callconv(.c) {
|
||||
u64_imp :: (self: *void, _cmd: *void) -> u64 abi(.c) {
|
||||
// sx integer-literal parser rejects values ≥ 2^63 even when the
|
||||
// receiving type is u64, so the leading bit stays clear.
|
||||
0x7FEDCBA987654321
|
||||
|
||||
@@ -51,7 +51,7 @@ main :: () -> i32 {
|
||||
|
||||
// release
|
||||
sel_release : SEL = sel_registerName("release".ptr);
|
||||
release_fn : (obj: *void, sel: *void) -> void callconv(.c) = xx objc_msgSend;
|
||||
release_fn : (obj: *void, sel: *void) -> void abi(.c) = xx objc_msgSend;
|
||||
release_fn(xx f, sel_release);
|
||||
}
|
||||
inline if OS != .macos {
|
||||
|
||||
@@ -52,7 +52,7 @@ main :: () -> i32 {
|
||||
print("at: ({}, {})\n", p.x, p.y); // expected: at: (7.500000, 8.250000)
|
||||
|
||||
sel_release : SEL = sel_registerName("release".ptr);
|
||||
release_fn : (obj: *void, sel: *void) -> void callconv(.c) = xx objc_msgSend;
|
||||
release_fn : (obj: *void, sel: *void) -> void abi(.c) = xx objc_msgSend;
|
||||
release_fn(xx m, sel_release);
|
||||
}
|
||||
inline if OS != .macos {
|
||||
|
||||
@@ -20,7 +20,7 @@ SxProbeNiladic :: #objc_class("SxProbeNiladic") extern {
|
||||
length :: (self: *Self) -> i32;
|
||||
}
|
||||
|
||||
length_imp :: (self: *void, _cmd: *void) -> i32 callconv(.c) {
|
||||
length_imp :: (self: *void, _cmd: *void) -> i32 abi(.c) {
|
||||
42
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ SxProbeOneArg :: #objc_class("SxProbeOneArg") extern {
|
||||
addObject :: (self: *Self, val: i32) -> i32;
|
||||
}
|
||||
|
||||
addObject_imp :: (self: *void, _cmd: *void, val: i32) -> i32 callconv(.c) {
|
||||
addObject_imp :: (self: *void, _cmd: *void, val: i32) -> i32 abi(.c) {
|
||||
val * 2
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ SxProbeMultiKeyword :: #objc_class("SxProbeMultiKeyword") extern {
|
||||
combine_and :: (self: *Self, a: i32, b: i32) -> i32;
|
||||
}
|
||||
|
||||
combine_imp :: (self: *void, _cmd: *void, a: i32, b: i32) -> i32 callconv(.c) {
|
||||
combine_imp :: (self: *void, _cmd: *void, a: i32, b: i32) -> i32 abi(.c) {
|
||||
a * 100 + b
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ SxManglingProbe :: #objc_class("SxManglingProbe") extern {
|
||||
custom_name :: (self: *Self) -> i32 #selector("actualSelectorName");
|
||||
}
|
||||
|
||||
universal_imp :: (self: *void, _cmd: *void, a: i32, b: i32, c: i32, d: i32) -> i32 callconv(.c) {
|
||||
universal_imp :: (self: *void, _cmd: *void, a: i32, b: i32, c: i32, d: i32) -> i32 abi(.c) {
|
||||
// Returns the arg count's witness; the test doesn't check return
|
||||
// values, only that dispatch succeeds for each selector shape.
|
||||
a + b + c + d
|
||||
|
||||
@@ -31,7 +31,7 @@ main :: () -> i32 {
|
||||
print("counter: {}\n", b.get()); // expected: 2
|
||||
|
||||
sel_release : SEL = sel_registerName("release".ptr);
|
||||
release_fn : (obj: *void, sel: *void) -> void callconv(.c) = xx objc_msgSend;
|
||||
release_fn : (obj: *void, sel: *void) -> void abi(.c) = xx objc_msgSend;
|
||||
release_fn(xx b, sel_release);
|
||||
}
|
||||
inline if OS != .macos {
|
||||
|
||||
@@ -21,7 +21,7 @@ UIApplicationMain :: (argc: i32, argv: *void, principal_class: *NSString, delega
|
||||
// IMP for application:didFinishLaunchingWithOptions:
|
||||
// Obj-C: -(BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)opts
|
||||
// Type encoding: "c@:@@" -- BOOL (signed char), self, _cmd, id, id
|
||||
did_finish_launching :: (self: *void, _cmd: *void, app: *void, opts: *void) -> u8 callconv(.c) {
|
||||
did_finish_launching :: (self: *void, _cmd: *void, app: *void, opts: *void) -> u8 abi(.c) {
|
||||
NSLog(xx "[sx] application:didFinishLaunchingWithOptions: called\n");
|
||||
return 1; // YES
|
||||
}
|
||||
|
||||
@@ -25,14 +25,14 @@ g_window : *void = ---;
|
||||
// AppDelegate's `window` property. iOS queries this getter to discover the
|
||||
// app's key window; without it, the legacy code path creates its own empty
|
||||
// window and ignores anything we configure.
|
||||
window_getter :: (self: *void, _cmd: *void) -> *void callconv(.c) {
|
||||
window_getter :: (self: *void, _cmd: *void) -> *void abi(.c) {
|
||||
return g_window;
|
||||
}
|
||||
window_setter :: (self: *void, _cmd: *void, w: *void) callconv(.c) {
|
||||
window_setter :: (self: *void, _cmd: *void, w: *void) abi(.c) {
|
||||
g_window = w;
|
||||
}
|
||||
|
||||
did_finish_launching :: (self: *void, _cmd: *void, app: *void, opts: *void) -> u8 callconv(.c) {
|
||||
did_finish_launching :: (self: *void, _cmd: *void, app: *void, opts: *void) -> u8 abi(.c) {
|
||||
UIWindow := objc_getClass("UIWindow".ptr);
|
||||
UIViewController := objc_getClass("UIViewController".ptr);
|
||||
UIColor := objc_getClass("UIColor".ptr);
|
||||
@@ -48,10 +48,10 @@ did_finish_launching :: (self: *void, _cmd: *void, app: *void, opts: *void) -> u
|
||||
sel_connected_scenes := sel_registerName("connectedScenes".ptr);
|
||||
sel_any_object := sel_registerName("anyObject".ptr);
|
||||
|
||||
msg_o : (*void, *void) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg_v : (*void, *void) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg_oo : (*void, *void, *void) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg_ooo : (*void, *void, *void) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg_o : (*void, *void) -> *void abi(.c) = xx objc_msgSend;
|
||||
msg_v : (*void, *void) -> void abi(.c) = xx objc_msgSend;
|
||||
msg_oo : (*void, *void, *void) -> void abi(.c) = xx objc_msgSend;
|
||||
msg_ooo : (*void, *void, *void) -> *void abi(.c) = xx objc_msgSend;
|
||||
|
||||
// Modern iOS path: get the connected windowScene, then construct the
|
||||
// window via initWithWindowScene: so UIKit auto-sizes it and attaches
|
||||
|
||||
@@ -39,7 +39,7 @@ configure :: () {
|
||||
// IMP for application:didFinishLaunchingWithOptions:
|
||||
// Obj-C: -(BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)opts
|
||||
// Type encoding: "c@:@@" -- BOOL (signed char), self, _cmd, id, id
|
||||
did_finish_launching :: (self: *void, _cmd: *void, app: *void, opts: *void) -> u8 callconv(.c) {
|
||||
did_finish_launching :: (self: *void, _cmd: *void, app: *void, opts: *void) -> u8 abi(.c) {
|
||||
NSLog(xx "[sx-device-probe] launched\n");
|
||||
return 1; // YES
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// PLAN-HTTPZ C2: an sx `callconv(.c)` function passes by name as a
|
||||
// PLAN-HTTPZ C2: an sx `abi(.c)` function passes by name as a
|
||||
// typed C function pointer — libc qsort drives an sx comparator.
|
||||
#import "modules/std.sx";
|
||||
|
||||
clib :: #library "c";
|
||||
qsort :: (base: [*]u8, nel: usize, width: usize, compar: (*void, *void) -> i32 callconv(.c)) extern clib;
|
||||
qsort :: (base: [*]u8, nel: usize, width: usize, compar: (*void, *void) -> i32 abi(.c)) extern clib;
|
||||
|
||||
cmp_i32 :: (a: *void, b: *void) -> i32 callconv(.c) {
|
||||
cmp_i32 :: (a: *void, b: *void) -> i32 abi(.c) {
|
||||
pa : *i32 = xx a;
|
||||
pb : *i32 = xx b;
|
||||
if pa.* < pb.* { return -1; }
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// PLAN-HTTPZ C2: the C->sx re-entry contract. A real OS thread enters
|
||||
// sx through a `callconv(.c)` entry (which has NO implicit context),
|
||||
// sx through a `abi(.c)` entry (which has NO implicit context),
|
||||
// fabricates its own Context via `push Context.{ allocator = xx gpa }`,
|
||||
// and calls default-conv sx code that allocates through it.
|
||||
#import "modules/std.sx";
|
||||
|
||||
clib :: #library "c";
|
||||
pthread_create :: (thread: *usize, attr: *void, start: (*void) -> *void callconv(.c), arg: *void) -> i32 extern clib;
|
||||
pthread_create :: (thread: *usize, attr: *void, start: (*void) -> *void abi(.c), arg: *void) -> i32 extern clib;
|
||||
pthread_join :: (thread: usize, retval: **void) -> i32 extern clib;
|
||||
|
||||
entry :: (arg: *void) -> *void callconv(.c) {
|
||||
entry :: (arg: *void) -> *void abi(.c) {
|
||||
p : *i64 = xx arg;
|
||||
gpa := GPA.init();
|
||||
push Context.{ allocator = xx gpa } {
|
||||
|
||||
@@ -11,7 +11,7 @@ Shared :: struct {
|
||||
}
|
||||
|
||||
// Raw-thread entry: C->sx boundary, own fabricated context.
|
||||
bump_entry :: (arg: *void) -> *void callconv(.c) {
|
||||
bump_entry :: (arg: *void) -> *void abi(.c) {
|
||||
sh : *Shared = xx arg;
|
||||
gpa := GPA.init();
|
||||
push Context.{ allocator = xx gpa } {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// CALLS BACK into an sx function (`cb`) by its symbol, then returns. For the asm
|
||||
// `bl _cb` to resolve, the sx callback needs EXTERNAL LINKAGE and a stable C
|
||||
// symbol — that is exactly what `export` provides (it also implies the C ABI, so
|
||||
// no hidden context parameter). `callconv(.c)` alone is NOT enough: it sets the
|
||||
// no hidden context parameter). `abi(.c)` alone is NOT enough: it sets the
|
||||
// ABI but leaves the function `internal`, so it is dead-code-eliminated (nothing
|
||||
// in the IR references it — the `bl` is opaque to the optimizer) and `_cb` is
|
||||
// undefined at link. macOS gives `export "cb"` the symbol `_cb` (leading
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
--- void / 0 args ---
|
||||
__invoke :: (block_self: *Block) -> void callconv(.c) { typed_fn : (*void) -> void = xx block_self.sx_fn; typed_fn(block_self.sx_env); } return .{ isa = @_NSConcreteStackBlock, flags = 0, reserved = 0, invoke = xx @__invoke, descriptor = xx @__sx_block_descriptor, sx_env = self.env, sx_fn = self.fn_ptr, };
|
||||
__invoke :: (block_self: *Block) -> void abi(.c) { typed_fn : (*void) -> void = xx block_self.sx_fn; typed_fn(block_self.sx_env); } return .{ isa = @_NSConcreteStackBlock, flags = 0, reserved = 0, invoke = xx @__invoke, descriptor = xx @__sx_block_descriptor, sx_env = self.env, sx_fn = self.fn_ptr, };
|
||||
--- void / bool ---
|
||||
__invoke :: (block_self: *Block, arg0: bool) -> void callconv(.c) { typed_fn : (*void, bool) -> void = xx block_self.sx_fn; typed_fn(block_self.sx_env, arg0); } return .{ isa = @_NSConcreteStackBlock, flags = 0, reserved = 0, invoke = xx @__invoke, descriptor = xx @__sx_block_descriptor, sx_env = self.env, sx_fn = self.fn_ptr, };
|
||||
__invoke :: (block_self: *Block, arg0: bool) -> void abi(.c) { typed_fn : (*void, bool) -> void = xx block_self.sx_fn; typed_fn(block_self.sx_env, arg0); } return .{ isa = @_NSConcreteStackBlock, flags = 0, reserved = 0, invoke = xx @__invoke, descriptor = xx @__sx_block_descriptor, sx_env = self.env, sx_fn = self.fn_ptr, };
|
||||
--- void / i64, string ---
|
||||
__invoke :: (block_self: *Block, arg0: i64, arg1: string) -> void callconv(.c) { typed_fn : (*void, i64, string) -> void = xx block_self.sx_fn; typed_fn(block_self.sx_env, arg0, arg1); } return .{ isa = @_NSConcreteStackBlock, flags = 0, reserved = 0, invoke = xx @__invoke, descriptor = xx @__sx_block_descriptor, sx_env = self.env, sx_fn = self.fn_ptr, };
|
||||
__invoke :: (block_self: *Block, arg0: i64, arg1: string) -> void abi(.c) { typed_fn : (*void, i64, string) -> void = xx block_self.sx_fn; typed_fn(block_self.sx_env, arg0, arg1); } return .{ isa = @_NSConcreteStackBlock, flags = 0, reserved = 0, invoke = xx @__invoke, descriptor = xx @__sx_block_descriptor, sx_env = self.env, sx_fn = self.fn_ptr, };
|
||||
--- i32 / f64 ---
|
||||
__invoke :: (block_self: *Block, arg0: f64) -> i32 callconv(.c) { typed_fn : (*void, f64) -> i32 = xx block_self.sx_fn; return typed_fn(block_self.sx_env, arg0); } return .{ isa = @_NSConcreteStackBlock, flags = 0, reserved = 0, invoke = xx @__invoke, descriptor = xx @__sx_block_descriptor, sx_env = self.env, sx_fn = self.fn_ptr, };
|
||||
__invoke :: (block_self: *Block, arg0: f64) -> i32 abi(.c) { typed_fn : (*void, f64) -> i32 = xx block_self.sx_fn; return typed_fn(block_self.sx_env, arg0); } return .{ isa = @_NSConcreteStackBlock, flags = 0, reserved = 0, invoke = xx @__invoke, descriptor = xx @__sx_block_descriptor, sx_env = self.env, sx_fn = self.fn_ptr, };
|
||||
--- build done ---
|
||||
rt
|
||||
|
||||
1
examples/expected/0625-comptime-weld-struct-field.exit
Normal file
1
examples/expected/0625-comptime-weld-struct-field.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/expected/0625-comptime-weld-struct-field.stderr
Normal file
1
examples/expected/0625-comptime-weld-struct-field.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
examples/expected/0625-comptime-weld-struct-field.stdout
Normal file
1
examples/expected/0625-comptime-weld-struct-field.stdout
Normal file
@@ -0,0 +1 @@
|
||||
name=7 ty=3
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
hello, compiler
|
||||
@@ -1,5 +1,5 @@
|
||||
error: call-convention mismatch: 'sx_handler' is declared with default sx convention but the target type expects callconv(.c)
|
||||
--> examples/1104-diagnostics-callconv-mismatch-diagnostic.sx:12:42
|
||||
error: call-convention mismatch: 'sx_handler' is declared with default sx convention but the target type expects abi(.c)
|
||||
--> examples/1104-diagnostics-callconv-mismatch-diagnostic.sx:12:37
|
||||
|
|
||||
12 | fp : (*void) -> *void callconv(.c) = sx_handler;
|
||||
| ^^^^^^^^^^
|
||||
12 | fp : (*void) -> *void abi(.c) = sx_handler;
|
||||
| ^^^^^^^^^^
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -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; }
|
||||
| ^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -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;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -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 '::')
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -1 +1 @@
|
||||
callconv(.c): 42
|
||||
abi(.c): 42
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ objc_msgSend :: (recv: *void, sel: *void) -> *void extern objc;
|
||||
//
|
||||
// IMPs (method implementations) are function pointers with the implicit
|
||||
// Obj-C method shape: `(self: *void, _cmd: *void, ...args) -> ret` with
|
||||
// `callconv(.c)` so they land args in the standard C registers.
|
||||
// `abi(.c)` so they land args in the standard C registers.
|
||||
//
|
||||
// Method type encoding strings follow Apple's runtime DSL:
|
||||
// v = void c = char/BOOL i = int l = long f = float d = double
|
||||
@@ -120,7 +120,7 @@ impl Into(*NSString) for string {
|
||||
convert :: (self: string) -> *NSString {
|
||||
cls := objc_getClass("NSString".ptr);
|
||||
sel := sel_registerName("stringWithUTF8String:".ptr);
|
||||
msg : (*void, *void, [*]u8) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg : (*void, *void, [*]u8) -> *void abi(.c) = xx objc_msgSend;
|
||||
return xx msg(cls, sel, self.ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ __sx_block_descriptor : BlockDescriptor = .{
|
||||
// `$args` is the bound pack of arg types and `$R` is the bound
|
||||
// return type. The `#insert` evaluates `build_block_convert` at
|
||||
// comptime and substitutes the resulting source — a nested
|
||||
// `callconv(.c)` trampoline + the Block literal that points its
|
||||
// `abi(.c)` trampoline + the Block literal that points its
|
||||
// `invoke` slot at it. One impl in stdlib replaces every per-
|
||||
// signature hand-rolled `__block_invoke_*` + `Into(Block)` pair.
|
||||
impl Into(Block) for Closure(..$args) -> $R {
|
||||
@@ -77,7 +77,7 @@ impl Into(Block) for Closure(..$args) -> $R {
|
||||
// (`args`, a comptime `[]Type`) and the closure's return type
|
||||
// (`$ret`), emits source that:
|
||||
//
|
||||
// 1. Declares a nested `__invoke` `callconv(.c)` trampoline whose
|
||||
// 1. Declares a nested `__invoke` `abi(.c)` trampoline whose
|
||||
// signature matches the per-shape Apple Block ABI: first arg is
|
||||
// `block_self: *Block`, then the pack types verbatim. The body
|
||||
// reconstructs a typed fn-pointer from `block_self.sx_fn`,
|
||||
@@ -105,7 +105,7 @@ build_block_convert :: (args: []Type, $ret: Type) -> string {
|
||||
}
|
||||
code = concat(code, ") -> ");
|
||||
code = concat(code, ret_name);
|
||||
code = concat(code, " callconv(.c) { typed_fn : (*void");
|
||||
code = concat(code, " abi(.c) { typed_fn : (*void");
|
||||
i = 0;
|
||||
while i < args.len {
|
||||
code = concat(code, ", ");
|
||||
|
||||
@@ -25,39 +25,39 @@ GL_LINE :u32: 0x1B01;
|
||||
GL_FILL :u32: 0x1B02;
|
||||
|
||||
// Function pointer variables (mutable, loaded at runtime)
|
||||
glClearColor : (f32, f32, f32, f32) -> void callconv(.c) = ---;
|
||||
glClear : (u32) -> void callconv(.c) = ---;
|
||||
glEnable : (u32) -> void callconv(.c) = ---;
|
||||
glDisable : (u32) -> void callconv(.c) = ---;
|
||||
glViewport : (i32, i32, i32, i32) -> void callconv(.c) = ---;
|
||||
glFlush : () -> void callconv(.c) = ---;
|
||||
glDrawArrays : (u32, i32, i32) -> void callconv(.c) = ---;
|
||||
glPolygonMode : (u32, u32) -> void callconv(.c) = ---;
|
||||
glLineWidth : (f32) -> void callconv(.c) = ---;
|
||||
glCreateShader : (u32) -> u32 callconv(.c) = ---;
|
||||
glShaderSource : (u32, i32, *[:0]u8, *i32) -> void callconv(.c) = ---;
|
||||
glCompileShader : (u32) -> void callconv(.c) = ---;
|
||||
glGetShaderiv : (u32, u32, *i32) -> void callconv(.c) = ---;
|
||||
glGetShaderInfoLog : (u32, i32, *i32, [*]u8) -> void callconv(.c) = ---;
|
||||
glCreateProgram : () -> u32 callconv(.c) = ---;
|
||||
glAttachShader : (u32, u32) -> void callconv(.c) = ---;
|
||||
glLinkProgram : (u32) -> void callconv(.c) = ---;
|
||||
glGetProgramiv : (u32, u32, *i32) -> void callconv(.c) = ---;
|
||||
glGetProgramInfoLog : (u32, i32, *i32, [*]u8) -> void callconv(.c) = ---;
|
||||
glUseProgram : (u32) -> void callconv(.c) = ---;
|
||||
glDeleteShader : (u32) -> void callconv(.c) = ---;
|
||||
glGenVertexArrays : (i32, *u32) -> void callconv(.c) = ---;
|
||||
glGenBuffers : (i32, *u32) -> void callconv(.c) = ---;
|
||||
glBindVertexArray : (u32) -> void callconv(.c) = ---;
|
||||
glBindBuffer : (u32, u32) -> void callconv(.c) = ---;
|
||||
glBufferData : (u32, isize, *void, u32) -> void callconv(.c) = ---;
|
||||
glVertexAttribPointer : (u32, i32, u32, u8, i32, *void) -> void callconv(.c) = ---;
|
||||
glEnableVertexAttribArray : (u32) -> void callconv(.c) = ---;
|
||||
glGetUniformLocation : (u32, [*]u8) -> i32 callconv(.c) = ---;
|
||||
glUniformMatrix4fv : (i32, i32, u8, [*]f32) -> void callconv(.c) = ---;
|
||||
glUniform3f : (i32, f32, f32, f32) -> void callconv(.c) = ---;
|
||||
glDepthFunc : (u32) -> void callconv(.c) = ---;
|
||||
glUniform1f : (i32, f32) -> void callconv(.c) = ---;
|
||||
glClearColor : (f32, f32, f32, f32) -> void abi(.c) = ---;
|
||||
glClear : (u32) -> void abi(.c) = ---;
|
||||
glEnable : (u32) -> void abi(.c) = ---;
|
||||
glDisable : (u32) -> void abi(.c) = ---;
|
||||
glViewport : (i32, i32, i32, i32) -> void abi(.c) = ---;
|
||||
glFlush : () -> void abi(.c) = ---;
|
||||
glDrawArrays : (u32, i32, i32) -> void abi(.c) = ---;
|
||||
glPolygonMode : (u32, u32) -> void abi(.c) = ---;
|
||||
glLineWidth : (f32) -> void abi(.c) = ---;
|
||||
glCreateShader : (u32) -> u32 abi(.c) = ---;
|
||||
glShaderSource : (u32, i32, *[:0]u8, *i32) -> void abi(.c) = ---;
|
||||
glCompileShader : (u32) -> void abi(.c) = ---;
|
||||
glGetShaderiv : (u32, u32, *i32) -> void abi(.c) = ---;
|
||||
glGetShaderInfoLog : (u32, i32, *i32, [*]u8) -> void abi(.c) = ---;
|
||||
glCreateProgram : () -> u32 abi(.c) = ---;
|
||||
glAttachShader : (u32, u32) -> void abi(.c) = ---;
|
||||
glLinkProgram : (u32) -> void abi(.c) = ---;
|
||||
glGetProgramiv : (u32, u32, *i32) -> void abi(.c) = ---;
|
||||
glGetProgramInfoLog : (u32, i32, *i32, [*]u8) -> void abi(.c) = ---;
|
||||
glUseProgram : (u32) -> void abi(.c) = ---;
|
||||
glDeleteShader : (u32) -> void abi(.c) = ---;
|
||||
glGenVertexArrays : (i32, *u32) -> void abi(.c) = ---;
|
||||
glGenBuffers : (i32, *u32) -> void abi(.c) = ---;
|
||||
glBindVertexArray : (u32) -> void abi(.c) = ---;
|
||||
glBindBuffer : (u32, u32) -> void abi(.c) = ---;
|
||||
glBufferData : (u32, isize, *void, u32) -> void abi(.c) = ---;
|
||||
glVertexAttribPointer : (u32, i32, u32, u8, i32, *void) -> void abi(.c) = ---;
|
||||
glEnableVertexAttribArray : (u32) -> void abi(.c) = ---;
|
||||
glGetUniformLocation : (u32, [*]u8) -> i32 abi(.c) = ---;
|
||||
glUniformMatrix4fv : (i32, i32, u8, [*]f32) -> void abi(.c) = ---;
|
||||
glUniform3f : (i32, f32, f32, f32) -> void abi(.c) = ---;
|
||||
glDepthFunc : (u32) -> void abi(.c) = ---;
|
||||
glUniform1f : (i32, f32) -> void abi(.c) = ---;
|
||||
GL_LESS :u32: 0x0201;
|
||||
GL_LEQUAL :u32: 0x0203;
|
||||
GL_SCISSOR_TEST :u32: 0x0C11;
|
||||
@@ -76,27 +76,27 @@ GL_RED :u32: 0x1903;
|
||||
GL_R8 :u32: 0x8229;
|
||||
GL_UNPACK_ALIGNMENT :u32: 0x0CF5;
|
||||
|
||||
glScissor : (i32, i32, i32, i32) -> void callconv(.c) = ---;
|
||||
glBufferSubData : (u32, isize, isize, *void) -> void callconv(.c) = ---;
|
||||
glGenTextures : (i32, *u32) -> void callconv(.c) = ---;
|
||||
glBindTexture : (u32, u32) -> void callconv(.c) = ---;
|
||||
glTexImage2D : (u32, i32, i32, i32, i32, i32, u32, u32, *void) -> void callconv(.c) = ---;
|
||||
glTexParameteri : (u32, u32, i32) -> void callconv(.c) = ---;
|
||||
glBlendFunc : (u32, u32) -> void callconv(.c) = ---;
|
||||
glReadPixels : (i32, i32, i32, i32, u32, u32, *void) -> void callconv(.c) = ---;
|
||||
glActiveTexture : (u32) -> void callconv(.c) = ---;
|
||||
glUniform1i : (i32, i32) -> void callconv(.c) = ---;
|
||||
glPixelStorei : (u32, i32) -> void callconv(.c) = ---;
|
||||
glTexSubImage2D : (u32, i32, i32, i32, i32, i32, u32, u32, *void) -> void callconv(.c) = ---;
|
||||
glDeleteTextures : (i32, *u32) -> void callconv(.c) = ---;
|
||||
glScissor : (i32, i32, i32, i32) -> void abi(.c) = ---;
|
||||
glBufferSubData : (u32, isize, isize, *void) -> void abi(.c) = ---;
|
||||
glGenTextures : (i32, *u32) -> void abi(.c) = ---;
|
||||
glBindTexture : (u32, u32) -> void abi(.c) = ---;
|
||||
glTexImage2D : (u32, i32, i32, i32, i32, i32, u32, u32, *void) -> void abi(.c) = ---;
|
||||
glTexParameteri : (u32, u32, i32) -> void abi(.c) = ---;
|
||||
glBlendFunc : (u32, u32) -> void abi(.c) = ---;
|
||||
glReadPixels : (i32, i32, i32, i32, u32, u32, *void) -> void abi(.c) = ---;
|
||||
glActiveTexture : (u32) -> void abi(.c) = ---;
|
||||
glUniform1i : (i32, i32) -> void abi(.c) = ---;
|
||||
glPixelStorei : (u32, i32) -> void abi(.c) = ---;
|
||||
glTexSubImage2D : (u32, i32, i32, i32, i32, i32, u32, u32, *void) -> void abi(.c) = ---;
|
||||
glDeleteTextures : (i32, *u32) -> void abi(.c) = ---;
|
||||
|
||||
glGenFramebuffers : (i32, *u32) -> void callconv(.c) = ---;
|
||||
glGenRenderbuffers : (i32, *u32) -> void callconv(.c) = ---;
|
||||
glBindFramebuffer : (u32, u32) -> void callconv(.c) = ---;
|
||||
glBindRenderbuffer : (u32, u32) -> void callconv(.c) = ---;
|
||||
glFramebufferRenderbuffer : (u32, u32, u32, u32) -> void callconv(.c) = ---;
|
||||
glGetRenderbufferParameteriv : (u32, u32, *i32) -> void callconv(.c) = ---;
|
||||
glCheckFramebufferStatus : (u32) -> u32 callconv(.c) = ---;
|
||||
glGenFramebuffers : (i32, *u32) -> void abi(.c) = ---;
|
||||
glGenRenderbuffers : (i32, *u32) -> void abi(.c) = ---;
|
||||
glBindFramebuffer : (u32, u32) -> void abi(.c) = ---;
|
||||
glBindRenderbuffer : (u32, u32) -> void abi(.c) = ---;
|
||||
glFramebufferRenderbuffer : (u32, u32, u32, u32) -> void abi(.c) = ---;
|
||||
glGetRenderbufferParameteriv : (u32, u32, *i32) -> void abi(.c) = ---;
|
||||
glCheckFramebufferStatus : (u32) -> u32 abi(.c) = ---;
|
||||
|
||||
GL_TEXTURE_WRAP_S :u32: 0x2802;
|
||||
GL_TEXTURE_WRAP_T :u32: 0x2803;
|
||||
@@ -104,7 +104,7 @@ GL_CLAMP_TO_EDGE :u32: 0x812F;
|
||||
|
||||
// Loader: call once after creating GL context
|
||||
// Pass in a proc loader (e.g. SDL_GL_GetProcAddress)
|
||||
load_gl :: (get_proc: ([*]u8) -> *void callconv(.c)) {
|
||||
load_gl :: (get_proc: ([*]u8) -> *void abi(.c)) {
|
||||
glClearColor = xx get_proc("glClearColor");
|
||||
glClear = xx get_proc("glClear");
|
||||
glEnable = xx get_proc("glEnable");
|
||||
|
||||
@@ -58,7 +58,7 @@ MTLClearColor :: struct {
|
||||
// MTLRegion is 48 bytes and MTLScissorRect is 32 bytes; both are passed
|
||||
// by value to the Obj-C runtime, which the compiler marshals as
|
||||
// `ptr byval(<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; }
|
||||
MTLSize :: struct { width: u64; height: u64; depth: u64; }
|
||||
MTLRegion :: struct { origin: MTLOrigin; size: MTLSize; }
|
||||
@@ -257,11 +257,11 @@ metal_init_ios :: (self: *MetalGPU) -> bool {
|
||||
if self.device == null { return false; }
|
||||
}
|
||||
|
||||
msg_oo : (*void, *void, *void) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg_ou : (*void, *void, u64) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg_ob : (*void, *void, u8) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg_osize : (*void, *void, CGSize) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg_o : (*void, *void) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg_oo : (*void, *void, *void) -> void abi(.c) = xx objc_msgSend;
|
||||
msg_ou : (*void, *void, u64) -> void abi(.c) = xx objc_msgSend;
|
||||
msg_ob : (*void, *void, u8) -> void abi(.c) = xx objc_msgSend;
|
||||
msg_osize : (*void, *void, CGSize) -> void abi(.c) = xx objc_msgSend;
|
||||
msg_o : (*void, *void) -> *void abi(.c) = xx objc_msgSend;
|
||||
|
||||
if self.queue == null {
|
||||
self.queue = msg_o(self.device, sel_registerName("newCommandQueue".ptr));
|
||||
@@ -290,7 +290,7 @@ metal_resize_ios :: (self: *MetalGPU) {
|
||||
inline if OS != .ios { return; }
|
||||
if self.layer == null { return; }
|
||||
|
||||
msg_osize : (*void, *void, CGSize) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg_osize : (*void, *void, CGSize) -> void abi(.c) = xx objc_msgSend;
|
||||
size := CGSize.{ width = xx self.pixel_w, height = xx self.pixel_h };
|
||||
msg_osize(self.layer, sel_registerName("setDrawableSize:".ptr), size);
|
||||
}
|
||||
@@ -301,12 +301,12 @@ metal_begin_frame_ios :: (self: *MetalGPU, clear: ClearColor) -> bool {
|
||||
if self.queue == null { return false; }
|
||||
if self.pixel_w <= 0 or self.pixel_h <= 0 { return false; }
|
||||
|
||||
msg_o : (*void, *void) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg_oo : (*void, *void, *void) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg_oo_ret : (*void, *void, *void) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg_ou : (*void, *void, u64) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg_ouret : (*void, *void, u64) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg_oclear : (*void, *void, MTLClearColor) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg_o : (*void, *void) -> *void abi(.c) = xx objc_msgSend;
|
||||
msg_oo : (*void, *void, *void) -> void abi(.c) = xx objc_msgSend;
|
||||
msg_oo_ret : (*void, *void, *void) -> *void abi(.c) = xx objc_msgSend;
|
||||
msg_ou : (*void, *void, u64) -> void abi(.c) = xx objc_msgSend;
|
||||
msg_ouret : (*void, *void, u64) -> *void abi(.c) = xx objc_msgSend;
|
||||
msg_oclear : (*void, *void, MTLClearColor) -> void abi(.c) = xx objc_msgSend;
|
||||
|
||||
// drawable = [layer nextDrawable]
|
||||
self.drawable = msg_o(self.layer, sel_registerName("nextDrawable".ptr));
|
||||
@@ -353,9 +353,9 @@ metal_end_frame_ios :: (self: *MetalGPU, target_time: f64) {
|
||||
if self.cmd_buffer == null { return; }
|
||||
if self.drawable == null { return; }
|
||||
|
||||
msg_v : (*void, *void) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg_oo : (*void, *void, *void) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg_ood : (*void, *void, *void, f64) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg_v : (*void, *void) -> void abi(.c) = xx objc_msgSend;
|
||||
msg_oo : (*void, *void, *void) -> void abi(.c) = xx objc_msgSend;
|
||||
msg_ood : (*void, *void, *void, f64) -> void abi(.c) = xx objc_msgSend;
|
||||
|
||||
msg_v(self.encoder, sel_registerName("endEncoding".ptr));
|
||||
|
||||
@@ -386,15 +386,15 @@ metal_create_shader_ios :: (self: *MetalGPU, src: string) -> u32 {
|
||||
inline if OS != .ios { return 0; }
|
||||
if self.device == null { return 0; }
|
||||
|
||||
msg_o : (*void, *void) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg_oo : (*void, *void, *void) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg_oo_r : (*void, *void, *NSString) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg_ou : (*void, *void, u64) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg_ouret: (*void, *void, u64) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg_ob : (*void, *void, u8) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg_o : (*void, *void) -> *void abi(.c) = xx objc_msgSend;
|
||||
msg_oo : (*void, *void, *void) -> void abi(.c) = xx objc_msgSend;
|
||||
msg_oo_r : (*void, *void, *NSString) -> *void abi(.c) = xx objc_msgSend;
|
||||
msg_ou : (*void, *void, u64) -> void abi(.c) = xx objc_msgSend;
|
||||
msg_ouret: (*void, *void, u64) -> *void abi(.c) = xx objc_msgSend;
|
||||
msg_ob : (*void, *void, u8) -> void abi(.c) = xx objc_msgSend;
|
||||
|
||||
// [device newLibraryWithSource:src options:nil error:&err]
|
||||
msg_lib : (*void, *void, *NSString, *void, **void) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg_lib : (*void, *void, *NSString, *void, **void) -> *void abi(.c) = xx objc_msgSend;
|
||||
err : *void = null;
|
||||
library := msg_lib(self.device,
|
||||
sel_registerName("newLibraryWithSource:options:error:".ptr),
|
||||
@@ -428,7 +428,7 @@ metal_create_shader_ios :: (self: *MetalGPU, src: string) -> u32 {
|
||||
msg_ou(att0, sel_registerName("setSourceAlphaBlendFactor:".ptr), MTL_BLEND_FACTOR_SRC_ALPHA);
|
||||
msg_ou(att0, sel_registerName("setDestinationAlphaBlendFactor:".ptr), MTL_BLEND_FACTOR_ONE_MINUS_SRC_A);
|
||||
|
||||
msg_pipe : (*void, *void, *void, **void) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg_pipe : (*void, *void, *void, **void) -> *void abi(.c) = xx objc_msgSend;
|
||||
err2 : *void = null;
|
||||
state := msg_pipe(self.device,
|
||||
sel_registerName("newRenderPipelineStateWithDescriptor:error:".ptr),
|
||||
@@ -452,7 +452,7 @@ metal_create_buffer_ios :: (self: *MetalGPU, size_bytes: i64) -> u32 {
|
||||
if size_bytes <= 0 { return 0; }
|
||||
|
||||
// MTLResourceStorageModeShared is the default (option value 0).
|
||||
msg_buf : (*void, *void, u64, u64) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg_buf : (*void, *void, u64, u64) -> *void abi(.c) = xx objc_msgSend;
|
||||
buf := msg_buf(self.device,
|
||||
sel_registerName("newBufferWithLength:options:".ptr),
|
||||
xx size_bytes, 0);
|
||||
@@ -469,7 +469,7 @@ metal_update_buffer_ios :: (self: *MetalGPU, handle: u32, data: *void, size_byte
|
||||
if data == null { return; }
|
||||
if size_bytes <= 0 { return; }
|
||||
|
||||
msg_o : (*void, *void) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg_o : (*void, *void) -> *void abi(.c) = xx objc_msgSend;
|
||||
dst := msg_o(buf, sel_registerName("contents".ptr));
|
||||
if dst == null { return; }
|
||||
memcpy(dst, data, size_bytes);
|
||||
@@ -483,7 +483,7 @@ metal_update_buffer_at_ios :: (self: *MetalGPU, handle: u32, data: *void, size_b
|
||||
if size_bytes <= 0 { return; }
|
||||
if byte_offset < 0 { return; }
|
||||
|
||||
msg_o : (*void, *void) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg_o : (*void, *void) -> *void abi(.c) = xx objc_msgSend;
|
||||
base := msg_o(buf, sel_registerName("contents".ptr));
|
||||
if base == null { return; }
|
||||
// Add byte_offset via integer arithmetic — `@dst[i]` on `[*]u8`
|
||||
@@ -530,7 +530,7 @@ metal_create_texture_ios :: (self: *MetalGPU, w: i32, h: i32, format: TextureFor
|
||||
|
||||
// [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:width:height:mipmapped:]
|
||||
MTLTextureDescriptor := objc_getClass("MTLTextureDescriptor".ptr);
|
||||
msg_desc : (*void, *void, u64, u64, u64, u8) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg_desc : (*void, *void, u64, u64, u64, u8) -> *void abi(.c) = xx objc_msgSend;
|
||||
desc := msg_desc(MTLTextureDescriptor,
|
||||
sel_registerName("texture2DDescriptorWithPixelFormat:width:height:mipmapped:".ptr),
|
||||
pixel_format, xx w, xx h, 0);
|
||||
@@ -539,10 +539,10 @@ metal_create_texture_ios :: (self: *MetalGPU, w: i32, h: i32, format: TextureFor
|
||||
// Force shared storage so the CPU can keep writing pixels (atlas updates,
|
||||
// sprite uploads). On iOS-sim under Apple Silicon the convenience class
|
||||
// method's default storage isn't reliably shared for every format.
|
||||
msg_ou_void : (*void, *void, u64) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg_ou_void : (*void, *void, u64) -> void abi(.c) = xx objc_msgSend;
|
||||
msg_ou_void(desc, sel_registerName("setStorageMode:".ptr), MTL_STORAGE_MODE_SHARED);
|
||||
|
||||
msg_oo : (*void, *void, *void) -> *void callconv(.c) = xx objc_msgSend;
|
||||
msg_oo : (*void, *void, *void) -> *void abi(.c) = xx objc_msgSend;
|
||||
tex := msg_oo(self.device, sel_registerName("newTextureWithDescriptor:".ptr), desc);
|
||||
if tex == null { return 0; }
|
||||
|
||||
@@ -575,7 +575,7 @@ metal_update_texture_region_ios :: (self: *MetalGPU, handle: u32, x: i32, y: i32
|
||||
bytes_per_row : u64 = xx (slot.bytes_per_pixel * cast(u32) w);
|
||||
|
||||
// [tex replaceRegion:region mipmapLevel:0 withBytes:pixels bytesPerRow:bytes_per_row]
|
||||
msg_replace : (*void, *void, MTLRegion, u64, *void, u64) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg_replace : (*void, *void, MTLRegion, u64, *void, u64) -> void abi(.c) = xx objc_msgSend;
|
||||
msg_replace(slot.tex,
|
||||
sel_registerName("replaceRegion:mipmapLevel:withBytes:bytesPerRow:".ptr),
|
||||
region, 0, pixels, bytes_per_row);
|
||||
@@ -596,7 +596,7 @@ metal_destroy_shader_ios :: (self: *MetalGPU, handle: u32) {
|
||||
if h64 > self.shaders.len { return; }
|
||||
obj := self.shaders.items[handle - 1];
|
||||
if obj == null { return; }
|
||||
msg : (*void, *void) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg : (*void, *void) -> void abi(.c) = xx objc_msgSend;
|
||||
msg(obj, sel_registerName("release".ptr));
|
||||
self.shaders.items[handle - 1] = null;
|
||||
}
|
||||
@@ -608,7 +608,7 @@ metal_destroy_buffer_ios :: (self: *MetalGPU, handle: u32) {
|
||||
if h64 > self.buffers.len { return; }
|
||||
obj := self.buffers.items[handle - 1];
|
||||
if obj == null { return; }
|
||||
msg : (*void, *void) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg : (*void, *void) -> void abi(.c) = xx objc_msgSend;
|
||||
msg(obj, sel_registerName("release".ptr));
|
||||
self.buffers.items[handle - 1] = null;
|
||||
}
|
||||
@@ -620,7 +620,7 @@ metal_destroy_texture_ios :: (self: *MetalGPU, handle: u32) {
|
||||
if h64 > self.textures.len { return; }
|
||||
obj := self.textures.items[handle - 1].tex;
|
||||
if obj == null { return; }
|
||||
msg : (*void, *void) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg : (*void, *void) -> void abi(.c) = xx objc_msgSend;
|
||||
msg(obj, sel_registerName("release".ptr));
|
||||
self.textures.items[handle - 1].tex = null;
|
||||
self.textures.items[handle - 1].bytes_per_pixel = 0;
|
||||
@@ -633,7 +633,7 @@ metal_set_shader_ios :: (self: *MetalGPU, sh: u32) {
|
||||
if self.encoder == null { return; }
|
||||
state := metal_lookup_shader(self, sh);
|
||||
if state == null { return; }
|
||||
msg : (*void, *void, *void) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg : (*void, *void, *void) -> void abi(.c) = xx objc_msgSend;
|
||||
msg(self.encoder, sel_registerName("setRenderPipelineState:".ptr), state);
|
||||
}
|
||||
|
||||
@@ -643,7 +643,7 @@ metal_set_vertex_buffer_ios :: (self: *MetalGPU, h: u32) {
|
||||
buf := metal_lookup_buffer(self, h);
|
||||
if buf == null { return; }
|
||||
// [encoder setVertexBuffer:buf offset:0 atIndex:0]
|
||||
msg : (*void, *void, *void, u64, u64) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg : (*void, *void, *void, u64, u64) -> void abi(.c) = xx objc_msgSend;
|
||||
msg(self.encoder, sel_registerName("setVertexBuffer:offset:atIndex:".ptr), buf, 0, 0);
|
||||
}
|
||||
|
||||
@@ -656,7 +656,7 @@ metal_set_texture_ios :: (self: *MetalGPU, slot: u32, h: u32) {
|
||||
tex := self.textures.items[h - 1].tex;
|
||||
if tex == null { return; }
|
||||
// [encoder setFragmentTexture:tex atIndex:slot]
|
||||
msg : (*void, *void, *void, u64) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg : (*void, *void, *void, u64) -> void abi(.c) = xx objc_msgSend;
|
||||
msg(self.encoder, sel_registerName("setFragmentTexture:atIndex:".ptr), tex, xx slot);
|
||||
}
|
||||
|
||||
@@ -666,7 +666,7 @@ metal_set_vertex_constants_ios :: (self: *MetalGPU, slot: u32, data: *void, size
|
||||
if data == null { return; }
|
||||
if size_bytes <= 0 { return; }
|
||||
// [encoder setVertexBytes:data length:size_bytes atIndex:slot]
|
||||
msg : (*void, *void, *void, u64, u64) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg : (*void, *void, *void, u64, u64) -> void abi(.c) = xx objc_msgSend;
|
||||
msg(self.encoder, sel_registerName("setVertexBytes:length:atIndex:".ptr),
|
||||
data, xx size_bytes, xx slot);
|
||||
}
|
||||
@@ -676,7 +676,7 @@ metal_set_scissor_ios :: (self: *MetalGPU, x: i32, y: i32, w: i32, h: i32) {
|
||||
if self.encoder == null { return; }
|
||||
rect : MTLScissorRect = .{ x = xx x, y = xx y, width = xx w, height = xx h };
|
||||
// [encoder setScissorRect:rect] (MTLScissorRect is 32 bytes → ptr byval)
|
||||
msg : (*void, *void, MTLScissorRect) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg : (*void, *void, MTLScissorRect) -> void abi(.c) = xx objc_msgSend;
|
||||
msg(self.encoder, sel_registerName("setScissorRect:".ptr), rect);
|
||||
}
|
||||
|
||||
@@ -686,7 +686,7 @@ metal_disable_scissor_ios :: (self: *MetalGPU) {
|
||||
// Metal has no "disable scissor" — set the rect to cover the full
|
||||
// drawable so subsequent draws aren't clipped.
|
||||
rect : MTLScissorRect = .{ x = 0, y = 0, width = xx self.pixel_w, height = xx self.pixel_h };
|
||||
msg : (*void, *void, MTLScissorRect) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg : (*void, *void, MTLScissorRect) -> void abi(.c) = xx objc_msgSend;
|
||||
msg(self.encoder, sel_registerName("setScissorRect:".ptr), rect);
|
||||
}
|
||||
|
||||
@@ -695,7 +695,7 @@ metal_draw_triangles_ios :: (self: *MetalGPU, vertex_offset: i32, vertex_count:
|
||||
if self.encoder == null { return; }
|
||||
if vertex_count <= 0 { return; }
|
||||
// [encoder drawPrimitives:.triangle vertexStart:offset vertexCount:count]
|
||||
msg : (*void, *void, u64, u64, u64) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg : (*void, *void, u64, u64, u64) -> void abi(.c) = xx objc_msgSend;
|
||||
msg(self.encoder, sel_registerName("drawPrimitives:vertexStart:vertexCount:".ptr),
|
||||
MTL_PRIMITIVE_TYPE_TRIANGLE, xx vertex_offset, xx vertex_count);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ ANativeWindow_setBuffersGeometry :: (w: *void, width: i32, height: i32, fmt: i32
|
||||
AAssetManager_fromJava :: (env: *void, mgr: *void) -> *void extern;
|
||||
|
||||
// pthread (link libpthread is built into bionic).
|
||||
pthread_create :: (thread: *u64, attr: *void, start: (*void) -> *void callconv(.c), arg: *void) -> i32 extern;
|
||||
pthread_create :: (thread: *u64, attr: *void, start: (*void) -> *void abi(.c), arg: *void) -> i32 extern;
|
||||
pthread_mutex_init :: (m: *void, attr: *void) -> i32 extern;
|
||||
pthread_mutex_lock :: (m: *void) -> i32 extern;
|
||||
pthread_mutex_unlock :: (m: *void) -> i32 extern;
|
||||
@@ -254,7 +254,7 @@ sx_android_start_render_thread :: (plat: *AndroidPlatform, entry_fn: () -> void)
|
||||
plat.render_thread_started = true;
|
||||
}
|
||||
|
||||
sx_android_render_thread_entry :: (arg: *void) -> *void callconv(.c) {
|
||||
sx_android_render_thread_entry :: (arg: *void) -> *void abi(.c) {
|
||||
plat : *AndroidPlatform = xx arg;
|
||||
while plat.app_window == null and !plat.should_stop {
|
||||
usleep(1000);
|
||||
|
||||
@@ -229,7 +229,7 @@ impl Platform for SdlPlatform {
|
||||
// SDL fires the watch synchronously when events are added — including during
|
||||
// macOS's modal resize-drag, when SDL_PollEvent can't run. Re-invoking the
|
||||
// frame closure here keeps content rendering at the new size during the drag.
|
||||
sdl_event_watch :: (userdata: *void, event: *SDL_Event) -> bool callconv(.c) {
|
||||
sdl_event_watch :: (userdata: *void, event: *SDL_Event) -> bool abi(.c) {
|
||||
plat : *SdlPlatform = xx userdata;
|
||||
if event.* == {
|
||||
case .window_resized: (data) {
|
||||
@@ -245,7 +245,7 @@ sdl_event_watch :: (userdata: *void, event: *SDL_Event) -> bool callconv(.c) {
|
||||
true
|
||||
}
|
||||
|
||||
sdl_wasm_tick :: () callconv(.c) {
|
||||
sdl_wasm_tick :: () abi(.c) {
|
||||
if g_sdl_plat == null { return; }
|
||||
if !g_sdl_plat.has_frame_closure { return; }
|
||||
fn := g_sdl_plat.frame_closure;
|
||||
|
||||
@@ -790,8 +790,8 @@ impl Platform for UIKitPlatform {
|
||||
}
|
||||
|
||||
// dlsym(RTLD_DEFAULT, name) — Apple platforms. RTLD_DEFAULT is (void*)-2.
|
||||
// callconv(.c) so this is callable from `load_gl`'s C-conv proc-loader slot.
|
||||
ios_gl_proc :: (name: [*]u8) -> *void callconv(.c) {
|
||||
// abi(.c) so this is callable from `load_gl`'s C-conv proc-loader slot.
|
||||
ios_gl_proc :: (name: [*]u8) -> *void abi(.c) {
|
||||
rtld_default : *void = xx (0 - 2);
|
||||
dlsym(rtld_default, name)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// pthreads (PLAN-HTTPZ S6).
|
||||
//
|
||||
// THE RE-ENTRY CONTRACT (pinned by examples/1636): a thread entry is a
|
||||
// `callconv(.c)` function — it has NO implicit context — and enters
|
||||
// `abi(.c)` function — it has NO implicit context — and enters
|
||||
// the sx world by fabricating one: `push Context.{ allocator = xx gpa }`
|
||||
// around the default-conv code it runs. Pool workers do exactly that,
|
||||
// each with its own malloc-backed GPA, so tasks allocate freely and
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
tlib :: #library "c";
|
||||
|
||||
pthread_create :: (thread: *usize, attr: *void, start: (*void) -> *void callconv(.c), arg: *void) -> i32 extern tlib;
|
||||
pthread_create :: (thread: *usize, attr: *void, start: (*void) -> *void abi(.c), arg: *void) -> i32 extern tlib;
|
||||
pthread_join :: (thread: usize, retval: **void) -> i32 extern tlib;
|
||||
pthread_detach :: (thread: usize) -> i32 extern tlib;
|
||||
|
||||
@@ -104,9 +104,9 @@ Cond :: struct {
|
||||
Thread :: struct {
|
||||
handle: usize = 0;
|
||||
|
||||
// `entry` is the C->sx boundary: callconv(.c), fabricates its own
|
||||
// `entry` is the C->sx boundary: abi(.c), fabricates its own
|
||||
// Context before touching default-conv sx code (examples/1636).
|
||||
spawn :: (entry: (*void) -> *void callconv(.c), arg: *void) -> (Thread, !ThreadErr) {
|
||||
spawn :: (entry: (*void) -> *void abi(.c), arg: *void) -> (Thread, !ThreadErr) {
|
||||
t : Thread = .{};
|
||||
if pthread_create(@t.handle, null, entry, arg) != 0 { raise error.Spawn; }
|
||||
return t;
|
||||
@@ -200,7 +200,7 @@ Pool :: struct {
|
||||
|
||||
// The worker loop: C entry, own fabricated Context, then
|
||||
// pop-task/run-task until stop with an empty queue.
|
||||
pool_worker :: (arg: *void) -> *void callconv(.c) {
|
||||
pool_worker :: (arg: *void) -> *void abi(.c) {
|
||||
p : *Pool = xx arg;
|
||||
gpa := GPA.init();
|
||||
push Context.{ allocator = xx gpa } {
|
||||
|
||||
@@ -427,7 +427,7 @@ write_fd :: (fd: i32, buf: [*]u8, count: u64) -> i64 extern libc "write";
|
||||
|
||||
`extern` / `export` are the keyword surface for C linkage. `extern` imports a
|
||||
symbol defined elsewhere; `export` is its dual — define a function in sx and
|
||||
expose it under the C ABI so C can call back in. Both imply `callconv(.c)` and take
|
||||
expose it under the C ABI so C can call back in. Both imply `abi(.c)` and take
|
||||
the same optional `[LIB] ["csym"]` rename tail; they also apply to data globals and
|
||||
to Obj-C / JNI runtime-class aggregates (postfix after the `#objc_class(…)` directive).
|
||||
```sx
|
||||
|
||||
5
specs.md
5
specs.md
@@ -1194,8 +1194,9 @@ C linkage is expressed with the postfix `extern` (import) and `export` (define +
|
||||
expose) keywords. `extern` declares a symbol defined elsewhere — a C function or
|
||||
data global resolved at link time; `export` is its dual — **define** a symbol in
|
||||
sx and expose it under the C ABI so C (or asm, or another language) can call it.
|
||||
Both imply `callconv(.c)`, carry external linkage, and suppress the implicit sx
|
||||
context parameter. They are postfix modifiers, written where `callconv` would go.
|
||||
Both imply `abi(.c)`, carry external linkage, and suppress the implicit sx
|
||||
context parameter. They are postfix modifiers, written in the slot after the
|
||||
`abi(...)` annotation.
|
||||
|
||||
```sx
|
||||
// Declare a named library constant
|
||||
|
||||
52
src/ast.zig
52
src/ast.zig
@@ -122,14 +122,26 @@ pub const Root = struct {
|
||||
decls: []const *Node,
|
||||
};
|
||||
|
||||
pub const CallingConvention = enum { default, c };
|
||||
/// ABI / calling-convention annotation written as the postfix `abi(.x)` form on a
|
||||
/// function declaration, function-type literal, or lambda. Subsumes the old
|
||||
/// `callconv(...)` spelling.
|
||||
/// - `.default` — no annotation: the ordinary sx-internal convention (implicit
|
||||
/// context, sx ABI). There is no surface spelling for `.default`; it is the
|
||||
/// value when `abi(...)` is absent.
|
||||
/// - `.c` — C ABI / cdecl, no implicit context (what `callconv(.c)` meant).
|
||||
/// - `.zig` — welded to the real internal Zig type/fn: layout follows the bound
|
||||
/// Zig type, functions dispatch over the comptime host-call bridge. The
|
||||
/// `compiler` library (`design/comptime-compiler-api.md`) binds via `abi(.zig)`.
|
||||
/// - `.pure` — a pure / naked function (inline asm body), no calling-convention
|
||||
/// prologue/epilogue.
|
||||
pub const ABI = enum { default, c, zig, pure };
|
||||
|
||||
/// Linkage modifier written in the postfix slot after `callconv(...)`:
|
||||
/// `name :: (sig) -> Ret [callconv(.x)] [extern | export] [;|{…}];`
|
||||
/// `extern` = import (external linkage, C ABI, no sx ctx — `extern`'s role);
|
||||
/// `export` = define + expose (body + external linkage + C ABI + no ctx).
|
||||
/// Both imply `callconv(.c)`. Variants carry a trailing `_` to dodge the Zig
|
||||
/// keywords. `.none` = no linkage modifier (the ordinary sx-internal decl).
|
||||
/// Linkage modifier written in the postfix slot before `abi(...)`:
|
||||
/// `name :: (sig) -> Ret [extern | export] [abi(.x)] [lib] [;|{…}];`
|
||||
/// `extern` = import (external linkage, no sx ctx — `extern`'s role);
|
||||
/// `export` = define + expose (body + external linkage + no ctx).
|
||||
/// Variants carry a trailing `_` to dodge the Zig keywords. `.none` = no linkage
|
||||
/// modifier (the ordinary sx-internal decl).
|
||||
pub const ExternExportModifier = enum { none, extern_, export_ };
|
||||
|
||||
pub const FnDecl = struct {
|
||||
@@ -139,10 +151,15 @@ pub const FnDecl = struct {
|
||||
body: *Node,
|
||||
type_params: []const StructTypeParam = &.{},
|
||||
is_arrow: bool = false,
|
||||
call_conv: CallingConvention = .default,
|
||||
/// Postfix linkage modifier (`extern`/`export`) written after the
|
||||
/// `callconv(...)` slot. `.none` for an ordinary sx-internal function.
|
||||
/// Parsed in Phase 0.1; not consumed by the fn-decl path until Phase 1.
|
||||
/// ABI / calling-convention annotation (`abi(.c)` / `abi(.zig)` / `abi(.pure)`)
|
||||
/// in the postfix slot after `extern`/`export`. `.default` = unannotated.
|
||||
/// `.zig` marks a function bound to the comptime `compiler` library — its
|
||||
/// signature is welded to the real internal Zig fn and it dispatches over the
|
||||
/// host-call bridge at comptime (consumed by the binding registry + host-call
|
||||
/// bridge in later phases).
|
||||
abi: ABI = .default,
|
||||
/// Postfix linkage modifier (`extern`/`export`) written before the `abi(...)`
|
||||
/// slot. `.none` for an ordinary sx-internal function.
|
||||
extern_export: ExternExportModifier = .none,
|
||||
/// Optional library reference + symbol-name override for an `extern`/`export`
|
||||
/// function, the optional library + symbol-name override. Both
|
||||
@@ -510,6 +527,15 @@ pub const StructDecl = struct {
|
||||
using_entries: []const UsingEntry = &.{},
|
||||
methods: []const *Node = &.{}, // fn_decl nodes for struct methods
|
||||
constants: []const *Node = &.{}, // const_decl nodes for struct-level constants
|
||||
/// ABI / layout annotation (`struct abi(.zig) extern <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
|
||||
/// (`` `i2 :: struct { … } ``) — exempt from the reserved-type-name decl
|
||||
/// check. A bare reserved-name decl still errors.
|
||||
@@ -533,7 +559,7 @@ pub const Lambda = struct {
|
||||
return_type: ?*Node,
|
||||
body: *Node,
|
||||
type_params: []const StructTypeParam = &.{},
|
||||
call_conv: CallingConvention = .default,
|
||||
abi: ABI = .default,
|
||||
};
|
||||
|
||||
pub const TypeExpr = struct {
|
||||
@@ -805,7 +831,7 @@ pub const FunctionTypeExpr = struct {
|
||||
param_types: []const *Node,
|
||||
param_names: ?[]const ?[]const u8 = null, // optional documentation names
|
||||
return_type: ?*Node, // null = void return
|
||||
call_conv: CallingConvention = .default,
|
||||
abi: ABI = .default,
|
||||
};
|
||||
|
||||
pub const ClosureTypeExpr = struct {
|
||||
|
||||
@@ -29,12 +29,12 @@ pub const AbiLowering = struct {
|
||||
/// `is_extern_c_api` knob. When true, sx `string` / `[]T` slices
|
||||
/// collapse to `ptr` — the libc convention where the user writes
|
||||
/// `string` to mean `char *` and the length is dropped. When
|
||||
/// false (sx-internal `callconv(.c)` like block trampolines), the
|
||||
/// false (sx-internal `abi(.c)` like block trampolines), the
|
||||
/// full slice shape is preserved and goes through the general
|
||||
/// struct-coerce path (16-byte slice → `[2 x i64]`, lands in two
|
||||
/// registers on AArch64 — the true C ABI for a 16-byte
|
||||
/// aggregate). Without the split, sx-to-sx calls through a
|
||||
/// `(*Block, string) -> void callconv(.c)` fn-pointer mismatched
|
||||
/// `(*Block, string) -> void abi(.c)` fn-pointer mismatched
|
||||
/// the caller's `{ptr, i64}` value against the trampoline's
|
||||
/// collapsed `ptr` param.
|
||||
pub fn abiCoerceParamTypeEx(self: AbiLowering, ir_ty: TypeId, llvm_ty: c.LLVMTypeRef, is_extern_c_api: bool) c.LLVMTypeRef {
|
||||
|
||||
@@ -1118,6 +1118,23 @@ pub const Ops = struct {
|
||||
pub fn emitCall(self: Ops, instruction: *const Inst, call_op: Call) void {
|
||||
// Evaluate comptime functions at compile time
|
||||
const callee_func = &self.e.ir_mod.functions.items[call_op.callee.index()];
|
||||
|
||||
// Welded `compiler`-library functions are comptime-only — they have no
|
||||
// runtime symbol (the comptime interp dispatches them to a Zig handler).
|
||||
// A welded call inside a RUNTIME function is illegal; surface a clean
|
||||
// build-gating error instead of an undefined-symbol link failure. A
|
||||
// welded call inside a COMPTIME function (a `#run` / `::` initializer
|
||||
// wrapper, `is_comptime`) is fine — that body is interp-evaluated and its
|
||||
// LLVM emission is dead, so skip the gate there.
|
||||
const enclosing = &self.e.ir_mod.functions.items[self.e.current_func_idx];
|
||||
if (callee_func.compiler_welded and !enclosing.is_comptime) {
|
||||
const fname = self.e.ir_mod.types.getString(callee_func.name);
|
||||
std.debug.print("error: '{s}' is a comptime-only compiler-library function — it cannot be called at runtime (use it inside #run or a comptime '::')\n", .{fname});
|
||||
self.e.comptime_failed = true;
|
||||
self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (callee_func.is_comptime and call_op.args.len == 0) {
|
||||
var interp_inst = Interpreter.init(self.e.ir_mod, self.e.alloc);
|
||||
interp_inst.build_config = &self.e.build_config;
|
||||
|
||||
177
src/ir/compiler_lib.test.zig
Normal file
177
src/ir/compiler_lib.test.zig
Normal 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
291
src/ir/compiler_lib.zig
Normal 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) };
|
||||
}
|
||||
@@ -1273,7 +1273,7 @@ pub const LLVMEmitter = struct {
|
||||
else if (needs_c_abi) self.abiCoerceParamTypeEx(func.ret, raw_ret_ty, func.is_extern)
|
||||
else raw_ret_ty;
|
||||
|
||||
// Build parameter types — apply C ABI coercion for extern/callconv(.c) functions.
|
||||
// Build parameter types — apply C ABI coercion for extern/abi(.c) functions.
|
||||
// When uses_sret, prepend the sret pointer at index 0.
|
||||
const sret_offset: usize = if (uses_sret) 1 else 0;
|
||||
const param_count: c_uint = @intCast(func.params.len + sret_offset);
|
||||
|
||||
@@ -575,8 +575,14 @@ pub const Function = struct {
|
||||
/// parameter that every default-conv sx function receives. Callers
|
||||
/// read this flag to decide whether to prepend their current
|
||||
/// `__sx_ctx` value to the args of a call. Extern decls and
|
||||
/// `callconv(.c)` functions have it false.
|
||||
/// `abi(.c)` functions have it false.
|
||||
has_implicit_ctx: bool = false,
|
||||
/// True for a `fn abi(.zig) extern compiler` welded to the comptime
|
||||
/// `compiler` library. Such a function has no real symbol — the comptime
|
||||
/// interpreter dispatches it to its registered Zig handler
|
||||
/// (`compiler_lib.findFn`) instead of dlsym. Comptime-only; a runtime call
|
||||
/// has no backing symbol. See design/comptime-compiler-api.md.
|
||||
compiler_welded: bool = false,
|
||||
|
||||
pub const Param = struct {
|
||||
name: StringId,
|
||||
|
||||
@@ -162,6 +162,7 @@ pub const InterpError = error{
|
||||
|
||||
const compiler_hooks = @import("compiler_hooks.zig");
|
||||
pub const BuildConfig = compiler_hooks.BuildConfig;
|
||||
const compiler_lib = @import("compiler_lib.zig");
|
||||
const host_ffi = @import("host_ffi.zig");
|
||||
|
||||
// ── Interpreter ─────────────────────────────────────────────────────────
|
||||
@@ -579,6 +580,15 @@ pub const Interpreter = struct {
|
||||
defer self.call_depth -= 1;
|
||||
|
||||
const func = self.module.getFunction(func_id);
|
||||
// Welded `compiler`-library function: dispatch to its registered Zig
|
||||
// handler (comptime-only), never dlsym. The binding registry IS the
|
||||
// safety boundary — a name not on the export list is a clean bail.
|
||||
if (func.compiler_welded) {
|
||||
const fname = self.module.types.getString(func.name);
|
||||
const bf = compiler_lib.findFn(fname) orelse
|
||||
return bailDetail("comptime compiler call: function not exported by the compiler library");
|
||||
return bf.handler(self, args);
|
||||
}
|
||||
if (func.is_extern or func.blocks.items.len == 0) {
|
||||
// Dispatch to host libc via dlsym. Lets `#run` (and the
|
||||
// post-link bundler) call ordinary extern symbols like
|
||||
|
||||
@@ -60,6 +60,7 @@ pub const ObjcLowering = ffi_objc.ObjcLowering;
|
||||
pub const ErrorFacts = error_analysis.ErrorFacts;
|
||||
|
||||
pub const compiler_hooks = @import("compiler_hooks.zig");
|
||||
pub const compiler_lib = @import("compiler_lib.zig");
|
||||
pub const emit_llvm = @import("emit_llvm.zig");
|
||||
pub const LLVMEmitter = emit_llvm.LLVMEmitter;
|
||||
|
||||
@@ -89,6 +90,7 @@ pub const type_bridge_tests = @import("type_bridge.test.zig");
|
||||
pub const emit_llvm_tests = @import("emit_llvm.test.zig");
|
||||
pub const jni_descriptor_tests = @import("jni_descriptor.test.zig");
|
||||
pub const jni_java_emit_tests = @import("jni_java_emit.test.zig");
|
||||
pub const compiler_lib_tests = @import("compiler_lib.test.zig");
|
||||
|
||||
test {
|
||||
@import("std").testing.refAllDecls(@This());
|
||||
|
||||
@@ -78,7 +78,7 @@ pub fn lowerLambda(self: *Lowering, lam: *const ast.Lambda) Ref {
|
||||
// Without implicit_ctx, env is slot 0 and user params follow.
|
||||
var params = std.ArrayList(Function.Param).empty;
|
||||
const env_ptr_ty = self.module.types.ptrTo(.void);
|
||||
const lambda_wants_ctx = self.implicit_ctx_enabled and lam.call_conv != .c;
|
||||
const lambda_wants_ctx = self.implicit_ctx_enabled and lam.abi != .c;
|
||||
if (lambda_wants_ctx) {
|
||||
params.append(self.alloc, .{
|
||||
.name = self.module.types.internString("__sx_ctx"),
|
||||
@@ -168,7 +168,7 @@ pub fn lowerLambda(self: *Lowering, lam: *const ast.Lambda) Ref {
|
||||
};
|
||||
const name_id = self.module.types.internString(name);
|
||||
const func_id = self.builder.beginFunction(name_id, params.items, ret_ty);
|
||||
if (lam.call_conv == .c) {
|
||||
if (lam.abi == .c) {
|
||||
self.module.getFunctionMut(func_id).call_conv = .c;
|
||||
}
|
||||
self.builder.currentFunc().has_implicit_ctx = lambda_wants_ctx;
|
||||
|
||||
@@ -10,6 +10,7 @@ const unescape = @import("../../unescape.zig");
|
||||
const errors = @import("../../errors.zig");
|
||||
const program_index_mod = @import("../program_index.zig");
|
||||
const resolver_mod = @import("../resolver.zig");
|
||||
const compiler_lib = @import("../compiler_lib.zig");
|
||||
const ProgramIndex = program_index_mod.ProgramIndex;
|
||||
const GlobalInfo = program_index_mod.GlobalInfo;
|
||||
const ModuleConstInfo = program_index_mod.ModuleConstInfo;
|
||||
@@ -497,7 +498,7 @@ pub fn detectContextDecl(decls: []const *const Node) bool {
|
||||
/// `__sx_<name>_impl` with the ctx param) lands in Step 4 proper.
|
||||
pub fn funcWantsImplicitCtx(self: *const Lowering, fd: *const ast.FnDecl) bool {
|
||||
if (!self.implicit_ctx_enabled) return false;
|
||||
if (fd.call_conv == .c) return false;
|
||||
if (fd.abi == .c) return false;
|
||||
// `extern` imports and `export` defines are external C symbols —
|
||||
// C ABI, no sx context (Phase 2, gap iv).
|
||||
if (fd.extern_export != .none) return false;
|
||||
@@ -2256,11 +2257,11 @@ pub fn declareFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8)
|
||||
}
|
||||
|
||||
// `extern` declarations are external C symbols by definition — promote
|
||||
// them to callconv(.c) when the user didn't write it explicitly. This keeps
|
||||
// them to abi(.c) when the user didn't write it explicitly. This keeps
|
||||
// fn-ptr coercion type-safe: anything typed by name as `(args) -> ret` of an
|
||||
// `extern` decl can be assigned to / passed as a `callconv(.c)` fn-pointer
|
||||
// `extern` decl can be assigned to / passed as a `abi(.c)` fn-pointer
|
||||
// without a call-convention mismatch.
|
||||
const cc: Function.CallingConvention = if (fd.call_conv == .c or is_extern_decl or fd.extern_export == .export_) .c else .default;
|
||||
const cc: Function.CallingConvention = if (fd.abi == .c or is_extern_decl or fd.extern_export == .export_) .c else .default;
|
||||
|
||||
// Symbol-name override: `extern … "csym"` / `export … "csym"` (fd.extern_name).
|
||||
// Declare under the C name and map the sx name → C name so call sites resolve
|
||||
@@ -2296,9 +2297,29 @@ pub fn declareFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8)
|
||||
func.source_file = self.current_source_file;
|
||||
func.is_variadic = is_variadic;
|
||||
func.has_implicit_ctx = wants_ctx;
|
||||
if (weldedCompilerFn(self, fd, name)) func.compiler_welded = true;
|
||||
self.fn_decl_fids.put(fd, fid) catch {};
|
||||
}
|
||||
|
||||
/// A `fn abi(.zig) extern <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
|
||||
/// name (`ns.fn`), giving each a UNIQUE FuncId in the function table. Two
|
||||
/// modules each exporting a top-level `parse` otherwise collide in the
|
||||
@@ -2553,7 +2574,7 @@ pub fn lowerFunctionBodyInto(self: *Lowering, fd: *const ast.FnDecl, fid: FuncId
|
||||
func.is_extern = false; // promote from extern stub to real function
|
||||
// `export` defines force external linkage + C ABI (Phase 2, gaps i+ii).
|
||||
func.linkage = if (isExportedEntryName(name) or fd.extern_export == .export_) .external else .internal;
|
||||
if (fd.call_conv == .c or fd.extern_export == .export_) func.call_conv = .c;
|
||||
if (fd.abi == .c or fd.extern_export == .export_) func.call_conv = .c;
|
||||
// Set inst_counter to param count (params occupy refs 0..N-1). IR params
|
||||
// = AST params + 1 if the function carries `__sx_ctx` at slot 0.
|
||||
const ctx_slots: usize = if (func.has_implicit_ctx) 1 else 0;
|
||||
@@ -2589,7 +2610,7 @@ pub fn lowerFunctionBodyInto(self: *Lowering, fd: *const ast.FnDecl, fid: FuncId
|
||||
scope.put(p.name, .{ .ref = slot, .ty = pty, .is_alloca = true });
|
||||
}
|
||||
|
||||
// Inbound entry points + callconv(.c) sx functions: bind current_ctx_ref
|
||||
// Inbound entry points + abi(.c) sx functions: bind current_ctx_ref
|
||||
// to the static default before any user code runs.
|
||||
if (!wants_ctx and self.implicit_ctx_enabled) {
|
||||
if (self.program_index.global_names.get("__sx_default_context")) |dctx_gi| {
|
||||
@@ -2695,7 +2716,7 @@ pub fn lowerFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8, i
|
||||
}
|
||||
|
||||
// Set calling convention. `export` defines promote to C ABI (gap ii).
|
||||
if (fd.call_conv == .c or fd.extern_export == .export_) {
|
||||
if (fd.abi == .c or fd.extern_export == .export_) {
|
||||
self.builder.currentFunc().call_conv = .c;
|
||||
}
|
||||
|
||||
@@ -2729,7 +2750,7 @@ pub fn lowerFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8, i
|
||||
scope.put(p.name, .{ .ref = slot, .ty = pty, .is_alloca = true });
|
||||
}
|
||||
|
||||
// Inbound entry points + callconv(.c) sx functions: bind
|
||||
// Inbound entry points + abi(.c) sx functions: bind
|
||||
// current_ctx_ref to &__sx_default_context. See companion comment
|
||||
// in `lowerFunction` for the same case.
|
||||
if (!wants_ctx_lf and self.implicit_ctx_enabled) {
|
||||
|
||||
@@ -1894,7 +1894,7 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
||||
// Coercing a bare fn name to a fn-pointer
|
||||
// type — the call_conv must match. A
|
||||
// default-conv sx fn assigned to a
|
||||
// callconv(.c) slot (e.g. passed to
|
||||
// abi(.c) slot (e.g. passed to
|
||||
// pthread_create) would otherwise crash at
|
||||
// runtime when the C caller doesn't supply
|
||||
// the implicit __sx_ctx arg.
|
||||
@@ -1902,8 +1902,8 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
||||
const func_cc = self.module.functions.items[@intFromEnum(fid)].call_conv;
|
||||
if (func_cc != tt_info.function.call_conv) {
|
||||
if (self.diagnostics) |d| {
|
||||
const want_cc = if (tt_info.function.call_conv == .c) "callconv(.c)" else "default sx convention";
|
||||
const have_cc = if (func_cc == .c) "callconv(.c)" else "default sx convention";
|
||||
const want_cc = if (tt_info.function.call_conv == .c) "abi(.c)" else "default sx convention";
|
||||
const have_cc = if (func_cc == .c) "abi(.c)" else "default sx convention";
|
||||
d.addFmt(.err, node.span, "call-convention mismatch: '{s}' is declared with {s} but the target type expects {s}", .{ eff_fn_name, have_cc, want_cc });
|
||||
}
|
||||
break :blk self.emitPlaceholder(eff_fn_name);
|
||||
|
||||
@@ -1133,7 +1133,7 @@ pub fn synthesizeJniMainStub(self: *Lowering, fcd: *const ast.RuntimeClassDecl,
|
||||
// JNI native methods are C-callable entry points — install the
|
||||
// static default Context so `context.X` reads in the method body
|
||||
// resolve through `current_ctx_ref`. Mirror the same binding
|
||||
// `lowerFunction` does for callconv(.c) / isExportedEntryName.
|
||||
// `lowerFunction` does for abi(.c) / isExportedEntryName.
|
||||
const saved_ctx_ref_jni = self.current_ctx_ref;
|
||||
defer self.current_ctx_ref = saved_ctx_ref_jni;
|
||||
if (self.implicit_ctx_enabled) {
|
||||
|
||||
@@ -6,6 +6,7 @@ const mod_mod = @import("../module.zig");
|
||||
const type_bridge = @import("../type_bridge.zig");
|
||||
const program_index_mod = @import("../program_index.zig");
|
||||
const resolver_mod = @import("../resolver.zig");
|
||||
const compiler_lib = @import("../compiler_lib.zig");
|
||||
const StructTemplate = program_index_mod.StructTemplate;
|
||||
const TemplateParam = program_index_mod.TemplateParam;
|
||||
|
||||
@@ -673,7 +674,13 @@ pub fn registerStructDecl(self: *Lowering, sd: *const ast.StructDecl, source_fil
|
||||
// any forward-reference stub. Same-name structs in DIFFERENT sources get
|
||||
// distinct TypeIds instead of last-wins clobbering the first.
|
||||
const info: types.TypeInfo = .{ .@"struct" = .{ .name = name_id, .fields = fields.items } };
|
||||
_ = self.internNamedTypeDecl(decl_key, name_id, info, nominal_id);
|
||||
const struct_tid = self.internNamedTypeDecl(decl_key, name_id, info, nominal_id);
|
||||
|
||||
// Welded `struct abi(.zig) extern compiler { … }`: the sx declaration is a
|
||||
// header checked against the compiler's real Zig type — validate the layout
|
||||
// matches the binding registry (a mismatch is a build error). See
|
||||
// design/comptime-compiler-api.md.
|
||||
if (sd.abi == .zig) validateWeldedStruct(self, sd, struct_tid, fields.items);
|
||||
|
||||
// Store field defaults for struct literal lowering
|
||||
if (sd.field_defaults.len > 0) {
|
||||
@@ -709,6 +716,51 @@ pub fn registerStructDecl(self: *Lowering, sd: *const ast.StructDecl, source_fil
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate a welded `struct abi(.zig) extern <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) —
|
||||
/// 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
|
||||
|
||||
@@ -255,7 +255,7 @@ pub fn lowerObjcPropertySetter(self: *Lowering, obj_expr: *const ast.Node, field
|
||||
} }, .void);
|
||||
}
|
||||
|
||||
/// Get a FuncId for an external C-callconv function. If a function
|
||||
/// Get a FuncId for an external C-ABI function. If a function
|
||||
/// with this exported name already exists in the module (e.g.
|
||||
/// declared by stdlib `extern` decl), return it; otherwise
|
||||
/// declare it fresh with the given signature.
|
||||
@@ -290,7 +290,7 @@ pub fn ensureCRuntimeDecl(self: *Lowering, name: []const u8, param_tys: []const
|
||||
/// 2. Calls `object_getIvar(obj, ivar)` to get the `*<Cls>State`
|
||||
/// state pointer.
|
||||
/// 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`).
|
||||
///
|
||||
/// IMP name: `__<ClassName>_<methodName>_imp`. emit_llvm's
|
||||
|
||||
@@ -91,7 +91,7 @@ pub const Module = struct {
|
||||
pub const ObjcDefinedMethodEntry = struct {
|
||||
sel: []const u8, // mangled Obj-C selector (`add:and:`)
|
||||
encoding: []const u8, // Apple-runtime type encoding (`v@:ii`)
|
||||
imp_name: []const u8, // C-callconv trampoline symbol (`__Cls_method_imp`)
|
||||
imp_name: []const u8, // C-ABI trampoline symbol (`__Cls_method_imp`)
|
||||
is_class: bool = false, // true ⇒ register on the metaclass (M2.1 class methods)
|
||||
};
|
||||
|
||||
|
||||
@@ -224,9 +224,15 @@ pub const TypeResolver = struct {
|
||||
defer param_ids.deinit(table.alloc);
|
||||
for (ft.param_types) |pt| param_ids.append(table.alloc, inner.resolveInner(pt)) catch return .unresolved;
|
||||
const ret_ty = if (ft.return_type) |rt| inner.resolveInner(rt) else TypeId.void;
|
||||
const cc: types.TypeInfo.CallConv = switch (ft.call_conv) {
|
||||
const cc: types.TypeInfo.CallConv = switch (ft.abi) {
|
||||
.default => .default,
|
||||
.c => .c,
|
||||
// `.zig` (compiler-lib weld) and `.pure` (naked asm) are
|
||||
// decl-level ABIs with no function-pointer-type calling
|
||||
// convention of their own; the IR function-type CC models only
|
||||
// sx-default vs C. Neither occurs in a function-TYPE position in
|
||||
// current usage — treated as sx-default here.
|
||||
.zig, .pure => .default,
|
||||
};
|
||||
break :blk table.functionTypeCC(param_ids.items, ret_ty, cc);
|
||||
},
|
||||
|
||||
@@ -537,9 +537,9 @@ test "lex keywords" {
|
||||
}
|
||||
|
||||
test "lex linkage keywords" {
|
||||
// extern / export are keywords (FFI-linkage stream), lexed beside callconv.
|
||||
var lex = Lexer.init("callconv extern export");
|
||||
const expected = [_]Tag{ .kw_callconv, .kw_extern, .kw_export };
|
||||
// extern / export are keywords (FFI-linkage stream), lexed beside abi.
|
||||
var lex = Lexer.init("abi extern export");
|
||||
const expected = [_]Tag{ .kw_abi, .kw_extern, .kw_export };
|
||||
for (expected) |exp| {
|
||||
try std.testing.expectEqual(exp, lex.next().tag);
|
||||
}
|
||||
|
||||
@@ -1682,7 +1682,7 @@ pub const Server = struct {
|
||||
.kw_protocol,
|
||||
.kw_impl,
|
||||
.kw_inline,
|
||||
.kw_callconv,
|
||||
.kw_abi,
|
||||
.kw_extern,
|
||||
.kw_export,
|
||||
.kw_asm,
|
||||
|
||||
@@ -909,6 +909,11 @@ fn extractLibraries(allocator: std.mem.Allocator, root: *const sx.ast.Node) ![]c
|
||||
for (decls) |d| {
|
||||
switch (d.data) {
|
||||
.library_decl => |ld| {
|
||||
// The `compiler` library is the comptime-only internal
|
||||
// surface (welded types / host-call functions), not a
|
||||
// linkable dylib — never dlopen it. See
|
||||
// design/comptime-compiler-api.md.
|
||||
if (std.mem.eql(u8, ld.lib_name, sx.ir.compiler_lib.lib_name)) continue;
|
||||
if (s.contains(ld.lib_name)) continue;
|
||||
try s.put(ld.lib_name, {});
|
||||
try l.append(a, ld.lib_name);
|
||||
|
||||
@@ -78,3 +78,148 @@ test "parser: comptime type-metaprogramming surface parses" {
|
||||
try std.testing.expect(d.data.fn_decl.return_type != null);
|
||||
}
|
||||
}
|
||||
|
||||
// Lock: the `compiler`-library binding surface PARSES — `name :: #library "x";`
|
||||
// (already supported) plus the new postfix `abi(.zig)` annotation (in the slot
|
||||
// before `extern`) followed by the library handle, on a function declaration. The
|
||||
// AST must carry the binding: `abi == .zig`, `extern_export == .extern_`, and the
|
||||
// library handle in `extern_lib`. No semantics yet — this is the first testable
|
||||
// sub-step of Phase 1 (parse only).
|
||||
test "parser: abi(.zig) extern <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);
|
||||
}
|
||||
|
||||
@@ -598,12 +598,12 @@ pub const Parser = struct {
|
||||
// '->' present: function type
|
||||
self.advance(); // skip '->'
|
||||
const return_type = try self.parseTypeExpr();
|
||||
const call_conv = try self.parseOptionalCallConv();
|
||||
const abi = try self.parseOptionalAbi();
|
||||
return try self.createNode(start, .{ .function_type_expr = .{
|
||||
.param_types = try param_types.toOwnedSlice(self.allocator),
|
||||
.param_names = if (has_names) try param_names.toOwnedSlice(self.allocator) else null,
|
||||
.return_type = return_type,
|
||||
.call_conv = call_conv,
|
||||
.abi = abi,
|
||||
} });
|
||||
}
|
||||
// No '->': tuple type (even for single element). Keep field names
|
||||
@@ -959,6 +959,18 @@ pub const Parser = struct {
|
||||
self.advance();
|
||||
}
|
||||
|
||||
// Optional welded-binding annotation: `struct abi(.zig) extern <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) { ... }
|
||||
var type_params = std.ArrayList(ast.StructTypeParam).empty;
|
||||
if (self.current.tag == .l_paren) {
|
||||
@@ -1146,6 +1158,8 @@ pub const Parser = struct {
|
||||
.using_entries = try using_entries.toOwnedSlice(self.allocator),
|
||||
.methods = try methods.toOwnedSlice(self.allocator),
|
||||
.constants = try constants.toOwnedSlice(self.allocator),
|
||||
.abi = struct_abi,
|
||||
.extern_lib = struct_extern_lib,
|
||||
.is_raw = name_is_raw,
|
||||
} });
|
||||
}
|
||||
@@ -1937,8 +1951,11 @@ pub const Parser = struct {
|
||||
return_type = try self.parseTypeExpr();
|
||||
}
|
||||
|
||||
// Optional calling convention: callconv(.c)
|
||||
const call_conv = try self.parseOptionalCallConv();
|
||||
// Optional ABI / calling-convention annotation: `abi(.c)` / `abi(.zig)` /
|
||||
// `abi(.pure)`. Sits in the postfix slot BEFORE the `extern`/`export`
|
||||
// linkage keyword (it is part of the function declaration). `abi(.zig)`
|
||||
// marks a binding to the comptime `compiler` library.
|
||||
const abi = try self.parseOptionalAbi();
|
||||
|
||||
// Optional postfix linkage modifier: `extern` (import) / `export` (define).
|
||||
const extern_export = self.parseOptionalExternExport();
|
||||
@@ -2018,7 +2035,7 @@ pub const Parser = struct {
|
||||
.body = body,
|
||||
.type_params = type_params,
|
||||
.is_arrow = is_arrow,
|
||||
.call_conv = call_conv,
|
||||
.abi = abi,
|
||||
.extern_export = extern_export,
|
||||
.extern_lib = extern_lib,
|
||||
.extern_name = extern_name,
|
||||
@@ -3688,8 +3705,8 @@ pub const Parser = struct {
|
||||
return_type = try self.parseTypeExpr();
|
||||
}
|
||||
|
||||
// Optional calling convention: callconv(.c)
|
||||
const call_conv = try self.parseOptionalCallConv();
|
||||
// Optional ABI annotation: abi(.c) / abi(.zig) / abi(.pure)
|
||||
const abi = try self.parseOptionalAbi();
|
||||
|
||||
// A closure is its own function boundary: clear the cleanup-body flags
|
||||
// so control-flow exits inside the closure body (`return` from the
|
||||
@@ -3719,7 +3736,7 @@ pub const Parser = struct {
|
||||
.return_type = return_type,
|
||||
.body = body,
|
||||
.type_params = type_params,
|
||||
.call_conv = call_conv,
|
||||
.abi = abi,
|
||||
} });
|
||||
}
|
||||
|
||||
@@ -3745,8 +3762,8 @@ pub const Parser = struct {
|
||||
// builtin marker) is a function-type literal, not a function def.
|
||||
if (tag == .arrow) return self.hasFnBodyAfterArrow();
|
||||
// `kw_extern`/`kw_export`: a postfix linkage modifier (e.g. `f :: () extern;`
|
||||
// with no return type) marks a fn decl just like `callconv`.
|
||||
return tag == .l_brace or tag == .hash_builtin or tag == .hash_compiler or tag == .fat_arrow or tag == .kw_callconv or tag == .kw_extern or tag == .kw_export;
|
||||
// with no return type) marks a fn decl just like `abi(...)`.
|
||||
return tag == .l_brace or tag == .hash_builtin or tag == .hash_compiler or tag == .fat_arrow or tag == .kw_abi or tag == .kw_extern or tag == .kw_export;
|
||||
}
|
||||
|
||||
fn hasFnBodyAfterArrow(self: *Parser) bool {
|
||||
@@ -3773,9 +3790,9 @@ pub const Parser = struct {
|
||||
if (self.current.tag == .fat_arrow) return true;
|
||||
if (self.current.tag == .l_brace) return true;
|
||||
if (self.current.tag == .hash_builtin or self.current.tag == .hash_compiler) return true;
|
||||
if (self.current.tag == .kw_callconv) return true;
|
||||
if (self.current.tag == .kw_abi) return true;
|
||||
// Postfix linkage modifier after the return type: `-> R extern;` /
|
||||
// `-> R export { … }` (and `-> R callconv(.c) extern`). Marks a fn def.
|
||||
// `-> R export { … }` (and `-> R abi(.c) extern`). Marks a fn def.
|
||||
if (self.current.tag == .kw_extern or self.current.tag == .kw_export) return true;
|
||||
// Inside a `struct #compiler` block, a `(...) -> Ret;` ending
|
||||
// with `;` after the return type is a `#compiler` method
|
||||
@@ -3806,25 +3823,32 @@ pub const Parser = struct {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn parseOptionalCallConv(self: *Parser) anyerror!ast.CallingConvention {
|
||||
if (self.current.tag != .kw_callconv) return .default;
|
||||
/// Optional ABI / calling-convention annotation `abi(.c)` / `abi(.zig)` /
|
||||
/// `abi(.pure)` in the postfix slot before `extern`/`export`. `.default` when
|
||||
/// absent. Subsumes the old `callconv(...)` spelling.
|
||||
fn parseOptionalAbi(self: *Parser) anyerror!ast.ABI {
|
||||
if (self.current.tag != .kw_abi) return .default;
|
||||
self.advance();
|
||||
try self.expect(.l_paren);
|
||||
try self.expect(.dot);
|
||||
if (self.current.tag != .identifier)
|
||||
return self.fail("expected calling convention name after '.'");
|
||||
const cc_name = self.tokenSlice(self.current);
|
||||
const cc: ast.CallingConvention = if (std.mem.eql(u8, cc_name, "c")) .c else return self.fail("unknown calling convention");
|
||||
return self.fail("expected ABI name ('.c', '.zig', or '.pure') after '.'");
|
||||
const abi_name = self.tokenSlice(self.current);
|
||||
const abi: ast.ABI = if (std.mem.eql(u8, abi_name, "c"))
|
||||
.c
|
||||
else if (std.mem.eql(u8, abi_name, "zig"))
|
||||
.zig
|
||||
else if (std.mem.eql(u8, abi_name, "pure"))
|
||||
.pure
|
||||
else
|
||||
return self.fail("unknown ABI (expected '.c', '.zig', or '.pure')");
|
||||
self.advance();
|
||||
try self.expect(.r_paren);
|
||||
return cc;
|
||||
return abi;
|
||||
}
|
||||
|
||||
/// Postfix linkage modifier in the slot after `callconv(...)`:
|
||||
/// Postfix linkage modifier in the slot after `abi(...)`:
|
||||
/// `extern` (import) or `export` (define + expose), or `.none` if neither.
|
||||
/// Mirrors `parseOptionalCallConv`. Bare-keyword today; the optional
|
||||
/// `"csym"` symbol-name override lands in Phase 1.2/2.2. Defined here in
|
||||
/// Phase 0.1 but NOT yet called from any decl path (wired in Phase 1.0).
|
||||
fn parseOptionalExternExport(self: *Parser) ast.ExternExportModifier {
|
||||
switch (self.current.tag) {
|
||||
.kw_extern => {
|
||||
|
||||
14
src/sema.zig
14
src/sema.zig
@@ -830,7 +830,7 @@ pub const Analyzer = struct {
|
||||
switch (node.data) {
|
||||
.fn_decl => |fd| {
|
||||
const saved_cc = self.in_c_conv;
|
||||
self.in_c_conv = fd.call_conv == .c;
|
||||
self.in_c_conv = fd.abi == .c;
|
||||
try self.pushScope();
|
||||
try self.analyzeParams(fd.params);
|
||||
try self.analyzeNode(fd.body);
|
||||
@@ -852,7 +852,7 @@ pub const Analyzer = struct {
|
||||
if (mnode.data == .fn_decl) {
|
||||
const m = mnode.data.fn_decl;
|
||||
const saved_cc = self.in_c_conv;
|
||||
self.in_c_conv = m.call_conv == .c;
|
||||
self.in_c_conv = m.abi == .c;
|
||||
try self.pushScope();
|
||||
try self.analyzeParams(m.params);
|
||||
try self.analyzeNode(m.body);
|
||||
@@ -979,7 +979,7 @@ pub const Analyzer = struct {
|
||||
try self.diagnostics.append(self.allocator, .{
|
||||
.level = .warn,
|
||||
.span = span,
|
||||
.message = "`context` is unavailable in a `callconv(.c)` function — the C ABI has no implicit context parameter; pass what you need explicitly",
|
||||
.message = "`context` is unavailable in an `abi(.c)` function — the C ABI has no implicit context parameter; pass what you need explicitly",
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -1032,7 +1032,7 @@ pub const Analyzer = struct {
|
||||
});
|
||||
}
|
||||
const saved_cc = self.in_c_conv;
|
||||
self.in_c_conv = fd.call_conv == .c;
|
||||
self.in_c_conv = fd.abi == .c;
|
||||
try self.pushScope();
|
||||
try self.analyzeParams(fd.params);
|
||||
try self.analyzeNode(fd.body);
|
||||
@@ -2366,7 +2366,7 @@ test "sema: member references record fields, methods, and enum variants" {
|
||||
try std.testing.expect(red_use);
|
||||
}
|
||||
|
||||
test "sema: context in a callconv(.c) function reports a specific diagnostic" {
|
||||
test "sema: context in an abi(.c) function reports a specific diagnostic" {
|
||||
const parser_mod = @import("parser.zig");
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
@@ -2374,7 +2374,7 @@ test "sema: context in a callconv(.c) function reports a specific diagnostic" {
|
||||
|
||||
const source =
|
||||
"Context :: struct { allocator: i64; data: i64; }" ++
|
||||
"cb :: () -> i64 callconv(.c) { context; 0; }" ++
|
||||
"cb :: () -> i64 abi(.c) { context; 0; }" ++
|
||||
"ok :: () -> i64 { context; 0; }";
|
||||
var parser = parser_mod.Parser.init(alloc, source);
|
||||
const root = try parser.parse();
|
||||
@@ -2385,7 +2385,7 @@ test "sema: context in a callconv(.c) function reports a specific diagnostic" {
|
||||
var c_conv_diag = false;
|
||||
var undefined_diag = false;
|
||||
for (res.diagnostics) |d| {
|
||||
if (std.mem.indexOf(u8, d.message, "callconv(.c)") != null) c_conv_diag = true;
|
||||
if (std.mem.indexOf(u8, d.message, "abi(.c)") != null) c_conv_diag = true;
|
||||
if (std.mem.indexOf(u8, d.message, "undefined") != null) undefined_diag = true;
|
||||
}
|
||||
try std.testing.expect(c_conv_diag); // `cb` accesses context under the C ABI
|
||||
|
||||
@@ -42,7 +42,7 @@ pub const Tag = enum {
|
||||
kw_impl, // impl
|
||||
kw_Self, // Self (in protocol declarations)
|
||||
kw_inline, // inline (compile-time if/for/while)
|
||||
kw_callconv, // callconv (calling convention annotation)
|
||||
kw_abi, // abi (ABI / calling-convention annotation: abi(.c)/abi(.zig)/abi(.pure))
|
||||
kw_extern, // extern (import: external linkage, C ABI, no body)
|
||||
kw_export, // export (define + expose: external linkage, C ABI)
|
||||
kw_asm, // asm (inline assembly expression / global asm decl)
|
||||
@@ -281,7 +281,7 @@ pub const keywords = std.StaticStringMap(Tag).initComptime(.{
|
||||
.{ "impl", .kw_impl },
|
||||
.{ "Self", .kw_Self },
|
||||
.{ "inline", .kw_inline },
|
||||
.{ "callconv", .kw_callconv },
|
||||
.{ "abi", .kw_abi },
|
||||
.{ "extern", .kw_extern },
|
||||
.{ "export", .kw_export },
|
||||
// `asm` is a real keyword; `volatile` / `clobbers` stay OUT of this table
|
||||
|
||||
Reference in New Issue
Block a user