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

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