如何用 WebAssembly 编写 'Hello World'

通过这个循序渐进的教程,开始用人类可读的文本编写 WebAssembly。
109 位读者喜欢这篇文章。
Beginners to Open Source theme: Hello World on bread

照片由 Windell Oskay 拍摄,Jen Wike Huger 修改

WebAssembly 是一种字节码格式,几乎每个浏览器都可以将其编译为其宿主系统的机器代码。与 JavaScript 和 WebGL 一起,WebAssembly 满足了在 Web 浏览器中移植应用程序以实现平台无关使用的需求。作为 C++ 和 Rust 的编译目标,WebAssembly 使 Web 浏览器能够以接近原生速度执行代码。

当您谈论 WebAssembly 应用程序时,您必须区分三种状态

  1. 源代码(例如,C++ 或 Rust): 您有一个用兼容语言编写的应用程序,您想在浏览器中执行它。
  2. WebAssembly 字节码: 您选择 WebAssembly 字节码作为您的编译目标。结果,您得到一个 .wasm 文件。
  3. 机器代码(操作码): 浏览器加载 .wasm 文件并将其编译为其宿主系统的相应机器代码。

WebAssembly 也有一种文本格式,以人类可读的文本表示二进制格式。为了简单起见,我将其称为 WASM-文本。WASM-文本可以比作高级汇编语言。当然,您不会基于 WASM-文本编写完整的应用程序,但了解其底层工作原理是很有好处的(特别是对于调试和性能优化)。

本文将指导您在 WASM-文本中创建经典的 Hello World 程序。

创建 .wat 文件

WASM-文本文件通常以 .wat 结尾。从头开始,创建一个名为 helloworld.wat 的空文本文件,用您最喜欢的文本编辑器打开它,然后粘贴以下内容

(module
    ;; Imports from JavaScript namespace
    (import  "console"  "log" (func  $log (param  i32  i32))) ;; Import log function
    (import  "js"  "mem" (memory  1)) ;; Import 1 page of memory (54kb)
    
    ;; Data section of our module
    (data (i32.const 0) "Hello World from WebAssembly!")
    
    ;; Function declaration: Exported as helloWorld(), no arguments
    (func (export  "helloWorld")
        i32.const 0  ;; pass offset 0 to log
        i32.const 29  ;; pass length 29 to log (strlen of sample text)
        call  $log
        )
)

WASM-文本格式基于 S-表达式。为了实现交互,JavaScript 函数使用 import 语句导入,WebAssembly 函数使用 export 语句导出。对于此示例,从 console 模块导入 log 函数,该函数接受两个 i32 类型的参数作为输入,并需要一页内存 (64KB) 来存储字符串。

字符串将被写入偏移量为 0data 部分。data 部分是您内存的覆盖,内存是在 JavaScript 部分分配的。

函数用关键字 func 标记。进入函数时,堆栈为空。函数参数在调用另一个函数之前被推入堆栈(此处为偏移量和长度)(参见 call $log)。当函数返回 f32 类型时(例如),离开函数时,f32 变量必须保留在堆栈上(但本例中并非如此)。

创建 .wasm 文件

WASM-文本和 WebAssembly 字节码具有 1:1 的对应关系。这意味着您可以将 WASM-文本转换为字节码(反之亦然)。您已经有了 WASM-文本,现在您想创建字节码。

转换可以使用 WebAssembly Binary Toolkit (WABT) 执行。克隆该链接的存储库并按照安装说明进行操作。

构建工具链后,通过打开控制台并输入以下内容将 WASM-文本转换为字节码

wat2wasm helloworld.wat -o helloworld.wasm

您还可以使用以下命令将字节码转换为 WASM-文本

wasm2wat helloworld.wasm -o helloworld_reverse.wat

.wasm 文件创建的 .wat 文件不包含任何函数或参数名称。默认情况下,WebAssembly 使用索引标识函数和参数。

编译 .wasm 文件

目前,WebAssembly 仅与 JavaScript 共存,因此您必须编写一个简短的脚本来加载和编译 .wasm 文件并执行函数调用。您还需要定义将在 WebAssembly 模块中导入的函数。

创建一个空的文本文件并将其命名为 helloworld.html,然后打开您最喜欢的文本编辑器并粘贴以下内容

<!DOCTYPE  html>
<html>
  <head>
    <meta  charset="utf-8">
    <title>Simple template</title>
  </head>
  <body>
    <script>
    
      var memory = new  WebAssembly.Memory({initial:1});

      function  consoleLogString(offset, length) {
        var  bytes = new  Uint8Array(memory.buffer, offset, length);
        var  string = new  TextDecoder('utf8').decode(bytes);
        console.log(string);
      };

      var  importObject = {
        console: {
          log:  consoleLogString
        },
        js : {
          mem:  memory
        }
      };
      
      WebAssembly.instantiateStreaming(fetch('helloworld.wasm'), importObject)
      .then(obj  => {
        obj.instance.exports.helloWorld();
      });
      
    </script>
  </body>
</html>

WebAssembly.Memory(...) 方法返回一页大小为 64KB 的内存。函数 consoleLogString 根据长度和偏移量从该内存页读取字符串。这两个对象都作为 importObject 的一部分传递给您的 WebAssembly 模块。

在您可以运行此示例之前,您可能需要允许 Firefox 访问此目录中的文件,方法是在地址栏中键入 about:config 并将 privacy.file_unique_origin 设置为 true

注意: 这将使您容易受到 CVE-2019-11730 安全问题的攻击。

现在,在 Firefox 中打开 helloworld.html 并输入 Ctrl+K 以打开开发者控制台。

了解更多

这个 Hello World 示例只是 MDN 理解 WebAssembly 文本格式 文档中详细教程之一。如果您想了解更多关于 WebAssembly 及其底层工作原理的信息,请查看这些文档。

接下来阅读什么
标签
User profile image.
Stephan 是一位技术爱好者,他欣赏开源,因为它能深入了解事物的运作方式。Stephan 在工业自动化软件这个大部分是专有领域的公司担任全职支持工程师。如果可能,他会从事基于 Python 的开源项目、撰写文章或骑摩托车。

评论已关闭。

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.