- WebAssembly 教程
- WebAssembly - 主页
- WebAssembly - 概述
- WebAssembly - 简介
- WebAssembly-WASM
- WebAssembly - 安装
- WebAssembly - 编译为 WASM 的工具
- WebAssembly - 程序结构
- WebAssembly - JavaScript
- WebAssembly - Javascript API
- WebAssembly - 在 Firefox 中调试 WASM
- WebAssembly - “你好世界”
- WebAssembly - 模块
- WebAssembly - 验证
- WebAssembly - 文本格式
- WebAssembly - 将 WAT 转换为 WASM
- WebAssembly - 动态链接
- WebAssembly - 安全
- WebAssembly - 使用 C
- WebAssembly - 使用 C++
- WebAssembly - 使用 Rust
- WebAssembly - 使用 Go
- WebAssembly - 使用 Nodejs
- WebAssembly - 示例
- WebAssembly 有用资源
- WebAssembly - 快速指南
- WebAssembly - 有用的资源
- WebAssembly - 讨论
WebAssembly - 快速指南
WebAssembly - 概述
WebAssembly 是一种新的网络计算机编程语言。WebAssembly 代码是一种低级二进制格式,与 Web 兼容,并且可以在现代 Web 浏览器中轻松运行。生成的文件很小,加载和执行速度更快。您现在可以将 C、C++、Rust 等语言编译为二进制格式,并且它可以像 javascript 一样在网络上运行。
WebAssembly 的定义
根据 WebAssembly 的官方网站(https://webassemble.org/),它被定义为 WebAssembly(缩写为 Wasm)是基于堆栈的虚拟机的二进制指令格式。Wasm 被设计为编译 C/C++/Rust 等高级语言的便携式目标,支持在 Web 上部署客户端和服务器应用程序。
WebAssembly 不是开发人员必须编写的东西,但代码是用 C、C++、Rust 等语言编写的,并且可以编译为 WebAssembly(wasm)。相同的代码可以在网络浏览器中运行。
WebAssembly是一种新语言,代码是低级汇编语言,但凭借其文本格式特性,代码具有可读性,并且在必要时可以进行调试。
WebAssembly 的目标
WebAssembly 的开放标准是在 W3C 社区组中开发的,该社区组包括来自所有主要浏览器的代表以及 W3C 工作组。
WebAssembly 的主要目标如下:
更快、更高效、更便携- WebAssembly 代码旨在利用可用的硬件在不同平台上更快地运行。
易于阅读和调试- WebAssembly 是一种低级汇编语言,具有文本格式支持,允许您调试代码以解决任何问题,并在必要时重写代码。
安全性- WebAssembly 可以安全地在 Web 浏览器上运行,因为它负责权限和同源策略。
WebAssembly 的优点
以下是 WebAssembly 的优点 -
Run is Modern Browsers - WebAssembly 能够在可用的现代 Web 浏览器上执行,没有任何问题。
多语言支持- C、C++、Rust、Go 等语言现在可以将代码编译为 WebAssembly 并在 Web 浏览器中运行相同的代码。因此,以前无法在浏览器中运行的语言现在可以在浏览器中运行。
更快、更高效、更便携- 由于代码较小,因此加载和执行速度更快。
易于理解- 开发人员不必在理解 WebAssembly 编码方面花费太多精力,因为他们不必在 WebAssembly 中编写代码。相反,在 WebAssembly 中编译代码并在 Web 上执行相同的代码。
易于调试- 虽然最终代码是低级汇编语言,但您也可以以文本格式获取它,这很容易阅读和调试。
WebAssembly 的缺点
以下是 WebAssembly 的缺点 -
WebAssembly 仍在开发中,现在决定它的未来还为时过早。
WebAssembly 依赖于 javascript 与文档对象模型 (DOM) 进行交互。
WebAssembly - 简介
WebAssembly 也称为 WASM,于 2017 年首次推出。WebAssembly 起源背后的大型科技公司是 Google、Apple、Microsoft、Mozilla 和 W3C。
有传言称 WebAssembly 将取代 Javascript,因为它的执行速度更快,但事实并非如此。WebAssembly 和 Javascript 旨在共同解决复杂的问题。
需要 WebAssembly
到目前为止,我们只有 Javascript 可以在浏览器中成功运行。有一些非常繁重的任务很难在浏览器中使用 JavaScript 执行。
仅举几例,包括图像识别、计算机辅助设计 (CAD) 应用程序、实时视频增强、VR 和增强现实、音乐应用程序、科学可视化和模拟、游戏、图像/视频编辑等。
WebAssembly 是一种具有二进制指令的新语言,可以更快地加载和执行。上述任务可以用 C、C++、Rust 等高级语言轻松完成。我们需要一种方法,可以编译 C、C++、Rust 中的代码并在 Web 浏览器中使用它。使用 WebAssembly 也可以实现同样的效果。
当 WebAssembly 代码加载到浏览器中时。然后,浏览器负责将其转换为处理器可以理解的机器格式。
对于 javascript,必须下载、解析代码并将其转换为机器格式。它需要花费大量时间,对于像我们之前提到的繁重任务可能会非常慢。
WebAssembly 的工作原理
C、C++ 和 Rust 等高级语言被编译为二进制格式,即.wasm和文本格式.wat。
使用C、C++ 和 Rust 编写的源代码使用编译器编译为.wasm 。您可以使用 Emscripten SDK 将 C/C++ 编译为.wasm。
流程如下 -
可以使用 Emscripten SDK 将 C/C++ 代码编译为.wasm。稍后,可以在 html 文件中的 javascript 的帮助下使用.wasm代码来显示输出。
WebAssembly 的关键概念
关键概念解释如下 -
模块
模块是由浏览器编译为可执行机器代码的对象。模块被认为是无状态的,可以在 Windows 和 Web Worker 之间共享。
记忆
WebAssembly 中的内存是保存数据的数组缓冲区。您可以使用 Javascript api WebAssembly.memory() 分配内存。
桌子
WebAssembly 中的表是一个类型化数组,位于 WebAssembly 内存之外,并且主要包含对函数的引用。它存储函数的内存地址。
实例
实例是一个对象,它具有可以从 javascript 调用并在浏览器内部执行的所有导出函数。
WebAssembly-WASM
WebAssembly 也称为 wasm,是对 Javascript 的改进。它被设计为在浏览器内运行,就像 JavaScript 一样,也可以与 NodeJS 一起运行。当编译任何高级语言(如 C、C++、Rust)时,您碰巧会得到 wasm 输出。
考虑以下 C 程序 -
int factorial(int n) { if (n == 0) return 1; else return n * factorial(n-1); }
利用 WasmExplorer(可在https://mbebenita.github.io/WasmExplorer/获取)来获取编译后的代码,如下所示 -
阶乘程序的 WebAssembly 文本格式如下所示 -
(module (table 0 anyfunc) (memory $0 1) (export "memory" (memory $0)) (export "factorial" (func $factorial)) (func $factorial (; 0 ;) (param $0 i32) (result i32) (local $1 i32) (local $2 i32) (block $label$0 (br_if $label$0 (i32.eqz (get_local $0) ) ) (set_local $2 (i32.const 1) ) (loop $label$1 (set_local $2 (i32.mul (get_local $0) (get_local $2) ) ) (set_local $0 (tee_local $1 (i32.add (get_local $0) (i32.const -1) ) ) ) (br_if $label$1 (get_local $1) ) ) (return (get_local $2) ) ) (i32.const 1) ) )
使用 Wat2Wasm 工具,您可以查看 WASM 代码,就像下面提到的那样 -
开发人员不应该在 wasm 中编写代码或学习在其中编写代码,因为它主要是在编译高级语言时生成的。
堆垛机型号
在 WASM 中,所有指令都被压入堆栈。参数被弹出,结果被推回堆栈。
考虑以下添加 2 个数字的 WebAssembly 文本格式 -
(module (func $add (param $a i32) (param $b i32) (result i32) get_local $a get_local $b i32.add ) (export "add" (func $add)) )
函数的名称是$add,它接受 2 个参数 $a 和 $b。结果是一个 32 位整数类型。使用 get_local 访问局部变量,使用 i32.add 执行添加操作。
执行时添加 2 个数字的堆栈表示如下 -
在步骤 1 - get_local $a 指令的执行中,第一个参数即 $a 被压入堆栈。
在步骤 2中- 在执行 get_local $b 指令期间,第二个参数(即 $b)被压入堆栈。
在步骤 3中- i32.add 的执行将从堆栈中弹出元素并将结果推回堆栈。栈中最后保留的值是函数$add 的结果。
WebAssembly - 安装
在本章中,将学习如何安装 Emscripten SDK 来编译 C/C++。Emscripten 是一个低级虚拟机 (LLVM),它采用 C/C++ 生成的字节码并将其编译为可以在浏览器内轻松执行的 JavaScript。
要将C/C++编译为WebAssembly,我们需要首先安装Emscripten sdk。
安装Emscripten sdk
安装 Emscripten sdk 的步骤如下 -
步骤 1 - 克隆 emsdk 存储库: git clone https://github.com/emscripten-core/emsdk.git。
E:\wa>git clone https://github.com/emscripten-core/emsdk.git Cloning into 'emsdk'... remote: Enumerating objects: 14, done. remote: Counting objects: 100% (14/14), done. remote: Compressing objects: 100% (12/12), done. remote: Total 1823 (delta 4), reused 4 (delta 2), pack-reused 1809 receiving obje cts: 99% (1819/1823), 924.01 KiB | 257.00 KiB/s Receiving objects: 100% (1823/1823), 1.01 MiB | 257.00 KiB/s, done. Resolving deltas: 100% (1152/1152), done.
步骤 2 - 进入目录 emsdk。
cd emsdk
步骤 3 - 对于 Windows:执行以下命令。
emsdk install latest
对于 Linux,此命令将需要一些时间来安装必要的工具,如 java、python 等。请遵循下面提到的代码 -
./emsdk install latest
步骤 4 - 要激活最新的 SDK,请在终端中执行以下命令。
对于 Windows,执行以下命令 -
emsdk activate latest
对于 linux,执行下面提到的命令 -
./emsdk activate latest
步骤 5 - 要激活 PATH 和其他环境变量,请在终端中运行以下命令。
对于 Windows,执行命令 -
emsdk_env.bat
对于 Linux,执行以下命令 -
source ./emsdk_env.sh
我们已经完成 emsdk 的安装,现在可以编译 C 或 C++ 代码。C/C++的编译将在接下来的章节中完成。
要编译任何 C 或 C++ 代码,请执行以下命令 -
emcc source.c or source.cpp -s WASM=1 -o source.html
输出将为您提供 source.html 文件、source.js 和 source.wasm 文件。js 将具有获取 source.wasm 的 api,当您在浏览器中点击 source.html 时,您可以看到输出。
要获取 wasm 文件,您可以使用以下命令。此命令只会为您提供 source.wasm 文件。
emcc source.c or source.cpp -s STANDALONE_WASM
WebAssembly - 编译为 WASM 的工具
本章将讨论一些在使用 WebAssembly 时非常有用的易于使用的工具。让我们首先了解 WebAssembly.studio 工具。
WebAssembly.studio
该工具允许您将 C、Rust、Wat 编译为 Wasm 等。
首先,您可以单击 Empty C Project、Empty Rust Project、Empty Wat Project 将 C 和 Rust 编译为 WASM。5.
它具有“构建”、“运行”来构建代码并检查输出。下载按钮允许您下载.wasm文件,该文件可用于在浏览器内进行测试。该工具对于编译 C 和 Rust 代码并检查输出非常有帮助。
WebAssembly 浏览器
WebAssembly Explorer 允许您编译 C 和 C++ 代码。请参阅链接https://mbebenita.github.io/WasmExplorer/了解更多详细信息。单击链接后出现的屏幕如下所示 -
您可以选择 C 和 C++ 版本。C 或 C++ 的源代码写在这里 -
一旦单击“编译”按钮,它就会在下面的块中提供 WebAssembly 文本格式 (WAT) 和 Firefox x86 汇编代码 -
您可以下载.wasm代码以在浏览器中测试它。
WASMFiddle
Wasmfiddle帮助您将 C 代码编译为 WebAssembly 并测试输出。单击链接https://wasdk.github.io/WasmFiddle/ 后,您将看到以下页面 -
单击“构建”来编译代码。您可以通过点击 Wat 和 Wasm 下载 Wat 和 Wasm 代码。要测试输出,请单击“运行”按钮。
WASM 至 WAT
当您输入 WebAssembly 文本格式时,工具wat2wasm将为您提供 wasm 代码。您可以单击链接https://web assembly.github.io/wabt/demo/wat2wasm/进行演示,将出现的屏幕如下所示 -
您可以使用上传按钮上传.wasm,文本区域将显示文本格式。
WASM 转 WASM
当您输入 WebAssembly 文本格式时,工具 wat2wasm 将为您提供 wasm 代码。您可以单击链接https://web assembly.github.io/wabt/demo/wat2wasm/进行演示,将出现的屏幕如下所示 -
这个工具非常有用,因为它有助于测试输出。您可以输入 WAT 代码并查看 .wasm 代码,还可以执行该代码以查看输出。
WebAssembly - 程序结构
WebAssembly,也称为 WASM,是二进制格式的低级代码,旨在以最有效的方式在浏览器内执行。WebAssembly 代码的结构如下:
- 价值观
- 类型
- 指示
现在让我们详细了解它们。
价值观
WebAssembly 中的值旨在存储复杂的数据,例如文本、字符串和向量。WebAssembly 支持以下内容 -
- 字节
- 整数
- 浮点
- 名称
字节
字节是 WebAssembly 支持的最简单的值形式。该值采用十六进制格式。
例如表示为b 的字节也可以采用自然数 n,其中 n <256。
byte ::= 0x00| .... |0xFF
整数
在 WebAssembly 中,支持的整数如下所示 -
- i32:32 位整数
- i64:64 位整数
浮点
在 WebAssembly 中支持的浮点数如下 -
- f32:32位浮点
- f64:64 位浮点
名称
名称是字符序列,具有由 Unicode 定义的标量值,可以从此处给出的链接http://www.unicode.org/versions/Unicode12.1.0/获得。
类型
WebAssembly 中的实体被分类为类型。支持的类型如下所示 -
- 值类型
- 结果类型
- 功能类型
- 限制
- 内存类型
- 表格类型
- 全局类型
- 外部类型
让我们一一研究一下。
值类型
WebAssembly 支持的值类型如下:
- i32:32 位整数
- i64:64 位整数
- f32:32位浮点
- f64:64 位浮点
valtype ::= i32|i64|f32|f64
结果类型
括号内写入的值将被执行并存储在结果类型内。结果类型是执行由值组成的代码块的输出。
resulttype::=[valtype?]
功能类型
函数类型将接受参数向量返回结果向量。
functype::=[vec(valtype)]--> [vec(valtype)]
限制
限制是与内存和表类型相关的存储范围。
limits ::= {min u32, max u32}
内存类型
存储器类型涉及线性存储器和大小范围。
memtype ::= limits
表格类型
表类型按分配给它的元素类型进行分类。
tabletype ::= limits elemtype elemtype ::= funcref
表类型取决于分配给它的最小和最大大小的限制。
全局类型
全局类型保存具有值的全局变量,可以更改或保持不变。
globaltype ::= mut valtype mut ::= const|var
外部类型
外部类型处理导入和外部值。
externtype ::= func functype | table tabletype | mem memtype | global globaltype
指示
WebAssembly 代码是遵循堆栈机器模型的指令序列。由于 WebAssembly 遵循堆栈机器模型,因此指令被推送到堆栈上。
例如,函数的参数值从堆栈中弹出,并将结果推回到堆栈上。最终,栈中只有一个值,那就是结果。
一些常用的指令如下 -
- 数字指令
- 变量指令
数字指令
数字指令是对数值执行的运算。
例如nn, mm ::= 32|64 ibinop ::= add|sub|mul|div_sx|rem_sx|and|or|xor irelop ::= eq | ne | lt_sx | gt_sx | le_sx | ge_sx frelop ::= eq | ne | lt | gt | le | ge
变量指令
变量指令是关于访问局部变量和全局变量的。
例如
访问局部变量 -
get_local $a get_local $b
设置局部变量-
set_local $a set_local $b
访问全局变量-
get_global $a get_global $b
设置全局变量-
set_global $a set_global $b
WebAssembly - JavaScript
本章将列出 WebAssembly 和 Javascript 之间的比较。
Javascript 是一种我们在浏览器中经常使用的语言。现在,随着 WebAssembly 的发布,我们也可以在浏览器中使用 WebAssembly。
WebAssembly 出现的原因并不是要取代 javascript,而是为了处理某些 javascript 难以处理的事情。
例如
使用 javascript 很难完成图像识别、CAD 应用程序、实时视频增强、VR 和增强现实、音乐应用程序、科学可视化和模拟、游戏、图像/视频编辑等任务。
使用C/C++、Rust等高级语言,现在可以将其编译为WebAssembly,很容易完成上述任务。WebAssembly 生成易于在浏览器内执行的二进制代码。
这里是 Javascript 和 WebAssembly 之间的比较列表。
参数 | JavaScript | 网络组装 |
---|---|---|
编码 |
您可以轻松地用 Javascript 编写代码。编写的代码是人类可读的并保存为 .js。在浏览器内部使用时,您需要使用 <script> 标签。 |
代码可以在 WebAssembly 中以文本格式编写,并保存为 .wat。以.wat 格式编写代码很困难。最好从其他高级语言编译代码,而不是从头开始编写 .wat。 您无法在浏览器内执行 .wat 文件,必须使用可用的编译器或在线工具将其转换为 .wasm。 |
执行 |
用 javascript 编写的代码在浏览器中使用时必须进行下载、解析、编译和优化。 |
我们的 .wasm 中的 WebAssembly 代码已编译为二进制格式。 |
内存管理 |
JavaScript 在创建变量时分配内存,在不使用时释放内存并将其添加到垃圾回收中。 |
WebAssembly 中的内存是保存数据的数组缓冲区。您可以使用 Javascript API WebAssembly.memory() 分配内存。 WebAssembly 内存以数组格式存储,即易于理解和执行的平面内存模型。 WebAssembly 中内存模型的缺点是 -
|
加载时间和性能 |
对于 javascript,当在浏览器内部调用时,必须下载并解析 javascript 文件。随后,解析器将源代码转换为 JavaScript 引擎在浏览器中执行代码的字节码。 Javascript 引擎非常强大,因此,与 WebAssembly 相比,Javascript 的加载时间和性能都非常快。 |
WebAssembly 最重要的目标是比 JavaScript 更快。由高级语言生成的 Wasm 代码尺寸更小,因此加载时间更快。 但是,像 GO 这样的语言,当编译为 wasm 时,会为一小段代码生成一个大文件。 WebAssembly 的设计方式使其编译速度更快,并且可以在所有主要浏览器上运行。与 JavaScript 相比,WebAssembly 在性能方面仍然需要进行大量改进。 |
调试 |
JavaScript 是人类可读的并且可以轻松调试。在浏览器内向 JavaScript 代码添加断点可以让您轻松调试代码。 |
WebAssembly 提供文本格式的代码,可读,但仍然很难调试。Firefox 允许您在浏览器中查看 .wat 格式的 wasm 代码。 您无法在 .wat 中添加断点,这将在将来提供。 |
浏览器支持 |
JavaScript 在所有浏览器中都能正常运行。 |
所有主流 Web 浏览器都支持 WebAssembly。 |
WebAssembly - JavaScript API
在本章中,我们将了解如何使用 JavaScript WebAssembly API 加载 wasm 代码并在浏览器中执行它们。
以下是一些重要的 API,我们将在整个教程中使用它们来执行 wasm 代码。
- fetch() 浏览器 API
- WebAssembly.compile
- WebAssembly.实例
- WebAssembly.实例化
- WebAssembly.instantiateStreaming
在我们讨论 WebAssembly javascript API 之前,为了测试 API 和输出,我们将使用以下 C 程序以及使用 wasm explorer 从该 c 程序生成的 .wasm 代码。
C 程序的示例如下 -
#include<stdio.h> int square(int n) { return n*n; }
我们将使用 WASM 浏览器来获取 wasm 代码 -
下载 WASM 代码并使用它来测试 API。
fetch() 浏览器 API
fetch() API 旨在加载 .wasm 网络资源。
<script> var result = fetch("findsquare.wasm"); console.log(result); </script>
它返回一个承诺,如下所示 -
您还可以使用 XMLHttpRequest 方法来获取 wasm 网络资源。
WebAssembly.compile()
API 的职责是编译从 .wasm 获取的模块详细信息。
句法
语法如下 -
WebAssembly.compile(buffer);
参数
Buffer - .wasm 中的代码必须转换为类型化数组或数组缓冲区,然后才能作为输入进行编译。
返回值
它将返回一个包含已编译模块的承诺。
例子
让我们看一个示例,它使用 webAssembly.compile() 将输出作为编译模块提供。
<script> fetch("findsquare.wasm") .then(bytes => bytes.arrayBuffer()) .then(mod => { var compiledmod = WebAssembly.compile(mod); compiledmod.then(test=> { console.log(test); }) }) </script>
输出
在浏览器中检查时,console.log 将为您提供已编译的模块详细信息 -
该模块有一个带有导入、导出和自定义部分的构造函数对象。让我们看看下一个 API,以获取已编译模块的更多详细信息。
WebAssembly.实例
使用 WebAssembly.instance,API 将为您提供已编译模块的可执行实例,可以进一步执行该实例以获得输出。
句法
语法如下 -
new WebAssembly.Instance(compiled module)
返回值
返回值将是一个带有可以执行的导出函数数组的对象。
例子
<script> fetch("findsquare.wasm") .then(bytes => bytes.arrayBuffer()) .then(mod => WebAssembly.compile(mod)).then(module => { let instance = new WebAssembly.Instance(module); console.log(instance); }) </script>
输出
输出将为我们提供一个导出函数数组,如下所示 -
您可以看到我们从编译的 C 代码中获得的 square 函数。
要执行平方函数,您可以执行以下操作 -
<script> fetch("findsquare.wasm") .then(bytes => bytes.arrayBuffer()) .then(mod => WebAssembly.compile(mod)) .then(module => { let instance = new WebAssembly.Instance(module); console.log(instance.exports.square(15)); }) </script>
输出将是 -
225
WebAssembly.实例化
该 API 负责一起编译和实例化模块。
句法
语法如下 -
WebAssembly.instantiate(arraybuffer, importObject)
参数
arraybuffer - .wasm 中的代码必须先转换为类型化数组或 arraybuffer,然后再作为输入进行实例化。
importObject - 导入对象必须具有内存的详细信息,要在模块内使用的导入函数。它可以是一个空的模块对象,以防没有什么可共享的。
返回值
它将返回一个承诺,其中包含模块和实例详细信息。
例子
<script type="text/javascript"> const importObj = { module: {} }; fetch("findsquare.wasm") .then(bytes => bytes.arrayBuffer()) .then(module => WebAssembly.instantiate(module, importObj)) .then(finalcode => { console.log(finalcode); console.log(finalcode.instance.exports.square(25)); }); </script>
输出
当您执行代码时,您将得到下面提到的输出。
WebAssembly.instantiateStreaming
该 API 负责根据给定的 .wasm 代码编译和实例化 WebAssembly 模块。
句法
语法如下 -
WebAssembly.instantiateStreaming(wasmcode, importObject);
参数
wasmcode - 来自 fetch 或任何其他提供 wasm 代码并返回承诺的 API 的响应。
importObject - 导入对象必须具有内存的详细信息,要在模块内使用的导入函数。如果没有什么可共享的,它可以是一个空模块对象。
返回值
它将返回一个承诺,其中包含模块和实例详细信息。
例子
下面讨论一个例子 -
<script type="text/javascript"> const importObj = { module: {} }; WebAssembly.instantiateStreaming(fetch("findsquare.wasm"), importObj).then(obj => { console.log(obj); }); </script>
当您在浏览器中测试它时,您会看到一个错误 -
为了使其在服务器端工作,您必须添加 mime 类型 application/wasm 或使用 WebAssembly.instantiate(arraybuffer, importObject)。
WebAssembly - 在 Firefox 中调试 WASM
目前所有可用的最新浏览器(例如 Chrome、Firefox)都添加了 WebAssembly 支持。Firefox 54+ 版本为您提供了调试 wasm 代码的特殊功能。
为此,请在调用 wasm 的 Firefox 浏览器中执行代码。例如,考虑以下用于求数字平方的 C 代码。
C 程序的示例如下 -
#include<stdio.h> int square(int n) { return n*n; }
我们将利用 WASM 浏览器来获取 wasm 代码 -
下载 WASM 代码并使用它在浏览器中查看输出。
加载 wasm 的 html 文件如下 -
!doctype html> <html> <head> <meta charset="utf-8"> <title>WebAssembly Square function</title> <style> div { font-size : 30px; text-align : center; color:orange; } </style> </head> <body> <div id="textcontent"></div> <script> let square; fetch("findsquare.wasm").then(bytes => bytes.arrayBuffer()) .then(mod => WebAssembly.compile(mod)) .then(module => {return new WebAssembly.Instance(module) }) .then(instance => { square = instance.exports.square(13); console.log("The square of 13 = " +square); document.getElementById("textcontent").innerHTML = "The square of 13 = " +square; }); </script> </body> </html>
打开 Firefox 浏览器并加载上述 html 文件并打开调试器工具。
您应该在调试器工具中看到 wasm:// 条目。单击 wasm://,它会显示转换为 .wat 格式的 wasm 代码,如上所示。
您可以查看导出函数的代码,如果出现任何问题,可以调试代码。Firefox 还打算添加断点,以便您可以调试代码并检查执行流程。
WebAssembly - “你好世界”
在本章中,我们将用 C 语言编写一个简单的程序并将其转换为 .wasm 并在浏览器中执行相同的程序以获取文本“Hello World”。
将使用 wasm 资源管理器工具将 C 程序转换为 .wasm 并在我们的 .html 文件中使用 .wasm。
https://mbebenita.github.io/WasmExplorer/上提供的 Wasm 浏览器工具如下所示 -
我们将使用的 C 代码如下 -
#include <stdio.h> char *c_hello() { return "Hello World"; }
使用 C 代码更新 wasm 资源管理器中的第一个块,如下所示 -
单击 COMPILE 按钮编译为 WASM 和 WAT 以及 Firefox x86 Web Assembly,如下所示 -
使用下载获取 .wasm 文件并将其另存为firstprog.wasm。
创建一个名为firstprog.html的.html文件,如下所示 -
<!doctype html> <html> <head> <meta charset="utf-8"> <title>WebAssembly Hello World</title> </head> <body> <div id="textcontent"></div> <script type="text/javascript"> //Your code from webassembly here </script> </body> </html>
现在让我们使用firstprog.wasm从C函数c_hello()读取helloworld。
步骤1
使用 fetch() api 读取firstprog.wasm 代码。
第2步
.wasm 代码必须使用ArrayBuffer转换为 arraybuffer 。ArrayBuffer 对象将返回一个固定长度的二进制数据缓冲区。
到目前为止的代码如下 -
<script type="text/javascript"> fetch("firstprog.wasm") .then(bytes => bytes.arrayBuffer()) </script>
步骤3
必须使用WebAssembly.compile(buffer)函数将 ArrayBuffer 中的字节编译到模块中。
代码如下所示 -
<script type="text/javascript"> fetch("firstprog.wasm") .then(bytes => bytes.arrayBuffer()) .then(mod => WebAssembly.compile(mod)) </script>
步骤4
要获取模块,我们必须调用 web assembly.instance 构造函数,如下所示 -
<script type="text/javascript"> fetch("firstprog.wasm") .then(bytes => bytes.arrayBuffer()) .then(mod => WebAssembly.compile(mod)) .then(module => {return new WebAssembly.Instance(module) }) </script>
步骤5
现在让我们控制台实例以在浏览器中查看详细信息。
<script type="text/javascript"> fetch("firstprog.wasm") .then(bytes => bytes.arrayBuffer()) .then(mod => WebAssembly.compile(mod)) .then(module => { return new WebAssembly.Instance(module) }) .then(instance => { console.log(instance); }); </script>
console.log 详细信息如下所示 -
为了从函数 c_hello() 中获取字符串“Hello World”,我们需要在 javascript 中添加一些代码。
首先,获取内存缓冲区详细信息,如下所示 -
let buffer = instance.exports.memory.buffer;;
缓冲区值必须转换为类型化数组,以便我们可以从中读取值。缓冲区中有字符串 Hello World。
要转换为类型化,请调用构造函数 Uint8Array ,如下所示 -
let buffer = new Uint8Array(instance.exports.memory.buffer);
现在,我们可以在 for 循环中从缓冲区读取值。
现在让我们通过调用我们编写的函数来开始读取缓冲区,如下所示 -
let test = instance.exports.c_hello();
现在,测试变量有了读取字符串的起点。WebAssembly 没有任何字符串值,所有内容都存储为整数。
因此,当我们从缓冲区读取值时,它们将是一个整数值,我们需要使用 JavaScript 中的 fromCharCode() 将其转换为字符串。
代码如下 -
let mytext = ""; for (let i=test; buffer[i]; i++){ mytext += String.fromCharCode(buffer[i]); }
现在,当您控制台 mytext 时,您应该看到字符串“Hello World”。
例子
完整代码如下 -
<!doctype html> <html> <head> <meta charset="utf-8"> <title>WebAssembly Add Function</title> <style> div { font-size : 30px; text-align : center; color:orange; } </style> </head> <body> <div id="textcontent"></div> <script> fetch("firstprog.wasm") .then(bytes => bytes.arrayBuffer()) .then(mod => WebAssembly.compile(mod)) .then(module => {return new WebAssembly.Instance(module)}) .then(instance => { console.log(instance); let buffer = new Uint8Array(instance.exports.memory.buffer); let test = instance.exports.c_hello(); let mytext = ""; for (let i=test; buffer[i]; i++) { mytext += String.fromCharCode(buffer[i]); } console.log(mytext); document.getElementById("textcontent").innerHTML = mytext; }); </script> </body> </html>
我们添加了一个div,并将内容添加到div中,因此字符串显示在浏览器上。
输出
输出如下 -
WebAssembly - 模块
我们已经了解了如何从 c /c++ 代码获取 .wasm 文件。在本章中,我们将把 wasm 转换为 WebAssembly 模块,并在浏览器中执行相同的模块。
让我们使用 C++ 阶乘代码,如下所示 -
int fact(int n) { if ((n==0)||(n==1)) return 1; else return n*fact(n-1); }
打开 Wasm Explorer,可从https://mbebenita.github.io/WasmExplorer/ 获取,如下所示 -
第一列具有 C++ 阶乘函数,第二列具有 WebAssembly 文本格式,最后一列具有 x86 汇编代码。
WebAssembly 文本格式 -
(module (table 0 anyfunc) (memory $0 1) (export "memory" (memory $0)) (export "_Z4facti" (func $_Z4facti)) (func $_Z4facti (; 0 ;) (param $0 i32) (result i32) (local $1 i32) (set_local $1 (i32.const 1) ) (block $label$0 (br_if $label$0 (i32.eq (i32.or (get_local $0) (i32.const 1) ) (i32.const 1) ) ) (set_local $1 (i32.const 1) ) (loop $label$1 (set_local $1 (i32.mul (get_local $0) (get_local $1) ) ) (br_if $label$1 (i32.ne (i32.or (tee_local $0 (i32.add (get_local $0) (i32.const -1) ) ) (i32.const 1) ) (i32.const 1) ) ) ) ) (get_local $1) ) )
C++ 函数事实已以 WebAssembly 文本格式导出为“ _Z4facti ”。
单击下载按钮下载 wasm 代码并将文件保存为 Factorial.wasm。
现在要将 .wasm 代码转换为模块,我们必须执行以下操作 -
步骤1
使用 ArrayBuffer 将 .wasm 转换为 arraybuffer 。ArrayBuffer 对象将返回一个固定长度的二进制数据缓冲区。
第2步
必须使用WebAssembly.compile(buffer)函数将 ArrayBuffer 中的字节编译到模块中。
WebAssembly.compile ()函数根据给定的字节编译并返回 WebAssembly.Module。
这里是步骤 1 和 2 中讨论的 Javascript 代码。
<script type="text/javascript"> let factorial; fetch("factorial.wasm") .then(bytes => bytes.arrayBuffer()) .then(mod => WebAssembly.compile(mod)) .then(module => {return new WebAssembly.Instance(module) }) .then(instance => { factorial = instance.exports._Z4facti; console.log('Test the output in Brower Console by using factorial(n)'); }); </script>
代码说明
Javascript浏览器API fetch用于获取factorial.wasm的内容。
使用 arrayBuffer() 将内容转换为字节。
该模块是通过调用 WebAssembly.compile(mod) 从字节创建的。
模块的实例是使用 new 创建的
WebAssembly.Instance(模块)
使用 WebAssembly.Module.exports() 将阶乘函数导出 _Z4facti 分配给变量阶乘。
例子
这里是 module.html 以及 javascript 代码 -
模块.html
<!doctype html> <html> <head> <meta charset="utf-8"> <title>WebAssembly Module</title> </head> <body> <script> let factorial; fetch("factorial.wasm") .then(bytes => bytes.arrayBuffer()) .then(mod => WebAssembly.compile(mod)) .then(module => {return new WebAssembly.Instance(module) }) .then(instance => { factorial = instance.exports._Z4facti; console.log('Test the output in Browser Console by using factorial(n)'); }); </script> </body> </html>
输出
在浏览器中执行 module.html 以查看输出 -
WebAssembly - 验证
在本章中,我们将讨论 web assembly.validate() 函数,该函数将验证 .wasm 输出。当我们编译 C、C++ 或 Rust 代码时,.wasm 可用。
您可以使用以下工具来获取 wasm 代码。
Wasm Fiddler,可在https://wasdk.github.io/WasmFiddle/获取
WebAssembly Explorer,可从https://mbebenita.github.io/WasmExplorer/ 获取。
句法
语法如下 -
WebAssembly.validate(bufferSource);
参数
bufferSource - bufferSource 具有来自 C、C++ 或 Rust 程序的二进制代码。它采用 typedarray 或 ArrayBuffer 的形式。
返回值
如果 .wasm 代码有效,该函数将返回 true,否则返回 false。
让我们尝试一个例子。转到Wasm fiddler(位于https://wasdk.github.io/WasmFiddle/),输入您选择的 C 代码并下载 wasm 代码。
红色标记的块是 C 代码。单击中心的“构建”按钮来执行代码。
单击 Wasm 按钮下载 .wasm 代码。将 .wasm 保存在您的末尾,让我们使用它进行验证。
例子
例如:validate.html
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Testing WASM validate()</title> </head> <body> <script> fetch('program.wasm').then(res => res.arrayBuffer() ).then(function(testbytes) { var valid = WebAssembly.validate(testbytes); if (valid) { console.log("Valid Wasm Bytes!"); } else { console.log("Invalid Wasm Code!"); } }); </script> </body> </html>
我已在 wamp 服务器中托管了上述 .html 文件以及下载的 .wasm 文件。这是在浏览器中测试时的输出。
输出
输出如下 -
WebAssembly - 文本格式
WebAssembly 的代码采用二进制格式,称为 WASM。您还可以在 WebAssembly 中获取文本格式,它称为 WAT(WebAssembly 文本格式)。作为开发人员,您不应该在 WebAssembly 中编写代码,相反,您必须将 C、C++ 和 Rust 等高级语言编译为 WebAssembly。
瓦特代码
让我们逐步编写WAT代码。
步骤 1 - WAT 的起点是声明模块。
(module)
步骤 2 - 现在让我们以函数的形式向其添加一些功能。
该函数声明如下 -
(func <parameters/result> <local variables> <function body>)
该函数以 func 关键字开头,后跟参数或结果。
参数/结果
参数和返回值作为结果。
wasm 支持的参数可以是以下类型 -
- i32:32 位整数
- i64:64 位整数
- f32:32 位浮点数
- f64:64 位浮点数
函数的参数编写如下 -
- (参数 i32)
- (参数 i64)
- (参数 f32)
- (参数 f64)
结果将写成如下 -
- (结果 i32)
- (结果 i64)
- (结果 f32)
- (结果 f64)
带有参数和返回值的函数定义如下 -
(func (param i32) (param i32) (result i64) <function body>)
局部变量
局部变量是您的函数中需要的变量。该函数的局部值将定义如下 -
(func (param i32) (param i32) (local i32) (result i64) <function body>)
函数体
函数体是要执行的逻辑。最终的程序将如下所示 -
(module (func (param i32) (param i32) (local i32) (result i64) <function body>) )
步骤 3 - 读取和设置参数和局部变量。
要读取参数和局部变量,请使用get_local和set_local命令。
例子
(module (func (param i32) (param i32) (local i32) (result i64) get_local 0 get_local 1 get_local 2 ) )
根据函数签名,
get_local 0将给出参数 i32
get_local 1将给出下一个参数param i32
get_local 2将给出本地值 i32
您也可以在参数之前使用名称,并在名称前加上美元符号作为前缀,而不是使用 0、1、2 等数值来引用参数和局部变量。
以下示例显示如何将名称与参数和局部变量一起使用。
例子
(module (func (param $a i32) (param $b i32) (local $c i32) (result i64) get_local $a get_local $b get_local $c ) )
步骤 4 - 函数体中的指令和执行。
wasm 中的执行遵循堆栈策略。执行的指令被一条一条地送入堆栈。例如,指令 get_local $a 将推送值,它在堆栈上读取。
像i32.add这样添加的指令将从堆栈中弹出元素。
(func (param $a i32) (param $b i32) get_local $a get_local $b i32.add )
i32.add的指令是($a+$b)。i32.add 的最终值将被推送到堆栈上,并将其分配给结果。
如果函数签名声明了结果,则执行结束时堆栈中应该有一个值。如果没有结果参数,则堆栈最后必须为空。
因此,最终代码和函数体如下 -
(module (func (param $a i32) (param $b i32) (result i32) get_local $a get_local $b i32.add ) )
步骤 5 - 调用该函数。
函数体的最终代码如步骤 4 所示。现在,要调用该函数,我们需要导出它。
要导出函数,可以使用 0,1 等索引值来完成,但是,我们也可以给出名称。该名称将以 $ 为前缀,并将其添加在 func 关键字之后。
例子
(module (func $add (param $a i32) (param $b i32) (result i32) get_local $a get_local $b i32.add ) )
必须使用 export 关键字导出函数 $add ,如下所示 -
(module (func $add (param $a i32) (param $b i32) (result i32) get_local $a get_local $b i32.add ) (export "add" (func $add)) )
要在浏览器中测试上述代码,您必须将其转换为二进制形式(.wasm)。请参阅下一章,了解如何将.WAT 转换为 .WASM。
WebAssembly - 将 WAT 转换为 WASM
在上一章中,我们了解了如何以.wat(即 WebAssembly 文本格式)编写代码。WebAssembly 文本格式不能直接在浏览器中工作,您需要将其转换为二进制格式,即 WASM 才能在浏览器中工作。
WASM 转 WASM
让我们将 .WAT 转换为 .WASM。
我们要使用的代码如下 -
(module (func $add (param $a i32) (param $b i32) (result i32) get_local $a get_local $b i32.add ) (export "add" (func $add)) )
现在,转到WebAssembly Studio ,可从https://webassemble.studio/获取。
当您点击链接时,您应该看到类似这样的内容 -
单击“Empty Wat”项目,然后单击底部的“创建”按钮。
它将带您到一个空项目,如下所示 -
单击 main.wat 并将现有代码替换为您的代码,然后单击“保存”按钮。
保存后,单击构建以转换为 .wasm -
如果构建成功,您应该看到创建的 .wasm 文件,如下所示 -
找到 main.wasm 文件并在 .html 文件中使用它来查看输出,如下所示。
例如 - add.html
<!doctype html> <html> <head> <meta charset="utf-8"> <title>WebAssembly Add Function</title> </head> <body> <script> let sum; fetch("main.wasm") .then(bytes => bytes.arrayBuffer()) .then(mod => WebAssembly.compile(mod)) .then(module => { return new WebAssembly.Instance(module) }) .then(instance => { sum = instance.exports.add(10,40); console.log("The sum of 10 and 40 = " +sum); }); </script> </body> </html>
函数 add 被导出,如代码所示。传递的参数是 2 个整数值 10 和 40,它返回它们的总和。
输出
输出显示在浏览器中。
WebAssembly - 动态链接
动态链接是两个或多个模块在运行时链接在一起的过程。
为了演示动态链接的工作原理,我们将使用 C 程序并使用 Ecmascript sdk 将其编译为 wasm。
所以这里我们有 -
测试1.c
int test1(){ return 100; }
测试2.c
int test2(){ return 200; }
主程序
#include <stdio.h> int test1(); int test2(); int main() { int result = test1() + test2(); return result; }
在 main.c 代码中,它使用了 test1() 和 test2(),它们在 test1.c 和 test2.c 中定义。让我们检查一下如何在 WebAssembly 中链接这些模块。
编译上述代码的命令如下:使用SIDE_MODULE =1进行动态链接,如命令所示。
emcc test1.c test2.c main.c -s SIDE_MODULE=1 -o maintest.wasm
使用 WasmtoWat(可在https://webassemble.github.io/wabt/demo/wasm2wat/获取)将获得 maintest.wasm 的 WebAssembly 文本格式。
(module (type $t0 (func (result i32))) (type $t1 (func)) (type $t2 (func (param i32))) (type $t3 (func (param i32 i32) (result i32))) (import "env" "stackSave" (func $env.stackSave (type $t0))) (import "env" "stackRestore" (func $env.stackRestore (type $t2))) (import "env" "__memory_base" (global $env.__memory_base i32)) (import "env" "__table_base" (global $env.__table_base i32)) (import "env" "memory" (memory $env.memory 0)) (import "env" "table" (table $env.table 0 funcref)) (func $f2 (type $t1) (call $__wasm_apply_relocs) ) (func $__wasm_apply_relocs (export "__wasm_apply_relocs") (type $t1)) (func $test1 (export "test1") (type $t0) (result i32) (local $l0 i32) (local.set $l0 (i32.const 100) ) (return (local.get $l0) ) ) (func $test2 (export "test2") (type $t0) (result i32) (local $l0 i32) (local.set $l0 (i32.const 200)) (return (local.get $l0) ) ) (func $__original_main (export "__original_main") (type $t0) (result i32) (local $l0 i32) (local $l1 i32) (local $l2 i32) (local $l3 i32) (local $l4 i32) (local $l5 i32) (local $l6 i32) (local $l7 i32) (local $l8 i32) (local $l9 i32) (local.set $l0(call $env.stackSave)) (local.set $l1 (i32.const 16)) (local.set $l2 (i32.sub (local.get $l0) (local.get $l1))) (call $env.stackRestore (local.get $l2) ) (local.set $l3(i32.const 0)) (i32.store offset=12 (local.get $l2) (local.get $l3)) (local.set $l4 (call $test1)) (local.set $l5 (call $test2)) (local.set $l6 (i32.add (local.get $l4) (local.get $l5))) (i32.store offset=8 (local.get $l2) (local.get $l6)) (local.set $l7 (i32.load offset=8 (local.get $l2))) (local.set $l8 (i32.const 16)) (local.set $l9 (i32.add (local.get $l2) (local.get $l8))) (call $env.stackRestore (local.get $l9)) (return(local.get $l7)) ) (func $main (export "main") (type $t3) (param $p0 i32) (param $p1 i32) (result i32) (local $l2 i32) (local.set $l2 (call $__original_main)) (return (local.get $l2)) ) (func $__post_instantiate (export "__post_instantiate") (type $t1) (call $f2)) (global $__dso_handle (export "__dso_handle") i32 (i32.const 0)) )
WebAssembly 文本格式有一些定义的导入,如下所示 -
(import "env" "stackSave" (func $env.stackSave (type $t0))) (import "env" "stackRestore" (func $env.stackRestore (type $t2))) (import "env" "__memory_base" (global $env.__memory_base i32)) (import "env" "__table_base" (global $env.__table_base i32)) (import "env" "memory" (memory $env.memory 0)) (import "env" "table" (table $env.table 0 funcref))
这是在 emcc(emscripten sdk) 编译代码时添加的,它处理 WebAssembly 中的内存管理。
处理导入和导出
现在要查看输出,我们必须定义您可以在 .wat 代码中看到的导入 -
(import "env" "stackSave" (func $env.stackSave (type $t0))) (import "env" "stackRestore" (func $env.stackRestore (type $t2))) (import "env" "__memory_base" (global $env.__memory_base i32)) (import "env" "__table_base" (global $env.__table_base i32)) (import "env" "memory" (memory $env.memory 0)) (import "env" "table" (table $env.table 0 funcref))
上述术语解释如下 -
env.stackSave - 用于堆栈管理,这是由 emscripten 编译代码提供的功能。
env.stackRestore - 它用于堆栈管理,这是由 emscripten 编译代码提供的功能。
env.__memory_base - 它是一个不可变的 i32 全局偏移量,在 env.memory 中使用并为 wasm 模块保留。模块可以在其数据段的初始化程序中使用此全局变量,以便将它们加载到正确的地址。
env.__table_base - 它是一个不可变的 i32 全局偏移量,在 env.table 中使用并为 wasm 模块保留。模块可以在其表元素段的初始化程序中使用此全局变量,以便将它们加载到正确的偏移量处。
env.memory - 这将包含 wasm 模块之间共享所需的内存详细信息。
env.table - 这将包含 wasm 模块之间需要共享的表详细信息。
导入必须在 javascript 中定义如下 -
var wasmMemory = new WebAssembly.Memory({'initial': 256,'maximum': 65536}); const importObj = { env: { stackSave: n => 2, stackRestore: n => 3, //abortStackOverflow: () => { throw new Error('overflow'); }, table: new WebAssembly.Table({ initial: 0, maximum: 65536, element: 'anyfunc' }), __table_base: 0, memory: wasmMemory, __memory_base: 256 } };
例子
以下是使用 WebAssembly.instantiate 内的 importObj 的 javascript 代码。
<!DOCTYPE html> <html> <h