Can you compile R to run natively on an iPhone? Not through a server, not through WebAssembly, but as a real ARM64 binary linked into an iOS app?
We spent a few weeks exploring this. The short answer: yes, with caveats. This post documents what we found.
Cross-Compiling R for ARM64
R’s build system is autoconf-based, which means cross-compilation is supported but not exactly plug-and-play. The basic setup:
- Compiler: Xcode’s
clangtargeting the iOS SDK - Target:
arm64-apple-ios15.0(Simulator first, then device) - Fortran: R traditionally depends on Fortran for BLAS/LAPACK. iOS has no Fortran compiler. We replaced this entirely with Apple’s Accelerate framework, which provides highly optimized BLAS/LAPACK routines already built for iOS.
- Dependencies: zlib, libbz2, and iconv come from the iOS SDK. We cross-compiled liblzma and PCRE2 separately.
- Disabled: readline, X11, Tcl/Tk, Aqua, Java, and recommended packages
After patching a few configure checks and stubbing some system calls (more on that below), R compiles as a static library. You link it into an Objective-C or Swift app, set up R_HOME in the app’s sandbox, and call Rf_initEmbeddedR(). It starts. 1 + 1 returns 2. mean(rnorm(1000)) works. Matrix operations go through Accelerate and are fast.


Here’s a quick demo of the prototype in action:
What Had to Be Stubbed
iOS is not a general-purpose Unix. Several POSIX functions that R depends on are either missing or prohibited:
fork() and exec() are completely blocked. iOS apps run as a single process; spawning child processes is not allowed. This means:
system()doesn’t workmclapply()and the parallel package fall back to serial execution- Piped connections (
pipe()) fail
We stubbed these to return errors gracefully rather than crash. R handles the fallbacks reasonably well for interactive use.
Dynamic library loading is the bigger problem. R loads packages by calling dlopen() on .so files (shared objects) at runtime. On macOS this works fine. On iOS, dlopen() is restricted to system frameworks and libraries that ship with the app bundle. You cannot load arbitrary compiled code at runtime. This is a security policy enforced by the kernel, not something you can work around.
The dlopen() Problem
This is where the experiment hit a wall.
R’s package installation system assumes it can compile C/C++/Fortran source code into a shared library and load it with dlopen(). On a desktop, you run install.packages("dplyr"), R downloads the source, compiles it with your system compiler, and loads the resulting .so. On iOS, every step of that pipeline is blocked:
- No compiler on device. There’s no
gccorclangavailable at runtime. - No
dlopen()for user code. Even if you could compile a package, iOS won’t let you load it. - No
fork()/exec(). R’s build system shells out tomake, which requires process spawning.
Pure R packages (no compiled code) can still be installed at runtime, since they don’t need compilation or dlopen(). But most of the packages people actually want (the tidyverse, data.table, Rcpp, stringi, jsonlite) have native code.
The Pre-Compilation Workaround
The only path forward for native packages is to pre-compile them on a Mac, targeting iOS, and bundle the resulting static libraries with the app. We built a system for this with three tiers:
- Base packages (base, stats, graphics, utils, methods, etc.) bundled at build time as part of R itself.
- Popular packages (dplyr, ggplot2, tidyr, readr, etc.) pre-compiled as iOS static libraries and shipped in a downloadable cache.
- User packages (pure R only) installable at runtime via
install.packages().
This works, but the maintenance burden is significant. Every time R releases a new version, every pre-compiled package needs to be rebuilt. Package updates require a new cache build. And users can’t install any native package that isn’t already in the cache.
We got a working prototype with base R, a graphics device (rendering through Core Graphics with Retina support), and a handful of pre-compiled packages. But the package story was always going to be the bottleneck.
The Graphics Device
One thing that worked well: we built a custom R graphics device on top of iOS’s Core Graphics framework. It supports lines, rectangles, circles, polygons, text with full font access via Core Text, raster images with rotation, clipping, transparency, and Retina resolution. Plots render natively without any web view.
This was satisfying to build, and the output quality is excellent. But it doesn’t matter much if users can’t install ggplot2.
Where This Leaves Us
This was always an exploration, not a product plan. We wanted to know: can R run natively on iOS? The answer is yes, with real constraints.
The experiment proved a few things worth knowing:
- R’s autoconf build system can target iOS ARM64 with manageable patching.
- Apple’s Accelerate framework is a drop-in replacement for Fortran-based BLAS/LAPACK, and it’s fast.
- Core Graphics makes a solid backend for an R graphics device.
- Base R and pure-R packages work fine in the iOS sandbox.
But the dlopen() restriction is fundamental. It’s not a bug to work around; it’s an iOS security policy enforced at the kernel level. Any approach that relies on loading compiled package code at runtime is blocked. Pre-compiling packages is technically possible but creates a maintenance burden that scales poorly.
Next Up: webR
There’s another path to R on iOS that sidesteps dlopen() entirely: webR, which compiles R to WebAssembly. WebAssembly has its own module loading system that doesn’t touch iOS’s dynamic linking restrictions, and the webR repository already hosts hundreds of CRAN packages compiled to Wasm, maintained by George Stagg and others at Posit.
The trade-off is performance and memory (WebAssembly is slower than native and limited to ~300 MB), but for interactive use on a phone that may be exactly the right trade-off. We’re going to find out.