mirror of
https://github.com/fluencelabs/wasmer
synced 2025-04-24 18:02:13 +00:00
Merge branch 'master' into wapm-wax
This commit is contained in:
commit
cce134de50
@ -3,6 +3,8 @@
|
||||
## **[Unreleased]**
|
||||
|
||||
- [#1286](https://github.com/wasmerio/wasmer/pull/1286) Updated Windows Wasmer icons. Add wax
|
||||
- [#1284](https://github.com/wasmerio/wasmer/pull/1284) Implement string and memory instructions in `wasmer-interface-types`
|
||||
- [#1272](https://github.com/wasmerio/wasmer/pull/1272) Fix off-by-one error bug when accessing memory with a `WasmPtr` that contains the last valid byte of memory. Also changes the behavior of `WasmPtr<T, Array>` with a length of 0 and `WasmPtr<T>` where `std::mem::size_of::<T>()` is 0 to always return `None`
|
||||
|
||||
## 0.15.0 - 2020-03-04
|
||||
|
||||
|
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -659,7 +659,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "inkwell"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/TheDan64/inkwell?rev=0a864ebf68b33d4d514b67796264b03898aa0944#0a864ebf68b33d4d514b67796264b03898aa0944"
|
||||
source = "git+https://github.com/TheDan64/inkwell?rev=af4cf4efbb27cdea8a54175ffc18ffd91964618c#af4cf4efbb27cdea8a54175ffc18ffd91964618c"
|
||||
dependencies = [
|
||||
"either",
|
||||
"inkwell_internals",
|
||||
@ -673,7 +673,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "inkwell_internals"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/TheDan64/inkwell?rev=0a864ebf68b33d4d514b67796264b03898aa0944#0a864ebf68b33d4d514b67796264b03898aa0944"
|
||||
source = "git+https://github.com/TheDan64/inkwell?rev=af4cf4efbb27cdea8a54175ffc18ffd91964618c#af4cf4efbb27cdea8a54175ffc18ffd91964618c"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30",
|
||||
"quote 0.6.13",
|
||||
|
@ -30,3 +30,68 @@ more](https://github.com/wasmerio/wasmer).
|
||||
|
||||
This crate is an implementation of [the living WebAssembly Interface
|
||||
Types standard](https://github.com/WebAssembly/interface-types).
|
||||
|
||||
## Encoders and decoders
|
||||
|
||||
The `wasmer-interface-types` crate comes with an encoder and a decoder
|
||||
for the WAT format, and the binary format, for the WebAssembly
|
||||
Interface Types. An encoder writes an AST into another format, like
|
||||
WAT or binary. A decoder reads an AST from another format, like WAT or
|
||||
binary.
|
||||
|
||||
## Instructions
|
||||
|
||||
Very basically, WebAssembly Interface Types defines a set of
|
||||
instructions, used by adapters to transform the data between
|
||||
WebAssembly core and the outside world ([learn
|
||||
mode](https://github.com/WebAssembly/interface-types/blob/master/proposals/interface-types/Explainer.md)).
|
||||
|
||||
Here is the instructions that are implemented:
|
||||
|
||||
| Instruction | WAT encoder | Binary encoder | WAT decoder | Binary decoder | Interpreter |
|
||||
|-|-|-|-|-|-|
|
||||
| `arg.get` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `call-core` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `memory-to-string` | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| `string-to-memory` | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| `call-adapter` | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| `defer-call-core` | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| `i32-to-s8` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `i32-to-s8x` | ✅ | ✅ | ✅ | ✅ | ❌ |
|
||||
| `i32-to-u8` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `i32-to-s16` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `i32-to-s16x` | ✅ | ✅ | ✅ | ✅ | ❌ |
|
||||
| `i32-to-u16` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `i32-to-s32` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `i32-to-u32` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `i32-to-s64` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `i32-to-u64` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `i64-to-s8` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `i64-to-s8x` | ✅ | ✅ | ✅ | ✅ | ❌ |
|
||||
| `i64-to-u8` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `i64-to-s16` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `i64-to-s16x` | ✅ | ✅ | ✅ | ✅ | ❌ |
|
||||
| `i64-to-u16` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `i64-to-s32` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `i64-to-s32x` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `i64-to-u32` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `i64-to-s64` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `i64-to-u64` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `s8-to-i32` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `u8-to-i32` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `s16-to-i32` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `u16-to-i32` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `s32-to-i32` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `u32-to-i32` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `s64-to-i32` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `s64-to-i32x` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `u64-to-i32` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `u64-to-i32x` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `s8-to-i64` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `u8-to-i64` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `s16-to-i64` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `u16-to-i64` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `s32-to-i64` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `u32-to-i64` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `s64-to-i64` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `u64-to-i64` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
|
@ -51,12 +51,16 @@ pub enum InterfaceType {
|
||||
}
|
||||
|
||||
/// Represents a type signature.
|
||||
///
|
||||
/// ```wasm,ignore
|
||||
/// (@interface type (param i32 i32) (result string))
|
||||
/// ```
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct Type {
|
||||
/// Types for the parameters.
|
||||
/// Types for the parameters (`(param …)`).
|
||||
pub inputs: Vec<InterfaceType>,
|
||||
|
||||
/// Types for the results.
|
||||
/// Types for the results (`(result …)`).
|
||||
pub outputs: Vec<InterfaceType>,
|
||||
}
|
||||
|
||||
@ -85,12 +89,12 @@ pub struct Export<'input> {
|
||||
|
||||
/// Represents an adapter.
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct Adapter<'input> {
|
||||
pub struct Adapter {
|
||||
/// The adapter function type.
|
||||
pub function_type: u32,
|
||||
|
||||
/// The instructions.
|
||||
pub instructions: Vec<Instruction<'input>>,
|
||||
pub instructions: Vec<Instruction>,
|
||||
}
|
||||
|
||||
/// Represents an implementation.
|
||||
@ -133,7 +137,7 @@ pub struct Interfaces<'input> {
|
||||
pub imports: Vec<Import<'input>>,
|
||||
|
||||
/// All the adapters.
|
||||
pub adapters: Vec<Adapter<'input>>,
|
||||
pub adapters: Vec<Adapter>,
|
||||
|
||||
/// All the exported functions.
|
||||
pub exports: Vec<Export<'input>>,
|
||||
|
@ -168,30 +168,20 @@ fn instruction<'input, E: ParseError<&'input [u8]>>(
|
||||
consume!((input, argument_0) = uleb(input)?);
|
||||
(
|
||||
input,
|
||||
Instruction::Call {
|
||||
Instruction::CallCore {
|
||||
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::MemoryToString),
|
||||
|
||||
0x04 => {
|
||||
consume!((input, argument_0) = string(input)?);
|
||||
consume!((input, argument_0) = uleb(input)?);
|
||||
(
|
||||
input,
|
||||
Instruction::WriteUtf8 {
|
||||
allocator_name: argument_0,
|
||||
Instruction::StringToMemory {
|
||||
allocator_index: argument_0 as u32,
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -637,12 +627,11 @@ mod tests {
|
||||
#[test]
|
||||
fn test_instructions() {
|
||||
let input = &[
|
||||
0x2c, // list of 44 items
|
||||
0x2b, // list of 43 items
|
||||
0x00, 0x01, // ArgumentGet { index: 1 }
|
||||
0x01, 0x01, // Call { function_index: 1 }
|
||||
0x02, 0x03, 0x61, 0x62, 0x63, // CallExport { export_name: "abc" }
|
||||
0x03, // ReadUtf8
|
||||
0x04, 0x03, 0x61, 0x62, 0x63, // WriteUtf8 { allocator_name: "abc" }
|
||||
0x01, 0x01, // CallCore { function_index: 1 }
|
||||
0x03, // MemoryToString
|
||||
0x04, 0x01, // StringToMemory { allocator_index: 1 }
|
||||
0x07, // I32ToS8
|
||||
0x08, // I32ToS8X
|
||||
0x09, // I32ToU8
|
||||
@ -688,12 +677,9 @@ mod tests {
|
||||
&[0x0a][..],
|
||||
vec![
|
||||
Instruction::ArgumentGet { index: 1 },
|
||||
Instruction::Call { function_index: 1 },
|
||||
Instruction::CallExport { export_name: "abc" },
|
||||
Instruction::ReadUtf8,
|
||||
Instruction::WriteUtf8 {
|
||||
allocator_name: "abc",
|
||||
},
|
||||
Instruction::CallCore { function_index: 1 },
|
||||
Instruction::MemoryToString,
|
||||
Instruction::StringToMemory { allocator_index: 1 },
|
||||
Instruction::I32ToS8,
|
||||
Instruction::I32ToS8X,
|
||||
Instruction::I32ToU8,
|
||||
|
@ -27,10 +27,9 @@ mod keyword {
|
||||
|
||||
// Instructions.
|
||||
custom_keyword!(argument_get = "arg.get");
|
||||
custom_keyword!(call);
|
||||
custom_keyword!(call_export = "call-export");
|
||||
custom_keyword!(read_utf8 = "read-utf8");
|
||||
custom_keyword!(write_utf8 = "write-utf8");
|
||||
custom_keyword!(call_core = "call-core");
|
||||
custom_keyword!(memory_to_string = "memory-to-string");
|
||||
custom_keyword!(string_to_memory = "string-to-memory");
|
||||
custom_keyword!(i32_to_s8 = "i32-to-s8");
|
||||
custom_keyword!(i32_to_s8x = "i32-to-s8x");
|
||||
custom_keyword!(i32_to_u8 = "i32-to-u8");
|
||||
@ -138,7 +137,7 @@ impl Parse<'_> for InterfaceType {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for Instruction<'a> {
|
||||
impl<'a> Parse<'a> for Instruction {
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn parse(parser: Parser<'a>) -> Result<Self> {
|
||||
let mut lookahead = parser.lookahead1();
|
||||
@ -149,27 +148,21 @@ impl<'a> Parse<'a> for Instruction<'a> {
|
||||
Ok(Instruction::ArgumentGet {
|
||||
index: parser.parse()?,
|
||||
})
|
||||
} else if lookahead.peek::<keyword::call>() {
|
||||
parser.parse::<keyword::call>()?;
|
||||
} else if lookahead.peek::<keyword::call_core>() {
|
||||
parser.parse::<keyword::call_core>()?;
|
||||
|
||||
Ok(Instruction::Call {
|
||||
Ok(Instruction::CallCore {
|
||||
function_index: parser.parse::<u64>()? as usize,
|
||||
})
|
||||
} else if lookahead.peek::<keyword::call_export>() {
|
||||
parser.parse::<keyword::call_export>()?;
|
||||
} else if lookahead.peek::<keyword::memory_to_string>() {
|
||||
parser.parse::<keyword::memory_to_string>()?;
|
||||
|
||||
Ok(Instruction::CallExport {
|
||||
export_name: parser.parse()?,
|
||||
})
|
||||
} else if lookahead.peek::<keyword::read_utf8>() {
|
||||
parser.parse::<keyword::read_utf8>()?;
|
||||
Ok(Instruction::MemoryToString)
|
||||
} else if lookahead.peek::<keyword::string_to_memory>() {
|
||||
parser.parse::<keyword::string_to_memory>()?;
|
||||
|
||||
Ok(Instruction::ReadUtf8)
|
||||
} else if lookahead.peek::<keyword::write_utf8>() {
|
||||
parser.parse::<keyword::write_utf8>()?;
|
||||
|
||||
Ok(Instruction::WriteUtf8 {
|
||||
allocator_name: parser.parse()?,
|
||||
Ok(Instruction::StringToMemory {
|
||||
allocator_index: parser.parse()?,
|
||||
})
|
||||
} else if lookahead.peek::<keyword::i32_to_s8>() {
|
||||
parser.parse::<keyword::i32_to_s8>()?;
|
||||
@ -399,7 +392,7 @@ impl Parse<'_> for FunctionType {
|
||||
enum Interface<'a> {
|
||||
Type(Type),
|
||||
Import(Import<'a>),
|
||||
Adapter(Adapter<'a>),
|
||||
Adapter(Adapter),
|
||||
Export(Export<'a>),
|
||||
Implementation(Implementation),
|
||||
}
|
||||
@ -527,7 +520,7 @@ impl<'a> Parse<'a> for Implementation {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for Adapter<'a> {
|
||||
impl<'a> Parse<'a> for Adapter {
|
||||
fn parse(parser: Parser<'a>) -> Result<Self> {
|
||||
parser.parse::<keyword::func>()?;
|
||||
|
||||
@ -673,10 +666,9 @@ mod tests {
|
||||
fn test_instructions() {
|
||||
let inputs = vec![
|
||||
"arg.get 7",
|
||||
"call 7",
|
||||
r#"call-export "foo""#,
|
||||
"read-utf8",
|
||||
r#"write-utf8 "foo""#,
|
||||
"call-core 7",
|
||||
"memory-to-string",
|
||||
"string-to-memory 42",
|
||||
"i32-to-s8",
|
||||
"i32-to-s8x",
|
||||
"i32-to-u8",
|
||||
@ -719,11 +711,10 @@ mod tests {
|
||||
];
|
||||
let outputs = vec![
|
||||
Instruction::ArgumentGet { index: 7 },
|
||||
Instruction::Call { function_index: 7 },
|
||||
Instruction::CallExport { export_name: "foo" },
|
||||
Instruction::ReadUtf8,
|
||||
Instruction::WriteUtf8 {
|
||||
allocator_name: "foo",
|
||||
Instruction::CallCore { function_index: 7 },
|
||||
Instruction::MemoryToString,
|
||||
Instruction::StringToMemory {
|
||||
allocator_index: 42,
|
||||
},
|
||||
Instruction::I32ToS8,
|
||||
Instruction::I32ToS8X,
|
||||
|
@ -162,7 +162,7 @@ where
|
||||
/// Encode an `Adapter` into bytes.
|
||||
///
|
||||
/// Decoder is in `decoders::binary::adapters`.
|
||||
impl<W> ToBytes<W> for Adapter<'_>
|
||||
impl<W> ToBytes<W> for Adapter
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
@ -244,7 +244,7 @@ where
|
||||
/// Encode an `Instruction` into bytes.
|
||||
///
|
||||
/// Decoder is `decoders::binary::instruction`.
|
||||
impl<W> ToBytes<W> for Instruction<'_>
|
||||
impl<W> ToBytes<W> for Instruction
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
@ -255,21 +255,16 @@ where
|
||||
(*index as u64).to_bytes(writer)?;
|
||||
}
|
||||
|
||||
Instruction::Call { function_index } => {
|
||||
Instruction::CallCore { function_index } => {
|
||||
0x01_u8.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::MemoryToString => 0x03_u8.to_bytes(writer)?,
|
||||
|
||||
Instruction::ReadUtf8 => 0x03_u8.to_bytes(writer)?,
|
||||
|
||||
Instruction::WriteUtf8 { allocator_name } => {
|
||||
Instruction::StringToMemory { allocator_index } => {
|
||||
0x04_u8.to_bytes(writer)?;
|
||||
allocator_name.to_bytes(writer)?;
|
||||
(*allocator_index as u64).to_bytes(writer)?;
|
||||
}
|
||||
|
||||
Instruction::I32ToS8 => 0x07_u8.to_bytes(writer)?,
|
||||
@ -554,12 +549,9 @@ mod tests {
|
||||
assert_to_bytes!(
|
||||
vec![
|
||||
Instruction::ArgumentGet { index: 1 },
|
||||
Instruction::Call { function_index: 1 },
|
||||
Instruction::CallExport { export_name: "abc" },
|
||||
Instruction::ReadUtf8,
|
||||
Instruction::WriteUtf8 {
|
||||
allocator_name: "abc",
|
||||
},
|
||||
Instruction::CallCore { function_index: 1 },
|
||||
Instruction::MemoryToString,
|
||||
Instruction::StringToMemory { allocator_index: 1 },
|
||||
Instruction::I32ToS8,
|
||||
Instruction::I32ToS8X,
|
||||
Instruction::I32ToU8,
|
||||
@ -601,12 +593,11 @@ mod tests {
|
||||
Instruction::U64ToI64,
|
||||
],
|
||||
&[
|
||||
0x2c, // list of 44 items
|
||||
0x2b, // list of 43 items
|
||||
0x00, 0x01, // ArgumentGet { index: 1 }
|
||||
0x01, 0x01, // Call { function_index: 1 }
|
||||
0x02, 0x03, 0x61, 0x62, 0x63, // CallExport { export_name: "abc" }
|
||||
0x03, // ReadUtf8
|
||||
0x04, 0x03, 0x61, 0x62, 0x63, // WriteUtf8 { allocator_name: "abc" }
|
||||
0x01, 0x01, // CallCore { function_index: 1 }
|
||||
0x03, // MemoryToString
|
||||
0x04, 0x01, // StringToMemory { allocator_index: 1 }
|
||||
0x07, // I32ToS8
|
||||
0x08, // I32ToS8X
|
||||
0x09, // I32ToU8
|
||||
|
@ -80,15 +80,14 @@ impl ToString for &InterfaceType {
|
||||
}
|
||||
|
||||
/// Encode an `Instruction` into a string.
|
||||
impl<'input> ToString for &Instruction<'input> {
|
||||
impl ToString for &Instruction {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Instruction::ArgumentGet { index } => format!("arg.get {}", index),
|
||||
Instruction::Call { function_index } => format!("call {}", function_index),
|
||||
Instruction::CallExport { export_name } => format!(r#"call-export "{}""#, export_name),
|
||||
Instruction::ReadUtf8 => "read-utf8".into(),
|
||||
Instruction::WriteUtf8 { allocator_name } => {
|
||||
format!(r#"write-utf8 "{}""#, allocator_name)
|
||||
Instruction::CallCore { function_index } => format!("call-core {}", function_index),
|
||||
Instruction::MemoryToString => "memory-to-string".into(),
|
||||
Instruction::StringToMemory { allocator_index } => {
|
||||
format!(r#"string-to-memory {}"#, allocator_index)
|
||||
}
|
||||
Instruction::I32ToS8 => "i32-to-s8".into(),
|
||||
Instruction::I32ToS8X => "i32-to-s8x".into(),
|
||||
@ -195,7 +194,7 @@ impl<'input> ToString for &Import<'input> {
|
||||
}
|
||||
|
||||
/// Encode an `Adapter` into a string.
|
||||
impl<'input> ToString for &Adapter<'input> {
|
||||
impl ToString for &Adapter {
|
||||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
r#"(@interface func (type {function_type}){instructions})"#,
|
||||
@ -361,11 +360,10 @@ mod tests {
|
||||
fn test_instructions() {
|
||||
let inputs: Vec<String> = vec![
|
||||
(&Instruction::ArgumentGet { index: 7 }).to_string(),
|
||||
(&Instruction::Call { function_index: 7 }).to_string(),
|
||||
(&Instruction::CallExport { export_name: "foo" }).to_string(),
|
||||
(&Instruction::ReadUtf8).to_string(),
|
||||
(&Instruction::WriteUtf8 {
|
||||
allocator_name: "foo",
|
||||
(&Instruction::CallCore { function_index: 7 }).to_string(),
|
||||
(&Instruction::MemoryToString).to_string(),
|
||||
(&Instruction::StringToMemory {
|
||||
allocator_index: 42,
|
||||
})
|
||||
.to_string(),
|
||||
(&Instruction::I32ToS8).to_string(),
|
||||
@ -410,10 +408,9 @@ mod tests {
|
||||
];
|
||||
let outputs = vec![
|
||||
"arg.get 7",
|
||||
"call 7",
|
||||
r#"call-export "foo""#,
|
||||
"read-utf8",
|
||||
r#"write-utf8 "foo""#,
|
||||
"call-core 7",
|
||||
"memory-to-string",
|
||||
"string-to-memory 42",
|
||||
"i32-to-s8",
|
||||
"i32-to-s8x",
|
||||
"i32-to-u8",
|
||||
|
@ -2,32 +2,26 @@
|
||||
|
||||
/// Represents all the possible WIT instructions.
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum Instruction<'input> {
|
||||
pub enum Instruction {
|
||||
/// The `arg.get` instruction.
|
||||
ArgumentGet {
|
||||
/// The argument index.
|
||||
index: u32,
|
||||
},
|
||||
|
||||
/// The `call` instruction.
|
||||
Call {
|
||||
/// The `call-core` instruction.
|
||||
CallCore {
|
||||
/// The function index.
|
||||
function_index: usize,
|
||||
},
|
||||
|
||||
/// The `call-export` instruction.
|
||||
CallExport {
|
||||
/// The exported function name.
|
||||
export_name: &'input str,
|
||||
},
|
||||
/// The `memory-to-string` instruction.
|
||||
MemoryToString,
|
||||
|
||||
/// The `read-utf8` instruction.
|
||||
ReadUtf8,
|
||||
|
||||
/// The `write-utf8` instruction.
|
||||
WriteUtf8 {
|
||||
/// The allocator function name.
|
||||
allocator_name: &'input str,
|
||||
/// The `string-to-memory` instruction.
|
||||
StringToMemory {
|
||||
/// The allocator function index.
|
||||
allocator_index: u32,
|
||||
},
|
||||
|
||||
/// The `i32-to-s8,` instruction.
|
||||
|
@ -4,7 +4,7 @@ use crate::interpreter::wasm::{
|
||||
};
|
||||
|
||||
executable_instruction!(
|
||||
call(function_index: usize, instruction_name: String) -> _ {
|
||||
call_core(function_index: usize, instruction_name: String) -> _ {
|
||||
move |runtime| -> _ {
|
||||
let instance = &mut runtime.wasm_instance;
|
||||
let index = FunctionIndex::new(function_index);
|
||||
@ -65,11 +65,11 @@ executable_instruction!(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
test_executable_instruction!(
|
||||
test_call =
|
||||
test_call_core =
|
||||
instructions: [
|
||||
Instruction::ArgumentGet { index: 1 },
|
||||
Instruction::ArgumentGet { index: 0 },
|
||||
Instruction::Call { function_index: 42 },
|
||||
Instruction::CallCore { function_index: 42 },
|
||||
],
|
||||
invocation_inputs: [
|
||||
InterfaceValue::I32(3),
|
||||
@ -80,39 +80,39 @@ mod tests {
|
||||
);
|
||||
|
||||
test_executable_instruction!(
|
||||
test_call__invalid_local_import_index =
|
||||
test_call_core__invalid_local_import_index =
|
||||
instructions: [
|
||||
Instruction::Call { function_index: 42 },
|
||||
Instruction::CallCore { function_index: 42 },
|
||||
],
|
||||
invocation_inputs: [
|
||||
InterfaceValue::I32(3),
|
||||
InterfaceValue::I32(4),
|
||||
],
|
||||
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_call__stack_is_too_small =
|
||||
test_call_core__stack_is_too_small =
|
||||
instructions: [
|
||||
Instruction::ArgumentGet { index: 0 },
|
||||
Instruction::Call { function_index: 42 },
|
||||
// ^^ `42` expects 2 values on the stack, only one is present
|
||||
Instruction::CallCore { function_index: 42 },
|
||||
// ^^ `42` expects 2 values on the stack, only one is present
|
||||
],
|
||||
invocation_inputs: [
|
||||
InterfaceValue::I32(3),
|
||||
InterfaceValue::I32(4),
|
||||
],
|
||||
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_call__invalid_types_in_the_stack =
|
||||
test_call_core__invalid_types_in_the_stack =
|
||||
instructions: [
|
||||
Instruction::ArgumentGet { index: 1 },
|
||||
Instruction::ArgumentGet { index: 0 },
|
||||
Instruction::Call { function_index: 42 },
|
||||
Instruction::CallCore { function_index: 42 },
|
||||
],
|
||||
invocation_inputs: [
|
||||
InterfaceValue::I32(3),
|
||||
@ -120,15 +120,15 @@ mod tests {
|
||||
// ^^^ mismatch with `42` signature
|
||||
],
|
||||
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_call__failure_when_calling =
|
||||
test_call_core__failure_when_calling =
|
||||
instructions: [
|
||||
Instruction::ArgumentGet { index: 1 },
|
||||
Instruction::ArgumentGet { index: 0 },
|
||||
Instruction::Call { function_index: 42 },
|
||||
Instruction::CallCore { function_index: 42 },
|
||||
],
|
||||
invocation_inputs: [
|
||||
InterfaceValue::I32(3),
|
||||
@ -151,15 +151,15 @@ mod tests {
|
||||
},
|
||||
..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_call__void =
|
||||
test_call_core__void =
|
||||
instructions: [
|
||||
Instruction::ArgumentGet { index: 1 },
|
||||
Instruction::ArgumentGet { index: 0 },
|
||||
Instruction::Call { function_index: 42 },
|
||||
Instruction::CallCore { function_index: 42 },
|
||||
],
|
||||
invocation_inputs: [
|
||||
InterfaceValue::I32(3),
|
@ -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: [],
|
||||
);
|
||||
}
|
@ -2,7 +2,7 @@ use crate::interpreter::wasm::values::InterfaceValue;
|
||||
use std::{cell::Cell, convert::TryFrom};
|
||||
|
||||
executable_instruction!(
|
||||
read_utf8(instruction_name: String) -> _ {
|
||||
memory_to_string(instruction_name: String) -> _ {
|
||||
move |runtime| -> _ {
|
||||
match runtime.stack.pop(2) {
|
||||
Some(inputs) => match runtime.wasm_instance.memory(0) {
|
||||
@ -55,11 +55,11 @@ executable_instruction!(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
test_executable_instruction!(
|
||||
test_read_utf8 =
|
||||
test_memory_to_string =
|
||||
instructions: [
|
||||
Instruction::ArgumentGet { index: 1 },
|
||||
Instruction::ArgumentGet { index: 0 },
|
||||
Instruction::ReadUtf8,
|
||||
Instruction::MemoryToString,
|
||||
],
|
||||
invocation_inputs: [
|
||||
InterfaceValue::I32(13),
|
||||
@ -75,11 +75,11 @@ mod tests {
|
||||
);
|
||||
|
||||
test_executable_instruction!(
|
||||
test_read_utf8__read_out_of_memory =
|
||||
test_memory_to_string__read_out_of_memory =
|
||||
instructions: [
|
||||
Instruction::ArgumentGet { index: 1 },
|
||||
Instruction::ArgumentGet { index: 0 },
|
||||
Instruction::ReadUtf8,
|
||||
Instruction::MemoryToString,
|
||||
],
|
||||
invocation_inputs: [
|
||||
InterfaceValue::I32(13),
|
||||
@ -91,15 +91,15 @@ mod tests {
|
||||
memory: Memory::new("Hello!".as_bytes().iter().map(|u| Cell::new(*u)).collect()),
|
||||
..Default::default()
|
||||
},
|
||||
error: r#"`read-utf8` failed because it has to read out of the memory bounds (index 13 > memory length 6)."#,
|
||||
error: r#"`memory-to-string` failed because it has to read out of the memory bounds (index 13 > memory length 6)."#,
|
||||
);
|
||||
|
||||
test_executable_instruction!(
|
||||
test_read_utf8__invalid_encoding =
|
||||
test_memory_to_string__invalid_encoding =
|
||||
instructions: [
|
||||
Instruction::ArgumentGet { index: 1 },
|
||||
Instruction::ArgumentGet { index: 0 },
|
||||
Instruction::ReadUtf8,
|
||||
Instruction::MemoryToString,
|
||||
],
|
||||
invocation_inputs: [
|
||||
InterfaceValue::I32(4),
|
||||
@ -111,21 +111,21 @@ mod tests {
|
||||
memory: Memory::new(vec![0, 159, 146, 150].iter().map(|b| Cell::new(*b)).collect::<Vec<Cell<u8>>>()),
|
||||
..Default::default()
|
||||
},
|
||||
error: r#"`read-utf8` failed because the read string isn't UTF-8 valid (invalid utf-8 sequence of 1 bytes from index 1)."#,
|
||||
error: r#"`memory-to-string` failed because the read string isn't UTF-8 valid (invalid utf-8 sequence of 1 bytes from index 1)."#,
|
||||
);
|
||||
|
||||
test_executable_instruction!(
|
||||
test_read_utf8__stack_is_too_small =
|
||||
test_memory_to_string__stack_is_too_small =
|
||||
instructions: [
|
||||
Instruction::ArgumentGet { index: 0 },
|
||||
Instruction::ReadUtf8,
|
||||
// ^^^^^^^^ `read-utf8` expects 2 values on the stack, only one is present.
|
||||
Instruction::MemoryToString,
|
||||
// ^^^^^^^^^^^^^^ `memory-to-string` expects 2 values on the stack, only one is present.
|
||||
],
|
||||
invocation_inputs: [
|
||||
InterfaceValue::I32(13),
|
||||
InterfaceValue::I32(0),
|
||||
],
|
||||
instance: Instance::new(),
|
||||
error: r#"`read-utf8` failed because there is not enough data on the stack (needs 2)."#,
|
||||
error: r#"`memory-to-string` failed because there is not enough data on the stack (needs 2)."#,
|
||||
);
|
||||
}
|
@ -1,16 +1,14 @@
|
||||
mod argument_get;
|
||||
mod call;
|
||||
mod call_export;
|
||||
mod call_core;
|
||||
mod lowering_lifting;
|
||||
mod read_utf8;
|
||||
mod write_utf8;
|
||||
mod memory_to_string;
|
||||
mod string_to_memory;
|
||||
|
||||
pub(crate) use argument_get::argument_get;
|
||||
pub(crate) use call::call;
|
||||
pub(crate) use call_export::call_export;
|
||||
pub(crate) use call_core::call_core;
|
||||
pub(crate) use lowering_lifting::*;
|
||||
pub(crate) use read_utf8::read_utf8;
|
||||
pub(crate) use write_utf8::write_utf8;
|
||||
pub(crate) use memory_to_string::memory_to_string;
|
||||
pub(crate) use string_to_memory::string_to_memory;
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
@ -133,23 +131,12 @@ pub(crate) 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
|
||||
},
|
||||
locals_or_imports: {
|
||||
let mut hashmap = HashMap::new();
|
||||
// sum
|
||||
hashmap.insert(
|
||||
42,
|
||||
LocalImport {
|
||||
@ -163,6 +150,19 @@ pub(crate) mod tests {
|
||||
},
|
||||
},
|
||||
);
|
||||
// string allocator
|
||||
hashmap.insert(
|
||||
43,
|
||||
LocalImport {
|
||||
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
|
||||
},
|
||||
|
@ -0,0 +1,169 @@
|
||||
use crate::interpreter::wasm::{
|
||||
structures::{FunctionIndex, TypedIndex},
|
||||
values::{InterfaceType, InterfaceValue},
|
||||
};
|
||||
use std::convert::TryInto;
|
||||
|
||||
executable_instruction!(
|
||||
string_to_memory(allocator_index: u32, instruction_name: String) -> _ {
|
||||
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(|| {
|
||||
format!(
|
||||
"`{}` failed because the function `{}` (the allocator) doesn't exist.",
|
||||
instruction_name,
|
||||
allocator_index
|
||||
)
|
||||
})?;
|
||||
|
||||
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_index,
|
||||
));
|
||||
}
|
||||
|
||||
let string = runtime.stack.pop1().ok_or_else(|| {
|
||||
format!(
|
||||
"`{}` cannot call the allocator `{}` because there is not enough data on the stack for the arguments (needs {}).",
|
||||
instruction_name,
|
||||
allocator_index,
|
||||
1
|
||||
)
|
||||
})?;
|
||||
|
||||
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))?;
|
||||
|
||||
let outputs = allocator.call(&[InterfaceValue::I32(string_length)]).map_err(|_| format!(
|
||||
"`{}` failed when calling the allocator `{}`.",
|
||||
instruction_name,
|
||||
allocator_index,
|
||||
))?;
|
||||
|
||||
let string_pointer: i32 = (&outputs[0]).try_into()?;
|
||||
|
||||
let memory_view = instance
|
||||
.memory(0)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"`{}` failed because there is no memory to write into.",
|
||||
instruction_name
|
||||
)
|
||||
})?
|
||||
.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` failed because the function `43` (the allocator) 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` cannot call the allocator `43` because there is not enough data on the stack for the arguments (needs 1)."#,
|
||||
);
|
||||
|
||||
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 when calling the allocator `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` failed because the allocator `153` has an invalid signature (expects [I32] -> [I32])."#,
|
||||
);
|
||||
}
|
@ -1,169 +0,0 @@
|
||||
use crate::interpreter::wasm::values::{InterfaceType, InterfaceValue};
|
||||
use std::convert::TryInto;
|
||||
|
||||
executable_instruction!(
|
||||
write_utf8(allocator_name: String, instruction_name: String) -> _ {
|
||||
move |runtime| -> _ {
|
||||
let instance = &mut 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 instance.memory(0) {
|
||||
Some(memory) => match runtime.stack.pop1() {
|
||||
Some(string) => {
|
||||
let memory_view = memory.view();
|
||||
|
||||
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 not 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
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
test_executable_instruction!(
|
||||
test_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_executable_instruction!(
|
||||
test_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_executable_instruction!(
|
||||
test_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_executable_instruction!(
|
||||
test_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 not enough data on the stack for the arguments (needs 1)."#,
|
||||
);
|
||||
|
||||
test_executable_instruction!(
|
||||
test_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_executable_instruction!(
|
||||
test_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])."#,
|
||||
);
|
||||
}
|
@ -71,7 +71,7 @@ pub(crate) type ExecutableInstruction<Instance, Export, LocalImport, Memory, Mem
|
||||
/// let interpreter: Interpreter<Instance, Export, LocalImport, Memory, MemoryView> = (&vec![
|
||||
/// Instruction::ArgumentGet { index: 1 },
|
||||
/// Instruction::ArgumentGet { index: 0 },
|
||||
/// Instruction::CallExport { export_name: "sum" },
|
||||
/// Instruction::CallCore { function_index: 42 },
|
||||
/// ])
|
||||
/// .try_into()
|
||||
/// .unwrap();
|
||||
@ -81,12 +81,12 @@ pub(crate) type ExecutableInstruction<Instance, Export, LocalImport, Memory, Mem
|
||||
///
|
||||
/// // 3. Creates a WebAssembly instance.
|
||||
/// let mut instance = Instance {
|
||||
/// // 3.1. Defines one exported function: `fn sum(a: i32, b: i32) -> i32 { a + b }`.
|
||||
/// exports: {
|
||||
/// // 3.1. Defines one function: `fn sum(a: i32, b: i32) -> i32 { a + b }`.
|
||||
/// locals_or_imports: {
|
||||
/// let mut hashmap = HashMap::new();
|
||||
/// hashmap.insert(
|
||||
/// "sum".into(),
|
||||
/// Export {
|
||||
/// 42,
|
||||
/// LocalImport {
|
||||
/// // Defines the argument types of the function.
|
||||
/// inputs: vec![InterfaceType::I32, InterfaceType::I32],
|
||||
///
|
||||
@ -174,8 +174,7 @@ where
|
||||
}
|
||||
|
||||
/// Transforms a `Vec<Instruction>` into an `Interpreter`.
|
||||
impl<'binary_input, Instance, Export, LocalImport, Memory, MemoryView>
|
||||
TryFrom<&Vec<Instruction<'binary_input>>>
|
||||
impl<Instance, Export, LocalImport, Memory, MemoryView> TryFrom<&Vec<Instruction>>
|
||||
for Interpreter<Instance, Export, LocalImport, Memory, MemoryView>
|
||||
where
|
||||
Export: wasm::structures::Export,
|
||||
@ -196,15 +195,12 @@ where
|
||||
Instruction::ArgumentGet { index } => {
|
||||
instructions::argument_get(*index, instruction_name)
|
||||
}
|
||||
Instruction::Call { function_index } => {
|
||||
instructions::call(*function_index, instruction_name)
|
||||
Instruction::CallCore { function_index } => {
|
||||
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::WriteUtf8 { allocator_name } => {
|
||||
instructions::write_utf8((*allocator_name).to_owned(), instruction_name)
|
||||
Instruction::MemoryToString => instructions::memory_to_string(instruction_name),
|
||||
Instruction::StringToMemory { allocator_index } => {
|
||||
instructions::string_to_memory(*allocator_index, instruction_name)
|
||||
}
|
||||
|
||||
Instruction::I32ToS8 => instructions::i32_to_s8(),
|
||||
@ -253,24 +249,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);
|
||||
}
|
||||
}
|
||||
|
@ -19,13 +19,13 @@ byteorder = "1"
|
||||
|
||||
[target.'cfg(target_arch = "x86_64")'.dependencies.inkwell]
|
||||
git = "https://github.com/TheDan64/inkwell"
|
||||
rev = "0a864ebf68b33d4d514b67796264b03898aa0944"
|
||||
rev = "af4cf4efbb27cdea8a54175ffc18ffd91964618c"
|
||||
default-features = false
|
||||
features = ["llvm8-0", "target-x86"]
|
||||
|
||||
[target.'cfg(target_arch = "aarch64")'.dependencies.inkwell]
|
||||
git = "https://github.com/TheDan64/inkwell"
|
||||
rev = "0a864ebf68b33d4d514b67796264b03898aa0944"
|
||||
rev = "af4cf4efbb27cdea8a54175ffc18ffd91964618c"
|
||||
default-features = false
|
||||
features = ["llvm8-0", "target-aarch64"]
|
||||
|
||||
|
@ -354,15 +354,15 @@ fn trap_if_not_representable_as_int<'ctx>(
|
||||
let failure_block = context.append_basic_block(*function, "conversion_failure_block");
|
||||
let continue_block = context.append_basic_block(*function, "conversion_success_block");
|
||||
|
||||
builder.build_conditional_branch(out_of_bounds, &failure_block, &continue_block);
|
||||
builder.position_at_end(&failure_block);
|
||||
builder.build_conditional_branch(out_of_bounds, failure_block, continue_block);
|
||||
builder.position_at_end(failure_block);
|
||||
builder.build_call(
|
||||
intrinsics.throw_trap,
|
||||
&[intrinsics.trap_illegal_arithmetic],
|
||||
"throw",
|
||||
);
|
||||
builder.build_unreachable();
|
||||
builder.position_at_end(&continue_block);
|
||||
builder.position_at_end(continue_block);
|
||||
}
|
||||
|
||||
fn trap_if_zero_or_overflow<'ctx>(
|
||||
@ -418,15 +418,15 @@ fn trap_if_zero_or_overflow<'ctx>(
|
||||
|
||||
let shouldnt_trap_block = context.append_basic_block(*function, "shouldnt_trap_block");
|
||||
let should_trap_block = context.append_basic_block(*function, "should_trap_block");
|
||||
builder.build_conditional_branch(should_trap, &should_trap_block, &shouldnt_trap_block);
|
||||
builder.position_at_end(&should_trap_block);
|
||||
builder.build_conditional_branch(should_trap, should_trap_block, shouldnt_trap_block);
|
||||
builder.position_at_end(should_trap_block);
|
||||
builder.build_call(
|
||||
intrinsics.throw_trap,
|
||||
&[intrinsics.trap_illegal_arithmetic],
|
||||
"throw",
|
||||
);
|
||||
builder.build_unreachable();
|
||||
builder.position_at_end(&shouldnt_trap_block);
|
||||
builder.position_at_end(shouldnt_trap_block);
|
||||
}
|
||||
|
||||
fn trap_if_zero<'ctx>(
|
||||
@ -460,15 +460,15 @@ fn trap_if_zero<'ctx>(
|
||||
|
||||
let shouldnt_trap_block = context.append_basic_block(*function, "shouldnt_trap_block");
|
||||
let should_trap_block = context.append_basic_block(*function, "should_trap_block");
|
||||
builder.build_conditional_branch(should_trap, &should_trap_block, &shouldnt_trap_block);
|
||||
builder.position_at_end(&should_trap_block);
|
||||
builder.build_conditional_branch(should_trap, should_trap_block, shouldnt_trap_block);
|
||||
builder.position_at_end(should_trap_block);
|
||||
builder.build_call(
|
||||
intrinsics.throw_trap,
|
||||
&[intrinsics.trap_illegal_arithmetic],
|
||||
"throw",
|
||||
);
|
||||
builder.build_unreachable();
|
||||
builder.position_at_end(&shouldnt_trap_block);
|
||||
builder.position_at_end(shouldnt_trap_block);
|
||||
}
|
||||
|
||||
fn v128_into_int_vec<'ctx>(
|
||||
@ -774,17 +774,17 @@ fn resolve_memory_ptr<'ctx>(
|
||||
let not_in_bounds_block = context.append_basic_block(*function, "not_in_bounds_block");
|
||||
builder.build_conditional_branch(
|
||||
ptr_in_bounds,
|
||||
&in_bounds_continue_block,
|
||||
¬_in_bounds_block,
|
||||
in_bounds_continue_block,
|
||||
not_in_bounds_block,
|
||||
);
|
||||
builder.position_at_end(¬_in_bounds_block);
|
||||
builder.position_at_end(not_in_bounds_block);
|
||||
builder.build_call(
|
||||
intrinsics.throw_trap,
|
||||
&[intrinsics.trap_memory_oob],
|
||||
"throw",
|
||||
);
|
||||
builder.build_unreachable();
|
||||
builder.position_at_end(&in_bounds_continue_block);
|
||||
builder.position_at_end(in_bounds_continue_block);
|
||||
}
|
||||
}
|
||||
|
||||
@ -918,9 +918,9 @@ fn trap_if_misaligned<'ctx>(
|
||||
|
||||
let continue_block = context.append_basic_block(*function, "aligned_access_continue_block");
|
||||
let not_aligned_block = context.append_basic_block(*function, "misaligned_trap_block");
|
||||
builder.build_conditional_branch(aligned, &continue_block, ¬_aligned_block);
|
||||
builder.build_conditional_branch(aligned, continue_block, not_aligned_block);
|
||||
|
||||
builder.position_at_end(¬_aligned_block);
|
||||
builder.position_at_end(not_aligned_block);
|
||||
builder.build_call(
|
||||
intrinsics.throw_trap,
|
||||
&[intrinsics.trap_misaligned_atomic],
|
||||
@ -928,7 +928,7 @@ fn trap_if_misaligned<'ctx>(
|
||||
);
|
||||
builder.build_unreachable();
|
||||
|
||||
builder.position_at_end(&continue_block);
|
||||
builder.position_at_end(continue_block);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -1053,11 +1053,11 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
|
||||
.builder
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.build_unconditional_branch(&start_of_code_block);
|
||||
.build_unconditional_branch(start_of_code_block);
|
||||
self.builder
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.position_at_end(&start_of_code_block);
|
||||
.position_at_end(start_of_code_block);
|
||||
|
||||
let cache_builder = self.context.as_ref().unwrap().create_builder();
|
||||
cache_builder.position_before(&entry_end_inst);
|
||||
@ -1210,7 +1210,7 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
|
||||
})?;
|
||||
|
||||
let end_block = context.append_basic_block(function, "end");
|
||||
builder.position_at_end(&end_block);
|
||||
builder.position_at_end(end_block);
|
||||
|
||||
let phis = if let Ok(wasmer_ty) = blocktype_to_type(ty) {
|
||||
let llvm_ty = type_to_llvm(intrinsics, wasmer_ty);
|
||||
@ -1223,15 +1223,15 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
|
||||
};
|
||||
|
||||
state.push_block(end_block, phis);
|
||||
builder.position_at_end(¤t_block);
|
||||
builder.position_at_end(current_block);
|
||||
}
|
||||
Operator::Loop { ty } => {
|
||||
let loop_body = context.append_basic_block(function, "loop_body");
|
||||
let loop_next = context.append_basic_block(function, "loop_outer");
|
||||
|
||||
builder.build_unconditional_branch(&loop_body);
|
||||
builder.build_unconditional_branch(loop_body);
|
||||
|
||||
builder.position_at_end(&loop_next);
|
||||
builder.position_at_end(loop_next);
|
||||
let phis = if let Ok(wasmer_ty) = blocktype_to_type(ty) {
|
||||
let llvm_ty = type_to_llvm(intrinsics, wasmer_ty);
|
||||
[llvm_ty]
|
||||
@ -1242,7 +1242,7 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
|
||||
SmallVec::new()
|
||||
};
|
||||
|
||||
builder.position_at_end(&loop_body);
|
||||
builder.position_at_end(loop_body);
|
||||
|
||||
if self.track_state {
|
||||
if let Some(offset) = opcode_offset {
|
||||
@ -1299,10 +1299,10 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
|
||||
// pop a value off the value stack and load it into
|
||||
// the corresponding phi.
|
||||
for (phi, value) in frame.phis().iter().zip(values) {
|
||||
phi.add_incoming(&[(&value, ¤t_block)]);
|
||||
phi.add_incoming(&[(&value, current_block)]);
|
||||
}
|
||||
|
||||
builder.build_unconditional_branch(frame.br_dest());
|
||||
builder.build_unconditional_branch(*frame.br_dest());
|
||||
|
||||
state.popn(value_len)?;
|
||||
state.reachable = false;
|
||||
@ -1327,7 +1327,7 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
|
||||
});
|
||||
|
||||
for (phi, value) in frame.phis().iter().zip(param_stack) {
|
||||
phi.add_incoming(&[(&value, ¤t_block)]);
|
||||
phi.add_incoming(&[(&value, current_block)]);
|
||||
}
|
||||
|
||||
let else_block = context.append_basic_block(function, "else");
|
||||
@ -1338,8 +1338,8 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
|
||||
intrinsics.i32_zero,
|
||||
&state.var_name(),
|
||||
);
|
||||
builder.build_conditional_branch(cond_value, frame.br_dest(), &else_block);
|
||||
builder.position_at_end(&else_block);
|
||||
builder.build_conditional_branch(cond_value, *frame.br_dest(), else_block);
|
||||
builder.position_at_end(else_block);
|
||||
}
|
||||
Operator::BrTable { ref table } => {
|
||||
let current_block = builder.get_insert_block().ok_or(CodegenError {
|
||||
@ -1360,7 +1360,7 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
|
||||
};
|
||||
|
||||
for (phi, value) in default_frame.phis().iter().zip(args.iter()) {
|
||||
phi.add_incoming(&[(value, ¤t_block)]);
|
||||
phi.add_incoming(&[(value, current_block)]);
|
||||
}
|
||||
|
||||
let cases: Vec<_> = label_depths
|
||||
@ -1377,14 +1377,14 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
|
||||
context.i32_type().const_int(case_index as u64, false);
|
||||
|
||||
for (phi, value) in frame.phis().iter().zip(args.iter()) {
|
||||
phi.add_incoming(&[(value, ¤t_block)]);
|
||||
phi.add_incoming(&[(value, current_block)]);
|
||||
}
|
||||
|
||||
Ok((case_index_literal, frame.br_dest()))
|
||||
Ok((case_index_literal, *frame.br_dest()))
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
builder.build_switch(index.into_int_value(), default_frame.br_dest(), &cases[..]);
|
||||
builder.build_switch(index.into_int_value(), *default_frame.br_dest(), &cases[..]);
|
||||
|
||||
let args_len = args.len();
|
||||
state.popn(args_len)?;
|
||||
@ -1399,7 +1399,7 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
|
||||
let end_block = context.append_basic_block(function, "if_end");
|
||||
|
||||
let end_phis = {
|
||||
builder.position_at_end(&end_block);
|
||||
builder.position_at_end(end_block);
|
||||
|
||||
let phis = if let Ok(wasmer_ty) = blocktype_to_type(ty) {
|
||||
let llvm_ty = type_to_llvm(intrinsics, wasmer_ty);
|
||||
@ -1411,7 +1411,7 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
|
||||
SmallVec::new()
|
||||
};
|
||||
|
||||
builder.position_at_end(¤t_block);
|
||||
builder.position_at_end(current_block);
|
||||
phis
|
||||
};
|
||||
|
||||
@ -1424,8 +1424,8 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
|
||||
&state.var_name(),
|
||||
);
|
||||
|
||||
builder.build_conditional_branch(cond_value, &if_then_block, &if_else_block);
|
||||
builder.position_at_end(&if_then_block);
|
||||
builder.build_conditional_branch(cond_value, if_then_block, if_else_block);
|
||||
builder.position_at_end(if_then_block);
|
||||
state.push_if(if_then_block, if_else_block, end_block, end_phis);
|
||||
}
|
||||
Operator::Else => {
|
||||
@ -1439,10 +1439,10 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
|
||||
let (value, info) = state.pop1_extra()?;
|
||||
let value =
|
||||
apply_pending_canonicalization(builder, intrinsics, value, info);
|
||||
phi.add_incoming(&[(&value, ¤t_block)])
|
||||
phi.add_incoming(&[(&value, current_block)])
|
||||
}
|
||||
let frame = state.frame_at_depth(0)?;
|
||||
builder.build_unconditional_branch(frame.code_after());
|
||||
builder.build_unconditional_branch(*frame.code_after());
|
||||
}
|
||||
|
||||
let (if_else_block, if_else_state) = if let ControlFrame::IfElse {
|
||||
@ -1458,7 +1458,7 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
|
||||
|
||||
*if_else_state = IfElseState::Else;
|
||||
|
||||
builder.position_at_end(if_else_block);
|
||||
builder.position_at_end(*if_else_block);
|
||||
state.reachable = true;
|
||||
}
|
||||
|
||||
@ -1473,10 +1473,10 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
|
||||
let (value, info) = state.pop1_extra()?;
|
||||
let value =
|
||||
apply_pending_canonicalization(builder, intrinsics, value, info);
|
||||
phi.add_incoming(&[(&value, ¤t_block)]);
|
||||
phi.add_incoming(&[(&value, current_block)]);
|
||||
}
|
||||
|
||||
builder.build_unconditional_branch(frame.code_after());
|
||||
builder.build_unconditional_branch(*frame.code_after());
|
||||
}
|
||||
|
||||
if let ControlFrame::IfElse {
|
||||
@ -1487,12 +1487,12 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
|
||||
} = &frame
|
||||
{
|
||||
if let IfElseState::If = if_else_state {
|
||||
builder.position_at_end(if_else);
|
||||
builder.build_unconditional_branch(next);
|
||||
builder.position_at_end(*if_else);
|
||||
builder.build_unconditional_branch(*next);
|
||||
}
|
||||
}
|
||||
|
||||
builder.position_at_end(frame.code_after());
|
||||
builder.position_at_end(*frame.code_after());
|
||||
state.reset_stack(&frame);
|
||||
|
||||
state.reachable = true;
|
||||
@ -1530,11 +1530,11 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
|
||||
for phi in frame.phis().to_vec().iter() {
|
||||
let (arg, info) = state.pop1_extra()?;
|
||||
let arg = apply_pending_canonicalization(builder, intrinsics, arg, info);
|
||||
phi.add_incoming(&[(&arg, ¤t_block)]);
|
||||
phi.add_incoming(&[(&arg, current_block)]);
|
||||
}
|
||||
|
||||
let frame = state.outermost_frame()?;
|
||||
builder.build_unconditional_branch(frame.br_dest());
|
||||
builder.build_unconditional_branch(*frame.br_dest());
|
||||
|
||||
state.reachable = false;
|
||||
}
|
||||
@ -2073,17 +2073,17 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
|
||||
context.append_basic_block(function, "not_in_bounds_block");
|
||||
builder.build_conditional_branch(
|
||||
index_in_bounds,
|
||||
&in_bounds_continue_block,
|
||||
¬_in_bounds_block,
|
||||
in_bounds_continue_block,
|
||||
not_in_bounds_block,
|
||||
);
|
||||
builder.position_at_end(¬_in_bounds_block);
|
||||
builder.position_at_end(not_in_bounds_block);
|
||||
builder.build_call(
|
||||
intrinsics.throw_trap,
|
||||
&[intrinsics.trap_call_indirect_oob],
|
||||
"throw",
|
||||
);
|
||||
builder.build_unreachable();
|
||||
builder.position_at_end(&in_bounds_continue_block);
|
||||
builder.position_at_end(in_bounds_continue_block);
|
||||
|
||||
// Next, check if the signature id is correct.
|
||||
|
||||
@ -2114,18 +2114,18 @@ impl<'ctx> FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator<'ct
|
||||
context.append_basic_block(function, "sigindices_notequal_block");
|
||||
builder.build_conditional_branch(
|
||||
sigindices_equal,
|
||||
&continue_block,
|
||||
&sigindices_notequal_block,
|
||||
continue_block,
|
||||
sigindices_notequal_block,
|
||||
);
|
||||
|
||||
builder.position_at_end(&sigindices_notequal_block);
|
||||
builder.position_at_end(sigindices_notequal_block);
|
||||
builder.build_call(
|
||||
intrinsics.throw_trap,
|
||||
&[intrinsics.trap_call_indirect_sig],
|
||||
"throw",
|
||||
);
|
||||
builder.build_unreachable();
|
||||
builder.position_at_end(&continue_block);
|
||||
builder.position_at_end(continue_block);
|
||||
|
||||
let wasmer_fn_sig = &info.signatures[sig_index];
|
||||
let fn_ty = signatures[sig_index];
|
||||
@ -8763,10 +8763,10 @@ impl<'ctx> ModuleCodeGenerator<LLVMFunctionCodeGenerator<'ctx>, LLVMBackend, Cod
|
||||
let mut state: State<'ctx> = State::new();
|
||||
let entry_block = context.append_basic_block(*function, "entry");
|
||||
let alloca_builder = context.create_builder();
|
||||
alloca_builder.position_at_end(&entry_block);
|
||||
alloca_builder.position_at_end(entry_block);
|
||||
|
||||
let return_block = context.append_basic_block(*function, "return");
|
||||
builder.position_at_end(&return_block);
|
||||
builder.position_at_end(return_block);
|
||||
|
||||
let phis: SmallVec<[PhiValue; 1]> = func_sig
|
||||
.returns()
|
||||
@ -8776,7 +8776,7 @@ impl<'ctx> ModuleCodeGenerator<LLVMFunctionCodeGenerator<'ctx>, LLVMBackend, Cod
|
||||
.collect();
|
||||
|
||||
state.push_block(return_block, phis);
|
||||
builder.position_at_end(&entry_block);
|
||||
builder.position_at_end(entry_block);
|
||||
|
||||
let mut locals = Vec::new();
|
||||
locals.extend(
|
||||
|
@ -10,20 +10,20 @@ use std::ops::{BitAnd, BitOr, BitOrAssign};
|
||||
#[derive(Debug)]
|
||||
pub enum ControlFrame<'ctx> {
|
||||
Block {
|
||||
next: BasicBlock,
|
||||
next: BasicBlock<'ctx>,
|
||||
phis: SmallVec<[PhiValue<'ctx>; 1]>,
|
||||
stack_size_snapshot: usize,
|
||||
},
|
||||
Loop {
|
||||
body: BasicBlock,
|
||||
next: BasicBlock,
|
||||
body: BasicBlock<'ctx>,
|
||||
next: BasicBlock<'ctx>,
|
||||
phis: SmallVec<[PhiValue<'ctx>; 1]>,
|
||||
stack_size_snapshot: usize,
|
||||
},
|
||||
IfElse {
|
||||
if_then: BasicBlock,
|
||||
if_else: BasicBlock,
|
||||
next: BasicBlock,
|
||||
if_then: BasicBlock<'ctx>,
|
||||
if_else: BasicBlock<'ctx>,
|
||||
next: BasicBlock<'ctx>,
|
||||
phis: SmallVec<[PhiValue<'ctx>; 1]>,
|
||||
stack_size_snapshot: usize,
|
||||
if_else_state: IfElseState,
|
||||
@ -37,7 +37,7 @@ pub enum IfElseState {
|
||||
}
|
||||
|
||||
impl<'ctx> ControlFrame<'ctx> {
|
||||
pub fn code_after(&self) -> &BasicBlock {
|
||||
pub fn code_after(&self) -> &BasicBlock<'ctx> {
|
||||
match self {
|
||||
ControlFrame::Block { ref next, .. }
|
||||
| ControlFrame::Loop { ref next, .. }
|
||||
@ -45,7 +45,7 @@ impl<'ctx> ControlFrame<'ctx> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn br_dest(&self) -> &BasicBlock {
|
||||
pub fn br_dest(&self) -> &BasicBlock<'ctx> {
|
||||
match self {
|
||||
ControlFrame::Block { ref next, .. } | ControlFrame::IfElse { ref next, .. } => next,
|
||||
ControlFrame::Loop { ref body, .. } => body,
|
||||
@ -367,7 +367,7 @@ impl<'ctx> State<'ctx> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn push_block(&mut self, next: BasicBlock, phis: SmallVec<[PhiValue<'ctx>; 1]>) {
|
||||
pub fn push_block(&mut self, next: BasicBlock<'ctx>, phis: SmallVec<[PhiValue<'ctx>; 1]>) {
|
||||
self.control_stack.push(ControlFrame::Block {
|
||||
next,
|
||||
phis,
|
||||
@ -377,8 +377,8 @@ impl<'ctx> State<'ctx> {
|
||||
|
||||
pub fn push_loop(
|
||||
&mut self,
|
||||
body: BasicBlock,
|
||||
next: BasicBlock,
|
||||
body: BasicBlock<'ctx>,
|
||||
next: BasicBlock<'ctx>,
|
||||
phis: SmallVec<[PhiValue<'ctx>; 1]>,
|
||||
) {
|
||||
self.control_stack.push(ControlFrame::Loop {
|
||||
@ -391,9 +391,9 @@ impl<'ctx> State<'ctx> {
|
||||
|
||||
pub fn push_if(
|
||||
&mut self,
|
||||
if_then: BasicBlock,
|
||||
if_else: BasicBlock,
|
||||
next: BasicBlock,
|
||||
if_then: BasicBlock<'ctx>,
|
||||
if_else: BasicBlock<'ctx>,
|
||||
next: BasicBlock<'ctx>,
|
||||
phis: SmallVec<[PhiValue<'ctx>; 1]>,
|
||||
) {
|
||||
self.control_stack.push(ControlFrame::IfElse {
|
||||
|
@ -55,7 +55,7 @@ fn generate_trampoline<'ctx>(
|
||||
intrinsics: &Intrinsics<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
let entry_block = context.append_basic_block(trampoline_func, "entry");
|
||||
builder.position_at_end(&entry_block);
|
||||
builder.position_at_end(entry_block);
|
||||
|
||||
let (vmctx_ptr, func_ptr, args_ptr, returns_ptr) = match trampoline_func.get_params().as_slice()
|
||||
{
|
||||
|
@ -78,7 +78,9 @@ impl<T: Copy + ValueType> WasmPtr<T, Item> {
|
||||
/// This invariant will be enforced in the future.
|
||||
#[inline]
|
||||
pub fn deref<'a>(self, memory: &'a Memory) -> Option<&'a Cell<T>> {
|
||||
if (self.offset as usize) + mem::size_of::<T>() >= memory.size().bytes().0 {
|
||||
if (self.offset as usize) + mem::size_of::<T>() > memory.size().bytes().0
|
||||
|| mem::size_of::<T>() == 0
|
||||
{
|
||||
return None;
|
||||
}
|
||||
unsafe {
|
||||
@ -99,7 +101,9 @@ impl<T: Copy + ValueType> WasmPtr<T, Item> {
|
||||
/// exclusive access to Wasm linear memory before calling this method.
|
||||
#[inline]
|
||||
pub unsafe fn deref_mut<'a>(self, memory: &'a Memory) -> Option<&'a mut Cell<T>> {
|
||||
if (self.offset as usize) + mem::size_of::<T>() >= memory.size().bytes().0 {
|
||||
if (self.offset as usize) + mem::size_of::<T>() > memory.size().bytes().0
|
||||
|| mem::size_of::<T>() == 0
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let cell_ptr = align_pointer(
|
||||
@ -127,7 +131,10 @@ impl<T: Copy + ValueType> WasmPtr<T, Array> {
|
||||
let item_size = mem::size_of::<T>() + (mem::size_of::<T>() % mem::align_of::<T>());
|
||||
let slice_full_len = index as usize + length as usize;
|
||||
|
||||
if (self.offset as usize) + (item_size * slice_full_len) >= memory.size().bytes().0 {
|
||||
if (self.offset as usize) + (item_size * slice_full_len) > memory.size().bytes().0
|
||||
|| length == 0
|
||||
|| mem::size_of::<T>() == 0
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
@ -161,7 +168,10 @@ impl<T: Copy + ValueType> WasmPtr<T, Array> {
|
||||
let item_size = mem::size_of::<T>() + (mem::size_of::<T>() % mem::align_of::<T>());
|
||||
let slice_full_len = index as usize + length as usize;
|
||||
|
||||
if (self.offset as usize) + (item_size * slice_full_len) >= memory.size().bytes().0 {
|
||||
if (self.offset as usize) + (item_size * slice_full_len) > memory.size().bytes().0
|
||||
|| length == 0
|
||||
|| mem::size_of::<T>() == 0
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
@ -180,7 +190,7 @@ impl<T: Copy + ValueType> WasmPtr<T, Array> {
|
||||
/// underlying data can be mutated if the Wasm is allowed to execute or
|
||||
/// an aliasing `WasmPtr` is used to mutate memory.
|
||||
pub fn get_utf8_string(self, memory: &Memory, str_len: u32) -> Option<&str> {
|
||||
if self.offset as usize + str_len as usize > memory.size().bytes().0 {
|
||||
if self.offset as usize + str_len as usize > memory.size().bytes().0 || str_len == 0 {
|
||||
return None;
|
||||
}
|
||||
let ptr = unsafe { memory.view::<u8>().as_ptr().add(self.offset as usize) as *const u8 };
|
||||
@ -245,3 +255,94 @@ impl<T: Copy, Ty> fmt::Debug for WasmPtr<T, Ty> {
|
||||
write!(f, "WasmPtr({:#x})", self.offset)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::memory;
|
||||
use crate::units::Pages;
|
||||
|
||||
/// Ensure that memory accesses work on the edges of memory and that out of
|
||||
/// bounds errors are caught with both `deref` and `deref_mut`.
|
||||
#[test]
|
||||
fn wasm_ptr_memory_bounds_checks_hold() {
|
||||
// create a memory
|
||||
let memory_descriptor =
|
||||
memory::MemoryDescriptor::new(Pages(1), Some(Pages(1)), false).unwrap();
|
||||
let memory = memory::Memory::new(memory_descriptor).unwrap();
|
||||
|
||||
// test that basic access works and that len = 0 is caught correctly
|
||||
let start_wasm_ptr: WasmPtr<u8> = WasmPtr::new(0);
|
||||
let start_wasm_ptr_array: WasmPtr<u8, Array> = WasmPtr::new(0);
|
||||
|
||||
assert!(start_wasm_ptr.deref(&memory).is_some());
|
||||
assert!(unsafe { start_wasm_ptr.deref_mut(&memory).is_some() });
|
||||
assert!(start_wasm_ptr_array.deref(&memory, 0, 0).is_none());
|
||||
assert!(start_wasm_ptr_array.get_utf8_string(&memory, 0).is_none());
|
||||
assert!(unsafe { start_wasm_ptr_array.deref_mut(&memory, 0, 0).is_none() });
|
||||
assert!(start_wasm_ptr_array.deref(&memory, 0, 1).is_some());
|
||||
assert!(unsafe { start_wasm_ptr_array.deref_mut(&memory, 0, 1).is_some() });
|
||||
|
||||
// test that accessing the last valid memory address works correctly and OOB is caught
|
||||
let last_valid_address_for_u8 = (memory.size().bytes().0 - 1) as u32;
|
||||
let end_wasm_ptr: WasmPtr<u8> = WasmPtr::new(last_valid_address_for_u8);
|
||||
assert!(end_wasm_ptr.deref(&memory).is_some());
|
||||
assert!(unsafe { end_wasm_ptr.deref_mut(&memory).is_some() });
|
||||
|
||||
let end_wasm_ptr_array: WasmPtr<u8, Array> = WasmPtr::new(last_valid_address_for_u8);
|
||||
|
||||
assert!(end_wasm_ptr_array.deref(&memory, 0, 1).is_some());
|
||||
assert!(unsafe { end_wasm_ptr_array.deref_mut(&memory, 0, 1).is_some() });
|
||||
let invalid_idx_len_combos: [(u32, u32); 3] = [(0, 0), (0, 2), (1, 1)];
|
||||
for &(idx, len) in invalid_idx_len_combos.into_iter() {
|
||||
assert!(end_wasm_ptr_array.deref(&memory, idx, len).is_none());
|
||||
assert!(unsafe { end_wasm_ptr_array.deref_mut(&memory, idx, len).is_none() });
|
||||
}
|
||||
assert!(end_wasm_ptr_array.get_utf8_string(&memory, 2).is_none());
|
||||
|
||||
// test that accesing the last valid memory address for a u32 is valid
|
||||
// (same as above test but with more edge cases to assert on)
|
||||
let last_valid_address_for_u32 = (memory.size().bytes().0 - 4) as u32;
|
||||
let end_wasm_ptr: WasmPtr<u32> = WasmPtr::new(last_valid_address_for_u32);
|
||||
assert!(end_wasm_ptr.deref(&memory).is_some());
|
||||
assert!(unsafe { end_wasm_ptr.deref_mut(&memory).is_some() });
|
||||
assert!(end_wasm_ptr.deref(&memory).is_some());
|
||||
assert!(unsafe { end_wasm_ptr.deref_mut(&memory).is_some() });
|
||||
|
||||
let end_wasm_ptr_oob_array: [WasmPtr<u32>; 4] = [
|
||||
WasmPtr::new(last_valid_address_for_u32 + 1),
|
||||
WasmPtr::new(last_valid_address_for_u32 + 2),
|
||||
WasmPtr::new(last_valid_address_for_u32 + 3),
|
||||
WasmPtr::new(last_valid_address_for_u32 + 4),
|
||||
];
|
||||
for oob_end_ptr in end_wasm_ptr_oob_array.into_iter() {
|
||||
assert!(oob_end_ptr.deref(&memory).is_none());
|
||||
assert!(unsafe { oob_end_ptr.deref_mut(&memory).is_none() });
|
||||
}
|
||||
let end_wasm_ptr_array: WasmPtr<u32, Array> = WasmPtr::new(last_valid_address_for_u32);
|
||||
assert!(end_wasm_ptr_array.deref(&memory, 0, 1).is_some());
|
||||
assert!(unsafe { end_wasm_ptr_array.deref_mut(&memory, 0, 1).is_some() });
|
||||
|
||||
let invalid_idx_len_combos: [(u32, u32); 4] = [(0, 0), (1, 0), (0, 2), (1, 1)];
|
||||
for &(idx, len) in invalid_idx_len_combos.into_iter() {
|
||||
assert!(end_wasm_ptr_array.deref(&memory, idx, len).is_none());
|
||||
assert!(unsafe { end_wasm_ptr_array.deref_mut(&memory, idx, len).is_none() });
|
||||
}
|
||||
|
||||
let end_wasm_ptr_array_oob_array: [WasmPtr<u32, Array>; 4] = [
|
||||
WasmPtr::new(last_valid_address_for_u32 + 1),
|
||||
WasmPtr::new(last_valid_address_for_u32 + 2),
|
||||
WasmPtr::new(last_valid_address_for_u32 + 3),
|
||||
WasmPtr::new(last_valid_address_for_u32 + 4),
|
||||
];
|
||||
|
||||
for oob_end_array_ptr in end_wasm_ptr_array_oob_array.into_iter() {
|
||||
assert!(oob_end_array_ptr.deref(&memory, 0, 1).is_none());
|
||||
assert!(unsafe { oob_end_array_ptr.deref_mut(&memory, 0, 1).is_none() });
|
||||
assert!(oob_end_array_ptr.deref(&memory, 0, 0).is_none());
|
||||
assert!(unsafe { oob_end_array_ptr.deref_mut(&memory, 0, 0).is_none() });
|
||||
assert!(oob_end_array_ptr.deref(&memory, 1, 0).is_none());
|
||||
assert!(unsafe { oob_end_array_ptr.deref_mut(&memory, 1, 0).is_none() });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user