4 releases (2 breaking)
0.3.0 | Mar 13, 2024 |
---|---|
0.2.0 | Feb 21, 2024 |
0.1.1 | Jan 20, 2024 |
0.1.0 | Jan 19, 2024 |
#749 in WebAssembly
Used in homestar-runtime
1MB
5.5K
SLoC
Outline
Description
This wasm library manages the wasmtime runtime, provides the IPLD to/from Wasm Interace Types (WIT) interpreter/translation-layer, and implements the input interface for working with Ipvm's standard Wasm tasks.
For more information, please go to our Homestar Readme.
Interpreting between IPLD and WIT
Our recursive interpreter is able to bidirectionally translate between the runtime IPLD data model and WIT values, based on known WIT interface types.
Primitive Types
We'll start by covering WIT primitive types.
Booleans
This section outlines the translation process between IPLD boolean values
(Ipld::Bool
) and WIT bool
runtime values.
-
IPLD to WIT Translation:
When a WIT function expects a
bool
input, anIpld::Bool
value (eithertrue
orfalse
) is mapped to abool
WIT runtime value.Example: Consider a WIT function defined as follows:
export fn: func(a: bool) -> bool;
Given a JSON input for this function:
{ "args": [true] }
true
is converted into anIpld::Bool
, which is then translated and passed intofn
as a boolean argument (bool
). -
WIT to IPLD Translation:
Conversely, when a boolean value is returned from a WIT function, it can be translated back into an
Ipld::Bool
.
IPLD Schema Definition:
type IPLDBooleanAsWit bool
Integers
This section outlines the translation process between IPLD integer values
(Ipld::Integer
) and WIT integer
rutime values.
The Component Model supports these integer types:
ty ::= 'u8' | 'u16' | 'u32' | 'u64'
| 's8' | 's16' | 's32' | 's64'
-
IPLD to WIT Translation:
Typically, when a WIT function expects an integer input, an
Ipld::Integer
value is mapped to an integer WIT runtime value.Example: Consider a WIT function defined as follows:
export fn: func(a: s32) -> s32;
Given a JSON input for this function:
{ "args": [1] }
1
is converted into anIpld::Integer
, which is then translated and passed intofn
as an integer argument (s32
).Note: However, if the input argument to the WIT interface is a
float
type, but the incoming value is anIpld::Integer
, then the IPLD value will be cast to afloat
, and remain as one for the rest of the computation. The cast is to provide affordances for JavaScript where, for example, the number1.0
is converted to1
. -
WIT to IPLD Translation:
Conversely, when an integer value (not a float) is returned from a WIT function, it can be translated back into an
Ipld::Integer
.
IPLD Schema Definitions:
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
Floats
This section outlines the translation process between IPLD float values
(Ipld::Float
) and WIT float
runtime values.
The Component Model supports these Float types:
ty ::= 'float32' | 'float64'
-
IPLD to WIT Translation:
Typically, when a WIT function expects a float input, an
Ipld::Float
value is mapped to a float WIT runtime value. Casting is done to convert fromf32
tof64
if necessary.Example: Consider a WIT function defined as follows:
export fn: func(a: f64) -> f64;
Given a JSON input for this function:
{ "args": [1.0] }
1.0
is converted into anIpld::Float
, which is then translated and passed intofn
as a float argument (f64
).Note: However, if the input argument to the WIT interface is one of the WIT interger types, but the incoming value is an
Ipld::Integer
, then the IPLD value will be cast to that integer type, and remain as one for the rest of the computation. -
WIT to IPLD Translation:
Conversely, when a
float32
orfloat64
value is returned from a WIT function, it can be translated back into anIpld::Float
.Note: In converting from
float32
tofloat64
, the latter of which is the default precision for IPLD, precision will be lost. The interpreter will use decimal precision in this conversion.
IPLD Schema Definitions:
type IPLDFloatAsWit union {
| Float32 float
| Float64 float
} representation kinded
type WitAsIpldFloat union {
| Float32 float
| Float64 float
} representation kinded
Strings
This section outlines the translation process between IPLD string values
(Ipld::String
) and various WIT runtime values. A Ipld::String
value can be
interpreted as one of a string
, char
, or an enum
discriminant
(which has no payload).
-
string
-
IPLD to WIT Translation
When a WIT function expects a
string
input, anIpld::String
value is mapped to astring
WIT runtime value.Example:
export fn: func(a: string) -> string;
Given a JSON input for this function:
{ "args": ["Saspirilla"] }
"Saspirilla"
is converted into anIpld::String
, which is then translated and passed intofn
as a string argument (string
). -
WIT to IPLD Translation:
Conversely, when a
string
value is returned from a WIT function, it is translated back into anIpld::String
.
-
-
char
-
IPLD to WIT Translation
When a WIT function expects a
char
input, anIpld::String
value is mapped to achar
WIT runtime value.Example:
export fn: func(a: char) -> char;
Given a JSON input for this function:
{ "args": ["S"] }
"S"
is converted into anIpld::String
, which is then translated and passed intofn
as a char argument (char
). -
WIT to IPLD Translation:
Conversely, when a char value is returned from a WIT function, it is translated back into an
Ipld::String
.
-
-
enum
:An enum statement defines a new type which is semantically equivalent to a variant where none of the cases have a payload type.
-
IPLD to WIT Translation
When a WIT function expects an
enum
input, anIpld::String
value is mapped to aenum
WIT runtime value.Example:
enum color { Red, Green, Blue } export fn: func(a: color) -> string;
Given a JSON input for this function:
{ "args": ["Green"] }
"Green"
is converted into anIpld::String
, which is then translated and passed intofn
as a enum argument (color
). You'll have to provide a string that matches on one of the discriminants. -
WIT to IPLD Translation:
Conversely, when an enum value is returned from a WIT function, it can be translated back into an
Ipld::String
value.
-
IPLD Schema Definitions:
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
Bytes
This section outlines the translation process between IPLD bytes values
(Ipld::Bytes
) and various WIT runtime values. A Ipld::Bytes
value
can be interpreted either as a list<u8>
or string
.
-
list
:-
IPLD to WIT Translation
When a WIT function expects a
list<u8>
input, anIpld::Bytes
value is mapped to alist<u8>
WIT runtime value.Example:
export fn: func(a: list<u8>) -> list<u8>;
Given a JSON input for this function:
{ "args": [{"/": {"bytes": "aGVsbDA"}}] }
"aGVsbDA"
is converted into anIpld::Bytes
, which is then translated into bytes and passed intofn
as alist<u8>
argument. -
WIT to IPLD Translation:
Conversely, when a
list<u8>
value is returned from a WIT function, it is translated back into anIpld::Bytes
value if the list contains validu8
values.
-
-
string
-
IPLD to WIT Translation
When a WIT function expects a
string
input, anIpld::Bytes
value is mapped to astring
WIT runtime value.Example:
export fn: func(a: string) -> string;
Given a JSON input for this function:
{ "args": [{"/": {"bytes": "aGVsbDA"}}] }
"aGVsbDA"
is converted into anIpld::Bytes
, which is then translated into astring
and passed intofn
as astring
argument. -
WIT to IPLD Translation:
Here, when a string value is returned from a WIT function, it is translated into an
Ipld::String
value, because we can't determine if it was originallybytes
.
-
IPLD Schema Definitions:
type IPLDBytesAsWit union {
| ListU8 bytes
| StringIn bytes
} representation kinded
type WitAsIpldBytes union {
| ListU8 bytes
| StringOut string
} representation kinded
Nulls
This section outlines the translation process between IPLD null values
(Ipld::Null
) and various WIT runtime values. A Ipld::Null
value
can be interpreted either as a string
or option
.
We'll cover only the string
case here and return to the option
case
below.
-
IPLD to WIT Translation
When a WIT function expects a
string
input, anIpld::Null
value is mapped as a"null"
string
WIT runtime value.Example:
export fn: func(a: string) -> string;
Given a JSON input for this function:
{ "args": [null] }
null
is converted into anIpld::Null
, which is then translated and passed intofn
as astring
argument with the value of"null"
. -
WIT to IPLD Translation:
Conversely, when a
string
value of"null"
is returned from a WIT function, it can be translated into anIpld::Null
value.
IPLD Schema Definitions:
type None unit representation null
type IPLDNullAsWit union {
| None
| String string
} representation kinded
type WitAsIpldNull union {
| None
| String string
} representation kinded
Links
This section outlines the translation process between IPLD link values
(Ipld::Link
) and WIT string
runtime values. A Ipld::Link
is always
interpreted as a string
in WIT, and vice versa.
-
IPLD to WIT Translation
When a WIT function expects a
string
input, anIpld::Link
value is mapped to astring
WIT runtime value, translated accordingly based on the link being Cidv0 or Cidv1.Example:
export fn: func(a: string) -> string;
Given a JSON input for this function:
{ "args": ["bafybeia32q3oy6u47x624rmsmgrrlpn7ulruissmz5z2ap6alv7goe7h3q"] }
"bafybeia32q3oy6u47x624rmsmgrrlpn7ulruissmz5z2ap6alv7goe7h3q"
is converted into anIpld::Link
, which is then translated and passed intofn
as astring
argument. -
WIT to IPLD Translation:
Conversely, when a
string
value is returned from a WIT function, and if it can be converted to a Cid, it can then be translated into anIpld::Link
value.
IPLD Schema Definitions:
type IPLDLinkAsWit &String link
type WitAsIpldLink &String link
Non-primitive Types
Next, we'll cover the more interesting, WIT non-primitive types.
List Values
This section outlines the translation process between IPLD list values
(Ipld::List
) and various WIT runtime values. A Ipld::List
value can be interpreted as one of a list
, tuple
, set of flags
,
or a result
.
We'll return to the result
case below, and cover the rest of the
possibilities here.
-
-
IPLD to WIT Translation
When a WIT function expects a
list
input, anIpld::List
value is mapped to alist
WIT runtime value.Example:
export fn: func(a: list<s32>, b: s32) -> list<s32>;
Given a JSON input for this function:
{ "args": [[1, 2, 3], 44] }
[1, 2, 3]
is converted into anIpld::List
, which is then translated and passed intofn
as alist<s32>
argument. -
WIT to IPLD Translation:
Conversely, when a
list
value is returned from a WIT function, it is translated back into anIpld::List
value.
-
-
-
IPLD to WIT Translation
When a WIT function expects a
tuple
input, anIpld::List
value is mapped to atuple
WIT runtime value.Example:
type ipv6-socket-address = tuple<u16, u16, u16, u16, u16, u16, u16, u16>; export fn: func(a: ipv6-socket-address) -> tuple<u32, u32>;
Given a JSON input for this function:
{ "args": [[8193, 3512, 34211, 0, 0, 35374, 880, 29492]] }
[8193, 3512, 34211, 0, 0, 35374, 880, 29492]
is converted into anIpld::List
, which is then translated and passed intofn
as atuple<u16, u16, u16, u16, u16, u16, u16, u16>
argument.If the length of list does not match not match the number of fields in the tuple interface type, then an error will be thrown in the interpreter.
-
WIT to IPLD Translation:
Conversely, when a
tuple
value is returned from a WIT function, it is translated back into anIpld::List
value.
-
-
flags
represent a bitset structure with a name for each bit. The type represents a set of named booleans. In an instance of the named type, each flag will be either true or false.-
IPLD to WIT Translation
When a WIT function expects a
flags
input, anIpld::List
value is mapped to aflags
WIT runtime value.When used as an input, you can set the flags you want turned on/true as an inclusive subset of strings. When used as an output, you will get a list of strings representing the flags that are set to true.
Example:
flags permissions { read, write, exec, } export fn: func(perm: permissions) -> bool;
Given a JSON input for this function:
{ "args": [["read", "write"]] }
[read, write]
is converted into anIpld::List
, which is then translated and passed intofn
as apermissions
argument. -
WIT to IPLD Translation:
Conversely, when a
flags
value is returned from a WIT function, it is translated back into anIpld::List
value.
-
IPLD Schema Definitions:
type IPLDListAsWit union {
| List [any]
| Tuple [any]
| Flags [string]
} representation kinded
type WitAsIpldList union {
| List [any]
| Tuple [any]
| Flags [string]
} representation kinded
Maps
This section outlines the translation process between IPLD map values
(Ipld::Map
) and various WIT runtime values. A Ipld::Map
value can be interpreted as one of a record
, variant
, or
a list
of two-element tuples
.
-
-
IPLD to WIT Translation
When a WIT function expects a
record
input, anIpld::Map
value is mapped to arecord
WIT runtime value.Example:
record pair { x: u32, y: u32, } export fn: func(a: pair) -> u32;
Given a JSON input for this function:
{ "args": [{"x": 1, "y": 2}] }
{"x": 1, "y": 2}
is converted into anIpld::Map
, which is then translated and passed intofn
as apair
argument.The keys in the map must match the field names in the record type.
-
WIT to IPLD Translation:
Conversely, when a
record
value is returned from a WIT function, it is translated back into anIpld::Map
value.
-
-
A variant statement defines a new type where instances of the type match exactly one of the variants listed for the type. This is similar to a "sum" type in algebraic datatypes (or an enum in Rust if you're familiar with it). Variants can be thought of as tagged unions as well.
Each case of a variant can have an optional type / payload associated with it which is present when values have that particular case's tag.
-
IPLD to WIT Translation
When a WIT function expects a
variant
input, anIpld::Map
value is mapped to avariant
WIT runtime value.Example:
variant filter { all, none, some(list<string>), } export fn: func(a: filter);
Given a JSON input for this function:
{ "args": [{"some" : ["a", "b", "c"]}] }
{"some" : ["a", "b", "c"]}
is converted into anIpld::Map
, which is then translated and passed intofn
as afilter
argument, where the key is the variant name and the value is the payload.The keys in the map must match the variant names in the variant type.
-
WIT to IPLD Translation:
Conversely, when a
variant
value is returned from a WIT function, it is translated back into anIpld::Map
value where the tag is the key and payload is the value.
-
-
list
:-
IPLD to WIT Translation
When a WIT function expects a nested
list
of two-elementtuples
as input, anIpld::Map
value is mapped to that specific WIT runtime value.Example:
export fn: func(a: list<tuple<string, u32>>) -> list<u32>;
Given a JSON input for this function:
{ "args": [{"a": 1, "b": 2}] }
{"a": 1, "b": 2}
is converted into anIpld::Map
, which is then translated and passed intofn
as alist<tuple<string, u32>>
argument. -
WIT to IPLD Translation:
Conversely, when a
list
of two-elementtuples
is returned from a WIT function, it can be translated back into anIpld::Map
value.
-
IPLD Schema Definitions:
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 Options
This section outlines the translation process between WIT option runtime values
(of type option
) and various IPLD values. An option
can be interpreted
as either a Ipld::Null
or of any other IPLD value.
-
IPLD to WIT Translation
When a WIT function expects an
option
as input, anIpld::Null
value is mapped to theNone
/Unit
case for a WIT option. Otherwise, any other IPLD value will be mapped to its matching WIT runtime value directly.Example:
export fn: func(a: option<s32>) -> option<s32>;
-
Some
case:-
Json Input:
{ "args": [1] }
-
-
None
case:-
Json Input:
{ "args": [null] }
-
1
is converted into anIpld::Integer
, which is then translated and passed intofn
as an integer argument (s32
), as theSome
case of the option.null
is converted into anIpld::Null
, which is then translated and passed intofn
as aNone
/Unit
case of the option (i.e. no value in WIT).Essentially, you can view this as
Ipld::Any
being theSome
case andIpld::Null
being theNone
case. -
-
WIT to IPLD Translation:
Conversely, when an
option
value is returned from a WIT function, it can be translated back into anIpld::Null
value if it's theNone
/Unit
case, or any other IPLD value if it's theSome
case.
IPLD Schema Definitions:
type IpldAsWitOption union {
| Some any
| None
} representation kinded
type WitAsIpldOption union {
| Some any
| None
} representation kinded
WIT Results
This section outlines the translation process between WIT result runtime values
(of type result
) and various IPLD values. We treat result as Left/Right
either types over an Ipld::List
of two elements.
A result
can be interpreted as one of these patterns:
-
Ok
(with a payload)-
IPLD to WIT Translation
When a WIT function expects a
result
as input, anIpld::List
value can be mapped to theOk
case of theresult
WIT runtime value, including a payload.Example:
export fn: func(a: result<s32, string>) -> result<s32, string>;
Given a JSON input for this function:
{ "args": [[47, null]] }
[47, null]
is converted into anIpld::List
, which is then translated and passed intofn
as anOk
case of theresult
argument with a payload of47
matching thes32
type on the left. -
WIT to IPLD Translation:
Conversely, when a
result
value is returned from a WIT function, it can be translated back into anIpld::List
of this specific structure.
-
-
Err
(with a payload)-
IPLD to WIT Translation
Example:
export fn: func(a: result<s32, string>) -> result<s32, string>;
Given a JSON input for this function:
{ "args": [[null, "error message"]] }
[null, "error message"]
is converted into anIpld::List
, which is then translated and passed intofn
as anErr
case of theresult
argument with a payload of"error message"
matching thestring
type on the right. -
WIT to IPLD Translation:
Conversely, when a
result
value is returned from a WIT function, it can be translated back into anIpld::List
of this specific structure.
-
-
Ok
case (without a payload)-
IPLD to WIT Translation
Example:
export fn: func(a: result<_, string>) -> result<_, string>;
Given a JSON input for this function:
{ "args": [[47, null]] }
[47, null]
is converted into anIpld::List
, which is then translated and passed intofn
as anOk
case of theresult
argument. The payload is ignored as it's not needed (expressed in the type as_
above), so47
is not used. -
WIT to IPLD Translation:
Here, when this specific
Ok
case is returned from a WIT function, it can be translated back into anIpld::List
, but one structured as[1, null]
internally, which signifies theOk
(not error) case, with the1
payload discarded.
-
-
Err
case (without a payload)-
IPLD to WIT Translation
Example:
export fn: func(a: result<s32, _>) -> result<s32, _>;
Given a JSON input for this function:
{ "args": [[null, "error message"]] }
[null, "error message"]
is converted into anIpld::List
, which is then translated and passed intofn
as anErr
case of theresult
argument. The payload is ignored as it's not needed (expressed in the type as_
above), so"error message"
is not used. -
WIT to IPLD Translation:
Here, when this specific
Err
case is returned from a WIT function, it can be translated back into anIpld::List
, but one structured as[null, 1]
internally, which signifies theErr
(error) case, with the1
payload discarded.
-
IPLD Schema Definitions:
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
Note: any
is used here to represent any type that's not Null
. So,
given an input with a result
type, the JSON value of
{
"args": [null, null]
}
will fail to be translated into a Wit result
runtime value, as it's ambiguous
which case it should be mapped to.
Dependencies
~98MB
~2M SLoC