#ipld #wit #wasmtime #ipvm

homestar-wasm

Homestar Wasm / Wasmtime 实现,以及 IPLD <=> WIT 解释器

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 日

#613WebAssembly


homestar-runtime 中使用

Apache-2.0

1MB
5.5K SLoC

Homestar logo

homestar-wasm

Crate License Docs Discord

大纲

描述

这个 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 运行时值。如果需要,进行从 f32f64 的类型转换。

    示例:考虑一个如下定义的 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 函数返回 float32float64 值时,它可以转换回 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值可以解释为stringoption

这里我们将仅涵盖字符串的情况,稍后回到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 运行时字符串值,根据链接是 Cidv0Cidv1 进行相应的翻译。

    示例:

    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 的情况,并在这里介绍其他可能性。.

  • list

    • 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 值可以解释为 recordvariant 或两个元素 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,然后将其翻译并作为带有有效载荷47Ok情况传入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