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

Introduce the welded comptime `compiler` library (`#library "compiler"` +
`abi(.zig) extern compiler`), per design/comptime-compiler-api.md, and unify
`callconv(...)` into the new `abi(...)` annotation.

abi(...) replaces callconv(...):
- New ABI enum { default, c, zig, pure }; `abi(.c|.zig|.pure)` parses in the
  postfix slot before extern/export (and standalone). `kw_callconv` -> `kw_abi`.
- Migrated 52 sx files, the call-convention-mismatch diagnostic, and docs
  (readme/specs) from `callconv(.c)` to `abi(.c)`.

Phase 1 — welded compiler library (parse -> registry -> validation -> bridge):
- `abi(.zig) extern compiler` parses on fn decls (carries abi/extern_lib) and
  struct decls (StructDecl.abi/extern_lib).
- `#library "compiler"` is the comptime-only internal surface — never dlopen'd.
- src/ir/compiler_lib.zig: the binding registry (the safety boundary). `Field`
  welded to StructInfo.Field with layout baked from the real Zig type
  (@offsetOf/@sizeOf); `findType`/`findFn`. Welded structs are layout-validated
  at registration (field set + total size) as a header checked against the impl.
- Host-call bridge: a `fn abi(.zig) extern compiler` dispatches under the
  comptime interp to its registered Zig handler (intern/text_of round-trip),
  never dlsym. IR Function.compiler_welded; validated in declareFunction.
- Comptime-only enforcement: a runtime call to a welded fn is a clean
  build-gating error (emitCall), not an undefined-symbol link failure.

Phase 2.1 — byte-layout weld foundation:
- Decision: full byte-layout weld (sx struct laid out byte-identically to the
  bound Zig type). Registered StructInfo (first non-natural / Zig-reordered
  layout). `computeWeldPlan` — pure offset-ordered element plan + padding +
  sx-field->LLVM-element remap; unit-tested. Emit/interp wiring is the next
  sub-step (2.2+, see current/CHECKPOINT-COMPILER-API.md).

Examples: 0625/0626 (welded struct + fn round-trip), 1183/1184/1185
(layout-mismatch, unexported-fn, runtime-call diagnostics).
This commit is contained in:
agra
2026-06-17 13:31:11 +03:00
parent 3a9b508502
commit cd5b958d19
100 changed files with 1490 additions and 298 deletions

View File

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