文本格式 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))
数据类型
数值类型
| 类型 | 说明 | 字面量示例 |
|---|---|---|
| i32 | 32位整数 | i32.const 42 |
| i64 | 64位整数 | i64.const 100 |
| f32 | 32位浮点 | f32.const 3.14 |
| f64 | 64位浮点 | 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 语法后,你可以继续学习:
- JavaScript 交互 - JavaScript 与 WebAssembly 互操作
- Rust 编译 Wasm - 使用 Rust 开发 WebAssembly
- 内存管理 - 深入理解内存模型