参考
https://developer.mozilla.org/zh-CN/docs/WebAssembly (快速学习)
https://emscripten.org/docs/getting_started/index.html (深入学习)
背景
WebAssembly 是一种新的编码方式,可以运行在主流的js引擎中 - 它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并且可以和js一起运行。
目前可以通过很多工具将c++转化为wasm和javascript api,主流工具为emscripten,其次还有assemblyscript,ruby等语音也提供了编译为wasm的方式。
WebAssembly概念
- 模块:表示一个已经被浏览器编译为可执行机器码的 WebAssembly 二进制代码。相当于代码在运行时装载二进制数据的一个容器。
- 内存:ArrayBuffer,大小可变。本质上是连续的字节数组,WebAssembly 的低级内存存取指令可以对它进行读写操作。
- 表格:带类型数组,大小可变。表格中的项存储了不能作为原始字节存储在内存里的对象的引用
- 实例:一个模块及其在运行时使用的所有状态,包括内存、表格和一系列导入值。
上述的这些概念对应了js中可以调用的js api(Module,Memory,Table,Instance ),这些js api的调用将被封装在JS “glue” code中,如果我们使用emscripten工具去编译,那么上述的概念对于我们来说是黑盒的。
安装和编译
安装
1 | git clone https://github.com/juj/emsdk.git |
编译
demo.cpp
1 |
|
EMSCRIPTEN_KEEPALIVE 是为了导出方法,也可以在编译命令中添加 -s EXPORTED_FUNCTIONS=_hello,_hello2
1 | emcc demo.cpp -s WASM=1 -o demo.html |
运行 demo.html
此时直接运行demo.html 即可运行webassembly,我们也可以在js中直接调用。
test.js
1 | let myModule = require('./demo.js') |
运行test.js
目前我们是调用了emscripten提供的demo.js (js glue code),我们还可以通过webassembly提供的api调用。
test2.js
1 | const path = require('path') |
其他
c++中 直接调用js
1
emscripten_run_script("alert('hi')");
1
2
3EM_ASM({
console.log('I received: ' + $0);
}, 100);编译时使用 -sMODULARIZE,require()模块返回一个可以实例化编译代码的工厂函数
1
2
3
4
5
6
7var factory = require('./api_example.js');
factory().then((instance) => {
instance._sayHi(); // direct calling works
instance.ccall("sayHi"); // using ccall etc. also work
console.log(instance._daysInWeek()); // values can be returned, etc.
});WAT格式
工具
wasm2wat
1
wasm2wat demo.wasm -o demo.wat
wat2wasm
1
wat2wasm demo.wat -o demo2.wasm
s-表达式
(module (func (param) (result) (locals) (body) ))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17(module
(import "console" "log" (func $log (param i32))) ;;导入js
(func (export "add") (param $lhs i32) (param $rhs i32) (result i32) ;;导出func
local.get $lhs ;;读取第一个参数$lhs
local.get $rhs ;;读取第二个参数$rhs
i32.add) ;;上面两个数据出栈,相加值入栈
(func $getAnswer (result i32) ;; getAnswer方法
i32.const 20) ;; 入栈20,
(func (export "getAnswerPlus1") (result i32)
call $getAnswer
i32.const 1
i32.add)
(func (export "log")
i32.const 123
call $log) ;;调用js中的log
)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20const path = require('path')
const fs = require('fs')
const imports = {
console: {
log: function log(arg) {
console.log(arg)
}
}
}
const filePath = path.resolve(__dirname, 'demo.wasm')
const buffer = fs.readFileSync(filePath)
WebAssembly.instantiate(buffer,imports).then((results) => {
console.log(results.instance.exports.add(2, 2))
console.log(results.instance.exports.getAnswerPlus1())
console.log(results.instance.exports.log())
})Memory wat
1
2
3
4
5
6
7
8(module
(import "console" "log" (func $log (param i32 i32)))
(import "js" "mem" (memory 1))
(data (i32.const 0) "Hi")
(func (export "writeHi")
i32.const 0 ;; offset,从0开始读取,这个设置log的第一个参数为1
i32.const 2 ;; length ,因为Hi字符串的长度为2,这里设置log的第二个参数为2
call $log))1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20var memory = new WebAssembly.Memory({initial: 1}); //1页内存为64kb
var importObj = {
console:
{
log: function consoleLogString(offset, length) {
var bytes = new Uint8Array(memory.buffer, offset, length);
var string = new TextDecoder('utf8').decode(bytes);
console.log(string);
}
},
js: {
mem: memory
}
};
const filePath = path.resolve(__dirname, 'memory.wasm')
const buffer = fs.readFileSync(filePath)
WebAssembly.instantiate(buffer,importObj).then((results) => {
results.instance.exports.writeHi();
});Table wat
导出表格,直接访问
1
2
3
4
5
6
7
8
9(module
(table $table 2 funcref)
(elem (i32.const 0) $f1 $f2)
(func $f1 (result i32)
i32.const 42)
(func $f2 (result i32)
i32.const 13)
(export "tbl" (table $table)) ;; 导出表格
)1
2
3
4
5const filePath = path.resolve(__dirname, 'table.wasm')
const buffer = fs.readFileSync(filePath)
WebAssembly.instantiate(buffer).then((results) => {
console.log(results.instance.exports.tbl.get(0)())
});通过函数访问表格
1
2
3
4
5
6
7
8
9
10
11
12
13(module
(table 2 funcref)
(func $f1 (result i32)
i32.const 42)
(func $f2 (result i32)
i32.const 13)
(elem (i32.const 0) $f1 $f2)
(type $return_i32 (func (result i32)))
(func (export "callByIndex") (param $i i32) (result i32) ;;导出函数,通过index来调用表格
local.get $i
call_indirect (type $return_i32)
)
)1
2
3
4
5
6const filePath = path.resolve(__dirname, 'table2.wasm')
const buffer = fs.readFileSync(filePath)
WebAssembly.instantiate(buffer).then((results) => {
console.log(results.instance.exports.callByIndex(0))
console.log(results.instance.exports.callByIndex(1))
});
- wasm 只支持4种数据类型传递:i32,i64,f32,f64;其他数据类型使用memory传递
- 一个内存和表格可以被多个模块实例使用;js中定义内存和表格,初始化多个模块时传入同一个内存和表格,这样多个模块可以相关通信;相当于动态链接。
JSC webassembly
编译支持 webassembly的jsc引擎
1 | git clone git@github.com:react-native-community/jsc-android-buildscripts.git |
安装工具链
coreutils,java-8,ndk-r19c,svn,ruby,gperf,python等
下载
1 | npm run download |
jsc.sh 改动
编译
1 | npm run start |