1281: feat(interface-types) Rename `call` to `call-core`, and remove `call-export` r=syrusakbary a=Hywan

The latest standard removes `call-export` and has a single `call-core` instruction.

Co-authored-by: Ivan Enderlin <ivan.enderlin@hoa-project.net>
This commit is contained in:
bors[bot] 2020-03-09 18:03:28 +00:00 committed by GitHub
commit 674d18ed66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 52 additions and 288 deletions

View File

@ -51,12 +51,16 @@ pub enum InterfaceType {
} }
/// Represents a type signature. /// Represents a type signature.
///
/// ```wasm,ignore
/// (@interface type (param i32 i32) (result string))
/// ```
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub struct Type { pub struct Type {
/// Types for the parameters. /// Types for the parameters (`(param …)`).
pub inputs: Vec<InterfaceType>, pub inputs: Vec<InterfaceType>,
/// Types for the results. /// Types for the results (`(result …)`).
pub outputs: Vec<InterfaceType>, pub outputs: Vec<InterfaceType>,
} }

View File

@ -168,22 +168,12 @@ fn instruction<'input, E: ParseError<&'input [u8]>>(
consume!((input, argument_0) = uleb(input)?); consume!((input, argument_0) = uleb(input)?);
( (
input, input,
Instruction::Call { Instruction::CallCore {
function_index: argument_0 as usize, function_index: argument_0 as usize,
}, },
) )
} }
0x02 => {
consume!((input, argument_0) = string(input)?);
(
input,
Instruction::CallExport {
export_name: argument_0,
},
)
}
0x03 => (input, Instruction::ReadUtf8), 0x03 => (input, Instruction::ReadUtf8),
0x04 => { 0x04 => {
@ -637,10 +627,9 @@ mod tests {
#[test] #[test]
fn test_instructions() { fn test_instructions() {
let input = &[ let input = &[
0x2c, // list of 44 items 0x2b, // list of 43 items
0x00, 0x01, // ArgumentGet { index: 1 } 0x00, 0x01, // ArgumentGet { index: 1 }
0x01, 0x01, // Call { function_index: 1 } 0x01, 0x01, // CallCore { function_index: 1 }
0x02, 0x03, 0x61, 0x62, 0x63, // CallExport { export_name: "abc" }
0x03, // ReadUtf8 0x03, // ReadUtf8
0x04, 0x03, 0x61, 0x62, 0x63, // WriteUtf8 { allocator_name: "abc" } 0x04, 0x03, 0x61, 0x62, 0x63, // WriteUtf8 { allocator_name: "abc" }
0x07, // I32ToS8 0x07, // I32ToS8
@ -688,8 +677,7 @@ mod tests {
&[0x0a][..], &[0x0a][..],
vec![ vec![
Instruction::ArgumentGet { index: 1 }, Instruction::ArgumentGet { index: 1 },
Instruction::Call { function_index: 1 }, Instruction::CallCore { function_index: 1 },
Instruction::CallExport { export_name: "abc" },
Instruction::ReadUtf8, Instruction::ReadUtf8,
Instruction::WriteUtf8 { Instruction::WriteUtf8 {
allocator_name: "abc", allocator_name: "abc",

View File

@ -27,8 +27,7 @@ mod keyword {
// Instructions. // Instructions.
custom_keyword!(argument_get = "arg.get"); custom_keyword!(argument_get = "arg.get");
custom_keyword!(call); custom_keyword!(call_core = "call-core");
custom_keyword!(call_export = "call-export");
custom_keyword!(read_utf8 = "read-utf8"); custom_keyword!(read_utf8 = "read-utf8");
custom_keyword!(write_utf8 = "write-utf8"); custom_keyword!(write_utf8 = "write-utf8");
custom_keyword!(i32_to_s8 = "i32-to-s8"); custom_keyword!(i32_to_s8 = "i32-to-s8");
@ -149,18 +148,12 @@ impl<'a> Parse<'a> for Instruction<'a> {
Ok(Instruction::ArgumentGet { Ok(Instruction::ArgumentGet {
index: parser.parse()?, index: parser.parse()?,
}) })
} else if lookahead.peek::<keyword::call>() { } else if lookahead.peek::<keyword::call_core>() {
parser.parse::<keyword::call>()?; parser.parse::<keyword::call_core>()?;
Ok(Instruction::Call { Ok(Instruction::CallCore {
function_index: parser.parse::<u64>()? as usize, function_index: parser.parse::<u64>()? as usize,
}) })
} else if lookahead.peek::<keyword::call_export>() {
parser.parse::<keyword::call_export>()?;
Ok(Instruction::CallExport {
export_name: parser.parse()?,
})
} else if lookahead.peek::<keyword::read_utf8>() { } else if lookahead.peek::<keyword::read_utf8>() {
parser.parse::<keyword::read_utf8>()?; parser.parse::<keyword::read_utf8>()?;
@ -673,8 +666,7 @@ mod tests {
fn test_instructions() { fn test_instructions() {
let inputs = vec![ let inputs = vec![
"arg.get 7", "arg.get 7",
"call 7", "call-core 7",
r#"call-export "foo""#,
"read-utf8", "read-utf8",
r#"write-utf8 "foo""#, r#"write-utf8 "foo""#,
"i32-to-s8", "i32-to-s8",
@ -719,8 +711,7 @@ mod tests {
]; ];
let outputs = vec![ let outputs = vec![
Instruction::ArgumentGet { index: 7 }, Instruction::ArgumentGet { index: 7 },
Instruction::Call { function_index: 7 }, Instruction::CallCore { function_index: 7 },
Instruction::CallExport { export_name: "foo" },
Instruction::ReadUtf8, Instruction::ReadUtf8,
Instruction::WriteUtf8 { Instruction::WriteUtf8 {
allocator_name: "foo", allocator_name: "foo",

View File

@ -255,16 +255,11 @@ where
(*index as u64).to_bytes(writer)?; (*index as u64).to_bytes(writer)?;
} }
Instruction::Call { function_index } => { Instruction::CallCore { function_index } => {
0x01_u8.to_bytes(writer)?; 0x01_u8.to_bytes(writer)?;
(*function_index as u64).to_bytes(writer)?; (*function_index as u64).to_bytes(writer)?;
} }
Instruction::CallExport { export_name } => {
0x02_u8.to_bytes(writer)?;
export_name.to_bytes(writer)?;
}
Instruction::ReadUtf8 => 0x03_u8.to_bytes(writer)?, Instruction::ReadUtf8 => 0x03_u8.to_bytes(writer)?,
Instruction::WriteUtf8 { allocator_name } => { Instruction::WriteUtf8 { allocator_name } => {
@ -554,8 +549,7 @@ mod tests {
assert_to_bytes!( assert_to_bytes!(
vec![ vec![
Instruction::ArgumentGet { index: 1 }, Instruction::ArgumentGet { index: 1 },
Instruction::Call { function_index: 1 }, Instruction::CallCore { function_index: 1 },
Instruction::CallExport { export_name: "abc" },
Instruction::ReadUtf8, Instruction::ReadUtf8,
Instruction::WriteUtf8 { Instruction::WriteUtf8 {
allocator_name: "abc", allocator_name: "abc",
@ -601,10 +595,9 @@ mod tests {
Instruction::U64ToI64, Instruction::U64ToI64,
], ],
&[ &[
0x2c, // list of 44 items 0x2b, // list of 43 items
0x00, 0x01, // ArgumentGet { index: 1 } 0x00, 0x01, // ArgumentGet { index: 1 }
0x01, 0x01, // Call { function_index: 1 } 0x01, 0x01, // CallCore { function_index: 1 }
0x02, 0x03, 0x61, 0x62, 0x63, // CallExport { export_name: "abc" }
0x03, // ReadUtf8 0x03, // ReadUtf8
0x04, 0x03, 0x61, 0x62, 0x63, // WriteUtf8 { allocator_name: "abc" } 0x04, 0x03, 0x61, 0x62, 0x63, // WriteUtf8 { allocator_name: "abc" }
0x07, // I32ToS8 0x07, // I32ToS8

View File

@ -84,8 +84,7 @@ impl<'input> ToString for &Instruction<'input> {
fn to_string(&self) -> String { fn to_string(&self) -> String {
match self { match self {
Instruction::ArgumentGet { index } => format!("arg.get {}", index), Instruction::ArgumentGet { index } => format!("arg.get {}", index),
Instruction::Call { function_index } => format!("call {}", function_index), Instruction::CallCore { function_index } => format!("call-core {}", function_index),
Instruction::CallExport { export_name } => format!(r#"call-export "{}""#, export_name),
Instruction::ReadUtf8 => "read-utf8".into(), Instruction::ReadUtf8 => "read-utf8".into(),
Instruction::WriteUtf8 { allocator_name } => { Instruction::WriteUtf8 { allocator_name } => {
format!(r#"write-utf8 "{}""#, allocator_name) format!(r#"write-utf8 "{}""#, allocator_name)
@ -361,8 +360,7 @@ mod tests {
fn test_instructions() { fn test_instructions() {
let inputs: Vec<String> = vec![ let inputs: Vec<String> = vec![
(&Instruction::ArgumentGet { index: 7 }).to_string(), (&Instruction::ArgumentGet { index: 7 }).to_string(),
(&Instruction::Call { function_index: 7 }).to_string(), (&Instruction::CallCore { function_index: 7 }).to_string(),
(&Instruction::CallExport { export_name: "foo" }).to_string(),
(&Instruction::ReadUtf8).to_string(), (&Instruction::ReadUtf8).to_string(),
(&Instruction::WriteUtf8 { (&Instruction::WriteUtf8 {
allocator_name: "foo", allocator_name: "foo",
@ -410,8 +408,7 @@ mod tests {
]; ];
let outputs = vec![ let outputs = vec![
"arg.get 7", "arg.get 7",
"call 7", "call-core 7",
r#"call-export "foo""#,
"read-utf8", "read-utf8",
r#"write-utf8 "foo""#, r#"write-utf8 "foo""#,
"i32-to-s8", "i32-to-s8",

View File

@ -9,18 +9,12 @@ pub enum Instruction<'input> {
index: u32, index: u32,
}, },
/// The `call` instruction. /// The `call-core` instruction.
Call { CallCore {
/// The function index. /// The function index.
function_index: usize, function_index: usize,
}, },
/// The `call-export` instruction.
CallExport {
/// The exported function name.
export_name: &'input str,
},
/// The `read-utf8` instruction. /// The `read-utf8` instruction.
ReadUtf8, ReadUtf8,

View File

@ -4,7 +4,7 @@ use crate::interpreter::wasm::{
}; };
executable_instruction!( executable_instruction!(
call(function_index: usize, instruction_name: String) -> _ { call_core(function_index: usize, instruction_name: String) -> _ {
move |runtime| -> _ { move |runtime| -> _ {
let instance = &mut runtime.wasm_instance; let instance = &mut runtime.wasm_instance;
let index = FunctionIndex::new(function_index); let index = FunctionIndex::new(function_index);
@ -65,11 +65,11 @@ executable_instruction!(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
test_executable_instruction!( test_executable_instruction!(
test_call = test_call_core =
instructions: [ instructions: [
Instruction::ArgumentGet { index: 1 }, Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 }, Instruction::ArgumentGet { index: 0 },
Instruction::Call { function_index: 42 }, Instruction::CallCore { function_index: 42 },
], ],
invocation_inputs: [ invocation_inputs: [
InterfaceValue::I32(3), InterfaceValue::I32(3),
@ -80,23 +80,23 @@ mod tests {
); );
test_executable_instruction!( test_executable_instruction!(
test_call__invalid_local_import_index = test_call_core__invalid_local_import_index =
instructions: [ instructions: [
Instruction::Call { function_index: 42 }, Instruction::CallCore { function_index: 42 },
], ],
invocation_inputs: [ invocation_inputs: [
InterfaceValue::I32(3), InterfaceValue::I32(3),
InterfaceValue::I32(4), InterfaceValue::I32(4),
], ],
instance: Default::default(), instance: Default::default(),
error: r#"`call 42` cannot call the local or imported function `42` because it doesn't exist."#, error: r#"`call-core 42` cannot call the local or imported function `42` because it doesn't exist."#,
); );
test_executable_instruction!( test_executable_instruction!(
test_call__stack_is_too_small = test_call_core__stack_is_too_small =
instructions: [ instructions: [
Instruction::ArgumentGet { index: 0 }, Instruction::ArgumentGet { index: 0 },
Instruction::Call { function_index: 42 }, Instruction::CallCore { function_index: 42 },
// ^^ `42` expects 2 values on the stack, only one is present // ^^ `42` expects 2 values on the stack, only one is present
], ],
invocation_inputs: [ invocation_inputs: [
@ -104,15 +104,15 @@ mod tests {
InterfaceValue::I32(4), InterfaceValue::I32(4),
], ],
instance: Instance::new(), instance: Instance::new(),
error: r#"`call 42` cannot call the local or imported function `42` because there is not enough data on the stack for the arguments (needs 2)."#, error: r#"`call-core 42` cannot call the local or imported function `42` because there is not enough data on the stack for the arguments (needs 2)."#,
); );
test_executable_instruction!( test_executable_instruction!(
test_call__invalid_types_in_the_stack = test_call_core__invalid_types_in_the_stack =
instructions: [ instructions: [
Instruction::ArgumentGet { index: 1 }, Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 }, Instruction::ArgumentGet { index: 0 },
Instruction::Call { function_index: 42 }, Instruction::CallCore { function_index: 42 },
], ],
invocation_inputs: [ invocation_inputs: [
InterfaceValue::I32(3), InterfaceValue::I32(3),
@ -120,15 +120,15 @@ mod tests {
// ^^^ mismatch with `42` signature // ^^^ mismatch with `42` signature
], ],
instance: Instance::new(), instance: Instance::new(),
error: r#"`call 42` cannot call the local or imported function `42` because the value types on the stack mismatch the function signature (expects [I32, I32])."#, error: r#"`call-core 42` cannot call the local or imported function `42` because the value types on the stack mismatch the function signature (expects [I32, I32])."#,
); );
test_executable_instruction!( test_executable_instruction!(
test_call__failure_when_calling = test_call_core__failure_when_calling =
instructions: [ instructions: [
Instruction::ArgumentGet { index: 1 }, Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 }, Instruction::ArgumentGet { index: 0 },
Instruction::Call { function_index: 42 }, Instruction::CallCore { function_index: 42 },
], ],
invocation_inputs: [ invocation_inputs: [
InterfaceValue::I32(3), InterfaceValue::I32(3),
@ -151,15 +151,15 @@ mod tests {
}, },
..Default::default() ..Default::default()
}, },
error: r#"`call 42` failed when calling the local or imported function `42`."#, error: r#"`call-core 42` failed when calling the local or imported function `42`."#,
); );
test_executable_instruction!( test_executable_instruction!(
test_call__void = test_call_core__void =
instructions: [ instructions: [
Instruction::ArgumentGet { index: 1 }, Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 }, Instruction::ArgumentGet { index: 0 },
Instruction::Call { function_index: 42 }, Instruction::CallCore { function_index: 42 },
], ],
invocation_inputs: [ invocation_inputs: [
InterfaceValue::I32(3), InterfaceValue::I32(3),

View File

@ -1,177 +0,0 @@
use crate::interpreter::wasm::values::InterfaceType;
executable_instruction!(
call_export(export_name: String, instruction_name: String) -> _ {
move |runtime| -> _ {
let instance = &mut runtime.wasm_instance;
match instance.export(&export_name) {
Some(export) => {
let inputs_cardinality = export.inputs_cardinality();
match runtime.stack.pop(inputs_cardinality) {
Some(inputs) => {
let input_types = inputs
.iter()
.map(Into::into)
.collect::<Vec<InterfaceType>>();
if input_types != export.inputs() {
return Err(format!(
"`{}` cannot call the exported function `{}` because the value types on the stack mismatch the function signature (expects {:?}).",
instruction_name,
export_name,
export.inputs(),
))
}
match export.call(&inputs) {
Ok(outputs) => {
for output in outputs.iter() {
runtime.stack.push(output.clone());
}
Ok(())
}
Err(_) => Err(format!(
"`{}` failed when calling the exported function `{}`.",
instruction_name,
export_name
))
}
}
None => Err(format!(
"`{}` cannot call the exported function `{}` because there is not enough data on the stack for the arguments (needs {}).",
instruction_name,
export_name,
inputs_cardinality,
))
}
}
None => Err(format!(
"`{}` cannot call the exported function `{}` because it doesn't exist.",
instruction_name,
export_name,
))
}
}
}
);
#[cfg(test)]
mod tests {
test_executable_instruction!(
test_call_export =
instructions: [
Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 },
Instruction::CallExport { export_name: "sum" },
],
invocation_inputs: [
InterfaceValue::I32(3),
InterfaceValue::I32(4),
],
instance: Instance::new(),
stack: [InterfaceValue::I32(7)],
);
test_executable_instruction!(
test_call_export__invalid_export_name =
instructions: [Instruction::CallExport { export_name: "bar" }],
invocation_inputs: [],
instance: Instance::new(),
error: r#"`call-export "bar"` cannot call the exported function `bar` because it doesn't exist."#,
);
test_executable_instruction!(
test_call_export__stack_is_too_small =
instructions: [
Instruction::ArgumentGet { index: 0 },
Instruction::CallExport { export_name: "sum" },
],
invocation_inputs: [
InterfaceValue::I32(3),
InterfaceValue::I32(4),
],
instance: Instance::new(),
error: r#"`call-export "sum"` cannot call the exported function `sum` because there is not enough data on the stack for the arguments (needs 2)."#,
);
test_executable_instruction!(
test_call_export__invalid_types_in_the_stack =
instructions: [
Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 },
Instruction::CallExport { export_name: "sum" },
],
invocation_inputs: [
InterfaceValue::I32(3),
InterfaceValue::I64(4),
// ^^^ mismatch with `sum` signature
],
instance: Instance::new(),
error: r#"`call-export "sum"` cannot call the exported function `sum` because the value types on the stack mismatch the function signature (expects [I32, I32])."#,
);
test_executable_instruction!(
test_call_export__failure_when_calling =
instructions: [
Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 },
Instruction::CallExport { export_name: "sum" },
],
invocation_inputs: [
InterfaceValue::I32(3),
InterfaceValue::I32(4),
],
instance: Instance {
exports: {
let mut hashmap = HashMap::new();
hashmap.insert(
"sum".into(),
Export {
inputs: vec![InterfaceType::I32, InterfaceType::I32],
outputs: vec![InterfaceType::I32],
function: |_| Err(()),
// ^^^^^^^ function fails
},
);
hashmap
},
..Default::default()
},
error: r#"`call-export "sum"` failed when calling the exported function `sum`."#,
);
test_executable_instruction!(
test_call_export__void =
instructions: [
Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 },
Instruction::CallExport { export_name: "sum" },
],
invocation_inputs: [
InterfaceValue::I32(3),
InterfaceValue::I32(4),
],
instance: Instance {
exports: {
let mut hashmap = HashMap::new();
hashmap.insert(
"sum".into(),
Export {
inputs: vec![InterfaceType::I32, InterfaceType::I32],
outputs: vec![InterfaceType::I32],
function: |_| Ok(vec![]),
// ^^^^^^^^^^ void function
},
);
hashmap
},
..Default::default()
},
stack: [],
);
}

View File

@ -1,13 +1,11 @@
mod argument_get; mod argument_get;
mod call; mod call_core;
mod call_export;
mod lowering_lifting; mod lowering_lifting;
mod read_utf8; mod read_utf8;
mod write_utf8; mod write_utf8;
pub(crate) use argument_get::argument_get; pub(crate) use argument_get::argument_get;
pub(crate) use call::call; pub(crate) use call_core::call_core;
pub(crate) use call_export::call_export;
pub(crate) use lowering_lifting::*; pub(crate) use lowering_lifting::*;
pub(crate) use read_utf8::read_utf8; pub(crate) use read_utf8::read_utf8;
pub(crate) use write_utf8::write_utf8; pub(crate) use write_utf8::write_utf8;

View File

@ -71,7 +71,7 @@ pub(crate) type ExecutableInstruction<Instance, Export, LocalImport, Memory, Mem
/// let interpreter: Interpreter<Instance, Export, LocalImport, Memory, MemoryView> = (&vec![ /// let interpreter: Interpreter<Instance, Export, LocalImport, Memory, MemoryView> = (&vec![
/// Instruction::ArgumentGet { index: 1 }, /// Instruction::ArgumentGet { index: 1 },
/// Instruction::ArgumentGet { index: 0 }, /// Instruction::ArgumentGet { index: 0 },
/// Instruction::CallExport { export_name: "sum" }, /// Instruction::CallCore { function_index: 42 },
/// ]) /// ])
/// .try_into() /// .try_into()
/// .unwrap(); /// .unwrap();
@ -81,12 +81,12 @@ pub(crate) type ExecutableInstruction<Instance, Export, LocalImport, Memory, Mem
/// ///
/// // 3. Creates a WebAssembly instance. /// // 3. Creates a WebAssembly instance.
/// let mut instance = Instance { /// let mut instance = Instance {
/// // 3.1. Defines one exported function: `fn sum(a: i32, b: i32) -> i32 { a + b }`. /// // 3.1. Defines one function: `fn sum(a: i32, b: i32) -> i32 { a + b }`.
/// exports: { /// locals_or_imports: {
/// let mut hashmap = HashMap::new(); /// let mut hashmap = HashMap::new();
/// hashmap.insert( /// hashmap.insert(
/// "sum".into(), /// 42,
/// Export { /// LocalImport {
/// // Defines the argument types of the function. /// // Defines the argument types of the function.
/// inputs: vec![InterfaceType::I32, InterfaceType::I32], /// inputs: vec![InterfaceType::I32, InterfaceType::I32],
/// ///
@ -196,11 +196,8 @@ where
Instruction::ArgumentGet { index } => { Instruction::ArgumentGet { index } => {
instructions::argument_get(*index, instruction_name) instructions::argument_get(*index, instruction_name)
} }
Instruction::Call { function_index } => { Instruction::CallCore { function_index } => {
instructions::call(*function_index, instruction_name) instructions::call_core(*function_index, instruction_name)
}
Instruction::CallExport { export_name } => {
instructions::call_export((*export_name).to_owned(), instruction_name)
} }
Instruction::ReadUtf8 => instructions::read_utf8(instruction_name), Instruction::ReadUtf8 => instructions::read_utf8(instruction_name),
Instruction::WriteUtf8 { allocator_name } => { Instruction::WriteUtf8 { allocator_name } => {
@ -253,24 +250,3 @@ where
}) })
} }
} }
#[cfg(test)]
mod tests {
use super::{wasm::structures::EmptyMemoryView, Instruction, Interpreter};
use std::convert::TryInto;
#[test]
fn test_interpreter_from_instructions() {
let instructions = vec![
Instruction::ArgumentGet { index: 0 },
Instruction::ArgumentGet { index: 0 },
Instruction::CallExport { export_name: "foo" },
Instruction::ReadUtf8,
Instruction::Call { function_index: 7 },
];
let interpreter: Interpreter<(), (), (), (), EmptyMemoryView> =
(&instructions).try_into().unwrap();
assert_eq!(interpreter.executable_instructions.len(), 5);
}
}