跳到主要内容

文本格式 WAT

WebAssembly 文本格式(WebAssembly Text Format,简称 WAT)是 WebAssembly 的可读表示形式,使用 S 表达式语法。本章将详细介绍 WAT 语法和使用方法。

S 表达式基础

WAT 使用 S 表达式(S-expressions)表示 WebAssembly 模块结构。S 表达式是一种简单的树形结构表示方法,每个节点用括号包围:

(node-type child1 child2 ...)

最简单的模块

一个空的 WebAssembly 模块:

(module)

编译后的二进制只包含 8 字节的头部:

00 61 73 6d  ; 魔数 "\0asm"
01 00 00 00 ; 版本号 1

函数定义

基本函数结构

函数包含签名、局部变量和函数体:

(func <函数名>
(param <类型> ...) ;; 参数
(result <类型>) ;; 返回值
(local <类型>) ;; 局部变量
<指令> ... ;; 函数体
)

参数和返回值

;; 无参数无返回值
(func $nop)

;; 有参数无返回值
(func $print (param $msg i32)
;; ...
)

;; 有参数有返回值
(func $add (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add)

;; 多返回值(需要 multi-value 支持)
(func $swap (param $a i32) (param $b i32) (result i32 i32)
local.get $b
local.get $a)

命名和索引

函数可以通过名称或索引引用:

(module
;; 使用名称
(func $first (result i32) i32.const 1)
(func $second (result i32) i32.const 2)

;; 使用索引调用($first 是索引 0,$second 是索引 1)
(func $call_first (result i32)
call 0)

;; 使用名称调用
(func $call_second (result i32)
call $second))

数据类型

数值类型

类型说明字面量示例
i3232位整数i32.const 42
i6464位整数i64.const 100
f3232位浮点f32.const 3.14
f6464位浮点f64.const 2.718

整数字面量

;; 十进制
i32.const 42
i32.const -17

;; 十六进制
i32.const 0x2a

;; 二进制
i32.const 0b101010

;; 使用下划线分隔
i32.const 1_000_000

浮点字面量

;; 小数形式
f64.const 3.14159
f64.const -0.5

;; 科学计数法
f64.const 1e10
f64.const 1.5e-3

;; 十六进制浮点
f64.const 0x1.5p3

;; 特殊值
f64.const inf
f64.const -inf
f64.const nan

引用类型

;; 函数引用
(func $my_func)
(ref.func $my_func) ;; 创建函数引用

;; 外部引用(externref)
(global $ext (mut externref) (ref.null extern))

变量操作

局部变量

(func $example (param $x i32) (result i32)
(local $temp i32) ;; 声明局部变量

local.get $x ;; 获取参数值
local.set $temp ;; 设置局部变量

local.get $temp
i32.const 1
i32.add
local.tee $temp ;; 设置并保留栈顶值
)

全局变量

(module
;; 不可变全局变量
(global $PI f64 (f64.const 3.14159))

;; 可变全局变量
(global $counter (mut i32) (i32.const 0))

(func $increment (result i32)
global.get $counter
i32.const 1
i32.add
global.set $counter
global.get $counter)

(export "increment" (func $increment)))

控制流

块(block)

块创建一个新的作用域,可以带标签:

(func $example (result i32)
(block $done (result i32)
i32.const 1
br $done ;; 跳转到块末尾
i32.const 2 ;; 不会执行
)
)

循环(loop)

循环与块类似,但 br 跳转到循环开始:

(func $sum_to_n (param $n i32) (result i32)
(local $i i32)
(local $sum i32)

(block $break
(loop $continue
local.get $i
local.get $n
i32.ge_s
br_if $break ;; 如果 i >= n,跳出循环

local.get $sum
local.get $i
i32.add
local.set $sum

local.get $i
i32.const 1
i32.add
local.set $i

br $continue ;; 继续循环
)
)

local.get $sum)

条件分支(if-else)

(func $abs (param $x i32) (result i32)
local.get $x
i32.const 0
i32.lt_s ;; x < 0 ?
(if (result i32)
(then
i32.const 0
local.get $x
i32.sub) ;; 返回 -x
(else
local.get $x)) ;; 返回 x
)
)

简写形式:

(func $abs (param $x i32) (result i32)
local.get $x
i32.const 0
i32.lt_s
if (result i32)
i32.const 0
local.get $x
i32.sub
else
local.get $x
end)

分支指令

指令说明
br $label无条件跳转
br_if $label条件跳转(栈顶为真时跳转)
br_table $l1 $l2 ... $default跳转表

跳转表示例:

(func $switch (param $i i32) (result i32)
(block $default (result i32)
(block $case2 (result i32)
(block $case1 (result i32)
local.get $i
br_table $case1 $case2 $default
)
i32.const 1
br $default
)
i32.const 2
br $default
)
)

算术运算

整数运算

;; 加减乘除
i32.add ;; a + b
i32.sub ;; a - b
i32.mul ;; a * b
i32.div_s ;; 有符号除法
i32.div_u ;; 无符号除法

;; 取余
i32.rem_s ;; 有符号取余
i32.rem_u ;; 无符号取余

;; 位运算
i32.and ;; 按位与
i32.or ;; 按位或
i32.xor ;; 按位异或
i32.shl ;; 左移
i32.shr_s ;; 有符号右移
i32.shr_u ;; 无符号右移
i32.rotl ;; 循环左移
i32.rotr ;; 循环右移

;; 一元运算
i32.eqz ;; 等于零
i32.clz ;; 前导零计数
i32.ctz ;; 尾随零计数
i32.popcnt ;; 1的位数计数

浮点运算

;; 基本运算
f32.add
f32.sub
f32.mul
f32.div
f32.sqrt ;; 平方根

;; 取整
f32.ceil ;; 向上取整
f32.floor ;; 向下取整
f32.trunc ;; 截断
f32.nearest ;; 四舍五入

;; 其他
f32.abs ;; 绝对值
f32.neg ;; 取反
f32.copysign ;; 复制符号
f32.min
f32.max

比较运算

;; 整数比较
i32.eq ;; 相等
i32.ne ;; 不相等
i32.lt_s ;; 有符号小于
i32.lt_u ;; 无符号小于
i32.gt_s ;; 有符号大于
i32.gt_u ;; 无符号大于
i32.le_s ;; 有符号小于等于
i32.le_u ;; 无符号小于等于
i32.ge_s ;; 有符号大于等于
i32.ge_u ;; 无符号大于等于

;; 浮点比较
f32.eq
f32.ne
f32.lt
f32.gt
f32.le
f32.ge

类型转换

整数转换

;; 扩展
i32.extend8_s ;; 8位有符号扩展到32位
i32.extend16_s ;; 16位有符号扩展到32位
i64.extend32_s ;; 32位有符号扩展到64位

;; 包装
i32.wrap_i64 ;; 64位包装到32位

浮点转换

;; 整数转浮点
f32.convert_i32_s ;; 有符号整数转浮点
f32.convert_i32_u ;; 无符号整数转浮点
f64.convert_i64_s

;; 浮点转整数
i32.trunc_f32_s ;; 浮点截断为有符号整数
i32.trunc_f32_u ;; 浮点截断为无符号整数

;; 浮点精度转换
f32.demote_f64 ;; 64位浮点降为32位
f64.promote_f32 ;; 32位浮点升为64位

;; 位重解释
i32.reinterpret_f32 ;; 浮点位模式解释为整数
f32.reinterpret_i32 ;; 整数位模式解释为浮点

内存操作

加载指令

;; 基本加载
i32.load ;; 加载 32 位整数
i64.load ;; 加载 64 位整数
f32.load ;; 加载 32 位浮点
f64.load ;; 加载 64 位浮点

;; 指定偏移和对齐
i32.load offset=4 align=4

;; 部分加载
i32.load8_s ;; 加载 8 位有符号
i32.load8_u ;; 加载 8 位无符号
i32.load16_s ;; 加载 16 位有符号
i32.load16_u ;; 加载 16 位无符号
i64.load8_s
i64.load8_u
i64.load16_s
i64.load16_u
i64.load32_s
i64.load32_u

存储指令

;; 基本存储
i32.store ;; 存储 32 位整数
i64.store ;; 存储 64 位整数
f32.store ;; 存储 32 位浮点
f64.store ;; 存储 64 位浮点

;; 指定偏移和对齐
i32.store offset=8 align=4

;; 部分存储
i32.store8 ;; 存储 8 位
i32.store16 ;; 存储 16 位
i64.store8
i64.store16
i64.store32

内存大小和增长

;; 获取内存大小(以页为单位)
memory.size

;; 增长内存
memory.grow ;; 栈顶是要增加的页数

表操作

;; 获取表元素
table.get

;; 设置表元素
table.set

;; 表大小
table.size

;; 增长表
table.grow

;; 填充表
table.fill

完整示例

斐波那契数列

(module
(func $fib (param $n i32) (result i32)
(local $a i32)
(local $b i32)
(local $temp i32)
(local $i i32)

;; 初始化
i32.const 0
local.set $a
i32.const 1
local.set $b
i32.const 0
local.set $i

;; 循环
(block $break
(loop $continue
local.get $i
local.get $n
i32.ge_s
br_if $break

;; temp = a + b
local.get $a
local.get $b
i32.add
local.set $temp

;; a = b
local.get $b
local.set $a

;; b = temp
local.get $temp
local.set $b

;; i++
local.get $i
i32.const 1
i32.add
local.set $i

br $continue
)
)

local.get $a)

(export "fib" (func $fib)))

字符串处理

(module
(memory 1)

;; 计算字符串长度
(func $strlen (param $ptr i32) (result i32)
(local $len i32)
i32.const 0
local.set $len

(block $break
(loop $continue
local.get $ptr
local.get $len
i32.add
i32.load8_u
i32.eqz
br_if $break

local.get $len
i32.const 1
i32.add
local.set $len

br $continue
)
)

local.get $len)

(export "strlen" (func $strlen))
(export "memory" (memory 0)))

调试技巧

使用注释

;; 单行注释

(;
多行注释
可以跨越多行
;)

使用打印调试

(module
(import "env" "log" (func $log (param i32)))

(func $debug_example (param $x i32) (result i32)
local.get $x
call $log ;; 打印调试信息

local.get $x
i32.const 1
i32.add)

(export "debug_example" (func $debug_example)))

JavaScript 端:

const importObject = {
env: {
log: (value) => console.log('Debug:', value)
}
};

工具使用

wat2wasm

将 WAT 编译为 WASM:

wat2wasm input.wat -o output.wasm

# 显示详细信息
wat2wasm -v input.wat

# 启用多内存支持
wat2wasm --enable-multi-memory input.wat

wasm2wat

将 WASM 反编译为 WAT:

wasm2wat input.wasm -o output.wat

wasm-validate

验证 WASM 文件:

wasm-validate input.wasm

下一步

掌握 WAT 语法后,你可以继续学习: