阅读 629

深入浅出WebAssembly(2) Basic API

这系列主要是我对WASM研究的笔记,可能内容比较简略。总共包括:

  1. 深入浅出WebAssembly(1) Compilation
  2. 深入浅出WebAssembly(2) Basic Api
  3. 深入浅出WebAssembly(3) Instructions
  4. 深入浅出WebAssembly(4) Validation
  5. 深入浅出WebAssembly(5) Memory
  6. 深入浅出WebAssembly(6) Binary Format
  7. 深入浅出WebAssembly(7) Future
  8. 深入浅出WebAssembly(8) Wasm in Rust(TODO)

API总览

//async version
WebAssembly.compile(bufferSource: ArrayBuffer): Promise<WebAssembly.Module>
WebAssembly.instantiate(bufferSource: ArrayBuffer, importObj?: any): Promise<{module: WebAssembly.Module, instance: WebAssembly.Instance}>
WebAssembly.instantiate(module: WebAssembly.Module, importObj?: any): Promise<WebAssembly.Instance>
WebAssembly.compileStreaming(source: Promise<Responce>): Promise<WebAssembly.Module> // wasm 请求头:Context-type: application/wasm

//sync version
new WebAssembly.Module(bufferSource: ArrayBuffer)
new WebAssembly.Instance(module: WebAssembly.Module, importObj?: any)

// helper
WebAssembly.Module.customSections(module:WebAssembly.Module, sectionName: string): ArrayBuffer[]
WebAssembly.Module.exports(module: WebAssembly.Module): { name: string, kind: "function|table|memory|global" }][]
WebAssembly.Module.imports(module: WebAssembly.Module): { module: string, name: string, kind: "function|table|memory|global" }][]

// validation and error
WebAssembly.validate(bufferSource: ArrayBuffer):boolean

interface CommonError {
  message: string
  filename: string
  lineNumber: number
}
new WebAssembly.CompileError(message: string, fileName: string, lineNumber: number) // 解码,验证阶段
new WebAssembly.LinkError(message: string, fileName: string, lineNumber: number)  // 实例化阶段
new WebAssembly.RuntimeError(message: string, fileName: string, lineNumber: number) // 运行阶段
复制代码

如何初始化一个模块?

Async way:

fetch('./index.wasm').then(response =>
  response.arrayBuffer()
).then(bytes => WebAssembly.instantiate(bytes, {
  env: {
    yyy: xxx
  }
})).then(wasm => {
  const { module, instance } = wasm;
}).catch(console.error);
复制代码

Streaming way:

const source = fetch('./index.wasm')
WebAssembly.compileStreaming(source).then(module =>
  WebAssembly.instantiate(module, {
    env: {
      xxx: yyy
    }
  })
).then(instance => {
  //xxx
}).catch(console.error);
复制代码

Sync way:

const source = fetch('./index.wasm')
WebAssembly.compileStreaming(source).then(module =>
  WebAssembly.instantiate(module, {
    env: {
      xxx: yyy
    }
  })
).then(instance => {
  //xxx
}).catch(console.error);
复制代码

最好的方式是通过 WebAssembly.compileStreaming 的方式来加载。可以对wasm模块提前进行编译验证。

TypedArray

JavaScript类型化数组是一种类似数组的对象,并提供了一种用于访问原始二进制数据的机制。 正如你可能已经知道,Array 存储的对象能动态增多和减少,并且可以存储任何JavaScript值。JavaScript引擎会做一些内部优化,以便对数组的操作可以很快。然而,随着Web应用程序变得越来越强大,尤其一些新增加的功能例如:音频视频编辑,访问WebSockets的原始数据等,很明显有些时候如果使用JavaScript代码可以快速方便地通过类型化数组来操作原始的二进制数据将会非常有帮助。 但是,不要把类型化数组与正常数组混淆,因为在类型数组上调用 Array.isArray() 会返回false。此外,并不是所有可用于正常数组的方法都能被类型化数组所支持(如 pushpop)。

CustomSections

Custom Sections — WebAssembly 1.0

custom section 是section id为零的段,不唯一。目前只实现了 name section

Memory

wasm-ld 有关memory的参数:(WebAssembly lld port — lld 10 documentation

--import-memory

    Import memory from the environment.

--initial-memory=<value>

    Initial size of the linear memory. Default: static data size.

--max-memory=<value>
   
    Maximum size of the linear memory. Default: unlimited.
复制代码

一般来说不需要限制,但是在某些安全场景下可以在js或者wasm里面设置。

WebAssembly.Memory

编译的时候启用--import-memory,然后在js中构造一个memory对象通过env传给wasm模块。

const memory = new WebAssembly.Memory({
  initial: 10,
  maxium: 100,
});

fetch('../a.wasm').then(buffer => WebAssembly.instantiate(buffer, {env: { memory }})).then((wasm) => {
  console.log(WebAssembly.imports(wasm.module));
})
复制代码

实际上不一定需要通过env传递,只是llvm编译器如此指定的,可以传如任意对象,然后手动修改wasm模块(github.com/mdn/webasse…

memory单位是page,pageSize为64KB, 初始内存不够的时候可以调用memory.grow(n)来增大,但总共不能超过最大内存maxium, maxium设置之后不可更改(同stack)。

wasm指令

  1. Instructions — WebAssembly 1.0
  2. Clang: clang/BuiltinsWebAssembly.def at 86d4513d3e0daa4d5a29b0b1de7c854ca15f9fe5 · microsoft/clang · GitHub 这些__builtin_开头的符号其实是一些编译器内置的函数或者编译优化处理开关等,其作用类似于宏。宏是高级语言用于预编译时进行替换的源代码块,而内置函数则是用于在编译阶段进行替换的机器指令块。因此编译器的这些内置函数其实并不是真实的函数,而只是一段指令块,起到编译时的内联功能。
  3. Rust: stdarch/memory.rs at ef6b0690192f1cfc753af698695c2ecde0c7b991 · rust-lang/stdarch · GitHub

Table

A table is an array of opaque values of a particular element type. It allows programs to select such values indirectly through a dynamic index operand. Currently, the only available element type is an untyped function reference. Thereby, a program can call functions indirectly through a dynamic index into a table. For example, this allows emulating function pointers by way of table indices.

js下,WebAssembly.Table 代表着wasm模块中的表结构实体。

const table = new WebAssembly.Table({
  initial: 2,  //初始时可储存的表项的数量
  element: "anyfunc",
  maxium: 10, //可选
});

table.length // 2
table.get(0) // null
table.grow(n) 
table.set(index: number, value: elem) // 只能设置类型为anyfunc的表项
复制代码

Exmaple

(module
    (import "js" "tbl" (table 2 anyfunc))
    (func $f42 (result i32) i32.const 42)
    (func $f83 (result i32) i32.const 83)
    (elem (i32.const 0) $f42 $f83)
)
复制代码
var tbl = new WebAssembly.Table({initial:2, element:"anyfunc"});
      console.log(tbl.length);
      console.log(tbl.get(0));
      console.log(tbl.get(1));
      var importObject = {
        js: {
          tbl:tbl
        }
      };
      WebAssembly.instantiateStreaming(fetch('table2.wasm'), importObject)
      .then(function(obj) {
        console.log(tbl.length);
        console.log(tbl.get(0)()); // 42
        console.log(tbl.get(1)()); // 83
      });
复制代码
文章分类
前端
文章标签