4 个版本 (2 个重大更新)
0.3.0 | 2024 年 3 月 13 日 |
---|---|
0.2.0 | 2024 年 2 月 21 日 |
0.1.1 | 2024 年 1 月 20 日 |
0.1.0 | 2024 年 1 月 19 日 |
#613 在 WebAssembly 中
在 homestar-runtime 中使用
1MB
5.5K SLoC
大纲
描述
这个 wasm 库管理了 wasmtime 运行时,提供从/到 Wasm Interace Types (WIT) 解释器/翻译层的 IPLD 解释器,并实现了与 Ipvm 标准的 Wasm 任务一起工作的输入接口。
更多信息,请参阅我们的 Homestar 读取说明。
在 IPLD 和 WIT 之间进行解释
我们的递归解释器能够基于已知的 WIT 接口类型,双向翻译运行时的 IPLD 数据模型 和 WIT 值。
基本类型
我们将首先介绍 WIT 基本类型。
布尔值
本节概述了 IPLD 布尔值 (Ipld::Bool
) 和 WIT bool
运行时值之间的转换过程。
-
IPLD 到 WIT 转换:
当一个 WIT 函数期望一个
bool
输入时,一个Ipld::Bool
值(无论是true
还是false
)被映射到一个bool
WIT 运行时值。示例:考虑一个如下定义的 WIT 函数
export fn: func(a: bool) -> bool;
给定此函数的 JSON 输入
{ "args": [true] }
true
转换为Ipld::Bool
,然后翻译并作为布尔参数(bool
)传递给fn
。 -
WIT 到 IPLD 的翻译:
相反,当 WIT 函数返回布尔值时,它可以翻译回
Ipld::Bool
。
IPLD 架构定义:
type IPLDBooleanAsWit bool
整数
本节概述了 IPLD 整数值(Ipld::Integer
)与 WIT integer
运行时值之间的转换过程。
ty ::= 'u8' | 'u16' | 'u32' | 'u64'
| 's8' | 's16' | 's32' | 's64'
-
IPLD 到 WIT 转换:
通常,当 WIT 函数期望整数输入时,一个
Ipld::Integer
值会被映射到整数 WIT 运行时值。示例:考虑一个如下定义的 WIT 函数
export fn: func(a: s32) -> s32;
给定此函数的 JSON 输入
{ "args": [1] }
1
转换为Ipld::Integer
,然后翻译并作为整数参数(s32
)传递给fn
。注意:然而,如果 WIT 接口输入参数是
float
类型,但传入的值是Ipld::Integer
,则 IPLD 值将被强制转换为float
,并在剩余的计算中保持该类型。这种转换是为了提供给 JavaScript 的便利,例如,数字1.0
转换为1
。 -
WIT 到 IPLD 的翻译:
相反,当 WIT 函数返回整数值(非浮点数)时,它可以转换回
Ipld::Integer
。
IPLD 架构定义:
type IPLDIntegerAsWit union {
| U8 int
| U16 int
| U32 int
| U64 int
| S8 int
| S16 int
| S32 int
| S64 int
| Float32In int
| Float64In int
} representation kinded
type WitAsIpldInteger union {
| U8 int
| U16 int
| U32 int
| U64 int
| S8 int
| S16 int
| S32 int
| S64 int
| Float32Out float
| Float64Out float
} representation kinded
浮点数
本节概述了 IPLD 浮点值(Ipld::Float
)与 WIT float
运行时值之间的转换过程。
组件模型 支持以下浮点类型。
ty ::= 'float32' | 'float64'
-
IPLD 到 WIT 转换:
通常,当 WIT 函数期望浮点数输入时,一个
Ipld::Float
值会被映射到浮点 WIT 运行时值。如果需要,进行从f32
到f64
的类型转换。示例:考虑一个如下定义的 WIT 函数
export fn: func(a: f64) -> f64;
给定此函数的 JSON 输入
{ "args": [1.0] }
1.0
转换为Ipld::Float
,然后翻译并作为浮点参数(f64
)传递给fn
。注意:然而,如果 WIT 接口输入参数是 WIT 整数类型之一,但传入的值是
Ipld::Integer
,则 IPLD 值将被强制转换为该整数类型,并在剩余的计算中保持该类型。 -
WIT 到 IPLD 的翻译:
相反,当 WIT 函数返回
float32
或float64
值时,它可以转换回Ipld::Float
。注意:在将
float32
转换为float64
时,后者是 IPLD 的默认精度(见 IPLD),精度将会丢失。在这次转换中,解释器将使用十进制精度。
IPLD 架构定义:
type IPLDFloatAsWit union {
| Float32 float
| Float64 float
} representation kinded
type WitAsIpldFloat union {
| Float32 float
| Float64 float
} representation kinded
字符串
本节概述了 IPLD 字符串值(Ipld::String
)与各种 WIT 运行时值之间的转换过程。一个 Ipld::String
值可以解释为一个 string
、一个 char
或一个(没有有效负载的)enum
区分符。
-
string
-
IPLD 到 WIT 转换
当 WIT 函数期望一个
string
输入时,一个Ipld::String
值会被映射为一个 WIT 运行时值中的string
。示例:
export fn: func(a: string) -> string;
给定此函数的 JSON 输入
{ "args": ["Saspirilla"] }
"Saspirilla"
被转换为Ipld::String
,然后将其翻译并作为字符串参数传递给fn
。 -
WIT 到 IPLD 的翻译:
相反,当从 WIT 函数返回一个
string
值时,它会转换回一个Ipld::String
。
-
-
char
-
IPLD 到 WIT 转换
当 WIT 函数期望一个
char
输入时,一个Ipld::String
值会被映射为一个 WIT 运行时值中的char
。示例:
export fn: func(a: char) -> char;
给定此函数的 JSON 输入
{ "args": ["S"] }
"S"
被转换为Ipld::String
,然后将其翻译并作为 char 参数传递给fn
。 -
WIT 到 IPLD 的翻译:
相反,当从 WIT 函数返回一个 char 值时,它会转换回一个
Ipld::String
。
-
-
enum
:枚举语句定义了一个新的类型,其语义等价于一个没有有效负载类型的变体。
-
IPLD 到 WIT 转换
当 WIT 函数期望一个
enum
输入时,一个Ipld::String
值会被映射为一个 WIT 运行时值中的enum
。示例:
enum color { Red, Green, Blue } export fn: func(a: color) -> string;
给定此函数的 JSON 输入
{ "args": ["Green"] }
"Green"
被转换为Ipld::String
,然后将其翻译并作为 enum 参数传递给fn
(例如color
)。您必须提供一个匹配其中一个区分符的字符串。 -
WIT 到 IPLD 的翻译:
相反,当从 WIT 函数返回一个 enum 值时,它可以转换回一个
Ipld::String
值。
-
IPLD 架构定义:
type Enum enum {
| Red
| Green
| Blue
}
type IPLDStringAsWit union {
| Enum Enum
| String string
| Char string
| Listu8In string
} representation kinded
type WitAsIpldString union {
| Enum Enum
| String string
| Char string
| Listu8Out bytes
} representation kinded
字节
本节概述了 IPLD 字节值(Ipld::Bytes
)与各种 WIT 运行时值之间的转换过程。一个 Ipld::Bytes
值可以解释为一个 list<u8>
或一个 string
。
-
list
:-
IPLD 到 WIT 转换
当WIT函数期望接收一个
list<u8>
输入时,一个Ipld::Bytes
值被映射为一个list<u8>
WIT运行时值。示例:
export fn: func(a: list<u8>) -> list<u8>;
给定此函数的 JSON 输入
{ "args": [{"/": {"bytes": "aGVsbDA"}}] }
"aGVsbDA"
被转换为一个Ipld::Bytes
,然后被转换成字节并作为一个list<u8>
参数传递给fn
。 -
WIT 到 IPLD 的翻译:
相反,当一个
list<u8>
值从WIT函数返回时,如果列表包含有效的u8
值,则将其转换回一个Ipld::Bytes
值。
-
-
string
-
IPLD 到 WIT 转换
当WIT函数期望接收一个
string
输入时,一个Ipld::Bytes
值被映射为一个string
WIT运行时值。示例:
export fn: func(a: string) -> string;
给定此函数的 JSON 输入
{ "args": [{"/": {"bytes": "aGVsbDA"}}] }
"aGVsbDA"
被转换为一个Ipld::Bytes
,然后被转换成字符串并作为一个string
参数传递给fn
。 -
WIT 到 IPLD 的翻译:
在这里,当一个字符串值从WIT函数返回时,它被转换为一个
Ipld::String
值,因为我们无法确定它最初是bytes
。.
-
IPLD 架构定义:
type IPLDBytesAsWit union {
| ListU8 bytes
| StringIn bytes
} representation kinded
type WitAsIpldBytes union {
| ListU8 bytes
| StringOut string
} representation kinded
空值
本节概述了IPLD空值(Ipld::Null
)与各种WIT运行时值之间的转换过程。一个Ipld::Null
值可以解释为string
或option
。
这里我们将仅涵盖字符串的情况,稍后回到option
的情况。
-
IPLD 到 WIT 转换
当WIT函数期望接收一个
string
输入时,一个Ipld::Null
值被映射为一个"null"
string
WIT运行时值。示例:
export fn: func(a: string) -> string;
给定此函数的 JSON 输入
{ "args": [null] }
null
被转换为一个Ipld::Null
,然后被转换并作为一个带有值"null"
的string
参数传递给fn
。 -
WIT 到 IPLD 的翻译:
相反,当一个值为
"null"
的字符串从WIT函数返回时,它可以被转换为一个Ipld::Null
值。
IPLD 架构定义:
type None unit representation null
type IPLDNullAsWit union {
| None
| String string
} representation kinded
type WitAsIpldNull union {
| None
| String string
} representation kinded
链接
本节概述了 IPLD 链接值(Ipld::Link
)与 WIT 运行时字符串值之间的翻译过程。在 WIT 中,Ipld::Link
总是解释为字符串,反之亦然。
-
IPLD 到 WIT 转换
当 WIT 函数期望一个
string
输入时,一个Ipld::Link
值被映射为一个 WIT 运行时字符串值,根据链接是 Cidv0 或 Cidv1 进行相应的翻译。示例:
export fn: func(a: string) -> string;
给定此函数的 JSON 输入
{ "args": ["bafybeia32q3oy6u47x624rmsmgrrlpn7ulruissmz5z2ap6alv7goe7h3q"] }
"bafybeia32q3oy6u47x624rmsmgrrlpn7ulruissmz5z2ap6alv7goe7h3q"
被转换为一个Ipld::Link
,然后被翻译并作为字符串参数传递给fn
。 -
WIT 到 IPLD 的翻译:
相反,当从 WIT 函数返回一个字符串值,并且它可以转换为 Cid 时,它可以被翻译成一个
Ipld::Link
值。
IPLD 架构定义:
type IPLDLinkAsWit &String link
type WitAsIpldLink &String link
非原始类型
接下来,我们将介绍更有趣的 WIT 非原始类型。
列表值
本节概述了 IPLD 列表值(Ipld::List
)与各种 WIT 运行时值之间的翻译过程。一个 Ipld::List
值可以解释为一个列表、元组、一组 flags
或一个 result
。
我们将在下面回到 result
的情况,并在这里介绍其他可能性。.
-
-
IPLD 到 WIT 转换
当 WIT 函数期望一个
list
输入时,一个Ipld::List
值被映射为一个 WIT 运行时列表值。示例:
export fn: func(a: list<s32>, b: s32) -> list<s32>;
给定此函数的 JSON 输入
{ "args": [[1, 2, 3], 44] }
[1, 2, 3]
被转换为一个Ipld::List
,然后被翻译并作为list<s32>
参数传递给fn
。 -
WIT 到 IPLD 的翻译:
相反,当从 WIT 函数返回一个列表值时,它被翻译回一个
Ipld::List
值。
-
-
元组
:-
IPLD 到 WIT 转换
当 WIT 函数期望一个
tuple
输入时,一个Ipld::List
值被映射为一个 WIT 运行时元组值。示例:
type ipv6-socket-address = tuple<u16, u16, u16, u16, u16, u16, u16, u16>; export fn: func(a: ipv6-socket-address) -> tuple<u32, u32>;
给定此函数的 JSON 输入
{ "args": [[8193, 3512, 34211, 0, 0, 35374, 880, 29492]] }
[8193, 3512, 34211, 0, 0, 35374, 880, 29492]
被转换为一个Ipld::List
,然后翻译并作为tuple<u16, u16, u16, u16, u16, u16, u16>
参数传递给fn
。如果列表长度与元组接口类型中的字段数量不匹配,则解释器将抛出错误。
-
WIT 到 IPLD 的翻译:
相反,当 WIT 函数返回一个
tuple
值时,它被翻译回一个Ipld::List
值。
-
-
标志
:flags
代表一个带有每个位的名称的位集结构。类型表示一组命名布尔值。在命名类型的实例中,每个标志将要么为 true,要么为 false。-
IPLD 到 WIT 转换
当 WIT 函数期望一个
flags
输入时,一个Ipld::List
值被映射到flags
WIT 运行时值。当用作输入时,您可以将要打开/设置为 true 的标志作为字符串的子集设置。当用作输出时,您将获得一个字符串列表,表示设置为 true 的标志。
示例:
flags permissions { read, write, exec, } export fn: func(perm: permissions) -> bool;
给定此函数的 JSON 输入
{ "args": [["read", "write"]] }
[read, write]
被转换为一个Ipld::List
,然后翻译并作为permissions
参数传递给fn
。 -
WIT 到 IPLD 的翻译:
相反,当 WIT 函数返回一个
flags
值时,它被翻译回一个Ipld::List
值。
-
IPLD 架构定义:
type IPLDListAsWit union {
| List [any]
| Tuple [any]
| Flags [string]
} representation kinded
type WitAsIpldList union {
| List [any]
| Tuple [any]
| Flags [string]
} representation kinded
映射
本节概述了 IPLD 映射值 (Ipld::Map
) 与各种 WIT 运行时值 之间的翻译过程。一个 Ipld::Map
值可以解释为 record
、variant
或两个元素 tuple
的列表之一。
-
记录
:-
IPLD 到 WIT 转换
当 WIT 函数期望一个
record
输入时,一个Ipld::Map
值被映射到一个record
WIT 运行时值。示例:
record pair { x: u32, y: u32, } export fn: func(a: pair) -> u32;
给定此函数的 JSON 输入
{ "args": [{"x": 1, "y": 2}] }
{"x": 1, "y": 2}
被转换为一个Ipld::Map
,然后翻译并作为pair
参数传递给fn
。映射中的键必须与记录类型中的字段名称匹配.
-
WIT 到 IPLD 的翻译:
相反,当一个
record
值从 WIT 函数返回时,它会转换回一个Ipld::Map
值。
-
-
变体
:变体语句定义了一个新的类型,其中类型的实例恰好匹配该类型列出的变体之一。这与代数数据类型中的“求和”类型类似(或者如果你熟悉 Rust,则类似于枚举)。变体也可以被视为带标签的联合体。
变体的每个情况都可以有一个可选的类型/有效负载与之关联,当值具有该特定情况标签时存在。
-
IPLD 到 WIT 转换
当 WIT 函数期望一个
variant
输入时,一个Ipld::Map
值会被映射到variant
WIT 运行时值。示例:
variant filter { all, none, some(list<string>), } export fn: func(a: filter);
给定此函数的 JSON 输入
{ "args": [{"some" : ["a", "b", "c"]}] }
{"some" : ["a", "b", "c"]}
转换成一个Ipld::Map
,然后转换并作为filter
参数传递给fn
,其中键是变体名称,值是有效负载。映射中的键必须与变体类型中的变体名称匹配.
-
WIT 到 IPLD 的翻译:
相反,当一个
variant
值从 WIT 函数返回时,它会转换回一个Ipld::Map
值,其中标签是键,有效负载是值。
-
-
list
:-
IPLD 到 WIT 转换
当 WIT 函数期望一个包含两个元素
tuples
的嵌套list
作为输入时,一个Ipld::Map
值会被映射到那个特定的 WIT 运行时值。示例:
export fn: func(a: list<tuple<string, u32>>) -> list<u32>;
给定此函数的 JSON 输入
{ "args": [{"a": 1, "b": 2}] }
{"a": 1, "b": 2}
转换成一个Ipld::Map
,然后转换并作为list<tuple<string, u32>>
参数传递给fn
。 -
WIT 到 IPLD 的翻译:
相反,当从 WIT 函数返回一个包含两个元素
tuples
的列表时,它可以转换回一个Ipld::Map
值。
-
IPLD 架构定义:
type TupleAsMap {string:any} representation listpairs
type IPLDMapAsWit union {
| Record {string:any}
| Variant {string:any}
| List TupleAsMap
} representation kinded
type WitAsIpldMap union {
| Record {string:any}
| Variant {string:any}
| List TupleAsMap
} representation kinded
WIT 选项
本节概述了 WIT 选项运行时值(类型为 option
)与各种 IPLD 值之间的转换过程。一个 WIT 选项 可以解释为 Ipld::Null
或任何其他 IPLD 值。
-
IPLD 到 WIT 转换
当WIT函数期望接收一个
option
作为输入时,将一个Ipld::Null
值映射为WIT选项的None
/Unit
情况。否则,任何其他IPLD值将直接映射为其对应的WIT运行时值。示例:
export fn: func(a: option<s32>) -> option<s32>;
-
Some
情况-
Json输入:
{ "args": [1] }
-
-
None
情况-
Json输入:
{ "args": [null] }
-
1
被转换为Ipld::Integer
,然后将其转换为整数参数(s32
),作为选项的Some
情况传入fn
。null
被转换为Ipld::Null
,然后将其转换为选项的None
/Unit
情况传入fn
(即在WIT中没有值)。基本上,您可以将
Ipld::Any
视为Some
情况,将Ipld::Null
视为None
情况。 -
-
WIT 到 IPLD 的翻译:
相反,当WIT函数返回一个
option
值时,如果它是None
/Unit
情况,则可以将其翻译回一个Ipld::Null
值;如果是Some
情况,则可以将其翻译回任何其他IPLD值。
IPLD 架构定义:
type IpldAsWitOption union {
| Some any
| None
} representation kinded
type WitAsIpldOption union {
| Some any
| None
} representation kinded
WIT结果
本节概述了WIT结果运行时值(类型为result
)与各种IPLD值之间的翻译过程。我们将结果视为一个包含两个元素的Ipld::List
的Left/Right either类型。
result
可以解释为以下模式之一
-
Ok
(带有有效载荷)-
IPLD 到 WIT 转换
当WIT函数期望接收一个
result
作为输入时,一个Ipld::List
值可以被映射为result
WIT运行时值的Ok
情况,包括一个有效载荷。示例:
export fn: func(a: result<s32, string>) -> result<s32, string>;
给定此函数的 JSON 输入
{ "args": [[47, null]] }
[47, null]
被转换为Ipld::List
,然后将其翻译并作为带有有效载荷47
的Ok
情况传入fn
,与左侧的s32
类型匹配。 -
WIT 到 IPLD 的翻译:
相反,当从WIT函数返回一个
result
值时,它可以被翻译回具有这种特定结构的Ipld::List
。
-
-
Err
(带有负载)-
IPLD 到 WIT 转换
示例:
export fn: func(a: result<s32, string>) -> result<s32, string>;
给定此函数的 JSON 输入
{ "args": [[null, "error message"]] }
[null, "错误消息"]
被转换为一个Ipld::List
,然后将其翻译并作为带有负载的"错误消息"
的Err
情况传递给result
参数,该负载与右侧的字符串类型匹配。 -
WIT 到 IPLD 的翻译:
相反,当从WIT函数返回一个
result
值时,它可以被翻译回具有这种特定结构的Ipld::List
。
-
-
Ok
情况(无负载)-
IPLD 到 WIT 转换
示例:
export fn: func(a: result<_, string>) -> result<_, string>;
给定此函数的 JSON 输入
{ "args": [[47, null]] }
[47, null]
被转换为一个Ipld::List
,然后将其翻译并作为Ok
情况传递给result
参数。由于不需要负载(在上述类型中表示为_
),因此47
未使用。 -
WIT 到 IPLD 的翻译:
在此,当此特定的
Ok
情况从WIT函数返回时,它可以被转换回一个Ipld::List
,但内部结构为[1, null]
,表示Ok
(非错误)情况,并且丢弃了1
负载。
-
-
Err
情况(无负载)-
IPLD 到 WIT 转换
示例:
export fn: func(a: result<s32, _>) -> result<s32, _>;
给定此函数的 JSON 输入
{ "args": [[null, "error message"]] }
[null, "错误消息"]
被转换为一个Ipld::List
,然后将其翻译并作为Err
情况传递给result
参数。由于不需要负载(在上述类型中表示为_
),因此"错误消息"
未使用。 -
WIT 到 IPLD 的翻译:
在此,当此特定的
Err
情况从WIT函数返回时,它可以被转换回一个Ipld::List
,但内部结构为[null, 1]
,表示Err
(错误)情况,并且丢弃了1
负载。
-
IPLD 架构定义:
type Null unit representation null
type IpldAsWitResult union {
| Ok [any, Null]
| Err [Null, any]
} representation kinded
type WitAsIpldResult union {
| Ok [any, Null]
| OkNone [1, Null]
| Err [Null, any]
| ErrNone [Null, 1]
} representation kinded
注意:any
用于表示任何非Null
的类型。因此,给定一个具有result
类型的输入,以下JSON值的
{
"args": [null, null]
}
将无法被转换为一个Wit Ipld::List
运行时值,因为它在映射到哪个情况时是不确定的。
依赖项
~99MB
~2M SLoC