diff --git a/src/decoders/binary.rs b/src/decoders/binary.rs index 367551d..2661d6f 100644 --- a/src/decoders/binary.rs +++ b/src/decoders/binary.rs @@ -207,13 +207,13 @@ fn instruction<'input, E: ParseError<&'input [u8]>>( 0x20 => (input, Instruction::I64FromU32), 0x21 => (input, Instruction::I64FromU64), - 0x22 => (input, Instruction::MemoryToString), + 0x22 => (input, Instruction::StringLiftMemory), 0x23 => { consume!((input, argument_0) = uleb(input)?); ( input, - Instruction::StringToMemory { + Instruction::StringLowerMemory { allocator_index: argument_0 as u32, }, ) @@ -655,8 +655,8 @@ mod tests { 0x1f, // I64FromU16 0x20, // I64FromU32 0x21, // I64FromU64 - 0x22, // MemoryToString - 0x23, 0x01, // StringToMemory { allocator_index: 1 } + 0x22, // StringLiftMemory + 0x23, 0x01, // StringLowerMemory { allocator_index: 1 } 0x0a, ]; let output = Ok(( @@ -696,8 +696,8 @@ mod tests { Instruction::I64FromU16, Instruction::I64FromU32, Instruction::I64FromU64, - Instruction::MemoryToString, - Instruction::StringToMemory { allocator_index: 1 }, + Instruction::StringLiftMemory, + Instruction::StringLowerMemory { allocator_index: 1 }, ], )); diff --git a/src/decoders/wat.rs b/src/decoders/wat.rs index 656673b..a3469ea 100644 --- a/src/decoders/wat.rs +++ b/src/decoders/wat.rs @@ -60,8 +60,8 @@ mod keyword { custom_keyword!(i64_from_u16 = "i64.from_u16"); custom_keyword!(i64_from_u32 = "i64.from_u32"); custom_keyword!(i64_from_u64 = "i64.from_u64"); - custom_keyword!(memory_to_string = "memory-to-string"); - custom_keyword!(string_to_memory = "string-to-memory"); + custom_keyword!(string_lift_memory = "string.lift_memory"); + custom_keyword!(string_lower_memory = "string.lower_memory"); } impl Parse<'_> for InterfaceType { @@ -275,14 +275,14 @@ impl<'a> Parse<'a> for Instruction { parser.parse::()?; Ok(Instruction::I64FromU64) - } else if lookahead.peek::() { - parser.parse::()?; + } else if lookahead.peek::() { + parser.parse::()?; - Ok(Instruction::MemoryToString) - } else if lookahead.peek::() { - parser.parse::()?; + Ok(Instruction::StringLiftMemory) + } else if lookahead.peek::() { + parser.parse::()?; - Ok(Instruction::StringToMemory { + Ok(Instruction::StringLowerMemory { allocator_index: parser.parse()?, }) } else { @@ -664,8 +664,8 @@ mod tests { "i64.from_u16", "i64.from_u32", "i64.from_u64", - "memory-to-string", - "string-to-memory 42", + "string.lift_memory", + "string.lower_memory 42", ]; let outputs = vec![ Instruction::ArgumentGet { index: 7 }, @@ -702,8 +702,8 @@ mod tests { Instruction::I64FromU16, Instruction::I64FromU32, Instruction::I64FromU64, - Instruction::MemoryToString, - Instruction::StringToMemory { + Instruction::StringLiftMemory, + Instruction::StringLowerMemory { allocator_index: 42, }, ]; diff --git a/src/encoders/binary.rs b/src/encoders/binary.rs index 9a89e3e..aece560 100644 --- a/src/encoders/binary.rs +++ b/src/encoders/binary.rs @@ -293,9 +293,9 @@ where Instruction::I64FromU32 => 0x20_u8.to_bytes(writer)?, Instruction::I64FromU64 => 0x21_u8.to_bytes(writer)?, - Instruction::MemoryToString => 0x22_u8.to_bytes(writer)?, + Instruction::StringLiftMemory => 0x22_u8.to_bytes(writer)?, - Instruction::StringToMemory { allocator_index } => { + Instruction::StringLowerMemory { allocator_index } => { 0x23_u8.to_bytes(writer)?; (*allocator_index as u64).to_bytes(writer)?; } @@ -575,8 +575,8 @@ mod tests { Instruction::I64FromU16, Instruction::I64FromU32, Instruction::I64FromU64, - Instruction::MemoryToString, - Instruction::StringToMemory { allocator_index: 1 }, + Instruction::StringLiftMemory, + Instruction::StringLowerMemory { allocator_index: 1 }, ], &[ 0x24, // list of 36 items @@ -614,8 +614,8 @@ mod tests { 0x1f, // I64FromU16 0x20, // I64FromU32 0x21, // I64FromU64 - 0x22, // MemoryToString - 0x23, 0x01, // StringToMemory { allocator_index: 1 } + 0x22, // StringLiftMemory + 0x23, 0x01, // StringLowerMemory { allocator_index: 1 } ] ); } diff --git a/src/encoders/wat.rs b/src/encoders/wat.rs index eb29201..4914225 100644 --- a/src/encoders/wat.rs +++ b/src/encoders/wat.rs @@ -117,9 +117,9 @@ impl ToString for &Instruction { Instruction::I64FromU16 => "i64.from_u16".into(), Instruction::I64FromU32 => "i64.from_u32".into(), Instruction::I64FromU64 => "i64.from_u64".into(), - Instruction::MemoryToString => "memory-to-string".into(), - Instruction::StringToMemory { allocator_index } => { - format!(r#"string-to-memory {}"#, allocator_index) + Instruction::StringLiftMemory => "string.lift_memory".into(), + Instruction::StringLowerMemory { allocator_index } => { + format!(r#"string.lower_memory {}"#, allocator_index) } } } @@ -386,8 +386,8 @@ mod tests { (&Instruction::I64FromU16).to_string(), (&Instruction::I64FromU32).to_string(), (&Instruction::I64FromU64).to_string(), - (&Instruction::MemoryToString).to_string(), - (&Instruction::StringToMemory { + (&Instruction::StringLiftMemory).to_string(), + (&Instruction::StringLowerMemory { allocator_index: 42, }) .to_string(), @@ -427,8 +427,8 @@ mod tests { "i64.from_u16", "i64.from_u32", "i64.from_u64", - "memory-to-string", - "string-to-memory 42", + "string.lift_memory", + "string.lower_memory 42", ]; assert_eq!(inputs, outputs); diff --git a/src/interpreter/instruction.rs b/src/interpreter/instruction.rs index 89959f3..0ef4c94 100644 --- a/src/interpreter/instruction.rs +++ b/src/interpreter/instruction.rs @@ -15,15 +15,6 @@ pub enum Instruction { function_index: usize, }, - /// The `memory-to-string` instruction. - MemoryToString, - - /// The `string-to-memory` instruction. - StringToMemory { - /// The allocator function index. - allocator_index: u32, - }, - /// The `s8.from_i32` instruction. S8FromI32, @@ -119,4 +110,13 @@ pub enum Instruction { /// The `i64.from_u64` instruction. I64FromU64, + + /// The `string.lift_memory` instruction. + StringLiftMemory, + + /// The `string.lower_memory` instruction. + StringLowerMemory { + /// The allocator function index. + allocator_index: u32, + }, } diff --git a/src/interpreter/instructions/memory_to_string.rs b/src/interpreter/instructions/memory_to_string.rs deleted file mode 100644 index 8e9902c..0000000 --- a/src/interpreter/instructions/memory_to_string.rs +++ /dev/null @@ -1,159 +0,0 @@ -use super::to_native; -use crate::{ - errors::{InstructionError, InstructionErrorKind}, - interpreter::{wasm::values::InterfaceValue, Instruction}, -}; -use std::cell::Cell; - -executable_instruction!( - memory_to_string(instruction: Instruction) -> _ { - move |runtime| -> _ { - let inputs = runtime.stack.pop(2).ok_or_else(|| { - InstructionError::new( - instruction, - InstructionErrorKind::StackIsTooSmall { needed: 2 }, - ) - })?; - - let memory_index: u32 = 0; - - let memory = runtime - .wasm_instance - .memory(memory_index as usize) - .ok_or_else(|| { - InstructionError::new( - instruction, - InstructionErrorKind::MemoryIsMissing { memory_index }, - ) - })?; - - let pointer = to_native::(&inputs[0], instruction)? as usize; - let length = to_native::(&inputs[1], instruction)? as usize; - let memory_view = memory.view(); - - if length == 0 { - runtime.stack.push(InterfaceValue::String("".into())); - - return Ok(()) - } - - if memory_view.len() <= pointer + length - 1 { - return Err(InstructionError::new( - instruction, - InstructionErrorKind::MemoryOutOfBoundsAccess { - index: pointer + length, - length: memory_view.len(), - }, - )); - } - - let data: Vec = (&memory_view[pointer..=pointer + length - 1]) - .iter() - .map(Cell::get) - .collect(); - - let string = String::from_utf8(data) - .map_err(|error| InstructionError::new(instruction, InstructionErrorKind::String(error)))?; - - runtime.stack.push(InterfaceValue::String(string)); - - Ok(()) - } - } -); - -#[cfg(test)] -mod tests { - test_executable_instruction!( - test_memory_to_string = - instructions: [ - Instruction::ArgumentGet { index: 0 }, - Instruction::ArgumentGet { index: 1 }, - Instruction::MemoryToString, - ], - invocation_inputs: [ - InterfaceValue::I32(0), - // ^^^^^^ pointer - InterfaceValue::I32(13), - // ^^^^^^^ length - ], - instance: Instance { - memory: Memory::new("Hello, World!".as_bytes().iter().map(|u| Cell::new(*u)).collect()), - ..Default::default() - }, - stack: [InterfaceValue::String("Hello, World!".into())], - ); - - test_executable_instruction!( - test_memory_to_string__empty_string = - instructions: [ - Instruction::ArgumentGet { index: 0 }, - Instruction::ArgumentGet { index: 1 }, - Instruction::MemoryToString, - ], - invocation_inputs: [ - InterfaceValue::I32(0), - InterfaceValue::I32(0), - ], - instance: Instance { - memory: Memory::new(vec![]), - ..Default::default() - }, - stack: [InterfaceValue::String("".into())], - ); - - test_executable_instruction!( - test_memory_to_string__read_out_of_memory = - instructions: [ - Instruction::ArgumentGet { index: 0 }, - Instruction::ArgumentGet { index: 1 }, - Instruction::MemoryToString, - ], - invocation_inputs: [ - InterfaceValue::I32(0), - // ^^^^^^ pointer - InterfaceValue::I32(13), - // ^^^^^^^ length is too long - ], - instance: Instance { - memory: Memory::new("Hello!".as_bytes().iter().map(|u| Cell::new(*u)).collect()), - ..Default::default() - }, - error: r#"`memory-to-string` read out of the memory bounds (index 13 > memory length 6)"#, - ); - - test_executable_instruction!( - test_memory_to_string__invalid_encoding = - instructions: [ - Instruction::ArgumentGet { index: 0 }, - Instruction::ArgumentGet { index: 1 }, - Instruction::MemoryToString, - ], - invocation_inputs: [ - InterfaceValue::I32(0), - // ^^^^^^ pointer - InterfaceValue::I32(4), - // ^^^^^^ length is too long - ], - instance: Instance { - memory: Memory::new(vec![0, 159, 146, 150].iter().map(|b| Cell::new(*b)).collect::>>()), - ..Default::default() - }, - error: r#"`memory-to-string` invalid utf-8 sequence of 1 bytes from index 1"#, - ); - - test_executable_instruction!( - test_memory_to_string__stack_is_too_small = - instructions: [ - Instruction::ArgumentGet { index: 0 }, - Instruction::MemoryToString, - // ^^^^^^^^^^^^^^ `memory-to-string` expects 2 values on the stack, only one is present. - ], - invocation_inputs: [ - InterfaceValue::I32(0), - InterfaceValue::I32(13), - ], - instance: Instance::new(), - error: r#"`memory-to-string` needed to read `2` value(s) from the stack, but it doesn't contain enough data"#, - ); -} diff --git a/src/interpreter/instructions/mod.rs b/src/interpreter/instructions/mod.rs index 34a5dbd..9d7f072 100644 --- a/src/interpreter/instructions/mod.rs +++ b/src/interpreter/instructions/mod.rs @@ -1,8 +1,7 @@ mod argument_get; mod call_core; -mod memory_to_string; mod numbers; -mod string_to_memory; +mod strings; use crate::{ errors::{InstructionError, InstructionErrorKind, InstructionResult, WasmValueNativeCastError}, @@ -13,10 +12,9 @@ use crate::{ }; pub(crate) use argument_get::argument_get; pub(crate) use call_core::call_core; -pub(crate) use memory_to_string::memory_to_string; pub(crate) use numbers::*; use std::convert::TryFrom; -pub(crate) use string_to_memory::string_to_memory; +pub(crate) use strings::*; /// Just a short helper to map the error of a cast from an /// `InterfaceValue` to a native value. diff --git a/src/interpreter/instructions/string_to_memory.rs b/src/interpreter/instructions/string_to_memory.rs deleted file mode 100644 index a4341a7..0000000 --- a/src/interpreter/instructions/string_to_memory.rs +++ /dev/null @@ -1,175 +0,0 @@ -use super::to_native; -use crate::{ - ast::InterfaceType, - errors::{InstructionError, InstructionErrorKind}, - interpreter::{ - wasm::{ - structures::{FunctionIndex, TypedIndex}, - values::InterfaceValue, - }, - Instruction, - }, -}; - -executable_instruction!( - string_to_memory(allocator_index: u32, instruction: Instruction) -> _ { - move |runtime| -> _ { - let instance = &mut runtime.wasm_instance; - let index = FunctionIndex::new(allocator_index as usize); - - let allocator = instance.local_or_import(index).ok_or_else(|| { - InstructionError::new( - instruction, - InstructionErrorKind::LocalOrImportIsMissing { function_index: allocator_index }, - ) - })?; - - if allocator.inputs() != [InterfaceType::I32] || allocator.outputs() != [InterfaceType::I32] { - return Err(InstructionError::new( - instruction, - InstructionErrorKind::LocalOrImportSignatureMismatch { - function_index: allocator_index, - expected: (vec![InterfaceType::I32], vec![InterfaceType::I32]), - received: (allocator.inputs().to_vec(), allocator.outputs().to_vec()) - } - )) - } - - let string = runtime.stack.pop1().ok_or_else(|| { - InstructionError::new( - instruction, - InstructionErrorKind::StackIsTooSmall { needed: 1 } - ) - })?; - - let string: String = to_native(&string, instruction)?; - let string_bytes = string.as_bytes(); - let string_length = string_bytes.len() as i32; - - let outputs = allocator.call(&[InterfaceValue::I32(string_length)]).map_err(|_| { - InstructionError::new( - instruction, - InstructionErrorKind::LocalOrImportCall { function_index: allocator_index }, - ) - })?; - let string_pointer: i32 = to_native(&outputs[0], instruction)?; - - let memory_index: u32 = 0; - let memory_view = instance - .memory(memory_index as usize) - .ok_or_else(|| { - InstructionError::new( - instruction, - InstructionErrorKind::MemoryIsMissing { memory_index } - ) - })? - .view(); - - for (nth, byte) in string_bytes.iter().enumerate() { - memory_view[string_pointer as usize + nth].set(*byte); - } - - runtime.stack.push(InterfaceValue::I32(string_pointer)); - runtime.stack.push(InterfaceValue::I32(string_length)); - - Ok(()) - } - } -); - -#[cfg(test)] -mod tests { - test_executable_instruction!( - test_string_to_memory = - instructions: [ - Instruction::ArgumentGet { index: 0 }, - Instruction::StringToMemory { allocator_index: 43 }, - ], - invocation_inputs: [InterfaceValue::String("Hello, World!".into())], - instance: Instance::new(), - stack: [ - InterfaceValue::I32(0), - // ^^^^^^ pointer - InterfaceValue::I32(13), - // ^^^^^^^ length - ] - ); - - test_executable_instruction!( - test_string_to_memory__roundtrip_with_memory_to_string = - instructions: [ - Instruction::ArgumentGet { index: 0 }, - Instruction::StringToMemory { allocator_index: 43 }, - Instruction::MemoryToString, - ], - invocation_inputs: [InterfaceValue::String("Hello, World!".into())], - instance: Instance::new(), - stack: [InterfaceValue::String("Hello, World!".into())], - ); - - test_executable_instruction!( - test_string_to_memory__allocator_does_not_exist = - instructions: [Instruction::StringToMemory { allocator_index: 43 }], - invocation_inputs: [], - instance: Instance { ..Default::default() }, - error: r#"`string-to-memory 43` the local or import function `43` doesn't exist"#, - ); - - test_executable_instruction!( - test_string_to_memory__stack_is_too_small = - instructions: [ - Instruction::StringToMemory { allocator_index: 43 } - // ^^ `43` expects 1 value on the stack, none is present - ], - invocation_inputs: [InterfaceValue::String("Hello, World!".into())], - instance: Instance::new(), - error: r#"`string-to-memory 43` needed to read `1` value(s) from the stack, but it doesn't contain enough data"#, - ); - - test_executable_instruction!( - test_string_to_memory__failure_when_calling_the_allocator = - instructions: [ - Instruction::ArgumentGet { index: 0 }, - Instruction::StringToMemory { allocator_index: 153 } - ], - invocation_inputs: [InterfaceValue::String("Hello, World!".into())], - instance: { - let mut instance = Instance::new(); - instance.locals_or_imports.insert( - 153, - LocalImport { - inputs: vec![InterfaceType::I32], - outputs: vec![InterfaceType::I32], - function: |_| Err(()), - // ^^^^^^^ function fails - }, - ); - - instance - }, - error: r#"`string-to-memory 153` failed while calling the local or import function `153`"#, - ); - - test_executable_instruction!( - test_string_to_memory__invalid_allocator_signature = - instructions: [ - Instruction::ArgumentGet { index: 0 }, - Instruction::StringToMemory { allocator_index: 153 } - ], - invocation_inputs: [InterfaceValue::String("Hello, World!".into())], - instance: { - let mut instance = Instance::new(); - instance.locals_or_imports.insert( - 153, - LocalImport { - inputs: vec![InterfaceType::I32, InterfaceType::I32], - outputs: vec![], - function: |_| Err(()), - }, - ); - - instance - }, - error: r#"`string-to-memory 153` the local or import function `153` has the signature `[I32] -> [I32]` but it received values of kind `[I32, I32] -> []`"#, - ); -} diff --git a/src/interpreter/instructions/strings.rs b/src/interpreter/instructions/strings.rs new file mode 100644 index 0000000..a64547b --- /dev/null +++ b/src/interpreter/instructions/strings.rs @@ -0,0 +1,326 @@ +use super::to_native; +use crate::{ + ast::InterfaceType, + errors::{InstructionError, InstructionErrorKind}, + interpreter::{ + wasm::{ + structures::{FunctionIndex, TypedIndex}, + values::InterfaceValue, + }, + Instruction, + }, +}; +use std::cell::Cell; + +executable_instruction!( + string_lift_memory(instruction: Instruction) -> _ { + move |runtime| -> _ { + let inputs = runtime.stack.pop(2).ok_or_else(|| { + InstructionError::new( + instruction, + InstructionErrorKind::StackIsTooSmall { needed: 2 }, + ) + })?; + + let memory_index: u32 = 0; + + let memory = runtime + .wasm_instance + .memory(memory_index as usize) + .ok_or_else(|| { + InstructionError::new( + instruction, + InstructionErrorKind::MemoryIsMissing { memory_index }, + ) + })?; + + let pointer = to_native::(&inputs[0], instruction)? as usize; + let length = to_native::(&inputs[1], instruction)? as usize; + let memory_view = memory.view(); + + if length == 0 { + runtime.stack.push(InterfaceValue::String("".into())); + + return Ok(()) + } + + if memory_view.len() <= pointer + length - 1 { + return Err(InstructionError::new( + instruction, + InstructionErrorKind::MemoryOutOfBoundsAccess { + index: pointer + length, + length: memory_view.len(), + }, + )); + } + + let data: Vec = (&memory_view[pointer..=pointer + length - 1]) + .iter() + .map(Cell::get) + .collect(); + + let string = String::from_utf8(data) + .map_err(|error| InstructionError::new(instruction, InstructionErrorKind::String(error)))?; + + runtime.stack.push(InterfaceValue::String(string)); + + Ok(()) + } + } +); + +executable_instruction!( + string_lower_memory(allocator_index: u32, instruction: Instruction) -> _ { + move |runtime| -> _ { + let instance = &mut runtime.wasm_instance; + let index = FunctionIndex::new(allocator_index as usize); + + let allocator = instance.local_or_import(index).ok_or_else(|| { + InstructionError::new( + instruction, + InstructionErrorKind::LocalOrImportIsMissing { function_index: allocator_index }, + ) + })?; + + if allocator.inputs() != [InterfaceType::I32] || allocator.outputs() != [InterfaceType::I32] { + return Err(InstructionError::new( + instruction, + InstructionErrorKind::LocalOrImportSignatureMismatch { + function_index: allocator_index, + expected: (vec![InterfaceType::I32], vec![InterfaceType::I32]), + received: (allocator.inputs().to_vec(), allocator.outputs().to_vec()) + } + )) + } + + let string = runtime.stack.pop1().ok_or_else(|| { + InstructionError::new( + instruction, + InstructionErrorKind::StackIsTooSmall { needed: 1 } + ) + })?; + + let string: String = to_native(&string, instruction)?; + let string_bytes = string.as_bytes(); + let string_length = string_bytes.len() as i32; + + let outputs = allocator.call(&[InterfaceValue::I32(string_length)]).map_err(|_| { + InstructionError::new( + instruction, + InstructionErrorKind::LocalOrImportCall { function_index: allocator_index }, + ) + })?; + let string_pointer: i32 = to_native(&outputs[0], instruction)?; + + let memory_index: u32 = 0; + let memory_view = instance + .memory(memory_index as usize) + .ok_or_else(|| { + InstructionError::new( + instruction, + InstructionErrorKind::MemoryIsMissing { memory_index } + ) + })? + .view(); + + for (nth, byte) in string_bytes.iter().enumerate() { + memory_view[string_pointer as usize + nth].set(*byte); + } + + runtime.stack.push(InterfaceValue::I32(string_pointer)); + runtime.stack.push(InterfaceValue::I32(string_length)); + + Ok(()) + } + } +); + +#[cfg(test)] +mod tests { + test_executable_instruction!( + test_string_lift_memory = + instructions: [ + Instruction::ArgumentGet { index: 0 }, + Instruction::ArgumentGet { index: 1 }, + Instruction::StringLiftMemory, + ], + invocation_inputs: [ + InterfaceValue::I32(0), + // ^^^^^^ pointer + InterfaceValue::I32(13), + // ^^^^^^^ length + ], + instance: Instance { + memory: Memory::new("Hello, World!".as_bytes().iter().map(|u| Cell::new(*u)).collect()), + ..Default::default() + }, + stack: [InterfaceValue::String("Hello, World!".into())], + ); + + test_executable_instruction!( + test_string_lift_memory__empty_string = + instructions: [ + Instruction::ArgumentGet { index: 0 }, + Instruction::ArgumentGet { index: 1 }, + Instruction::StringLiftMemory, + ], + invocation_inputs: [ + InterfaceValue::I32(0), + InterfaceValue::I32(0), + ], + instance: Instance { + memory: Memory::new(vec![]), + ..Default::default() + }, + stack: [InterfaceValue::String("".into())], + ); + + test_executable_instruction!( + test_string_lift_memory__read_out_of_memory = + instructions: [ + Instruction::ArgumentGet { index: 0 }, + Instruction::ArgumentGet { index: 1 }, + Instruction::StringLiftMemory, + ], + invocation_inputs: [ + InterfaceValue::I32(0), + // ^^^^^^ pointer + InterfaceValue::I32(13), + // ^^^^^^^ length is too long + ], + instance: Instance { + memory: Memory::new("Hello!".as_bytes().iter().map(|u| Cell::new(*u)).collect()), + ..Default::default() + }, + error: r#"`string.lift_memory` read out of the memory bounds (index 13 > memory length 6)"#, + ); + + test_executable_instruction!( + test_string_lift_memory__invalid_encoding = + instructions: [ + Instruction::ArgumentGet { index: 0 }, + Instruction::ArgumentGet { index: 1 }, + Instruction::StringLiftMemory, + ], + invocation_inputs: [ + InterfaceValue::I32(0), + // ^^^^^^ pointer + InterfaceValue::I32(4), + // ^^^^^^ length is too long + ], + instance: Instance { + memory: Memory::new(vec![0, 159, 146, 150].iter().map(|b| Cell::new(*b)).collect::>>()), + ..Default::default() + }, + error: r#"`string.lift_memory` invalid utf-8 sequence of 1 bytes from index 1"#, + ); + + test_executable_instruction!( + test_string_lift_memory__stack_is_too_small = + instructions: [ + Instruction::ArgumentGet { index: 0 }, + Instruction::StringLiftMemory, + // ^^^^^^^^^^^^^^^^ `string.lift_memory` expects 2 values on the stack, only one is present. + ], + invocation_inputs: [ + InterfaceValue::I32(0), + InterfaceValue::I32(13), + ], + instance: Instance::new(), + error: r#"`string.lift_memory` needed to read `2` value(s) from the stack, but it doesn't contain enough data"#, + ); + + test_executable_instruction!( + test_string_memory = + instructions: [ + Instruction::ArgumentGet { index: 0 }, + Instruction::StringLowerMemory { allocator_index: 43 }, + ], + invocation_inputs: [InterfaceValue::String("Hello, World!".into())], + instance: Instance::new(), + stack: [ + InterfaceValue::I32(0), + // ^^^^^^ pointer + InterfaceValue::I32(13), + // ^^^^^^^ length + ] + ); + + test_executable_instruction!( + test_string_memory__roundtrip_with_memory_to_string = + instructions: [ + Instruction::ArgumentGet { index: 0 }, + Instruction::StringLowerMemory { allocator_index: 43 }, + Instruction::StringLiftMemory, + ], + invocation_inputs: [InterfaceValue::String("Hello, World!".into())], + instance: Instance::new(), + stack: [InterfaceValue::String("Hello, World!".into())], + ); + + test_executable_instruction!( + test_string_memory__allocator_does_not_exist = + instructions: [Instruction::StringLowerMemory { allocator_index: 43 }], + invocation_inputs: [], + instance: Instance { ..Default::default() }, + error: r#"`string.lower_memory 43` the local or import function `43` doesn't exist"#, + ); + + test_executable_instruction!( + test_string_memory__stack_is_too_small = + instructions: [ + Instruction::StringLowerMemory { allocator_index: 43 } + // ^^ `43` expects 1 value on the stack, none is present + ], + invocation_inputs: [InterfaceValue::String("Hello, World!".into())], + instance: Instance::new(), + error: r#"`string.lower_memory 43` needed to read `1` value(s) from the stack, but it doesn't contain enough data"#, + ); + + test_executable_instruction!( + test_string_memory__failure_when_calling_the_allocator = + instructions: [ + Instruction::ArgumentGet { index: 0 }, + Instruction::StringLowerMemory { allocator_index: 153 } + ], + invocation_inputs: [InterfaceValue::String("Hello, World!".into())], + instance: { + let mut instance = Instance::new(); + instance.locals_or_imports.insert( + 153, + LocalImport { + inputs: vec![InterfaceType::I32], + outputs: vec![InterfaceType::I32], + function: |_| Err(()), + // ^^^^^^^ function fails + }, + ); + + instance + }, + error: r#"`string.lower_memory 153` failed while calling the local or import function `153`"#, + ); + + test_executable_instruction!( + test_string_memory__invalid_allocator_signature = + instructions: [ + Instruction::ArgumentGet { index: 0 }, + Instruction::StringLowerMemory { allocator_index: 153 } + ], + invocation_inputs: [InterfaceValue::String("Hello, World!".into())], + instance: { + let mut instance = Instance::new(); + instance.locals_or_imports.insert( + 153, + LocalImport { + inputs: vec![InterfaceType::I32, InterfaceType::I32], + outputs: vec![], + function: |_| Err(()), + }, + ); + + instance + }, + error: r#"`string.lower_memory 153` the local or import function `153` has the signature `[I32] -> [I32]` but it received values of kind `[I32, I32] -> []`"#, + ); +} diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index f2a9c44..560928f 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -230,9 +230,9 @@ where Instruction::I64FromU32 => instructions::i64_from_u32(*instruction), Instruction::I64FromU64 => instructions::i64_from_u64(*instruction), - Instruction::MemoryToString => instructions::memory_to_string(*instruction), - Instruction::StringToMemory { allocator_index } => { - instructions::string_to_memory(*allocator_index, *instruction) + Instruction::StringLiftMemory => instructions::string_lift_memory(*instruction), + Instruction::StringLowerMemory { allocator_index } => { + instructions::string_lower_memory(*allocator_index, *instruction) } }) .collect();