0%

WebAssembly分享

参考

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的方式。

emscripten-diagram.png

WebAssembly概念

  • 模块:表示一个已经被浏览器编译为可执行机器码的 WebAssembly 二进制代码。相当于代码在运行时装载二进制数据的一个容器。
  • 内存:ArrayBuffer,大小可变。本质上是连续的字节数组,WebAssembly 的低级内存存取指令可以对它进行读写操作。
  • 表格:带类型数组,大小可变。表格中的项存储了不能作为原始字节存储在内存里的对象的引用
  • 实例:一个模块及其在运行时使用的所有状态,包括内存、表格和一系列导入值。

上述的这些概念对应了js中可以调用的js api(Module,Memory,Table,Instance),这些js api的调用将被封装在JS “glue” code中,如果我们使用emscripten工具去编译,那么上述的概念对于我们来说是黑盒的。

安装和编译

安装

1
2
3
4
5
git clone https://github.com/juj/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh //导出环境变量

编译

demo.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <iostream>
#include <emscripten/emscripten.h>

int main(int argc, char ** argv) {
printf("Hello World\n");
}

extern "C" {
std::string EMSCRIPTEN_KEEPALIVE hello() {
std::string str("songpengfei");
return str;
}

int EMSCRIPTEN_KEEPALIVE hello2() { //EMSCRIPTEN_KEEPALIVE 目的是导出方法
return 234;
}
}

EMSCRIPTEN_KEEPALIVE 是为了导出方法,也可以在编译命令中添加 -s EXPORTED_FUNCTIONS=_hello,_hello2

1
emcc demo.cpp -s WASM=1 -o demo.html

运行 demo.html
image.png
此时直接运行demo.html 即可运行webassembly,我们也可以在js中直接调用。
test.js

1
2
3
4
5
let myModule = require('./demo.js')

myModule.onRuntimeInitialized= function () {
console.log(myModule._hello2())
}

运行test.js
image.png
目前我们是调用了emscripten提供的demo.js (js glue code),我们还可以通过webassembly提供的api调用。
test2.js

1
2
3
4
5
6
7
8
const path = require('path')
const fs = require('fs')
const filePath = path.resolve(__dirname, 'demo.wasm')
const buffer = fs.readFileSync(filePath)
const source = new Int8Array(buffer)
const module1 = new WebAssembly.Module(source);
const ins = new WebAssembly.Instance(module1);
console.log(ins.exports.hello2());

其他

  1. c++中 直接调用js

    1
    emscripten_run_script("alert('hi')");
    1
    2
    3
    EM_ASM({
    console.log('I received: ' + $0);
    }, 100);
  2. 编译时使用 -sMODULARIZE,require()模块返回一个可以实例化编译代码的工厂函数

    1
    2
    3
    4
    5
    6
    7
    var 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
    20
    const 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
    20
    var 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
    5
    const 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
    6
    const 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 改动
image.png
编译

1
npm run start