iOS lock step keyboard + metal
This commit is contained in:
32
examples/88-enum-through-protocol-dispatch.sx
Normal file
32
examples/88-enum-through-protocol-dispatch.sx
Normal file
@@ -0,0 +1,32 @@
|
||||
// Sub-32-bit enum variants ride through a protocol-typed receiver's
|
||||
// method call without being collapsed to tag=0.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Fmt :: enum { a; b; }
|
||||
|
||||
Proto :: protocol {
|
||||
take_fmt :: (f: Fmt);
|
||||
}
|
||||
|
||||
Impl :: struct {}
|
||||
impl Proto for Impl {
|
||||
take_fmt :: (self: *Impl, f: Fmt) {
|
||||
n : s64 = xx f;
|
||||
print("proto f = {}\n", n);
|
||||
}
|
||||
}
|
||||
|
||||
take :: (f: Fmt) -> s64 {
|
||||
n : s64 = xx f;
|
||||
n;
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
print("direct a={} b={}\n", take(.a), take(.b));
|
||||
|
||||
p : Proto = xx @Impl.{};
|
||||
p.take_fmt(.b);
|
||||
p.take_fmt(.a);
|
||||
0;
|
||||
}
|
||||
20
examples/89-enum-arg-through-closure-field.sx
Normal file
20
examples/89-enum-arg-through-closure-field.sx
Normal file
@@ -0,0 +1,20 @@
|
||||
// A closure stored in a struct field receives sub-32-bit enum args
|
||||
// with the right tag, same as direct or protocol-dispatched calls.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Fmt :: enum { a; b; }
|
||||
|
||||
Ctx :: struct {
|
||||
on: Closure(Fmt) -> void;
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
c : Ctx = .{ on = (f: Fmt) => {
|
||||
n : s64 = xx f;
|
||||
print("cl f = {}\n", n);
|
||||
}};
|
||||
c.on(.b);
|
||||
c.on(.a);
|
||||
0;
|
||||
}
|
||||
30
examples/90-protocol-real-pointer-return.sx
Normal file
30
examples/90-protocol-real-pointer-return.sx
Normal file
@@ -0,0 +1,30 @@
|
||||
// A protocol method declared with a real pointer return (`-> *u8`,
|
||||
// NOT `-> Self`) returns the raw pointer to the caller without the
|
||||
// dispatch path auto-dereferencing it. Without this, a method whose
|
||||
// pointee is a single byte gets `sizeof(target)` bytes loaded past
|
||||
// it and segfaults.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Proto :: protocol {
|
||||
get :: () -> *u8;
|
||||
}
|
||||
|
||||
Impl :: struct {
|
||||
val: u8 = 42;
|
||||
}
|
||||
|
||||
impl Proto for Impl {
|
||||
get :: (self: *Impl) -> *u8 {
|
||||
@self.val;
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
imp : Impl = .{};
|
||||
p : Proto = xx @imp;
|
||||
raw : *u8 = p.get();
|
||||
addr_word : u64 = xx raw;
|
||||
print("got pointer: {}\n", addr_word != 0);
|
||||
0;
|
||||
}
|
||||
20
examples/91-protocol-typeparam-parse.sx
Normal file
20
examples/91-protocol-typeparam-parse.sx
Normal file
@@ -0,0 +1,20 @@
|
||||
// Phase 1 (xx-via-Into mechanism): proves the new syntax parses + lowers
|
||||
// without error. The parameterised protocol Into(Target: Type) and the
|
||||
// matching `impl Into(Block) for Closure() -> void` declarations are
|
||||
// registered but unused. Resolution (Phase 3) is what makes the impl
|
||||
// reachable from `xx`.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
MyTag :: struct { value: s64 = 0; }
|
||||
|
||||
impl Into(MyTag) for s64 {
|
||||
convert :: (self: s64) -> MyTag {
|
||||
.{ value = self };
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
print("ok\n");
|
||||
0;
|
||||
}
|
||||
20
examples/92-xx-userspace.sx
Normal file
20
examples/92-xx-userspace.sx
Normal file
@@ -0,0 +1,20 @@
|
||||
// Phase 3 (xx-via-Into): a user-defined `impl Into(Target) for Source`
|
||||
// reaches the xx operator through compile-time dispatch. The compiler
|
||||
// monomorphises `convert` for the (Source, Target) pair and emits a
|
||||
// direct call — no vtable, identical to a hand-written call.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
MyString :: struct { tag: s64 = 0; }
|
||||
|
||||
impl Into(MyString) for s64 {
|
||||
convert :: (self: s64) -> MyString {
|
||||
.{ tag = self };
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
x : MyString = xx 42;
|
||||
print("tag = {}\n", x.tag);
|
||||
0;
|
||||
}
|
||||
12
examples/93-into-impl-helper.sx
Normal file
12
examples/93-into-impl-helper.sx
Normal file
@@ -0,0 +1,12 @@
|
||||
// Helper for 93-into-import-scope.sx: declares Wrap + an impl Into for it.
|
||||
// No paired tests/expected file — not executed standalone by the runner.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Wrap :: struct { v: s64 = 0; }
|
||||
|
||||
impl Into(Wrap) for s64 {
|
||||
convert :: (self: s64) -> Wrap {
|
||||
.{ v = self * 10 };
|
||||
}
|
||||
}
|
||||
13
examples/93-into-import-scope.sx
Normal file
13
examples/93-into-import-scope.sx
Normal file
@@ -0,0 +1,13 @@
|
||||
// Phase 4 (xx-via-Into mechanism): an `impl Into(...) for ...` lives in
|
||||
// a separate file and reaches the xx site through a direct `#import`.
|
||||
// The visibility filter accepts the impl because the user file
|
||||
// transitively imports the impl's defining module.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "./93-into-impl-helper.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
w : Wrap = xx 3;
|
||||
print("w.v = {}\n", w.v);
|
||||
0;
|
||||
}
|
||||
14
examples/94-foreign-global.sx
Normal file
14
examples/94-foreign-global.sx
Normal file
@@ -0,0 +1,14 @@
|
||||
// Extern data globals via `<name> : <type> #foreign;`. Lets sx code
|
||||
// reference libSystem / framework symbols (NSConcreteStackBlock,
|
||||
// __stdinp, etc.) for FFI bridges. Mirrors the long-standing
|
||||
// `<fn> :: (...) -> ... #foreign;` form on the function side.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
__stdinp : *void #foreign;
|
||||
|
||||
main :: () -> s32 {
|
||||
addr_bits : u64 = xx @__stdinp;
|
||||
print("stdin extern global non-null: {}\n", addr_bits != 0);
|
||||
0;
|
||||
}
|
||||
17
examples/95-objc-block-noop.sx
Normal file
17
examples/95-objc-block-noop.sx
Normal file
@@ -0,0 +1,17 @@
|
||||
// `xx <closure> : Block` builds an Apple-ABI block whose invoke
|
||||
// trampoline delegates to the sx closure. Verifies end-to-end:
|
||||
// stdlib Block layout, _NSConcreteStackBlock extern, per-signature
|
||||
// invoke trampoline, Into(Block) for Closure() -> void. Runs on
|
||||
// macOS — invokes the block's invoke fn directly via a typed fn
|
||||
// pointer instead of going through the Obj-C runtime.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "modules/std/objc_block.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
cl := () => { print("noop block ran\n"); };
|
||||
b : Block = xx cl;
|
||||
invoke_fn : (*Block) -> void = xx b.invoke;
|
||||
invoke_fn(@b);
|
||||
0;
|
||||
}
|
||||
17
examples/96-objc-block-capture.sx
Normal file
17
examples/96-objc-block-capture.sx
Normal file
@@ -0,0 +1,17 @@
|
||||
// A capturing closure rides through `xx ... : Block` and the
|
||||
// captured state survives across the call. The block's sx_env field
|
||||
// holds the closure's env pointer; the invoke trampoline restores it
|
||||
// before delegating.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "modules/std/objc_block.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
x : s64 = 42;
|
||||
y : s64 = 100;
|
||||
cl := () => { print("x + y = {}\n", x + y); };
|
||||
b : Block = xx cl;
|
||||
invoke_fn : (*Block) -> void = xx b.invoke;
|
||||
invoke_fn(@b);
|
||||
0;
|
||||
}
|
||||
19
examples/97-objc-block-inline.sx
Normal file
19
examples/97-objc-block-inline.sx
Normal file
@@ -0,0 +1,19 @@
|
||||
// `xx <closure>` passed as a `*Block` fn argument auto-allocates the
|
||||
// Block instance and passes its address — no named temp required.
|
||||
// Matches the ergonomics of ObjC's `^{...}` literal at the call site.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "modules/std/objc_block.sx";
|
||||
|
||||
invoke_once :: (b: *Block) {
|
||||
invoke_fn : (*Block) -> void = xx b.invoke;
|
||||
invoke_fn(b);
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
x : s64 = 7;
|
||||
invoke_once(xx () => {
|
||||
print("inline block, x = {}\n", x);
|
||||
});
|
||||
0;
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
// issue-0026: Chess game on iOS-sim with `plat.gpu_mode = .metal` crashes
|
||||
// inside `[MTLTexture replaceRegion:mipmapLevel:withBytes:bytesPerRow:]`
|
||||
// when uploading the 1024×1024 R8 font atlas. The 1×1 RGBA8 white tex
|
||||
// through the SAME code path (metal_update_texture_region_ios in
|
||||
// library/modules/gpu/metal.sx) works.
|
||||
//
|
||||
// Blocked on issue-0024 (NSLog inside if/else not firing — or unified-log
|
||||
// buffer loss on crash; investigation pending) — without a trustworthy
|
||||
// tracer we can't reliably bisect which arg arrives wrong. Most likely
|
||||
// cause: this is downstream of issue-0025's ABI gaps (MTLRegion is 48
|
||||
// bytes and goes through `xx objc_msgSend` cast, which is the
|
||||
// call_indirect path that issue-0025 part B covers).
|
||||
//
|
||||
// ── Reproduction recipe ───────────────────────────────────────────────────
|
||||
//
|
||||
// cd /Users/agra/projects/game
|
||||
// /Users/agra/projects/sx/zig-out/bin/sx build --target ios-sim main.sx \
|
||||
// --bundle sx-out/ios/SxChess.app --bundle-id co.swipelab.sxchess \
|
||||
// -F ~/Library/Frameworks
|
||||
// cp -R assets sx-out/ios/SxChess.app/
|
||||
// codesign --force --sign - --timestamp=none sx-out/ios/SxChess.app
|
||||
// xcrun simctl install booted sx-out/ios/SxChess.app
|
||||
// xcrun simctl launch --terminate-running-process booted co.swipelab.sxchess
|
||||
// sleep 4 && xcrun simctl io booted screenshot /tmp/sx-chess.png
|
||||
//
|
||||
// Expected (after fix): chess board renders via Metal.
|
||||
// Observed: app launches, returns immediately to home screen, no screen
|
||||
// touched. The simpler examples/63-metal-clear.sx demo still renders the
|
||||
// colored triangle on the same sim, so the Metal pipeline itself works
|
||||
// for small uploads.
|
||||
//
|
||||
// ── Candidate root causes (in priority order) ─────────────────────────────
|
||||
//
|
||||
// 1. issue-0025 fallout (most likely): MTLRegion (48 B by value) passed
|
||||
// via the *MTLRegion workaround. The call_indirect path (issue-0025
|
||||
// part B) doesn't ABI-coerce, so the pointer-shaped declaration may
|
||||
// not actually pass the address in the right register slot for that
|
||||
// call site shape (6 args, including the indirect aggregate).
|
||||
//
|
||||
// 2. iOS-sim Metal-driver limitation: `setStorageMode:.shared` may not be
|
||||
// honored for r8 textures of this size; default may be `.private`
|
||||
// which precludes CPU-side replaceRegion. Workaround would be to
|
||||
// upload via `MTLBuffer` + `MTLBlitCommandEncoder` (newBufferWithBytes
|
||||
// + copyFromBuffer:sourceOffset:sourceBytesPerRow:...:toTexture:...).
|
||||
//
|
||||
// 3. sx-side `xx` cast bug: bytes_per_row : u64 = xx (u32_expr) may
|
||||
// truncate or sign-extend incorrectly. Less likely (the math comes
|
||||
// out to 1024, which fits in any width).
|
||||
//
|
||||
// ── How to resolve ────────────────────────────────────────────────────────
|
||||
//
|
||||
// After issues 0024 + 0025 are landed:
|
||||
// 1. Re-add the trace NSLog markers ("[metal] U1..U5" in
|
||||
// metal_update_texture_region_ios) — now they should actually print.
|
||||
// 2. Re-build + relaunch chess on iOS-sim.
|
||||
// 3. If U5 fires after U4 (no crash inside msg_replace), the bug was
|
||||
// ABI-related; declare success and rename this file to
|
||||
// examples/NN-metal-large-region-upload.sx (next free NN).
|
||||
// 4. If U4 → crash persists, fall back to the MTLBuffer + blit
|
||||
// encoder path in metal.sx's create_texture (when pixels != null,
|
||||
// allocate a temporary MTLBuffer with newBufferWithBytes:length:options:
|
||||
// then run a one-shot command buffer with a MTLBlitCommandEncoder
|
||||
// copying the buffer into the texture). This is the Apple-recommended
|
||||
// approach for large texture initial-uploads.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () -> s32 { 0; }
|
||||
@@ -1,50 +0,0 @@
|
||||
// issue-0027: Feature — support Obj-C blocks (^{...}) so sx code can call
|
||||
// APIs that take a block parameter. Required for step 4 of the Metal port
|
||||
// (keyboard lockstep via `[UIView animateWithDuration:animations:^{...}]`),
|
||||
// and broadly useful for any UIKit/AppKit API.
|
||||
//
|
||||
// ── Proposed surface ──────────────────────────────────────────────────────
|
||||
//
|
||||
// Option A — comptime intrinsic that wraps a sx closure as a block:
|
||||
//
|
||||
// block := objc_block(@my_closure); // returns *void (an id<Block>)
|
||||
// msg_block(view, sel, 0.3, block); // pass like any id arg
|
||||
//
|
||||
// Internals: emit a Block_literal struct constant with the right invoke
|
||||
// fn pointer, isa, flags, descriptor pointer. Approximately what clang
|
||||
// generates for ^{...}.
|
||||
//
|
||||
// Option B — surface-level syntax `^{ ... }` that lowers to Option A
|
||||
// automatically. Cleaner for users; more parser work.
|
||||
//
|
||||
// Recommended: start with Option A (intrinsic). Migrate to Option B once
|
||||
// the codegen path is proven.
|
||||
//
|
||||
// ── Implementation sketch ────────────────────────────────────────────────
|
||||
//
|
||||
// 1. New `library/modules/std/objc_block.sx` defining the Block_literal
|
||||
// struct that mirrors clang's layout (isa, flags, reserved, invoke fn
|
||||
// pointer, descriptor pointer).
|
||||
// 2. `objc_block(fn_or_closure) -> *void` intrinsic that builds the
|
||||
// literal at the call site. Initial implementation can be a
|
||||
// stack-allocated block (_NSConcreteStackBlock); upgrade to
|
||||
// heap-promoted (_Block_copy) once block lifetime exceeds the call.
|
||||
// 3. Link libSystem's symbols `_NSConcreteStackBlock` and
|
||||
// `_NSConcreteGlobalBlock` (auto on iOS; may need `#library "System"`
|
||||
// on macOS).
|
||||
// 4. (Deferred) surface syntax `^{ ... }` — parser hook + lowering
|
||||
// to the intrinsic. Must not clash with bitwise XOR `^`.
|
||||
//
|
||||
// ── References ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// - Apple block ABI spec (clang's "Block Implementation Specification")
|
||||
// - _NSConcreteStackBlock + _NSConcreteGlobalBlock from libSystem
|
||||
//
|
||||
// ── Real-world impact ─────────────────────────────────────────────────────
|
||||
//
|
||||
// Without this, the keyboard inset cannot be animated in lockstep with the
|
||||
// keyboard slide. See library/modules/platform/uikit.sx's
|
||||
// uikit_keyboard_will_change_frame comments for the deferred lockstep work.
|
||||
|
||||
#import "modules/std.sx";
|
||||
main :: () -> s32 { 0; }
|
||||
17
examples/issue-0032.sx
Normal file
17
examples/issue-0032.sx
Normal file
@@ -0,0 +1,17 @@
|
||||
// Phase 2 verification: two impls for the same parameterised-protocol
|
||||
// (Source, Target) pair declared in the same file MUST produce a clean
|
||||
// "duplicate impl" diagnostic at registration time.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
MyA :: struct { v: s64 = 0; }
|
||||
|
||||
impl Into(MyA) for s64 {
|
||||
convert :: (self: s64) -> MyA { .{ v = self }; }
|
||||
}
|
||||
|
||||
impl Into(MyA) for s64 {
|
||||
convert :: (self: s64) -> MyA { .{ v = self * 2 }; }
|
||||
}
|
||||
|
||||
main :: () -> s32 { 0; }
|
||||
10
examples/issue-0033-impl.sx
Normal file
10
examples/issue-0033-impl.sx
Normal file
@@ -0,0 +1,10 @@
|
||||
// Helper that defines the impl. issue-0033's user file does NOT
|
||||
// directly import this — that's the whole point of the test.
|
||||
#import "modules/std.sx";
|
||||
#import "./issue-0033-types.sx";
|
||||
|
||||
impl Into(Wrap) for s64 {
|
||||
convert :: (self: s64) -> Wrap {
|
||||
.{ v = self * 10 };
|
||||
}
|
||||
}
|
||||
2
examples/issue-0033-types.sx
Normal file
2
examples/issue-0033-types.sx
Normal file
@@ -0,0 +1,2 @@
|
||||
// Shared type for issue-0033 — Wrap struct.
|
||||
Wrap :: struct { v: s64 = 0; }
|
||||
10
examples/issue-0033-user.sx
Normal file
10
examples/issue-0033-user.sx
Normal file
@@ -0,0 +1,10 @@
|
||||
// User file uses xx but only imports the shared types — NOT the impl.
|
||||
// The Phase 4 visibility filter should reject the impl from issue-0033-impl.sx.
|
||||
#import "modules/std.sx";
|
||||
#import "./issue-0033-types.sx";
|
||||
|
||||
run_user :: () -> s32 {
|
||||
w : Wrap = xx 7;
|
||||
print("user: w.v = {}\n", w.v);
|
||||
0;
|
||||
}
|
||||
16
examples/issue-0033.sx
Normal file
16
examples/issue-0033.sx
Normal file
@@ -0,0 +1,16 @@
|
||||
// Phase 4 verification: an `impl Into(...) for ...` is registered into the
|
||||
// global impl table when its module is imported anywhere in the program, but
|
||||
// is only **visible** from files that themselves transitively import the impl's
|
||||
// defining module. Here:
|
||||
// - issue-0033-impl.sx declares an `impl Into(Wrap) for s64`.
|
||||
// - issue-0033-user.sx tries to `xx 7 : Wrap` but only imports the shared
|
||||
// types — not the impl module.
|
||||
// - The xx at issue-0033-user.sx:7 must produce a clean "no visible xx
|
||||
// conversion" diagnostic, not silently fall through to whatever was
|
||||
// registered in another module.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "./issue-0033-impl.sx";
|
||||
#import "./issue-0033-user.sx";
|
||||
|
||||
main :: () -> s32 { run_user(); }
|
||||
9
examples/issue-0034-impl-a.sx
Normal file
9
examples/issue-0034-impl-a.sx
Normal file
@@ -0,0 +1,9 @@
|
||||
// Helper A — one of two conflicting impls for the same (s64, Wrap) pair.
|
||||
#import "modules/std.sx";
|
||||
#import "./issue-0034-types.sx";
|
||||
|
||||
impl Into(Wrap) for s64 {
|
||||
convert :: (self: s64) -> Wrap {
|
||||
.{ v = self * 10 };
|
||||
}
|
||||
}
|
||||
9
examples/issue-0034-impl-b.sx
Normal file
9
examples/issue-0034-impl-b.sx
Normal file
@@ -0,0 +1,9 @@
|
||||
// Helper B — second conflicting impl for the same (s64, Wrap) pair.
|
||||
#import "modules/std.sx";
|
||||
#import "./issue-0034-types.sx";
|
||||
|
||||
impl Into(Wrap) for s64 {
|
||||
convert :: (self: s64) -> Wrap {
|
||||
.{ v = self + 100 };
|
||||
}
|
||||
}
|
||||
2
examples/issue-0034-types.sx
Normal file
2
examples/issue-0034-types.sx
Normal file
@@ -0,0 +1,2 @@
|
||||
// Shared type for issue-0034.
|
||||
Wrap :: struct { v: s64 = 0; }
|
||||
14
examples/issue-0034.sx
Normal file
14
examples/issue-0034.sx
Normal file
@@ -0,0 +1,14 @@
|
||||
// Phase 5 verification: two impls for the same (Source, Target) pair are
|
||||
// both visible from the same xx site (because both their defining modules
|
||||
// are transitively imported). The compiler must emit a clean
|
||||
// "duplicate xx conversion" diagnostic naming both modules.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "./issue-0034-impl-a.sx";
|
||||
#import "./issue-0034-impl-b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
w : Wrap = xx 7;
|
||||
print("w.v = {}\n", w.v);
|
||||
0;
|
||||
}
|
||||
Reference in New Issue
Block a user