std: restructure step 3 — ffi/ moves, build.sx, math dir spelling, fixtures
- objc.sx, objc_block.sx (from std/) + sdl3/opengl/raylib/stb/stb_truetype/ wasm vendor bindings (from modules/ root) -> modules/ffi/ - std/uikit.sx deleted: platform/uikit.sx already declares UIApplicationMain and imports objc; '#framework "UIKit"' cannot live in a file imported on macOS targets (unconditional link directive, UIKit is iOS-only), so the three iOS-only examples carry the 3-line glue inline. 1607/1608/1616 also un-rotted (dead ns_string -> 'xx "..."' Into conversions, callconv(.c) msgSend fn-ptrs) — all three build for ios-sim/ios again. - math/math.sx -> math/scalar.sx; one spelling '#import "modules/math"' everywhere (4 pinned IR snapshots regenerated: dir import adds Vec2/Mat4 to the type tables). - compiler.sx -> build.sx (imports, CLAUDE.md bundling table, specs.md). - testpkg/ + test_c.sx -> tests/fixtures/ (resolve CWD-relative from repo root, same as vendors/). - library-internal imports use full modules/... paths (std.sx tail, platform/bundle.sx, fixtures).
This commit is contained in:
@@ -30,7 +30,7 @@
|
||||
// =====================================================================
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "modules/compiler.sx";
|
||||
#import "modules/build.sx";
|
||||
proc :: #import "modules/std/process.sx";
|
||||
|
||||
libc :: #library "c";
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
// Obj-C runtime FFI primitives.
|
||||
//
|
||||
// objc_msgSend has the standard ARM64 calling convention (no varargs path).
|
||||
// Each call site must invoke through a function pointer of the *exact*
|
||||
// argument and return shape. The idiom:
|
||||
//
|
||||
// msg_fn : (recv: *void, sel: *void, arg: [*]u8) -> *void = xx objc_msgSend;
|
||||
// result := msg_fn(receiver, selector, c_str);
|
||||
|
||||
// ─── Obj-C primitive type aliases ───────────────────────────────────────
|
||||
// Named stand-ins for the three opaque Obj-C runtime types. They all
|
||||
// resolve to `*void` at the LLVM layer (no runtime cost) but improve
|
||||
// readability in foreign-class declarations and call sites.
|
||||
//
|
||||
// id — any Obj-C instance pointer
|
||||
// Class — a class object pointer
|
||||
// SEL — a registered selector
|
||||
//
|
||||
// `Class(T)` parameterization (phantom-typed, with `#extends`-aware
|
||||
// covariance) is a follow-up — needs compiler-level type-check support.
|
||||
// For now, `Class` alone is the only form; assignments are not checked
|
||||
// against the referent's class hierarchy.
|
||||
id :: *void;
|
||||
Class :: *void;
|
||||
SEL :: *void;
|
||||
|
||||
// Apple's `BOOL` is a signed char (NOT sx's built-in `bool`, which is
|
||||
// LLVM `i1`). Obj-C method signatures that take or return `BOOL` cross
|
||||
// the FFI boundary as `s8`.
|
||||
BOOL :: s8;
|
||||
|
||||
// On macOS libobjc is auto-loaded by libSystem; on iOS it isn't, so we
|
||||
// link it explicitly. Foundation registers NSString etc. with the runtime,
|
||||
// also auto-loaded on macOS and required as an explicit framework on iOS.
|
||||
objc :: #library "objc";
|
||||
#framework "Foundation";
|
||||
|
||||
objc_getClass :: (name: [*]u8) -> *void #foreign objc;
|
||||
objc_lookUpClass :: (name: [*]u8) -> *void #foreign objc;
|
||||
sel_registerName :: (name: [*]u8) -> *void #foreign objc;
|
||||
class_createInstance :: (cls: *void, extra: usize) -> *void #foreign objc;
|
||||
object_getClass :: (obj: *void) -> *void #foreign objc;
|
||||
object_getIvar :: (obj: *void, ivar: *void) -> *void #foreign objc;
|
||||
object_setIvar :: (obj: *void, ivar: *void, val: *void) #foreign objc;
|
||||
|
||||
// Declared with the simplest non-variadic shape. Cast per call site.
|
||||
objc_msgSend :: (recv: *void, sel: *void) -> *void #foreign objc;
|
||||
|
||||
// ─── Dynamic class registration ─────────────────────────────────────────
|
||||
// Define a new Obj-C class at runtime: allocate the pair, attach methods +
|
||||
// protocols, then finalize with `objc_registerClassPair`. The class is then
|
||||
// usable via `class_createInstance` and Obj-C dispatch.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Method type encoding strings follow Apple's runtime DSL:
|
||||
// v = void c = char/BOOL i = int l = long f = float d = double
|
||||
// @ = id (object) : = SEL # = Class
|
||||
// Return type comes first, then receiver (`@`), then `_cmd` (`:`), then args.
|
||||
// Examples:
|
||||
// "v@:" -> void method(id, SEL)
|
||||
// "c@:" -> BOOL method(id, SEL)
|
||||
// "@@:@" -> id method(id, SEL, id)
|
||||
// "B@:@@" -> BOOL method(id, SEL, id, id)
|
||||
objc_allocateClassPair :: (super: *void, name: [*]u8, extra: usize) -> *void #foreign objc;
|
||||
class_addMethod :: (cls: *void, sel: *void, imp: *void, types: [*]u8) -> bool #foreign objc;
|
||||
class_addProtocol :: (cls: *void, proto: *void) -> bool #foreign objc;
|
||||
objc_getProtocol :: (name: [*]u8) -> *void #foreign objc;
|
||||
objc_registerClassPair :: (cls: *void) -> void #foreign objc;
|
||||
|
||||
// Foundation C-API helpers (Foundation is already linked above via #framework).
|
||||
// NSLog takes an NSString format; the variadic tail is not exposed here.
|
||||
NSLog :: (fmt: *NSString) #foreign;
|
||||
|
||||
// ─── NSObject (Phase 4 / M4.A) ───────────────────────────────────────────
|
||||
// Root of every Obj-C class hierarchy. Apple's runtime supplies the IMPs;
|
||||
// sx declares the method surface so user code can write
|
||||
// `defer view.release();` and `view.retain()` directly instead of going
|
||||
// through `objc_msgSend` casts. M2.3's `#extends`-aware dispatch finds
|
||||
// these methods automatically once a class roots its `#extends` chain at
|
||||
// NSObject (foreign classes in uikit.sx etc. add `#extends NSObject;` to
|
||||
// inherit the surface).
|
||||
//
|
||||
// `release` is NOT a reserved keyword in sx — modern clang ARC bans
|
||||
// user-source calls to it (the ARC compiler emits them automatically), but
|
||||
// sx isn't under clang ARC. Calling `view.release()` here is equivalent to
|
||||
// pre-ARC Obj-C source code: dispatches through the runtime, decrements the
|
||||
// refcount, fires `-dealloc` at zero.
|
||||
NSObject :: #foreign #objc_class("NSObject") {
|
||||
alloc :: () -> *NSObject;
|
||||
init :: (self: *Self) -> *NSObject;
|
||||
new :: () -> *NSObject; // shorthand for [[Cls alloc] init]
|
||||
retain :: (self: *Self) -> *Self;
|
||||
release :: (self: *Self);
|
||||
autorelease :: (self: *Self) -> *Self;
|
||||
class :: () -> *void; // metaclass query — `Cls.class()`
|
||||
description :: (self: *Self) -> *void; // returns *NSString
|
||||
hash :: (self: *Self) -> u64;
|
||||
isEqual :: (self: *Self, other: *void) -> BOOL;
|
||||
isKindOfClass :: (self: *Self, cls: *void) -> BOOL;
|
||||
respondsToSelector :: (self: *Self, sel: *void) -> BOOL;
|
||||
}
|
||||
|
||||
// ─── NSString ────────────────────────────────────────────────────────────
|
||||
// Foundation's immutable string. `UTF8String` views the bytes as a C string
|
||||
// whose lifetime is tied to the NSString (don't free it). The `Into` impl
|
||||
// lets a string literal flow into any `*NSString` slot via `xx`, e.g.
|
||||
// `dict.objectForKey(xx "SomeKey")`.
|
||||
NSString :: #foreign #objc_class("NSString") {
|
||||
#extends NSObject;
|
||||
UTF8String :: (self: *Self) -> [*]u8;
|
||||
}
|
||||
|
||||
// Wraps the bytes in an autoreleased NSString via `+stringWithUTF8String:`.
|
||||
// `self.ptr` must be NUL-terminated — string literals are; an arbitrary
|
||||
// sliced/built `string` may not be.
|
||||
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;
|
||||
return xx msg(cls, sel, self.ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Autoreleasepool (M4.A) ──────────────────────────────────────────────
|
||||
// Foundation factory methods (`NSString.stringWithUTF8String:`,
|
||||
// `[NSArray array]`, ...) return autoreleased objects — valid until the
|
||||
// current pool drains. Wrap such code in `autoreleasepool(() => { ... })`
|
||||
// so the pool drains deterministically at block end.
|
||||
//
|
||||
// Stdlib helper, not a language keyword. The closure call adds a frame —
|
||||
// for hot loops, inline the push/defer-pop pattern manually.
|
||||
|
||||
objc_autoreleasePoolPush :: () -> *void #foreign objc;
|
||||
objc_autoreleasePoolPop :: (pool: *void) #foreign objc;
|
||||
|
||||
autoreleasepool :: (body: Closure()) {
|
||||
pool := objc_autoreleasePoolPush();
|
||||
defer objc_autoreleasePoolPop(pool);
|
||||
body();
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
// Obj-C blocks bridged to sx closures.
|
||||
//
|
||||
// Apple's block ABI (clang's "Block Implementation Specification"): a block
|
||||
// pointer is a struct whose first five fields are { isa, flags, reserved,
|
||||
// invoke, descriptor } followed by per-block captured state. When an API
|
||||
// like `[UIView animateWithDuration:animations:]` receives a block, the
|
||||
// runtime reads `invoke` and calls it with the block pointer as the first
|
||||
// argument. UIKit / Foundation callers always `_Block_copy` synchronously
|
||||
// before returning, so a stack-allocated block is safe to pass directly.
|
||||
//
|
||||
// We layer the sx closure onto Apple's layout by appending two pointer
|
||||
// fields to the standard 32-byte header: `sx_env` (the closure's captured
|
||||
// environment pointer) and `sx_fn` (the closure trampoline). The per-
|
||||
// signature `__block_invoke_*` C-ABI fn knows the offsets and calls
|
||||
// through to `sx_fn(sx_env, args...)`.
|
||||
//
|
||||
// ── Lifetime contract ───────────────────────────────────────────────────
|
||||
// `xx <closure> : *Block` returns a pointer into the surrounding sx
|
||||
// function's stack frame. Same rule as `&local_var`: pass it directly to
|
||||
// a callee that consumes it immediately or `_Block_copy`s internally
|
||||
// (UIKit/Foundation always do). Don't store the pointer to use after the
|
||||
// caller returns. If you need that, ship a `Block_copy`-backed sibling
|
||||
// API and use it instead.
|
||||
|
||||
// `build_block_convert` (below) is a comptime metaprogram that emits sx source
|
||||
// with `concat` / `int_to_string`; those live in std.sx. A metaprogram body
|
||||
// resolves its bare names in its OWN module (issue 0106), so this module must
|
||||
// import std.sx itself rather than relying on the call site's visibility.
|
||||
#import "modules/std.sx";
|
||||
|
||||
// Standard 32-byte block header plus two trailing slots for the sx closure
|
||||
// it wraps. Total = 48 bytes.
|
||||
Block :: struct {
|
||||
isa: *void;
|
||||
flags: s32;
|
||||
reserved: s32;
|
||||
invoke: *void;
|
||||
descriptor: *void;
|
||||
sx_env: *void;
|
||||
sx_fn: *void;
|
||||
}
|
||||
|
||||
// Per-block-shape metadata. The runtime reads `size` when copying the
|
||||
// block to the heap, so it must equal the actual instance size.
|
||||
BlockDescriptor :: struct {
|
||||
reserved: u64;
|
||||
size: u64;
|
||||
}
|
||||
|
||||
// libSystem isa pointer for stack-allocated blocks. Resolved at link time
|
||||
// (auto-linked on every Apple target via libSystem).
|
||||
_NSConcreteStackBlock : *void #foreign;
|
||||
|
||||
// Shared descriptor for the 48-byte sx-block layout. All Into impls below
|
||||
// point their `descriptor` field at this.
|
||||
__sx_block_descriptor : BlockDescriptor = .{
|
||||
reserved = 0,
|
||||
size = 48,
|
||||
};
|
||||
|
||||
// Single generic impl covers every closure shape. The compiler
|
||||
// monomorphises this body per call shape; inside each mono,
|
||||
// `$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
|
||||
// `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 {
|
||||
convert :: (self: Closure(..$args) -> $R) -> Block {
|
||||
#insert build_block_convert($args, $R);
|
||||
}
|
||||
}
|
||||
|
||||
// Comptime builder for the generic `Into(Block) for Closure(..$args)
|
||||
// -> $R` impl body. Receives the closure's per-position pack types
|
||||
// (`args`, a comptime `[]Type`) and the closure's return type
|
||||
// (`$ret`), emits source that:
|
||||
//
|
||||
// 1. Declares a nested `__invoke` `callconv(.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`,
|
||||
// prepends `block_self.sx_env`, and tail-calls the sx closure.
|
||||
// 2. Returns a stack Block literal whose `invoke` slot points at
|
||||
// the nested trampoline via `xx @__invoke`.
|
||||
//
|
||||
// The host impl wraps the emitted source via `#insert
|
||||
// build_block_convert($args, $R);`. Per-call-shape monomorphisation
|
||||
// of the impl body re-runs this builder with the concrete types
|
||||
// bound, so each closure shape gets its own dedicated trampoline.
|
||||
//
|
||||
// Void return type emits `typed_fn(...)`; non-void emits
|
||||
// `return typed_fn(...)`.
|
||||
build_block_convert :: (args: []Type, $ret: Type) -> string {
|
||||
ret_name := type_name(ret);
|
||||
code := "__invoke :: (block_self: *Block";
|
||||
i : s64 = 0;
|
||||
while i < args.len {
|
||||
code = concat(code, ", arg");
|
||||
code = concat(code, int_to_string(i));
|
||||
code = concat(code, ": ");
|
||||
code = concat(code, type_name(args[i]));
|
||||
i = i + 1;
|
||||
}
|
||||
code = concat(code, ") -> ");
|
||||
code = concat(code, ret_name);
|
||||
code = concat(code, " callconv(.c) { typed_fn : (*void");
|
||||
i = 0;
|
||||
while i < args.len {
|
||||
code = concat(code, ", ");
|
||||
code = concat(code, type_name(args[i]));
|
||||
i = i + 1;
|
||||
}
|
||||
code = concat(code, ") -> ");
|
||||
code = concat(code, ret_name);
|
||||
code = concat(code, " = xx block_self.sx_fn; ");
|
||||
if ret_name == "void" {
|
||||
code = concat(code, "typed_fn(block_self.sx_env");
|
||||
} else {
|
||||
code = concat(code, "return typed_fn(block_self.sx_env");
|
||||
}
|
||||
i = 0;
|
||||
while i < args.len {
|
||||
code = concat(code, ", arg");
|
||||
code = concat(code, int_to_string(i));
|
||||
i = i + 1;
|
||||
}
|
||||
code = concat(code, "); } ");
|
||||
code = concat(code, "return .{ ");
|
||||
code = concat(code, "isa = @_NSConcreteStackBlock, ");
|
||||
code = concat(code, "flags = 0, ");
|
||||
code = concat(code, "reserved = 0, ");
|
||||
code = concat(code, "invoke = xx @__invoke, ");
|
||||
code = concat(code, "descriptor = xx @__sx_block_descriptor, ");
|
||||
code = concat(code, "sx_env = self.env, ");
|
||||
code = concat(code, "sx_fn = self.fn_ptr, ");
|
||||
code = concat(code, "};");
|
||||
return code;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// UIKit framework bindings — iOS only.
|
||||
//
|
||||
// Consumers `#import "modules/std/uikit.sx";` and inherit the
|
||||
// `#framework "UIKit"` link directive plus any C-API declarations exposed
|
||||
// here. Obj-C class/method dispatch goes through `modules/std/objc.sx`
|
||||
// which we re-import so users only need this one file.
|
||||
|
||||
#import "objc.sx";
|
||||
|
||||
#framework "UIKit";
|
||||
|
||||
// int UIApplicationMain(int argc, char *_Nullable argv[_Nonnull],
|
||||
// NSString *_Nullable principalClassName,
|
||||
// NSString *_Nullable delegateClassName);
|
||||
//
|
||||
// Blocks. Drives the iOS run loop. Normally never returns.
|
||||
UIApplicationMain :: (argc: s32, argv: *void, principal_class: *void, delegate_class: *void) -> s32 #foreign;
|
||||
Reference in New Issue
Block a user