mirror of
https://github.com/fluencelabs/interface-types
synced 2024-12-04 15:20:20 +00:00
feat(interface-types) Implement the record.lift
instruction.
This commit is contained in:
parent
5249b243ee
commit
2f0f4b1e84
@ -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
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
224
src/interpreter/instructions/records.rs
Normal file
224
src/interpreter/instructions/records.rs
Normal 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"#,
|
||||
);
|
||||
}
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -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")]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user