bors[bot] 4d33020b35
Merge #1331
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>
2020-04-06 06:17:45 +00:00
..
2020-03-11 19:15:31 -07:00
2020-03-30 17:38:51 -07:00
2020-04-02 15:11:41 +00:00
2020-03-23 11:55:31 +09:00

Wasmer Libraries

Wasmer is modularized into different libraries, separated into three main sections:

Runtime

The core of Wasmer is the runtime, which provides the necessary abstractions to create a good user experience when embedding.

The runtime is divided into two main libraries:

  • runtime-core: The main implementation of the runtime.
  • runtime: Easy-to-use API on top of runtime-core.

Integrations

The integration builds on the Wasmer runtime and allow us to run WebAssembly files compiled for different environments.

Wasmer intends to support different integrations:

Backends

The Wasmer runtime is designed to support multiple compiler backends, allowing the user to tune the codegen properties (compile speed, performance, etc) to best fit their use case.

Currently, we support multiple backends for compiling WebAssembly to machine code:

  • singlepass-backend: Single pass backend - super fast compilation, slower runtime speed
  • clif-backend: Cranelift backend - slower compilation, normal runtime speed
  • llvm-backend: LLVM backend - slow compilation, native runtime speed