这系列主要是我对WASM研究的笔记,可能内容比较简略。总共包括:
- 深入浅出WebAssembly(1) Compilation
- 深入浅出WebAssembly(2) Basic Api
- 深入浅出WebAssembly(3) Instructions
- 深入浅出WebAssembly(4) Validation
- 深入浅出WebAssembly(5) Memory
- 深入浅出WebAssembly(6) Binary Format
- 深入浅出WebAssembly(7) Future
- 深入浅出WebAssembly(8) Wasm in Rust(TODO)
Types
定义一些常见的类型:
Value Types
- Conventions
一般用来指代一个值,它的大小在上下文数据类型可表示的范围之间
一般用来表示t的字节宽(bit width),
,
Result Types
Function Types
当前只允许返回值vector的长度最多为1
Limits
Memory Types
Table Types
The element type 𝖿𝗎𝗇𝖼𝗋𝖾𝖿 is the infinite union of all function types. A table of that type thus contains references to functions of heterogeneous(混杂的) type.
Global Types
External Types
用来定义imports和external值的类型:
-
Conventions
指令(Instructions)
WebAssembly 代码由一系列的指令构成,他的计算模型基于堆栈机(stack machine),所有指令在一个隐式操作数栈(implicit operand stack), 消耗(popping)参数值或者返回(pushing)结果值
!!! caution 理论上堆栈机的返回值可以有多个,但是当前版本的WebAssembly中,最多只能返回一个单值。这个限制可能会在将来版本中解除 !!!
除了堆栈中的动态操作数之外,一些指令同时也拥有静态的直接参数(immediate arguments),典型的如索引(indices)或类型标注(type annotations),他们都是当前指令的一部分。
一些指令是结构化的,包含着一系列内嵌(nested)指令
Numeric Instructions
对数值和类型提供基本操作。这些指令密切的对应着相应的硬件操作。
数值指令可以按数值类型归类,对于每一种类型,都存在着下面几个子类型:
- Contants: 返回一个静态常量
- Unary Operations: 消耗一个操作数并且产生一个相应类型的结果
- Binary Operations:消耗两个操作数并且产生一个相应类型的结果
- Tests: 消耗一个操作数,产生一个Boolean interger的结果
- Comparisons: 消耗两个操作数,产生一个Boolean integer结果
- Conversions: 消耗一个某种类型的值,产生一个其他种类的结果(原类型标志在"_"后面)
一些整型指令有两种风格(flavors),通过符号标注sx
来区分其操作数将会以unsigned还是signed整型来解释(interpreted)。对于其他的整型指令,将会对符号使用二进制补码来解释,这意味着有无符号他们的表现都是一致的。
-
Conventions
Parametric Instructions
下面的指令可以操作栈上任意值类型的操作数:
drop
指令简单的丢一个操作数select
指令将基于第三台操作数是否为0的条件来选择前两个操作数其中之一
Variable Instructions
变量指令需要考虑是从local
还是从global
获取变量
这些指令用了获取或者设置变量的值。其中,local.tee
指令跟local.set
指令相似,但同时也返回它的参数
Memory Instructions
内存访问由对应不同value类型的load
指令和store
指令构成。他们都接收一个跟内存直接相关的参数memarg。memarg包含了一个地址偏移和期望的对齐(expressed as the exponent of a power of 2)
整数load
和store
的时候可以可选的指定一个比对应值类型比特位更小的储存类型。在这种情况下,load
指令需要附加一个sx
后缀来指定相应的行为。
静态的地址偏移量将会与动态的地址累加器相加,得到一个33bit的有效地址(effective address),他是一个从0开始的内存访问地址索引。所有值的读写都是小端(little endian)字节序。当访问越界时,一个trap将会被触发。
在以后的版本中,wasm可能会提供64位空间范围的内存指令
memory.size
指令返回当前的内存大小,memory.grow
指令将当前内存增大并且返回之前的大小,当内存不够的时候,返回-1。两个指令都以page size
(64kb)为单位
当前WebAssembly版本中,所有的内存指令都隐式的在索引为0的内存上进行操作,一个wasm模块也只能有一个memory对象。这个限制可能在将来版本中开放。
Control Instructions
下面是一些能够影响控制流的指令:
nop
指令不做任何事情unreachable
指令将会触发一个无条件的trapblock
,loop
,if
指令是结构化指令。他们包含一系列内嵌(nested)指令,称为(blocks),终结或分割于end
或else
虚拟指令(pseudo-instructions)。按照语法规定,他们必须是良好嵌套(well-nested)的。一条结构化指令能够产生一个被result type
标注标记的值- 每个结构化指令都会引入一个隐式标签(label)。标签(
)用于分支指令,我们能够使用标签索引(label indices)来引用(reference)他们。不同于其他的索引空间(index spaces)。标签的索引方式与嵌套层数相关。例如:
label 0
指向包含当前应用分支最内层的结构化控制指令,而索引越大指向的指令就越外层。因此,标签只能被当前结构控制语句内部引用。这也说明:分支只能直接外向的(朝外的,directed outwards)。“打断”当前它指向的控制结构体。具体的效果与控制结构相关。block
或者是if
是一个前向跳转(forward jump),让end
语句之后的结构开始执行。loop
是一个后向跳转(backward jump),跳向当前loop的开始。
直观的,一个朝向block
或者if
的分支跳转表现像C语言中的break
语句;而一个朝向loop
的分支跳转表现的像continue
语句
Branch instructions come in several flavors: 𝖻𝗋 performs an unconditional branch, 𝖻𝗋_𝗂𝖿 performs a conditional branch, and 𝖻𝗋_𝗍𝖺𝖻𝗅𝖾 performs an indirect branch through an operand indexing into the label vector that is an immediate to the instruction, or to a default target if the operand is out of bounds. The 𝗋𝖾𝗍𝗎𝗋𝗇
instruction is a shortcut for an unconditional branch to the outermost block, which implicitly is the body of the current function. Taking a branch unwinds the operand stack up to the height where the targeted structured control instruction was entered. However, forward branches that target a control instruction with a non-empty result type consume matching operands first and push them back on the operand stack after unwinding, as a result for the terminated structured instruction.
The 𝖼𝖺𝗅𝗅 instruction invokes another function, consuming the necessary arguments from the stack and returning the result values of the call. The 𝖼𝖺𝗅𝗅_𝗂𝗇𝖽𝗂𝗋𝖾𝖼𝗍 instruction calls a function indirectly through an operand indexing into a table. Since tables may contain function elements of heterogeneous type 𝖿𝗎𝗇𝖼𝗋𝖾𝖿, the callee is dynamically checked against the function type indexed by the instruction’s immediate, and the call aborted with a trap if it does not match
In the current version of WebAssembly, 𝖼𝖺𝗅𝗅_𝗂𝗇𝖽𝗂𝗋𝖾𝖼𝗍 implicitly operates on table index 0. This restriction may be lifted in future versions.
Examples
;; br 无条件跳转
block
...
br 0 ;; 终端label为0的block
nop ;; 不会被执行的代码
end
;; br_if 单条件跳转
block
get_local $var
br_if 0
nop ;;如果$var值为0则执行这条语句
end
;; 如果$var不为0则从次开始执行
;; loop 循环
loop
nop
;; 只会执行一次
end
;; 执行完一次loop之后接着执行,不会循环
;; loop需要使用br_xx指令来进行回溯
loop
...
get_local $var
br_if 0 ;; 当$var不为0,loop将会再次从头开始执行,do ... while()
end
Expressions
函数主体、给globals或element
或者data
段的offset初始化的语句被称为表达式。它是一系列以end
标记结尾的语句:
Modules
WebAssembly程序由模块来组织,模块是wasm部署,加载,编译的单位。一个模块包括:
上述vector乃至整个模块都有可能为空
索引
Definitions are referenced with zero-based indices. Each class of definition has its own index space, as distinguished by the following classes.
- (functions,tables, memories, globals)的索引空间包括同在一个模块的imports。imorts的索引顺序排在其他定义之前。
- locals的索引空间只能在当前函数内部访问,索引空间同时也包括函数的参数。并且参数的索引顺序排在所有本地变量之前。
- 标签的索引空间与结构化控制指令相关。
-
Conventions
通常代表标签索引
通常代表除标签外的其他索引
Types
是一个vector,定义了当前模块的函数类型(function types),所有的函数都要定义对应的类型。types可以被
type indeces
所引用
未来的wasm可能会有出函数类型之外更多的类型
Functions
定义来当前模块中的函数:
body的语句必须产生一个与type中相符的return type
Tables
定义了当前模块的table
- table是一个包含了许多不透明的特殊的
table element type
的值的vector。 - tables能够被
element segment
初始化 - tables可以用
table indices
引用(索引空间包括import进来的table),大部分语句隐式引用一个索引0处的table
目前table索引只能到0,并且所有相关语句都隐式引用第一个table。未来这些限制可能会被开放
Memories
定义了当前模块中线性内存。
- memory能够被
data segment
初始化 - memory可以用
memory indices
引用。大部分语句隐式引用一个索引为0处的memory。
目前每个模块的memory只能有一个,所有的相关语句都隐式引用第一个memory。未来这些限制可能会被开发。
Globals
Element Segmemts
用于初始化table
offset 必须是一个常量表达式
Data Segments
用于初始化memory
offset 必须是一个常量表达式
Start Function
用于定义一个自动运行的初始化函数。运行时机在于tables和memories初始化之后,在模块初始化完毕以及exports可用之前。有点像Go中的init函数
Exports
export.name
需要唯一
-
Conventions
Imports
import.name
不需要唯一。module/name
也可以重复,但重复的表现是未定义行为。