mirror of
https://github.com/fluencelabs/wasmer
synced 2025-05-15 03:51:20 +00:00
feat(interface-types) Implement the record.lift
instruction.
This commit is contained in:
parent
0af1076eee
commit
02b7e21345
@ -1,7 +1,10 @@
|
|||||||
//! The error module contains all the data structures that represent
|
//! The error module contains all the data structures that represent
|
||||||
//! an error.
|
//! an error.
|
||||||
|
|
||||||
use crate::{ast::InterfaceType, interpreter::Instruction};
|
use crate::{
|
||||||
|
ast::{InterfaceType, TypeKind},
|
||||||
|
interpreter::Instruction,
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
fmt::{self, Display, Formatter},
|
fmt::{self, Display, Formatter},
|
||||||
@ -149,6 +152,21 @@ pub enum InstructionErrorKind {
|
|||||||
|
|
||||||
/// The string contains invalid UTF-8 encoding.
|
/// The string contains invalid UTF-8 encoding.
|
||||||
String(string::FromUtf8Error),
|
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 {}
|
impl Error for InstructionErrorKind {}
|
||||||
@ -196,11 +214,7 @@ impl Display for InstructionErrorKind {
|
|||||||
Self::LocalOrImportSignatureMismatch { function_index, expected, received } => write!(
|
Self::LocalOrImportSignatureMismatch { function_index, expected, received } => write!(
|
||||||
formatter,
|
formatter,
|
||||||
"the local or import function `{}` has the signature `{:?} -> {:?}` but it received values of kind `{:?} -> {:?}`",
|
"the local or import function `{}` has the signature `{:?} -> {:?}` but it received values of kind `{:?} -> {:?}`",
|
||||||
function_index,
|
function_index, expected.0, expected.1, received.0, received.1,
|
||||||
expected.0,
|
|
||||||
expected.1,
|
|
||||||
received.0,
|
|
||||||
received.1,
|
|
||||||
),
|
),
|
||||||
|
|
||||||
Self::LocalOrImportCall { function_index } => write!(
|
Self::LocalOrImportCall { function_index } => write!(
|
||||||
@ -218,14 +232,21 @@ impl Display for InstructionErrorKind {
|
|||||||
Self::MemoryOutOfBoundsAccess { index, length } => write!(
|
Self::MemoryOutOfBoundsAccess { index, length } => write!(
|
||||||
formatter,
|
formatter,
|
||||||
"read out of the memory bounds (index {} > memory length {})",
|
"read out of the memory bounds (index {} > memory length {})",
|
||||||
index,
|
index, length,
|
||||||
length,
|
|
||||||
),
|
),
|
||||||
|
|
||||||
Self::String(error) => write!(
|
Self::String(error) => write!(formatter, "{}", error),
|
||||||
|
|
||||||
|
Self::TypeIsMissing { type_index } => write!(
|
||||||
formatter,
|
formatter,
|
||||||
"{}",
|
"the type `{}` doesn't exist",
|
||||||
error
|
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 argument_get;
|
||||||
mod call_core;
|
mod call_core;
|
||||||
mod numbers;
|
mod numbers;
|
||||||
|
mod records;
|
||||||
mod strings;
|
mod strings;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -10,6 +11,7 @@ use crate::{
|
|||||||
pub(crate) use argument_get::argument_get;
|
pub(crate) use argument_get::argument_get;
|
||||||
pub(crate) use call_core::call_core;
|
pub(crate) use call_core::call_core;
|
||||||
pub(crate) use numbers::*;
|
pub(crate) use numbers::*;
|
||||||
|
pub(crate) use records::*;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
pub(crate) use strings::*;
|
pub(crate) use strings::*;
|
||||||
|
|
||||||
@ -158,9 +160,12 @@ where
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use crate::interpreter::wasm::{
|
use crate::{
|
||||||
self,
|
ast,
|
||||||
values::{InterfaceType, InterfaceValue},
|
interpreter::wasm::{
|
||||||
|
self,
|
||||||
|
values::{InterfaceType, InterfaceValue},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use std::{cell::Cell, collections::HashMap, convert::TryInto, ops::Deref, rc::Rc};
|
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) exports: HashMap<String, Export>,
|
||||||
pub(crate) locals_or_imports: HashMap<usize, LocalImport>,
|
pub(crate) locals_or_imports: HashMap<usize, LocalImport>,
|
||||||
pub(crate) memory: Memory,
|
pub(crate) memory: Memory,
|
||||||
|
pub(crate) wit_types: Vec<ast::Type>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
@ -313,6 +319,15 @@ pub(crate) mod tests {
|
|||||||
hashmap
|
hashmap
|
||||||
},
|
},
|
||||||
memory: Memory::new(vec![Cell::new(0); 128]),
|
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> {
|
fn memory(&self, _index: usize) -> Option<&Memory> {
|
||||||
Some(&self.memory)
|
Some(&self.memory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn wit_type(&self, index: u32) -> Option<&ast::Type> {
|
||||||
|
self.wit_types.get(index as usize)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
224
lib/interface-types/src/interpreter/instructions/records.rs
Normal file
224
lib/interface-types/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::StringSize => instructions::string_size(*instruction),
|
||||||
|
|
||||||
Instruction::RecordLift { type_index: _ } => todo!(),
|
Instruction::RecordLift { type_index } => {
|
||||||
|
instructions::record_lift(*type_index, *instruction)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
use super::values::{InterfaceType, InterfaceValue};
|
use crate::{
|
||||||
|
ast,
|
||||||
|
interpreter::wasm::values::{InterfaceType, InterfaceValue},
|
||||||
|
};
|
||||||
use std::{cell::Cell, ops::Deref};
|
use std::{cell::Cell, ops::Deref};
|
||||||
|
|
||||||
pub trait TypedIndex: Copy + Clone {
|
pub trait TypedIndex: Copy + Clone {
|
||||||
@ -74,6 +77,7 @@ where
|
|||||||
fn export(&self, export_name: &str) -> Option<&E>;
|
fn export(&self, export_name: &str) -> Option<&E>;
|
||||||
fn local_or_import<I: TypedIndex + LocalImportIndex>(&mut self, index: I) -> Option<&LI>;
|
fn local_or_import<I: TypedIndex + LocalImportIndex>(&mut self, index: I) -> Option<&LI>;
|
||||||
fn memory(&self, index: usize) -> Option<&M>;
|
fn memory(&self, index: usize) -> Option<&M>;
|
||||||
|
fn wit_type(&self, index: u32) -> Option<&ast::Type>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Export for () {
|
impl Export for () {
|
||||||
@ -156,4 +160,8 @@ where
|
|||||||
fn local_or_import<I: TypedIndex + LocalImportIndex>(&mut self, _index: I) -> Option<&LI> {
|
fn local_or_import<I: TypedIndex + LocalImportIndex>(&mut self, _index: I) -> Option<&LI> {
|
||||||
None
|
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::I32(_) => Self::I32,
|
||||||
InterfaceValue::I64(_) => Self::I64,
|
InterfaceValue::I64(_) => Self::I64,
|
||||||
InterfaceValue::Record(values) => Self::Record(RecordType {
|
InterfaceValue::Record(values) => Self::Record(RecordType {
|
||||||
fields: values
|
fields: values.iter().map(Into::into).collect(),
|
||||||
.iter()
|
|
||||||
.map(Into::into)
|
|
||||||
.collect::<Vec<InterfaceType>>(),
|
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,12 +41,12 @@
|
|||||||
missing_docs,
|
missing_docs,
|
||||||
nonstandard_style,
|
nonstandard_style,
|
||||||
unreachable_patterns,
|
unreachable_patterns,
|
||||||
|
unsafe_code,
|
||||||
unused_imports,
|
unused_imports,
|
||||||
unused_mut,
|
unused_mut,
|
||||||
unused_unsafe,
|
unused_unsafe,
|
||||||
unused_variables
|
unused_variables
|
||||||
)]
|
)]
|
||||||
#![forbid(unsafe_code)]
|
|
||||||
#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")]
|
#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")]
|
||||||
#![doc(html_logo_url = "https://github.com/wasmerio.png")]
|
#![doc(html_logo_url = "https://github.com/wasmerio.png")]
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user