From 2f0f4b1e84f57499b4f44d362c173332df7e6372 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 31 Mar 2020 08:54:05 +0200 Subject: [PATCH] feat(interface-types) Implement the `record.lift` instruction. --- src/errors.rs | 43 +++-- src/interpreter/instructions/mod.rs | 25 ++- src/interpreter/instructions/records.rs | 224 ++++++++++++++++++++++++ src/interpreter/mod.rs | 4 +- src/interpreter/wasm/structures.rs | 10 +- src/interpreter/wasm/values.rs | 5 +- src/lib.rs | 2 +- 7 files changed, 292 insertions(+), 21 deletions(-) create mode 100644 src/interpreter/instructions/records.rs diff --git a/src/errors.rs b/src/errors.rs index 5df5c49..f2f4aad 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -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 ), } } diff --git a/src/interpreter/instructions/mod.rs b/src/interpreter/instructions/mod.rs index 383c596..96657b5 100644 --- a/src/interpreter/instructions/mod.rs +++ b/src/interpreter/instructions/mod.rs @@ -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, pub(crate) locals_or_imports: HashMap, pub(crate) memory: Memory, + pub(crate) wit_types: Vec, } 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) + } } } diff --git a/src/interpreter/instructions/records.rs b/src/interpreter/instructions/records.rs new file mode 100644 index 0000000..bea4a5c --- /dev/null +++ b/src/interpreter/instructions/records.rs @@ -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`. 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`. +/// +/// 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, + record_type: &RecordType, +) -> Result { + 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::::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"#, + ); +} diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 7a553d7..ac80d36 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -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(); diff --git a/src/interpreter/wasm/structures.rs b/src/interpreter/wasm/structures.rs index dbeab83..7d9c267 100644 --- a/src/interpreter/wasm/structures.rs +++ b/src/interpreter/wasm/structures.rs @@ -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(&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(&mut self, _index: I) -> Option<&LI> { None } + + fn wit_type(&self, _index: u32) -> Option<&ast::Type> { + None + } } diff --git a/src/interpreter/wasm/values.rs b/src/interpreter/wasm/values.rs index 53d677f..5cd2ea1 100644 --- a/src/interpreter/wasm/values.rs +++ b/src/interpreter/wasm/values.rs @@ -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::>(), + fields: values.iter().map(Into::into).collect(), }), } } diff --git a/src/lib.rs b/src/lib.rs index 9e88cac..ebf8603 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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")]