1362: feat(interface-types) Remove allocator index from `string.lower_memory` r=Hywan a=Hywan
This PR updates `string.lower_memory` to remove the allocator index. Indeed, the string pointer is assumed to be present on the stack.
Also, this PR updates `string.size` to pop, and not to peek, the string to compute the length from.
That way, it matches the WIT proposal.
Co-authored-by: Ivan Enderlin <ivan@mnt.io>
1363: fix(interface-types) Don't duplicate code in the binary encoder. r=MarkMcCaskey a=Hywan
Use the `ToBytes` implementation of `RecordType` to encode the inner
record type of a type, so that it avoids code duplication.
Co-authored-by: Ivan Enderlin <ivan@mnt.io>
1354: test(middleware-common) Remove an unused import r=MarkMcCaskey a=Hywan
This PR removes an unused import.
Co-authored-by: Ivan Enderlin <ivan.enderlin@hoa-project.net>
1331: feat(interface-types) Implement the `record` instructions r=Hywan a=Hywan
### Description
This PR implements the `record` WIT type, along with the `record.lift` and `record.lower` instructions.
With my current understanding of the draft/specification, here is how it works. Let's say we want to represent a Rust struct like the following:
```rust
struct S {
x: String,
y: i32
}
```
First declare a WIT type, such as:
```wat
(@interface type (record (field string) (field i32)))
```
The `record` type is supported by the binary encoder, the WAT encoder, the binary decoder, and the WAT decoder. A new `TypeKind` node has been introduced in the AST to differentiate a function type (`(@interface type (func (param …) (result …)))`) of a record type (see above).
Second, the `record.lower` transforms a host value (here Rust value, `S`) into a WIT value. In our implementation, a record value is defined as:
```rust
InterfaceValue::Record(Vec<InterfaceValue>)
```
Multiple mechanisms are used to type check a record value based on a record type. The code of the `record.lower` is pretty straightforward.
Because transforming a host value into a WIT value isn't obvious, a `Serializer` has been implemented, based on [`serde`](https://serde.rs/). This feature is behind the `serde` flag, which is turned on by default.
Serde is only used to cross the host/Wasm boundary, but it's not used to represent the value in memory or anything. It's only a shortcut to transform a host value into a WIT value, and vice versa.
Use the following code to transform `S` into a WIT value:
```rust
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct S {
x: String,
y: i32,
}
let host_value = S { x: "hello".to_string(), y: 42 };
let wit_value = to_interface_value(&host_value).unwrap();
assert_eq!(
wit_value,
InterfaceValue::Record(vec![
InterfaceValue::String("hello".to_string()),
InterfaceValue::I32(42),
])
);
```
Third, the `record.lift` instruction does the opposite of `record.lower`: It transforms WIT values into a host value. To also facilitate the user experience, this PR contains a `Deserializer` implementation, still based on `serde`, with the `from_interface_values` function. It looks like this:
```rust
let wit_values = vec![
InterfaceValue::Record(vec![
InterfaceValue::String("hello".to_string()),
InterfaceValue::I32(42),
])
];
let host_value = from_interface_values::<S>(&wit_values).unwrap();
assert_eq!(
host_value,
S { x: "hello".to_string(), y: 42 },
);
```
With the `Serializer` and `Deserializer`, it's super easy for the user to send or receive values from WIT.
The `record.lift` and `record.lower` instructions are kind of basic. The `record.lift` instruction has a little trick to reduce vector allocations, but there is a documentation for that.
#### Opened questions
Records of dimension 1 do not raise any issue. With `record.lift`, all values on the stack (the WIT interpreter stack) are popped, and are used as record field values. Something like:
```
[stack]
i32(1)
i64(2),
string("hello")
record.lift <record_type>
```
generates
```
[stack]
record { i32(1), i64(2), string("hello") }
```
But it's not clear what happens with record of dimension > 1, for instance for a type like `record (field i32) (record (field i32) (field i32)) (field string)`, it is assumed (in this PR) that the stack must be like this:
```
[stack]
i32(1)
i32(2)
i32(3)
string("hello")
record.lift <record_type>
```
to generate:
```
[stack]
record { i32(1), record { i32(2), i32(3) }, string("hello") }
```
If we want the stack to contain an intermediate record, we should have something like this:
```
[stack]
i32(1)
i32(2)
i32(3)
record.lift <record_type_2>
string("hello")
record.lift <record_type_1>
```
But it would imply that `record_type_1` is defined as `record (field i32) (record (type record_type_2)) (field i32)`.
A sub-record defined by another record type isn't support, as it is not specified in the draft. I believe my assumption is fine enough for a first implementation of records in WIT.
### To do
- [x] Encode and decode record type (`(@interface type (record string i32))`):
- [x] Binary encoder/decoder
- [x] WAT encoder/decoder
- [x] Implement the `record.lift` instruction
- [x] Implement the `record.lower` instruction
- [x] Test
- [x] Documentation
- [x] Surprise!
- [x] Serialize a Rust value to WIT values (useful for `record`s)
- [x] Deserialize WIT values to a Rust value (useful for `record`s)
Co-authored-by: Ivan Enderlin <ivan.enderlin@hoa-project.net>
`Vec1` is used by `RecordType` to ensure that a record have at least 1
field. Then an `InterfaceValue::Record` is ensured to get at least one
value with the type-checking pass.
1350: update blake3 to 0.3.1 r=syrusakbary a=oconnor663
Version 0.3.0 caused problems because it required a C compiler with
AVX-512 support, which broke Android x86 cross-compilation. Version
0.3.1 automatically falls back to a pure Rust build when the C compiler
either doesn't exist or doesn't support the flags we need.
Co-authored-by: Jack O'Connor <oconnor663@gmail.com>
Version 0.3.0 caused problems because it required a C compiler with
AVX-512 support, which broke Android x86 cross-compilation. Version
0.3.1 automatically falls back to a pure Rust build when the C compiler
either doesn't exist or doesn't support the flags we need.
`seq`, `map` and `tuple` for instance are not supported officially. It
was fun to play with it at the beginning, but it is time to remove it
to match the `Serializer` implementation.
WIT values are native Rust values. But records are represented as a
vector of WIT values. In order to provide a super neat API to the
user, Serde is used to deserialize this vector of WIT values to a
large variety of Rust values.
The `Type::Record` variant now is defined by `RecordType`. In
addition, `InterfaceType` has a new variant: `Record`, that is also
defined by `RecordType`. Encoders and decoders are updated to consider
`RecordType`, which removes code duplication and simplify code.
This patch updates the `Type` type to be an enum with 2 variants:
`Function` and `Record`, resp. to represent:
1. `(@interface type (func (param i32 i32) (result string)))`
2. `(@interface type (record string i32))`
This patch updates the binary encoder and decoder, along with the WAT
encoder and decoder.
The index is bound to `u32::max_value()`. The invocation inputs'
length is bound to `usize::max_value()`, which can be
`u64::max_value`. Consequently, casting the invocation inputs' length
to `u32` can lead to an integer overflow. It is better to cast `index`
to `usize` when comparing with the invocation inputs' length.