Skip to content

Latest commit

 

History

History
185 lines (109 loc) · 13.4 KB

Modules.md

File metadata and controls

185 lines (109 loc) · 13.4 KB

模块

WebAssembly 中可分发、可加载和可执行的代码单元称为模块。在运行时,模块可以已实例化与一组导入值一起生成例子,这是一个引用运行模块可访问的所有状态的不可变元组。多个模块实例可以访问相同的共享状态,这是在 WebAssembly 中的动态链接基础。WebAssembly 模块还设计为与 ES6 模块集成

模块包含以下部分:

  • [导入] (# 导入)
  • [出口] (# 出口)
  • [开始] (# 模块-开始-功能)
  • [global] (#global-section)
  • [内存] (# 线性-内存-部分)
  • [数据] (#data-section)
  • [表] (#table-section)
  • [元素] (#Elements-Section)
  • [函数和代码] (# 函数和代码部分)

模块还定义了几个索引空间由模块中的各种运算符和节字段静态索引的字段:

进口

模块可以声明在实例化时由主机环境提供的进口模块序列。进口有几种:

  • 函数导入,可以通过 [ call](semantics.MD#calls)操作符在模块内部调用;
  • 全球进口,可通过全球运营商在模块内部访问;
  • 线性内存导入,可通过内存运算符在模块内部访问;和
  • Future:unicorn: 中的和其他表运算符表导入可以在模块调用 _ 间接内部访问。

未来可能还会增加其他种类的进口。导入旨在允许模块共享代码和数据,同时仍允许单独编译和缓存。

所有导入都包括两个不透明的名称:a模块名称和 an导出名称,它们必须是 Valid UTF-8。这些名称的解释由主机环境决定,但旨在允许主机环境(如Web)支持两级命名空间。

每种特定类型的导入都定义了其他字段:

a函数导入包括用于模块中导入函数里面的签名。根据模块中导入的函数在外面检查签名由主机环境定义。但是,如果导入的函数是 WebAssembly 函数,则如果存在签名不匹配,宿主环境必须引发实例化时错误。

A全局变量导入包括值类型全局变量的 AND易变性。这些字段与中全球部分的含义相同。在 MVP 中,全局变量导入必须是不可改变的

线性内存导入包括:初始长度和可选最大长度线性存储器部分定义的相同字段集。主机环境必须仅允许导入初始长度大于或等于**更少或相等大于导入中声明的初始长度且最大长度大于导入中声明的最大长度的 WebAssembly 线性内存。这确保了单独编译可以假设:低于声明的初始长度的内存访问总是在边界内,高于声明的最大长度的访问总是在边界外,并且如果初始长度等于最大长度,则长度是固定的。在 MVP 中,每个存储器都是 A默认内存,因此最多可以有一个线性存储器导入或线性存储器定义。

表导入包括在表节元素类型**初始长度和可选最大长度中定义的相同字段集。与线性内存部分一样,主机环境必须确保仅导入具有完全匹配的元素类型、大于或等于初始长度以及小于或等于最大长度的 WebAssembly 表。在 MVP 中,每个表都是一个默认表,因此最多只能有一个表导入或表定义。

由于 WebAssembly 规范没有定义如何解释导入名称:

  • 网络环境名称定义为 UTF8 编码的字符串。
  • 主机环境可以将模块名称解释为文件路径、URL、一组固定的内置模块中的键,或者主机环境可以调用用户定义的钩子来将模块名称解析为这些内容之一。
  • 模块名称不需要解析为 WebAssembly 模块;它可以解析为内置模块(由主机环境实现)或以另一种兼容语言编写的模块;和
  • 调用导入函数的含义是主机定义的。

模块导入的开放性允许它们用于向 WebAssembly 代码公开任意主机环境功能,类似于本机 syscall。例如,Shell 环境可以使用导出 puts 定义内置 stdio 模块。

出口

模块可以声明一个序列,出口该序列在实例化时返回给主机环境。每个导出都有三个字段:A名字,要求是 Valid UTF-8,其含义由主机环境定义;A类型,指示导出是函数、全局、内存还是表;以及索引类型对应索引空间的 INTO.

所有定义都是可导出的:函数、全局变量、线性内存和表。导出定义的含义由主机环境定义。但是,如果另一个 WebAssembly 实例导入该定义,则两个实例将共享相同的定义,并且共享关联的状态(全局变量值、线性内存字节、表元素)。

导出名称必须唯一。

在 MVP 中,不可改变的只能导出全局变量。

模块启动功能

如果模块定义了一个开始节点,那么它所引用的函数应该在实例初始化之后,包括它的内存和表,以及数据和元素部分,并且在导出的函数可调用之前,由加载器调用。

  • Start 函数不能接受任何参数或返回任何内容
  • 函数由标识功能索引,可以导入,也可以导出
  • 每个模块最多只能有一个开始节点

例如,模块中的开始节点将为:

(start $start_function)

(start 42)

在第一个示例中,环境应该在调用任何其他模块函数之前调用函数 $start_ 函数。在第二种情况下,环境应调用索引为 42 的模块函数。此数字是从 0 开始的函数索引(与的 export 相同)。

模块可以:

  • 最多只有一个开始节点
  • 如果模块包含开始节点,则必须在模块中定义函数
  • start 函数将在模块加载之后和对模块的任何调用之前被调用。 功能已完成

全球部分

全球部分提供零或更多全局变量的内部定义。

每个全局变量内部定义都声明其类型(avalue type)、易变性(布尔标志)和初始化程序(an初始化表达式)。

线性存储器部分

线性存储器部分提供了 1线性存储器的内部定义。在 MVP 中,每个存储器都是默认存储器,并且最多可以有一个线性存储器导入或线性存储器定义。

每个线性内存部分都声明了一个开始的内存大小(随后可能会增加 [ grow_memory](semantics.MD#resizing))和一个可选最大内存大小的。

如果尝试增长超过声明的最大值,[ grow_memory](semantics.MD#resizing)肯定会失败。声明时,实现应该(非标准)会尝试保留最大大小的虚拟内存。虽然无法分配开始的内存大小是运行时错误,但无法保留到则不最大量是。当声明最大内存大小时,在虚拟地址空间有限的体系结构上,引擎应仅分配初始大小并按需重新分配。

数据节

线性存储器的初始内容为零。数据节包含一个可能为空的数组数据段,该数组指定给定内存的固定 (offset, length) 范围的初始内容,由其线性记忆指数指定。此部分类似于 .data 本机可执行文件部分。 length 是一个整数常数值(定义给定段的长度)。 offset 是一个初始化表达式

表节

表节包含零个或多个 DISTINCTtables定义。在 MVP 中,每个表都是默认表,最多只能有一个表导入或表定义。

每个表定义都声明元素类型初始长度和可选最大长度

在 MVP 中,唯一有效的元素类型是 "anyfunc",但在 Future:unicorn: 中,可以添加更多的元素类型。

在 MVP 中,只能通过主机定义的 API(如 JavaScript[ WebAssembly.Table.prototype.grow](JS.MD#WebAssemblyTablePrototypeGrow))来调整表的大小。 grow_table 可以在 [未来:unicorn:][未来类型] 中添加。在这两种情况下,如果试图增长超过声明的最大值,表增长肯定会失败。与线性内存一样,当声明最大值时,实现应该(非标准)会尝试保留最大大小的虚拟内存。虽然无法分配开始的内存大小是运行时错误,但无法保留到则不最大量是。当声明最大内存大小时,在虚拟地址空间有限的体系结构上,引擎应仅分配初始大小并按需重新分配。

元素部分

表元素的初始内容是标记(sentinel)值(如果调用该值,则会产生陷阱)。元素部分允许模块使用模块中的任何其他定义来初始化(在实例化时)任何导入或内部定义的表的元素。这与允许模块初始化任何导入或定义的内存的字节的方式数据节是对称的。

元素部分包含一个可能为空的数组元素段,该数组指定给定表的固定 (offset, length) 范围的初始内容,由其表索引指定。 length 是一个整数常数值(定义给定段的长度)。 offset 是一个初始化表达式。元素由其索引指定到相应索引空间的。

功能和代码部分

单个逻辑函数定义分为两部分:

  • 功能部分声明模块中每个内部函数定义的签名。
  • 代码节包含由函数节声明的每个函数的函数体

这种拆分通过将构成模块的大部分字节大小的函数体放在靠近末尾的位置来帮助流式编译,以便在编译开始之前可以使用递归模块加载和并行编译所需的所有元数据。

函数索引空间

函数索引空间索引所有导入的和内部定义的函数定义,根据模块中定义的顺序分配单调递增的索引(由二进制编码定义)。因此,索引空间从零开始,函数导入(如果有)后跟模块中定义的函数。

函数索引空间由以下用户使用:

  • Calls,用于标识直接呼叫的被叫方。
  • Elements
  • Exports,以确定向嵌入器公开哪些函数。
  • 启动功能,以标识在实例完全初始化时调用哪个函数。

全局索引空间

全局索引空间索引所有导入和内部定义的全局定义,根据模块中定义的顺序分配单调递增的索引(由二进制编码定义)。因此,索引空间从零开始,全局导入(如果有)后跟模块中定义的全局。

全局索引空间由以下用户使用:

线性存储器索引空间

线性存储器索引空间索引所有导入的和内部定义的线性内存定义,根据模块中定义的顺序分配单调递增的索引(由二进制编码定义)。因此,索引空间从零开始,内存导入(如果有)后跟模块内定义的内存。

线性内存索引空间仅由数据节使用。在 MVP 中,最多有一个线性内存,所以这个索引空间只是一个占位符,表示何时可以有 [多个内存:unicorn:][未来的多个表]。

表索引空间

表索引空间索引所有导入的和内部定义的表定义,根据模块中定义的顺序分配单调递增的索引(由二进制编码定义)。因此,索引空间从零开始,表导入(如果有)后跟模块中定义的表。

表索引空间仅由元素部分使用。在 MVP 中,最多有一个表,所以这个索引空间只是一个占位符,表示何时可以有 [多个表:unicorn:][未来的多个表]。

初始化表达式

初始化程序表达式在实例化时计算,当前用于:

初始化器表达式是一个纯 WebAssembly 表达式,其编码方式与 WebAssembly 表达式相同二进制编码。并非所有 WebAssembly 运算符都可以或应该在初始值设定项表达式中得到支持。初始化器表达式表示 WebAssembly 表达式的最小纯子集。

在 MVP 中,为了保持简单,同时仍然支持的动态链接基本需求,初始化器表达式被限制为以下空运算符:

  • 四个常量运算符;和
  • get_global,其中全局索引必须引用不可变的导入。

将来,可以添加类似 i32.add 的操作符,以允许更具表现力的 base + offset 加载时间计算。