Jitless aarch64-apple-ios builds (#2011)

* spike: build V8 from source for iOS (sim) + jitless smoke test

- build.rs: add iOS branch for bindgen clang args (iphoneos/iphonesimulator
  sysroot + arm64-apple-ios[-simulator] target triple).
- iOS gn build driven via GN_ARGS (target_os=ios, target_environment,
  ios_deployment_target, v8_enable_webassembly=false to satisfy torque).
- examples/ios_jitless.rs: forces --jitless and runs recursive JS; verified
  on the iOS simulator (fib(25)=75025).

* Add iOS (device + simulator) build support

Build V8 from source for aarch64-apple-ios and aarch64-apple-ios-sim, and
publish the static libs from CI like the other targets.

- build.rs: select iOS settings automatically per target (no extra GN args).
  The simulator keeps the JIT; the device build is jitless -- iOS denies the
  JIT entitlement to non-WebKit apps -- which requires V8's optimizing tiers
  (Sparkplug/Maglev/Turbofan) and WebAssembly disabled. bindgen is pointed at
  the iphoneos/iphonesimulator SDK with the matching clang target triple.
- ci.yml: add aarch64-apple-ios and aarch64-apple-ios-sim (release) to the
  matrix; iOS is cross-compiled so it gets a build-only step instead of the
  test/clippy steps, and the existing publish step uploads the gzipped lib.
- README: document the iOS build.
- examples/ios_jitless.rs: forces --jitless and runs recursive JS; verified
  on the iOS simulator (fib(25)=75025).

Verified locally: V8 builds from source for both iOS targets (arm64); the
v8 crate links against the simulator lib and runs JS on the simulator,
including in --jitless mode. The device lib builds with compile-time jitless.
CI rows are best-effort (no iOS runner available to self-test).

* ci(ios): build iOS with simdutf (the variant downstream consumers need)
This commit is contained in:
Divy Srivastava authored and GitHub committed 2026-07-09 21:18:11 +05:30
1 parent 1f9be2ee7d
commit 951fe30362
4 files changed
+132 -1

No files matched your search

+38 -1
View File
@@ -75,6 +75,25 @@ jobs:
v8_enable_pointer_compression: true
cargo: cargo
# iOS: built from source. The simulator keeps the JIT; the device
# build is jitless (no JIT entitlement on device) -- see build.rs.
# Cross-compiled, so the test suite is not run on the runner.
# Built with simdutf (the variant downstream consumers like Deno
# need); WebAssembly is disabled (Torque can't build it jitless).
- os: macos-15
target: aarch64-apple-ios-sim
variant: release
v8_enable_pointer_compression: false
simdutf: true
cargo: cargo
- os: macos-15
target: aarch64-apple-ios
variant: release
v8_enable_pointer_compression: false
simdutf: true
cargo: cargo
- os: ${{ github.repository == 'denoland/rusty_v8' && 'ubuntu-22.04-xl' || 'ubuntu-22.04' }}
target: x86_64-unknown-linux-gnu
variant: debug
@@ -277,6 +296,10 @@ jobs:
echo "CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/riscv64-linux-gnu-gcc-10" >> ${GITHUB_ENV}
echo "QEMU_LD_PREFIX=/usr/riscv64-linux-gnu" >> ${GITHUB_ENV}
- name: Install iOS target
if: contains(matrix.config.target, 'ios')
run: rustup target add ${{ matrix.config.target }}
- name: Write git_submodule_status.txt
run: git submodule status --recursive > git_submodule_status.txt
@@ -313,6 +336,9 @@ jobs:
# Unlike aarch64, the riscv64 sccache binary is not static, so use the host architecture one.
"riscv64gc-unknown-linux-gnu" = "x86_64-unknown-linux-musl"
"x86_64-pc-windows-msvc" = "x86_64-pc-windows-msvc"
# iOS cross-compiles on the arm64 macOS runner, so use the host sccache binary.
"aarch64-apple-ios" = "aarch64-apple-darwin"
"aarch64-apple-ios-sim" = "aarch64-apple-darwin"
}['${{ matrix.config.target }}']
$basename = "sccache-$version-$platform"
$url = "https://github.com/mozilla/sccache/releases/download/$version/$basename.tar.gz"
@@ -346,13 +372,24 @@ jobs:
run: |
clang-format-19 --verbose --Werror --dry-run src/*.cc src/*.hpp src/*.h
# iOS targets cross-compile and cannot run on the runner, so they get a
# build-only step (which also produces gn_out/obj/librusty_v8.a for the
# publish step below) instead of the test + clippy steps.
- name: Build (iOS)
if: contains(matrix.config.target, 'ios')
env:
SCCACHE_IDLE_TIMEOUT: 0
V8_FROM_SOURCE: 1
run: ${{ matrix.config.cargo }} build -v --locked --target ${{ matrix.config.target }} ${{ env.CARGO_VARIANT_FLAG }} ${{ env.CARGO_FEATURE_FLAGS }}
- name: Test
env:
SCCACHE_IDLE_TIMEOUT: 0
if: matrix.config.variant == 'debug' || matrix.config.variant == 'release'
if: (matrix.config.variant == 'debug' || matrix.config.variant == 'release') && !contains(matrix.config.target, 'ios')
run: ${{ matrix.config.cargo }} nextest run -v --cargo-verbose --cargo-verbose --all-targets --locked --target ${{ matrix.config.target }} ${{ env.CARGO_VARIANT_FLAG }} ${{ env.CARGO_FEATURE_FLAGS }}
- name: Clippy
if: ${{ !contains(matrix.config.target, 'ios') }}
run: ${{ matrix.config.cargo }} clippy --all-targets --locked --target ${{ matrix.config.target }} ${{ env.CARGO_VARIANT_FLAG }} ${{ env.CARGO_FEATURE_FLAGS }} -- -D clippy::all
- name: Prepare binary publish
+12
View File
@@ -145,6 +145,18 @@ docker build --build-arg CROSS_BASE_IMAGE=ghcr.io/cross-rs/aarch64-linux-android
V8_FROM_SOURCE=1 cross build -vv --target aarch64-linux-android
```
For iOS builds: cross compile from an arm64 macOS host. The simulator target
keeps the JIT; the device target (`aarch64-apple-ios`) is built jitless, since
iOS denies the JIT entitlement to non-WebKit apps (WebAssembly is also disabled
in this configuration). `build.rs` selects these settings automatically per
target — no extra GN args required:
```bash
rustup target add aarch64-apple-ios-sim # simulator
rustup target add aarch64-apple-ios # device (jitless)
V8_FROM_SOURCE=1 cargo build -vv --target aarch64-apple-ios-sim
```
The build depends on several binary tools: `gn`, `ninja` and `clang`. The tools
will automatically be downloaded, if they are not detected in the environment.
+52
View File
@@ -212,6 +212,30 @@ fn build_binding() {
clang_args.push(format!("-isystem{}/include", resource_dir.trim()));
}
}
} else if target_os == "ios" {
// iOS: point bindgen at the iOS (device) or iOS-simulator SDK and set the
// matching clang target triple so the V8 headers parse correctly.
let target_triple = env::var("TARGET").unwrap();
let is_sim = target_triple.ends_with("-sim")
|| target_triple.starts_with("x86_64-apple-ios");
let sdk = if is_sim {
"iphonesimulator"
} else {
"iphoneos"
};
let output = Command::new("xcrun")
.args(["--sdk", sdk, "--show-sdk-path"])
.output()
.unwrap();
let sdk_path = String::from_utf8(output.stdout).unwrap();
clang_args.push("-isysroot".to_string());
clang_args.push(sdk_path.trim().to_string());
let clang_target = if is_sim {
"arm64-apple-ios-simulator"
} else {
"arm64-apple-ios"
};
clang_args.push(format!("--target={clang_target}"));
}
let bindings = bindgen::Builder::default()
@@ -453,6 +477,34 @@ fn build_v8(is_asan: bool) {
);
}
// iOS / iOS-simulator. iOS denies the JIT entitlement to non-WebKit apps, so
// a device build must be jitless -- which in turn requires V8's optimizing
// tiers (Sparkplug/Maglev/Turbofan) and WebAssembly to be disabled. The
// simulator runs on the host and could keep the JIT, but WebAssembly is
// disabled there too because Torque can't generate the Wasm builtins in this
// configuration. `target_cpu="arm64"` is already set above for aarch64.
// Pass an explicit `target_os="ios"` in GN_ARGS to fully override this.
if target_os == "ios" && !gn_args_env.contains(r#"target_os="ios""#) {
let is_sim = target_triple.ends_with("-sim")
|| target_triple.starts_with("x86_64-apple-ios");
gn_args.push(r#"target_os="ios""#.to_string());
gn_args.push(format!(
r#"target_environment="{}""#,
if is_sim { "simulator" } else { "device" }
));
gn_args.push(r#"ios_deployment_target="14.0""#.to_string());
gn_args.push("ios_enable_code_signing=false".to_string());
gn_args.push("treat_warnings_as_errors=false".to_string());
gn_args.push("v8_enable_webassembly=false".to_string());
if !is_sim {
// Device: no JIT permitted -> jitless build, all tiers off.
gn_args.push("v8_jitless=true".to_string());
gn_args.push("v8_enable_sparkplug=false".to_string());
gn_args.push("v8_enable_maglev=false".to_string());
gn_args.push("v8_enable_turbofan=false".to_string());
}
}
if target_triple.starts_with("i686-") {
gn_args.push(r#"target_cpu="x86""#.to_string());
}
+30
View File
@@ -0,0 +1,30 @@
// Minimal jitless smoke test for the iOS spike: force V8 into jitless mode
// (the mode required on a real iOS device, which denies the JIT entitlement)
// and run a little recursive JS to confirm the Ignition interpreter executes.
fn main() {
let platform = v8::new_default_platform(0, false).make_shared();
v8::V8::initialize_platform(platform);
// The flag a real device build needs: no runtime codegen / no RWX pages.
v8::V8::set_flags_from_string("--jitless");
v8::V8::initialize();
{
let isolate = &mut v8::Isolate::new(v8::CreateParams::default());
v8::scope!(let handle_scope, isolate);
let context = v8::Context::new(handle_scope, Default::default());
let scope = &v8::ContextScope::new(handle_scope, context);
let src = "const fib=(n)=>n<2?n:fib(n-1)+fib(n-2); `fib(25)=${fib(25)}`";
let code = v8::String::new(scope, src).unwrap();
let script = v8::Script::compile(scope, code, None).unwrap();
let result = script.run(scope).unwrap();
let result = result.to_string(scope).unwrap();
println!("JITLESS OK: {}", result.to_rust_string_lossy(scope));
}
unsafe {
v8::V8::dispose();
}
v8::V8::dispose_platform();
}