首页
/ uBlock Origin WebAssembly 编译指南:从 .wat 到 .wasm 的构建流程与共享内存模型解析

uBlock Origin WebAssembly 编译指南:从 .wat 到 .wasm 的构建流程与共享内存模型解析

2026-09-04 11:57:20作者:冯梦姬Eddie

uBlock Origin 的过滤器引擎中,两个性能关键的字符串/主机名数据结构(HNTrie 与 BidiTrie)提供了可选的 WebAssembly 加速实现。本文以 src/js/wasm/README.md 为核心,完整讲解该目录下 .wat 文本格式模块如何用 wat2wasm 编译为 .wasm 二进制模块,并结合仓库源码说明这些模块在运行时如何被加载、如何与 JavaScript 侧共享同一块线性内存,以及编译产物的验证前提。

目录内容与编译目标

.wat 与 .wasm 源文件

README 面向的是代码审查者(code reviewers),其核心信息是:src/js/wasm/ 目录下的所有 .wasm 文件都是由对应的 .wat 文件编译而来,二者一一对应。当前目录中实际存在两组配对文件:

源文件(文本格式 WAT) 产物(二进制 WASM) 被谁使用
hntrie.wat hntrie.wasm src/js/hntrie.js 中的 HNTrieContainer(主机名倒排 Trie,用于过滤器字典的主机名匹配)
biditrie.wat biditrie.wasm src/js/biditrie.js 中的双向 Trie(用于通配符主机名的双向前缀/后缀匹配)

两个 .wat 文件的头部注释都明确写着 Description: WebAssembly code used by src/js/hntrie.js(或 src/js/biditrie.js)以及 How to compile: See README.md in this directory.,这与 README 的编译说明互相印证。

编译命令:wat2wasm

README 给出的标准编译命令(以 hntrie.wat/hntrie.wasm 为例)是:

wat2wasm hntrie.wat -o hntrie.wasm

编译的唯一前提假设是:命令必须在 src/js/wasm/ 目录内执行(即工作目录就是 README 所在目录),这样输入/输出文件才能按裸文件名正确解析。同理,编译 biditrie 时执行 wat2wasm biditrie.wat -o biditrie.wasm 即可。

wat2wasm 工具从哪里来

README 指出,wat2wasm 是官方 WebAssembly 项目的 wabt(WebAssembly Binary Toolkit) 工具链中的一员,可以从 wabt 官方发布(releases)页面下载(README 中引用的版本 tag 为 1.0.29)。如果不想本地安装,也可以使用 wabt 项目提供的在线 wat2wasm 演示页面:将 .wat 文件的完整内容粘贴到 WAT 输入面板,点击 "Download" 按钮即可获得编译出的 .wasm 文件。

也就是说,这套构建流程没有依赖仓库内的 Makefile 目标,而是刻意保持极简:一条 wat2wasm 命令即可完成产物重建,这也是审查者能够在本地快速验证 .wasm.wat 一致性的原因。

WAT 模块的结构:导入、导出与共享内存

理解编译产物之前,有必要看一下 WAT 源文件本身。以 hntrie.wat 为例,模块头部声明了两个来自宿主环境的导入:

(func $growBuf (import "imports" "growBuf"))
(memory (import "imports" "memory") 1)
  • memory:模块不声明自己的内存,而是直接导入 JS 侧提供的内存。这是整个方案的要害——WASM 代码与 JS 代码操作的是同一块字节缓冲,HNTrieContainer 在 JS 侧维护的 Trie 数据对 WASM 函数完全可见,无需任何跨边界拷贝。
  • growBuf:导入的回调函数。当 add 函数判断内存不足(如字符数据区剩余空间小于 24 字节,或缓冲区尾部剩余空间不足 256 字节)时,会调用 growBuf 让 JS 侧负责扩容,然后继续执行。

模块导出两个公共函数,与 JS 侧类方法一一对应:

  • matches(iroot) -> i32:判断当前设置的 needle(缓冲区 0-254 字节存放的主机名)是否匹配指定根偏移处的 Trie,返回匹配索引,未命中返回 -1
  • add(iroot) -> i32:向指定根单元的新增一个主机名,返回 0(未添加)或 1(已添加)。

这两个函数内部又依赖三个私有函数 addCelladdLeafCelladdSegment,分别负责追加单元、追加叶子链(超过 127 字符的分段)、以及把 needle 中的一段字符数据复制进字符数据区并返回段描述符(isegchar << 8 | lsegchar - lsegend)。

biditrie.wat 的结构类似但更复杂:除 memory 外还导入了一个三参数回调 extraHandler,并导出 matches(icell, ai) 等更多字符串操作入口,以支持双向 Trie 的匹配语义。

缓冲区内存布局

两个 WAT 模块都依赖一套约定好的内存布局。hntrie.wat 头部注释写明(字节偏移):

0-254:   正在处理的 needle
  255:   needle 长度
256-259: Trie 数据区起点 (trie0)
260-263: Trie 数据区终点 (trie1)
264-267: 字符数据区起点 (char0)
268-271: 字符数据区终点 (char1)
   272:  Trie 数据区实际开始位置

这与 src/js/hntrie.js 中 JS 侧的常量定义完全一致(TRIE0_SLOT = 256 >>> 2CHAR0_SLOTTRIE0_START = 272 等),说明 WAT 与 JS 是按同一份布局契约编写的:WASM 中那些看似魔法的数字 255260264268272,全部是这份契约中的槽位偏移。Trie 的每个单元占 3 个 i32(idownirightv,共 12 字节),v 的位布局为:高 24 位是字符段偏移,0x80 是"边界位"(表示该主机名在此结束),低 7 位是段内剩余字符数。

biditrie.wat 的布局则不同:前 8192 字节是固定大小的 haystack 区(HAYSTACK_SIZE = 8192),其后依次是 TRIE0_SLOT(8196)、TRIE1_SLOT(8200)、CHAR0_SLOT(8204)、CHAR1_SLOT(8208) 等元数据槽位,Trie 数据从偏移 8224 开始。

JS 侧如何加载 WASM 模块

编译产物的运行时加载路径是:src/js/start.js 在启动时判断 vAPI.canWASM 且用户没有通过隐藏设置 disableWebAssembly 禁用 WebAssembly,随后构造一个 fetcher 并调用静态过滤引擎的 enableWASM

// src/js/start.js(节选)
const wasmModuleFetcher = function(path) {
    return fetch(`${path}.wasm`, { mode: 'same-origin' }).then(
        WebAssembly.compileStreaming
    ).catch(reason => {
        ubolog(reason);
    });
};
staticNetFilteringEngine.enableWASM(wasmModuleFetcher, './js/wasm/').then(result => {
    ...
});

注意 fetcher 的语义:传入目录前缀 ./js/wasm/,实际请求的文件名是 `${path}hntrie`.wasm`${path}biditrie`.wasm,即扩展名由 fetcher 补全。src/js/static-net-filtering.js 中的 enableWASM 会依次对本引擎内的三个 Trie 容器(bidiTrie、origHNTrieContainer、destHNTrieContainer)执行 WASM 化。

hntrie.jsenableWASM(wasmModuleFetcher, path) 做了四件关键事:

  1. 端序检查:底部的 getWasmModule 先用 Uint32Array[0]=1 检查首个字节是否为 1,确认 CPU 原生小端序后才继续加载——因为 JS 侧直接用 Uint32Array 视图操作同一块内存,大端机器上字节序会错位;
  2. 创建共享内存new WebAssembly.Memory({ initial: 2 }) 创建 2 页(128 KiB)初始内存,并把 JS 现有的 this.buf 整体拷贝进去,然后把 this.buf/this.buf32 重新指向 memory.buffer 上的新视图,从此两侧共用同一块缓冲区;
  3. 注入导入:以 { imports: { memory, growBuf: this.growBuf.bind(this, 24, 256) } } 实例化模块,正好对应 WAT 里的两个导入项;
  4. 替换热路径方法:成功后执行 this.matches = instance.exports.matches; this.add = instance.exports.add;,JS 侧的原生实现(matchesJS/addJS)被旁路但保留在原型上作为回退。

biditrie.jsenableWASM 流程同构,导入项换成 { memory, extraHandler: this.extraHandler },被替换的方法则包括 matchesstartsWithindexOflastIndexOf 四个。

WASM 是纯可选的加速层

hntrie.js 底部注释说得非常直白:WASM 模块是"entirely optional"的,一旦模块不可用(环境不支持、fetch 失败、端序不符、实例化失败),enableWASM 返回 false,容器继续使用 JS 实现。另外由于 WebAssembly.Memory 只能增长不能收缩,shrinkBuf()wasmMemory !== null 时直接跳过,而 resizeBuf/reallocateBuf 在 WASM 模式下改走 memory.grow(pageCount) 的扩容路径——这也是 WAT 里必须导入 growBuf 回调的原因:WASM 代码不能自己移动/重新分配缓冲区,只能请 JS 宿主来做。

审查与重新生成产物的完整流程

综合 README 与源码,代码审查者验证或重新生成 src/js/wasm/ 产物时的完整流程是:

  1. 进入仓库的 src/js/wasm/ 目录;
  2. 取得 wabt 工具链中的 wat2wasm(从官方 wabt 发布页下载对应平台二进制,或使用 wabt 在线 demo);
  3. 执行 wat2wasm hntrie.wat -o hntrie.wasmwat2wasm biditrie.wat -o biditrie.wasm
  4. 用产物与仓库中已提交的 .wasm 文件比对,确认 .wat 源文件与提交的二进制一致。

需要注意的适用前提与限制:

  • 编译必须在 src/js/wasm/ 目录内执行,否则裸文件名的输入/输出会找不到文件;
  • 该目录的构建是"手工"的,不接入仓库根目录的 Makefile 目标,产物以提交文件形式随源码分发;
  • 运行时侧仅在小端 CPU、浏览器支持 WebAssembly 且未设置 disableWebAssembly 隐藏开关时才会真正启用这些模块,其他场景静默回退到等价的 JS 实现,功能上无差异。

这套".wat 可读源码 + 单命令编译 + 共享内存导入 + JS 完整回退"的设计,让 uBlock Origin 在保持代码可审查性的同时,把最耗 CPU 的 Trie 匹配/插入热路径交给了 WebAssembly 执行。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384