Skip to content

wasm

3 posts with the tag “wasm”

Vite 加载 wasm-pack 构建ES模块

学习rust 构建 WebAssembly 的官方教程,教程中是使用 webpack 作为 Javascript 打包器和开发服务器的(注意,使用 Rust 和 WebAssembly 不要求 webpack 或者其他类似工具,只是为了开发方便)。但不太喜欢 webpack 配置太过繁琐,而以前常用的 Parcel 对 WebAssembly 支持不太友好,而 Vite 正好可以满足需要,所以使用了Vite, 下面记录的配置。

在学习一段时间zig来构建wasm后,发现 zig 语言本身用起来的是很舒服,可也有些问题,主要还是语言还没有发布1.0版本,语言生态 环境还不够完善,就连 build.zig 构建 API 也在频繁变动中,Github.com 上的相关包在 zig 最新版本也无法顺利通过构建。其他的相关包,就更少了。 相比于此, rust 社区在2018开始就将 WebAssembly 作为,注重改善编程体验几个不同领域之一(其他还有Command Line、Networking, Embedded)。用 rust 来开发 WebAssembly 过程要舒适非常多,相关的生态也非常完备,社区活跃度也很高。

wasm-pack build 命令支持多个 --target 参数,这里选择的是 web, 输出可以在浏览器中作为ES模块原始导入的JS,其他几个参数可以相见官方文档Deploying Rust and WebAssembly

Vite 5.0 版本对 WebAssembly 的 ES 模块集成提案 尚未支持。 请使用 vite-plugin-wasm 或其他社区上的插件来处理。

将构建出来的本地wasm package 添加到 package.json 中:

"dependencies": {
"wasm-game-of-life": "file:../pkg"
},
Terminal window
yarn add --dev vite@5.0.0

根目录下添加配置文件vite.config.js:

import { defineConfig } from 'vite'
import wasm from "vite-plugin-wasm";
import topLevelAwait from "vite-plugin-top-level-await";
export default defineConfig({});
Terminal window
yarn add --dev vite-plugin-wasm
yarn add --dev vite-plugin-top-level-await
import wasm from "vite-plugin-wasm";
import topLevelAwait from "vite-plugin-top-level-await";
export default defineConfig({
plugins: [
wasm(),
topLevelAwait()
],
optimizeDeps: {
exclude: ["wasm-game-of-life"],
},
});

完成代码放在Github

Zig Wasm Memory Canvas

展示HTML canvas、wasm memory 和 zig 交互的一个小示例。

这个示例来自https://github.com/daneelsan/minimal-zig-wasm-canvas,而这示例本来修改来自https://wasmbyexample.dev/examples/reading-and-writing-graphics/reading-and-writing-graphics.rust.en-us.html。对于当前这个本身,它如大多数 zig 项目一样,zig 命令是你唯一的依赖项。

main.zig 定义了一个全局的 8X8 像素数组: checkerboard_buffer。并导出三个函数:getCheckerboardBufferPointer()getCheckerboardSize()colorCheckerboard(...), getCheckerboardBufferPointer() 返回一个指向checkerboard_buffer开始处的指针,这将在script.js 被使用。getCheckerboardSize() 返回棋盘格子数量。 colorCheckerboard(...)函数根据传入的颜色参数,来改变棋盘格的颜色。

main.zig 通过build.zig被编译成 wasm模块 checkerboard.wasm

index.html 定义了一个canvas,并指定样式:image-rendering: pixelated; width: 50%,这是因为main.zig是定义的一个8x8 象素的棋盘格,所以需要通过 CSS属性image-rendering 来对 canvas 画出的图像进行放大显示,以获得一个清晰的棋盘格来清楚的展示颜色的变化。

script.js 文件创建了一个名为memoryWebAssembly.Memory ,它存储的是WebAssembly实例所访问内存的原始字节码。下一步是使用WebAssembly.instantiateStreaming()方法编译和示例化获取到的wasm模块,返回的一个Promise对象,它包含一个instanace字段(WebAssembly.instanace) , 它保存有从checkerboard.wasm 导出的所有方法。

接下来是:

  • WebAssembly.Memory memory 创建一个Uint8Array数组:wasmMemoryArray

  • 获取到index.html中定义的canvas checkerboard, 并使用getCheckerboardSize()获取到的棋盘格数来指定canvas 的widthheight,接着使用canvas的context 创建一个 ImageData 对象:ImageData

  • 在循环中更新棋盘格的颜色:

    • 传入RGB颜色参数,调用colorCheckerboard(...),这会修改checkerboard_buffer中保存的值
    • checkerboard_buffer中的内容,是一个段wasmMemoryArray,可以通过getCheckerboardBufferPointer获取内存起始地址 和 8 _ 8 _ 4 字节的偏移长度来获取
    • 将获取到的内容,放入到imageData
    • imageData 写入到canvas

build.zig 指定的为一个构建目标是:wasm32-freestanding-musl

用于构建项目的最新zig版本是:

Terminal window
$ zig version
0.12.0-dev.817+54e7f58fc

构建wasm 模块,运行:

Terminal window
$ zig build
$ ls zig-out/lib/
zig-wasm-canvas.wasm

注意: 为了运行将内存导入到 WASM 二进制文件中,构建时需要指定--import-memory参数

在项目目录上,启动一个服务:

Terminal window
$ python3 -m http.server

这时在你的浏览器中浏览:localhost:8000,就可以看到棋盘格在改变颜色。

Zig in Webassembly

Zig 是一种通用编程语言和工具链,用于维护健壮、优化和可重用的软件,Zig 支持开箱即用的 Webassembly 构建,Zig 的实现是通过使用 LLVM 来提供编译目标,对Webassembly System Interface(WASI) 的支持尚处于积极开发中,可以参见官方文档

Zig 从 0.4.0 开始提供对 WebAssembly 的实现

WebAssembly 是一种运行在现代网络浏览器中的新型代码,并且提供新的性能特性和效果。它设计的目的不是为了手写代码而是为诸如 C、C++和 Rust 等低级源语言提供一个高效的编译目标。

Webassembly 1.0 已经在4个主要浏览器引擎中发布Roadmap

为了进一步推动模块化 WebAssembly 生态系统,Mozilla、Fastly、英特尔和红帽公司携手成立了ByteCode Alliance(字节码联盟)。ByteCode Alliance 是一个非营利组织,致力于在 WebAssembly 和 WebAssembly System Interface(WASI) 等标准的基础上创建安全的新软件基础。

作为 W3C WebAssembly Community Group 中的一项开放标准,WebAssembly 是为下列目标而生的:

  • 快速、高效、可移植——通过利用常见的硬件能力,WebAssembly 代码在不同平台上能够以接近本地速度运行。
  • 可读、可调试——WebAssembly 是一门低阶语言,但是它有确实有一种人类可读的文本格式(其标准即将得到最终版本),这允许通过手工来写代码,看代码以及调试代码。
  • 保持安全——WebAssembly 被限制运行在一个安全的沙箱执行环境中。像其他网络代码一样,它遵循浏览器的同源策略和授权策略。
  • 不破坏网络——WebAssembly 的设计原则是与其他网络技术和谐共处并保持向后兼容。

更多参见: MDN Webassembly概念

1 life.zig 导出了三个函数

// Advance the world by strong mutation
export fn advance() u32 {
....
WORLD.cells = cell_buf;
return num_changed;
}
export fn set_cell(index: u32) void {
if (index > NUM_CELLS) return;
WORLD.cells[index] = .Alive;
}
export fn get_char(index: u32) u32 {
if (index > NUM_CELLS) return '◻';
return switch (WORLD.cells[index]) {
.Dead => {
return '◻';
},
.Alive => {
return '◼';
},
};
}

2 在 life.js 中会调用这些 WASM 导出的方法

// Load WebAssembly module `life.wasm`
// https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/instantiateStreaming
Game = await WebAssembly.instantiateStreaming(
fetch("life.wasm"), importObject
)
set_cell = Game.instance.exports.set_cell;
advance = Game.instance.exports.advance;
get_char = Game.instance.exports.get_char;
wasm_loaed = true
...

3 在 index.html 加载life.js

<html lang="en">
<head>
<title>Life Demo</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<input type="button" id="button" label="button" value="BUTTON" />
<pre id="life_pre"></pre>
</body>
<script src="life.js"></script>
</html>

以上这些就是使用Zig 创建 WebAssemble 程序的代码。(非常感谢sleibrock/zigtoys)

4 构建 life.wasm

Terminal window
$ zig build-lib life.zig -target wasm32-freestanding -dynamic -rdynamic -O ReleaseSmall

更多构建说明参见 Ziglang.org

5 运行

启动一个本地web server:

Terminal window
$ python3 -m http.server

在浏览器中,浏览:http://localhost:8000 demo

  1. Webassembly
  2. Webassembly developer guide
  3. MDN Webassembly
  4. Zig language Reference
  5. WebAssembly with Zig, Part 1
  6. WebAssembly With Zig, Pt. II
  7. ByteCode Alliance