Commit Graph

24 Commits

Author SHA1 Message Date
agra
33a6f5c650 wip(E4): partial source-pin + non-transitive flip [stdlib E4 attempt-1 WIP checkpoint]
Incomplete WIP from a worker killed at the 55-min wall (large blast radius:
core source-pin + ~8 example migrations + ~10 library module migrations).
Committed so the resumed session continues on a clean tree. May not build.
2026-06-08 11:12:08 +03:00
agra
0fc7a72cbc feat(lang): std.cli exit-code + --json contract helpers [F3.3]
Foundation milestone close — the minimal exit-code / --json contract
`dist` relies on, in pure sx (no compiler change).

- EX_OK (0) / EX_USAGE (64, sysexits.h) / EX_UNAVAILABLE (70) named
  constants in std.cli.
- exit_ok() / exit_usage() terminators routing through the canonical
  process.exit(code: u8) — removes the hand-rolled cli_bail_exit `_exit`
  binding; the unsupported-platform path now uses proc.exit(EX_UNAVAILABLE).
- --json read is parsed.json (already parsed by F3.2); documented as the
  detection point with a stdout-pure / stderr-human convention.
- examples/0718-modules-cli-exit-json.sx exercises the contract: json true
  with --json / false without, EX_USAGE == 64, and a usage path that exits
  64 via exit_usage() (expected .exit = 64).
- readme.md gains a std.cli command-line-interface subsection.
2026-06-05 01:01:25 +03:00
agra
17b437ecfb F3.2: std.cli minimal subcommand + flag parser over explicit []string
Extend std/cli.sx with a zero-heap argument parser that the caller drives
over a logical argv ([]string), separate from the F3.1 os_args accessor.

Grammar: <group> <command> [--flag VALUE | --bool]... [--json] [-- rest...]
  - (group, command) dispatched against a caller-provided Command table;
    no match -> error.UnknownCommand.
  - value-taking vs boolean flags fixed by each command's FlagSpec list;
    --json is a reserved global boolean surfaced as parsed.json.
  - `--` or the first bare operand ends flag parsing; the remainder is
    parsed.rest (operand views).

Heap discipline (heap-discipline.md): zero heap, zero copy. group/command/
flag values/rest are all VIEWS into args. Parsed is a by-value stack struct;
flag presence/values live in a fixed [16]FlagValue inline array indexed by
spec position (no per-flag allocation, no context.allocator). The flag-spec
list and command table are caller storage passed as views.

Failure surfacing (no silent skip): unknown command, unknown flag, a
value-flag missing its value, and an absent required flag each raise a
specific CliError variant; a caller-owned Diag records the offending token
(index + view) before each raise, since error tags carry no data.

examples/0717 drives the parser over explicit []string vectors: a valid
group/command/--flag/--bool/--json case (asserting parsed values + that
values are views into argv), subcommand dispatch, `--`/bare-operand
separators, and the five failure variants each asserted via destructure +
Diag. zig build && zig build test && run_examples.sh green (385 passed).
2026-06-04 06:13:09 +03:00
agra
e7f5bd7aaa F3.1: std.cli os_args — real OS argv accessor via #foreign _NSGetArgv (examples/0716)
Add library/modules/std/cli.sx: a pure-sx command-line argument accessor
backed by the macOS C runtime (_NSGetArgv/_NSGetArgc), no compiler change.

  os_argc() -> s64
  os_args(buf: []string) -> []string

Zero heap, zero per-arg allocation: os_args fills a caller-provided buffer
(stack array) with string VIEWS over the process's own argv block, which
lives for the whole process. The returned slice header is a by-value stack
return; nothing touches context.allocator.

Documents the `sx run` reality: under `sx run <prog.sx> ...` the process
argv is the interpreter's argv (sx, run, prog.sx, ...), not a program's
logical args. This accessor reports the real process argv truthfully;
mapping to logical args is a later consumer concern (distribution P3.1).

Non-macOS platforms bail loudly (message + _exit) rather than returning a
silent empty.

examples/0716-modules-cli-argv.sx asserts only deterministic structural
invariants (argc >= 1, argv[0] non-empty, os_argc() == filled length).
2026-06-04 03:21:41 +03:00
agra
2871342c0a F2.2: reject raw control bytes (U+0000..U+001F) in JSON strings
parse_string scanned for `"` and `\` but accepted every other byte,
including raw control characters. RFC 8259 §7 requires those bytes to be
escaped inside a string; an unescaped one is invalid JSON and must surface
a parse error, not be silently accepted.

Add `BadControlChar` to JsonParseError and reject any unescaped byte < 0x20
in the string body scan (which gates the decode path too, so escaped forms
like \t/\n/	 still decode correctly; 0x20 and 0x7F are not over-rejected).

Regression test in examples/0714: raw 0x09/0x0A/0x00 each raise
BadControlChar via `?`/`!`; a positive case proves the escaped forms still
decode to the right bytes. All prior assertions kept.
2026-06-04 02:32:32 +03:00
agra
88be541778 F2.2: std/json reader — explicit-alloc parse with error surfacing
Add the JSON reader (parser) to library/modules/std/json.sx, the inverse
of the F2.1 writer over the same value model: insertion-ordered objects,
arrays, strings (full unescaping incl. \uXXXX + surrogate pairs), s64
integers, bool, null.

Heap discipline (binding): exactly two allocation kinds, both through the
EXPLICIT `alloc` parameter, never the implicit context allocator —
composite backing stores (Array/Object.items via add/put) and decoded
escaped-string buffers (bounded by the raw span). Un-escaped string
values are zero-copy VIEWS into the input buffer (valid only while it
lives); scalars carry no heap.

Failure surfacing (hard contract): malformed input raises a meaningful
JsonParseError variant (UnexpectedToken / UnexpectedEnd / BadEscape /
BadNumber / TrailingGarbage) on the error channel, never a bogus value.
Trailing non-whitespace is TrailingGarbage; fractions/exponents,
out-of-s64 magnitudes, and leading zeros are BadNumber. Number
accumulation runs in negative space so s64 MIN parses exactly.

examples/0714-modules-json-reader.sx asserts the parsed structure
(insertion order, every kind), proves the view-vs-decoded heap split by
pointer containment, round-trips back through the writer byte-for-byte,
decodes a surrogate-pair into 4 UTF-8 bytes, and checks every malformed
variant.

Filed issues/0078: a string `==` (or any sub-CFG operand) used in a
short-circuit `and`/`or` emits invalid LLVM IR (stale PHI predecessor),
hit while writing the example's assertions and worked around there by not
combining comparisons with `and`/`or`. src/ untouched.
2026-06-04 01:41:33 +03:00
agra
4552ed61f6 std/json: value model + zero-alloc writer with stable key order
Add library/modules/std/json.sx — the JSON value model and writer
(reader lands in a later step).

Value model: a tagged union over null/bool/integer(s64)/string/array/
object. Objects are an ORDERED list of (key,value) pairs preserving
INSERTION ORDER (no hash map, never sorted/deduped). Integers only — no
fraction/exponent this milestone.

Heap discipline:
  - Scalars carry no heap; string values are VIEWS into caller memory
    (never copied into the node).
  - Composite nodes (Array/Object) own growable child storage, allocated
    through an EXPLICIT allocator parameter on the builder methods
    (arr.add(v, alloc) / obj.put(key, val, alloc), mirroring List.append)
    — never the implicit context allocator.
  - The writer adds ZERO output allocations: it emits into a caller-
    provided Sink, either a fixed []u8 buffer (overflow raises, never
    truncates) or streaming straight to an fs.File through a small caller
    staging buffer (no whole-document string; peak memory O(staging)).
    Integer digits format in a stack [20]u8; s64 MIN is handled by
    formatting in negative space. Sink/IO/overflow surface on the !
    error channel.

examples/0713-modules-json-writer.sx builds a nested object + array +
string with every escape kind + negative int + bool + null, then asserts
the EXACT bytes (insertion order, escaping) from both the buffer sink and
the file-streaming sink, plus the overflow-raises path.
2026-06-04 00:47:30 +03:00
agra
f9bc593bb8 F1.2: std.hash zero-heap [64]u8 hex API + chunked file + pinned vectors
Make the SHA-256 digest path allocation-free (foundation heap-discipline):

- final() and sha256_hex() now return the 64-char lowercase hex digest as
  a [64]u8 by value on the stack; the cstring(64) heap allocation is gone.
- sha256_file() streams the file in fixed 64KB stack chunks via open_file/
  File.read/File.close (defer-closed on every path) instead of slurping it
  with read_file; peak memory is O(chunk), not O(filesize).

Tests (compare via a zero-copy string view over the [64]u8):
- 0710 updated to the by-value API (output unchanged).
- 0711 known-answer vectors: "", "abc", NIST-56/112, padding boundaries
  {0,55,56,57,63,64,65,119,120}, and 1000 / 1,000,000 'a' repeats, each
  pinned to its published digest (cross-checked with shasum -a 256).
- 0712 streaming equivalence (one-shot == byte-at-a-time == split-mid-block
  == split-on-boundary) plus sha256_file(temp) == in-memory digest.

src/ untouched. zig build && zig build test && tests/run_examples.sh green.
2026-06-04 00:08:46 +03:00
agra
8f9691c206 F1.1: std.hash — streaming SHA-256 in library/modules/std/hash.sx
Add a pure-sx streaming SHA-256 (FIPS 180-4) stdlib module, importable
as `#import "modules/std/hash.sx";`. All 32-bit word arithmetic is done
in s64 and masked back with `& MASK32`, so digests are deterministic and
platform-independent — no shelling out, no native crypto.

API:
- init() -> Sha256          (by-value *self pattern)
- update(*Sha256, string)   (multi-block + partial-block buffering)
- final(*Sha256) -> string  (32-byte digest as lowercase hex)
- sha256_hex(string) -> string             (one-shot)
- sha256_file([:0]u8) -> ?string           (digest of a file via fs.read_file)

Verified against FIPS/NIST known-answer vectors and `shasum -a 256`:
"" , "abc", the 56- and 112-byte multi-block vectors, 1000×'a', and the
64/65-byte block boundaries; chunked update() matches the one-shot call.

examples/0710-modules-sha256.sx pins the KAT vectors + the streaming
invariant; gate green (zig build, zig build test, run_examples 370/0/0/0).
2026-06-03 22:38:58 +03:00
agra
fb8a5399f1 objc: remove ns_string/c_string helpers
ns_string's only caller was impl Into(*NSString) for string, so +stringWithUTF8String: is inlined there. c_string's one use (NSBundle.resourcePath in uikit) becomes rsrc.UTF8String() with resourcePath retyped *NSString. ffi-objc-call-06 and ffi-objc-dsl-07 .ir snapshots regenerated — they only drop the now-absent extern declares.
2026-05-30 18:01:27 +03:00
agra
a29ede0383 objc: migrate remaining ns_string call sites to xx NSString
NSLog's fmt, addObserver's name, UIApplicationMain's principal-class, CADisplayLink's run-loop mode, and metal's newLibraryWithSource/newFunctionWithName string args are retyped *NSString, so their call sites read xx "..." instead of ns_string("...".ptr). ns_string is now used only by impl Into(*NSString) for string.
2026-05-30 17:54:23 +03:00
agra
8e3c3ae981 objc: NSString type + Into(*NSString) for string
Adds an NSString foreign class and impl Into(*NSString) for string so a string literal flows into any *NSString slot via xx. uikit's keyboard userInfo lookups now read objectForKey(xx "...") instead of ns_string("...".ptr), and objectForKey's key param is retyped *NSString.

ffi-objc-call-06 .ir snapshot regenerated: declaring the NSString type adds its reflection thunks (struct_to_string/pointer_to_string), same as the existing NSObject/NSDictionary. Runtime output unchanged.
2026-05-30 17:39:38 +03:00
agra
2eaf932fcf ffi M5.A.next.5.3: delete hand-rolled __block_invoke trampolines
Removes `__block_invoke_void` / `__block_invoke_bool` and their
companion `Into(Block)` impls from
`library/modules/std/objc_block.sx`. The generic
`Into(Block) for Closure(..$args) -> $R` impl from step 5.2 now
covers both shapes (and every other closure shape) via per-mono
`#insert build_block_convert($args, $R)` source emission.

Net stdlib shrinkage: ~52 lines, two trampolines + two per-shape
impls down to zero. Adding a new block-shape consumer no longer
requires touching stdlib — the impl emits per-call-shape on
demand.

`examples/95-objc-block-noop.sx` (zero-arg closure) and
`examples/96-objc-block-multi-arg.sx` (user-declared per-shape
impl for `Closure(s32, *void) -> void`) still pass: 95 routes
through the new generic, 96 keeps its in-file impl as a
documentation example of the user-declares-their-own path.
Suite at 217/217.
2026-05-27 21:59:57 +03:00
agra
165b621ab3 ffi M5.A.next.5.2.B: generic Into(Block) impl — make-green
Adds the generic `impl Into(Block) for Closure(..$args) -> $R`
in `library/modules/std/objc_block.sx` alongside the existing
hand-rolled `Closure() -> void` and `Closure(bool) -> void`
impls. The convert body is a single
`#insert build_block_convert($args, $R);` — per-call-shape
monomorphisation re-runs the builder so each closure shape gets
its dedicated nested `callconv(.c)` trampoline + Block literal.

The impl-mono path threads pack types through
`pack_bindings[args]` and the single-type return through
`type_bindings[R]`. Both need to be visible to the body's
`$args` / `$R` expression-position references — the existing
lowering only consulted `pack_arg_types` (set by pack-fn mono,
not by tryPackImplMatch). Two small extensions:

- `lowerExpr`'s `.comptime_pack_ref` arm now consults
  `pack_arg_types` → `pack_bindings` → `type_bindings` in order,
  treating a `type_bindings` hit as a single `const_type(T)`
  value rather than the slice form.
- `resolveTypeArg` grows a `.comptime_pack_ref` arm that maps
  the same name through `type_bindings` so type-arg positions
  (e.g. inside `type_name(...)` in the builder body) resolve
  the bound single Type.
- `type_bridge.isTypeShapedAstNode` lists `comptime_pack_ref`
  and `pack_index_type_expr` as type-shaped so
  `buildTypeBindings`'s strategy-1 explicit-arg path picks
  them up when calling a `$T: Type`-generic fn.

`examples/177-generic-into-block.sx` flips green: a
`Closure(s64, s64) -> void` (no hand-rolled impl) is converted
through the generic impl, its block invoked via a typed
`callconv(.c)` fn-pointer, and the closure's side effects land
in the host globals. Hand-rolled impls remain for `()` and
`(bool)` shapes; 5.3 deletes those once a focused test covers
their behaviour through the generic path. Suite at 217/217.
2026-05-27 21:58:33 +03:00
agra
aeb950b86f ffi M5.A.next.5.1.B: build_block_convert added to stdlib — make-green
`build_block_convert(args: []Type, $ret: Type) -> string` emits
the convert-body source for the generic `Into(Block) for
Closure(..$args) -> $R` impl (step 5.2):

  1. A nested `__invoke :: (block_self: *Block, arg0: T0, ...) ->
     R callconv(.c) { ... }` trampoline matching the per-shape
     Apple Block ABI.
  2. A `return Block.{ ... };` literal whose `invoke` slot points
     at the nested trampoline via `xx @__invoke`.

Void-returning shapes emit `typed_fn(block_self.sx_env, args...)`;
non-void emits `return typed_fn(...)`. Per-position arg names
follow `arg0`, `arg1`, ... in declaration order; the typed-fn
cast reconstructs the closure's call signature so the trampoline
hands control back to `sx_fn` with the right argument layout.

`examples/176-build-block-convert.sx` flips green (216/216).
2026-05-27 21:48:45 +03:00
agra
07f25689ff ffi M5.A revert: drop compiler synthesis, require explicit Into(Block) impls
Reconsidered the M5.A.2 cleanup. The compiler-synthesised trampoline
path was hidden behaviour — a user reading their code couldn't tell
how `xx my_closure : Block` worked without reading lower.zig. That's
exactly the kind of magic sx's design has been pushing against.

New design (strict mode):

1. Stdlib's modules/std/objc_block.sx hand-rolls
   `__block_invoke_void` + `Into(Block) for Closure() -> void` and
   the same pair for `Closure(bool) -> void` (restored from M5.A.2).
   These are readable reference implementations of the bridge ABI.

2. The compiler intercept fires NO synthesis — instead, when
   `tryUserConversion` can't find a reachable `Into(Block)` impl for
   the closure's signature, it emits a focused diagnostic:
     "no `Into(Block) for <Closure-sig>` impl — add a per-signature
      `__block_invoke_<sig>` trampoline + Into impl alongside the
      existing ones in modules/std/objc_block.sx, or declare it in
      your own code"

3. Per-signature declarations live in stdlib (for common signatures)
   or in user code (for app-specific ones). 96-objc-block-multi-arg
   now demonstrates the user-side pattern in-file — it declares its
   own `__block_invoke_void_s32_p` + `Into(Block) for Closure(s32,
   *void) -> void` impl alongside its main().

Net effect:
- Every block bridge is source-visible. No hidden compiler magic.
- Users see exactly how the Apple ABI shape is constructed in sx
  source — stdlib serves as the reference implementation.
- Compiler enforces the discipline: missing impl → clear diagnostic
  pointing at the template.
- Coverage for arbitrary signatures requires conscious user opt-in,
  not silent fallthrough.

Removed from lower.zig: `tryClosureToBlockConversion`,
`emitBlockInvokeTrampoline`, `mangleClosureSigForBlock`,
`mangleTypeForBlock`, and the `block_invoke_trampolines` dedup
state field. Net: the synthesis machinery is gone; only the
detection helper `isClosureToBlockCast` remains, used by the
diagnostic.

190/190 example tests pass; chess on iOS-sim green.
2026-05-27 00:34:26 +03:00
agra
556e4e12ea ffi M5.A.2: drop hand-rolled __block_invoke_* impls + Into(Block) per-sig boilerplate
The compiler-synthesised trampoline path (previous commit) covers
every closure signature on demand; the hand-rolled stdlib impls
were only for two specific shapes (`Closure() -> void`,
`Closure(bool) -> void`) and are now strictly redundant.

Kept: the `Block` struct, `BlockDescriptor`, the
`_NSConcreteStackBlock` extern decl, and the shared
`__sx_block_descriptor` global. The compiler-emitted code
references all four; users still need to `#import
"modules/std/objc_block.sx";` to bring them into the module.

Removed: `__block_invoke_void`, `__block_invoke_bool`, and both
`impl Into(Block) for Closure(...) -> void` blocks. Replaced with
a comment block explaining how the compiler now handles the cast.

After this commit, `xx my_closure : Block` works for ANY closure
signature with no per-signature stdlib boilerplate. 189/189
example tests pass; chess on iOS-sim green.
2026-05-27 00:24:36 +03:00
agra
5c1d00a877 ffi M4.B helpers: objcPropertyKind + ARC runtime decls + xfail tests
Three pieces, no behavior change yet:

1. `ObjcPropertyKind` enum (strong/weak/copy/assign) + `objcPropertyKind`
   helper in lower.zig. Reads `field.property_modifiers`, applies the
   default rule (`*<ObjC-class>` → strong; primitives → assign), and
   emits loud diagnostics for the silent-error budget:
   - unknown modifier name (typo) → "expected one of: strong, weak, copy, ..."
   - conflicting modifiers (e.g. `strong,weak`) → "mutually exclusive"
   - `weak` on non-object slot → "requires a pointer-to-Obj-C-class type"
   - `copy` on non-object slot → same
   - `strong` (default or explicit) on `*void` → "ambiguous: specify
     #property(strong|weak|copy|assign) explicitly"
   Called from `emitObjcDefinedClassPropertyImps` for validation; the
   returned kind isn't wired into setter/getter/dealloc yet — that's
   the next three commits.

2. `ensureArcRuntimeDecls` lazily declares libobjc's ARC helpers:
   objc_retain, objc_release, objc_storeWeak, objc_loadWeakRetained,
   objc_initWeak, objc_destroyWeak. Uses the existing
   `ensureCRuntimeDecl` pattern; idempotent.

3. Fix existing NSObject method names in std/objc.sx — `isEqual_`,
   `isKindOfClass_`, `respondsToSelector_` had trailing underscores
   that the selector mangling turned into double-colon selectors
   (`isEqual::`). Removed the trailing underscore so the selectors
   come out as `isEqual:`, `isKindOfClass:`, `respondsToSelector:`
   as Apple's runtime expects.

4. Two xfail regression tests:
   - ffi-objc-arc-02-strong-property: assigns child to parent's strong
     property, releases the original child reference. Midpoint check:
     child's dealloc should NOT have fired (strong setter retained).
     Pre-M4.B-setter: child dealloc fires immediately → "FAIL: child
     dealloc'd at midpoint" snapshot. Exit code 1.
   - ffi-objc-arc-03-weak-property: assigns target to holder's weak
     property, releases target. Reads holder.target → should be null
     (auto-niled). Pre-M4.B-getter/setter: reads stale pointer →
     "FAIL: weak property didn't auto-nil" snapshot.

These will turn green as M4.B setter (commit 2), getter (commit 3),
and dealloc-cleanup (commit 4) land. Each subsequent commit updates
the snapshot to reflect the now-passing output.

189/189 example tests pass; chess on iOS-sim green.
2026-05-26 22:58:30 +03:00
agra
29404afdee ffi M4.A: stdlib NSObject + autoreleasepool helper + extends rooting
Declare `NSObject` in std/objc.sx as `#foreign #objc_class("NSObject")`
with the canonical instance + class-method surface every Obj-C class
inherits: `retain`/`release`/`autorelease`/`new`/`alloc`/`init`/
`description`/`hash`/`isEqual_`/`isKindOfClass_`/`respondsToSelector_`/
`class`. Root the foreign-class hierarchy in uikit.sx at NSObject by
adding `#extends NSObject;` to every previously-unrooted declaration
(NSValue, NSNumber, NSDictionary, NSSet, NSNotification, NSBundle,
NSNotificationCenter, NSRunLoop, CADisplayLink, CALayer, EAGLContext,
UIScreen, UIResponder) plus deeper chain fixes (NSMutableDictionary
extends NSDictionary; UIWindow extends UIView; UIViewController
extends UIResponder). After this, M2.3's extends-chain walk finds
`retain`/`release` on any UIKit-typed value:

  view := UIView.alloc().init();
  defer view.release();        // canonical sx idiom — no language magic

Plus `autoreleasepool(body: Closure())` stdlib helper that wraps
`body` in `objc_autoreleasePoolPush` / `defer objc_autoreleasePoolPop`.
Required for Foundation factory returns; closure-call frame is real
cost so hot loops should inline the push/defer-pop pattern manually.

Smoke test `ffi-objc-arc-01-autoreleasepool.sx` exercises both
patterns; refresh of two IR snapshots picks up the new stdlib decls
appearing in test outputs that include `modules/std/objc.sx`.

185/185 example tests pass; chess on iOS-sim green.
2026-05-26 22:38:32 +03:00
agra
a1736f3213 ffi M1.2 A.5: synthesized +alloc IMP + ensureCRuntimeDecl helper
For every sx-defined #objc_class, emit a C-callconv +alloc IMP
that the Obj-C runtime calls when '[Cls alloc]' fires (from sx
code, UIKit instantiation, Info.plist principal class, etc.):

  +alloc IMP (cls: Class, _cmd: SEL) -> id
      instance = class_createInstance(cls, 0)
      state    = malloc(STATE_SIZE)
      memset(state, 0, STATE_SIZE)
      object_setIvar(instance, load(@__<Cls>_state_ivar), state)
      return instance

STATE_SIZE = max(typeSizeBytes(state struct), 1) — always at
least one byte so the ivar is never null after +alloc returns.

The IMP is registered on the METACLASS (class methods live there
— every Class object's isa points to the metaclass) in emit_llvm's
class-pair init constructor:

  metaclass = object_getClass(cls)
  sel_alloc = sel_registerName("alloc")
  class_addMethod(metaclass, sel_alloc, alloc_imp, "@@:")

That override wins over NSObject's default +alloc; runtime
instantiations get the __sx_state ivar bound automatically.

Per-instance allocator binding (the plan's full design — store
the Allocator value in the state struct so -dealloc frees through
the same one) is deferred. libc malloc/free is fine for v1; we'll
upgrade once Month 4's autoreleasepool + ARC ops shake out.

REFACTOR: collapsed five duplicate 'get<Name>Fid' helpers and
their cache fields (object_getIvar, object_setIvar,
class_createInstance, malloc, memset) into a single
'ensureCRuntimeDecl(name, params, ret) -> FuncId'. The helper
checks for an existing decl by name first (avoids the
'class_createInstance.1' duplicate-symbol crash when stdlib's
'#foreign' decl is already in the module). One helper instead
of one-per-function = ~150 lines deleted.

object_getIvar / object_setIvar added to stdlib std/objc.sx
so user code can use them too (146 exercises object_getIvar
to verify __sx_state was bound to a non-null state pointer
after +alloc).

146-objc-class-alloc-roundtrip.sx end-to-end against macOS:
'[SxFoo alloc]' returns non-null AND object_getIvar(instance,
__sx_state) returns the state ptr. Real Obj-C runtime, no
mocks.

175 example tests pass (+1). zig build test green.
2026-05-25 23:17:30 +03:00
agra
d9dbdad3f5 ffi M1.1 (first pass): id / Class / SEL / BOOL type aliases
Adds named stand-ins for the three opaque Obj-C runtime types
and Apple's signed-char boolean to library/modules/std/objc.sx:

  id    :: *void;   // any Obj-C instance pointer
  Class :: *void;   // a class object pointer
  SEL   :: *void;   // a registered selector
  BOOL  :: s8;      // Apple's signed-char boolean (NOT sx's bool)

All resolve to their underlying type at the LLVM layer — no
runtime cost — but make foreign-class declarations read closer
to Objective-C source. The header's old caveat about lacking
type aliases is gone.

141-objc-type-aliases.sx exercises the aliases against the real
macOS Obj-C runtime: alloc/init an NSObject, fetch its class
via objc_getClass, sel_registerName a SEL, then call
'isKindOfClass:' returning BOOL=1. Non-macOS paths print the
same line to keep the snapshot stable.

DEFERRED (M1.1.b, follow-up): 'Class(T)' parameterization with
#extends-aware covariance, and 'instancetype' per-decl
substitution. Both require compiler-level type-check support
beyond plain stdlib aliases.

170 examples pass (+1).
2026-05-25 21:33:20 +03:00
agra
d4a342d0c1 mem: implicit-Context platform fixes — chess green on macOS/iOS/Android
Verify-step uncovered three categories of regressions where sx code
calls into the platform's C ABI through fn-pointer types or as a
registered callback. Every site now declares the right convention.

C-side calls INTO sx → callconv(.c) on the sx function:
- platform/android.sx: sx_android_render_thread_entry is the start
  routine pthread_create invokes — pthread treats it as a C function.
  Also annotate the pthread_create signature so the start-routine fn-
  pointer field rejects mismatching sx fns at compile time.

sx code calling typed fn-pointers cast from C symbols → callconv(.c)
on the fn-pointer type:
- opengl.sx: 55 GL fn-ptr globals + load_gl's proc-loader param. GL
  trampolines are macOS/iOS/Android system code.
- std/objc.sx: the two typed `objc_msgSend` casts.
- gpu/metal.sx: ~40 typed `objc_msgSend` casts across Metal command
  encoder / device / pipeline construction.

The block invoke trampolines (objc_block.sx) call back INTO sx (the
closure trampoline). The typed fn-ptr there stays default-conv so
ctx prepends correctly. Compiler change: a callconv(.c) sx function
now binds `current_ctx_ref` to `&__sx_default_context` at entry (used
to be gated by `isExportedEntryName`). C-callable sx callbacks like
the block invokes don't get their own __sx_ctx param but their bodies
still need a real Context to forward to the closure they delegate to.

Tests: 152/152 example suite + chess green on all 3 platforms.
Screenshots at /tmp/sx-game-{macos,iossim,android}.png.
2026-05-25 09:35:15 +03:00
agra
f9ecf9d00e iOS lock step keyboard + metal 2026-05-18 17:40:10 +03:00
agra
c027e1969b stdlib: relocate modules under library/
- examples/modules/ -> library/modules/ (top-level, no more
  symlink hacks in consumer projects)
- compiler discovers stdlib via _NSGetExecutablePath / readlink
  /proc/self/exe; searches dev layout (../../library), install
  layout (../library), and alongside-binary fallback
- SX_STDLIB_PATH env var overrides for tests / dev convenience
- SX_DEBUG_STDLIB env var dumps the discovery results
- build.zig installs library/ alongside the binary
- Compilation gains stdlib_paths field threaded through resolveImports
- 50 tests pass; consumer projects can now build from any cwd
2026-05-17 13:49:25 +03:00