Skip to content

Missing Scala Native for iOS #12

@MateuszKubuszok

Description

@MateuszKubuszok

There is a possibility that Scala Native could be compiled for iOS - with no support/bindings for ts frameworks, but still. If it's true, we could just draw on a canvas, since we are a game library, and that would not require integrating with any of these frameworks.

But there are possible blockers. They are provided by Claude, so they might be wrong, hallucinated, etc - cc @WojciechMazur


iOS Platform: Blockers and Path Forward

Supplements ios-backend-feasibility.md with actionable upstream blocker details.

Current status

SGE has 14 backend files marked as "deferred (iOS)". The existing ios-backend-feasibility.md documents the overall architecture. This document focuses on the concrete blockers in Scala Native and what we need from upstream.

Scala Native upstream blockers

Blocker 1: Immix GC heap allocation crashes on iOS (High)

Issue: scala-native#4334

Heap_getMemoryLimit() returns full device physical RAM via sysctl(HW_MEMSIZE). The Immix GC then calls mmap twice with that size (for smallHeap and largeHeap). On a 6GB iPhone this tries to mmap ~12GB — iOS kills the process with EXC_BAD_ACCESS.

Current workarounds:

  • GC_MAXIMUM_HEAP_SIZE environment variable to cap heap size
  • Boehm GC (nativeConfig ~= { _.withGC(GC.boehm) }) with tuned limits
  • GC.none (no collection — unacceptable for games)

Proper fix: Heap_getMemoryLimit() should use os_proc_available_memory() on iOS (available since iOS 13) instead of sysctl(HW_MEMSIZE). This is a targeted C-level patch.

File: nativelib/src/main/resources/scala-native/gc/immix/Heap.c (or similar — the heap limit function)

Blocker 2: C runtime preprocessor guards don't recognize iOS (Medium)

Issue: scala-native#2875

Two files fail on iOS SDK:

time_nano.c: Uses __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ which is undefined on iOS. The static_assert fires: "macOS version must be 10.12 or greater".

Fix: Add iOS variant:

#if defined(__APPLE__)
  #if defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__)
    // iOS: clock_gettime available since iOS 10
  #elif defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__)
    // macOS: existing check
  #endif
#endif

process_monitor.cpp: Includes <sys/posix_sem.h> which doesn't exist on iOS. Apple removed POSIX semaphores from the iOS SDK.

Fix: Use dispatch_semaphore_t (Grand Central Dispatch) on iOS, or #ifdef out process monitoring entirely (games don't fork child processes).

Blocker 3: No LinktimeInfo.isIOS (Low)

Scala Native has LinktimeInfo.isWindows, isMac, isLinux, isFreeBSD but no isIOS. This prevents compile-time platform branching for iOS-specific code paths.

Fix: Add isIOS following the same pattern as PR #2809 which added isFreeBSD.

Blocker 4: Discover.scala hardcodes macOS include paths (Low)

When the target triple contains apple-ios, Discover.scala should use xcrun --sdk iphoneos --show-sdk-path for include paths instead of macOS Xcode paths. This may already be handled by clang when given the correct -isysroot flag, but should be verified.

What is NOT a blocker

Concern Why it's fine
JIT restrictions Scala Native is fully AOT — no JIT needed
Code signing Scala Native produces .a static library; signing happens at Xcode level
Bitcode Deprecated in Xcode 14, removed in Xcode 16
Exception handling iOS ARM64 uses same unwinding as macOS ARM64 (LLVM handles it)
setjmp.S assembly The __aarch64__ block works on iOS — same Unix ARM64 ABI as macOS
Static library output BuildTarget.libraryStatic exists since Scala Native 0.4.8
@exported FFI Works — provides C-compatible entry points callable from Swift/ObjC

Rust native libraries for iOS

All SGE Rust libraries can target iOS. Rust has Tier 2 support for iOS:

rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
cargo build --target aarch64-apple-ios --release

Per-library assessment:

Library iOS viable? Notes
Buffer ops / ETC1 Yes Pure Rust/C, platform-independent
GLFW No Desktop-only. Replace with SDL3 on iOS
miniaudio Yes Core Audio backend for iOS. Build with -DMA_NO_RUNTIME_LINKING, link -framework CoreFoundation -framework CoreAudio -framework AudioToolbox
FreeType Yes Well-established iOS cross-compilation. Pre-built xcframeworks available
Rapier2D / 3D Yes Pure Rust, no platform dependencies, no_std compatible

Distribution: Static libraries (.a) bundled into .xcframework format (Apple's standard for multi-architecture libraries).

multiarch-scala changes needed

New Platform entries

case object IosAarch64   extends Platform("ios-aarch64",     "aarch64-apple-ios")
case object IosSimArm64  extends Platform("ios-sim-aarch64", "aarch64-apple-ios-sim")
case object IosSimX86_64 extends Platform("ios-sim-x86_64",  "x86_64-apple-ios")

val ios: Seq[Platform] = Seq(IosAarch64, IosSimArm64, IosSimX86_64)

Note: Rust uses aarch64-apple-ios-sim while Scala Native uses aarch64-apple-ios-simulator — this discrepancy needs reconciliation in the Platform mapping.

NativeProviderPlugin for iOS

Same pattern as desktop Scala Native: provider JARs contain .a files + sn-provider.json manifest. iOS-specific link flags (e.g., -framework CoreAudio) go in the manifest.

iOS backend design (SGE-specific)

From ios-backend-feasibility.md, ~8 iOS-specific files needed:

Subsystem Desktop (GLFW) iOS Shared code?
Windowing/Lifecycle GLFW SDL3 (handles UIKit internally) No
GL rendering ANGLE (GL ES → desktop GL) ANGLE (GL ES → Metal) Yes — same GL20/GL30 code
Audio miniaudio miniaudio (Core Audio backend) Yes — same wrapper
Physics Rapier Rapier Yes — identical
Files POSIX filesystem NSBundle + Documents dir No
Input GLFW keyboard + mouse SDL3 touch + accelerometer No

ANGLE provides OpenGL ES → Metal translation on iOS, so SGE's entire GL rendering stack works unchanged. Metal is required — Apple deprecated OpenGL ES in iOS 12 (2018).

CI testing

iOS simulators run on macOS CI runners (macos-latest). The workflow:

  1. Build Scala Native static library for aarch64-apple-ios-simulator
  2. Build Xcode test project linking the .a file
  3. Run in simulator: xcodebuild test -destination 'platform=iOS Simulator,name=iPhone 15'

What we need from Scala Native maintainers

Concrete patches (estimated <500 lines total):

  1. Heap_getMemoryLimit(): Use os_proc_available_memory() on iOS
  2. time_nano.c: Add __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ check
  3. process_monitor.cpp: Use dispatch_semaphore_t on iOS or exclude process monitoring
  4. LinktimeInfo: Add isIOS property
  5. Discover.scala: iOS SDK path detection via xcrun --sdk iphoneos

The risk is that maintainers may not accept these without a full iOS CI matrix (they won't maintain what they can't test). We may need to offer CI resources or maintain a fork.

Comparison with Kotlin Native

Kotlin/Native has production-ready iOS support:

  • Official targets: iosArm64, iosSimulatorArm64, iosX64
  • Generates .framework bundles directly consumable by Xcode
  • Bi-directional Swift/ObjC interop
  • Custom tracing GC (replaced reference counting in 2023)
  • Hundreds of production apps in the App Store

Scala Native is years behind. The patches above would bring it to "functional with caveats" — not production-grade.

Comparison with LibGDX's approach

LibGDX uses MobiVM (community fork of discontinued RoboVM): Java bytecode → LLVM IR → native ARM binary. It works but has a very small maintainer pool and uses deprecated Apple APIs (GLKit, EAGLContext). The MetalANGLE backend partially addresses this.

SGE's Scala Native approach would be more maintainable long-term since it compiles directly to LLVM IR without an intermediate bytecode translation layer.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions