feat(interface-types) Implement the write-utf8 executable instruction.

This commit is contained in:
Ivan Enderlin
2019-09-26 00:55:26 +02:00
parent 77546784cf
commit 0e1422f16a
2 changed files with 191 additions and 10 deletions

View File

@ -3,7 +3,11 @@ use crate::instructions::{
wasm::{self, FunctionIndex, InterfaceType, InterfaceValue, TypedIndex},
Instruction,
};
use std::{cell::Cell, convert::TryFrom, marker::PhantomData};
use std::{
cell::Cell,
convert::{TryFrom, TryInto},
marker::PhantomData,
};
struct Runtime<'invocation, 'instance, Instance, Export, LocalImport, Memory>
where
@ -15,9 +19,9 @@ where
invocation_inputs: &'invocation [InterfaceValue],
stack: Stack<InterfaceValue>,
wasm_instance: &'instance Instance,
wasm_exports: PhantomData<Export>,
wasm_locals_or_imports: PhantomData<LocalImport>,
wasm_memories: PhantomData<Memory>,
_wasm_exports: PhantomData<Export>,
_wasm_locals_or_imports: PhantomData<LocalImport>,
_wasm_memories: PhantomData<Memory>,
}
type ExecutableInstruction<Instance, Export, LocalImport, Memory> =
@ -56,9 +60,9 @@ where
invocation_inputs,
stack: Stack::new(),
wasm_instance,
wasm_exports: PhantomData,
wasm_locals_or_imports: PhantomData,
wasm_memories: PhantomData,
_wasm_exports: PhantomData,
_wasm_locals_or_imports: PhantomData,
_wasm_memories: PhantomData,
};
for executable_instruction in self.iter() {
@ -274,6 +278,76 @@ where
}
})
}
Instruction::WriteUtf8 { allocator_name } => {
let allocator_name = (*allocator_name).to_owned();
let instruction_name: String = instruction.into();
Box::new(move |runtime: &mut Runtime<Instance, Export, LocalImport, Memory>| -> Result<(), _> {
let instance = runtime.wasm_instance;
match instance.export(&allocator_name) {
Some(allocator) => {
if allocator.inputs() != [InterfaceType::I32] ||
allocator.outputs() != [InterfaceType::I32] {
return Err(format!(
"`{}` failed because the allocator `{}` has an invalid signature (expects [I32] -> [I32]).",
instruction_name,
allocator_name,
))
}
match runtime.wasm_instance.memory(0) {
Some(memory) => match runtime.stack.pop1() {
Some(string) => {
let memory_view = memory.view::<u8>();
let string: String = (&string).try_into()?;
let string_bytes = string.as_bytes();
let string_length = (string_bytes.len() as i32)
.try_into()
.map_err(|error| format!("{}", error))?;
match allocator.call(&[InterfaceValue::I32(string_length)]) {
Ok(outputs) => {
let string_pointer: i32 = (&outputs[0]).try_into()?;
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(())
}
Err(_) => Err(format!(
"`{}` failed when calling the allocator `{}`.",
instruction_name,
allocator_name,
))
}
}
None => Err(format!(
"`{}` cannot call the allocator `{}` because there is no enough data on the stack for the arguments (needs {}).",
instruction_name,
allocator_name,
1
))
}
None => Err(format!(
"`{}` failed because there is no memory to write into.",
instruction_name
))
}
}
None => Err(format!(
"`{}` failed because the exported function `{}` (the allocator) doesn't exist.",
instruction_name,
allocator_name
))
}
})
}
_ => unimplemented!(),
}
},
@ -396,6 +470,18 @@ mod tests {
},
},
);
hashmap.insert(
"alloc".into(),
Export {
inputs: vec![InterfaceType::I32],
outputs: vec![InterfaceType::I32],
function: |arguments: &[InterfaceValue]| {
let _size: i32 = (&arguments[0]).try_into().unwrap();
Ok(vec![InterfaceValue::I32(0)])
},
},
);
hashmap
},
@ -417,7 +503,7 @@ mod tests {
hashmap
},
memory: Memory::new(vec![]),
memory: Memory::new(vec![Cell::new(0); 128]),
}
}
}
@ -850,4 +936,98 @@ mod tests {
},
stack: [],
);
test!(
test_interpreter_write_utf8 =
instructions: [
Instruction::ArgumentGet { index: 0 },
Instruction::WriteUtf8 { allocator_name: "alloc" },
],
invocation_inputs: [InterfaceValue::String("Hello, World!".into())],
instance: Instance::new(),
stack: [
InterfaceValue::I32(0),
// ^^^^^^ pointer
InterfaceValue::I32(13),
// ^^^^^^^ length
]
);
test!(
test_interpreter_write_utf8__roundtrip_with_read_utf8 =
instructions: [
Instruction::ArgumentGet { index: 0 },
Instruction::WriteUtf8 { allocator_name: "alloc" },
Instruction::ReadUtf8,
],
invocation_inputs: [InterfaceValue::String("Hello, World!".into())],
instance: Instance::new(),
stack: [InterfaceValue::String("Hello, World!".into())],
);
test!(
test_interpreter_write_utf8__allocator_does_not_exist =
instructions: [Instruction::WriteUtf8 { allocator_name: "alloc" }],
invocation_inputs: [],
instance: Instance { ..Default::default() },
error: r#"`write-utf8 "alloc"` failed because the exported function `alloc` (the allocator) doesn't exist."#,
);
test!(
test_interpreter_write_utf8__stack_is_too_small =
instructions: [
Instruction::WriteUtf8 { allocator_name: "alloc" }
// ^^^^^ `alloc` expects 1 value on the stack, none is present
],
invocation_inputs: [InterfaceValue::String("Hello, World!".into())],
instance: Instance::new(),
error: r#"`write-utf8 "alloc"` cannot call the allocator `alloc` because there is no enough data on the stack for the arguments (needs 1)."#,
);
test!(
test_interpreter_write_utf8__failure_when_calling_the_allocator =
instructions: [
Instruction::ArgumentGet { index: 0 },
Instruction::WriteUtf8 { allocator_name: "alloc-fail" }
],
invocation_inputs: [InterfaceValue::String("Hello, World!".into())],
instance: {
let mut instance = Instance::new();
instance.exports.insert(
"alloc-fail".into(),
Export {
inputs: vec![InterfaceType::I32],
outputs: vec![InterfaceType::I32],
function: |_| Err(()),
// ^^^^^^^ function fails
},
);
instance
},
error: r#"`write-utf8 "alloc-fail"` failed when calling the allocator `alloc-fail`."#,
);
test!(
test_interpreter_write_utf8__invalid_allocator_signature =
instructions: [
Instruction::ArgumentGet { index: 0 },
Instruction::WriteUtf8 { allocator_name: "alloc-fail" }
],
invocation_inputs: [InterfaceValue::String("Hello, World!".into())],
instance: {
let mut instance = Instance::new();
instance.exports.insert(
"alloc-fail".into(),
Export {
inputs: vec![InterfaceType::I32, InterfaceType::I32],
outputs: vec![],
function: |_| Err(()),
},
);
instance
},
error: r#"`write-utf8 "alloc-fail"` failed because the allocator `alloc-fail` has an invalid signature (expects [I32] -> [I32])."#,
);
}

View File

@ -49,8 +49,8 @@ macro_rules! from_x_for_interface_value {
type Error = &'static str;
fn try_from(w: &InterfaceValue) -> Result<Self, Self::Error> {
match *w {
InterfaceValue::$value_variant(n) => Ok(n),
match w {
InterfaceValue::$value_variant(n) => Ok(n.clone()),
_ => Err("Invalid cast."),
}
}
@ -58,6 +58,7 @@ macro_rules! from_x_for_interface_value {
};
}
from_x_for_interface_value!(String, String);
from_x_for_interface_value!(i32, I32);
from_x_for_interface_value!(i64, I64);
from_x_for_interface_value!(f32, F32);