Skip to content

Fall back to the interpreter when the compiler backend is unavailable - #64

Open
replghost wants to merge 4 commits into
mainfrom
feat/android-compiler-backend
Open

replghost wants to merge 4 commits into
mainfrom
feat/android-compiler-backend

Conversation

@replghost

Copy link
Copy Markdown
Collaborator

preferred_backend() hands the interpreter to wasm32, iOS and Android. Android does not belong in that list: polkavm's own gate (polkavm/src/lib.rs:9-17) enables the compiler for any(target_arch = "x86_64", target_arch = "aarch64") on target_os = "linux", and Android is aarch64 Linux. The only recorded justification is a line in epoca's docs/android-development-plan.md stating JIT is unavailable — no ticket, no code comment, and no security rationale.

What that costs, measured on this machine with the same guest (Supafaust) and the same ROM, changing nothing but the backend:

backend cpu per frame fps
Compiler 5.205 ms 56.1
Interpreter 129.221 ms 7.7

24.8x. The interpreter saturates a full desktop core to deliver 7.7 fps; a phone core is several times slower again. (Method: the pinned runtime rev was copied out of the cargo git cache with preferred_backend() forced to Interpreter, patched into a native host build, measured, reverted.)

Two changes:

  • preferred_backend() returns Interpreter for wasm32 and ios only. iOS stays because polkavm's gate excludes it outright, quite apart from Apple's JIT policy.
  • New with_backend_fallback() wraps the four convenience constructors (Runtime::new, ApplicationRuntime::new, ComputerRuntime::new, ComputerSupervisor::new). Passing a compile-time platform gate is not the same as a device permitting writable-then-executable mappings — SELinux is the case this exists for — so a failed construction retries once on the interpreter and attaches the original failure as context. A slow guest beats a guest that will not start. The explicit new_with_backend entry points are untouched, so callers that pin a backend still do.

Verified: cargo check -p polkavm-host-runtime and cargo test -p polkavm-host-runtime pass, and cargo check -p polkavm-host-runtime --target aarch64-linux-android compiles clean with the compiler backend enabled.

This needs a device to accept. The host already reports the active backend (android.rs nativeBackend, surfaced by PvmProductView), so the check is: install, confirm the reported backend is compiler, then run a framebuffer guest and confirm the frame rate moves. If a device's policy refuses executable mappings, the fallback should report interpreter and the guest should still run — that path deserves a look on a real device too, since I can only exercise it by construction failure here.

No test asserts preferred_backend() == Compiler; that would restate the cfg rather than defend behaviour.

@replghost replghost changed the title Ask Android for the compiler backend Fall back to the interpreter when the compiler backend is unavailable Sep 18, 2026
@replghost

Copy link
Copy Markdown
Collaborator Author

I tested this on a device and the premise was wrong. Rewritten.

Android does not qualify for polkavm's compiler at all, and not because of SELinux — because of the platform gate itself. polkavm/src/lib.rs:9-17 enables the compiler for target_os = "linux" (plus macOS/FreeBSD under generic-sandbox), and a Rust Android target is target_os = "android", which matches neither. My earlier claim that cargo check --target aarch64-linux-android proved the compiler was enabled was wrong: it compiled, with the compiler backend cfg'd out.

Measured on an API 35 x86_64 emulator, with a probe binary built against this branch and run on-device:

compiler supported at compile time: false
compiler: FAILED: create PolkaVM engine: the 'compiler' backend is not supported on this platform
interpreter: 56 frames in 8.0s = 7.0 fps | gas 1436471250

7.0 fps for a real SNES guest, which reproduces the field report of 7-10 fps on Android exactly.

I then tried to make the compiler work by adding target_os = "android" to the generic-sandbox arm. It is a genuine port, not a flag:

  • the sys module splits along polkavm-linux-raw (linux) versus libc (everyone else), and the libc dependency is itself gated to macOS/FreeBSD;
  • bionic's mcontext_t on x86_64 exposes registers through gregs[], not the glibc-style named rip/rax/trapno fields the signal handler reads, so fault handling has to be ported.

That work belongs upstream in polkavm, and it needs validating on a real arm64 device under app-domain SELinux, not just an emulator shell.

So this PR now contains only the part that survived: preferred_backend() keeps Android on the interpreter, with the reasoning and the measurement recorded in the doc comment, and with_backend_fallback() stays — any platform whose compiler is compiled in but refused at runtime degrades to the interpreter instead of failing to construct, carrying the original error as context.

For Android speed the realistic path is the one iOS just took: hand the guest to the WASM translator and let the WebView's engine compile it. GeckoView is already in the Android app, and wasm_codegen.rs plus the browser runtime already exist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant