深入浅出WebAssembly(3) Instructions

481 阅读8分钟

这系列主要是我对WASM研究的笔记,可能内容比较简略。总共包括:

  1. 深入浅出WebAssembly(1) Compilation
  2. 深入浅出WebAssembly(2) Basic Api
  3. 深入浅出WebAssembly(3) Instructions
  4. 深入浅出WebAssembly(4) Validation
  5. 深入浅出WebAssembly(5) Memory
  6. 深入浅出WebAssembly(6) Binary Format
  7. 深入浅出WebAssembly(7) Future
  8. 深入浅出WebAssembly(8) Wasm in Rust(TODO)

Types

定义一些常见的类型:

Value Types

\begin{array}{lll} {\mathit{valtype}} &::=& {\mathsf{i32}} ~|~ {\mathsf{i64}} ~|~ {\mathsf{f32}} ~|~ {\mathsf{f64}} \\ \end{array}
  • Conventions
    1. t 一般用来指代一个值,它的大小在上下文数据类型可表示的范围之间
    2. |t| 一般用来表示t的字节宽(bit width), |i32| = |f32| = 32|i64| = |f64| = 64

Result Types

\begin{array}{lll} {\mathit{resulttype}} &::=& [{\mathit{valtype}}^?] \\ \end{array}

Function Types

\begin{array}{lll} \mathit{functype} &::=& [{\mathit{vec}}({\mathit{valtype}})]{\rightarrow} [{\mathit{vec}}({\mathit{valtype}})] \\ \end{array}

当前只允许返回值vector的长度最多为1

Limits

\begin{array}{lll} {\mathit{limits}} &::=& \{ {\mathsf{min}}~{\mathit{u32}}, {\mathsf{max}}~{\mathit{u32}}^? \} \\ \end{array}

Memory Types

\begin{array}{lll} {\mathit{memtype}} &::=& {\mathit{limits}} \\ \end{array}

Table Types

\begin{array}{lll} {\mathit{tabletype}} &::=& {\mathit{limits}}~{\mathit{elemtype}} \\ {\mathit{elemtype}} &::=& {\mathsf{funcref}} \\ \end{array}

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

\begin{array}{lll} {\mathit{globaltype}} &::=& {\mathit{mut}}~{\mathit{valtype}} \\ {\mathit{mut}} &::=& {\mathsf{const}} ~|~ {\mathsf{var}} \\ \end{array}

External Types

用来定义imports和external值的类型:

\begin{array}{llll} {\mathit{externtype}} &::=& {\mathsf{func}}~{\mathit{functype}} ~|~ {\mathsf{table}}~{\mathit{tabletype}} ~|~ {\mathsf{mem}}~{\mathit{memtype}} ~|~ {\mathsf{global}}~{\mathit{globaltype}} \\ \end{array}
  • Conventions

    {\mathrm{funcs}}({\mathit{externtype}}^\ast) = [{\mathit{functype}} ~|~ ({\mathsf{func}}~{\mathit{functype}}) \in {\mathit{externtype}}^\ast]\\{\mathrm{tables}}({\mathit{externtype}}^\ast) = [{\mathit{tabletype}} ~|~ ({\mathsf{table}}~{\mathit{tabletype}}) \in {\mathit{externtype}}^\ast]\\{\mathrm{mems}}({\mathit{externtype}}^\ast) = [{\mathit{memtype}} ~|~ ({\mathsf{mem}}~{\mathit{memtype}}) \in {\mathit{externtype}}^\ast]\\{\mathrm{globals}}({\mathit{externtype}}^\ast) = [{\mathit{globaltype}} ~|~ ({\mathsf{global}}~{\mathit{globaltype}}) \in {\mathit{externtype}}^\ast]

指令(Instructions)

WebAssembly 代码由一系列的指令构成,他的计算模型基于堆栈机(stack machine),所有指令在一个隐式操作数栈(implicit operand stack), 消耗(popping)参数值或者返回(pushing)结果值

!!! caution 理论上堆栈机的返回值可以有多个,但是当前版本的WebAssembly中,最多只能返回一个单值。这个限制可能会在将来版本中解除 !!!

除了堆栈中的动态操作数之外,一些指令同时也拥有静态的直接参数(immediate arguments),典型的如索引(indices)或类型标注(type annotations),他们都是当前指令的一部分。

一些指令是结构化的,包含着一系列内嵌(nested)指令

Numeric Instructions

对数值和类型提供基本操作。这些指令密切的对应着相应的硬件操作。

\begin{aligned} \begin{array}{llcl}  &\mathit{nn}, \mathit{mm} &::=&   \mathsf{32} ~|~ \mathsf{64} \\  &{\mathit{sx}} &::=&   \mathsf{u} ~|~ \mathsf{s} \\  &{\mathit{instr}} &::=&   \mathsf{i}\mathit{nn}\mathsf{.}{\mathsf{const}}~{{\mathit{nn}}} ~|~   \mathsf{f}\mathit{nn}\mathsf{.}{\mathsf{const}}~{{{\mathit{f}}}{\mathit{nn}}} \\&&|&   \mathsf{i}\mathit{nn}\mathsf{.}{\mathit{iunop}} ~|~   \mathsf{f}\mathit{nn}\mathsf{.}{\mathit{funop}} \\&&|&   \mathsf{i}\mathit{nn}\mathsf{.}{\mathit{ibinop}} ~|~   \mathsf{f}\mathit{nn}\mathsf{.}{\mathit{fbinop}} \\&&|&   \mathsf{i}\mathit{nn}\mathsf{.}{\mathit{itestop}} \\&&|&   \mathsf{i}\mathit{nn}\mathsf{.}{\mathit{irelop}} ~|~   \mathsf{f}\mathit{nn}\mathsf{.}{\mathit{frelop}} \\&&|&   \mathsf{i32.}{\mathsf{wrap}}\mathsf{\_i64} ~|~   \mathsf{i64.}{\mathsf{extend}}\mathsf{\_i32}\mathsf{\_}{\mathit{sx}} ~|~   \mathsf{i}\mathit{nn}\mathsf{.}{\mathsf{trunc}}\mathsf{\_f}\mathit{mm}\mathsf{\_}{\mathit{sx}} \\&&|&   \mathsf{f32.}{\mathsf{demote}}\mathsf{\_f64} ~|~   \mathsf{f64.}{\mathsf{promote}}\mathsf{\_f32} ~|~   \mathsf{f}\mathit{nn}\mathsf{.}{\mathsf{convert}}\mathsf{\_i}\mathit{mm}\mathsf{\_}{\mathit{sx}} \\&&|&   \mathsf{i}\mathit{nn}\mathsf{.}{\mathsf{reinterpret}}\mathsf{\_f}\mathit{nn} ~|~   \mathsf{f}\mathit{nn}\mathsf{.}{\mathsf{reinterpret}}\mathsf{\_i}\mathit{nn} \\&&|&   \dots \\  &{\mathit{iunop}} &::=&   \mathsf{clz} ~|~   \mathsf{ctz} ~|~   \mathsf{popcnt} \\  &{\mathit{ibinop}} &::=&   \mathsf{add} ~|~   \mathsf{sub} ~|~   \mathsf{mul} ~|~   \mathsf{div\_}{\mathit{sx}} ~|~   \mathsf{rem\_}{\mathit{sx}} \\&&|&   \mathsf{and} ~|~   \mathsf{or} ~|~   \mathsf{xor} ~|~   \mathsf{shl} ~|~   \mathsf{shr\_}{\mathit{sx}} ~|~   \mathsf{rotl} ~|~   \mathsf{rotr} \\  &{\mathit{funop}} &::=&   \mathsf{abs} ~|~   \mathsf{neg} ~|~   \mathsf{sqrt} ~|~   \mathsf{ceil} ~|~   \mathsf{floor} ~|~   \mathsf{trunc} ~|~   \mathsf{nearest} \\ &{\mathit{fbinop}} &::=&   \mathsf{add} ~|~   \mathsf{sub} ~|~   \mathsf{mul} ~|~   \mathsf{div} ~|~   \mathsf{min} ~|~   \mathsf{max} ~|~   \mathsf{copysign} \\  &{\mathit{itestop}} &::=&   \mathsf{eqz} \\  &{\mathit{irelop}} &::=&   \mathsf{eq} ~|~   \mathsf{ne} ~|~   \mathsf{lt\_}{\mathit{sx}} ~|~   \mathsf{gt\_}{\mathit{sx}} ~|~   \mathsf{le\_}{\mathit{sx}} ~|~   \mathsf{ge\_}{\mathit{sx}} \\  &{\mathit{frelop}} &::=&   \mathsf{eq} ~|~   \mathsf{ne} ~|~   \mathsf{lt} ~|~   \mathsf{gt} ~|~   \mathsf{le} ~|~   \mathsf{ge} \\ \end{array} \end{aligned}

数值指令可以按数值类型归类,对于每一种类型,都存在着下面几个子类型:

  1. Contants: 返回一个静态常量
  2. Unary Operations: 消耗一个操作数并且产生一个相应类型的结果
  3. Binary Operations:消耗两个操作数并且产生一个相应类型的结果
  4. Tests: 消耗一个操作数,产生一个Boolean interger的结果
  5. Comparisons: 消耗两个操作数,产生一个Boolean integer结果
  6. Conversions: 消耗一个某种类型的值,产生一个其他种类的结果(原类型标志在"_"后面)

一些整型指令有两种风格(flavors),通过符号标注sx来区分其操作数将会以unsigned还是signed整型来解释(interpreted)。对于其他的整型指令,将会对符号使用二进制补码来解释,这意味着有无符号他们的表现都是一致的。

  • Conventions

    \begin{array}{lll} {\mathit{unop}} &::=& {\mathit{iunop}} ~|~ {\mathit{funop}} \\ {\mathit{binop}} &::=& {\mathit{ibinop}} ~|~ {\mathit{fbinop}} \\ {\mathit{testop}} &::=& {\mathit{itestop}} \\ {\mathit{relop}} &::=& {\mathit{irelop}} ~|~ {\mathit{frelop}} \\ {\mathit{cvtop}} &::=& {\mathsf{wrap}} ~|~ {\mathsf{extend}} ~|~ {\mathsf{trunc}} ~|~ {\mathsf{convert}} ~|~ {\mathsf{demote}} ~|~ {\mathsf{promote}} ~|~ {\mathsf{reinterpret}} \\ \end{array}

Parametric Instructions

下面的指令可以操作栈上任意值类型的操作数:

\begin{array}{llcl} &{\mathit{instr}} &::=& \dots \\&&|& {\mathsf{drop}} \\&&|& {\mathsf{select}} \end{array}
  1. drop指令简单的丢一个操作数
  2. select指令将基于第三台操作数是否为0的条件来选择前两个操作数其中之一

Variable Instructions

变量指令需要考虑是从local还是从global获取变量

\begin{array}{llcl} & {\mathit{instr}} &::=& \dots \\&&|& {\mathsf{local.get}}~{\mathit{localidx}} \\&&|& {\mathsf{local.set}}~{\mathit{localidx}} \\&&|& {\mathsf{local.tee}}~{\mathit{localidx}} \\&&|& {\mathsf{global.get}}~{\mathit{globalidx}} \\&&|& {\mathsf{global.set}}~{\mathit{globalidx}} \\ \end{array}

这些指令用了获取或者设置变量的值。其中,local.tee指令跟local.set指令相似,但同时也返回它的参数

Memory Instructions

\begin{array}{llcl}  & {\mathit{memarg}} &::=&   \{ {\mathsf{offset}}~{\mathit{u32}}, {\mathsf{align}}~{\mathit{u32}} \} \\  & {\mathit{instr}} &::=&   \dots \\&&|&   \mathsf{i}\mathit{nn}\mathsf{.}{\mathsf{load}}~{\mathit{memarg}} ~|~   \mathsf{f}\mathit{nn}\mathsf{.}{\mathsf{load}}~{\mathit{memarg}} \\&&|&   \mathsf{i}\mathit{nn}\mathsf{.}{\mathsf{store}}~{\mathit{memarg}} ~|~   \mathsf{f}\mathit{nn}\mathsf{.}{\mathsf{store}}~{\mathit{memarg}} \\&&|&   \mathsf{i}\mathit{nn}\mathsf{.}{\mathsf{load}}\mathsf{8\_}{\mathit{sx}}~{\mathit{memarg}} ~|~   \mathsf{i}\mathit{nn}\mathsf{.}{\mathsf{load}}\mathsf{16\_}{\mathit{sx}}~{\mathit{memarg}} ~|~   \mathsf{i64.}{\mathsf{load}}\mathsf{32\_}{\mathit{sx}}~{\mathit{memarg}} \\&&|&   \mathsf{i}\mathit{nn}\mathsf{.}{\mathsf{store}}\mathsf{8}~{\mathit{memarg}} ~|~   \mathsf{i}\mathit{nn}\mathsf{.}{\mathsf{store}}\mathsf{16}~{\mathit{memarg}} ~|~   \mathsf{i64.}{\mathsf{store}}\mathsf{32}~{\mathit{memarg}} \\&&|&   {\mathsf{memory.size}} \\&&|&   {\mathsf{memory.grow}} \\ \end{array}

内存访问由对应不同value类型的load指令和store指令构成。他们都接收一个跟内存直接相关的参数memarg。memarg包含了一个地址偏移和期望的对齐(expressed as the exponent of a power of 2) 整数loadstore的时候可以可选的指定一个比对应值类型比特位更小的储存类型。在这种情况下,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

下面是一些能够影响控制流的指令:

\begin{array}{llcl} & {\mathit{instr}} &::=& \dots \\&&|& {\mathsf{nop}} \\&&|& {\mathsf{unreachable}} \\&&|& {\mathsf{block}}~{\mathit{resulttype}}~{\mathit{instr}}^\ast~{\mathsf{end}} \\&&|& {\mathsf{loop}}~{\mathit{resulttype}}~{\mathit{instr}}^\ast~{\mathsf{end}} \\&&|& {\mathsf{if}}~{\mathit{resulttype}}~{\mathit{instr}}^\ast~{\mathsf{else}}~{\mathit{instr}}^\ast~{\mathsf{end}} \\&&|& {\mathsf{br}}~{\mathit{labelidx}} \\&&|& {\mathsf{br\_if}}~{\mathit{labelidx}} \\&&|& {\mathsf{br\_table}}~{\mathit{vec}}({\mathit{labelidx}})~{\mathit{labelidx}} \\&&|& {\mathsf{return}} \\&&|& {\mathsf{call}}~{\mathit{funcidx}} \\&&|& {\mathsf{call\_indirect}}~{\mathit{typeidx}} \\ \end{array}
  1. nop指令不做任何事情
  2. unreachable指令将会触发一个无条件的trap
  3. block,loop,if 指令是结构化指令。他们包含一系列内嵌(nested)指令,称为(blocks),终结或分割于endelse虚拟指令(pseudo-instructions)。按照语法规定,他们必须是良好嵌套(well-nested)的。一条结构化指令能够产生一个被result type标注标记的值
  4. 每个结构化指令都会引入一个隐式标签(label)。标签(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标记结尾的语句:

\begin{array}{llll} {\mathit{expr}} &::=& {\mathit{instr}}^\ast~{\mathsf{end}} \\ \end{array}

Modules

WebAssembly程序由模块来组织,模块是wasm部署,加载,编译的单位。一个模块包括:

\begin{array}{lllll} & {\mathit{module}} &::=& \{ & {\mathsf{types}}~{\mathit{vec}}({\mathit{functype}}), \\&&&& {\mathsf{funcs}}~{\mathit{vec}}({\mathit{func}}), \\&&&& {\mathsf{tables}}~{\mathit{vec}}({\mathit{table}}), \\&&&& {\mathsf{mems}}~{\mathit{vec}}({\mathit{mem}}), \\&&&& {\mathsf{globals}}~{\mathit{vec}}({\mathit{global}}), \\&&&& {\mathsf{elem}}~{\mathit{vec}}({\mathit{elem}}), \\&&&& {\mathsf{data}}~{\mathit{vec}}({\mathit{data}}), \\&&&& {\mathsf{start}}~{\mathit{start}}^?, \\&&&& {\mathsf{imports}}~{\mathit{vec}}({\mathit{import}}), \\&&&& {\mathsf{exports}}~{\mathit{vec}}({\mathit{export}}) \quad\} \\ \end{array}

上述vector乃至整个模块都有可能为空

索引

Definitions are referenced with zero-based indices. Each class of definition has its own index space, as distinguished by the following classes.

\begin{array}{llll} & {\mathit{typeidx}} &::=& {\mathit{u32}} \\ & {\mathit{funcidx}} &::=& {\mathit{u32}} \\ & {\mathit{tableidx}} &::=& {\mathit{u32}} \\ & {\mathit{memidx}} &::=& {\mathit{u32}} \\ & {\mathit{globalidx}} &::=& {\mathit{u32}} \\ & {\mathit{localidx}} &::=& {\mathit{u32}} \\ & {\mathit{labelidx}} &::=& {\mathit{u32}} \\ \end{array}
  1. (functions,tables, memories, globals)的索引空间包括同在一个模块的imports。imorts的索引顺序排在其他定义之前。
  2. locals的索引空间只能在当前函数内部访问,索引空间同时也包括函数的参数。并且参数的索引顺序排在所有本地变量之前。
  3. 标签的索引空间与结构化控制指令相关。
  • Conventions

    l 通常代表标签索引 x,y 通常代表除标签外的其他索引

Types

types是一个vector,定义了当前模块的函数类型(function types),所有的函数都要定义对应的类型。types可以被type indeces所引用

未来的wasm可能会有出函数类型之外更多的类型

Functions

funcs定义来当前模块中的函数:

\begin{array}{llll} {\mathit{func}} &::=& \{ {\mathsf{type}}~{\mathit{typeidx}}, {\mathsf{locals}}~{\mathit{vec}}({\mathit{valtype}}), {\mathsf{body}}~{\mathit{expr}} \} \\ \end{array}

body的语句必须产生一个与type中相符的return type

Tables

tables 定义了当前模块的table

\begin{array}{llll} {\mathit{table}} &::=& \{ {\mathsf{type}}~{\mathit{tabletype}} \} \\ \end{array}
  1. table是一个包含了许多不透明的特殊的table element type的值的vector。
  2. tables能够被element segment初始化
  3. tables可以用table indices引用(索引空间包括import进来的table),大部分语句隐式引用一个索引0处的table

目前table索引只能到0,并且所有相关语句都隐式引用第一个table。未来这些限制可能会被开放

Memories

mem定义了当前模块中线性内存。

\begin{array}{llll} {\mathit{mem}} &::=& \{ {\mathsf{type}}~{\mathit{memtype}} \} \\ \end{array}
  1. memory能够被data segment初始化
  2. memory可以用memory indices引用。大部分语句隐式引用一个索引为0处的memory。

目前每个模块的memory只能有一个,所有的相关语句都隐式引用第一个memory。未来这些限制可能会被开发。

Globals

\begin{array}{llll} {\mathit{global}} &::=& \{ {\mathsf{type}}~{\mathit{globaltype}}, {\mathsf{init}}~{\mathit{expr}} \} \\ \end{array}

Element Segmemts

用于初始化table

\begin{array}{llll} {\mathit{elem}} &::=& \{ {\mathsf{table}}~{\mathit{tableidx}}, {\mathsf{offset}}~{\mathit{expr}}, {\mathsf{init}}~{\mathit{vec}}({\mathit{funcidx}}) \} \\ \end{array}

offset 必须是一个常量表达式

Data Segments

用于初始化memory

\begin{array}{llll} {\mathit{data}} &::=& \{ {\mathsf{data}}~{\mathit{memidx}}, {\mathsf{offset}}~{\mathit{expr}}, {\mathsf{init}}~{\mathit{vec}}({\mathit{byte}}) \} \\ \end{array}

offset 必须是一个常量表达式

Start Function

用于定义一个自动运行的初始化函数。运行时机在于tables和memories初始化之后,在模块初始化完毕以及exports可用之前。有点像Go中的init函数

\begin{array}{llll} {\mathit{start}} &::=& \{ {\mathsf{func}}~{\mathit{funcidx}} \} \\ \end{array}

Exports

\begin{array}{llcl} &{\mathit{export}} &::=& \{ {\mathsf{name}}~{\mathit{name}}, {\mathsf{desc}}~{\mathit{exportdesc}} \} \\ &{\mathit{exportdesc}} &::=& {\mathsf{func}}~{\mathit{funcidx}} \\&&|& {\mathsf{table}}~{\mathit{tableidx}} \\&&|& {\mathsf{mem}}~{\mathit{memidx}} \\&&|& {\mathsf{global}}~{\mathit{globalidx}} \\ \end{array}

export.name需要唯一

  • Conventions

    \begin{aligned} {\mathrm{funcs}}({\mathit{export}}^\ast) &=& [{\mathit{funcidx}} ~|~ {\mathsf{func}}~{\mathit{funcidx}} \in ({\mathit{export}}.{\mathsf{desc}})^\ast] \\ {\mathrm{tables}}({\mathit{export}}^\ast) &=& [{\mathit{tableidx}} ~|~ {\mathsf{table}}~{\mathit{tableidx}} \in ({\mathit{export}}.{\mathsf{desc}})^\ast] \\ {\mathrm{mems}}({\mathit{export}}^\ast) &=& [{\mathit{memidx}} ~|~ {\mathsf{mem}}~{\mathit{memidx}} \in ({\mathit{export}}.{\mathsf{desc}})^\ast] \\ {\mathrm{globals}}({\mathit{export}}^\ast) &=& [{\mathit{globalidx}} ~|~ {\mathsf{global}}~{\mathit{globalidx}} \in ({\mathit{export}}.{\mathsf{desc}})^\ast] \end{aligned}

Imports

\begin{array}{llll} &{\mathit{import}} &::=& \{ {\mathsf{module}}~{\mathit{name}}, {\mathsf{name}}~{\mathit{name}}, {\mathsf{desc}}~{\mathit{importdesc}} \} \\ &{\mathit{importdesc}} &::=& {\mathsf{func}}~{\mathit{typeidx}} \\&&|& {\mathsf{table}}~{\mathit{tabletype}} \\&&|& {\mathsf{mem}}~{\mathit{memtype}} \\&&|& {\mathsf{global}}~{\mathit{globaltype}} \\ \end{array}

import.name不需要唯一。module/name也可以重复,但重复的表现是未定义行为。