mirror of
https://github.com/fluencelabs/wasmer
synced 2025-06-27 07:31:33 +00:00
feat(interface-types) Implement the string.(lift|lower)_memory
instructions.
The `string.lift_memory` instruction replaces `memory-to-string`, and `string.lower_memory` replaces `string-to-memory`.
This commit is contained in:
@ -207,13 +207,13 @@ fn instruction<'input, E: ParseError<&'input [u8]>>(
|
|||||||
0x20 => (input, Instruction::I64FromU32),
|
0x20 => (input, Instruction::I64FromU32),
|
||||||
0x21 => (input, Instruction::I64FromU64),
|
0x21 => (input, Instruction::I64FromU64),
|
||||||
|
|
||||||
0x22 => (input, Instruction::MemoryToString),
|
0x22 => (input, Instruction::StringLiftMemory),
|
||||||
|
|
||||||
0x23 => {
|
0x23 => {
|
||||||
consume!((input, argument_0) = uleb(input)?);
|
consume!((input, argument_0) = uleb(input)?);
|
||||||
(
|
(
|
||||||
input,
|
input,
|
||||||
Instruction::StringToMemory {
|
Instruction::StringLowerMemory {
|
||||||
allocator_index: argument_0 as u32,
|
allocator_index: argument_0 as u32,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -655,8 +655,8 @@ mod tests {
|
|||||||
0x1f, // I64FromU16
|
0x1f, // I64FromU16
|
||||||
0x20, // I64FromU32
|
0x20, // I64FromU32
|
||||||
0x21, // I64FromU64
|
0x21, // I64FromU64
|
||||||
0x22, // MemoryToString
|
0x22, // StringLiftMemory
|
||||||
0x23, 0x01, // StringToMemory { allocator_index: 1 }
|
0x23, 0x01, // StringLowerMemory { allocator_index: 1 }
|
||||||
0x0a,
|
0x0a,
|
||||||
];
|
];
|
||||||
let output = Ok((
|
let output = Ok((
|
||||||
@ -696,8 +696,8 @@ mod tests {
|
|||||||
Instruction::I64FromU16,
|
Instruction::I64FromU16,
|
||||||
Instruction::I64FromU32,
|
Instruction::I64FromU32,
|
||||||
Instruction::I64FromU64,
|
Instruction::I64FromU64,
|
||||||
Instruction::MemoryToString,
|
Instruction::StringLiftMemory,
|
||||||
Instruction::StringToMemory { allocator_index: 1 },
|
Instruction::StringLowerMemory { allocator_index: 1 },
|
||||||
],
|
],
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -60,8 +60,8 @@ mod keyword {
|
|||||||
custom_keyword!(i64_from_u16 = "i64.from_u16");
|
custom_keyword!(i64_from_u16 = "i64.from_u16");
|
||||||
custom_keyword!(i64_from_u32 = "i64.from_u32");
|
custom_keyword!(i64_from_u32 = "i64.from_u32");
|
||||||
custom_keyword!(i64_from_u64 = "i64.from_u64");
|
custom_keyword!(i64_from_u64 = "i64.from_u64");
|
||||||
custom_keyword!(memory_to_string = "memory-to-string");
|
custom_keyword!(string_lift_memory = "string.lift_memory");
|
||||||
custom_keyword!(string_to_memory = "string-to-memory");
|
custom_keyword!(string_lower_memory = "string.lower_memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse<'_> for InterfaceType {
|
impl Parse<'_> for InterfaceType {
|
||||||
@ -275,14 +275,14 @@ impl<'a> Parse<'a> for Instruction {
|
|||||||
parser.parse::<keyword::i64_from_u64>()?;
|
parser.parse::<keyword::i64_from_u64>()?;
|
||||||
|
|
||||||
Ok(Instruction::I64FromU64)
|
Ok(Instruction::I64FromU64)
|
||||||
} else if lookahead.peek::<keyword::memory_to_string>() {
|
} else if lookahead.peek::<keyword::string_lift_memory>() {
|
||||||
parser.parse::<keyword::memory_to_string>()?;
|
parser.parse::<keyword::string_lift_memory>()?;
|
||||||
|
|
||||||
Ok(Instruction::MemoryToString)
|
Ok(Instruction::StringLiftMemory)
|
||||||
} else if lookahead.peek::<keyword::string_to_memory>() {
|
} else if lookahead.peek::<keyword::string_lower_memory>() {
|
||||||
parser.parse::<keyword::string_to_memory>()?;
|
parser.parse::<keyword::string_lower_memory>()?;
|
||||||
|
|
||||||
Ok(Instruction::StringToMemory {
|
Ok(Instruction::StringLowerMemory {
|
||||||
allocator_index: parser.parse()?,
|
allocator_index: parser.parse()?,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -664,8 +664,8 @@ mod tests {
|
|||||||
"i64.from_u16",
|
"i64.from_u16",
|
||||||
"i64.from_u32",
|
"i64.from_u32",
|
||||||
"i64.from_u64",
|
"i64.from_u64",
|
||||||
"memory-to-string",
|
"string.lift_memory",
|
||||||
"string-to-memory 42",
|
"string.lower_memory 42",
|
||||||
];
|
];
|
||||||
let outputs = vec![
|
let outputs = vec![
|
||||||
Instruction::ArgumentGet { index: 7 },
|
Instruction::ArgumentGet { index: 7 },
|
||||||
@ -702,8 +702,8 @@ mod tests {
|
|||||||
Instruction::I64FromU16,
|
Instruction::I64FromU16,
|
||||||
Instruction::I64FromU32,
|
Instruction::I64FromU32,
|
||||||
Instruction::I64FromU64,
|
Instruction::I64FromU64,
|
||||||
Instruction::MemoryToString,
|
Instruction::StringLiftMemory,
|
||||||
Instruction::StringToMemory {
|
Instruction::StringLowerMemory {
|
||||||
allocator_index: 42,
|
allocator_index: 42,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -293,9 +293,9 @@ where
|
|||||||
Instruction::I64FromU32 => 0x20_u8.to_bytes(writer)?,
|
Instruction::I64FromU32 => 0x20_u8.to_bytes(writer)?,
|
||||||
Instruction::I64FromU64 => 0x21_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)?;
|
0x23_u8.to_bytes(writer)?;
|
||||||
(*allocator_index as u64).to_bytes(writer)?;
|
(*allocator_index as u64).to_bytes(writer)?;
|
||||||
}
|
}
|
||||||
@ -575,8 +575,8 @@ mod tests {
|
|||||||
Instruction::I64FromU16,
|
Instruction::I64FromU16,
|
||||||
Instruction::I64FromU32,
|
Instruction::I64FromU32,
|
||||||
Instruction::I64FromU64,
|
Instruction::I64FromU64,
|
||||||
Instruction::MemoryToString,
|
Instruction::StringLiftMemory,
|
||||||
Instruction::StringToMemory { allocator_index: 1 },
|
Instruction::StringLowerMemory { allocator_index: 1 },
|
||||||
],
|
],
|
||||||
&[
|
&[
|
||||||
0x24, // list of 36 items
|
0x24, // list of 36 items
|
||||||
@ -614,8 +614,8 @@ mod tests {
|
|||||||
0x1f, // I64FromU16
|
0x1f, // I64FromU16
|
||||||
0x20, // I64FromU32
|
0x20, // I64FromU32
|
||||||
0x21, // I64FromU64
|
0x21, // I64FromU64
|
||||||
0x22, // MemoryToString
|
0x22, // StringLiftMemory
|
||||||
0x23, 0x01, // StringToMemory { allocator_index: 1 }
|
0x23, 0x01, // StringLowerMemory { allocator_index: 1 }
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -117,9 +117,9 @@ impl ToString for &Instruction {
|
|||||||
Instruction::I64FromU16 => "i64.from_u16".into(),
|
Instruction::I64FromU16 => "i64.from_u16".into(),
|
||||||
Instruction::I64FromU32 => "i64.from_u32".into(),
|
Instruction::I64FromU32 => "i64.from_u32".into(),
|
||||||
Instruction::I64FromU64 => "i64.from_u64".into(),
|
Instruction::I64FromU64 => "i64.from_u64".into(),
|
||||||
Instruction::MemoryToString => "memory-to-string".into(),
|
Instruction::StringLiftMemory => "string.lift_memory".into(),
|
||||||
Instruction::StringToMemory { allocator_index } => {
|
Instruction::StringLowerMemory { allocator_index } => {
|
||||||
format!(r#"string-to-memory {}"#, allocator_index)
|
format!(r#"string.lower_memory {}"#, allocator_index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -386,8 +386,8 @@ mod tests {
|
|||||||
(&Instruction::I64FromU16).to_string(),
|
(&Instruction::I64FromU16).to_string(),
|
||||||
(&Instruction::I64FromU32).to_string(),
|
(&Instruction::I64FromU32).to_string(),
|
||||||
(&Instruction::I64FromU64).to_string(),
|
(&Instruction::I64FromU64).to_string(),
|
||||||
(&Instruction::MemoryToString).to_string(),
|
(&Instruction::StringLiftMemory).to_string(),
|
||||||
(&Instruction::StringToMemory {
|
(&Instruction::StringLowerMemory {
|
||||||
allocator_index: 42,
|
allocator_index: 42,
|
||||||
})
|
})
|
||||||
.to_string(),
|
.to_string(),
|
||||||
@ -427,8 +427,8 @@ mod tests {
|
|||||||
"i64.from_u16",
|
"i64.from_u16",
|
||||||
"i64.from_u32",
|
"i64.from_u32",
|
||||||
"i64.from_u64",
|
"i64.from_u64",
|
||||||
"memory-to-string",
|
"string.lift_memory",
|
||||||
"string-to-memory 42",
|
"string.lower_memory 42",
|
||||||
];
|
];
|
||||||
|
|
||||||
assert_eq!(inputs, outputs);
|
assert_eq!(inputs, outputs);
|
||||||
|
@ -15,15 +15,6 @@ pub enum Instruction {
|
|||||||
function_index: usize,
|
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.
|
/// The `s8.from_i32` instruction.
|
||||||
S8FromI32,
|
S8FromI32,
|
||||||
|
|
||||||
@ -119,4 +110,13 @@ pub enum Instruction {
|
|||||||
|
|
||||||
/// The `i64.from_u64` instruction.
|
/// The `i64.from_u64` instruction.
|
||||||
I64FromU64,
|
I64FromU64,
|
||||||
|
|
||||||
|
/// The `string.lift_memory` instruction.
|
||||||
|
StringLiftMemory,
|
||||||
|
|
||||||
|
/// The `string.lower_memory` instruction.
|
||||||
|
StringLowerMemory {
|
||||||
|
/// The allocator function index.
|
||||||
|
allocator_index: u32,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -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::<i32>(&inputs[0], instruction)? as usize;
|
|
||||||
let length = to_native::<i32>(&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<u8> = (&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::<Vec<Cell<u8>>>()),
|
|
||||||
..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"#,
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,8 +1,7 @@
|
|||||||
mod argument_get;
|
mod argument_get;
|
||||||
mod call_core;
|
mod call_core;
|
||||||
mod memory_to_string;
|
|
||||||
mod numbers;
|
mod numbers;
|
||||||
mod string_to_memory;
|
mod strings;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{InstructionError, InstructionErrorKind, InstructionResult, WasmValueNativeCastError},
|
errors::{InstructionError, InstructionErrorKind, InstructionResult, WasmValueNativeCastError},
|
||||||
@ -13,10 +12,9 @@ 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 memory_to_string::memory_to_string;
|
|
||||||
pub(crate) use numbers::*;
|
pub(crate) use numbers::*;
|
||||||
use std::convert::TryFrom;
|
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
|
/// Just a short helper to map the error of a cast from an
|
||||||
/// `InterfaceValue` to a native value.
|
/// `InterfaceValue` to a native value.
|
||||||
|
@ -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] -> []`"#,
|
|
||||||
);
|
|
||||||
}
|
|
326
lib/interface-types/src/interpreter/instructions/strings.rs
Normal file
326
lib/interface-types/src/interpreter/instructions/strings.rs
Normal file
@ -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::<i32>(&inputs[0], instruction)? as usize;
|
||||||
|
let length = to_native::<i32>(&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<u8> = (&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::<Vec<Cell<u8>>>()),
|
||||||
|
..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] -> []`"#,
|
||||||
|
);
|
||||||
|
}
|
@ -230,9 +230,9 @@ where
|
|||||||
Instruction::I64FromU32 => instructions::i64_from_u32(*instruction),
|
Instruction::I64FromU32 => instructions::i64_from_u32(*instruction),
|
||||||
Instruction::I64FromU64 => instructions::i64_from_u64(*instruction),
|
Instruction::I64FromU64 => instructions::i64_from_u64(*instruction),
|
||||||
|
|
||||||
Instruction::MemoryToString => instructions::memory_to_string(*instruction),
|
Instruction::StringLiftMemory => instructions::string_lift_memory(*instruction),
|
||||||
Instruction::StringToMemory { allocator_index } => {
|
Instruction::StringLowerMemory { allocator_index } => {
|
||||||
instructions::string_to_memory(*allocator_index, *instruction)
|
instructions::string_lower_memory(*allocator_index, *instruction)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
Reference in New Issue
Block a user