WebAssembly HelloWorld
了解一下 WebAssembly 的原理。
- x86 assembly 可查看 x86 Assembly on MacOS
- arm64 assembly 可查看 ARM Assembly on MacOS 和 ARM Assembly HelloWorld on MacOS
环境准备
- 安装 emscripten
- Mac 可直接通过 brew 来安装:
brew install emscripten
- Mac 可直接通过 brew 来安装:
- 安装 wabt The WebAssembly Binary Toolkit
- Mac 可直接使用
brew install wabt
来安装。
- Mac 可直接使用
Quick Start
新建 mian.c
文件
#include<stdio.h>
int main() {
printf("hello world\n");
}
Run it by NodeJS
# compile c-lang
$ emcc main.c
# run code by NodeJS
$ node a.out.js
hello, world!
Run it by Browser
# compile it & default html page
$ emcc main.c -O3 -o main.html
# start a static http server
$ npx serve .
# open it
$ open http://localhost:5000/main.html
最简单的 a + b 实现
参考:https://depth-first.com/articles/2019/10/16/compiling-c-to-webassembly-and-running-it-without-emscripten/
新建 main.c
int add (int first, int second)
{
return first + second;
}
新建 main.html
<!DOCTYPE html>
<html>
<head></head>
<body>
<script type="module">
(async() => {
const response = await fetch('main.wasm');
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes);
console.log('The answer is: ' + instance.exports.add(1, 2));
})();
</script>
</body>
</html>
# 如果报 `no available targets are compatible`,可以查看一下后面章节的常见问题
$ clang --target=wasm32 --no-standard-libraries -Wl,--export-all -Wl,--no-entry -o main.wasm main.c
# 启动静态资源服务器
$ npx serve .
# 打开页面
$ open http://localhost:5000/main.html
WebAssembly 二进制逐字解析
新建 main.wat
文件,实现一个最简单的 1 + 2 = 3
的程序,因为正常的打印输出涉及 import
, 所以,为简单起见,直接 return pop 最后一个元素了。
PS: start 函数是不支持返回值的,所以外部获取不到最终的 return 结果。
(module
(func
i32.const 1
i32.const 2
i32.add
return
)
(start 0)
)
使用 wat2wasm
编译上述文件,并查看最终的二进制文件。
$ wat2wasm main.wat
$ xxd -c 8 hello.wasm
00000000: 0061 736d 0100 0000 .asm....
00000008: 0104 0160 0000 0302 ...`....
00000010: 0100 0801 000a 0a01 ........
00000018: 0800 4101 4102 6a0f ..A.A.j.
00000020: 0b .
字节码与指令对应分析,参考 WebAssembly Opcodes, learning-webassembly-2-wasm-binary-format
地址 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
00000000 | 00 | 61 | 73 | 6d | 01 | 00 | 00 | 00 |
备注 | asm 文件类型标记 | 版本标记 | ||||||
00000008 | 01 | 04 | 01 | 06 | 00 | 00 | 03 | 02 |
备注 | 类型区块[1] | 总 4 字节[1] | 类型向量 1 个[2] | 类型为函数[3][4] | 0 个入参[4] | 0 个出参[4] | 函数区块[1][5] | 共 2 字节[1] |
00000010 | 01 | 00 | 08 | 01 | 00 | 0a | 0a | 01 |
备注 | 函数数量为1[2][5] | 函数编号为 0[5] | 开始区块[1][6] | 共 1 字节[1] | 调用函数编号为 0[6] | 代码区块[1] | 共 10 字节[1] | 代码向量个数为1[7] |
00000018 | 08 | 00 | 41 | 01 | 41 | 02 | 6a | 0f |
备注 | 代码共 8 字节[7] | 本地变量数量为 0[7] | i32.const 指令[8] | 值为 1 | i32.const 指令[8] | 值为 2 | i32.add 指令[8] | return 指令[8] |
00000020 | 0b | |||||||
备注 | end 指令[8] |
表格参考:
- https://webassembly.github.io/spec/core/binary/modules.html#sections
Each section consists of
- a one-byte section id,
- the u32 size of the contents, in bytes,
- the actual contents, whose structure is depended on the section id.
For most sections, the contents B encodes a vector.
每个区块由【区块 ID (即类型)】+ 【区块大小】 + 【区块内容】组成,区块内容在大多数情况下为向量。0x01 为类型、0x03 为函数、0x08 为开始、0x0a 为代码。
- https://webassembly.github.io/spec/core/binary/conventions.html#binary-vec
Vectors are encoded with their u32 length followed by the encoding of their element sequence.
每个向量由【向量个数】+ 【向量】组成
- https://webassembly.github.io/spec/core/appendix/index-types.html
类型为 0x60 为函数类型
- https://webassembly.github.io/spec/core/syntax/types.html#syntax-functype
Function types classify the signature of functions, mapping a vector of parameters to a vector of results.
函数类型,后续跟两个类型,分别为入参和出参。
- https://webassembly.github.io/spec/core/binary/modules.html#binary-funcsec
It decodes into a vector of type indices that represent the type fields of the functions in the funcs component of a module.
函数区块后面跟一个代码函数编码的向量。
- https://webassembly.github.io/spec/core/binary/modules.html#binary-startsec
The start section has the id 8. It decodes into an optional start function that represents the start component of a module.
开始区块,可以通过直接指定函数 id 编号来执行。
- https://webassembly.github.io/spec/core/binary/modules.html#code-section
They represent the locals and body field of the functions in the funcs component of a module. codedesc = vec(code) code = size + func func = locals + expr
由于没有本地变量,所以直接是函数个数说明,已经后续跟进的函数体的大小。
- https://webassembly.github.io/spec/core/appendix/index-instructions.html
表达式指令集
Debug on Browser
Chrome 已原生支持 WASM 调试,当上述代码执行之后,可以看到 stack 的最终 value 为 3,符合预期。
常见问题
- 执行 clang 的时候,报
no available targets are compatible
- 参考 172, 需要安装最新的 llvm:
brew install llvm
,而非 mac 自带的 clang 工具。
- 参考 172, 需要安装最新的 llvm:
参考
- https://wasmbyexample.dev/examples/hello-world/hello-world.c.en-us.html
- https://pengowray.github.io/wasm-ops/
- https://depth-first.com/articles/2019/10/16/compiling-c-to-webassembly-and-running-it-without-emscripten/
- https://developer.mozilla.org/en-US/docs/WebAssembly/Text_format_to_wasm
- https://github.com/webassembly/wabt
- https://webassembly.github.io/spec/core/binary/index.html#high-level-structure
- http://troubles.md/wasm-is-not-a-stack-machine/