通过 LLVM 在 Android 上运行 Swift 代码
Swift 已经发布一年多了,苹果承诺将在 2015 年底开源 Swift。这是非常棒的一件事情,但是我们现在可以在 Android 设备上运行 Swift 吗?
Swift 编译器
这都是由 Chris Lattner 设计的,很容易就可以发现 Swift 的编译器是基于 LLVM 构建的。LLVM 是个编译器基础设施,利用了了一个可重定向编译器的有趣概念。
也就是说,不是生成特定架构的机器代码,LLVM 为一个虚拟机生成汇编代码,然后转换成中间代码,适配架构需要的实际代码。
模块化的设计非常的好,因为允许高度代码复用(前端和后端的共享优化)。更多关于 LLVM 的资料请看这里。
适配不同的机器
在这一点上,你可能会想:
如果 LLVM 已经够模块化,那么我们是否可以使用一个不同的后端,生成二进制代码,适配 OS X,iOS 或者是 Android?
假设是可以的,我们来看看如何实现。
手动构建 Swift 代码
如果使用 Xcode,系统会自动完成这些。我们现在需要手动编译和连接一个简单的 Swift "Hello world" :
// hello.swiftprint("Hello, world!");
构建对象文件:
$ $SDK/usr/bin/swiftc -emit-object hello.swift
hello.o
里面到底有什么:
$ nm hello_swift.o U __TFSSCfMSSFT21_builtinStringLiteralBp8byteSizeBw7isASCIIBi1__SS U __TFSs27_allocateUninitializedArrayurFBwTGSaq__Bp_ U __TFSs5printFTGSaP__9separatorSS10terminatorSS_T_ U __TIFSs5printFTGSaP__9separatorSS10terminatorSS_T_A0_ U __TIFSs5printFTGSaP__9separatorSS10terminatorSS_T_A1_ 0000000000000140 S __TMLP_ 0000000000000100 S __TMaP_ U __TMdSS U __TZvOSs7Process11_unsafeArgvGVSs20UnsafeMutablePointerGS0_VSs4Int8__ U __TZvOSs7Process5_argcVSs5Int32 U _globalinit_33_1BDF70FFC18749BAB495A73B459ED2F0_func6 U _globalinit_33_1BDF70FFC18749BAB495A73B459ED2F0_token6 0000000000000000 T _main U _swift_getExistentialTypeMetadata U _swift_once
看吧,这非常有趣。Swift mangles symbols 看起来明显有点像 C++。事实上,print
函数并没有成为 _print
symbol ,但是成为了更复杂的 symbol 的 __TFSs5printFTGSaP__9separatorSS10terminatorSS_T_
列表。
同时也要求其他 symbols,主要是为了处理字符串转换和内存处理。
无论如何,所有这些 symbols 已经在 libswiftCore.dylib
定义,也出现在 $SDK
。我们现在要把这些信息给 linker:
$ ld -arch x86_64 -o hello hello.o -L$SDK/usr/lib/swift/macosx -lSystem -lswiftCore $ DYLD_LIBRARY_PATH=$SDK/usr/lib/swift/macosx ./hello Hello, world!
是的,这个方法是可行的。
适配 Android
现在最大的问题是 SwiftCore 库缺失。现在苹果已经为 iOS,OS X 和 Watch OS 都提供了一个。但是,很明显,并没有提供 Android 版本。
但是,不是所有 Swift 代码都要求 SwiftCore 库,跟不是所有 C++ 代码都要求 STL 一样。所以只要使用 Swift 的子集,不需要 SwiftCore 的那部分,这问题就算解决了。
为了演示,我们先来一个简单的:
// add.swiftfunc addTwoNumbers(first: UInt8, second: UInt8) -> UInt8 { return first + second}
所以这过程基本分为 3 个步骤:
-
让 Swift 编译器生成一些 LLVM-IR
-
使用 LLVM 从中间表示的代码生成 ARM ELF
-
使用 Android NDK 来生成一个二进制代码,连接到已生成的对象文件
1. 让 Swift 编译器生成一些 LLVM-IR
在之前的步骤中,当运行 swiftc hello.swift
,Swift 编译器实际在干两件事情:
-
从 Swift 代码中生成 LLVM 中间表示代码
-
转换 IR 为一些 x86_64 机器代码,打包为一个 Mach-O 文件
这个实际上是非常常用的事例,所以编译器可以一次性做完这些。但是我们想要生成一些 ARM ELF 文件 (在 Android 上使用的二进制格式文件)。
$SDK/usr/bin/swiftc -parse-as-library # We don't need a "main" function -target armv7-apple-ios9.0 -emit-ir add.swift | grep -v "^!" # Filter-out iOS metadata > add.ll
注意:我们需要添加 "grep" 过滤器来移除一些 iOS 特定的元数据(Swift 编译器加进去的) 。
2. 从 LLVM-IR 中生成一个对象文件
在这点上,我们需要 Android NDK。非常幸运的是已经包括了一个 LLVM 工具链,我们可以利用 llc
(LLVM static compiler) :
$NDK/toolchains/llvm-3.5/prebuilt/darwin-x86_64/bin/llc -mtriple=armv7-none-linux-androideabi -filetype=obj add.ll
非常棒,所以我们已经构建了一个 ARM ELF 对象文件!
3. 打包一个 Android 应用的对象文件
我们需要从 Java 中调用它,所以需要一个 JNI bridge。这使用 C 来编写非常简单:
// jni-bridge.c// Let's work around Swift symbol mangling#define SWIFT_ADD _TF3add13addTwoNumbersFTVSs5UInt86secondS0__S0_uint8_t SWIFT_ADD(uint8_t, uint8_t);jstring jni_bridge(JNIEnv * env, jobject thiz ) { uint8_t a = 123; uint8_t b = 45; uint8_t c = SWIFT_ADD(a,b); char result[255]; sprintf(result, "The result is %d", c); return (*env)->NewStringUTF(env, result);}
最后,我们需要打包所有,变成一个共享库:
$NDK_GCC/bin/arm-linux-androideabi-ld add.o jni_bridge.o -shared # Build a shared library -lc # We'll need the libc -L$NDK/platforms/android-13/arch-arm/usr/lib
就是这样!我们需要打包,在一个 Android 应用中分享对象文件,然后运行:
总结
这非常有趣,但是并没有什么用:
-
一般来讲,NDK 只是对一小部分的应用有意义,所以情况的 Google 反对使用 NDK 编写整个 Android 应用。
-
而且,因为我们缺失 SwiftCore 库,所以有了一定的限制,只适用于一小部分的 Swift 子集。
最后,很重要的一点,这个示例已经放到了 GitHub。
via romain.goyet.com
来自:http://www.oschina.net/news/67082/running-swift-code-on-android