feat(interface-types) Implement the record.lift instruction.

This commit is contained in:
Ivan Enderlin 2020-03-31 08:54:05 +02:00
parent 5249b243ee
commit 2f0f4b1e84
7 changed files with 292 additions and 21 deletions

View File

@ -1,7 +1,10 @@
//! The error module contains all the data structures that represent
//! an error.
use crate::{ast::InterfaceType, interpreter::Instruction};
use crate::{
ast::{InterfaceType, TypeKind},
interpreter::Instruction,
};
use std::{
error::Error,
fmt::{self, Display, Formatter},
@ -149,6 +152,21 @@ pub enum InstructionErrorKind {
/// The string contains invalid UTF-8 encoding.
String(string::FromUtf8Error),
/// The type doesn't exist.
TypeIsMissing {
/// The type index.
type_index: u32,
},
/// Read a type that has an unexpected type.
InvalidTypeKind {
/// The expected kind.
expected_kind: TypeKind,
/// The received kind.
received_kind: TypeKind,
},
}
impl Error for InstructionErrorKind {}
@ -196,11 +214,7 @@ impl Display for InstructionErrorKind {
Self::LocalOrImportSignatureMismatch { function_index, expected, received } => write!(
formatter,
"the local or import function `{}` has the signature `{:?} -> {:?}` but it received values of kind `{:?} -> {:?}`",
function_index,
expected.0,
expected.1,
received.0,
received.1,
function_index, expected.0, expected.1, received.0, received.1,
),
Self::LocalOrImportCall { function_index } => write!(
@ -218,14 +232,21 @@ impl Display for InstructionErrorKind {
Self::MemoryOutOfBoundsAccess { index, length } => write!(
formatter,
"read out of the memory bounds (index {} > memory length {})",
index,
length,
index, length,
),
Self::String(error) => write!(
Self::String(error) => write!(formatter, "{}", error),
Self::TypeIsMissing { type_index } => write!(
formatter,
"{}",
error
"the type `{}` doesn't exist",
type_index
),
Self::InvalidTypeKind { expected_kind, received_kind } => write!(
formatter,
"read a type of kind `{:?}`, but the kind `{:?}` was expected",
received_kind, expected_kind
),
}
}

View File

@ -1,6 +1,7 @@
mod argument_get;
mod call_core;
mod numbers;
mod records;
mod strings;
use crate::{
@ -10,6 +11,7 @@ use crate::{
pub(crate) use argument_get::argument_get;
pub(crate) use call_core::call_core;
pub(crate) use numbers::*;
pub(crate) use records::*;
use std::convert::TryFrom;
pub(crate) use strings::*;
@ -158,9 +160,12 @@ where
#[cfg(test)]
pub(crate) mod tests {
use crate::interpreter::wasm::{
self,
values::{InterfaceType, InterfaceValue},
use crate::{
ast,
interpreter::wasm::{
self,
values::{InterfaceType, InterfaceValue},
},
};
use std::{cell::Cell, collections::HashMap, convert::TryInto, ops::Deref, rc::Rc};
@ -257,6 +262,7 @@ pub(crate) mod tests {
pub(crate) exports: HashMap<String, Export>,
pub(crate) locals_or_imports: HashMap<usize, LocalImport>,
pub(crate) memory: Memory,
pub(crate) wit_types: Vec<ast::Type>,
}
impl Instance {
@ -313,6 +319,15 @@ pub(crate) mod tests {
hashmap
},
memory: Memory::new(vec![Cell::new(0); 128]),
wit_types: vec![ast::Type::Record(ast::RecordType {
fields: vec![
InterfaceType::I32,
InterfaceType::Record(ast::RecordType {
fields: vec![InterfaceType::String, InterfaceType::F32],
}),
InterfaceType::I64,
],
})],
}
}
}
@ -332,5 +347,9 @@ pub(crate) mod tests {
fn memory(&self, _index: usize) -> Option<&Memory> {
Some(&self.memory)
}
fn wit_type(&self, index: u32) -> Option<&ast::Type> {
self.wit_types.get(index as usize)
}
}
}

View File

@ -0,0 +1,224 @@
use crate::{
ast::{InterfaceType, RecordType, Type, TypeKind},
errors::{InstructionError, InstructionErrorKind},
interpreter::{
stack::{Stack, Stackable},
wasm::values::InterfaceValue,
Instruction,
},
};
use std::mem::{transmute, MaybeUninit};
/// Build a `InterfaceValue::Record` based on values on the stack.
///
/// To fill a record, every field `field_1` to `field_n` must get its
/// value from the stack with `value_1` to `value_n`. To simplify this
/// algorithm that also typed-checks values when hydrating, the number
/// of values to read from the stack isn't known ahead-of-time. Thus,
/// the `Stack::pop` method cannot be used, and `Stack::pop1` is used
/// instead. It implies that values are read one after the other from
/// the stack, in a natural reverse order, from `value_n` to
/// `value_1`.
///
/// Consequently, record fields are filled in reverse order, from
/// `field_n` to `field_1`.
///
/// A basic algorithm would then be:
///
/// ```rust,ignore
/// let mut values = vec![];
///
/// // Read fields in reverse-order, from `field_n` to `field_1`.
/// for field in fields.iter().rev() {
/// let value = stack.pop1();
/// // type-check with `field` and `value`, to finally…
/// values.push(value);
/// }
///
/// InterfaceValue::Record(values.iter().rev().collect())
/// ```
///
/// Note that it is required to reverse the `values` vector at the end
/// because `InterfaceValue::Record` expects its values to match the
/// original `fields` order.
///
/// Because this approach allocates two vectors for `values`, another
/// approach has been adopted. `values` is an initialized vector
/// containing uninitialized values of type
/// `MaybeUninit<InterfaceValue>`. With this approach, it is possible
/// to fill `values` from index `n` to `0`. Once `values` is entirely
/// filled, it is `transmute`d to `Vec<InterfaceType>`.
///
/// This latter approach allows to allocate one and final vector to
/// hold all the record values.
#[allow(unsafe_code)]
fn record_hydrate(
stack: &mut Stack<InterfaceValue>,
record_type: &RecordType,
) -> Result<InterfaceValue, InstructionErrorKind> {
let length = record_type.fields.len();
let mut values = {
// Initialize a vector of length `length` with `MaybeUninit`
// values.
let mut v = Vec::with_capacity(length);
for _ in 0..length {
v.push(MaybeUninit::<InterfaceValue>::uninit());
}
v
};
let max = length - 1;
// Iterate over fields in reverse order to match the stack `pop`
// order.
for (nth, field) in record_type.fields.iter().rev().enumerate() {
match field {
// The record type tells a record is expected.
InterfaceType::Record(record_type) => {
// Build it recursively.
let value = record_hydrate(stack, &record_type)?;
unsafe {
values[max - nth].as_mut_ptr().write(value);
}
}
// Any other type.
ty => {
let value = stack.pop1().unwrap();
let value_type = (&value).into();
if *ty != value_type {
return Err(InstructionErrorKind::InvalidValueOnTheStack {
expected_type: ty.clone(),
received_type: value_type,
});
}
unsafe {
values[max - nth].as_mut_ptr().write(value);
}
}
}
}
Ok(InterfaceValue::Record(unsafe { transmute(values) }))
}
executable_instruction!(
record_lift(type_index: u32, instruction: Instruction) -> _ {
move |runtime| -> _ {
let instance = &runtime.wasm_instance;
let record_type = match instance.wit_type(type_index).ok_or_else(|| {
InstructionError::new(
instruction,
InstructionErrorKind::TypeIsMissing { type_index }
)
})? {
Type::Record(record_type) => record_type,
Type::Function { .. } => return Err(InstructionError::new(
instruction,
InstructionErrorKind::InvalidTypeKind {
expected_kind: TypeKind::Record,
received_kind: TypeKind::Function
}
)),
};
let record = record_hydrate(&mut runtime.stack, &record_type)
.map_err(|k| InstructionError::new(instruction, k))?;
runtime.stack.push(record);
Ok(())
}
}
);
#[cfg(test)]
mod tests {
use crate::ast::{RecordType, Type};
test_executable_instruction!(
test_record_lift =
instructions: [
Instruction::ArgumentGet { index: 0 },
Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 2 },
Instruction::ArgumentGet { index: 3 },
Instruction::RecordLift { type_index: 0 },
],
invocation_inputs: [
InterfaceValue::I32(1),
InterfaceValue::String("Hello".to_string()),
InterfaceValue::F32(2.),
InterfaceValue::I64(3),
],
instance: Instance::new(),
stack: [InterfaceValue::Record(vec![
InterfaceValue::I32(1),
InterfaceValue::Record(vec![
InterfaceValue::String("Hello".to_string()),
InterfaceValue::F32(2.),
]),
InterfaceValue::I64(3),
])],
);
test_executable_instruction!(
test_record_lift__one_dimension =
instructions: [
Instruction::ArgumentGet { index: 0 },
Instruction::ArgumentGet { index: 1 },
Instruction::RecordLift { type_index: 1 },
],
invocation_inputs: [
InterfaceValue::I32(1),
InterfaceValue::I32(2),
],
instance: {
let mut instance = Instance::new();
instance.wit_types.push(
Type::Record(RecordType {
fields: vec![InterfaceType::I32, InterfaceType::I32],
})
);
instance
},
stack: [InterfaceValue::Record(vec![
InterfaceValue::I32(1),
InterfaceValue::I32(2),
])],
);
test_executable_instruction!(
test_record_lift__type_is_missing =
instructions: [
Instruction::RecordLift { type_index: 0 },
],
invocation_inputs: [],
instance: Default::default(),
error: r#"`record.lift 0` the type `0` doesn't exist"#,
);
test_executable_instruction!(
test_record_lift__invalid_value_on_the_stack =
instructions: [
Instruction::ArgumentGet { index: 0 },
Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 2 },
Instruction::ArgumentGet { index: 3 },
Instruction::RecordLift { type_index: 0 },
],
invocation_inputs: [
InterfaceValue::I32(1),
InterfaceValue::String("Hello".to_string()),
InterfaceValue::F64(2.),
// ^^^ F32 is expected
InterfaceValue::I64(3),
],
instance: Instance::new(),
error: r#"`record.lift 0` read a value of type `F64` from the stack, but the type `F32` was expected"#,
);
}

View File

@ -235,7 +235,9 @@ where
}
Instruction::StringSize => instructions::string_size(*instruction),
Instruction::RecordLift { type_index: _ } => todo!(),
Instruction::RecordLift { type_index } => {
instructions::record_lift(*type_index, *instruction)
}
})
.collect();

View File

@ -1,6 +1,9 @@
#![allow(missing_docs)]
use super::values::{InterfaceType, InterfaceValue};
use crate::{
ast,
interpreter::wasm::values::{InterfaceType, InterfaceValue},
};
use std::{cell::Cell, ops::Deref};
pub trait TypedIndex: Copy + Clone {
@ -74,6 +77,7 @@ where
fn export(&self, export_name: &str) -> Option<&E>;
fn local_or_import<I: TypedIndex + LocalImportIndex>(&mut self, index: I) -> Option<&LI>;
fn memory(&self, index: usize) -> Option<&M>;
fn wit_type(&self, index: u32) -> Option<&ast::Type>;
}
impl Export for () {
@ -156,4 +160,8 @@ where
fn local_or_import<I: TypedIndex + LocalImportIndex>(&mut self, _index: I) -> Option<&LI> {
None
}
fn wit_type(&self, _index: u32) -> Option<&ast::Type> {
None
}
}

View File

@ -70,10 +70,7 @@ impl From<&InterfaceValue> for InterfaceType {
InterfaceValue::I32(_) => Self::I32,
InterfaceValue::I64(_) => Self::I64,
InterfaceValue::Record(values) => Self::Record(RecordType {
fields: values
.iter()
.map(Into::into)
.collect::<Vec<InterfaceType>>(),
fields: values.iter().map(Into::into).collect(),
}),
}
}

View File

@ -41,12 +41,12 @@
missing_docs,
nonstandard_style,
unreachable_patterns,
unsafe_code,
unused_imports,
unused_mut,
unused_unsafe,
unused_variables
)]
#![forbid(unsafe_code)]
#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")]
#![doc(html_logo_url = "https://github.com/wasmerio.png")]