通过 LLVM 在 Android 上运行 Swift 代码

oschina
 oschina
发布于 2015年10月15日
收藏 38

Swift 已经发布一年多了,苹果承诺将在 2015 年底开源 Swift。这是非常棒的一件事情,但是我们现在可以在 Android 设备上运行 Swift 吗?

Swift 编译器

这都是由 Chris Lattner 设计的,很容易就可以发现 Swift 的编译器是基于 LLVM 构建的。LLVM 是个编译器基础设施,利用了了一个可重定向编译器的有趣概念。

LLVM architecture

也就是说,不是生成特定架构的机器代码,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 个步骤:

  1. 让 Swift 编译器生成一些 LLVM-IR

  2. 使用 LLVM 从中间表示的代码生成 ARM ELF

  3. 使用 Android NDK 来生成一个二进制代码,连接到已生成的对象文件

1. 让 Swift 编译器生成一些 LLVM-IR

在之前的步骤中,当运行 swiftc hello.swift,Swift 编译器实际在干两件事情:

  1. 从 Swift 代码中生成 LLVM 中间表示代码

  2. 转换 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 应用中分享对象文件,然后运行:

We are computing the addition of 123 and 45. The result is 168. This computation was written in Swift and is running on this Android device!"

总结

这非常有趣,但是并没有什么用:

  • 一般来讲,NDK 只是对一小部分的应用有意义,所以情况的 Google 反对使用 NDK 编写整个 Android 应用。

  • 而且,因为我们缺失 SwiftCore 库,所以有了一定的限制,只适用于一小部分的 Swift 子集。

最后,很重要的一点,这个示例已经放到了 GitHub

via romain.goyet.com

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处,尊重他人劳动共创开源社区。
转载请注明:文章转载自 OSCHINA 社区 [http://www.oschina.net]
本文标题:通过 LLVM 在 Android 上运行 Swift 代码
加载中

最新评论(15

夲仒無道
夲仒無道

引用来自“幻想空”的评论

java开发安卓太依赖xml,使用ruby会不会好玩一点
然而你可以不用XML~
lux233
lux233
java开发安卓太依赖xml,使用ruby会不会好玩一点
TuWei
TuWei

引用来自“BinSys”的评论

然而并没有什么卵用~还得忍受着java和eclipse

引用来自“TuWei”的评论

什么叫忍受?

引用来自“BinSys”的评论

用过VS或者XCODE你就知道android开发体验是什么情况了。尤其是调试,JNI,JAVA
觉得挺好,最起码比xcode好用…
TuWei
TuWei

引用来自“BinSys”的评论

然而并没有什么卵用~还得忍受着java和eclipse

引用来自“TuWei”的评论

什么叫忍受?

引用来自“BinSys”的评论

用过VS或者XCODE你就知道android开发体验是什么情况了。尤其是调试,JNI,JAVA
我都用过…
BinSys
BinSys

引用来自“BinSys”的评论

然而并没有什么卵用~还得忍受着java和eclipse

引用来自“TuWei”的评论

什么叫忍受?
用过VS或者XCODE你就知道android开发体验是什么情况了。尤其是调试,JNI,JAVA
TuWei
TuWei

引用来自“BinSys”的评论

然而并没有什么卵用~还得忍受着java和eclipse
什么叫忍受?
从今以后
从今以后
现在就等swift 开源了~
百世经纶之傲笑红尘
百世经纶之傲笑红尘
有点趣,但有什么用吗
BinSys
BinSys
然而并没有什么卵用~还得忍受着java和eclipse
eechen
eechen
关于Clang/LLVM:
源代码->前端->优化->后端->机器码
Clang是C/C++的前端,用于生成统一格式的LLVM IR,然后经过LLVM优化后生成新的LLVM IR,最后由LLVM后端生成机器码.
这样做的优点是如果需要支持一种新的编程语言,那么我们只需要实现一种新的前端.
如果我们需要支持一种新的硬件设备,那我们只需要实现一个新的后端.
而优化阶段因为是针对统一的LLVM IR,所以它是一个通用的阶段,
不论是支持新的编程语言,还是支持新的硬件设备,这里都不需要对优化阶段做修改.
PHP Zend-JIT的概念验证用到了LLVM JIT.

Bytecode之于JVM,跟LLVM IR之于LLVM,有其相似性.
所以也有很多基于JVM的语言,比如Groovy,Scala等.
返回顶部
顶部