Skip to main content

WASM-入门

wasm本职是二进制文件,编译好的字节码(接近机器码可以快速转换)

特点

  • 高效
    • WebAssembly有一套完整的语义,实际上wasm是体积小妾加载快的二进制格式,其目标就是充分发挥硬件的能力达到原生执行效率
  • 安全
    • WebAssembly运行在沙箱环境中,甚至可以在现有的JavaScript虚拟机中运行。在Web环境中WebAssembly将会严格遵守同源策略。
  • 开发
    • WebAssembly设计了一个非常规整的文本格式用来调试、测试、实验、优化、教学或者编写教程。可以以这种文本格式在web页面上查看wasm模块的源码
  • 标准
    • WebAssembly在web中被设计成无版本特性可测试、向后兼容的。WebAssembly可以被JavaScript调用,进入JavaScript上下文,可以像Web API一样调用浏览器的功能。当然,WebAssembly不仅可以运行在浏览器上,也可以运行在非web环境下。

Locale Dropdown

Locale Dropdown

编译wasm的工具

  • AssemblyScript:支持直接将Typescript编译成WebAssembly。这对于前端来说,入门的门槛很低的。
  • Emscripten:可以说是WebAssembly的灵魂工具,将其他高级语言,编译成WebAssembly。
  • WABT:将WebAssembly在字节码和文本格式互相转换的一个工具,方便开发者去理解这个wasm到底在做什么事。

性能对比

WebAssembly版本和原生JavaScript版本的递归无优化的(裴波那契数列)Fibonacci函数,下图是这两个函数的值是45、48、50的时候的性能对比。

Locale Dropdown

JavaScript API

方法

  • WebAssembly.compile()
  • WebAssembly.instantiate()
  • WebAssembly.validate()

  • WebAssembly.Module
  • WebAssembly.Instance
  • WebAssembly.Memory
  • WebAssembly.Table
  • WebAssembly.CompileError
  • WebAssembly.LinkError
  • WebAssembly.RuntimeError

具体流程

wasm
// WebAssembly.compile() 装载 wasm
Promise<WebAssembly.Module> WebAssembly.compile(bufferSource);

// WebAssembly.Module 转换为 module
var myModule = new WebAssembly.Module(bufferSource);

// WebAssembly.instantiate() 实例化
Promise<ResultObject> WebAssembly.instantiate(bufferSource, importObj) // ResultObject:{module, instance}


Promise<WebAssembly.Instance> WebAssembly.instantiate(module, importObject);

// WebAssembly.Instance
var myInstance = new WebAssembly.Instance(module, importObject);

// WebAssembly.validate() 验证

// WebAssembly.validate(bufferSource); // boolean

// WebAssembly.Memory 可用于 JavaScript 和 WebAssembly 的数据共享(不在 v8 中,自己的内存回收体系)
// 可以使用JS创建,传递给 WASM
// 可以在WASM里创建,使用JS获取

// WebAssembly.Table 可以用来在JS对象上存放WASM函数的引用

// 错误
// WebAssembly.CompileError
// WebAssembly.LinkError
// WebAssembly.RuntimeError

使用

import module from 'test.wasm'js只能通过请求发起。webpack5支持导入

const importObject = {
func:{
log:(num) => document.write(num)
}
}

fetch('test.wasm')
.then(response => response.arrayBuffer())
.then(types => WebAsembly.instantiate(byte, importObject))
.then(({instance}) => window.test = instance.exports.test)

执行流程

过程如下:

Javascript -> WebAssembly(test) -> Javascript(document.write())

参考链接

构建wasm 方式

建议弃坑 使用WABT来编译S-表达式

推荐编译工具 Emscripten

在线方式 WasmFiddle

WebAssembly 中文网

Emscripten

使用emsdk构建参考文档

# mac 平台直接执行可执行文件  window 自行百度 or ./emsdk.bat
./emsdk install latest
./emsdk activate latest

配置环境变量:

# mac 平台, window 平台自行体会 emsdk_env.bat
source ./emsdk_env.sh

编译方式

1、可执行文件

gcc hello.c -o hello
# 执行
./hello

2、编译成wasm在nodejs中执行


emcc hello.c -o hello_node.js

# 生成两个文件
=> hello_node.wasm
=> hello_node.js

# 执行
node hello_node.js

3、编译成wasm在浏览器中执行

# 其中 -s WASM=1 是制定要wasm文件; -O3 优化选项 ,优化选项中优化度最低是O1,优化度最高是O3,最好推荐 O1,避免意外优化
emcc hello.c -s WASM=1 -O3 -o hello_html.html

# 生成三个文件
=> hello_html.wasm
=> hello_html.js
=> hello_html.html

# 执行
# 在浏览器中执行,注意,生成的文件要放在web服务器中然后通过 web 访问,不能直接从硬盘访问

测试 Math.c 文件

参考链接

Q & A

math.c / mathcpp.cpp

int add( int a, int b)
{
return a + b;
}

int reducer(int a, int b) {
return a - b;
}

int square(int a)
{
return a * a;
}

sh

# math.c
emcc math.c -Os -s WASM=1 -s SIDE_MODULE=1 -o math.wasm
# mathcpp.cpp
emcc math.cpp -Os -s WASM=1 -s SIDE_MODULE=1 -o mathcpp.wasm

html

<script>
/**
* @param {String} path wasm 文件路径
* @param {Object} imports 传递到 wasm 代码中的变量
*/
function loadWebAssembly(path, imports = {}) {
return fetch(path) // 加载文件
.then(response => response.arrayBuffer()) // 转成 ArrayBuffer
.then(buffer => WebAssembly.compile(buffer))
.then(module => {
imports.env = imports.env || {}

// 开辟内存空间
imports.env.memoryBase = imports.env.memoryBase || 0
if (!imports.env.memory) {
imports.env.memory = new WebAssembly.Memory({ initial: 256 })
}

// 创建变量映射表
imports.env.tableBase = imports.env.tableBase || 0
if (!imports.env.table) {
// 在 MVP 版本中 element 只能是 "anyfunc"
imports.env.table = new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
}
console.log(module)
// 创建 WebAssembly 实例
return new WebAssembly.Instance(module, imports)
})
}
//调用
loadWebAssembly('./mathcpp.wasm')
.then(instance => {
console.log(instance.exports)
// const add = instance.exports.add // 取出cpp里面的方法
// const square = instance.exports.square // 取出cpp里面的方法
// const reducer = instance.exports.reducer

// console.log('10 + 20 =', add(10, 20))
// console.log('3*3 =', square(3))
// console.log('10-5 =', reducer(10, 5))
// console.log('(2 + 5)*2 =', square(add(2 + 5)))
})

</script>