Embedding rustc on iOS


#1

Hi folks,

I’m trying to embed rustc in an iOS app (roughly to produce a “learn rust” IDE-like environment for iPad). Assuming that the rust compiler is fully embedded in the app, and that I only compile to WASM and execute the result in an embedded WebKit view, it ought to pass muster in AppStore review - but I’ll cross that bridge when I get to it.

In the meantime, I’m running into issues that affect progress:

  1. The compiler crates are only built as dynamiclib, so can’t be built into a staticlib suitable for embedding.
  2. The compiler won’t produce dynamiclibs for any *-ios targets.

I can trivially correct (1) by adding rlib targets to each compiler crate, as in https://github.com/rust-lang/rust/pull/55860. Alex doesn’t sound very positive on the topic of building these by default (and sadly cargo doesn’t provide target type overrides for dependencies).

There’s a possibility that I wouldn’t need to solve (1) if it weren’t for (2). I presume the *-ios targets refuse to build dylibs because Apple traditionally didn’t support dynamic linking on iOS, but to the best of my knowledge they relaxed that restriction after iOS 8, and dylibs can be linked if packaged as a codesigned embedded framework. I haven’t had a chance to test whether this would actually pass App Store review, and the internet has mixed opinions on the topic.

Regardless, I’d like to understand why the compiler crates are built as dylibs in the first place. Is this a space optimisation because multiple tools include the same crates? A startup-time optimisation?

How much overhead are we talking about if I were to introduce the rlib targets in addition? The intermediates should all be shared between the dynamiclib and the rlib, no?


#2

Regardless, I’d like to understand why the compiler crates are built as dylibs in the first place.

So that compiler plugins can be dynamically linked to rustc.

The intermediates should all be shared between the dynamiclib and the rlib, no?

Those are included in both. An rlib consists of those intermediate object files, while a dynamiclib consists of them linked together, so that would mean 2x size.


#3

Starting with iOS 8, you can build dynamic libraries into your application.


#4

Don’t those plugins eventually get linked together into rustc? Or are they also used in other binaries?

Indeed. However rustc refuses to generate dynamiclibs for ios targets:

error: cannot produce dylib for `..` as the target `x86_64-apple-ios` does not support these crate types

#5

These plugins get dlopened by rustc.


#6

All right, well, assuming that static linking is unlikely to be supported, what would be required to enable dylib generation on ios targets?


#7

My naive answer would be to modify the target spec for ios, which is defined in the rustc_target crate here. It looks like it sets a dynamic_linking flag to false there, so it could be as simple as changing that. I’m not sure if that will require other changes elsewhere, but that’s where i would start.


#8

That does seem to work, however there are several other interesting barriers along this path.

There doesn’t seem to be a supported mechanism for cross-compiling the compiler crates. There isn’t a technical problem with doing this - if I take a local path dependency on the compiler crates (and add #![feature(rustc_private)] to all of them), they compile just fine for the ios targets, but the x.py build system doesn’t seem to provide a way to actually build the compiler crates for any target except the host triple.

Since the entire dependency chain is dylibs in this scenario, I’m going to have to write a dynamic equivalent to the cargo lipo tool, to lipo up the multiple architectures of every single dependent dylib, and export them in a form suitable for XCode to consume/link.