WebAssembly简介
下面是WebAssembly官方文档给的说明:
WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.
大致意思就是"WebAssembly是一个基于栈的二进制指令格式,被设计为编程语言可移植的编译目标,支持部署到web客户端和服务端应用",也就是WebAssembly通常被当作其他编程语言的编译目标 上面的介绍比较太宽泛,下面是WebAssembly核心规范给的定义:
WebAssembly (abbreviated Wasm ) is a safe, portable, low-level code format designed for efficient execution and compact representation. Its main goal is to enable high performance applications on the Web, but it does not make any Web-specific assumptions or provide Web-specific features, so it can be employed in other environments as well.
大致意思就是"WebAssembly(简称为Wasm)是一个安全、可移植的低级代码格式,被设计成高效执行和紧凑表示。它的主要目标是在Web开启高性能应用,但是不做任何特定于Web的假设或提供特定Web的特性,因此它可以也可以部署在其他环境" 两个定义其实都说明了WebAssembly的一些特点,第一个是从广义的角度来讲,第二个是从核心规范的角度来讲
发展历程
WebAssembly的初衷是为了提升Web应用的性能,但是在初版发布之后,WebAssembly在开发者社区越来越流行;因此WebAssembly的潜在价值开始向其他领域蔓延,如云原生、AI以及区块链等。以下是一些关键节点:
- 2015年,Mozilla发布一种新型的二进制代码格式"WebAssembly"
- 2017年,FireFox、Chrome、Edge和Webkit四大浏览器厂商在WebAssembly MVP(最小可用版本)标准的设计达成共识;同年,WebAssembly Working Group(简称WWG)成立,标识着WebAssembly 成为 W3C 标准技术体系的一部分
- 2019年12月,宣布 WebAssembly 成为第 4 种 Web 语言;Bytecode Alliance 字节码联盟宣布正式成立
- 2022 年, WebAssembly 2.0 草案正式发布,WebAssembly 2.0 草案中加入了很多值得关注的新特性,比如引用类型(Reference Types)、固定宽度的 SIMD(Fixed-width SIMD)、批量内存操作(Bulk Memory Operations)
规范相关
WebAssembly涉及的内容主要由以下几部分组成:
- 核心规范:定义了独立于具体嵌入环境的WebAssembly模块语义,可以理解为Wasm的语义规范
- 嵌入的接口:由三部分组成:
- JavaScript API:定义从JavaScript访问WebAssembly的类和对象,包括用于验证、编译和实例化的方法,以及导入作为操作和代表、导出作为JavaScript对象的类。
- Web API:定义可用于特定浏览器的JavaScript API的扩展,如使用请求的
Response
作为流式编译和实例化的接口 - WASI API:定义了模块化的系统接口,用于Web外运行WebAssembly,包括文件系统、网络连接、锁和随机数
- 工具:相关工具,如连接方案、调试信息和语言ABI等
- 原始设计文档:WebAssembly的设计、目标和高级概述
特性相关的一些文档/链接:
WebAssembly有什么优势
- 快速、高效 :WebAssembly是静态强类型语言 ,利用常见的硬件能力,可以在不同平台上接近原生的速度运行(我理解的接近原生速度运行是使用C/C++代码编写,编译为Wasm后性能接近C/C++,也就是和原始语言的性能接近)
- 跨平台、可移植:WebAssembly是一个可移植、体积小,并且与语言和平台无关的二进制格式,开发者可以使用各种自身熟悉的语言开发,生成的wasm作为平台无关的发布形式
- 安全:WebAssembly被限制在一个安全的沙箱环境中运行,一方面可以防止恶意代码攻击,另一方面可以保证数据安全
- 标准化 :WebAssembly开放标准是由W3C社区组(包括所有主流浏览的代表)和W3C工作组一同制订的
- 灵活的开发语言:WebAssembly制定了标准化的中间指令格式,开发可以使用各自熟悉或喜欢的开发语言进行开发,如C/C++、Java/Kotlin、TypeScript和Rust等
以上这些优势其实也是WebAseembly的设计目标,WebAssembly的设计目标详情可以查看WebAssembly核心规范(设计目标) 上面的优势看起来比较官方,不那么直观,用我的理解通俗一点的解释一下:
- 使用支持编译为WebAssembly的语言写的代码可以跨平台运行,目前为止大部分主流语言基本都支持编译为WebAssembly,一套代码在Window、PC、Linux、Android、iPhone、物联网终端运行将成为可能
- 可以带来更好的性能,如使用Rust编写的代码编译为Wasm后将会有将近Rust的性能,这对于Web应用来说是质的飞跃
现状
支持编译为Wasm语言
主流语言C/C++、Go、Rust、TypeScript(AssemblyScript)、Java、Python、.Net/C#等等,完整的支持列表可见awesome-wasm-langs 目前支持情况最好的几个语言是:
- Rust
- C/C++
- AsssemblyScript
- Blazor
- Python
- Go
以上一些语言的详细信息可以查看【字节跳动】WebAssembly常用开发语言和工具链 针对开发WebAssembly应用程序的常用语言,有机构进行了相关的调查,近两年的调查结果如下: 相关资料 :The State of WebAssembly 2021
相关资料 :The State of WebAssembly 2022
编译器
常见的编译器有如下几个:
- Emscripten:可以将C/C++代码编译为WebAssembly
- wasi-sdk:提供了一个基于LLVM更为纯净将C/C++编译为WebAssembly的编译器,该项目主要仅提供WASI库,其他编译功能主要基于LLVM
- TingGo:基于LLVM将Go编译为WebAssembly的轻量编译器,可以在嵌入式场景使用
- wasm-pack:将Rust代码编译WebAssembly并打包成npm模块
- wasm-bindgen:一个Rust工具,用来将JavaScript能力导入到Rust或将Rust能力到处到JavaScript(这不算一个编译器,但在Rust生态经常和wasm-pack一起使用)
- AssemblyScript:将类TypeScript的AssemblyScript代码编译为WebAssembly的编译器
- Binaryen:与语言无关的一个通用的WebAssembly优化器
- 等等
详细信息可以查看【字节跳动】WebAssembly常用开发语言和工具链
引擎
有关于引擎的详细信息可以查看【字节跳动】WebAssembly 常见引擎简介
wasmtime
仓库地址 :github.com/bytecodeall... 是Bytecode Alliance(字节码联盟)推出的拳头项目之一,wasmtime对于WebAssembly相关标准支持的完善度非常高。它支持了标准的WASI(WebAssembly System Interface),并紧密跟踪WebAssembly核心特性。目前 Fixed-Width SIMD、Reference Types、Bulk Memory operations成熟提案, 以及Tail-Call、Threads 和 Garbage Collection 等还处于标准实现阶段的提案都已经被支持。 支持的语言:Rust(wasmtime的实现语言)、C/C++、Python、.NET、Go等
wasm3
仓库地址 :github.com/wasm3/wasm3 wasm3是一款解释执行的轻量级WebAssembly引擎,使用C语言编写。最低可用系统要求:64kb的存储空间和10kb的内存空间。再加上wasm3是纯解释执行,可以在iOS等设备上运行,这一点其他纯编译型引擎无法做到的,使跨平台具有可行性。但是目前wasm3对WebAssembly规范的支持度一般,具体情况如下: 支持的语言:Rust、C/C++、Python3、.NET、Go等
WasmEdge
仓库地址 :github.com/WasmEdge/Wa... WasmEdge是一款由CNCF(Cloud Native Computing Foundation,云原生计算基金会)托管的WebAssembly引擎,从它的名称也可以看出,WasmEdge主要面向边缘计算、云原生和去中心化应用。和wasmtime一样,WasmEdge也是编译型的wasm引擎,可以按照JIT/AOT两种模式对wasm指令进行编译,并执行。不同之处在于WasmEdge使用LLVM作为编译器后段,利用了LLVM出色的优化编译能力。因此,相比于使用Cranelift的wasmtime,WasmEdge生成的指令更优,执行速度更快。Wasm支持所有标准的WebAssembly特性和大量扩展提案。 支持的语言:Rust、C、Go等
V8
上面几款引擎都是纯粹的WebAssembly引擎,专用于wasm程序的执行,通常在standalone的场景中使用。但是WebAssembly是由四大浏览器厂商联合推出的,最初在Web环境中使用,JavaScript引擎才是wasm最早的执行引擎。 V8 执行 WebAssembly 也是使用编译后执行的方式: 通过自身的 TurboFan 或者 LiftOff 编译后端,进行 JIT 编译后执行。根据一份 2021 年的测试数据,NodeJS 在 Benchmark 上的性能优于 wasmtime,可见 V8 的 性能足以媲美多数专门的wasm引擎。 在标准支持方面,考虑到 V8 一般在 Web 或者 NodeJS 环境中运行 wasm 程序,所以 V8 并不支持 WASI 标准。但是 Chrome & V8 团队作为 wasm 标准制定的主要参与者之一,V8 引擎对 WebAssembly 的 JS API、MVP 以及 Post-MVP 等核心提案都完整支持,并且实验性地支持各类处在探索阶段的提案。因此,如果想要尝试 WebAssembly 的最新提案实现,V8 引擎是一个不错的选择。 支持的语言:C++ WebAssembly官网有一个特性表格,列出了各引擎对特性的支持情况,可以查看你的浏览器支持哪些特性:webassembly.org/roadmap/
调试
目前WebAssembly对以下几种调试方式支持较好:
- 使用Chrome的Devtool进行调试,具体如何调试可以查看Chrome开发者工具的文档,支持C/C++、AssemlbyScript等语言编译为WebAssembly调试
- 原生调试,这种方式其实不是在调试WebAssembly代码,而是将WebAssembly反编译为原始代码,然后调试原始代码
- lld+wasmtime调试:借助 lldb 和 wasmtime 的能力,将 wasm 的调试信息在 JIT 编译时同步转换到 native 格式,可以获得非常接近于原生调试的体验
前端开发建议首选Chrome+Chrome插件的方式,和平时开发使用source面板基本类似,示例如下:
使用场景和未来趋势
使用场景
- Web环境:视频解码和GUI控件(爱奇艺的解码)、浏览器平台的虚拟实现(VR)、游戏相关(Doom 3)、数据库、物理引擎等等
- 云原生:2022年Docker宣布推出与WASM集成的首个技术预览版、Krustlet(用于将 Kubernetes 管理的工作负载交付给 WASM 运行时的工具,实现的是Kubelet的功能)
- 边缘计算
- 移动设备、IoT和物联网
- 区块链技术
未来趋势
- 更好的开发体验
- 标准继续推进
- 理念、传播和社区
WebAssembly支持的格式
二进制格式
WebAssembly的二进制格式通常使用wasm
扩展名,通常使用其它语言编译为WebAssembly的产物就是该格式,是一种紧凑的、机器可度的二进制形式,这也意味着该格式是人为不可读的。 Web浏览器或其他支持WebAssembly运行时环境加载和执行的格式就是该格式,该格式在编译器中呈现的形式可能类似如下:
文本格式
为了能够让人类阅读和编辑 WebAssembly,WebAssembly提供了相应的文本表示。这是一种用来在文本编辑器、浏览器开发者工具等工具中显示的中间形式。相对于二进制格式,文本格式可读性较高,但基本上和汇编语言一个层级,通常使用wat 作为文件后缀(两种格式是可以互转的,在不包含其他语言特性的情况下可以使用wabt工具集的wasm2wat和wat2wasm命令互相转换),wat文件内容类似如下:
运行一个简单的程序
下面介绍一个简单的例子,如何在V8引擎中(浏览器)运行一个wasm模块(这里以AssemblyScript作为WebAssembly的原始开发语言) 在开始之前先介绍一下WebAssembly的一些关键概念,这些概念都一一映射到Web Assembly的JavaScript API中:
- 模块:表示一个已经被浏览器编译为可执行机器码的 WebAssembly 二进制代码。一个模块是无状态的,并且像一个二进制大对象(在前端中的就是Blob)一样能够被缓存到IndexedDB中或者在 window 和 worker 之间进行共享。一个模块能够像一个 ES2015 的模块一样声明导入和导出。
- 内存:ArrayBuffer,大小可变。本质上是连续的字节数组,WebAssembly 的低级内存存取指令可以对它进行读写操作。
- 表格:带类型数组,大小可变。表格中的项存储了不能作为原始字节存储在内存里的对象的引用(为了安全和可移植性的原因),如函数。
- 实例:一个模块及其在运行时使用的所有状态,包括内存、表格和一系列导入值。一个实例就像一个已经被加载到一个拥有一组特定导入全局变量的 ES2015 模块。
- 配置AssemblyScript编译环境(省略),编写AssemblyScript代码(以下就是一个简单的add函数)
typescript
export function add(a: i32, b: i32): i32 {
return a + b;
}
- 编译AssemblyScript到WebAssembly,AssemblyScript编译成WebAssembly后,除了生成wat、wasm文件外还是生成一个
js
文件,这个文件包含了一些胶水代码,该例子的js
文件内容如下:
typescript
async function instantiate(module, imports = {}) {
// 示例化wasm,返回一个resolve一个WebAssembly实例的Promise
const { exports } = await WebAssembly.instantiate(module, imports);
// wasm导出内容
return exports;
}
export const {
memory,
add,
} = await (async url => instantiate(
await (async () => {
try {
// 浏览器环境(这里包含两步:加载wasm、编译wasm为机器码),编译完生成一个模块
return await globalThis.WebAssembly.compileStreaming(globalThis.fetch(url));
} catch {
// 报错使用Node环境兜底
return globalThis.WebAssembly.compile(
await (
await import("node:fs/promises")).readFile(url)
);
}
})(), {
}
))(new URL("release.wasm", import.meta.url));
- 加载wasm、编译wasm、实例化wasm
- 在HTML文件中加载该
js
文件(需要使用ES模块进行加载,因为使用了export),HTML文件代码如下:
html
<!DOCTYPE html>
<html lang="en">
<body>
<script type="module">
import { add } from "./build/release.js";
console.log("wasm add: ", add(34, 26));
</script>
</body>
</html>
运行结果如下: