当前位置: 首页 > news >正文

文本格式 .WAT

文章目录

  • 基本结构
    • 注释
    • 类型域
    • 导入和导出域
      • 导入域
      • 导出域
    • 函数域
    • 表域和表元素
    • 内存域与数据域
    • 全局域
    • 起始域
  • 指令
    • 普通形式
      • 其他指令
      • 结构化控制指令(block,loop 和 if)
    • 折叠形式

基本结构

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 123i32.load offset=100 align=4i32.const 456i32.store offset=200global.get $g1local.get $ai32.addcall $f1drop)
)

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

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

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

;; block、loop、if、br 和 br_if
(module(func $fooblock $11 (result i32)i32.const 123br $11loop $12i32.const 123br_if $12endenddrop)(func $max(param $a i32) (param $b i32) (result i32)local.get $alocal.get $bi32.gt_sif(result i32)local.get $aelselocal.get $bend)
)

折叠形式

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

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))))
)

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • ueditor抓取图片
  • 2024.09.02 校招 实习 内推 面经
  • mysql快速定位cpu 占比过高的sql语句
  • UE5.3 新学到的一些性能测试合计(曼巴学习笔记)
  • 人工智能在行业中的应用
  • Java 创建图形用户界面(GUI)入门指南(Swing库 JFrame 类)概述
  • git分支的管理
  • 2024.09.04【读书笔记】|如何使用Tombo进行Nanopore Direct RNA-seq(DRS)分析
  • spring security 中的异常
  • 【Linux系统编程】TCP实现--socket
  • 数学建模笔记——熵权法(客观赋权法)
  • 【卷起来】VUE3.0教程-05-侦听器
  • 南通网站建设手机版网页
  • HTML5好看的花店商城源码3
  • MySQL—死锁
  • 002-读书笔记-JavaScript高级程序设计 在HTML中使用JavaScript
  • 2017前端实习生面试总结
  • angular2 简述
  • css选择器
  • JAVA多线程机制解析-volatilesynchronized
  • jQuery(一)
  • JS笔记四:作用域、变量(函数)提升
  • Median of Two Sorted Arrays
  • React-redux的原理以及使用
  • session共享问题解决方案
  • Yii源码解读-服务定位器(Service Locator)
  • 基于webpack 的 vue 多页架构
  • 理解 C# 泛型接口中的协变与逆变(抗变)
  • -- 数据结构 顺序表 --Java
  • 小程序滚动组件,左边导航栏与右边内容联动效果实现
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • 一起参Ember.js讨论、问答社区。
  • 正则表达式小结
  • 做一名精致的JavaScripter 01:JavaScript简介
  • Nginx惊现漏洞 百万网站面临“拖库”风险
  • ​软考-高级-信息系统项目管理师教程 第四版【第14章-项目沟通管理-思维导图】​
  • #if等命令的学习
  • #我与Java虚拟机的故事#连载07:我放弃了对JVM的进一步学习
  • $(document).ready(function(){}), $().ready(function(){})和$(function(){})三者区别
  • (3)医疗图像处理:MRI磁共振成像-快速采集--(杨正汉)
  • (51单片机)第五章-A/D和D/A工作原理-A/D
  • (javascript)再说document.body.scrollTop的使用问题
  • (LeetCode) T14. Longest Common Prefix
  • (Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六
  • (Redis使用系列) Springboot 整合Redisson 实现分布式锁 七
  • (附源码)ssm学生管理系统 毕业设计 141543
  • (算法设计与分析)第一章算法概述-习题
  • (学习日记)2024.03.12:UCOSIII第十四节:时基列表
  • (转)socket Aio demo
  • .NET Core中的去虚
  • .NET NPOI导出Excel详解
  • .Net6 Api Swagger配置
  • .NET单元测试
  • .net中调用windows performance记录性能信息
  • /etc/apt/sources.list 和 /etc/apt/sources.list.d