文本格式 .WAT

基本结构

Wasm 规范定义了模块的文本格式(WebAssembly Text Format, 简称 WAT)

Wasm 文本格式使用 S- 表达式描述模块。

使用了大量圆括号,特别适合描述类似抽象语法树 (AST)的树形结构

Wasm 文本格式的整体结构

(module
    (type ...);;
    (import ...);;
    (func ...);;
    (table ...);;
    (memory ...);;
    (global ...);;
    (export ...);;
    (statr ...);;
    (elem ...);;
    (data ...);;
)

从整体上看,Wasm 文本格式和二进制格式基本是一致的(除了表现形式明显不同以外,在结构上,两种格式还有几个较大的不同之处)

  1. 文本格式以域(Field)为单位组织内容。域相当于二进制段中的项目,但不一定要连续出现,WAT 编译器会把同类型的域收集起来,合并成二进制段。
  1. 在二进制格式中,除了自定义段以外,其他段必须按照 ID 递增的顺序排列,文本格式中的域则没有这么严格的限制。(导入域必须出现在函数域,表域,内存域和全局域之前)
  1. 文本格式中的域和二进制格式中的段基本是一一对应的,但是有两种情况例外。
    第一种是文本格式没有单独的代码域,只有函数域名。 WAT 编译器会将函数域收集起来,分别生成函数段和代码段。
    第二种是文本格式没有自定义域,没办法描述自定义段(已经有提案建议增强 WAT 语法,支持表达自定义数据)
  1. 文本格式提供了多种内联写法。例如:函数域,表域,内存域,全局域可以内敛导入或导出域,表域可以内敛元素,内存域可以内联数据域,函数域和导入域可以内联类型域。这些内联写法只是”语法糖“,WAT编译器会妥善处理

注释

WAT 支持两种类型的注释
单行注释以 ;; 开始,直到行尾
跨行注释以 (;; 开始,以 ;;)结束

;; 单行注释以 ;; 开始,直到行尾

(;; 
    跨行注释 
;;)

类型域

类型域用于定义函数类型

定义一个接收两个 i32 类型参数,返回一个 i32 类型结果的函数类型

(module
   (type (func (param i32) (param i32) (result i32)))
)

圆括号是 WAT 语言的主要分隔符(Separator)
module, type, func, param, result 等是 WAT 语言的关键字(keyword, 以小写字母开头)
由于 WAT 语言较为简单,大部分语法规则都可以通过示例代码理解

可以给函数类型分配一个标识符(Identifier, 以 $ 符开头)就是给它取个名字
这样就可以在其他地方通过调用名字来引用函数类型,不必直接使用索引
多个参数可以简写在同一个 param 块里,多个返回值可以简写在同一个 result 块里

(module
   (type $ft1 (func (param i32 i32) (result i32)))  ;; 命名函数 ft1, 接收两个 int32 整数,返回 int32 整数
   (type $ft2 (func (param f64) (result f64 f64)))  ;; 命名函数 ft2, 接收一个 float64 浮点数,返回两个 float64 浮点数
)

导入和导出域

导入域

Wasm 模块可以导入或者导出函数,表,内存和全局变量这 4 种类型的元素
导入和导出域也支持这 4 种类型

(module
    (type $ft1 (func (param i32 i32) (result i32)))
    (import "env" "f1" (func $f1 (type $ft1)))
    (import "env" "t1" (table $t 1 8 funcref))
    (import "env" "m1" (memory $m 4 16))
    (import "env" "g1" (global $g1 i32))  ;; immutable
    (import "env" "g2" (global $g2 (mut i32)))  (;; mutable ;;)
)

在导入域中,需要指明模块名,导入元素名,以及导入元素的具体类型
模块名和元素名用字符串指定,以双引号分隔
导入域也可以附带一个标识符,这样就可以在后面通过名字引用被导入的元素

上面的例子中,类型域是单独出现的,并在导入函数中通过名字(也可以通过索引)引用。
当多个导入函数有相同的类型时,这种写法可以避免代码重复出现
如果某个函数类型只被使用一次,也可以把它内联进导入域中

(module
    (import "env" "f1"
        (func $f1
            (param i32 i32) (result i32) ;; inline function type
        )
    )
)

导出域

导出域只须指定导出名和元素索引
更好的做法是通过标识符引用元素,实际索引交给 WAT 编译器去计算。
导出名在整个模块内必须是唯一的,这点一定要注意

(module
    ;;...
    (export "f1" (func $f1))
    (export "f2" (func $f2))
    (export "t1" (func $t))
    (export "m1" (memory $m))
    (export "g1" (global $g1))
    (export "g2" (global $g2))
)

导入域和导出域可以内联在函数,表,内存,全局域中

导出域的内联写法

(module
    (type $f1 (func (param i32 i32)(result i32)))
    (func $f1( import "env" "f1") (type $ft1))
    (table $t1 (import "env" "t") 1 8 funcref)
    (memory $m1 (import "env" "g1") 4 16)
    (global $g1 (import "env" "g1") i32)
    (global $g2 (import "env" "g2") (mut i32))
)

导出域的内联写法

(module
    (func $f (export "f1") ...)
    (func $t (export "t") ...)
    (memory $m (export "m") ...)
    (global $g (export "g1") ...)
)

函数域

函数域定义函数的类型和局部变量,并给出函数的指令。
WAT 编译器会把函数域拆开,把类型索引放在函数段中,把局部变量信息和字节码放在代码段中

简写方式

(module
    (type $ft1 (func (param i32 i32) (result i32)))
    (func $add (type $ft1)
        (local i64 i64)
    
        (i64.add (local.get 2) (local.get 3)) (drop)
        (i32.add (local.get 0) (local.get 1))
    )
)

函数的参数本质上也是局部变量,同函数域里定义的局部变量一起构成了函数的局部变量空间,索引从 0 开始递增。

可以把函数类型内联进函数域,并把 param 块拆成多个参数,这样就可以给参数起名字
给参数和局部变量起了名字,就可以在变量指令中通过名字而非索引定位参数或局部变量,这样有助于提高代码的可读性

;; 内联类型定义,并给参数和局部变量分配标识符(有助于提高代码的可读性)
(module
    (func $add (param $a i32) (param $b i32) (result i32)
        (local $c i64) (local $d i64)
        
        (i64.add (local.get $c) (local.get $d)) (drop)
        (i32.add (local.get $a) (local.get $b))  
    )
)

表域和表元素

模块最多只能导入或者定义一张表,表域最多只能出现一次,但元素与可以出现多次
表域需要描述表的类型,包括限制和元素类型(目前只能是 funcref)
元素域可以指定若干个函数索引,以及第一个索引的表内偏移量

(module
    (func $f1) (func $f2) (func $f3)
    (table 10 20 funcref)
    (elem (offset (i32.const 5)) $f1 $f2 $f3)
)

表和内存偏移量以及全局变量的初始值是通过常量指令指定的
表域中可以内联一个元素域(使用这种方式无法指定表的限制,只能由编译器根据内联元素进行推测),也无法指定元素表内偏移量(只能从 0 开始)

(module
    (func $f1) (func $f2) (func $f3)
    (table funcref  ;; min=3, max=3
        (elem $f1 $f2 $f3)  ;; inline elem, offset=0
    )
)

内存域与数据域

和表相似,模块最多只能导入或定义一块内存,所以内存与最多也只能出现一次,数据域则可以出现多次。
内存域需要描述内存的类型(即页数上下限)
数据域需要指定内存的偏移量和初始数据。

;; 内存域和数据域的写法
(module
    (memory 4 16)
    (data (offset (i32.const 100)) "Hello, ")
    (data (offset (i32.const 108)) "World!\n")
)

内存初始数据是以字符串形式指定的
除了普通字符,还可以使用转义序列在字符串中嵌入回车换行等特殊符号,十六进制编码的任意字节,以及 unicode 代码点

和表域相似,内存与中也可以内联一个数据域,但是使用这种方式无法指定内存的页数(只能由编译器根据内联数据进行推测),也无法指定内存的偏移量(只能从 0 开始)。
数据域中的数据还可以写成多个字符串

;; 数据域内联写法
(module
    (memory ;; min=1, max=1
        (data "Hello, " "World!\n") ;; inline data, offset=0
    )
)

全局域

全局域定义全局变量,需要描述全局变量的类型和可变性,并给定初始值
和其他元素一样,全局域也可以指定标识符,这样就可以在变量指令中使用全局变量的名字而非索引

(module
    (global $g1 (mut i32 ) (i32.const 100))  ;;mutable
    (global $g1 (mut i32) (i32.const 200))  ;; mutable
    (global $g3 f32 (f32.const 3.14))  ;; immutable
    (global $g4 f64 (f64.const 2.71))  ;; immutable
    (func 
        (global.get $g1) (global.set $g2)
    )
)

起始域

起始域写法最为简单,只须指定一个起始函数名或索引

(module
    (func $main ...)
    (start $main)
)

指令

Wasm 文本格式里,指令有两种写法: 普通形式和折叠形式

普通形式和其二进制编码格式基本一致,非常容易理解

折叠形式则完全是语法糖,更方便人类编写,但是 WAT 编译器会把它们全部展开

普通形式

其他指令

指令的普通形式写法非常直接,对于大部分指令来说,就是操作码后跟立即数

;; 除控制指令外的其他指令的一般写法
(module
    (memory 1 2)
    (global $g1 (mut i32) (i32.const 0))
    (func $f1)
    (func $f2 (param $a i32)
        i32.const 123
        i32.load offset=100 align=4
        i32.const 456
        i32.store offset=200
        global.get $g1
        local.get $a
        i32.add
        call $f1
        drop
    )
)

大部分指令的立即数(如果有的话)都是不能省略的,必须以数值或者名字的形式跟在操作码后面
内存读写系列指令是个例外,offset 和 align 这两个立即数都是可选的,需要显式指定(名称和数值用等号分开)

结构化控制指令(block,loop 和 if)

可以指定可选的参数和结果类型,必须以 end 结尾。
if 指令还可以用 else 分隔成两条分支。

;; block、loop、if、br 和 br_if
(module
    (func $foo
        block $11 (result i32)
            i32.const 123
            br $11
            loop $12
                i32.const 123
                br_if $12
            end
        end
    drop
    )
    (func $max(param $a i32) (param $b i32) (result i32)
        local.get $a
        local.get $b
        i32.gt_s
        if(result i32)
            local.get $a
        else
            local.get $b
        end
    )
)

折叠形式

指令的普通形式较难理解写起来略为繁琐,使用折叠形式可以缓解这个情况
可以对普通指令做三步调整,把它变为折叠形式

1 使用圆括号把指令包起来;

2.如果是结构化控制指令,把 end 去掉,if指令要稍微麻烦一些

3.如果某条指令(无论是普通还是折叠形式)和它前面的几条指令从逻辑上可以看成一组操作,则把前几条指令折叠进该指令

折叠指令实际上是把指令从扁平结构变成了树形结构,WAT 编译器会按照后续遍历(从左到右访问子树,最后访问树根)的方式展开折叠指令

;; 按照上面的 3 个步骤改写前面的例子,改写后的代码应该是下面这样(注意 if 指令)
(module
    (func $foo
        (block $11 (result i32)
            (i32.const 123)
            (br $11)
            (loop $12
                (br_if $12 (i32.const 123))
            )
        )
        (drop)
    )
    (func $max (param $a i32) (param $b i32) (result i32)
        (if (result i32)
            (i32.gt_s (local.get $a) (local.get $b))
            (then (local.get $a))
            (else (local.get $b))
        )
    )
)
(module 
    (func $max (param $a i32) (param $b i32) (result i32)
        (i32.gt_s (local.get $a) (local.get $b))
        (if $1 (result i32)
            (then (local.get $a))
            (else (local.get $b))
        )
    )
)
  • 12
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值