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,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);

View File

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

View File

@@ -0,0 +1,24 @@
// Comptime compiler API — a layout-welded struct binding.
//
// `Field :: struct abi(.zig) extern compiler { … }` binds the sx struct to the
// compiler's real internal Zig type (`StructInfo.Field`, two u32s) via the
// `compiler` library. The compiler validates the sx declaration against the
// welded type's layout at registration time (the sx side is a header checked
// against the implementation) — a faithful declaration validates clean and the
// struct is otherwise ordinary data. The `compiler` library is the comptime-only
// internal surface, so `#library "compiler"` is NOT dlopen'd.
//
// Phase 1 (foundation): the weld is layout-validated; field offsets coincide with
// the natural layout for `Field` (two u32s). Welded host-call functions land in a
// later phase.
#import "modules/std.sx";
compiler :: #library "compiler";
Field :: struct abi(.zig) extern compiler { name: u32; ty: u32; }
main :: () {
f := Field.{ name = 7, ty = 3 };
print("name={} ty={}\n", f.name, f.ty);
}

View File

@@ -0,0 +1,24 @@
// Comptime compiler API — welded compiler FUNCTIONS over the host-call bridge.
//
// `intern` / `text_of` are bound to the `compiler` library via
// `abi(.zig) extern compiler`. They have no real symbol — under the comptime
// interpreter the call dispatches to the compiler's registered Zig handler
// (the string pool), never dlsym. Comptime-only: here they run inside `#run`,
// folding to a plain string constant the runtime `main` prints.
//
// Round-trip: `text_of(intern(s)) == s` — interning a string yields a handle,
// and resolving the handle gives the text back.
#import "modules/std.sx";
compiler :: #library "compiler";
StringId :: u32;
intern :: (s: string) -> StringId abi(.zig) extern compiler;
text_of :: (id: StringId) -> string abi(.zig) extern compiler;
greeting :: #run text_of(intern("hello, compiler"));
main :: () {
print("{}\n", greeting);
}

View File

@@ -1,4 +1,4 @@
// Passing a default-conv sx function as a callconv(.c) fn-pointer
// Passing a default-conv sx function as a abi(.c) fn-pointer
// silently mismatches ABIs — historically that meant the C-side caller
// 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;
}

View File

@@ -0,0 +1,17 @@
// Diagnostic: a layout-welded struct whose sx declaration does NOT faithfully
// mirror the compiler's real Zig type is a build error — the sx side is a header
// checked against the implementation, not a free reinterpretation.
//
// `Field` is two u32s (`name`, `ty`) in the compiler library; declaring it with a
// single field must be rejected at registration with a clear field-count message.
#import "modules/std.sx";
compiler :: #library "compiler";
Field :: struct abi(.zig) extern compiler { name: u32; }
main :: () {
f := Field.{ name = 1 };
print("{}\n", f.name);
}

View File

@@ -0,0 +1,11 @@
// Diagnostic: a `fn abi(.zig) extern compiler` whose name is NOT on the compiler
// library's function-export list is a build error — the export list is the
// safety boundary, so an unbound name can't silently fall through to dlsym.
#import "modules/std.sx";
compiler :: #library "compiler";
not_a_real_compiler_fn :: (x: i64) -> i64 abi(.zig) extern compiler;
main :: () { print("unreached\n"); }

View File

@@ -0,0 +1,17 @@
// Diagnostic: a welded `compiler`-library function is comptime-only — it has no
// runtime symbol (the comptime interpreter dispatches it to a Zig handler).
// Calling one from runtime code is a build error with a clear message, NOT an
// undefined-symbol link failure. (A comptime use — inside `#run` or a `::` —
// is fine; see examples/0626.)
#import "modules/std.sx";
compiler :: #library "compiler";
StringId :: u32;
intern :: (s: string) -> StringId abi(.zig) extern compiler;
main :: () {
id := intern("called at runtime");
print("{}\n", id);
}

View File

@@ -1,16 +1,16 @@
// `callconv(.c)` on function pointers passed to extern callbacks — ensures
// `abi(.c)` on function pointers passed to extern callbacks — ensures
// the function uses C ABI so it can be safely invoked from `extern`
// 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);
}

View File

@@ -1,4 +1,4 @@
// `callconv(.c)` callbacks accessing struct methods on global pointers —
// `abi(.c)` callbacks accessing struct methods on global pointers —
// regression coverage for prior data-corruption when the callback dispatches
// 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();

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
// Pure-sx fn-pointer cast: a function-pointer typed without `callconv(.c)`
// Pure-sx fn-pointer cast: a function-pointer typed without `abi(.c)`
// keeps the default (sx) calling convention. Passing a >16-byte aggregate
// 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";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
// PLAN-HTTPZ C2: an sx `callconv(.c)` function passes by name as a
// PLAN-HTTPZ C2: an sx `abi(.c)` function passes by name as a
// typed C function pointer — libc qsort drives an sx comparator.
#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; }

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

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

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

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

View File

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

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,5 @@
error: welded type 'Field' has 2 field(s) in the compiler library but the declaration has 1
--> examples/1183-diagnostics-weld-struct-field-count.sx:12:51
|
12 | Field :: struct abi(.zig) extern compiler { name: u32; }
| ^^^

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,5 @@
error: 'not_a_real_compiler_fn' is not a function exported by the 'compiler' library
--> examples/1184-diagnostics-weld-fn-unexported.sx:9:1
|
9 | not_a_real_compiler_fn :: (x: i64) -> i64 abi(.zig) extern compiler;
| ^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1 @@
error: 'intern' is a comptime-only compiler-library function — it cannot be called at runtime (use it inside #run or a comptime '::')

View File

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