ffi 1.14: #objc_call OS-gating cross-compiles cleanly to Android

109/109 host tests pass; tests/cross_compile.sh's first real tuple
(`android | examples/ffi-objc-call-10-os-gate.sx`) compiles
through `sx build --target android` without finding any
`@objc_msgSend` / `@sel_registerName` symbols in the output —
the `inline if OS == .ios { #objc_call(...) }` arm is stripped
at sx compile time before emit_llvm runs, so the Android
toolchain (Bionic + libGLESv3 / NDK linker) doesn't see the
Obj-C runtime references that would otherwise be undefined.

Host (macOS): the example prints "host stripped both" — the iOS
arm is stripped (we're not iOS) AND the Android arm is stripped
(we're not Android), confirming `inline if OS == { case }`
symmetric strip-and-render works around `#objc_call` sites.

The example carries a 3-line `android_main` trampoline so the
NDK linker's `-u ANativeActivity_onCreate` / entry-point
discovery is satisfied — pattern shared with chess + the other
android examples.
This commit is contained in:
agra
2026-05-19 19:00:47 +03:00
parent 6dab8a157f
commit 5fad92785e
4 changed files with 51 additions and 3 deletions

View File

@@ -0,0 +1,43 @@
// Phase 1 step 1.14 (PLAN-FFI.md): `#objc_call` inside an
// `inline if OS == .ios { ... }` arm cross-compiles cleanly to
// Android. The comptime gate must strip the arm BEFORE the
// `objc_msg_send` lowering runs, otherwise emit_llvm would
// produce calls to `@objc_msgSend` / `@sel_registerName` that
// don't exist in Bionic + libGLESv3 / linker would fail.
//
// On macOS the iOS arm is also stripped (we're not iOS) so the
// runtime test just prints "host stripped both", proving the
// `inline if OS == { case }` form works around `#objc_call`
// sites the same way it does elsewhere.
#import "modules/std.sx";
#import "modules/compiler.sx";
main :: () -> s32 {
inline if OS == {
case .ios: {
// Stripped on macOS + Android. Compiled on iOS / ios-sim.
#objc_call(void)(null, "init");
print("ios path\n");
}
case .android: {
// Stripped on macOS + iOS. Compiled on Android.
// Nothing #objc_call-shaped here — just text — so we
// exercise the gate symmetrically across targets.
print("android path\n");
}
else: {
print("host stripped both\n");
}
}
0;
}
// Android target requires `android_main` as the NDK entry — kept as
// a 3-line trampoline so this example can pass through
// `--target android` builds in `tests/cross_compile.sh`.
android_main :: (app: *void) {
inline if OS == .android {
main();
}
}

View File

@@ -22,9 +22,12 @@ TMP_DIR="${TMPDIR:-/tmp}/sx-cross-compile"
mkdir -p "$TMP_DIR"
# Tuple format: "<target>|<example_path>"
# Add entries as cross-only examples land. Empty for now — Phase 0 only
# lays down the runner; baselines live in tests/run_examples.sh territory.
TUPLES=()
# Add entries as cross-only examples land. Verifies the example
# compiles cleanly for the target's NDK / SDK without needing the
# host to actually run it.
TUPLES=(
"android|examples/ffi-objc-call-10-os-gate.sx"
)
PASS=0
FAIL=0

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@
host stripped both