之前介绍了WASM在生产环境中的部署方案,编译的过程留了个坑,由于后来LLVM和Emscripten都有了很多更新,这里讲一下最新的发展状况,以及手把手环境搭建指南。
标准发展
多数proposal仍然在开发中...... 这篇文章有详细介绍 《WebAssembly’s post-MVP future: A cartoon skill tree》。
总的来说,post-mvp时代还没有到来。
不过有一项post-mvp功能已经可以提前体验了,chrome70之后的版本已经默认开启了SharedArrayBuffer的支持(之前由于meltdown漏洞被默认关闭),并提供了 WebAssembly threads support 的实验性功能,相关工具并没有跟进,比可能需要自己动手体验这个接口 :)
工具栈发展
Emscripten
将完整的c/cpp桌面端项目编译成Web版本,并提供wasm版本的支持。
WASM社区目前最活跃的项目,虽然不是为WASM而设计的,也不是为模块化设计的,但是提供了目前最完整的toolchain,收到广泛支持。今年的更新中,着重优化了编译结果中胶水代码体积(之前饱受诟病的一点),可用性进一步提升。如果该项目今后能够更多的考虑到函数级以及模块级编译的需求,将大大提升WASM的易用性和普及速度。
AssemblyScript
快速发展的新兴项目,从之前的Binaryen/TypeScript编译器中发展出来。在TypeScript基础上进一步规范类型得到的一个语言子集。相关的工具栈已经非常成熟(相对于C/CPP工具栈),有可能成为接下来WASM的发展动力。
熟悉TypeScript的同学不妨尝试一下。在MVP阶段,C/CPP的很多底层性能优化特性起始用不上力,所以AssemblyScript的性能不见得比C差,而且不需要考虑标准库等麻烦的问题,与打包系统配合起来也很方便。
RUST
出于某种不明原因,RUST对WASM的支持可能是所有高级语言中最完整的(Mozilla血缘关系?)。toolchain完整,webpack支持良好,标准库也有成熟方案。观望。
LLVM/Clang
决定你能否在浏览器中跑C++的关键所在。
对WASM的支持在稳定发展,一部分功能已经加入正式版,新版本中安装和使用过程已经非常流畅,目前的遗留问题是标准库。如果你不介意不使用标准库或者跟着社区一起折腾标准库替代方案,该工具链目前已经可用。接下来会介绍如何搭建环境。
本地构建LLVM-WASM编译环境
由于LLVM对WASM的支持已经逐步稳定,社区中介绍的很多hack方案已经过时,例如:
- "wasm32-unknown-unknown-wasm"这个triple已经不再需要,直接target=wasm即可
- lld这个工具不再适用于wasm,应该使用专用的wasm-ld
- llc不再需要,因为clang已经支持了wasm
现在的编译过程已经非常简化:
- 按照官网标准过程编译LLVM,但要加入lld并开启其WASM实验性功能
- 用clang编译成
.o
- 用wasm-ld连接成
.wasm
你可以直接跑这个脚本来安装编译环境。
注意:
- 测试环境osx,编译过程需要至少25G硬盘
- 下载源码可能需要较长时间(10min),如果svn下载失败可以从git上的llvm-mirror下载
- make过程可能需要较长时间(10-30min),具体要看你的电脑性能以及核心数,注意修改make -j8这一行,使用你的核心数来并行编译,不然要等几个小时
# 建个项目目录
mkdir llvm-wasm
cd llvm-wasm
# src dir
# llvm 必选
svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm
# clang 必选
cd llvm/tools
svn co http://llvm.org/svn/llvm-project/cfe/trunk clang
cd ../..
# extra 必选
cd llvm/tools/clang/tools
svn co http://llvm.org/svn/llvm-project/clang-tools-extra/trunk extra
cd ../../../..
# RT 必选
cd llvm/projects
svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt
cd ../..
# libcxx ?必选?
cd llvm/projects
# 似乎会搞挂官网然后网络失败,反正这部分标准库连接不进去......
svn co http://llvm.org/svn/llvm-project/libcxx/trunk libcxx
cd ../..
# lld 必选,编译wasm-ld
cd llvm/tools
svn co http://llvm.org/svn/llvm-project/lld/trunk lld
cd ../..
# build dir
mkdir build
cd build
# cmake
cmake -G "Unix Makefiles" -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly ../llvm
# make 线程数写逻辑核数(如:4核8线程 -j8)
make -j8
使用
加入稳定支持后,使用方案就很简单了。
# 加入PATH
export PATH=当前目录/llvm/build/bin:$PATH
# 编译
clang -c -O3 --target=wasm32 test.cpp
# 连接
wasm-ld --no-entry --strip-all --allow-undefined --export-all test.o -o test.wasm
输入:
float max(float a, float b) {
return a > b ? a : b;
}
输出(wasm转换成可读的wat):
(module
(type $t0 (func))
(type $t1 (func (param f32 f32) (result f32)))
(func $__wasm_call_ctors (type $t0))
(func $_Z3maxff (type $t1) (param $p0 f32) (param $p1 f32) (result f32)
get_local $p0
get_local $p1
get_local $p0
get_local $p1
f32.gt
select)
(table $T0 1 1 anyfunc)
(memory $memory 2)
(global $g0 (mut i32) (i32.const 66560))
(global $__heap_base i32 (i32.const 66560))
(global $__data_end i32 (i32.const 1024))
(global $__dso_handle i32 (i32.const 1024))
(export "memory" (memory 0))
(export "__wasm_call_ctors" (func $__wasm_call_ctors))
(export "__heap_base" (global 1))
(export "__data_end" (global 2))
(export "__dso_handle" (global 3))
(export "_Z3maxff" (func $_Z3maxff)))
注意由于使用了-O3优化,最后暴露出的函数名被加上了前后缀,代表其传入传出参数类型,调用的时候要注意加上。