WJ-Yuan / Notes

My Tech Notes
https://wj-yuan.github.io/Notes/
0 stars 0 forks source link

[Note] 理解 wasm 格式 #17

Open WJ-Yuan opened 11 months ago

WJ-Yuan commented 11 months ago

理解 wasm 格式

S-表达式

不论是二进制还是文本格式,WebAssembly 代码中的基本单元是一个模块。在文本格式中,一个模块被表示为一个大的 S-表达式。

S-表达式 一个 () 代表一个模块,比如:

(module (memory 1) (func))

这个表达式中,表示一棵根节点为模块(module)的树,该树有两个孩子节点,分别是 属性为 1 的内存(memory)节点 和 一个函数(func)节点。

最简单的模块

(module)

向模块中添加功能

( func <signature> <locals> <body> )

签名

签名是由一系列参数类型声明,及其后面的返回值类型声明列表组成。 参数格式为 (param <类型>),返回值格式为 (result <类型>)

每一个参数都有一个显式声明的类型,wasm 当前有四个可用类型:

接受两个 32 位整数,返回一个 64 位浮点数的函数如下::

(func (param i32) (param i32) (result f64) ...)

局部变量和参数获取

局部变量和参数能够被函数体使用 local.getlocal.set 指令进行读写

(func (param i32) (param f32) (local f64)
  local.get 0
  local.get 1
  local.get 2)
  • local.get 0 会得到 i32 类型的参数
  • local.get 1 会得到 f32 类型的参数
  • local.get 2 会得到 f64 类型的局部变量

由于使用数字索引来指向某个条目容易让人混淆,因此,也可以通过别名的方式来访问它们,方法就是在类型声明的前面添加一个使用美元符号($)作为前缀的名字。

(func (param $p1 i32) (param $p2 f32) (local $loc i32) …)

函数体

(module
  (func (param $lhs i32) (param $rhs i32) (result i32)
    local.get $lhs
    local.get $rhs
    i32.add))

这个函数获取两个参数,然后相加,最后返回其结果。

命名函数

命名了一个 $add 函数

(func $add … )
导出函数
(export "add" (func $add))

这里的 add 是 JavaScript 中用来区别这个函数的名字,而$add 则是指出模块中的哪个 WebAssembly 函数将会被导出

(module
  (func $add (param $lhs i32) (param $rhs i32) (result i32)
    local.get $lhs
    local.get $rhs
    i32.add)
  (export "add" (func $add))
)

栈式机器

虽然浏览器把 wasm 编译为某种更高效的东西,但是,wasm 的执行是以栈式机器定义的。也就是说,其基本理念是每种类型的指令都是在栈上执行数值的入栈出栈操作。

例如,local.get 被定义为把它读到的局部变量值压入到栈上,然后 i32.add 从栈上取出两个 i32 类型值(它的含义是把前面压入栈上的两个值取出来)计算它们的和(以 2^32 求模),最后把结果压入栈上。

当函数被调用的时候,它是从一个空栈开始的。随着函数体指令的执行,栈会逐步填满和清空。例如,在执行了下面的函数之后:

(func (param $p i32)
  local.get $p
  local.get $p
  i32.add)

栈上只包含一个 i32 类型值——表达式 ($p + $p) 的结果,该结果是由 i32.add 得到的。函数的返回值就是栈上留下的那个最终值。

基本原则

在同一模块里的函数调用其他函数成员

(module
  (func $getAnswer (result i32)
    i32.const 42)
  (func (export "getAnswerPlus1") (result i32)
    call $getAnswer
    i32.const 1
    i32.add))

i32.const 只是定义一个 32 位整数并把它压入栈。

从 Javascript 中导入函数

(module
  (import "console" "log" (func $log (param i32)))
  (func (export "logIt")
    i32.const 13
    call $log))

我们要求从 console 模块导入 log 函数。导入的函数就像普通函数一样:它们拥有一个 WebAssembly 验证机制,会静态检查的签名,可以被设置一个索引,能够被命名和被调用。

var importObject = {
  console: {
    log: function (arg) {
      console.log(arg);
    },
  },
};

fetchAndInstantiate("logger.wasm", importObject).then(function (instance) {
  instance.exports.logIt();
});

.wat 转为 .wasm

  1. 安装 wabt
  2. 运行转化命令 wat2wasm simple.wat -o simple.wasm

查看 .wat

wat2wasm simple.wat -v