Week 7 of /Users/agra/.claude/plans/lets-plan-to-move-splendid-pumpkin.md
plus the android.sx refactor + three sx-compiler fixes hit along the way
to get chess on Pixel 7 Pro responding to touch end-to-end.
library/modules/platform/bundle.sx now covers the Android APK shape
alongside macOS / iOS-sim / iOS-device. `android_bundle_main` discovers
the SDK ($ANDROID_HOME / $ANDROID_SDK_ROOT / $HOME/Library/Android/sdk),
picks the highest-versioned build-tools + platforms via
`process.run("ls .. | sort -V | tail -1")`, stages
`<apk>.stage/lib/arm64-v8a/<libfoo.so>`, synthesizes
AndroidManifest.xml (NativeActivity vs `#jni_main` Activity branch),
writes each `#jni_main` decl's Java source under
`<stage>/java/<pkg>/<Cls>.java`, runs javac --release 11 + d8 to
produce classes.dex, aapt2-links the unaligned APK, appends lib/ +
classes.dex + each registered asset tree via zip, zipalign + ensure
debug keystore via keytool + apksigner sign.
Compiler-side accessors (src/ir/compiler_hooks.zig + library/modules/compiler.sx):
- is_android predicate.
- set_manifest_path / manifest_path + set_keystore_path / keystore_path.
- jni_main_count / jni_main_foreign_path_at(i) /
jni_main_java_source_at(i) surface the `#jni_main` emissions that
the Zig createApk previously consumed directly.
- main.zig wires manifest_path, keystore_path, and the per-decl
(foreign_path, java_source) parallel slices into BuildConfig before
invoking the post-link callback.
CLI `--apk <path>` keeps working as a transitional alias: it now feeds
bundle_path so the existing auto-`post_link_module = "platform.bundle"`
shim fires the same way as `--bundle`. main.zig no longer calls
target.createApk directly.
Deletions in src/target.zig: createApk, compileJniMainSources,
buildJniMainManifest, buildAndroidManifest, ensureDebugKeystore,
libNameFromSoBasename, plus helpers splitForeignPath / discoverJavac /
discoverAndroidSdk / findHighestSubdir / runProcess / runProcessIn
(~400 lines). git grep returns only the obituary comment.
library/modules/platform/android.sx refactor (chess Android dependency):
- Module-level globals retired (g_app_window, g_egl_*, g_viewport_*,
g_dpi_scale, g_should_stop, g_render_thread*, g_user_main_fn,
g_touch_*) → AndroidPlatform struct fields.
- All sx_android_* helpers take `plat: *AndroidPlatform` as first arg.
Render thread receives plat via pthread_create's arg.
- New `logical_w: f32 = 0.0` field. Consumers set it before init() to
define the design width in points; `recompute_scale` derives
`dpi_scale = pixel_w / logical_w` (or 1.0 if unset). Called on
init / set_viewport / egl_init. drain_touches divides incoming
physical pixel coords by dpi_scale so chess sees logical-space
positions matching its layout. Touch lands on the right squares.
Three sx-compiler bugs hit + fixed along the way:
1. Top-level `inline if OS == .X { decls }` body decls were silently
dropped because scanDecls/lowerDecls had no .if_expr arm. New
`flattenComptimeConditionals` pre-pass in src/imports.zig
(threaded via ComptimeContext from core.zig) hoists matching arms
recursively. Regression at examples/124-inline-if-hoist-toplevel.sx.
2. Parser rejected `#import` / `#framework` inside inline-if bodies
because parseStmt in src/parser.zig only had arms for `#insert`.
Added the missing arms. Regression at
examples/123-inline-if-import-in-body.sx (landed earlier).
3. JNI `Call<T>Method` switches in src/ir/emit_llvm.zig (instance /
nonvirtual / static) were missing `.f32` rows — jfloat returns
(e.g. MotionEvent.getX/getY) fell into the silent-undef else arm.
Chess's sx_android_push_touch(plat, getAction(), getX(), getY())
delivered garbage f32 coords to the touch ring, so taps landed
nowhere recognisable. Added `.f32 => Jni.Call{Static,Nonvirtual,}FloatMethod`
rows to all three switches; lifted unsupported-type detection
from emit_llvm into lowerForeignMethodCall with proper
source-spanned diagnostics (`isJniReturnTypeSupported`). Regressions
at examples/ffi-jni-call-10-jfloat-return.sx,
examples/ffi-jni-class-09-multi-float-args.sx,
examples/ffi-jni-call-11-unsupported-return-diag.sx.
Stale-snapshot drift in tests/expected/ffi-objc-call-03-selector-sharing.ir
and ffi-objc-call-06-sret-return.ir picks up the new BuildOptions
accessor extern decls (is_android, set_manifest_path,
set_keystore_path, jni_main_count, jni_main_foreign_path_at,
jni_main_java_source_at). Verified diff is dead-decl-only.
Chess on Pixel 7 Pro: tap on e2 white pawn -> yellow selection +
green dots on legal e3/e4 targets; tap on e4 -> board updates with
1. e4, "Black to move" + "1. e4" in info panel.
zig build && zig build test && bash tests/run_examples.sh -> 145/145
green. bash tests/cross_compile.sh -> 7/7 green.
50 lines
1.7 KiB
Plaintext
50 lines
1.7 KiB
Plaintext
// Regression: `obj.method()` foreign-class dispatch with a `float`
|
|
// return type used to silently emit `LLVMGetUndef` because the
|
|
// `Call<T>Method` switch in `emit_llvm.zig` didn't cover `.f32`.
|
|
// Combined with multiple such calls inlined as args to a single
|
|
// outer call (`f(o.a(), o.b(), o.c())`), every arg after the first
|
|
// went out as `undef` — exactly the chess Android touch failure
|
|
// (`MotionEvent.getX()` + `getY()` came through as `undef`s into
|
|
// `sx_android_push_touch`).
|
|
//
|
|
// This test exercises BOTH the `.f32` jdispatch slot AND the
|
|
// "multiple foreign-class method calls as args to one outer call"
|
|
// pattern. The bodies are gated behind a runtime-false flag so the
|
|
// JNI lookups never execute (no JVM in the test runtime), but the
|
|
// codegen path still has to emit the calls correctly.
|
|
|
|
#import "modules/std.sx";
|
|
|
|
MotionEvent :: #foreign #jni_class("android/view/MotionEvent") {
|
|
getAction :: (self: *Self) -> s32;
|
|
getX :: (self: *Self) -> f32;
|
|
getY :: (self: *Self) -> f32;
|
|
}
|
|
|
|
sx_consume_touch :: (action: s32, x: f32, y: f32) {
|
|
// Black-hole call so the args aren't dead-stripped before LLVM
|
|
// verification gets a chance to look at the call site.
|
|
if action == 0 and x == 0.0 and y == 0.0 {
|
|
print("zero\n");
|
|
}
|
|
}
|
|
|
|
g_should_call : bool = false;
|
|
|
|
drive_touch :: (env: *void, ev: *MotionEvent) {
|
|
#jni_env(env) {
|
|
// The bug: getX() / getY() lowered to `undef` floats and the
|
|
// call to sx_consume_touch passed garbage. Post-fix, all three
|
|
// JNI calls emit proper Call<T>Method dispatches.
|
|
sx_consume_touch(ev.getAction(), ev.getX(), ev.getY());
|
|
}
|
|
}
|
|
|
|
main :: () -> s32 {
|
|
if g_should_call {
|
|
drive_touch(null, null);
|
|
}
|
|
print("ok\n");
|
|
0;
|
|
}
|