mirror of
https://github.com/fluencelabs/parity-wasm
synced 2025-06-22 03:02:08 +00:00
drop interpreter
This commit is contained in:
@ -2,7 +2,6 @@ dist: trusty
|
|||||||
sudo: required
|
sudo: required
|
||||||
language:
|
language:
|
||||||
- rust
|
- rust
|
||||||
- cpp
|
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
sources:
|
sources:
|
||||||
@ -17,7 +16,6 @@ script:
|
|||||||
- cargo build --release --verbose
|
- cargo build --release --verbose
|
||||||
- cargo test --release --verbose
|
- cargo test --release --verbose
|
||||||
- cargo test --release --manifest-path=spec/Cargo.toml
|
- cargo test --release --manifest-path=spec/Cargo.toml
|
||||||
- cargo test --manifest-path=pwasm-emscripten/Cargo.toml
|
|
||||||
- cargo run --example bench-decoder --release
|
- cargo run --example bench-decoder --release
|
||||||
after_success: |-
|
after_success: |-
|
||||||
[ $TRAVIS_BRANCH = master ] &&
|
[ $TRAVIS_BRANCH = master ] &&
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
// In this example we execute a contract funciton exported as "_call"
|
|
||||||
#![allow(deprecated)]
|
|
||||||
|
|
||||||
extern crate parity_wasm;
|
|
||||||
|
|
||||||
use std::env::args;
|
|
||||||
|
|
||||||
use parity_wasm::ModuleInstanceInterface;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let args: Vec<_> = args().collect();
|
|
||||||
if args.len() != 3 {
|
|
||||||
println!("Usage: {} <wasm file> <arg>", args[0]);
|
|
||||||
println!(" wasm file should contain exported `_call` function with single I32 argument");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intrepreter initialization.
|
|
||||||
let program = parity_wasm::ProgramInstance::new();
|
|
||||||
|
|
||||||
// Here we load module using dedicated for this purpose
|
|
||||||
// `deserialize_file` function (which works only with modules)
|
|
||||||
let module = parity_wasm::deserialize_file(&args[1]).expect("Failed to load module");
|
|
||||||
|
|
||||||
// Intialize deserialized module. It adds module into It expects 3 parameters:
|
|
||||||
// - a name for the module
|
|
||||||
// - a module declaration
|
|
||||||
// - "main" module doesn't import native module(s) this is why we don't need to provide external native modules here
|
|
||||||
// This test shows how to implement native module https://github.com/NikVolf/parity-wasm/blob/master/src/interpreter/tests/basics.rs#L197
|
|
||||||
let module = program.add_module("main", module, None).expect("Failed to initialize module");
|
|
||||||
|
|
||||||
// The argument should be parsable as a valid integer
|
|
||||||
let argument: i32 = args[2].parse().expect("Integer argument required");
|
|
||||||
|
|
||||||
// "_call" export of function to be executed with an i32 argument and prints the result of execution
|
|
||||||
println!("Result: {:?}", module.execute_export("_call", vec![parity_wasm::RuntimeValue::I32(argument)].into()));
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
#![allow(deprecated)]
|
|
||||||
|
|
||||||
extern crate parity_wasm;
|
|
||||||
|
|
||||||
use std::env::args;
|
|
||||||
|
|
||||||
use parity_wasm::{interpreter, ModuleInstanceInterface, RuntimeValue};
|
|
||||||
use parity_wasm::elements::{Internal, External, Type, FunctionType, ValueType};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let args: Vec<_> = args().collect();
|
|
||||||
if args.len() < 3 {
|
|
||||||
println!("Usage: {} <wasm file> <exported func> [<arg>...]", args[0]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let func_name = &args[2];
|
|
||||||
let (_, program_args) = args.split_at(3);
|
|
||||||
|
|
||||||
// Intrepreter initialization.
|
|
||||||
let program = parity_wasm::ProgramInstance::new();
|
|
||||||
|
|
||||||
let module = parity_wasm::deserialize_file(&args[1]).expect("File to be deserialized");
|
|
||||||
|
|
||||||
// Extracts call arguments from command-line arguments
|
|
||||||
let execution_params = {
|
|
||||||
// Export section has an entry with a func_name with an index inside a module
|
|
||||||
let export_section = module.export_section().expect("No export section found");
|
|
||||||
// It's a section with function declarations (which are references to the type section entries)
|
|
||||||
let function_section = module.function_section().expect("No function section found");
|
|
||||||
// Type section stores function types which are referenced by function_section entries
|
|
||||||
let type_section = module.type_section().expect("No type section found");
|
|
||||||
|
|
||||||
// Given function name used to find export section entry which contains
|
|
||||||
// an `internal` field which points to the index in the function index space
|
|
||||||
let found_entry = export_section.entries().iter()
|
|
||||||
.find(|entry| func_name == entry.field()).expect(&format!("No export with name {} found", func_name));
|
|
||||||
|
|
||||||
// Function index in the function index space (internally-defined + imported)
|
|
||||||
let function_index: usize = match found_entry.internal() {
|
|
||||||
&Internal::Function(index) => index as usize,
|
|
||||||
_ => panic!("Founded export is not a function"),
|
|
||||||
};
|
|
||||||
|
|
||||||
// We need to count import section entries (functions only!) to subtract it from function_index
|
|
||||||
// and obtain the index within the function section
|
|
||||||
let import_section_len: usize = match module.import_section() {
|
|
||||||
Some(import) =>
|
|
||||||
import.entries().iter().filter(|entry| match entry.external() {
|
|
||||||
&External::Function(_) => true,
|
|
||||||
_ => false,
|
|
||||||
}).count(),
|
|
||||||
None => 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calculates a function index within module's function section
|
|
||||||
let function_index_in_section = function_index - import_section_len;
|
|
||||||
|
|
||||||
// Getting a type reference from a function section entry
|
|
||||||
let func_type_ref: usize = function_section.entries()[function_index_in_section].type_ref() as usize;
|
|
||||||
|
|
||||||
// Use the reference to get an actual function type
|
|
||||||
let function_type: &FunctionType = match &type_section.types()[func_type_ref] {
|
|
||||||
&Type::Function(ref func_type) => func_type,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parses arguments and constructs runtime values in correspondence of their types
|
|
||||||
let args: Vec<RuntimeValue> = function_type.params().iter().enumerate().map(|(i, value)| match value {
|
|
||||||
&ValueType::I32 => RuntimeValue::I32(program_args[i].parse::<i32>().expect(&format!("Can't parse arg #{} as i32", program_args[i]))),
|
|
||||||
&ValueType::I64 => RuntimeValue::I64(program_args[i].parse::<i64>().expect(&format!("Can't parse arg #{} as i64", program_args[i]))),
|
|
||||||
&ValueType::F32 => RuntimeValue::F32(program_args[i].parse::<f32>().expect(&format!("Can't parse arg #{} as f32", program_args[i]))),
|
|
||||||
&ValueType::F64 => RuntimeValue::F64(program_args[i].parse::<f64>().expect(&format!("Can't parse arg #{} as f64", program_args[i]))),
|
|
||||||
}).collect();
|
|
||||||
|
|
||||||
interpreter::ExecutionParams::from(args)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Intialize deserialized module. It adds module into It expects 3 parameters:
|
|
||||||
// - a name for the module
|
|
||||||
// - a module declaration
|
|
||||||
// - "main" module doesn't import native module(s) this is why we don't need to provide external native modules here
|
|
||||||
// This test shows how to implement native module https://github.com/NikVolf/parity-wasm/blob/master/src/interpreter/tests/basics.rs#L197
|
|
||||||
let module = program.add_module("main", module, None).expect("Failed to initialize module");
|
|
||||||
|
|
||||||
println!("Result: {:?}", module.execute_export(func_name, execution_params).expect(""));
|
|
||||||
}
|
|
1
pwasm-emscripten/.gitignore
vendored
1
pwasm-emscripten/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
target
|
|
@ -1,7 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "pwasm-emscripten"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Sergey Pepyakin <s.pepyakin@gmail.com>"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
parity-wasm = "0.18"
|
|
@ -1,263 +0,0 @@
|
|||||||
//! This crate provides some of the simplest exports
|
|
||||||
//! from the Emscripten runtime, such as `STACKTOP` or `abort`.
|
|
||||||
|
|
||||||
extern crate parity_wasm;
|
|
||||||
|
|
||||||
use std::sync::{Arc, Weak};
|
|
||||||
use parity_wasm::builder::module;
|
|
||||||
use parity_wasm::elements::{ExportEntry, Internal, ValueType};
|
|
||||||
use parity_wasm::interpreter::Error;
|
|
||||||
use parity_wasm::interpreter::{native_module, UserDefinedElements, UserFunctionDescriptor, UserFunctionExecutor};
|
|
||||||
use parity_wasm::interpreter::{CallerContext, ModuleInstance, ModuleInstanceInterface};
|
|
||||||
use parity_wasm::interpreter::RuntimeValue;
|
|
||||||
use parity_wasm::interpreter::ProgramInstance;
|
|
||||||
use parity_wasm::interpreter::{VariableInstance, VariableType};
|
|
||||||
|
|
||||||
/// Memory address, at which stack begins.
|
|
||||||
const DEFAULT_STACK_TOP: u32 = 256 * 1024;
|
|
||||||
/// Memory, allocated for stack.
|
|
||||||
const DEFAULT_TOTAL_STACK: u32 = 5 * 1024 * 1024;
|
|
||||||
/// Total memory, allocated by default.
|
|
||||||
const DEFAULT_TOTAL_MEMORY: u32 = 16 * 1024 * 1024;
|
|
||||||
/// Whether memory can be enlarged, or not.
|
|
||||||
const DEFAULT_ALLOW_MEMORY_GROWTH: bool = true;
|
|
||||||
/// Default tableBase variable value.
|
|
||||||
const DEFAULT_TABLE_BASE: u32 = 0;
|
|
||||||
/// Default tableBase variable value.
|
|
||||||
const DEFAULT_MEMORY_BASE: u32 = 0;
|
|
||||||
|
|
||||||
/// Default table size.
|
|
||||||
const DEFAULT_TABLE_SIZE: u32 = 64;
|
|
||||||
|
|
||||||
/// Index of default memory.
|
|
||||||
const INDEX_MEMORY: u32 = 0;
|
|
||||||
|
|
||||||
/// Index of default table.
|
|
||||||
const INDEX_TABLE: u32 = 0;
|
|
||||||
|
|
||||||
const LINEAR_MEMORY_PAGE_SIZE: u32 = 64 * 1024;
|
|
||||||
|
|
||||||
/// Emscripten environment parameters.
|
|
||||||
pub struct EmscriptenParams {
|
|
||||||
/// Stack size in bytes.
|
|
||||||
pub total_stack: u32,
|
|
||||||
/// Total memory size in bytes.
|
|
||||||
pub total_memory: u32,
|
|
||||||
/// Allow memory growth.
|
|
||||||
pub allow_memory_growth: bool,
|
|
||||||
/// Table size.
|
|
||||||
pub table_size: u32,
|
|
||||||
/// Static reserve, if any
|
|
||||||
pub static_size: Option<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EmscriptenFunctionExecutor {
|
|
||||||
total_mem_global: Arc<VariableInstance>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> UserFunctionExecutor for EmscriptenFunctionExecutor {
|
|
||||||
fn execute(
|
|
||||||
&mut self,
|
|
||||||
name: &str,
|
|
||||||
context: CallerContext,
|
|
||||||
) -> Result<Option<RuntimeValue>, Error> {
|
|
||||||
match name {
|
|
||||||
"_abort" | "abort" => {
|
|
||||||
Err(Error::Trap("abort".into()).into())
|
|
||||||
},
|
|
||||||
"assert" => {
|
|
||||||
let condition = context.value_stack.pop_as::<i32>()?;
|
|
||||||
if condition == 0 {
|
|
||||||
Err(Error::Trap("assertion failed".into()))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"enlargeMemory" => {
|
|
||||||
// TODO: support memory enlarge
|
|
||||||
Ok(Some(RuntimeValue::I32(0)))
|
|
||||||
},
|
|
||||||
"getTotalMemory" => {
|
|
||||||
let total_memory = self.total_mem_global.get();
|
|
||||||
Ok(Some(total_memory))
|
|
||||||
},
|
|
||||||
_ => Err(Error::Trap("not implemented".into()).into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn env_module(params: EmscriptenParams) -> Result<Arc<ModuleInstanceInterface>, Error> {
|
|
||||||
debug_assert!(params.total_stack < params.total_memory);
|
|
||||||
debug_assert!((params.total_stack % LINEAR_MEMORY_PAGE_SIZE) == 0);
|
|
||||||
debug_assert!((params.total_memory % LINEAR_MEMORY_PAGE_SIZE) == 0);
|
|
||||||
|
|
||||||
let stack_top = params.static_size.unwrap_or(DEFAULT_STACK_TOP);
|
|
||||||
|
|
||||||
// Build module with defined memory and table,
|
|
||||||
// instantiate it and wrap into an Arc.
|
|
||||||
let instance = {
|
|
||||||
let builder = module()
|
|
||||||
// memory regions
|
|
||||||
.memory()
|
|
||||||
.with_min(params.total_memory / LINEAR_MEMORY_PAGE_SIZE)
|
|
||||||
.with_max(params.max_memory().map(|m| m / LINEAR_MEMORY_PAGE_SIZE))
|
|
||||||
.build()
|
|
||||||
.with_export(ExportEntry::new("memory".into(), Internal::Memory(INDEX_MEMORY)))
|
|
||||||
// tables
|
|
||||||
.table()
|
|
||||||
.with_min(params.table_size)
|
|
||||||
.build()
|
|
||||||
.with_export(ExportEntry::new("table".into(), Internal::Table(INDEX_TABLE)));
|
|
||||||
let mut instance = ModuleInstance::new(Weak::default(), "env".into(), builder.build())?;
|
|
||||||
instance.instantiate(None)?;
|
|
||||||
Arc::new(instance)
|
|
||||||
};
|
|
||||||
let total_mem_global = Arc::new(
|
|
||||||
VariableInstance::new(
|
|
||||||
false,
|
|
||||||
VariableType::I32,
|
|
||||||
RuntimeValue::I32(params.total_memory as i32),
|
|
||||||
).unwrap(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let function_executor = EmscriptenFunctionExecutor {
|
|
||||||
total_mem_global: Arc::clone(&total_mem_global),
|
|
||||||
};
|
|
||||||
|
|
||||||
const SIGNATURES: &'static [UserFunctionDescriptor] = &[
|
|
||||||
UserFunctionDescriptor::Static("_abort", &[], None),
|
|
||||||
UserFunctionDescriptor::Static("abort", &[], None),
|
|
||||||
UserFunctionDescriptor::Static("assert", &[ValueType::I32], None),
|
|
||||||
UserFunctionDescriptor::Static("enlargeMemory", &[], Some(ValueType::I32)),
|
|
||||||
UserFunctionDescriptor::Static("getTotalMemory", &[], Some(ValueType::I32)),
|
|
||||||
];
|
|
||||||
|
|
||||||
let elements = UserDefinedElements {
|
|
||||||
executor: Some(function_executor),
|
|
||||||
globals: vec![
|
|
||||||
(
|
|
||||||
"STACK_BASE".into(),
|
|
||||||
Arc::new(
|
|
||||||
VariableInstance::new(
|
|
||||||
false,
|
|
||||||
VariableType::I32,
|
|
||||||
RuntimeValue::I32(stack_top as i32),
|
|
||||||
).unwrap(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"STACKTOP".into(),
|
|
||||||
Arc::new(
|
|
||||||
VariableInstance::new(
|
|
||||||
false,
|
|
||||||
VariableType::I32,
|
|
||||||
RuntimeValue::I32(stack_top as i32),
|
|
||||||
).unwrap(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"STACK_MAX".into(),
|
|
||||||
Arc::new(
|
|
||||||
VariableInstance::new(
|
|
||||||
false,
|
|
||||||
VariableType::I32,
|
|
||||||
RuntimeValue::I32((stack_top + params.total_stack) as i32),
|
|
||||||
).unwrap(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"DYNAMIC_BASE".into(),
|
|
||||||
Arc::new(
|
|
||||||
VariableInstance::new(
|
|
||||||
false,
|
|
||||||
VariableType::I32,
|
|
||||||
RuntimeValue::I32((stack_top + params.total_stack) as i32),
|
|
||||||
).unwrap(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"DYNAMICTOP_PTR".into(),
|
|
||||||
Arc::new(
|
|
||||||
VariableInstance::new(
|
|
||||||
false,
|
|
||||||
VariableType::I32,
|
|
||||||
RuntimeValue::I32((stack_top + params.total_stack) as i32),
|
|
||||||
).unwrap(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"EXITSTATUS".into(),
|
|
||||||
Arc::new(
|
|
||||||
VariableInstance::new(false, VariableType::I32, RuntimeValue::I32(0)).unwrap(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"tableBase".into(),
|
|
||||||
Arc::new(
|
|
||||||
VariableInstance::new(
|
|
||||||
false,
|
|
||||||
VariableType::I32,
|
|
||||||
RuntimeValue::I32(DEFAULT_TABLE_BASE as i32),
|
|
||||||
).unwrap(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"memoryBase".into(),
|
|
||||||
Arc::new(
|
|
||||||
VariableInstance::new(
|
|
||||||
false,
|
|
||||||
VariableType::I32,
|
|
||||||
RuntimeValue::I32(DEFAULT_MEMORY_BASE as i32),
|
|
||||||
).unwrap(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("TOTAL_MEMORY".into(), total_mem_global),
|
|
||||||
].into_iter()
|
|
||||||
.collect(),
|
|
||||||
functions: ::std::borrow::Cow::from(SIGNATURES),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(native_module(instance, elements)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for EmscriptenParams {
|
|
||||||
fn default() -> Self {
|
|
||||||
EmscriptenParams {
|
|
||||||
total_stack: DEFAULT_TOTAL_STACK,
|
|
||||||
total_memory: DEFAULT_TOTAL_MEMORY,
|
|
||||||
allow_memory_growth: DEFAULT_ALLOW_MEMORY_GROWTH,
|
|
||||||
table_size: DEFAULT_TABLE_SIZE,
|
|
||||||
static_size: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EmscriptenParams {
|
|
||||||
fn max_memory(&self) -> Option<u32> {
|
|
||||||
if self.allow_memory_growth { None } else { Some(self.total_memory) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn program_with_emscripten_env(params: EmscriptenParams) -> Result<ProgramInstance, Error> {
|
|
||||||
let program = ProgramInstance::new();
|
|
||||||
program.insert_loaded_module("env", env_module(params)?)?;
|
|
||||||
Ok(program)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::program_with_emscripten_env;
|
|
||||||
use parity_wasm::builder::module;
|
|
||||||
use parity_wasm::elements::{ImportEntry, External, GlobalType, ValueType};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn import_env_mutable_global() {
|
|
||||||
let program = program_with_emscripten_env(Default::default()).unwrap();
|
|
||||||
|
|
||||||
let module = module()
|
|
||||||
.with_import(ImportEntry::new("env".into(), "STACKTOP".into(), External::Global(GlobalType::new(ValueType::I32, false))))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
program.add_module("main", module, None).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
use elements::BlockType;
|
|
||||||
|
|
||||||
pub mod stack;
|
|
||||||
|
|
||||||
/// Index of default linear memory.
|
|
||||||
pub const DEFAULT_MEMORY_INDEX: u32 = 0;
|
|
||||||
/// Index of default table.
|
|
||||||
pub const DEFAULT_TABLE_INDEX: u32 = 0;
|
|
||||||
|
|
||||||
/// Control stack frame.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct BlockFrame {
|
|
||||||
/// Frame type.
|
|
||||||
pub frame_type: BlockFrameType,
|
|
||||||
/// A signature, which is a block signature type indicating the number and types of result values of the region.
|
|
||||||
pub block_type: BlockType,
|
|
||||||
/// A label for reference to block instruction.
|
|
||||||
pub begin_position: usize,
|
|
||||||
/// A label for reference from branch instructions.
|
|
||||||
pub branch_position: usize,
|
|
||||||
/// A label for reference from end instructions.
|
|
||||||
pub end_position: usize,
|
|
||||||
/// A limit integer value, which is an index into the value stack indicating where to reset it to on a branch to that label.
|
|
||||||
pub value_stack_len: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Type of block frame.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub enum BlockFrameType {
|
|
||||||
/// Function frame.
|
|
||||||
Function,
|
|
||||||
/// Usual block frame.
|
|
||||||
Block,
|
|
||||||
/// Loop frame (branching to the beginning of block).
|
|
||||||
Loop,
|
|
||||||
/// True-subblock of if expression.
|
|
||||||
IfTrue,
|
|
||||||
/// False-subblock of if expression.
|
|
||||||
IfFalse,
|
|
||||||
}
|
|
@ -1,113 +0,0 @@
|
|||||||
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::error;
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Error(String);
|
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl error::Error for Error {
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stack with limit.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct StackWithLimit<T> where T: Clone {
|
|
||||||
/// Stack values.
|
|
||||||
values: VecDeque<T>,
|
|
||||||
/// Stack limit (maximal stack len).
|
|
||||||
limit: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> StackWithLimit<T> where T: Clone {
|
|
||||||
pub fn with_data(data: Vec<T>, limit: usize) -> Self {
|
|
||||||
StackWithLimit {
|
|
||||||
values: data.into_iter().collect(),
|
|
||||||
limit: limit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_limit(limit: usize) -> Self {
|
|
||||||
StackWithLimit {
|
|
||||||
values: VecDeque::new(),
|
|
||||||
limit: limit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.values.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.values.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn limit(&self) -> usize {
|
|
||||||
self.limit
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn values(&self) -> &VecDeque<T> {
|
|
||||||
&self.values
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn top(&self) -> Result<&T, Error> {
|
|
||||||
self.values
|
|
||||||
.back()
|
|
||||||
.ok_or(Error("non-empty stack expected".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn top_mut(&mut self) -> Result<&mut T, Error> {
|
|
||||||
self.values
|
|
||||||
.back_mut()
|
|
||||||
.ok_or(Error("non-empty stack expected".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, index: usize) -> Result<&T, Error> {
|
|
||||||
if index >= self.values.len() {
|
|
||||||
return Err(Error(format!("trying to get value at position {} on stack of size {}", index, self.values.len())));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self.values.get(self.values.len() - 1 - index).expect("checked couple of lines above"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(&mut self, value: T) -> Result<(), Error> {
|
|
||||||
if self.values.len() >= self.limit {
|
|
||||||
return Err(Error(format!("exceeded stack limit {}", self.limit)));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.values.push_back(value);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_penultimate(&mut self, value: T) -> Result<(), Error> {
|
|
||||||
if self.values.is_empty() {
|
|
||||||
return Err(Error("trying to insert penultimate element into empty stack".into()));
|
|
||||||
}
|
|
||||||
self.push(value)?;
|
|
||||||
|
|
||||||
let last_index = self.values.len() - 1;
|
|
||||||
let penultimate_index = last_index - 1;
|
|
||||||
self.values.swap(last_index, penultimate_index);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop(&mut self) -> Result<T, Error> {
|
|
||||||
self.values
|
|
||||||
.pop_back()
|
|
||||||
.ok_or(Error("non-empty stack expected".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resize(&mut self, new_size: usize, dummy: T) {
|
|
||||||
debug_assert!(new_size <= self.values.len());
|
|
||||||
self.values.resize(new_size, dummy);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,166 +0,0 @@
|
|||||||
use std::sync::{Arc, Weak};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use elements::{ImportSection, ImportEntry, External, Internal};
|
|
||||||
use interpreter::Error;
|
|
||||||
use interpreter::memory::MemoryInstance;
|
|
||||||
use interpreter::module::{ModuleInstanceInterface, ItemIndex, ExportEntryType, FunctionSignature};
|
|
||||||
use interpreter::program::ProgramInstanceEssence;
|
|
||||||
use interpreter::table::TableInstance;
|
|
||||||
use interpreter::variable::{VariableInstance, VariableType};
|
|
||||||
|
|
||||||
/// Module imports.
|
|
||||||
pub struct ModuleImports {
|
|
||||||
/// Program instance.
|
|
||||||
program: Weak<ProgramInstanceEssence>,
|
|
||||||
/// External functions.
|
|
||||||
functions: Vec<usize>,
|
|
||||||
/// External tables.
|
|
||||||
tables: Vec<usize>,
|
|
||||||
/// External memory regions.
|
|
||||||
memory: Vec<usize>,
|
|
||||||
/// External globals.
|
|
||||||
globals: Vec<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ModuleImports {
|
|
||||||
/// Create new imports for given import section.
|
|
||||||
pub fn new(program: Weak<ProgramInstanceEssence>, import_section: Option<&ImportSection>) -> Self {
|
|
||||||
let mut functions = Vec::new();
|
|
||||||
let mut tables = Vec::new();
|
|
||||||
let mut memory = Vec::new();
|
|
||||||
let mut globals = Vec::new();
|
|
||||||
if let Some(import_section) = import_section {
|
|
||||||
for (import_index, import_entry) in import_section.entries().iter().enumerate() {
|
|
||||||
match import_entry.external() {
|
|
||||||
&External::Function(_) => functions.push(import_index),
|
|
||||||
&External::Table(_) => tables.push(import_index),
|
|
||||||
&External::Memory(_) => memory.push(import_index),
|
|
||||||
&External::Global(_) => globals.push(import_index),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ModuleImports {
|
|
||||||
program: program,
|
|
||||||
functions: functions,
|
|
||||||
tables: tables,
|
|
||||||
memory: memory,
|
|
||||||
globals: globals,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Number of imported tables.
|
|
||||||
pub fn tables_len(&self) -> usize {
|
|
||||||
self.tables.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Number of imported memory regions.
|
|
||||||
pub fn memory_regions_len(&self) -> usize {
|
|
||||||
self.memory.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse function index.
|
|
||||||
pub fn parse_function_index(&self, index: ItemIndex) -> ItemIndex {
|
|
||||||
match index {
|
|
||||||
ItemIndex::IndexSpace(index) => match index.checked_sub(self.functions.len() as u32) {
|
|
||||||
Some(index) => ItemIndex::Internal(index),
|
|
||||||
None => ItemIndex::External(self.functions[index as usize] as u32),
|
|
||||||
},
|
|
||||||
index @ _ => index,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse table index.
|
|
||||||
pub fn parse_table_index(&self, index: ItemIndex) -> ItemIndex {
|
|
||||||
match index {
|
|
||||||
ItemIndex::IndexSpace(index) => match index.checked_sub(self.tables.len() as u32) {
|
|
||||||
Some(index) => ItemIndex::Internal(index),
|
|
||||||
None => ItemIndex::External(self.tables[index as usize] as u32),
|
|
||||||
},
|
|
||||||
index @ _ => index,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse memory index.
|
|
||||||
pub fn parse_memory_index(&self, index: ItemIndex) -> ItemIndex {
|
|
||||||
match index {
|
|
||||||
ItemIndex::IndexSpace(index) => match index.checked_sub(self.memory.len() as u32) {
|
|
||||||
Some(index) => ItemIndex::Internal(index),
|
|
||||||
None => ItemIndex::External(self.memory[index as usize] as u32),
|
|
||||||
},
|
|
||||||
index @ _ => index,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse global index.
|
|
||||||
pub fn parse_global_index(&self, index: ItemIndex) -> ItemIndex {
|
|
||||||
match index {
|
|
||||||
ItemIndex::IndexSpace(index) => match index.checked_sub(self.globals.len() as u32) {
|
|
||||||
Some(index) => ItemIndex::Internal(index),
|
|
||||||
None => ItemIndex::External(self.globals[index as usize] as u32),
|
|
||||||
},
|
|
||||||
index @ _ => index,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get module reference.
|
|
||||||
pub fn module<'a>(&self, externals: Option<&'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>>, name: &str) -> Result<Arc<ModuleInstanceInterface + 'a>, Error> {
|
|
||||||
if let Some(externals) = externals {
|
|
||||||
if let Some(module) = externals.get(name).cloned() {
|
|
||||||
return Ok(module);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.program
|
|
||||||
.upgrade()
|
|
||||||
.ok_or(Error::Program("program unloaded".into()))
|
|
||||||
.and_then(|p| p.module(name).ok_or(Error::Program(format!("module {} is not loaded", name))))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get function index.
|
|
||||||
pub fn function<'a>(&self, externals: Option<&'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>>, import: &ImportEntry, required_type: Option<FunctionSignature>) -> Result<u32, Error> {
|
|
||||||
let (_, export) = self.external_export(externals, import, &required_type.map(|ft| ExportEntryType::Function(ft)).unwrap_or(ExportEntryType::Any))?;
|
|
||||||
if let Internal::Function(external_index) = export {
|
|
||||||
return Ok(external_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error::Program(format!("wrong import {} from module {} (expecting function)", import.field(), import.module())))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get table reference.
|
|
||||||
pub fn table<'a>(&self, externals: Option<&'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>>, import: &ImportEntry) -> Result<Arc<TableInstance>, Error> {
|
|
||||||
let (module, export) = self.external_export(externals, import, &ExportEntryType::Any)?;
|
|
||||||
if let Internal::Table(external_index) = export {
|
|
||||||
return module.table(ItemIndex::Internal(external_index));
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error::Program(format!("wrong import {} from module {} (expecting table)", import.field(), import.module())))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get memory reference.
|
|
||||||
pub fn memory<'a>(&self, externals: Option<&'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>>, import: &ImportEntry) -> Result<Arc<MemoryInstance>, Error> {
|
|
||||||
let (module, export) = self.external_export(externals, import, &ExportEntryType::Any)?;
|
|
||||||
if let Internal::Memory(external_index) = export {
|
|
||||||
return module.memory(ItemIndex::Internal(external_index));
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error::Program(format!("wrong import {} from module {} (expecting memory)", import.field(), import.module())))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get global reference.
|
|
||||||
pub fn global<'a>(&self, externals: Option<&'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>>, import: &ImportEntry, required_type: Option<VariableType>) -> Result<Arc<VariableInstance>, Error> {
|
|
||||||
let (module, export) = self.external_export(externals, import, &required_type.clone().map(|rt| ExportEntryType::Global(rt)).unwrap_or(ExportEntryType::Any))?;
|
|
||||||
if let Internal::Global(external_index) = export {
|
|
||||||
return module.global(ItemIndex::Internal(external_index), required_type, externals);
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error::Program(format!("wrong import {} from module {} (expecting global)", import.field(), import.module())))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn external_export<'a>(&self, externals: Option<&'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>>, import: &ImportEntry, required_type: &ExportEntryType) -> Result<(Arc<ModuleInstanceInterface + 'a>, Internal), Error> {
|
|
||||||
self.module(externals, import.module())
|
|
||||||
.and_then(|m|
|
|
||||||
m.export_entry(import.field(), required_type)
|
|
||||||
.map(|e| (m, e)))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,280 +0,0 @@
|
|||||||
use std::u32;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::ops::Range;
|
|
||||||
use std::cmp;
|
|
||||||
use parking_lot::RwLock;
|
|
||||||
use elements::{MemoryType, ResizableLimits};
|
|
||||||
use interpreter::Error;
|
|
||||||
use interpreter::module::check_limits;
|
|
||||||
|
|
||||||
/// Linear memory page size.
|
|
||||||
pub const LINEAR_MEMORY_PAGE_SIZE: u32 = 65536;
|
|
||||||
/// Maximal number of pages.
|
|
||||||
const LINEAR_MEMORY_MAX_PAGES: u32 = 65536;
|
|
||||||
|
|
||||||
/// Linear memory instance.
|
|
||||||
pub struct MemoryInstance {
|
|
||||||
/// Memofy limits.
|
|
||||||
limits: ResizableLimits,
|
|
||||||
/// Linear memory buffer.
|
|
||||||
buffer: RwLock<Vec<u8>>,
|
|
||||||
/// Maximum buffer size.
|
|
||||||
maximum_size: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CheckedRegion<'a, B: 'a> where B: ::std::ops::Deref<Target=Vec<u8>> {
|
|
||||||
buffer: &'a B,
|
|
||||||
offset: usize,
|
|
||||||
size: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, B: 'a> CheckedRegion<'a, B> where B: ::std::ops::Deref<Target=Vec<u8>> {
|
|
||||||
fn range(&self) -> Range<usize> {
|
|
||||||
self.offset..self.offset+self.size
|
|
||||||
}
|
|
||||||
|
|
||||||
fn slice(&self) -> &[u8] {
|
|
||||||
&self.buffer[self.range()]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn intersects(&self, other: &Self) -> bool {
|
|
||||||
let low = cmp::max(self.offset, other.offset);
|
|
||||||
let high = cmp::min(self.offset + self.size, other.offset + other.size);
|
|
||||||
|
|
||||||
low < high
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MemoryInstance {
|
|
||||||
/// Create new linear memory instance.
|
|
||||||
pub fn new(memory_type: &MemoryType) -> Result<Arc<Self>, Error> {
|
|
||||||
check_limits(memory_type.limits())?;
|
|
||||||
|
|
||||||
let maximum_size = match memory_type.limits().maximum() {
|
|
||||||
Some(maximum_pages) if maximum_pages > LINEAR_MEMORY_MAX_PAGES =>
|
|
||||||
return Err(Error::Memory(format!("maximum memory size must be at most {} pages", LINEAR_MEMORY_MAX_PAGES))),
|
|
||||||
Some(maximum_pages) => maximum_pages.saturating_mul(LINEAR_MEMORY_PAGE_SIZE),
|
|
||||||
None => u32::MAX,
|
|
||||||
};
|
|
||||||
let initial_size = calculate_memory_size(0, memory_type.limits().initial(), maximum_size)
|
|
||||||
.ok_or(Error::Memory(format!("initial memory size must be at most {} pages", LINEAR_MEMORY_MAX_PAGES)))?;
|
|
||||||
|
|
||||||
let memory = MemoryInstance {
|
|
||||||
limits: memory_type.limits().clone(),
|
|
||||||
buffer: RwLock::new(vec![0; initial_size as usize]),
|
|
||||||
maximum_size: maximum_size,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Arc::new(memory))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return linear memory limits.
|
|
||||||
pub fn limits(&self) -> &ResizableLimits {
|
|
||||||
&self.limits
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return linear memory size (in pages).
|
|
||||||
pub fn size(&self) -> u32 {
|
|
||||||
self.buffer.read().len() as u32 / LINEAR_MEMORY_PAGE_SIZE
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get data at given offset.
|
|
||||||
pub fn get(&self, offset: u32, size: usize) -> Result<Vec<u8>, Error> {
|
|
||||||
let buffer = self.buffer.read();
|
|
||||||
let region = self.checked_region(&buffer, offset as usize, size)?;
|
|
||||||
|
|
||||||
Ok(region.slice().to_vec())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write memory slice into another slice
|
|
||||||
pub fn get_into(&self, offset: u32, target: &mut [u8]) -> Result<(), Error> {
|
|
||||||
let buffer = self.buffer.read();
|
|
||||||
let region = self.checked_region(&buffer, offset as usize, target.len())?;
|
|
||||||
|
|
||||||
target.copy_from_slice(region.slice());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set data at given offset.
|
|
||||||
pub fn set(&self, offset: u32, value: &[u8]) -> Result<(), Error> {
|
|
||||||
let mut buffer = self.buffer.write();
|
|
||||||
let range = self.checked_region(&buffer, offset as usize, value.len())?.range();
|
|
||||||
|
|
||||||
buffer[range].copy_from_slice(value);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Increases the size of the linear memory by given number of pages.
|
|
||||||
/// Returns -1 if allocation fails or previous memory size, if succeeds.
|
|
||||||
pub fn grow(&self, pages: u32) -> Result<u32, Error> {
|
|
||||||
let mut buffer = self.buffer.write();
|
|
||||||
let old_size = buffer.len() as u32;
|
|
||||||
match calculate_memory_size(old_size, pages, self.maximum_size) {
|
|
||||||
None => Ok(u32::MAX),
|
|
||||||
Some(new_size) => {
|
|
||||||
buffer.resize(new_size as usize, 0);
|
|
||||||
Ok(old_size / LINEAR_MEMORY_PAGE_SIZE)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn checked_region<'a, B>(&self, buffer: &'a B, offset: usize, size: usize) -> Result<CheckedRegion<'a, B>, Error>
|
|
||||||
where B: ::std::ops::Deref<Target=Vec<u8>>
|
|
||||||
{
|
|
||||||
let end = offset.checked_add(size)
|
|
||||||
.ok_or(Error::Memory(format!("trying to access memory block of size {} from offset {}", size, offset)))?;
|
|
||||||
|
|
||||||
if end > buffer.len() {
|
|
||||||
return Err(Error::Memory(format!("trying to access region [{}..{}] in memory [0..{}]", offset, end, buffer.len())));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(CheckedRegion {
|
|
||||||
buffer: buffer,
|
|
||||||
offset: offset,
|
|
||||||
size: size,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copy memory region. Semantically equivalent to `memmove`.
|
|
||||||
pub fn copy(&self, src_offset: usize, dst_offset: usize, len: usize) -> Result<(), Error> {
|
|
||||||
let buffer = self.buffer.write();
|
|
||||||
|
|
||||||
let read_region = self.checked_region(&buffer, src_offset, len)?;
|
|
||||||
let write_region = self.checked_region(&buffer, dst_offset, len)?;
|
|
||||||
|
|
||||||
unsafe { ::std::ptr::copy(
|
|
||||||
buffer[read_region.range()].as_ptr(),
|
|
||||||
buffer[write_region.range()].as_ptr() as *mut _,
|
|
||||||
len,
|
|
||||||
)}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copy memory region, non-overlapping version. Semantically equivalent to `memcpy`,
|
|
||||||
/// but returns Error if source overlaping with destination.
|
|
||||||
pub fn copy_nonoverlapping(&self, src_offset: usize, dst_offset: usize, len: usize) -> Result<(), Error> {
|
|
||||||
let buffer = self.buffer.write();
|
|
||||||
|
|
||||||
let read_region = self.checked_region(&buffer, src_offset, len)?;
|
|
||||||
let write_region = self.checked_region(&buffer, dst_offset, len)?;
|
|
||||||
|
|
||||||
if read_region.intersects(&write_region) {
|
|
||||||
return Err(Error::Memory(format!("non-overlapping copy is used for overlapping regions")))
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe { ::std::ptr::copy_nonoverlapping(
|
|
||||||
buffer[read_region.range()].as_ptr(),
|
|
||||||
buffer[write_region.range()].as_ptr() as *mut _,
|
|
||||||
len,
|
|
||||||
)}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear memory region with a specified value. Semantically equivalent to `memset`.
|
|
||||||
pub fn clear(&self, offset: usize, new_val: u8, len: usize) -> Result<(), Error> {
|
|
||||||
let mut buffer = self.buffer.write();
|
|
||||||
|
|
||||||
let range = self.checked_region(&buffer, offset, len)?.range();
|
|
||||||
for val in &mut buffer[range] { *val = new_val }
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Zero memory region
|
|
||||||
pub fn zero(&self, offset: usize, len: usize) -> Result<(), Error> {
|
|
||||||
self.clear(offset, 0, len)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_memory_size(old_size: u32, additional_pages: u32, maximum_size: u32) -> Option<u32> {
|
|
||||||
additional_pages
|
|
||||||
.checked_mul(LINEAR_MEMORY_PAGE_SIZE)
|
|
||||||
.and_then(|size| size.checked_add(old_size))
|
|
||||||
.and_then(|size| if size > maximum_size {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(size)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
|
|
||||||
use super::MemoryInstance;
|
|
||||||
use interpreter::Error;
|
|
||||||
use elements::MemoryType;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
fn create_memory(initial_content: &[u8]) -> Arc<MemoryInstance> {
|
|
||||||
let mem = MemoryInstance::new(&MemoryType::new(1, Some(1)))
|
|
||||||
.expect("MemoryInstance created successfuly");
|
|
||||||
mem.set(0, initial_content).expect("Successful initialize the memory");
|
|
||||||
mem
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn copy_overlaps_1() {
|
|
||||||
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
||||||
mem.copy(0, 4, 6).expect("Successfully copy the elements");
|
|
||||||
let result = mem.get(0, 10).expect("Successfully retrieve the result");
|
|
||||||
assert_eq!(result, &[0, 1, 2, 3, 0, 1, 2, 3, 4, 5]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn copy_overlaps_2() {
|
|
||||||
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
||||||
mem.copy(4, 0, 6).expect("Successfully copy the elements");
|
|
||||||
let result = mem.get(0, 10).expect("Successfully retrieve the result");
|
|
||||||
assert_eq!(result, &[4, 5, 6, 7, 8, 9, 6, 7, 8, 9]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn copy_nonoverlapping() {
|
|
||||||
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
||||||
mem.copy_nonoverlapping(0, 10, 10).expect("Successfully copy the elements");
|
|
||||||
let result = mem.get(10, 10).expect("Successfully retrieve the result");
|
|
||||||
assert_eq!(result, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn copy_nonoverlapping_overlaps_1() {
|
|
||||||
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
||||||
let result = mem.copy_nonoverlapping(0, 4, 6);
|
|
||||||
match result {
|
|
||||||
Err(Error::Memory(_)) => {},
|
|
||||||
_ => panic!("Expected Error::Memory(_) result, but got {:?}", result),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn copy_nonoverlapping_overlaps_2() {
|
|
||||||
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
||||||
let result = mem.copy_nonoverlapping(4, 0, 6);
|
|
||||||
match result {
|
|
||||||
Err(Error::Memory(_)) => {},
|
|
||||||
_ => panic!("Expected Error::Memory(_), but got {:?}", result),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn clear() {
|
|
||||||
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
||||||
mem.clear(0, 0x4A, 10).expect("To successfully clear the memory");
|
|
||||||
let result = mem.get(0, 10).expect("To successfully retrieve the result");
|
|
||||||
assert_eq!(result, &[0x4A; 10]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn get_into() {
|
|
||||||
let mem = MemoryInstance::new(&MemoryType::new(1, None)).expect("memory instance creation should not fail");
|
|
||||||
mem.set(6, &[13, 17, 129]).expect("memory set should not fail");
|
|
||||||
|
|
||||||
let mut data = [0u8; 2];
|
|
||||||
mem.get_into(7, &mut data[..]).expect("get_into should not fail");
|
|
||||||
|
|
||||||
assert_eq!(data, [17, 129]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,181 +0,0 @@
|
|||||||
//! WebAssembly interpreter module.
|
|
||||||
|
|
||||||
#![deprecated(since = "0.23", note = "Use wasmi crate to interpret wasm")]
|
|
||||||
|
|
||||||
use std::any::TypeId;
|
|
||||||
use std::error;
|
|
||||||
use std::fmt;
|
|
||||||
use validation;
|
|
||||||
use common;
|
|
||||||
|
|
||||||
/// Custom user error.
|
|
||||||
pub trait UserError: 'static + ::std::fmt::Display + ::std::fmt::Debug {
|
|
||||||
#[doc(hidden)]
|
|
||||||
fn __private_get_type_id__(&self) -> TypeId {
|
|
||||||
TypeId::of::<Self>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UserError {
|
|
||||||
/// Attempt to downcast this `UserError` to a concrete type by reference.
|
|
||||||
pub fn downcast_ref<T: UserError>(&self) -> Option<&T> {
|
|
||||||
if self.__private_get_type_id__() == TypeId::of::<T>() {
|
|
||||||
unsafe { Some(&*(self as *const UserError as *const T)) }
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempt to downcast this `UserError` to a concrete type by mutable
|
|
||||||
/// reference.
|
|
||||||
pub fn downcast_mut<T: UserError>(&mut self) -> Option<&mut T> {
|
|
||||||
if self.__private_get_type_id__() == TypeId::of::<T>() {
|
|
||||||
unsafe { Some(&mut *(self as *mut UserError as *mut T)) }
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal interpreter error.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Program-level error.
|
|
||||||
Program(String),
|
|
||||||
/// Validation error.
|
|
||||||
Validation(String),
|
|
||||||
/// Initialization error.
|
|
||||||
Initialization(String),
|
|
||||||
/// Function-level error.
|
|
||||||
Function(String),
|
|
||||||
/// Table-level error.
|
|
||||||
Table(String),
|
|
||||||
/// Memory-level error.
|
|
||||||
Memory(String),
|
|
||||||
/// Variable-level error.
|
|
||||||
Variable(String),
|
|
||||||
/// Global-level error.
|
|
||||||
Global(String),
|
|
||||||
/// Local-level error.
|
|
||||||
Local(String),
|
|
||||||
/// Stack-level error.
|
|
||||||
Stack(String),
|
|
||||||
/// Value-level error.
|
|
||||||
Value(String),
|
|
||||||
/// Interpreter (code) error.
|
|
||||||
Interpreter(String),
|
|
||||||
/// Native module error.
|
|
||||||
Native(String),
|
|
||||||
/// Trap.
|
|
||||||
Trap(String),
|
|
||||||
/// Custom user error.
|
|
||||||
User(Box<UserError>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<String> for Error {
|
|
||||||
fn into(self) -> String {
|
|
||||||
match self {
|
|
||||||
Error::Program(s) => s,
|
|
||||||
Error::Validation(s) => s,
|
|
||||||
Error::Initialization(s) => s,
|
|
||||||
Error::Function(s) => s,
|
|
||||||
Error::Table(s) => s,
|
|
||||||
Error::Memory(s) => s,
|
|
||||||
Error::Variable(s) => s,
|
|
||||||
Error::Global(s) => s,
|
|
||||||
Error::Local(s) => s,
|
|
||||||
Error::Stack(s) => s,
|
|
||||||
Error::Interpreter(s) => s,
|
|
||||||
Error::Value(s) => s,
|
|
||||||
Error::Native(s) => s,
|
|
||||||
Error::Trap(s) => format!("trap: {}", s),
|
|
||||||
Error::User(e) => format!("user: {}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match *self {
|
|
||||||
Error::Program(ref s) => write!(f, "Program: {}", s),
|
|
||||||
Error::Validation(ref s) => write!(f, "Validation: {}", s),
|
|
||||||
Error::Initialization(ref s) => write!(f, "Initialization: {}", s),
|
|
||||||
Error::Function(ref s) => write!(f, "Function: {}", s),
|
|
||||||
Error::Table(ref s) => write!(f, "Table: {}", s),
|
|
||||||
Error::Memory(ref s) => write!(f, "Memory: {}", s),
|
|
||||||
Error::Variable(ref s) => write!(f, "Variable: {}", s),
|
|
||||||
Error::Global(ref s) => write!(f, "Global: {}", s),
|
|
||||||
Error::Local(ref s) => write!(f, "Local: {}", s),
|
|
||||||
Error::Stack(ref s) => write!(f, "Stack: {}", s),
|
|
||||||
Error::Interpreter(ref s) => write!(f, "Interpreter: {}", s),
|
|
||||||
Error::Value(ref s) => write!(f, "Value: {}", s),
|
|
||||||
Error::Native(ref s) => write!(f, "Native: {}", s),
|
|
||||||
Error::Trap(ref s) => write!(f, "Trap: {}", s),
|
|
||||||
Error::User(ref e) => write!(f, "User: {}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl error::Error for Error {
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
match *self {
|
|
||||||
Error::Program(ref s) => s,
|
|
||||||
Error::Validation(ref s) => s,
|
|
||||||
Error::Initialization(ref s) => s,
|
|
||||||
Error::Function(ref s) => s,
|
|
||||||
Error::Table(ref s) => s,
|
|
||||||
Error::Memory(ref s) => s,
|
|
||||||
Error::Variable(ref s) => s,
|
|
||||||
Error::Global(ref s) => s,
|
|
||||||
Error::Local(ref s) => s,
|
|
||||||
Error::Stack(ref s) => s,
|
|
||||||
Error::Interpreter(ref s) => s,
|
|
||||||
Error::Value(ref s) => s,
|
|
||||||
Error::Native(ref s) => s,
|
|
||||||
Error::Trap(ref s) => s,
|
|
||||||
Error::User(_) => "User error",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<U> From<U> for Error where U: UserError + Sized {
|
|
||||||
fn from(e: U) -> Self {
|
|
||||||
Error::User(Box::new(e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<validation::Error> for Error {
|
|
||||||
fn from(e: validation::Error) -> Self {
|
|
||||||
Error::Validation(e.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<common::stack::Error> for Error {
|
|
||||||
fn from(e: common::stack::Error) -> Self {
|
|
||||||
Error::Stack(e.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod validator;
|
|
||||||
mod native;
|
|
||||||
mod imports;
|
|
||||||
mod memory;
|
|
||||||
mod module;
|
|
||||||
mod program;
|
|
||||||
mod runner;
|
|
||||||
mod stack;
|
|
||||||
mod table;
|
|
||||||
mod value;
|
|
||||||
mod variable;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
|
|
||||||
pub use self::memory::MemoryInstance;
|
|
||||||
pub use self::module::{ModuleInstance, ModuleInstanceInterface,
|
|
||||||
ItemIndex, ExportEntryType, CallerContext, ExecutionParams, FunctionSignature};
|
|
||||||
pub use self::table::TableInstance;
|
|
||||||
pub use self::program::ProgramInstance;
|
|
||||||
pub use self::value::RuntimeValue;
|
|
||||||
pub use self::variable::{VariableInstance, VariableType, ExternalVariableValue};
|
|
||||||
pub use self::native::{native_module, UserDefinedElements, UserFunctionExecutor, UserFunctionDescriptor};
|
|
@ -1,746 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::iter::repeat;
|
|
||||||
use std::sync::{Arc, Weak};
|
|
||||||
use std::fmt;
|
|
||||||
use elements::{Module, InitExpr, Opcode, Type, FunctionType, Internal, External, ResizableLimits, Local, ValueType, BlockType};
|
|
||||||
use interpreter::Error;
|
|
||||||
use interpreter::native::UserFunctionDescriptor;
|
|
||||||
use interpreter::imports::ModuleImports;
|
|
||||||
use interpreter::memory::MemoryInstance;
|
|
||||||
use interpreter::program::ProgramInstanceEssence;
|
|
||||||
use interpreter::runner::{Interpreter, FunctionContext, prepare_function_args};
|
|
||||||
use interpreter::table::TableInstance;
|
|
||||||
use interpreter::validator::{Validator, FunctionValidationContext};
|
|
||||||
use interpreter::value::{RuntimeValue, TryInto};
|
|
||||||
use interpreter::variable::{VariableInstance, VariableType};
|
|
||||||
use common::stack::StackWithLimit;
|
|
||||||
|
|
||||||
/// Maximum number of entries in value stack.
|
|
||||||
const DEFAULT_VALUE_STACK_LIMIT: usize = 16384;
|
|
||||||
/// Maximum number of entries in frame stack.
|
|
||||||
const DEFAULT_FRAME_STACK_LIMIT: usize = 1024;
|
|
||||||
|
|
||||||
/// Execution context.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ExecutionParams<'a> {
|
|
||||||
/// Arguments.
|
|
||||||
pub args: Vec<RuntimeValue>,
|
|
||||||
/// Execution-local external modules.
|
|
||||||
pub externals: HashMap<String, Arc<ModuleInstanceInterface + 'a>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Export type.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum ExportEntryType<'a> {
|
|
||||||
/// Any type.
|
|
||||||
Any,
|
|
||||||
/// Type of function.
|
|
||||||
Function(FunctionSignature<'a>),
|
|
||||||
/// Type of global.
|
|
||||||
Global(VariableType),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Function signature.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum FunctionSignature<'a> {
|
|
||||||
/// Module function reference.
|
|
||||||
Module(&'a FunctionType),
|
|
||||||
/// Native user function refrence.
|
|
||||||
User(&'a UserFunctionDescriptor),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Module instance API.
|
|
||||||
pub trait ModuleInstanceInterface {
|
|
||||||
/// Execute function with the given index.
|
|
||||||
fn execute_index(&self, index: u32, params: ExecutionParams) -> Result<Option<RuntimeValue>, Error>;
|
|
||||||
/// Execute function with the given export name.
|
|
||||||
fn execute_export(&self, name: &str, params: ExecutionParams) -> Result<Option<RuntimeValue>, Error>;
|
|
||||||
/// Get export entry.
|
|
||||||
fn export_entry<'a>(&self, name: &str, required_type: &ExportEntryType) -> Result<Internal, Error>;
|
|
||||||
/// Get table reference.
|
|
||||||
fn table(&self, index: ItemIndex) -> Result<Arc<TableInstance>, Error>;
|
|
||||||
/// Get memory reference.
|
|
||||||
fn memory(&self, index: ItemIndex) -> Result<Arc<MemoryInstance>, Error>;
|
|
||||||
/// Get global reference.
|
|
||||||
fn global<'a>(&self, index: ItemIndex, variable_type: Option<VariableType>, externals: Option<&'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>>) -> Result<Arc<VariableInstance>, Error>;
|
|
||||||
/// Get function type for given function index.
|
|
||||||
fn function_type(&self, function_index: ItemIndex) -> Result<FunctionSignature, Error>;
|
|
||||||
/// Get function type for given function index.
|
|
||||||
fn function_type_by_index(&self, type_index: u32) -> Result<FunctionSignature, Error>;
|
|
||||||
/// Get function reference.
|
|
||||||
fn function_reference<'a>(&self, index: ItemIndex, externals: Option<&'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>>) -> Result<InternalFunctionReference<'a>, Error>;
|
|
||||||
/// Get function indirect reference.
|
|
||||||
fn function_reference_indirect<'a>(&self, table_idx: u32, type_idx: u32, func_idx: u32, externals: Option<&'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>>) -> Result<InternalFunctionReference<'a>, Error>;
|
|
||||||
/// Get internal function for interpretation.
|
|
||||||
fn function_body<'a>(&'a self, internal_index: u32) -> Result<Option<InternalFunction<'a>>, Error>;
|
|
||||||
/// Call function with given internal index.
|
|
||||||
fn call_internal_function(&self, outer: CallerContext, index: u32) -> Result<Option<RuntimeValue>, Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Item index in items index space.
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum ItemIndex {
|
|
||||||
/// Index in index space.
|
|
||||||
IndexSpace(u32),
|
|
||||||
/// Internal item index (i.e. index of item in items section).
|
|
||||||
Internal(u32),
|
|
||||||
/// External module item index (i.e. index of item in the import section).
|
|
||||||
External(u32),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Module instance.
|
|
||||||
pub struct ModuleInstance {
|
|
||||||
/// Module name.
|
|
||||||
name: String,
|
|
||||||
/// Module.
|
|
||||||
module: Module,
|
|
||||||
/// Function labels.
|
|
||||||
functions_labels: HashMap<u32, HashMap<usize, usize>>,
|
|
||||||
/// Module imports.
|
|
||||||
imports: ModuleImports,
|
|
||||||
/// Module exports.
|
|
||||||
exports: HashMap<String, Vec<Internal>>,
|
|
||||||
/// Tables.
|
|
||||||
tables: Vec<Arc<TableInstance>>,
|
|
||||||
/// Linear memory regions.
|
|
||||||
memory: Vec<Arc<MemoryInstance>>,
|
|
||||||
/// Globals.
|
|
||||||
globals: Vec<Arc<VariableInstance>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Caller context.
|
|
||||||
pub struct CallerContext<'a> {
|
|
||||||
/// Value stack limit
|
|
||||||
pub value_stack_limit: usize,
|
|
||||||
/// Frame stack limit
|
|
||||||
pub frame_stack_limit: usize,
|
|
||||||
/// Stack of the input parameters
|
|
||||||
pub value_stack: &'a mut StackWithLimit<RuntimeValue>,
|
|
||||||
/// Execution-local external modules.
|
|
||||||
pub externals: &'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal function reference.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct InternalFunctionReference<'a> {
|
|
||||||
/// Module reference.
|
|
||||||
pub module: Arc<ModuleInstanceInterface + 'a>,
|
|
||||||
/// Internal function index.
|
|
||||||
pub internal_index: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> fmt::Debug for InternalFunctionReference<'a> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "InternalFunctionReference")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal function ready for interpretation.
|
|
||||||
pub struct InternalFunction<'a> {
|
|
||||||
/// Function locals.
|
|
||||||
pub locals: &'a [Local],
|
|
||||||
/// Function body.
|
|
||||||
pub body: &'a [Opcode],
|
|
||||||
/// Function labels.
|
|
||||||
pub labels: &'a HashMap<usize, usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ExecutionParams<'a> {
|
|
||||||
/// Create new execution params with given externa; module override.
|
|
||||||
pub fn with_external(name: String, module: Arc<ModuleInstanceInterface + 'a>) -> Self {
|
|
||||||
let mut externals = HashMap::new();
|
|
||||||
externals.insert(name, module);
|
|
||||||
ExecutionParams {
|
|
||||||
args: Vec::new(),
|
|
||||||
externals: externals,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add argument.
|
|
||||||
pub fn add_argument(mut self, arg: RuntimeValue) -> Self {
|
|
||||||
self.args.push(arg);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Default for ExecutionParams<'a> {
|
|
||||||
fn default() -> Self {
|
|
||||||
ExecutionParams {
|
|
||||||
args: Vec::default(),
|
|
||||||
externals: HashMap::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<Vec<RuntimeValue>> for ExecutionParams<'a> {
|
|
||||||
fn from(args: Vec<RuntimeValue>) -> ExecutionParams<'a> {
|
|
||||||
ExecutionParams {
|
|
||||||
args: args,
|
|
||||||
externals: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ModuleInstance {
|
|
||||||
/// Instantiate given module within program context.
|
|
||||||
pub fn new<'a>(program: Weak<ProgramInstanceEssence>, name: String, module: Module) -> Result<Self, Error> {
|
|
||||||
// load entries from import section
|
|
||||||
let imports = ModuleImports::new(program, module.import_section());
|
|
||||||
|
|
||||||
// instantiate linear memory regions, if any
|
|
||||||
let memory = match module.memory_section() {
|
|
||||||
Some(memory_section) => memory_section.entries()
|
|
||||||
.iter()
|
|
||||||
.map(MemoryInstance::new)
|
|
||||||
.collect::<Result<Vec<_>, _>>()?,
|
|
||||||
None => Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// instantiate tables, if any
|
|
||||||
let tables = match module.table_section() {
|
|
||||||
Some(table_section) => table_section.entries()
|
|
||||||
.iter()
|
|
||||||
.map(|tt| TableInstance::new(tt))
|
|
||||||
.collect::<Result<Vec<_>, _>>()?,
|
|
||||||
None => Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// instantiate globals, if any
|
|
||||||
let globals = match module.global_section() {
|
|
||||||
Some(global_section) => global_section.entries()
|
|
||||||
.iter()
|
|
||||||
.map(|g| {
|
|
||||||
get_initializer(g.init_expr(), &module, &imports, g.global_type().content_type().into())
|
|
||||||
.map_err(|e| Error::Initialization(e.into()))
|
|
||||||
.and_then(|v| VariableInstance::new_global(g.global_type(), v).map(Arc::new))
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>, _>>()?,
|
|
||||||
None => Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(ModuleInstance {
|
|
||||||
name: name,
|
|
||||||
module: module,
|
|
||||||
imports: imports,
|
|
||||||
exports: HashMap::new(),
|
|
||||||
functions_labels: HashMap::new(),
|
|
||||||
memory: memory,
|
|
||||||
tables: tables,
|
|
||||||
globals: globals,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run instantiation-time procedures (validation). Module is not completely validated until this call.
|
|
||||||
pub fn instantiate<'a>(&mut self, externals: Option<&'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>>) -> Result<(), Error> {
|
|
||||||
::validation::validate_module(&self.module)?;
|
|
||||||
|
|
||||||
// validate start section
|
|
||||||
if let Some(start_function) = self.module.start_section() {
|
|
||||||
let func_type_index = self.require_function(ItemIndex::IndexSpace(start_function))?;
|
|
||||||
let func_type = self.function_type_by_index(func_type_index)?;
|
|
||||||
if func_type.return_type() != None || func_type.params().len() != 0 {
|
|
||||||
return Err(Error::Validation("start function expected to have type [] -> []".into()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate export section
|
|
||||||
if let Some(export_section) = self.module.export_section() {
|
|
||||||
for export in export_section.entries() {
|
|
||||||
match export.internal() {
|
|
||||||
&Internal::Function(function_index) => {
|
|
||||||
self.require_function(ItemIndex::IndexSpace(function_index)).map(|_| ())?;
|
|
||||||
self.exports.entry(export.field().into()).or_insert_with(Default::default).push(Internal::Function(function_index));
|
|
||||||
},
|
|
||||||
&Internal::Global(global_index) => {
|
|
||||||
self.global(ItemIndex::IndexSpace(global_index), None, externals)
|
|
||||||
.and_then(|g| if g.is_mutable() {
|
|
||||||
Err(Error::Validation(format!("trying to export mutable global {}", export.field())))
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
self.exports.entry(export.field().into()).or_insert_with(Default::default).push(Internal::Global(global_index));
|
|
||||||
},
|
|
||||||
&Internal::Memory(memory_index) => {
|
|
||||||
self.memory(ItemIndex::IndexSpace(memory_index)).map(|_| ())?;
|
|
||||||
self.exports.entry(export.field().into()).or_insert_with(Default::default).push(Internal::Memory(memory_index));
|
|
||||||
},
|
|
||||||
&Internal::Table(table_index) => {
|
|
||||||
self.table(ItemIndex::IndexSpace(table_index)).map(|_| ())?;
|
|
||||||
self.exports.entry(export.field().into()).or_insert_with(Default::default).push(Internal::Table(table_index));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate import section
|
|
||||||
if let Some(import_section) = self.module.import_section() {
|
|
||||||
for import in import_section.entries() {
|
|
||||||
match import.external() {
|
|
||||||
// for functions we need to check if function type matches in both modules
|
|
||||||
&External::Function(ref function_type_index) => {
|
|
||||||
// External::Function points to function type in type section in this module
|
|
||||||
let import_function_type = self.function_type_by_index(*function_type_index)?;
|
|
||||||
|
|
||||||
// get export entry in external module
|
|
||||||
let external_module = self.imports.module(externals, import.module())?;
|
|
||||||
let export_entry = external_module.export_entry(import.field(), &ExportEntryType::Function(import_function_type.clone()))?;
|
|
||||||
|
|
||||||
// export entry points to function in function index space
|
|
||||||
// and Internal::Function points to type in type section
|
|
||||||
match export_entry {
|
|
||||||
Internal::Function(function_index) => {
|
|
||||||
external_module.function_type(ItemIndex::IndexSpace(function_index))?
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(Error::Validation(format!(
|
|
||||||
"Export with name {} from module {} is not a function",
|
|
||||||
import.field(),
|
|
||||||
import.module()
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
&External::Global(ref global_type) => if global_type.is_mutable() {
|
|
||||||
return Err(Error::Validation(format!("trying to import mutable global {}", import.field())));
|
|
||||||
} else {
|
|
||||||
self.imports.global(externals, import, Some(global_type.content_type().into()))?;
|
|
||||||
},
|
|
||||||
&External::Memory(ref memory_type) => {
|
|
||||||
let import_limits = memory_type.limits();
|
|
||||||
check_limits(import_limits)?;
|
|
||||||
|
|
||||||
let memory = self.imports.memory(externals, import)?;
|
|
||||||
let memory_limits = memory.limits();
|
|
||||||
|
|
||||||
// a linear-memory import's minimum length is required to be at most the imported linear memory's minimum length.
|
|
||||||
if import_limits.initial() > memory_limits.initial() {
|
|
||||||
return Err(Error::Validation(format!("trying to import memory with initial={} and import.initial={}", memory_limits.initial(), import_limits.initial())));
|
|
||||||
}
|
|
||||||
|
|
||||||
// not working because of wabt tests:
|
|
||||||
// a linear-memory import is required to have a maximum length if the imported linear memory has a maximum length.
|
|
||||||
|
|
||||||
// if present, a linear-memory import's maximum length is required to be at least the imported linear memory's maximum length.
|
|
||||||
match (memory_limits.maximum(), import_limits.maximum()) {
|
|
||||||
(Some(ml), Some(il)) if il < ml =>
|
|
||||||
return Err(Error::Validation(format!("trying to import memory with maximum={} and import.maximum={}", ml, il))),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
&External::Table(ref table_type) => {
|
|
||||||
let import_limits = table_type.limits();
|
|
||||||
check_limits(import_limits)?;
|
|
||||||
|
|
||||||
let table = self.imports.table(externals, import)?;
|
|
||||||
let table_limits = table.limits();
|
|
||||||
|
|
||||||
// a table import's minimum length is required to be at most the imported table's minimum length.
|
|
||||||
if import_limits.initial() > table_limits.initial() {
|
|
||||||
return Err(Error::Validation(format!("trying to import table with initial={} and import.initial={}", table_limits.initial(), import_limits.initial())));
|
|
||||||
}
|
|
||||||
|
|
||||||
// not working because of wabt tests:
|
|
||||||
// a table import is required to have a maximum length if the imported table has a maximum length.
|
|
||||||
|
|
||||||
// if present, a table import's maximum length is required to be at least the imported table's maximum length.
|
|
||||||
match (table_limits.maximum(), import_limits.maximum()) {
|
|
||||||
(Some(ml), Some(il)) if il < ml =>
|
|
||||||
return Err(Error::Validation(format!("trying to import table with maximum={} and import.maximum={}", ml, il))),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// there must be no greater than 1 table in tables index space
|
|
||||||
if self.imports.tables_len() + self.tables.len() > 1 {
|
|
||||||
return Err(Error::Validation(format!("too many tables in index space: {}", self.imports.tables_len() + self.tables.len())));
|
|
||||||
}
|
|
||||||
|
|
||||||
// there must be no greater than 1 memory region in memory regions index space
|
|
||||||
if self.imports.memory_regions_len() + self.memory.len() > 1 {
|
|
||||||
return Err(Error::Validation(format!("too many memory regions in index space: {}", self.imports.memory_regions_len() + self.memory.len())));
|
|
||||||
}
|
|
||||||
|
|
||||||
// for every function section entry there must be corresponding entry in code section and type && vice versa
|
|
||||||
let function_section_len = self.module.function_section().map(|s| s.entries().len()).unwrap_or(0);
|
|
||||||
let code_section_len = self.module.code_section().map(|s| s.bodies().len()).unwrap_or(0);
|
|
||||||
if function_section_len != code_section_len {
|
|
||||||
return Err(Error::Validation(format!("length of function section is {}, while len of code section is {}", function_section_len, code_section_len)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate every function body in user modules
|
|
||||||
if function_section_len != 0 { // tests use invalid code
|
|
||||||
let function_section = self.module.function_section().expect("function_section_len != 0; qed");
|
|
||||||
let code_section = self.module.code_section().expect("function_section_len != 0; function_section_len == code_section_len; qed");
|
|
||||||
// check every function body
|
|
||||||
for (index, function) in function_section.entries().iter().enumerate() {
|
|
||||||
let function_labels = {
|
|
||||||
let function_type = self.function_type_by_index(function.type_ref())?;
|
|
||||||
let function_body = code_section.bodies().get(index as usize).ok_or(Error::Validation(format!("Missing body for function {}", index)))?;
|
|
||||||
let mut locals = function_type.params().to_vec();
|
|
||||||
locals.extend(function_body.locals().iter().flat_map(|l| repeat(l.value_type()).take(l.count() as usize)));
|
|
||||||
|
|
||||||
let mut context = FunctionValidationContext::new(
|
|
||||||
self,
|
|
||||||
externals,
|
|
||||||
&locals,
|
|
||||||
DEFAULT_VALUE_STACK_LIMIT,
|
|
||||||
DEFAULT_FRAME_STACK_LIMIT,
|
|
||||||
function_type.clone());
|
|
||||||
|
|
||||||
let block_type = function_type.return_type().map(BlockType::Value).unwrap_or(BlockType::NoResult);
|
|
||||||
Validator::validate_function(&mut context, block_type, function_body.code().elements())
|
|
||||||
.map_err(|e| {
|
|
||||||
if let Error::Validation(msg) = e {
|
|
||||||
Error::Validation(format!("Function #{} validation error: {}", index, msg))
|
|
||||||
} else {
|
|
||||||
e
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
context.function_labels()
|
|
||||||
};
|
|
||||||
self.functions_labels.insert(index as u32, function_labels);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// use data section to initialize linear memory regions
|
|
||||||
if let Some(data_section) = self.module.data_section() {
|
|
||||||
for (data_segment_index, data_segment) in data_section.entries().iter().enumerate() {
|
|
||||||
let offset: u32 = get_initializer(data_segment.offset(), &self.module, &self.imports, VariableType::I32)?.try_into()?;
|
|
||||||
self.memory(ItemIndex::IndexSpace(data_segment.index()))
|
|
||||||
.map_err(|e| Error::Initialization(format!("DataSegment {} initializes non-existant MemoryInstance {}: {:?}", data_segment_index, data_segment.index(), e)))
|
|
||||||
.and_then(|m| m.set(offset, data_segment.value()))
|
|
||||||
.map_err(|e| Error::Initialization(e.into()))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// use element section to fill tables
|
|
||||||
if let Some(element_section) = self.module.elements_section() {
|
|
||||||
for (element_segment_index, element_segment) in element_section.entries().iter().enumerate() {
|
|
||||||
let offset: u32 = get_initializer(element_segment.offset(), &self.module, &self.imports, VariableType::I32)?.try_into()?;
|
|
||||||
for function_index in element_segment.members() {
|
|
||||||
self.require_function(ItemIndex::IndexSpace(*function_index))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.table(ItemIndex::IndexSpace(element_segment.index()))
|
|
||||||
.map_err(|e| Error::Initialization(format!("ElementSegment {} initializes non-existant Table {}: {:?}", element_segment_index, element_segment.index(), e)))
|
|
||||||
.and_then(|m| m.set_raw(offset, self.name.clone(), element_segment.members()))
|
|
||||||
.map_err(|e| Error::Initialization(e.into()))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run start function [if any].
|
|
||||||
pub fn run_start_function(&self) -> Result<(), Error> {
|
|
||||||
// execute start function (if any)
|
|
||||||
if let Some(start_function) = self.module.start_section() {
|
|
||||||
self.execute_index(start_function, ExecutionParams::default())?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn self_ref<'a>(&self, externals: Option<&'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>>) -> Result<Arc<ModuleInstanceInterface + 'a>, Error> {
|
|
||||||
self.imports.module(externals, &self.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn require_function(&self, index: ItemIndex) -> Result<u32, Error> {
|
|
||||||
match self.imports.parse_function_index(index) {
|
|
||||||
ItemIndex::IndexSpace(_) => unreachable!("parse_function_index resolves IndexSpace option"),
|
|
||||||
ItemIndex::Internal(index) => self.module.function_section()
|
|
||||||
.ok_or(Error::Function(format!("missing internal function {}", index)))
|
|
||||||
.and_then(|s| s.entries().get(index as usize)
|
|
||||||
.ok_or(Error::Function(format!("missing internal function {}", index))))
|
|
||||||
.map(|f| f.type_ref()),
|
|
||||||
ItemIndex::External(index) => self.module.import_section()
|
|
||||||
.ok_or(Error::Function(format!("missing external function {}", index)))
|
|
||||||
.and_then(|s| s.entries().get(index as usize)
|
|
||||||
.ok_or(Error::Function(format!("missing external function {}", index))))
|
|
||||||
.and_then(|import| match import.external() {
|
|
||||||
&External::Function(type_idx) => Ok(type_idx),
|
|
||||||
_ => Err(Error::Function(format!("external function {} is pointing to non-function import", index))),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ModuleInstanceInterface for ModuleInstance {
|
|
||||||
fn execute_index(&self, index: u32, params: ExecutionParams) -> Result<Option<RuntimeValue>, Error> {
|
|
||||||
let ExecutionParams { args, externals } = params;
|
|
||||||
let mut args = StackWithLimit::with_data(args, DEFAULT_VALUE_STACK_LIMIT);
|
|
||||||
let function_reference = self.function_reference(ItemIndex::IndexSpace(index), Some(&externals))?;
|
|
||||||
let function_context = CallerContext::topmost(&mut args, &externals);
|
|
||||||
function_reference.module.call_internal_function(function_context, function_reference.internal_index)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute_export(&self, name: &str, params: ExecutionParams) -> Result<Option<RuntimeValue>, Error> {
|
|
||||||
let index = self.exports.get(name)
|
|
||||||
.ok_or(Error::Function(format!("missing executable export with name {}", name)))
|
|
||||||
.and_then(|l| l.iter()
|
|
||||||
.find(|i| match i {
|
|
||||||
&&Internal::Function(_) => true,
|
|
||||||
_ => false,
|
|
||||||
})
|
|
||||||
.ok_or(Error::Function(format!("missing exported function with name {}", name)))
|
|
||||||
.map(|i| match i {
|
|
||||||
&Internal::Function(index) => index,
|
|
||||||
_ => unreachable!(), // checked couple of lines above
|
|
||||||
})
|
|
||||||
)?;
|
|
||||||
self.execute_index(index, params)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn export_entry<'a>(&self, name: &str, required_type: &ExportEntryType) -> Result<Internal, Error> {
|
|
||||||
self.exports.get(name)
|
|
||||||
.ok_or(Error::Function(format!("missing export entry with name {}", name)))
|
|
||||||
.and_then(|l| l.iter()
|
|
||||||
.find(|i| match required_type {
|
|
||||||
&ExportEntryType::Any => true,
|
|
||||||
&ExportEntryType::Global(global_type) => match i {
|
|
||||||
&&Internal::Global(global_index) => self.global(ItemIndex::IndexSpace(global_index), Some(global_type), None).map(|_| true).unwrap_or(false),
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
&ExportEntryType::Function(ref required_type) => match i {
|
|
||||||
&&Internal::Function(function_index) =>
|
|
||||||
self.function_type(ItemIndex::IndexSpace(function_index))
|
|
||||||
.map(|ft| ft == *required_type)
|
|
||||||
.unwrap_or(false),
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.map(|i| *i)
|
|
||||||
.ok_or(Error::Program(format!("unresolved export {}", name))))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn table(&self, index: ItemIndex) -> Result<Arc<TableInstance>, Error> {
|
|
||||||
match self.imports.parse_table_index(index) {
|
|
||||||
ItemIndex::IndexSpace(_) => unreachable!("parse_table_index resolves IndexSpace option"),
|
|
||||||
ItemIndex::Internal(index) => self.tables.get(index as usize).cloned()
|
|
||||||
.ok_or(Error::Table(format!("trying to access table with local index {} when there are only {} local tables", index, self.tables.len()))),
|
|
||||||
ItemIndex::External(index) => self.module.import_section()
|
|
||||||
.ok_or(Error::Table(format!("trying to access external table with index {} in module without import section", index)))
|
|
||||||
.and_then(|s| s.entries().get(index as usize)
|
|
||||||
.ok_or(Error::Table(format!("trying to access external table with index {} in module with {}-entries import section", index, s.entries().len()))))
|
|
||||||
.and_then(|e| self.imports.table(None, e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn memory(&self, index: ItemIndex) -> Result<Arc<MemoryInstance>, Error> {
|
|
||||||
match self.imports.parse_memory_index(index) {
|
|
||||||
ItemIndex::IndexSpace(_) => unreachable!("parse_memory_index resolves IndexSpace option"),
|
|
||||||
ItemIndex::Internal(index) => self.memory.get(index as usize).cloned()
|
|
||||||
.ok_or(Error::Memory(format!("trying to access memory with local index {} when there are only {} memory regions", index, self.memory.len()))),
|
|
||||||
ItemIndex::External(index) => self.module.import_section()
|
|
||||||
.ok_or(Error::Memory(format!("trying to access external memory with index {} in module without import section", index)))
|
|
||||||
.and_then(|s| s.entries().get(index as usize)
|
|
||||||
.ok_or(Error::Memory(format!("trying to access external memory with index {} in module with {}-entries import section", index, s.entries().len()))))
|
|
||||||
.and_then(|e| self.imports.memory(None, e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn global<'a>(&self, index: ItemIndex, variable_type: Option<VariableType>, externals: Option<&'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>>) -> Result<Arc<VariableInstance>, Error> {
|
|
||||||
match self.imports.parse_global_index(index) {
|
|
||||||
ItemIndex::IndexSpace(_) => unreachable!("parse_global_index resolves IndexSpace option"),
|
|
||||||
ItemIndex::Internal(index) => self.globals.get(index as usize).cloned()
|
|
||||||
.ok_or(Error::Global(format!("trying to access global with local index {} when there are only {} globals", index, self.globals.len()))),
|
|
||||||
ItemIndex::External(index) => self.module.import_section()
|
|
||||||
.ok_or(Error::Global(format!("trying to access external global with index {} in module without import section", index)))
|
|
||||||
.and_then(|s| s.entries().get(index as usize)
|
|
||||||
.ok_or(Error::Global(format!("trying to access external global with index {} in module with {}-entries import section", index, s.entries().len()))))
|
|
||||||
.and_then(|e| self.imports.global(externals, e, variable_type)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn function_type(&self, function_index: ItemIndex) -> Result<FunctionSignature, Error> {
|
|
||||||
match self.imports.parse_function_index(function_index) {
|
|
||||||
ItemIndex::IndexSpace(_) => unreachable!("parse_function_index resolves IndexSpace option"),
|
|
||||||
ItemIndex::Internal(index) => self.require_function(ItemIndex::Internal(index))
|
|
||||||
.and_then(|ft| self.function_type_by_index(ft)),
|
|
||||||
ItemIndex::External(index) => self.module.import_section()
|
|
||||||
.ok_or(Error::Function(format!("trying to access external function with index {} in module without import section", index)))
|
|
||||||
.and_then(|s| s.entries().get(index as usize)
|
|
||||||
.ok_or(Error::Function(format!("trying to access external function with index {} in module with {}-entries import section", index, s.entries().len()))))
|
|
||||||
.and_then(|e| match e.external() {
|
|
||||||
&External::Function(type_index) => self.function_type_by_index(type_index),
|
|
||||||
_ => Err(Error::Function(format!("exported function {} is not a function", index))),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn function_type_by_index(&self, type_index: u32) -> Result<FunctionSignature, Error> {
|
|
||||||
self.module.type_section()
|
|
||||||
.ok_or(Error::Validation(format!("type reference {} exists in module without type section", type_index)))
|
|
||||||
.and_then(|s| match s.types().get(type_index as usize) {
|
|
||||||
Some(&Type::Function(ref function_type)) => Ok(function_type),
|
|
||||||
_ => Err(Error::Validation(format!("missing function type with index {}", type_index))),
|
|
||||||
})
|
|
||||||
.map(FunctionSignature::Module)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn function_reference<'a>(&self, index: ItemIndex, externals: Option<&'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>>) -> Result<InternalFunctionReference<'a>, Error> {
|
|
||||||
match self.imports.parse_function_index(index) {
|
|
||||||
ItemIndex::IndexSpace(_) => unreachable!("parse_function_index resolves IndexSpace option"),
|
|
||||||
ItemIndex::Internal(index) => Ok(InternalFunctionReference {
|
|
||||||
module: self.self_ref(externals)?,
|
|
||||||
internal_index: index,
|
|
||||||
}),
|
|
||||||
ItemIndex::External(index) => {
|
|
||||||
let import_entry = self.module.import_section()
|
|
||||||
.expect("parse_function_index has returned External(index); it is only returned when import section exists; qed")
|
|
||||||
.entries().get(index as usize)
|
|
||||||
.expect("parse_function_index has returned External(index); it is only returned when entry with index exists in import section exists; qed");
|
|
||||||
let required_function_type = self.function_type(ItemIndex::External(index))?;
|
|
||||||
let internal_function_index = self.imports.function(externals, import_entry, Some(required_function_type))?;
|
|
||||||
Ok(InternalFunctionReference {
|
|
||||||
module: self.imports.module(externals, import_entry.module())?,
|
|
||||||
internal_index: internal_function_index,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn function_reference_indirect<'a>(&self, table_idx: u32, type_idx: u32, func_idx: u32, externals: Option<&'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>>) -> Result<InternalFunctionReference<'a>, Error> {
|
|
||||||
let table = self.table(ItemIndex::IndexSpace(table_idx))?;
|
|
||||||
let (module, index) = match table.get(func_idx)? {
|
|
||||||
RuntimeValue::AnyFunc(module, index) => (module.clone(), index),
|
|
||||||
_ => return Err(Error::Function(format!("trying to indirect call function {} via non-anyfunc table {:?}", func_idx, table_idx))),
|
|
||||||
};
|
|
||||||
|
|
||||||
let module = self.imports.module(externals, &module)?;
|
|
||||||
let required_function_type = self.function_type_by_index(type_idx)?;
|
|
||||||
let actual_function_type = module.function_type(ItemIndex::IndexSpace(index))?;
|
|
||||||
if required_function_type != actual_function_type {
|
|
||||||
return Err(Error::Function(format!("expected indirect function with signature ({:?}) -> {:?} when got with ({:?}) -> {:?}",
|
|
||||||
required_function_type.params(), required_function_type.return_type(),
|
|
||||||
actual_function_type.params(), actual_function_type.return_type())));
|
|
||||||
}
|
|
||||||
|
|
||||||
module.function_reference(ItemIndex::IndexSpace(index), externals)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn function_body<'a>(&'a self, internal_index: u32) -> Result<Option<InternalFunction<'a>>, Error> {
|
|
||||||
let function_body = self.module
|
|
||||||
.code_section()
|
|
||||||
.ok_or(Error::Function(format!("trying to call function with index {} in module without code section", internal_index)))
|
|
||||||
.and_then(|s| s.bodies()
|
|
||||||
.get(internal_index as usize)
|
|
||||||
.ok_or(Error::Function(format!("trying to call function with index {} in module with {} functions codes", internal_index, s.bodies().len()))))?;
|
|
||||||
let function_labels = self.functions_labels.get(&internal_index)
|
|
||||||
.ok_or(Error::Function(format!("trying to call non-validated internal function {}", internal_index)))?;
|
|
||||||
|
|
||||||
Ok(Some(InternalFunction {
|
|
||||||
locals: function_body.locals(),
|
|
||||||
body: function_body.code().elements(),
|
|
||||||
labels: function_labels,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call_internal_function(&self, outer: CallerContext, index: u32) -> Result<Option<RuntimeValue>, Error> {
|
|
||||||
let function_type = self.function_type(ItemIndex::Internal(index))?;
|
|
||||||
let args = prepare_function_args(&function_type, outer.value_stack)?;
|
|
||||||
let function_ref = InternalFunctionReference { module: self.self_ref(Some(outer.externals))?, internal_index: index };
|
|
||||||
let inner = FunctionContext::new(function_ref, outer.externals, outer.value_stack_limit, outer.frame_stack_limit, &function_type, args);
|
|
||||||
Interpreter::run_function(inner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> CallerContext<'a> {
|
|
||||||
/// Top most args
|
|
||||||
pub fn topmost(args: &'a mut StackWithLimit<RuntimeValue>, externals: &'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>) -> Self {
|
|
||||||
CallerContext {
|
|
||||||
value_stack_limit: DEFAULT_VALUE_STACK_LIMIT,
|
|
||||||
frame_stack_limit: DEFAULT_FRAME_STACK_LIMIT,
|
|
||||||
value_stack: args,
|
|
||||||
externals: externals,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Nested context
|
|
||||||
pub fn nested(outer: &'a mut FunctionContext) -> Self {
|
|
||||||
CallerContext {
|
|
||||||
value_stack_limit: outer.value_stack().limit() - outer.value_stack().len(),
|
|
||||||
frame_stack_limit: outer.frame_stack().limit() - outer.frame_stack().len(),
|
|
||||||
value_stack: &mut outer.value_stack,
|
|
||||||
externals: &outer.externals,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_limits(limits: &ResizableLimits) -> Result<(), Error> {
|
|
||||||
if let Some(maximum) = limits.maximum() {
|
|
||||||
if maximum < limits.initial() {
|
|
||||||
return Err(Error::Validation(format!("maximum limit {} is lesser than minimum {}", maximum, limits.initial())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_initializer(expr: &InitExpr, module: &Module, imports: &ModuleImports, expected_type: VariableType) -> Result<RuntimeValue, Error> {
|
|
||||||
let first_opcode = match expr.code().len() {
|
|
||||||
1 => &expr.code()[0],
|
|
||||||
2 if expr.code().len() == 2 && expr.code()[1] == Opcode::End => &expr.code()[0],
|
|
||||||
_ => return Err(Error::Initialization(format!("expected 1-instruction len initializer. Got {:?}", expr.code()))),
|
|
||||||
};
|
|
||||||
|
|
||||||
match first_opcode {
|
|
||||||
&Opcode::GetGlobal(index) => {
|
|
||||||
let index = match imports.parse_global_index(ItemIndex::IndexSpace(index)) {
|
|
||||||
ItemIndex::External(index) => index,
|
|
||||||
_ => return Err(Error::Global(format!("trying to initialize with non-external global {}", index))),
|
|
||||||
};
|
|
||||||
module.import_section()
|
|
||||||
.ok_or(Error::Global(format!("trying to initialize with external global with index {} in module without import section", index)))
|
|
||||||
.and_then(|s| s.entries().get(index as usize)
|
|
||||||
.ok_or(Error::Global(format!("trying to initialize with external global with index {} in module with {}-entries import section", index, s.entries().len()))))
|
|
||||||
.and_then(|e| imports.global(None, e, Some(expected_type)))
|
|
||||||
.map(|g| g.get())
|
|
||||||
},
|
|
||||||
&Opcode::I32Const(val) => Ok(RuntimeValue::I32(val)),
|
|
||||||
&Opcode::I64Const(val) => Ok(RuntimeValue::I64(val)),
|
|
||||||
&Opcode::F32Const(val) => Ok(RuntimeValue::decode_f32(val)),
|
|
||||||
&Opcode::F64Const(val) => Ok(RuntimeValue::decode_f64(val)),
|
|
||||||
_ => Err(Error::Initialization(format!("not-supported {:?} instruction in instantiation-time initializer", first_opcode))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> FunctionSignature<'a> {
|
|
||||||
/// Get return type of this function.
|
|
||||||
pub fn return_type(&self) -> Option<ValueType> {
|
|
||||||
match self {
|
|
||||||
&FunctionSignature::Module(ft) => ft.return_type(),
|
|
||||||
&FunctionSignature::User(fd) => fd.return_type(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get parameters of this function.
|
|
||||||
pub fn params(&self) -> &[ValueType] {
|
|
||||||
match self {
|
|
||||||
&FunctionSignature::Module(ft) => ft.params(),
|
|
||||||
&FunctionSignature::User(fd) => fd.params(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> PartialEq for FunctionSignature<'a> {
|
|
||||||
fn eq<'b>(&self, other: &FunctionSignature<'b>) -> bool {
|
|
||||||
match self {
|
|
||||||
&FunctionSignature::Module(ft1) => match other {
|
|
||||||
&FunctionSignature::Module(ft2) => ft1 == ft2,
|
|
||||||
&FunctionSignature::User(ft2) => ft1.params() == ft2.params() && ft1.return_type() == ft2.return_type(),
|
|
||||||
},
|
|
||||||
&FunctionSignature::User(ft1) => match other {
|
|
||||||
&FunctionSignature::User(ft2) => ft1 == ft2,
|
|
||||||
&FunctionSignature::Module(ft2) => ft1.params() == ft2.params() && ft1.return_type() == ft2.return_type(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a> From<&'a FunctionType> for FunctionSignature<'a> {
|
|
||||||
fn from(other: &'a FunctionType) -> Self {
|
|
||||||
FunctionSignature::Module(other)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,288 +0,0 @@
|
|||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use parking_lot::RwLock;
|
|
||||||
use elements::{Internal, ValueType};
|
|
||||||
use interpreter::Error;
|
|
||||||
use interpreter::module::{ModuleInstanceInterface, ExecutionParams, ItemIndex,
|
|
||||||
CallerContext, ExportEntryType, InternalFunctionReference, InternalFunction, FunctionSignature};
|
|
||||||
use interpreter::memory::MemoryInstance;
|
|
||||||
use interpreter::table::TableInstance;
|
|
||||||
use interpreter::value::RuntimeValue;
|
|
||||||
use interpreter::variable::{VariableInstance, VariableType};
|
|
||||||
|
|
||||||
/// Min index of native function.
|
|
||||||
pub const NATIVE_INDEX_FUNC_MIN: u32 = 10001;
|
|
||||||
/// Min index of native global.
|
|
||||||
pub const NATIVE_INDEX_GLOBAL_MIN: u32 = 20001;
|
|
||||||
|
|
||||||
/// User functions executor.
|
|
||||||
pub trait UserFunctionExecutor {
|
|
||||||
/// Execute function with given name.
|
|
||||||
fn execute(&mut self, name: &str, context: CallerContext) -> Result<Option<RuntimeValue>, Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// User function descriptor
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum UserFunctionDescriptor {
|
|
||||||
/// Static function definition
|
|
||||||
Static(&'static str, &'static [ValueType], Option<ValueType>),
|
|
||||||
/// Dynamic heap function definition
|
|
||||||
Heap(String, Vec<ValueType>, Option<ValueType>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UserFunctionDescriptor {
|
|
||||||
/// New function with statically known params
|
|
||||||
pub fn statik(name: &'static str, params: &'static [ValueType], result: Option<ValueType>) -> Self {
|
|
||||||
UserFunctionDescriptor::Static(name, params, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// New function with statically unknown params
|
|
||||||
pub fn heap(name: String, params: Vec<ValueType>, result: Option<ValueType>) -> Self {
|
|
||||||
UserFunctionDescriptor::Heap(name, params, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Name of the function
|
|
||||||
pub fn name(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
&UserFunctionDescriptor::Static(name, _, _) => name,
|
|
||||||
&UserFunctionDescriptor::Heap(ref name, _, _) => name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Arguments of the function
|
|
||||||
pub fn params(&self) -> &[ValueType] {
|
|
||||||
match self {
|
|
||||||
&UserFunctionDescriptor::Static(_, params, _) => params,
|
|
||||||
&UserFunctionDescriptor::Heap(_, ref params, _) => params,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return type of the function
|
|
||||||
pub fn return_type(&self) -> Option<ValueType> {
|
|
||||||
match self {
|
|
||||||
&UserFunctionDescriptor::Static(_, _, result) => result,
|
|
||||||
&UserFunctionDescriptor::Heap(_, _, result) => result,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set of user-defined module elements.
|
|
||||||
pub struct UserDefinedElements<E: UserFunctionExecutor> {
|
|
||||||
/// User globals list.
|
|
||||||
pub globals: HashMap<String, Arc<VariableInstance>>,
|
|
||||||
/// User functions list.
|
|
||||||
pub functions: Cow<'static, [UserFunctionDescriptor]>,
|
|
||||||
/// Functions executor.
|
|
||||||
pub executor: Option<E>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Native module instance.
|
|
||||||
pub struct NativeModuleInstance<E: UserFunctionExecutor> {
|
|
||||||
/// Underlying module reference.
|
|
||||||
base: Arc<ModuleInstanceInterface>,
|
|
||||||
/// User function executor.
|
|
||||||
executor: RwLock<Option<E>>,
|
|
||||||
/// By-name functions index.
|
|
||||||
functions_by_name: HashMap<String, u32>,
|
|
||||||
/// User functions list.
|
|
||||||
functions: Cow<'static, [UserFunctionDescriptor]>,
|
|
||||||
/// By-name functions index.
|
|
||||||
globals_by_name: HashMap<String, u32>,
|
|
||||||
/// User globals list.
|
|
||||||
globals: Vec<Arc<VariableInstance>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: UserFunctionExecutor> NativeModuleInstance<E> {
|
|
||||||
/// Create new native module
|
|
||||||
pub fn new(base: Arc<ModuleInstanceInterface>, elements: UserDefinedElements<E>) -> Result<Self, Error> {
|
|
||||||
if !elements.functions.is_empty() && elements.executor.is_none() {
|
|
||||||
return Err(Error::Function("trying to construct native module with functions, but without executor".into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(NativeModuleInstance {
|
|
||||||
base: base,
|
|
||||||
executor: RwLock::new(elements.executor),
|
|
||||||
functions_by_name: elements.functions.iter().enumerate().map(|(i, f)| (f.name().to_owned(), i as u32)).collect(),
|
|
||||||
functions: elements.functions,
|
|
||||||
globals_by_name: elements.globals.iter().enumerate().map(|(i, (g_name, _))| (g_name.to_owned(), i as u32)).collect(),
|
|
||||||
globals: elements.globals.into_iter().map(|(_, g)| g).collect(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: UserFunctionExecutor> ModuleInstanceInterface for NativeModuleInstance<E> {
|
|
||||||
fn execute_index(&self, index: u32, params: ExecutionParams) -> Result<Option<RuntimeValue>, Error> {
|
|
||||||
self.base.execute_index(index, params)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute_export(&self, name: &str, params: ExecutionParams) -> Result<Option<RuntimeValue>, Error> {
|
|
||||||
self.base.execute_export(name, params)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn export_entry<'b>(&self, name: &str, required_type: &ExportEntryType) -> Result<Internal, Error> {
|
|
||||||
if let Some(index) = self.functions_by_name.get(name) {
|
|
||||||
let composite_index = NATIVE_INDEX_FUNC_MIN + *index;
|
|
||||||
match required_type {
|
|
||||||
&ExportEntryType::Any => return Ok(Internal::Function(composite_index)),
|
|
||||||
&ExportEntryType::Function(ref required_type) => {
|
|
||||||
let actual_type = self.function_type(ItemIndex::Internal(composite_index))
|
|
||||||
.expect(
|
|
||||||
"by_name contains index; function_type succeeds for all functions from by_name; qed",
|
|
||||||
);
|
|
||||||
return if actual_type == *required_type {
|
|
||||||
Ok(Internal::Function(composite_index))
|
|
||||||
} else {
|
|
||||||
Err(Error::Validation(format!(
|
|
||||||
"Export function type {} mismatch. Expected function with signature ({:?}) -> {:?} when got with ({:?}) -> {:?}",
|
|
||||||
index,
|
|
||||||
required_type.params(),
|
|
||||||
required_type.return_type(),
|
|
||||||
actual_type.params(),
|
|
||||||
actual_type.return_type()
|
|
||||||
)))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(index) = self.globals_by_name.get(name) {
|
|
||||||
let composite_index = NATIVE_INDEX_GLOBAL_MIN + *index;
|
|
||||||
match required_type {
|
|
||||||
&ExportEntryType::Any => {
|
|
||||||
return Ok(Internal::Global(composite_index))
|
|
||||||
}
|
|
||||||
&ExportEntryType::Global(ref required_type) => {
|
|
||||||
let actual_type = self.globals
|
|
||||||
.get(*index as usize)
|
|
||||||
.expect(
|
|
||||||
"globals_by_name maps to indexes of globals; index read from globals_by_name; qed",
|
|
||||||
)
|
|
||||||
.variable_type();
|
|
||||||
return if actual_type == *required_type {
|
|
||||||
Ok(Internal::Global(composite_index))
|
|
||||||
} else {
|
|
||||||
Err(Error::Validation(format!(
|
|
||||||
"Export global type {} mismatch. Expected type {:?} when got {:?}",
|
|
||||||
index,
|
|
||||||
required_type,
|
|
||||||
actual_type
|
|
||||||
)))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.base.export_entry(name, required_type)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn table(&self, index: ItemIndex) -> Result<Arc<TableInstance>, Error> {
|
|
||||||
self.base.table(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn memory(&self, index: ItemIndex) -> Result<Arc<MemoryInstance>, Error> {
|
|
||||||
self.base.memory(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn global<'b>(&self, global_index: ItemIndex, variable_type: Option<VariableType>, externals: Option<&'b HashMap<String, Arc<ModuleInstanceInterface + 'b>>>) -> Result<Arc<VariableInstance>, Error> {
|
|
||||||
let index = match global_index {
|
|
||||||
ItemIndex::IndexSpace(index) | ItemIndex::Internal(index) => index,
|
|
||||||
ItemIndex::External(_) => unreachable!("trying to get global, exported by native module"),
|
|
||||||
};
|
|
||||||
|
|
||||||
if index < NATIVE_INDEX_GLOBAL_MIN {
|
|
||||||
return self.base.global(global_index, variable_type, externals);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.globals
|
|
||||||
.get((index - NATIVE_INDEX_GLOBAL_MIN) as usize)
|
|
||||||
.cloned()
|
|
||||||
.ok_or(Error::Native(format!("trying to get native global with index {}", index)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn function_type(&self, function_index: ItemIndex) -> Result<FunctionSignature, Error> {
|
|
||||||
let index = match function_index {
|
|
||||||
ItemIndex::IndexSpace(index) | ItemIndex::Internal(index) => index,
|
|
||||||
ItemIndex::External(_) => unreachable!("trying to call function, exported by native module"),
|
|
||||||
};
|
|
||||||
|
|
||||||
if index < NATIVE_INDEX_FUNC_MIN || index >= NATIVE_INDEX_GLOBAL_MIN {
|
|
||||||
return self.base.function_type(function_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(FunctionSignature::User(self.functions
|
|
||||||
.get((index - NATIVE_INDEX_FUNC_MIN) as usize)
|
|
||||||
.ok_or(Error::Native(format!("missing native function with index {}", index)))?))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn function_type_by_index(&self, type_index: u32) -> Result<FunctionSignature, Error> {
|
|
||||||
self.function_type(ItemIndex::Internal(type_index))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn function_reference<'b>(&self, index: ItemIndex, externals: Option<&'b HashMap<String, Arc<ModuleInstanceInterface + 'b>>>) -> Result<InternalFunctionReference<'b>, Error> {
|
|
||||||
self.base.function_reference(index, externals)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn function_reference_indirect<'b>(&self, table_idx: u32, type_idx: u32, func_idx: u32, externals: Option<&'b HashMap<String, Arc<ModuleInstanceInterface + 'b>>>) -> Result<InternalFunctionReference<'b>, Error> {
|
|
||||||
self.base.function_reference_indirect(table_idx, type_idx, func_idx, externals)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn function_body<'b>(&'b self, _internal_index: u32) -> Result<Option<InternalFunction<'b>>, Error> {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call_internal_function(&self, outer: CallerContext, index: u32) -> Result<Option<RuntimeValue>, Error> {
|
|
||||||
if index < NATIVE_INDEX_FUNC_MIN || index >= NATIVE_INDEX_GLOBAL_MIN {
|
|
||||||
return self.base.call_internal_function(outer, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.functions
|
|
||||||
.get((index - NATIVE_INDEX_FUNC_MIN) as usize)
|
|
||||||
.ok_or(Error::Native(format!("trying to call native function with index {}", index)).into())
|
|
||||||
.and_then(|f| self.executor.write()
|
|
||||||
.as_mut()
|
|
||||||
.expect("function exists; if function exists, executor must also exists [checked in constructor]; qed")
|
|
||||||
.execute(&f.name(), outer))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create wrapper for a module with given native user functions.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use parity_wasm::interpreter::{CallerContext, Error, RuntimeValue, UserFunctionExecutor};
|
|
||||||
///
|
|
||||||
/// struct MyExecutor;
|
|
||||||
///
|
|
||||||
/// impl UserFunctionExecutor for MyExecutor {
|
|
||||||
/// fn execute(
|
|
||||||
/// &mut self,
|
|
||||||
/// name: &str,
|
|
||||||
/// context: CallerContext,
|
|
||||||
/// ) -> Result<Option<RuntimeValue>, Error> {
|
|
||||||
/// match name {
|
|
||||||
/// "add" => {
|
|
||||||
/// // fn add(a: u32, b: u32) -> u32
|
|
||||||
/// let b = context.value_stack.pop_as::<u32>()?;
|
|
||||||
/// let a = context.value_stack.pop_as::<u32>()?;
|
|
||||||
/// let sum = a + b;
|
|
||||||
/// Ok(Some(RuntimeValue::I32(sum as i32)))
|
|
||||||
/// }
|
|
||||||
/// _ => Err(Error::Trap("not implemented".into()).into()),
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn native_module<'a, E: UserFunctionExecutor + 'a>(base: Arc<ModuleInstanceInterface>, user_elements: UserDefinedElements<E>) -> Result<Arc<ModuleInstanceInterface + 'a>, Error> {
|
|
||||||
Ok(Arc::new(NativeModuleInstance::new(base, user_elements)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> PartialEq for UserFunctionDescriptor {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.params() == other.params()
|
|
||||||
&& self.return_type() == other.return_type()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use parking_lot::RwLock;
|
|
||||||
use elements::Module;
|
|
||||||
use interpreter::Error;
|
|
||||||
use interpreter::module::{ModuleInstance, ModuleInstanceInterface};
|
|
||||||
|
|
||||||
/// Program instance. Program is a set of instantiated modules.
|
|
||||||
pub struct ProgramInstance {
|
|
||||||
/// Shared data reference.
|
|
||||||
essence: Arc<ProgramInstanceEssence>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Program instance essence.
|
|
||||||
pub struct ProgramInstanceEssence {
|
|
||||||
/// Loaded modules.
|
|
||||||
modules: RwLock<HashMap<String, Arc<ModuleInstanceInterface>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProgramInstance {
|
|
||||||
/// Create new program instance.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
ProgramInstance {
|
|
||||||
essence: Arc::new(ProgramInstanceEssence::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Instantiate module with validation.
|
|
||||||
pub fn add_module<'a>(&self, name: &str, module: Module, externals: Option<&'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>>) -> Result<Arc<ModuleInstance>, Error> {
|
|
||||||
let mut module_instance = ModuleInstance::new(Arc::downgrade(&self.essence), name.into(), module)?;
|
|
||||||
module_instance.instantiate(externals)?;
|
|
||||||
|
|
||||||
let module_instance = Arc::new(module_instance);
|
|
||||||
self.essence.modules.write().insert(name.into(), module_instance.clone());
|
|
||||||
module_instance.run_start_function()?;
|
|
||||||
Ok(module_instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Insert instantiated module.
|
|
||||||
pub fn insert_loaded_module(&self, name: &str, module_instance: Arc<ModuleInstanceInterface>) -> Result<Arc<ModuleInstanceInterface>, Error> {
|
|
||||||
// replace existing module with the same name with new one
|
|
||||||
self.essence.modules.write().insert(name.into(), Arc::clone(&module_instance));
|
|
||||||
Ok(module_instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get one of the modules by name
|
|
||||||
pub fn module(&self, name: &str) -> Option<Arc<ModuleInstanceInterface>> {
|
|
||||||
self.essence.module(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProgramInstanceEssence {
|
|
||||||
/// Create new program essence.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
ProgramInstanceEssence {
|
|
||||||
modules: RwLock::new(HashMap::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get module reference.
|
|
||||||
pub fn module(&self, name: &str) -> Option<Arc<ModuleInstanceInterface>> {
|
|
||||||
self.modules.read().get(name).cloned()
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,35 +0,0 @@
|
|||||||
use interpreter::{Error as InterpreterError};
|
|
||||||
use interpreter::value::{RuntimeValue, TryInto};
|
|
||||||
use common::stack::StackWithLimit;
|
|
||||||
|
|
||||||
impl StackWithLimit<RuntimeValue> {
|
|
||||||
pub fn pop_as<T>(&mut self) -> Result<T, InterpreterError>
|
|
||||||
where
|
|
||||||
RuntimeValue: TryInto<T, InterpreterError>,
|
|
||||||
{
|
|
||||||
let value = self.pop()?;
|
|
||||||
TryInto::try_into(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop_pair(&mut self) -> Result<(RuntimeValue, RuntimeValue), InterpreterError> {
|
|
||||||
let right = self.pop()?;
|
|
||||||
let left = self.pop()?;
|
|
||||||
Ok((left, right))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop_pair_as<T>(&mut self) -> Result<(T, T), InterpreterError>
|
|
||||||
where
|
|
||||||
RuntimeValue: TryInto<T, InterpreterError>,
|
|
||||||
{
|
|
||||||
let right = self.pop_as()?;
|
|
||||||
let left = self.pop_as()?;
|
|
||||||
Ok((left, right))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop_triple(&mut self) -> Result<(RuntimeValue, RuntimeValue, RuntimeValue), InterpreterError> {
|
|
||||||
let right = self.pop()?;
|
|
||||||
let mid = self.pop()?;
|
|
||||||
let left = self.pop()?;
|
|
||||||
Ok((left, mid, right))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,94 +0,0 @@
|
|||||||
use std::u32;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use parking_lot::RwLock;
|
|
||||||
use elements::{TableType, ResizableLimits};
|
|
||||||
use interpreter::Error;
|
|
||||||
use interpreter::module::check_limits;
|
|
||||||
use interpreter::variable::{VariableInstance, VariableType};
|
|
||||||
use interpreter::value::RuntimeValue;
|
|
||||||
|
|
||||||
/// Table instance.
|
|
||||||
pub struct TableInstance {
|
|
||||||
/// Table limits.
|
|
||||||
limits: ResizableLimits,
|
|
||||||
/// Table variables type.
|
|
||||||
variable_type: VariableType,
|
|
||||||
/// Table memory buffer.
|
|
||||||
buffer: RwLock<Vec<TableElement>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Table element. Cloneable wrapper around VariableInstance.
|
|
||||||
struct TableElement {
|
|
||||||
pub var: VariableInstance,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TableInstance {
|
|
||||||
/// New instance of the table
|
|
||||||
pub fn new(table_type: &TableType) -> Result<Arc<Self>, Error> {
|
|
||||||
check_limits(table_type.limits())?;
|
|
||||||
|
|
||||||
let variable_type = table_type.elem_type().into();
|
|
||||||
Ok(Arc::new(TableInstance {
|
|
||||||
limits: table_type.limits().clone(),
|
|
||||||
variable_type: variable_type,
|
|
||||||
buffer: RwLock::new(
|
|
||||||
vec![TableElement::new(VariableInstance::new(true, variable_type, RuntimeValue::Null)?); table_type.limits().initial() as usize]
|
|
||||||
),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return table limits.
|
|
||||||
pub fn limits(&self) -> &ResizableLimits {
|
|
||||||
&self.limits
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get variable type for this table.
|
|
||||||
pub fn variable_type(&self) -> VariableType {
|
|
||||||
self.variable_type
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the specific value in the table
|
|
||||||
pub fn get(&self, offset: u32) -> Result<RuntimeValue, Error> {
|
|
||||||
let buffer = self.buffer.read();
|
|
||||||
let buffer_len = buffer.len();
|
|
||||||
buffer.get(offset as usize)
|
|
||||||
.map(|v| v.var.get())
|
|
||||||
.ok_or(Error::Table(format!("trying to read table item with index {} when there are only {} items", offset, buffer_len)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the table value from raw slice
|
|
||||||
pub fn set_raw(&self, mut offset: u32, module_name: String, value: &[u32]) -> Result<(), Error> {
|
|
||||||
for val in value {
|
|
||||||
match self.variable_type {
|
|
||||||
VariableType::AnyFunc => self.set(offset, RuntimeValue::AnyFunc(module_name.clone(), *val))?,
|
|
||||||
_ => return Err(Error::Table(format!("table of type {:?} is not supported", self.variable_type))),
|
|
||||||
}
|
|
||||||
offset += 1;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the table from runtime variable value
|
|
||||||
pub fn set(&self, offset: u32, value: RuntimeValue) -> Result<(), Error> {
|
|
||||||
let mut buffer = self.buffer.write();
|
|
||||||
let buffer_len = buffer.len();
|
|
||||||
buffer.get_mut(offset as usize)
|
|
||||||
.ok_or(Error::Table(format!("trying to update table item with index {} when there are only {} items", offset, buffer_len)))
|
|
||||||
.and_then(|v| v.var.set(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TableElement {
|
|
||||||
pub fn new(var: VariableInstance) -> Self {
|
|
||||||
TableElement {
|
|
||||||
var: var,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for TableElement {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
TableElement::new(VariableInstance::new(self.var.is_mutable(), self.var.variable_type(), self.var.get())
|
|
||||||
.expect("it only fails when variable_type() != passed variable value; both are read from already constructed var; qed"))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,608 +0,0 @@
|
|||||||
///! Basic tests for instructions/constructions, missing in wabt tests
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use builder::module;
|
|
||||||
use elements::{ExportEntry, Internal, ImportEntry, External, GlobalEntry, GlobalType,
|
|
||||||
InitExpr, ValueType, Opcodes, Opcode, FunctionType, TableType, MemoryType};
|
|
||||||
use interpreter::{Error, UserError, ProgramInstance};
|
|
||||||
use interpreter::native::{native_module, UserDefinedElements, UserFunctionExecutor, UserFunctionDescriptor};
|
|
||||||
use interpreter::memory::MemoryInstance;
|
|
||||||
use interpreter::module::{ModuleInstanceInterface, CallerContext, ItemIndex, ExecutionParams, ExportEntryType, FunctionSignature};
|
|
||||||
use interpreter::value::{RuntimeValue, TryInto};
|
|
||||||
use interpreter::variable::{VariableInstance, ExternalVariableValue, VariableType};
|
|
||||||
use super::utils::program_with_default_env;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn import_function() {
|
|
||||||
let module1 = module()
|
|
||||||
.with_export(ExportEntry::new("external_func".into(), Internal::Function(0)))
|
|
||||||
.function()
|
|
||||||
.signature().return_type().i32().build()
|
|
||||||
.body().with_opcodes(Opcodes::new(vec![
|
|
||||||
Opcode::I32Const(3),
|
|
||||||
Opcode::End,
|
|
||||||
])).build()
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let module2 = module()
|
|
||||||
.with_import(ImportEntry::new("external_module".into(), "external_func".into(), External::Function(0)))
|
|
||||||
.function()
|
|
||||||
.signature().return_type().i32().build()
|
|
||||||
.body().with_opcodes(Opcodes::new(vec![
|
|
||||||
Opcode::Call(0),
|
|
||||||
Opcode::I32Const(7),
|
|
||||||
Opcode::I32Add,
|
|
||||||
Opcode::End,
|
|
||||||
])).build()
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let program = ProgramInstance::new();
|
|
||||||
let external_module = program.add_module("external_module", module1, None).unwrap();
|
|
||||||
let main_module = program.add_module("main", module2, None).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(external_module.execute_index(0, vec![].into()).unwrap().unwrap(), RuntimeValue::I32(3));
|
|
||||||
assert_eq!(main_module.execute_index(1, vec![].into()).unwrap().unwrap(), RuntimeValue::I32(10));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn wrong_import() {
|
|
||||||
let side_module = module()
|
|
||||||
.with_export(ExportEntry::new("cool_func".into(), Internal::Function(0)))
|
|
||||||
.function()
|
|
||||||
.signature().return_type().i32().build()
|
|
||||||
.body().with_opcodes(Opcodes::new(vec![
|
|
||||||
Opcode::I32Const(3),
|
|
||||||
Opcode::End,
|
|
||||||
])).build()
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let module = module()
|
|
||||||
.with_import(ImportEntry::new("side_module".into(), "not_cool_func".into(), External::Function(0)))
|
|
||||||
.function()
|
|
||||||
.signature().return_type().i32().build()
|
|
||||||
.body().with_opcodes(Opcodes::new(vec![
|
|
||||||
Opcode::Call(0),
|
|
||||||
Opcode::I32Const(7),
|
|
||||||
Opcode::I32Add,
|
|
||||||
Opcode::End,
|
|
||||||
])).build()
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let program = ProgramInstance::new();
|
|
||||||
let _side_module_instance = program.add_module("side_module", side_module, None).unwrap();
|
|
||||||
assert!(program.add_module("main", module, None).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn global_get_set() {
|
|
||||||
let module = module()
|
|
||||||
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, true), InitExpr::new(vec![Opcode::I32Const(42), Opcode::End])))
|
|
||||||
.function()
|
|
||||||
.signature().return_type().i32().build()
|
|
||||||
.body().with_opcodes(Opcodes::new(vec![
|
|
||||||
Opcode::GetGlobal(0),
|
|
||||||
Opcode::I32Const(8),
|
|
||||||
Opcode::I32Add,
|
|
||||||
Opcode::SetGlobal(0),
|
|
||||||
Opcode::GetGlobal(0),
|
|
||||||
Opcode::End,
|
|
||||||
])).build()
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let program = ProgramInstance::new();
|
|
||||||
let module = program.add_module("main", module, None).unwrap();
|
|
||||||
assert_eq!(module.execute_index(0, vec![].into()).unwrap().unwrap(), RuntimeValue::I32(50));
|
|
||||||
assert_eq!(module.execute_index(0, vec![].into()).unwrap().unwrap(), RuntimeValue::I32(58));
|
|
||||||
}
|
|
||||||
|
|
||||||
const SIGNATURE_I32_I32: &'static [ValueType] = &[ValueType::I32, ValueType::I32];
|
|
||||||
|
|
||||||
const SIGNATURES: &'static [UserFunctionDescriptor] = &[
|
|
||||||
UserFunctionDescriptor::Static(
|
|
||||||
"add",
|
|
||||||
SIGNATURE_I32_I32,
|
|
||||||
Some(ValueType::I32),
|
|
||||||
),
|
|
||||||
UserFunctionDescriptor::Static(
|
|
||||||
"sub",
|
|
||||||
SIGNATURE_I32_I32,
|
|
||||||
Some(ValueType::I32),
|
|
||||||
),
|
|
||||||
UserFunctionDescriptor::Static(
|
|
||||||
"err",
|
|
||||||
SIGNATURE_I32_I32,
|
|
||||||
Some(ValueType::I32),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
const NO_SIGNATURES: &'static [UserFunctionDescriptor] = &[];
|
|
||||||
|
|
||||||
// 'external' variable
|
|
||||||
struct MeasuredVariable {
|
|
||||||
pub val: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExternalVariableValue for MeasuredVariable {
|
|
||||||
fn get(&self) -> RuntimeValue {
|
|
||||||
RuntimeValue::I32(self.val)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set(&mut self, val: RuntimeValue) -> Result<(), Error> {
|
|
||||||
self.val = val.try_into()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// custom user error
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
struct UserErrorWithCode {
|
|
||||||
error_code: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ::std::fmt::Display for UserErrorWithCode {
|
|
||||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> {
|
|
||||||
write!(f, "{}", self.error_code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UserError for UserErrorWithCode {}
|
|
||||||
|
|
||||||
// user function executor
|
|
||||||
struct FunctionExecutor {
|
|
||||||
pub memory: Arc<MemoryInstance>,
|
|
||||||
pub values: Vec<i32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> UserFunctionExecutor for &'a mut FunctionExecutor {
|
|
||||||
fn execute(&mut self, name: &str, context: CallerContext) -> Result<Option<RuntimeValue>, Error> {
|
|
||||||
match name {
|
|
||||||
"add" => {
|
|
||||||
let memory_value = self.memory.get(0, 1).unwrap()[0];
|
|
||||||
let fn_argument_unused = context.value_stack.pop_as::<u32>().unwrap() as u8;
|
|
||||||
let fn_argument = context.value_stack.pop_as::<u32>().unwrap() as u8;
|
|
||||||
assert_eq!(fn_argument_unused, 0);
|
|
||||||
|
|
||||||
let sum = memory_value + fn_argument;
|
|
||||||
self.memory.set(0, &vec![sum]).unwrap();
|
|
||||||
self.values.push(sum as i32);
|
|
||||||
Ok(Some(RuntimeValue::I32(sum as i32)))
|
|
||||||
},
|
|
||||||
"sub" => {
|
|
||||||
let memory_value = self.memory.get(0, 1).unwrap()[0];
|
|
||||||
let fn_argument_unused = context.value_stack.pop_as::<u32>().unwrap() as u8;
|
|
||||||
let fn_argument = context.value_stack.pop_as::<u32>().unwrap() as u8;
|
|
||||||
assert_eq!(fn_argument_unused, 0);
|
|
||||||
|
|
||||||
let diff = memory_value - fn_argument;
|
|
||||||
self.memory.set(0, &vec![diff]).unwrap();
|
|
||||||
self.values.push(diff as i32);
|
|
||||||
Ok(Some(RuntimeValue::I32(diff as i32)))
|
|
||||||
},
|
|
||||||
"err" => {
|
|
||||||
Err(Error::User(Box::new(UserErrorWithCode { error_code: 777 })))
|
|
||||||
},
|
|
||||||
_ => Err(Error::Trap("not implemented".into()).into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn native_env_function() {
|
|
||||||
// create new program
|
|
||||||
let program = program_with_default_env();
|
|
||||||
// => env module is created
|
|
||||||
let env_instance = program.module("env").unwrap();
|
|
||||||
// => linear memory is created
|
|
||||||
let env_memory = env_instance.memory(ItemIndex::Internal(0)).unwrap();
|
|
||||||
|
|
||||||
// create native env module executor
|
|
||||||
let mut executor = FunctionExecutor {
|
|
||||||
memory: env_memory.clone(),
|
|
||||||
values: Vec::new(),
|
|
||||||
};
|
|
||||||
{
|
|
||||||
let functions = UserDefinedElements {
|
|
||||||
executor: Some(&mut executor),
|
|
||||||
globals: HashMap::new(),
|
|
||||||
functions: ::std::borrow::Cow::from(SIGNATURES),
|
|
||||||
};
|
|
||||||
let native_env_instance = native_module(env_instance, functions).unwrap();
|
|
||||||
let params = ExecutionParams::with_external("env".into(), native_env_instance);
|
|
||||||
|
|
||||||
let module = module()
|
|
||||||
.with_import(ImportEntry::new("env".into(), "add".into(), External::Function(0)))
|
|
||||||
.with_import(ImportEntry::new("env".into(), "sub".into(), External::Function(0)))
|
|
||||||
.function()
|
|
||||||
.signature().param().i32().param().i32().return_type().i32().build()
|
|
||||||
.body().with_opcodes(Opcodes::new(vec![
|
|
||||||
Opcode::GetLocal(0),
|
|
||||||
Opcode::GetLocal(1),
|
|
||||||
Opcode::Call(0),
|
|
||||||
Opcode::End,
|
|
||||||
])).build()
|
|
||||||
.build()
|
|
||||||
.function()
|
|
||||||
.signature().param().i32().param().i32().return_type().i32().build()
|
|
||||||
.body().with_opcodes(Opcodes::new(vec![
|
|
||||||
Opcode::GetLocal(0),
|
|
||||||
Opcode::GetLocal(1),
|
|
||||||
Opcode::Call(1),
|
|
||||||
Opcode::End,
|
|
||||||
])).build()
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// load module
|
|
||||||
let module_instance = program.add_module("main", module, Some(¶ms.externals)).unwrap();
|
|
||||||
|
|
||||||
{
|
|
||||||
assert_eq!(module_instance.execute_index(2, params.clone().add_argument(RuntimeValue::I32(7)).add_argument(RuntimeValue::I32(0))).unwrap().unwrap(), RuntimeValue::I32(7));
|
|
||||||
assert_eq!(module_instance.execute_index(2, params.clone().add_argument(RuntimeValue::I32(50)).add_argument(RuntimeValue::I32(0))).unwrap().unwrap(), RuntimeValue::I32(57));
|
|
||||||
assert_eq!(module_instance.execute_index(3, params.clone().add_argument(RuntimeValue::I32(15)).add_argument(RuntimeValue::I32(0))).unwrap().unwrap(), RuntimeValue::I32(42));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(executor.memory.get(0, 1).unwrap()[0], 42);
|
|
||||||
assert_eq!(executor.values, vec![7, 57, 42]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn native_env_function_own_memory() {
|
|
||||||
// create program + env module is auto instantiated + env module memory is instantiated (we do not need this)
|
|
||||||
let program = program_with_default_env();
|
|
||||||
|
|
||||||
struct OwnMemoryReference {
|
|
||||||
pub memory: RefCell<Option<Arc<MemoryInstance>>>,
|
|
||||||
}
|
|
||||||
struct OwnMemoryExecutor {
|
|
||||||
pub memory_ref: Arc<OwnMemoryReference>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> UserFunctionExecutor for &'a mut OwnMemoryExecutor {
|
|
||||||
fn execute(&mut self, name: &str, context: CallerContext) -> Result<Option<RuntimeValue>, Error> {
|
|
||||||
match name {
|
|
||||||
"add" => {
|
|
||||||
let memory = self.memory_ref.memory.borrow_mut().as_ref().expect("initialized before execution; qed").clone();
|
|
||||||
let memory_value = memory.get(0, 1).unwrap()[0];
|
|
||||||
let fn_argument_unused = context.value_stack.pop_as::<u32>().unwrap() as u8;
|
|
||||||
let fn_argument = context.value_stack.pop_as::<u32>().unwrap() as u8;
|
|
||||||
assert_eq!(fn_argument_unused, 0);
|
|
||||||
|
|
||||||
let sum = memory_value + fn_argument;
|
|
||||||
memory.set(0, &vec![sum]).unwrap();
|
|
||||||
Ok(Some(RuntimeValue::I32(sum as i32)))
|
|
||||||
},
|
|
||||||
_ => Err(Error::Trap("not implemented".into()).into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let env_instance = program.module("env").unwrap();
|
|
||||||
let memory_ref = Arc::new(OwnMemoryReference { memory: RefCell::new(None) });
|
|
||||||
let mut executor = OwnMemoryExecutor { memory_ref: memory_ref.clone() };
|
|
||||||
let native_env_instance = native_module(env_instance, UserDefinedElements {
|
|
||||||
executor: Some(&mut executor),
|
|
||||||
globals: HashMap::new(),
|
|
||||||
functions: ::std::borrow::Cow::from(SIGNATURES),
|
|
||||||
}).unwrap();
|
|
||||||
let params = ExecutionParams::with_external("env".into(), native_env_instance);
|
|
||||||
|
|
||||||
// create module definition with its own memory
|
|
||||||
// => since we do not import env' memory, all instructions from this module will access this memory
|
|
||||||
let module = module()
|
|
||||||
.memory().build() // new memory is created
|
|
||||||
.with_import(ImportEntry::new("env".into(), "add".into(), External::Function(0))) // import 'native' function
|
|
||||||
.function() // add simple wasm function
|
|
||||||
.signature().param().i32().param().i32().return_type().i32().build()
|
|
||||||
.body().with_opcodes(Opcodes::new(vec![
|
|
||||||
Opcode::GetLocal(0),
|
|
||||||
Opcode::GetLocal(1),
|
|
||||||
Opcode::Call(0),
|
|
||||||
Opcode::End,
|
|
||||||
])).build()
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// instantiate module
|
|
||||||
let module_instance = program.add_module("main", module, Some(¶ms.externals)).unwrap();
|
|
||||||
// now get memory reference
|
|
||||||
let module_memory = module_instance.memory(ItemIndex::Internal(0)).unwrap();
|
|
||||||
// post-initialize our executor with memory reference
|
|
||||||
*memory_ref.memory.borrow_mut() = Some(module_memory);
|
|
||||||
|
|
||||||
// now execute function => executor updates memory
|
|
||||||
assert_eq!(module_instance.execute_index(1, params.clone().add_argument(RuntimeValue::I32(7)).add_argument(RuntimeValue::I32(0))).unwrap().unwrap(),
|
|
||||||
RuntimeValue::I32(7));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn native_env_global() {
|
|
||||||
struct DummyExecutor;
|
|
||||||
impl UserFunctionExecutor for DummyExecutor {
|
|
||||||
fn execute(&mut self, _name: &str, _context: CallerContext) -> Result<Option<RuntimeValue>, Error> {
|
|
||||||
// this code should be unreachable, because we actually doesn't call any
|
|
||||||
// native functions in this test.
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let module_constructor = |elements: UserDefinedElements<DummyExecutor>| {
|
|
||||||
let program = program_with_default_env();
|
|
||||||
let env_instance = program.module("env").unwrap();
|
|
||||||
let native_env_instance = native_module(env_instance, elements).unwrap();
|
|
||||||
let params = ExecutionParams::with_external("env".into(), native_env_instance);
|
|
||||||
|
|
||||||
let module = module()
|
|
||||||
.with_import(ImportEntry::new("env".into(), "ext_global".into(), External::Global(GlobalType::new(ValueType::I32, false))))
|
|
||||||
.function()
|
|
||||||
.signature().return_type().i32().build()
|
|
||||||
.body().with_opcodes(Opcodes::new(vec![
|
|
||||||
Opcode::GetGlobal(0),
|
|
||||||
Opcode::End,
|
|
||||||
])).build()
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
program.add_module("main", module, Some(¶ms.externals))?
|
|
||||||
.execute_index(0, params.clone())
|
|
||||||
};
|
|
||||||
|
|
||||||
// try to add module, exporting non-existant env' variable => error
|
|
||||||
{
|
|
||||||
assert!(module_constructor(UserDefinedElements {
|
|
||||||
executor: None,
|
|
||||||
globals: HashMap::new(),
|
|
||||||
functions: ::std::borrow::Cow::from(NO_SIGNATURES),
|
|
||||||
}).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
// now add simple variable natively => ok
|
|
||||||
{
|
|
||||||
assert_eq!(module_constructor(UserDefinedElements {
|
|
||||||
executor: None,
|
|
||||||
globals: vec![("ext_global".into(), Arc::new(VariableInstance::new(false, VariableType::I32, RuntimeValue::I32(777)).unwrap()))].into_iter().collect(),
|
|
||||||
functions: ::std::borrow::Cow::from(NO_SIGNATURES),
|
|
||||||
}).unwrap().unwrap(), RuntimeValue::I32(777));
|
|
||||||
}
|
|
||||||
|
|
||||||
// now add 'getter+setter' variable natively => ok
|
|
||||||
{
|
|
||||||
assert_eq!(module_constructor(UserDefinedElements {
|
|
||||||
executor: None,
|
|
||||||
globals: vec![("ext_global".into(), Arc::new(VariableInstance::new_external_global(false, VariableType::I32, Box::new(MeasuredVariable { val: 345 })).unwrap()))].into_iter().collect(),
|
|
||||||
functions: ::std::borrow::Cow::from(NO_SIGNATURES),
|
|
||||||
}).unwrap().unwrap(), RuntimeValue::I32(345));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn native_custom_error() {
|
|
||||||
let program = program_with_default_env();
|
|
||||||
let env_instance = program.module("env").unwrap();
|
|
||||||
let env_memory = env_instance.memory(ItemIndex::Internal(0)).unwrap();
|
|
||||||
|
|
||||||
let mut executor = FunctionExecutor { memory: env_memory.clone(), values: Vec::new() };
|
|
||||||
let functions = UserDefinedElements {
|
|
||||||
executor: Some(&mut executor),
|
|
||||||
globals: HashMap::new(),
|
|
||||||
functions: ::std::borrow::Cow::from(SIGNATURES),
|
|
||||||
};
|
|
||||||
let native_env_instance = native_module(env_instance, functions).unwrap();
|
|
||||||
let params = ExecutionParams::with_external("env".into(), native_env_instance);
|
|
||||||
|
|
||||||
let module = module()
|
|
||||||
.with_import(ImportEntry::new("env".into(), "err".into(), External::Function(0)))
|
|
||||||
.function()
|
|
||||||
.signature().param().i32().param().i32().return_type().i32().build()
|
|
||||||
.body().with_opcodes(Opcodes::new(vec![
|
|
||||||
Opcode::GetLocal(0),
|
|
||||||
Opcode::GetLocal(1),
|
|
||||||
Opcode::Call(0),
|
|
||||||
Opcode::End,
|
|
||||||
])).build()
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let module_instance = program.add_module("main", module, Some(¶ms.externals)).unwrap();
|
|
||||||
let user_error1 = match module_instance.execute_index(
|
|
||||||
0,
|
|
||||||
params
|
|
||||||
.clone()
|
|
||||||
.add_argument(RuntimeValue::I32(7))
|
|
||||||
.add_argument(RuntimeValue::I32(0)),
|
|
||||||
) {
|
|
||||||
Err(Error::User(user_error)) => user_error,
|
|
||||||
result => panic!("Unexpected result {:?}", result),
|
|
||||||
};
|
|
||||||
assert_eq!(user_error1.downcast_ref::<UserErrorWithCode>().unwrap(), &UserErrorWithCode { error_code: 777 });
|
|
||||||
|
|
||||||
let user_error2 = match module_instance.execute_index(
|
|
||||||
0,
|
|
||||||
params
|
|
||||||
.clone()
|
|
||||||
.add_argument(RuntimeValue::I32(7))
|
|
||||||
.add_argument(RuntimeValue::I32(0)),
|
|
||||||
) {
|
|
||||||
Err(Error::User(user_error)) => user_error,
|
|
||||||
result => panic!("Unexpected result {:?}", result),
|
|
||||||
};
|
|
||||||
assert_eq!(user_error2.downcast_ref::<UserErrorWithCode>().unwrap(), &UserErrorWithCode { error_code: 777 });
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn env_native_export_entry_type_check() {
|
|
||||||
let program = program_with_default_env();
|
|
||||||
let env_instance = program.module("env").unwrap();
|
|
||||||
let env_memory = env_instance.memory(ItemIndex::Internal(0)).unwrap();
|
|
||||||
let mut function_executor = FunctionExecutor {
|
|
||||||
memory: env_memory,
|
|
||||||
values: Vec::new(),
|
|
||||||
};
|
|
||||||
let native_env_instance = native_module(env_instance, UserDefinedElements {
|
|
||||||
executor: Some(&mut function_executor),
|
|
||||||
globals: vec![("ext_global".into(), Arc::new(VariableInstance::new(false, VariableType::I32, RuntimeValue::I32(1312)).unwrap()))].into_iter().collect(),
|
|
||||||
functions: ::std::borrow::Cow::from(SIGNATURES),
|
|
||||||
}).unwrap();
|
|
||||||
|
|
||||||
assert!(native_env_instance.export_entry("add", &ExportEntryType::Function(FunctionSignature::Module(&FunctionType::new(vec![ValueType::I32, ValueType::I32], Some(ValueType::I32))))).is_ok());
|
|
||||||
match native_env_instance.export_entry("add", &ExportEntryType::Function(FunctionSignature::Module(&FunctionType::new(vec![], Some(ValueType::I32))))) {
|
|
||||||
Err(Error::Validation(_)) => { },
|
|
||||||
result => panic!("Unexpected result {:?}", result),
|
|
||||||
}
|
|
||||||
match native_env_instance.export_entry("add", &ExportEntryType::Function(FunctionSignature::Module(&FunctionType::new(vec![ValueType::I32, ValueType::I32], None)))) {
|
|
||||||
Err(Error::Validation(_)) => { },
|
|
||||||
result => panic!("Unexpected result {:?}", result),
|
|
||||||
}
|
|
||||||
match native_env_instance.export_entry("add", &ExportEntryType::Function(FunctionSignature::Module(&FunctionType::new(vec![ValueType::I32, ValueType::I32], Some(ValueType::I64))))) {
|
|
||||||
Err(Error::Validation(_)) => { },
|
|
||||||
result => panic!("Unexpected result {:?}", result),
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(native_env_instance.export_entry("ext_global", &ExportEntryType::Global(VariableType::I32)).is_ok());
|
|
||||||
match native_env_instance.export_entry("ext_global", &ExportEntryType::Global(VariableType::F32)) {
|
|
||||||
Err(Error::Validation(_)) => { },
|
|
||||||
result => panic!("Unexpected result {:?}", result),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn memory_import_limits_initial() {
|
|
||||||
let core_module = module()
|
|
||||||
.memory().with_min(10).build()
|
|
||||||
.with_export(ExportEntry::new("memory".into(), Internal::Memory(0)))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let program = ProgramInstance::new();
|
|
||||||
program.add_module("core", core_module, None).unwrap();
|
|
||||||
|
|
||||||
let test_cases = vec![
|
|
||||||
(9, false),
|
|
||||||
(10, false),
|
|
||||||
(11, true),
|
|
||||||
];
|
|
||||||
|
|
||||||
for test_case in test_cases {
|
|
||||||
let (import_initial, is_error) = test_case;
|
|
||||||
let client_module = module()
|
|
||||||
.with_import(ImportEntry::new("core".into(), "memory".into(), External::Memory(MemoryType::new(import_initial, None))))
|
|
||||||
.build();
|
|
||||||
match program.add_module("client", client_module, None).map(|_| ()) {
|
|
||||||
Ok(_) if !is_error => (),
|
|
||||||
Err(Error::Validation(ref actual_error))
|
|
||||||
if is_error && actual_error == &format!("trying to import memory with initial=10 and import.initial={}", import_initial) => (),
|
|
||||||
x @ _ => panic!("unexpected result for test_case {:?}: {:?}", test_case, x),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn memory_import_limits_maximum() {
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
enum MaximumError { ValueMismatch, Ok };
|
|
||||||
|
|
||||||
let test_cases = vec![
|
|
||||||
(None, Some(100), MaximumError::Ok),
|
|
||||||
(Some(100), None, MaximumError::Ok),
|
|
||||||
(Some(100), Some(98), MaximumError::ValueMismatch),
|
|
||||||
(Some(100), Some(100), MaximumError::Ok),
|
|
||||||
(Some(100), Some(101), MaximumError::Ok),
|
|
||||||
(None, None, MaximumError::Ok),
|
|
||||||
];
|
|
||||||
|
|
||||||
let program = ProgramInstance::new();
|
|
||||||
for test_case in test_cases {
|
|
||||||
let (core_maximum, client_maximum, expected_err) = test_case;
|
|
||||||
let core_module = module()
|
|
||||||
.memory().with_min(10).with_max(core_maximum).build()
|
|
||||||
.with_export(ExportEntry::new("memory".into(), Internal::Memory(0)))
|
|
||||||
.build();
|
|
||||||
let client_module = module()
|
|
||||||
.with_import(ImportEntry::new("core".into(), "memory".into(), External::Memory(MemoryType::new(10, client_maximum))))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
program.add_module("core", core_module, None).unwrap();
|
|
||||||
match program.add_module("client", client_module, None).map(|_| ()) {
|
|
||||||
Err(Error::Validation(actual_err)) => match expected_err {
|
|
||||||
MaximumError::ValueMismatch
|
|
||||||
if actual_err == format!("trying to import memory with maximum={} and import.maximum={}", core_maximum.unwrap_or_default(), client_maximum.unwrap_or_default()) => (),
|
|
||||||
_ => panic!("unexpected validation error for test_case {:?}: {}", test_case, actual_err),
|
|
||||||
},
|
|
||||||
Ok(_) if expected_err == MaximumError::Ok => (),
|
|
||||||
x @ _ => panic!("unexpected result for test_case {:?}: {:?}", test_case, x),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn table_import_limits_initial() {
|
|
||||||
let core_module = module()
|
|
||||||
.table().with_min(10).build()
|
|
||||||
.with_export(ExportEntry::new("table".into(), Internal::Table(0)))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let program = ProgramInstance::new();
|
|
||||||
program.add_module("core", core_module, None).unwrap();
|
|
||||||
|
|
||||||
let test_cases = vec![
|
|
||||||
(9, false),
|
|
||||||
(10, false),
|
|
||||||
(11, true),
|
|
||||||
];
|
|
||||||
|
|
||||||
for test_case in test_cases {
|
|
||||||
let (import_initial, is_error) = test_case;
|
|
||||||
let client_module = module()
|
|
||||||
.with_import(ImportEntry::new("core".into(), "table".into(), External::Table(TableType::new(import_initial, None))))
|
|
||||||
.build();
|
|
||||||
match program.add_module("client", client_module, None).map(|_| ()) {
|
|
||||||
Ok(_) if !is_error => (),
|
|
||||||
Err(Error::Validation(ref actual_error))
|
|
||||||
if is_error && actual_error == &format!("trying to import table with initial=10 and import.initial={}", import_initial) => (),
|
|
||||||
x @ _ => panic!("unexpected result for test_case {:?}: {:?}", test_case, x),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn table_import_limits_maximum() {
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
enum MaximumError { ValueMismatch, Ok };
|
|
||||||
|
|
||||||
let test_cases = vec![
|
|
||||||
(None, Some(100), MaximumError::Ok),
|
|
||||||
(Some(100), None, MaximumError::Ok),
|
|
||||||
(Some(100), Some(98), MaximumError::ValueMismatch),
|
|
||||||
(Some(100), Some(100), MaximumError::Ok),
|
|
||||||
(Some(100), Some(101), MaximumError::Ok),
|
|
||||||
(None, None, MaximumError::Ok),
|
|
||||||
];
|
|
||||||
|
|
||||||
let program = ProgramInstance::new();
|
|
||||||
for test_case in test_cases {
|
|
||||||
let (core_maximum, client_maximum, expected_err) = test_case;
|
|
||||||
let core_module = module()
|
|
||||||
.table().with_min(10).with_max(core_maximum).build()
|
|
||||||
.with_export(ExportEntry::new("table".into(), Internal::Table(0)))
|
|
||||||
.build();
|
|
||||||
let client_module = module()
|
|
||||||
.with_import(ImportEntry::new("core".into(), "table".into(), External::Table(TableType::new(10, client_maximum))))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
program.add_module("core", core_module, None).unwrap();
|
|
||||||
match program.add_module("client", client_module, None).map(|_| ()) {
|
|
||||||
Err(Error::Validation(actual_err)) => match expected_err {
|
|
||||||
MaximumError::ValueMismatch
|
|
||||||
if actual_err == format!("trying to import table with maximum={} and import.maximum={}", core_maximum.unwrap_or_default(), client_maximum.unwrap_or_default()) => (),
|
|
||||||
_ => panic!("unexpected validation error for test_case {:?}: {}", test_case, actual_err),
|
|
||||||
},
|
|
||||||
Ok(_) if expected_err == MaximumError::Ok => (),
|
|
||||||
x @ _ => panic!("unexpected result for test_case {:?}: {:?}", test_case, x),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
mod basics;
|
|
||||||
mod wabt;
|
|
||||||
mod wasm;
|
|
||||||
|
|
||||||
mod utils {
|
|
||||||
use elements::{Internal, ExportEntry, InitExpr, Opcode, ValueType, GlobalType, GlobalEntry};
|
|
||||||
use interpreter::ProgramInstance;
|
|
||||||
use builder::module;
|
|
||||||
|
|
||||||
pub fn program_with_default_env() -> ProgramInstance {
|
|
||||||
let program = ProgramInstance::new();
|
|
||||||
let env_module = module()
|
|
||||||
.memory()
|
|
||||||
.with_min(256) // 256 pages. 256 * 64K = 16MB
|
|
||||||
.build()
|
|
||||||
.with_export(ExportEntry::new("memory".into(), Internal::Memory(0)))
|
|
||||||
.table()
|
|
||||||
.with_min(64)
|
|
||||||
.build()
|
|
||||||
.with_export(ExportEntry::new("table".into(), Internal::Table(0)))
|
|
||||||
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, false), InitExpr::new(vec![Opcode::I32Const(0), Opcode::End])))
|
|
||||||
.with_export(ExportEntry::new("tableBase".into(), Internal::Global(0)))
|
|
||||||
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, false), InitExpr::new(vec![Opcode::I32Const(0), Opcode::End])))
|
|
||||||
.with_export(ExportEntry::new("memoryBase".into(), Internal::Global(1)))
|
|
||||||
.build();
|
|
||||||
program.add_module("env", env_module, None).unwrap();
|
|
||||||
program
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,77 +0,0 @@
|
|||||||
use elements::deserialize_file;
|
|
||||||
use elements::Module;
|
|
||||||
use interpreter::ExecutionParams;
|
|
||||||
use interpreter::value::RuntimeValue;
|
|
||||||
use interpreter::module::{ModuleInstanceInterface, ItemIndex};
|
|
||||||
use super::utils::program_with_default_env;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn interpreter_inc_i32() {
|
|
||||||
// Name of function contained in WASM file (note the leading underline)
|
|
||||||
const FUNCTION_NAME: &'static str = "_inc_i32";
|
|
||||||
// The WASM file containing the module and function
|
|
||||||
const WASM_FILE: &str = &"res/cases/v1/inc_i32.wasm";
|
|
||||||
|
|
||||||
let program = program_with_default_env();
|
|
||||||
|
|
||||||
let module: Module =
|
|
||||||
deserialize_file(WASM_FILE).expect("Failed to deserialize module from buffer");
|
|
||||||
let i32_val = 42;
|
|
||||||
// the functions expects a single i32 parameter
|
|
||||||
let args = vec![RuntimeValue::I32(i32_val)];
|
|
||||||
let exp_retval = Some(RuntimeValue::I32(i32_val + 1));
|
|
||||||
let execution_params = ExecutionParams::from(args);
|
|
||||||
|
|
||||||
let module_result = program
|
|
||||||
.add_module("main", module, None);
|
|
||||||
|
|
||||||
let module = module_result
|
|
||||||
.expect("Failed to initialize module");
|
|
||||||
|
|
||||||
let retval = module
|
|
||||||
.execute_export(FUNCTION_NAME, execution_params)
|
|
||||||
.expect("");
|
|
||||||
assert_eq!(exp_retval, retval);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn interpreter_accumulate_u8() {
|
|
||||||
// Name of function contained in WASM file (note the leading underline)
|
|
||||||
const FUNCTION_NAME: &'static str = "_accumulate_u8";
|
|
||||||
// The WASM file containing the module and function
|
|
||||||
const WASM_FILE: &str = &"res/cases/v1/accumulate_u8.wasm";
|
|
||||||
// The octet sequence being accumulated
|
|
||||||
const BUF: &[u8] = &[9,8,7,6,5,4,3,2,1];
|
|
||||||
|
|
||||||
let program = program_with_default_env();
|
|
||||||
|
|
||||||
// Load the module-structure from wasm-file and add to program
|
|
||||||
let module: Module =
|
|
||||||
deserialize_file(WASM_FILE).expect("Failed to deserialize module from buffer");
|
|
||||||
let module = program
|
|
||||||
.add_module("main", module, None)
|
|
||||||
.expect("Failed to initialize module");
|
|
||||||
|
|
||||||
// => env module is created
|
|
||||||
let env_instance = program.module("env").unwrap();
|
|
||||||
// => linear memory is created
|
|
||||||
let env_memory = env_instance.memory(ItemIndex::Internal(0)).unwrap();
|
|
||||||
|
|
||||||
// Place the octet-sequence at index 0 in linear memory
|
|
||||||
let offset: u32 = 0;
|
|
||||||
let _ = env_memory.set(offset, BUF);
|
|
||||||
|
|
||||||
// Set up the function argument list and invoke the function
|
|
||||||
let args = vec![RuntimeValue::I32(BUF.len() as i32), RuntimeValue::I32(offset as i32)];
|
|
||||||
let execution_params = ExecutionParams::from(args);
|
|
||||||
let retval = module
|
|
||||||
.execute_export(FUNCTION_NAME, execution_params)
|
|
||||||
.expect("Failed to execute function");
|
|
||||||
|
|
||||||
// For verification, repeat accumulation using native code
|
|
||||||
let accu = BUF.into_iter().fold(0 as i32, |a, b| a + *b as i32);
|
|
||||||
let exp_retval: Option<RuntimeValue> = Some(RuntimeValue::I32(accu));
|
|
||||||
|
|
||||||
// Verify calculation from WebAssembly runtime is identical to expected result
|
|
||||||
assert_eq!(exp_retval, retval);
|
|
||||||
}
|
|
@ -1,809 +0,0 @@
|
|||||||
use std::u32;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use elements::{Opcode, BlockType, ValueType};
|
|
||||||
use interpreter::Error;
|
|
||||||
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
|
||||||
use interpreter::module::{ModuleInstance, ModuleInstanceInterface, ItemIndex, FunctionSignature};
|
|
||||||
use common::stack::StackWithLimit;
|
|
||||||
use interpreter::variable::VariableType;
|
|
||||||
|
|
||||||
/// Constant from wabt' validator.cc to skip alignment validation (not a part of spec).
|
|
||||||
const NATURAL_ALIGNMENT: u32 = 0xFFFFFFFF;
|
|
||||||
|
|
||||||
/// Function validation context.
|
|
||||||
pub struct FunctionValidationContext<'a> {
|
|
||||||
/// Wasm module instance (in process of instantiation).
|
|
||||||
module_instance: &'a ModuleInstance,
|
|
||||||
/// Native externals.
|
|
||||||
externals: Option<&'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>>,
|
|
||||||
/// Current instruction position.
|
|
||||||
position: usize,
|
|
||||||
/// Local variables.
|
|
||||||
locals: &'a [ValueType],
|
|
||||||
/// Value stack.
|
|
||||||
value_stack: StackWithLimit<StackValueType>,
|
|
||||||
/// Frame stack.
|
|
||||||
frame_stack: StackWithLimit<BlockFrame>,
|
|
||||||
/// Function return type. None if validating expression.
|
|
||||||
return_type: Option<BlockType>,
|
|
||||||
/// Labels positions.
|
|
||||||
labels: HashMap<usize, usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Value type on the stack.
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum StackValueType {
|
|
||||||
/// Any value type.
|
|
||||||
Any,
|
|
||||||
/// Any number of any values of any type.
|
|
||||||
AnyUnlimited,
|
|
||||||
/// Concrete value type.
|
|
||||||
Specific(ValueType),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Control stack frame.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct BlockFrame {
|
|
||||||
/// Frame type.
|
|
||||||
pub frame_type: BlockFrameType,
|
|
||||||
/// A signature, which is a block signature type indicating the number and types of result values of the region.
|
|
||||||
pub block_type: BlockType,
|
|
||||||
/// A label for reference to block instruction.
|
|
||||||
pub begin_position: usize,
|
|
||||||
/// A label for reference from branch instructions.
|
|
||||||
pub branch_position: usize,
|
|
||||||
/// A label for reference from end instructions.
|
|
||||||
pub end_position: usize,
|
|
||||||
/// A limit integer value, which is an index into the value stack indicating where to reset it to on a branch to that label.
|
|
||||||
pub value_stack_len: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Type of block frame.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub enum BlockFrameType {
|
|
||||||
/// Function frame.
|
|
||||||
Function,
|
|
||||||
/// Usual block frame.
|
|
||||||
Block,
|
|
||||||
/// Loop frame (branching to the beginning of block).
|
|
||||||
Loop,
|
|
||||||
/// True-subblock of if expression.
|
|
||||||
IfTrue,
|
|
||||||
/// False-subblock of if expression.
|
|
||||||
IfFalse,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Function validator.
|
|
||||||
pub struct Validator;
|
|
||||||
|
|
||||||
/// Instruction outcome.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum InstructionOutcome {
|
|
||||||
/// Continue with next instruction.
|
|
||||||
ValidateNextInstruction,
|
|
||||||
/// Unreachable instruction reached.
|
|
||||||
Unreachable,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Validator {
|
|
||||||
pub fn validate_function(context: &mut FunctionValidationContext, block_type: BlockType, body: &[Opcode]) -> Result<(), Error> {
|
|
||||||
context.push_label(BlockFrameType::Function, block_type)?;
|
|
||||||
Validator::validate_function_block(context, body)?;
|
|
||||||
while !context.frame_stack.is_empty() {
|
|
||||||
context.pop_label()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_function_block(context: &mut FunctionValidationContext, body: &[Opcode]) -> Result<(), Error> {
|
|
||||||
let body_len = body.len();
|
|
||||||
if body_len == 0 {
|
|
||||||
return Err(Error::Validation("Non-empty function body expected".into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let opcode = &body[context.position];
|
|
||||||
match Validator::validate_instruction(context, opcode)? {
|
|
||||||
InstructionOutcome::ValidateNextInstruction => (),
|
|
||||||
InstructionOutcome::Unreachable => context.unreachable()?,
|
|
||||||
}
|
|
||||||
|
|
||||||
context.position += 1;
|
|
||||||
if context.position >= body_len {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_instruction(context: &mut FunctionValidationContext, opcode: &Opcode) -> Result<InstructionOutcome, Error> {
|
|
||||||
debug!(target: "validator", "validating {:?}", opcode);
|
|
||||||
match opcode {
|
|
||||||
&Opcode::Unreachable => Ok(InstructionOutcome::Unreachable),
|
|
||||||
&Opcode::Nop => Ok(InstructionOutcome::ValidateNextInstruction),
|
|
||||||
&Opcode::Block(block_type) => Validator::validate_block(context, block_type),
|
|
||||||
&Opcode::Loop(block_type) => Validator::validate_loop(context, block_type),
|
|
||||||
&Opcode::If(block_type) => Validator::validate_if(context, block_type),
|
|
||||||
&Opcode::Else => Validator::validate_else(context),
|
|
||||||
&Opcode::End => Validator::validate_end(context),
|
|
||||||
&Opcode::Br(idx) => Validator::validate_br(context, idx),
|
|
||||||
&Opcode::BrIf(idx) => Validator::validate_br_if(context, idx),
|
|
||||||
&Opcode::BrTable(ref table, default) => Validator::validate_br_table(context, table, default),
|
|
||||||
&Opcode::Return => Validator::validate_return(context),
|
|
||||||
|
|
||||||
&Opcode::Call(index) => Validator::validate_call(context, index),
|
|
||||||
&Opcode::CallIndirect(index, _reserved) => Validator::validate_call_indirect(context, index),
|
|
||||||
|
|
||||||
&Opcode::Drop => Validator::validate_drop(context),
|
|
||||||
&Opcode::Select => Validator::validate_select(context),
|
|
||||||
|
|
||||||
&Opcode::GetLocal(index) => Validator::validate_get_local(context, index),
|
|
||||||
&Opcode::SetLocal(index) => Validator::validate_set_local(context, index),
|
|
||||||
&Opcode::TeeLocal(index) => Validator::validate_tee_local(context, index),
|
|
||||||
&Opcode::GetGlobal(index) => Validator::validate_get_global(context, index),
|
|
||||||
&Opcode::SetGlobal(index) => Validator::validate_set_global(context, index),
|
|
||||||
|
|
||||||
&Opcode::I32Load(align, _) => Validator::validate_load(context, align, 4, ValueType::I32.into()),
|
|
||||||
&Opcode::I64Load(align, _) => Validator::validate_load(context, align, 8, ValueType::I64.into()),
|
|
||||||
&Opcode::F32Load(align, _) => Validator::validate_load(context, align, 4, ValueType::F32.into()),
|
|
||||||
&Opcode::F64Load(align, _) => Validator::validate_load(context, align, 8, ValueType::F64.into()),
|
|
||||||
&Opcode::I32Load8S(align, _) => Validator::validate_load(context, align, 1, ValueType::I32.into()),
|
|
||||||
&Opcode::I32Load8U(align, _) => Validator::validate_load(context, align, 1, ValueType::I32.into()),
|
|
||||||
&Opcode::I32Load16S(align, _) => Validator::validate_load(context, align, 2, ValueType::I32.into()),
|
|
||||||
&Opcode::I32Load16U(align, _) => Validator::validate_load(context, align, 2, ValueType::I32.into()),
|
|
||||||
&Opcode::I64Load8S(align, _) => Validator::validate_load(context, align, 1, ValueType::I64.into()),
|
|
||||||
&Opcode::I64Load8U(align, _) => Validator::validate_load(context, align, 1, ValueType::I64.into()),
|
|
||||||
&Opcode::I64Load16S(align, _) => Validator::validate_load(context, align, 2, ValueType::I64.into()),
|
|
||||||
&Opcode::I64Load16U(align, _) => Validator::validate_load(context, align, 2, ValueType::I64.into()),
|
|
||||||
&Opcode::I64Load32S(align, _) => Validator::validate_load(context, align, 4, ValueType::I64.into()),
|
|
||||||
&Opcode::I64Load32U(align, _) => Validator::validate_load(context, align, 4, ValueType::I64.into()),
|
|
||||||
|
|
||||||
&Opcode::I32Store(align, _) => Validator::validate_store(context, align, 4, ValueType::I32.into()),
|
|
||||||
&Opcode::I64Store(align, _) => Validator::validate_store(context, align, 8, ValueType::I64.into()),
|
|
||||||
&Opcode::F32Store(align, _) => Validator::validate_store(context, align, 4, ValueType::F32.into()),
|
|
||||||
&Opcode::F64Store(align, _) => Validator::validate_store(context, align, 8, ValueType::F64.into()),
|
|
||||||
&Opcode::I32Store8(align, _) => Validator::validate_store(context, align, 1, ValueType::I32.into()),
|
|
||||||
&Opcode::I32Store16(align, _) => Validator::validate_store(context, align, 2, ValueType::I32.into()),
|
|
||||||
&Opcode::I64Store8(align, _) => Validator::validate_store(context, align, 1, ValueType::I64.into()),
|
|
||||||
&Opcode::I64Store16(align, _) => Validator::validate_store(context, align, 2, ValueType::I64.into()),
|
|
||||||
&Opcode::I64Store32(align, _) => Validator::validate_store(context, align, 4, ValueType::I64.into()),
|
|
||||||
|
|
||||||
&Opcode::CurrentMemory(_) => Validator::validate_current_memory(context),
|
|
||||||
&Opcode::GrowMemory(_) => Validator::validate_grow_memory(context),
|
|
||||||
|
|
||||||
&Opcode::I32Const(_) => Validator::validate_const(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I64Const(_) => Validator::validate_const(context, ValueType::I64.into()),
|
|
||||||
&Opcode::F32Const(_) => Validator::validate_const(context, ValueType::F32.into()),
|
|
||||||
&Opcode::F64Const(_) => Validator::validate_const(context, ValueType::F64.into()),
|
|
||||||
|
|
||||||
&Opcode::I32Eqz => Validator::validate_testop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32Eq => Validator::validate_relop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32Ne => Validator::validate_relop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32LtS => Validator::validate_relop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32LtU => Validator::validate_relop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32GtS => Validator::validate_relop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32GtU => Validator::validate_relop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32LeS => Validator::validate_relop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32LeU => Validator::validate_relop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32GeS => Validator::validate_relop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32GeU => Validator::validate_relop(context, ValueType::I32.into()),
|
|
||||||
|
|
||||||
&Opcode::I64Eqz => Validator::validate_testop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64Eq => Validator::validate_relop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64Ne => Validator::validate_relop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64LtS => Validator::validate_relop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64LtU => Validator::validate_relop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64GtS => Validator::validate_relop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64GtU => Validator::validate_relop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64LeS => Validator::validate_relop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64LeU => Validator::validate_relop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64GeS => Validator::validate_relop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64GeU => Validator::validate_relop(context, ValueType::I64.into()),
|
|
||||||
|
|
||||||
&Opcode::F32Eq => Validator::validate_relop(context, ValueType::F32.into()),
|
|
||||||
&Opcode::F32Ne => Validator::validate_relop(context, ValueType::F32.into()),
|
|
||||||
&Opcode::F32Lt => Validator::validate_relop(context, ValueType::F32.into()),
|
|
||||||
&Opcode::F32Gt => Validator::validate_relop(context, ValueType::F32.into()),
|
|
||||||
&Opcode::F32Le => Validator::validate_relop(context, ValueType::F32.into()),
|
|
||||||
&Opcode::F32Ge => Validator::validate_relop(context, ValueType::F32.into()),
|
|
||||||
|
|
||||||
&Opcode::F64Eq => Validator::validate_relop(context, ValueType::F64.into()),
|
|
||||||
&Opcode::F64Ne => Validator::validate_relop(context, ValueType::F64.into()),
|
|
||||||
&Opcode::F64Lt => Validator::validate_relop(context, ValueType::F64.into()),
|
|
||||||
&Opcode::F64Gt => Validator::validate_relop(context, ValueType::F64.into()),
|
|
||||||
&Opcode::F64Le => Validator::validate_relop(context, ValueType::F64.into()),
|
|
||||||
&Opcode::F64Ge => Validator::validate_relop(context, ValueType::F64.into()),
|
|
||||||
|
|
||||||
&Opcode::I32Clz => Validator::validate_unop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32Ctz => Validator::validate_unop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32Popcnt => Validator::validate_unop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32Add => Validator::validate_binop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32Sub => Validator::validate_binop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32Mul => Validator::validate_binop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32DivS => Validator::validate_binop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32DivU => Validator::validate_binop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32RemS => Validator::validate_binop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32RemU => Validator::validate_binop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32And => Validator::validate_binop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32Or => Validator::validate_binop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32Xor => Validator::validate_binop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32Shl => Validator::validate_binop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32ShrS => Validator::validate_binop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32ShrU => Validator::validate_binop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32Rotl => Validator::validate_binop(context, ValueType::I32.into()),
|
|
||||||
&Opcode::I32Rotr => Validator::validate_binop(context, ValueType::I32.into()),
|
|
||||||
|
|
||||||
&Opcode::I64Clz => Validator::validate_unop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64Ctz => Validator::validate_unop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64Popcnt => Validator::validate_unop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64Add => Validator::validate_binop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64Sub => Validator::validate_binop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64Mul => Validator::validate_binop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64DivS => Validator::validate_binop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64DivU => Validator::validate_binop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64RemS => Validator::validate_binop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64RemU => Validator::validate_binop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64And => Validator::validate_binop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64Or => Validator::validate_binop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64Xor => Validator::validate_binop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64Shl => Validator::validate_binop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64ShrS => Validator::validate_binop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64ShrU => Validator::validate_binop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64Rotl => Validator::validate_binop(context, ValueType::I64.into()),
|
|
||||||
&Opcode::I64Rotr => Validator::validate_binop(context, ValueType::I64.into()),
|
|
||||||
|
|
||||||
&Opcode::F32Abs => Validator::validate_unop(context, ValueType::F32.into()),
|
|
||||||
&Opcode::F32Neg => Validator::validate_unop(context, ValueType::F32.into()),
|
|
||||||
&Opcode::F32Ceil => Validator::validate_unop(context, ValueType::F32.into()),
|
|
||||||
&Opcode::F32Floor => Validator::validate_unop(context, ValueType::F32.into()),
|
|
||||||
&Opcode::F32Trunc => Validator::validate_unop(context, ValueType::F32.into()),
|
|
||||||
&Opcode::F32Nearest => Validator::validate_unop(context, ValueType::F32.into()),
|
|
||||||
&Opcode::F32Sqrt => Validator::validate_unop(context, ValueType::F32.into()),
|
|
||||||
&Opcode::F32Add => Validator::validate_binop(context, ValueType::F32.into()),
|
|
||||||
&Opcode::F32Sub => Validator::validate_binop(context, ValueType::F32.into()),
|
|
||||||
&Opcode::F32Mul => Validator::validate_binop(context, ValueType::F32.into()),
|
|
||||||
&Opcode::F32Div => Validator::validate_binop(context, ValueType::F32.into()),
|
|
||||||
&Opcode::F32Min => Validator::validate_binop(context, ValueType::F32.into()),
|
|
||||||
&Opcode::F32Max => Validator::validate_binop(context, ValueType::F32.into()),
|
|
||||||
&Opcode::F32Copysign => Validator::validate_binop(context, ValueType::F32.into()),
|
|
||||||
|
|
||||||
&Opcode::F64Abs => Validator::validate_unop(context, ValueType::F64.into()),
|
|
||||||
&Opcode::F64Neg => Validator::validate_unop(context, ValueType::F64.into()),
|
|
||||||
&Opcode::F64Ceil => Validator::validate_unop(context, ValueType::F64.into()),
|
|
||||||
&Opcode::F64Floor => Validator::validate_unop(context, ValueType::F64.into()),
|
|
||||||
&Opcode::F64Trunc => Validator::validate_unop(context, ValueType::F64.into()),
|
|
||||||
&Opcode::F64Nearest => Validator::validate_unop(context, ValueType::F64.into()),
|
|
||||||
&Opcode::F64Sqrt => Validator::validate_unop(context, ValueType::F64.into()),
|
|
||||||
&Opcode::F64Add => Validator::validate_binop(context, ValueType::F64.into()),
|
|
||||||
&Opcode::F64Sub => Validator::validate_binop(context, ValueType::F64.into()),
|
|
||||||
&Opcode::F64Mul => Validator::validate_binop(context, ValueType::F64.into()),
|
|
||||||
&Opcode::F64Div => Validator::validate_binop(context, ValueType::F64.into()),
|
|
||||||
&Opcode::F64Min => Validator::validate_binop(context, ValueType::F64.into()),
|
|
||||||
&Opcode::F64Max => Validator::validate_binop(context, ValueType::F64.into()),
|
|
||||||
&Opcode::F64Copysign => Validator::validate_binop(context, ValueType::F64.into()),
|
|
||||||
|
|
||||||
&Opcode::I32WrapI64 => Validator::validate_cvtop(context, ValueType::I64.into(), ValueType::I32.into()),
|
|
||||||
&Opcode::I32TruncSF32 => Validator::validate_cvtop(context, ValueType::F32.into(), ValueType::I32.into()),
|
|
||||||
&Opcode::I32TruncUF32 => Validator::validate_cvtop(context, ValueType::F32.into(), ValueType::I32.into()),
|
|
||||||
&Opcode::I32TruncSF64 => Validator::validate_cvtop(context, ValueType::F64.into(), ValueType::I32.into()),
|
|
||||||
&Opcode::I32TruncUF64 => Validator::validate_cvtop(context, ValueType::F64.into(), ValueType::I32.into()),
|
|
||||||
&Opcode::I64ExtendSI32 => Validator::validate_cvtop(context, ValueType::I32.into(), ValueType::I64.into()),
|
|
||||||
&Opcode::I64ExtendUI32 => Validator::validate_cvtop(context, ValueType::I32.into(), ValueType::I64.into()),
|
|
||||||
&Opcode::I64TruncSF32 => Validator::validate_cvtop(context, ValueType::F32.into(), ValueType::I64.into()),
|
|
||||||
&Opcode::I64TruncUF32 => Validator::validate_cvtop(context, ValueType::F32.into(), ValueType::I64.into()),
|
|
||||||
&Opcode::I64TruncSF64 => Validator::validate_cvtop(context, ValueType::F64.into(), ValueType::I64.into()),
|
|
||||||
&Opcode::I64TruncUF64 => Validator::validate_cvtop(context, ValueType::F64.into(), ValueType::I64.into()),
|
|
||||||
&Opcode::F32ConvertSI32 => Validator::validate_cvtop(context, ValueType::I32.into(), ValueType::F32.into()),
|
|
||||||
&Opcode::F32ConvertUI32 => Validator::validate_cvtop(context, ValueType::I32.into(), ValueType::F32.into()),
|
|
||||||
&Opcode::F32ConvertSI64 => Validator::validate_cvtop(context, ValueType::I64.into(), ValueType::F32.into()),
|
|
||||||
&Opcode::F32ConvertUI64 => Validator::validate_cvtop(context, ValueType::I64.into(), ValueType::F32.into()),
|
|
||||||
&Opcode::F32DemoteF64 => Validator::validate_cvtop(context, ValueType::F64.into(), ValueType::F32.into()),
|
|
||||||
&Opcode::F64ConvertSI32 => Validator::validate_cvtop(context, ValueType::I32.into(), ValueType::F64.into()),
|
|
||||||
&Opcode::F64ConvertUI32 => Validator::validate_cvtop(context, ValueType::I32.into(), ValueType::F64.into()),
|
|
||||||
&Opcode::F64ConvertSI64 => Validator::validate_cvtop(context, ValueType::I64.into(), ValueType::F64.into()),
|
|
||||||
&Opcode::F64ConvertUI64 => Validator::validate_cvtop(context, ValueType::I64.into(), ValueType::F64.into()),
|
|
||||||
&Opcode::F64PromoteF32 => Validator::validate_cvtop(context, ValueType::F32.into(), ValueType::F64.into()),
|
|
||||||
|
|
||||||
&Opcode::I32ReinterpretF32 => Validator::validate_cvtop(context, ValueType::F32.into(), ValueType::I32.into()),
|
|
||||||
&Opcode::I64ReinterpretF64 => Validator::validate_cvtop(context, ValueType::F64.into(), ValueType::I64.into()),
|
|
||||||
&Opcode::F32ReinterpretI32 => Validator::validate_cvtop(context, ValueType::I32.into(), ValueType::F32.into()),
|
|
||||||
&Opcode::F64ReinterpretI64 => Validator::validate_cvtop(context, ValueType::I64.into(), ValueType::F64.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_const(context: &mut FunctionValidationContext, value_type: StackValueType) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.push_value(value_type)?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_unop(context: &mut FunctionValidationContext, value_type: StackValueType) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.pop_value(value_type)?;
|
|
||||||
context.push_value(value_type)?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_binop(context: &mut FunctionValidationContext, value_type: StackValueType) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.pop_value(value_type)?;
|
|
||||||
context.pop_value(value_type)?;
|
|
||||||
context.push_value(value_type)?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_testop(context: &mut FunctionValidationContext, value_type: StackValueType) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.pop_value(value_type)?;
|
|
||||||
context.push_value(ValueType::I32.into())?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_relop(context: &mut FunctionValidationContext, value_type: StackValueType) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.pop_value(value_type)?;
|
|
||||||
context.pop_value(value_type)?;
|
|
||||||
context.push_value(ValueType::I32.into())?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_cvtop(context: &mut FunctionValidationContext, value_type1: StackValueType, value_type2: StackValueType) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.pop_value(value_type1)?;
|
|
||||||
context.push_value(value_type2)?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_drop(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.pop_any_value().map(|_| ())?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_select(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.pop_value(ValueType::I32.into())?;
|
|
||||||
let select_type = context.pop_any_value()?;
|
|
||||||
context.pop_value(select_type)?;
|
|
||||||
context.push_value(select_type)?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_get_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
let local_type = context.require_local(index)?;
|
|
||||||
context.push_value(local_type)?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_set_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
let local_type = context.require_local(index)?;
|
|
||||||
let value_type = context.pop_any_value()?;
|
|
||||||
if local_type != value_type {
|
|
||||||
return Err(Error::Validation(format!("Trying to update local {} of type {:?} with value of type {:?}", index, local_type, value_type)));
|
|
||||||
}
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_tee_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
let local_type = context.require_local(index)?;
|
|
||||||
let value_type = context.tee_any_value()?;
|
|
||||||
if local_type != value_type {
|
|
||||||
return Err(Error::Validation(format!("Trying to update local {} of type {:?} with value of type {:?}", index, local_type, value_type)));
|
|
||||||
}
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_get_global(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
let global_type = context.require_global(index, None)?;
|
|
||||||
context.push_value(global_type)?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_set_global(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
let global_type = context.require_global(index, Some(true))?;
|
|
||||||
let value_type = context.pop_any_value()?;
|
|
||||||
if global_type != value_type {
|
|
||||||
return Err(Error::Validation(format!("Trying to update global {} of type {:?} with value of type {:?}", index, global_type, value_type)));
|
|
||||||
}
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_load(context: &mut FunctionValidationContext, align: u32, max_align: u32, value_type: StackValueType) -> Result<InstructionOutcome, Error> {
|
|
||||||
if align != NATURAL_ALIGNMENT {
|
|
||||||
if 1u32.checked_shl(align).unwrap_or(u32::MAX) > max_align {
|
|
||||||
return Err(Error::Validation(format!("Too large memory alignment 2^{} (expected at most {})", align, max_align)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.pop_value(ValueType::I32.into())?;
|
|
||||||
context.require_memory(DEFAULT_MEMORY_INDEX)?;
|
|
||||||
context.push_value(value_type)?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_store(context: &mut FunctionValidationContext, align: u32, max_align: u32, value_type: StackValueType) -> Result<InstructionOutcome, Error> {
|
|
||||||
if align != NATURAL_ALIGNMENT {
|
|
||||||
if 1u32.checked_shl(align).unwrap_or(u32::MAX) > max_align {
|
|
||||||
return Err(Error::Validation(format!("Too large memory alignment 2^{} (expected at most {})", align, max_align)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.require_memory(DEFAULT_MEMORY_INDEX)?;
|
|
||||||
context.pop_value(value_type)?;
|
|
||||||
context.pop_value(ValueType::I32.into())?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_block(context: &mut FunctionValidationContext, block_type: BlockType) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.push_label(BlockFrameType::Block, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_loop(context: &mut FunctionValidationContext, block_type: BlockType) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.push_label(BlockFrameType::Loop, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_if(context: &mut FunctionValidationContext, block_type: BlockType) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.pop_value(ValueType::I32.into())?;
|
|
||||||
context.push_label(BlockFrameType::IfTrue, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_else(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
|
||||||
let block_type = {
|
|
||||||
let top_frame = context.top_label()?;
|
|
||||||
if top_frame.frame_type != BlockFrameType::IfTrue {
|
|
||||||
return Err(Error::Validation("Misplaced else instruction".into()));
|
|
||||||
}
|
|
||||||
top_frame.block_type
|
|
||||||
};
|
|
||||||
context.pop_label()?;
|
|
||||||
|
|
||||||
if let BlockType::Value(value_type) = block_type {
|
|
||||||
context.pop_value(value_type.into())?;
|
|
||||||
}
|
|
||||||
context.push_label(BlockFrameType::IfFalse, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_end(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
|
||||||
{
|
|
||||||
let top_frame = context.top_label()?;
|
|
||||||
if top_frame.frame_type == BlockFrameType::IfTrue {
|
|
||||||
if top_frame.block_type != BlockType::NoResult {
|
|
||||||
return Err(Error::Validation(format!("If block without else required to have NoResult block type. But it have {:?} type", top_frame.block_type)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.pop_label().map(|_| InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_br(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
let (frame_type, frame_block_type) = {
|
|
||||||
let frame = context.require_label(idx)?;
|
|
||||||
(frame.frame_type, frame.block_type)
|
|
||||||
};
|
|
||||||
if frame_type != BlockFrameType::Loop {
|
|
||||||
if let BlockType::Value(value_type) = frame_block_type {
|
|
||||||
context.tee_value(value_type.into())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(InstructionOutcome::Unreachable)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_br_if(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.pop_value(ValueType::I32.into())?;
|
|
||||||
|
|
||||||
let (frame_type, frame_block_type) = {
|
|
||||||
let frame = context.require_label(idx)?;
|
|
||||||
(frame.frame_type, frame.block_type)
|
|
||||||
};
|
|
||||||
if frame_type != BlockFrameType::Loop {
|
|
||||||
if let BlockType::Value(value_type) = frame_block_type {
|
|
||||||
context.tee_value(value_type.into())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_br_table(context: &mut FunctionValidationContext, table: &[u32], default: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
let mut required_block_type = None;
|
|
||||||
|
|
||||||
{
|
|
||||||
let default_block = context.require_label(default)?;
|
|
||||||
if default_block.frame_type != BlockFrameType::Loop {
|
|
||||||
required_block_type = Some(default_block.block_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
for label in table {
|
|
||||||
let label_block = context.require_label(*label)?;
|
|
||||||
if label_block.frame_type != BlockFrameType::Loop {
|
|
||||||
if let Some(required_block_type) = required_block_type {
|
|
||||||
if required_block_type != label_block.block_type {
|
|
||||||
return Err(Error::Validation(format!("Labels in br_table points to block of different types: {:?} and {:?}", required_block_type, label_block.block_type)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
required_block_type = Some(label_block.block_type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.pop_value(ValueType::I32.into())?;
|
|
||||||
if let Some(required_block_type) = required_block_type {
|
|
||||||
if let BlockType::Value(value_type) = required_block_type {
|
|
||||||
context.tee_value(value_type.into())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(InstructionOutcome::Unreachable)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_return(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
|
||||||
if let BlockType::Value(value_type) = context.return_type()? {
|
|
||||||
context.tee_value(value_type.into())?;
|
|
||||||
}
|
|
||||||
Ok(InstructionOutcome::Unreachable)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_call(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
let (argument_types, return_type) = context.require_function(idx)?;
|
|
||||||
for argument_type in argument_types.iter().rev() {
|
|
||||||
context.pop_value((*argument_type).into())?;
|
|
||||||
}
|
|
||||||
if let BlockType::Value(value_type) = return_type {
|
|
||||||
context.push_value(value_type.into())?;
|
|
||||||
}
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_call_indirect(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.require_table(DEFAULT_TABLE_INDEX, VariableType::AnyFunc)?;
|
|
||||||
|
|
||||||
context.pop_value(ValueType::I32.into())?;
|
|
||||||
let (argument_types, return_type) = context.require_function_type(idx)?;
|
|
||||||
for argument_type in argument_types.iter().rev() {
|
|
||||||
context.pop_value((*argument_type).into())?;
|
|
||||||
}
|
|
||||||
if let BlockType::Value(value_type) = return_type {
|
|
||||||
context.push_value(value_type.into())?;
|
|
||||||
}
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_current_memory(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.require_memory(DEFAULT_MEMORY_INDEX)?;
|
|
||||||
context.push_value(ValueType::I32.into())?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_grow_memory(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.require_memory(DEFAULT_MEMORY_INDEX)?;
|
|
||||||
context.pop_value(ValueType::I32.into())?;
|
|
||||||
context.push_value(ValueType::I32.into())?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> FunctionValidationContext<'a> {
|
|
||||||
pub fn new(
|
|
||||||
module_instance: &'a ModuleInstance,
|
|
||||||
externals: Option<&'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>>,
|
|
||||||
locals: &'a [ValueType],
|
|
||||||
value_stack_limit: usize,
|
|
||||||
frame_stack_limit: usize,
|
|
||||||
function: FunctionSignature,
|
|
||||||
) -> Self {
|
|
||||||
FunctionValidationContext {
|
|
||||||
module_instance: module_instance,
|
|
||||||
externals: externals,
|
|
||||||
position: 0,
|
|
||||||
locals: locals,
|
|
||||||
value_stack: StackWithLimit::with_limit(value_stack_limit),
|
|
||||||
frame_stack: StackWithLimit::with_limit(frame_stack_limit),
|
|
||||||
return_type: Some(function.return_type().map(BlockType::Value).unwrap_or(BlockType::NoResult)),
|
|
||||||
labels: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
|
||||||
Ok(self.value_stack.push(value_type.into())?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
|
||||||
self.check_stack_access()?;
|
|
||||||
match self.value_stack.pop()? {
|
|
||||||
StackValueType::Specific(stack_value_type) if stack_value_type == value_type => Ok(()),
|
|
||||||
StackValueType::Any => Ok(()),
|
|
||||||
StackValueType::AnyUnlimited => {
|
|
||||||
self.value_stack.push(StackValueType::AnyUnlimited)?;
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
stack_value_type @ _ => Err(Error::Validation(format!("Expected value of type {:?} on top of stack. Got {:?}", value_type, stack_value_type))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tee_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
|
||||||
self.check_stack_access()?;
|
|
||||||
match *self.value_stack.top()? {
|
|
||||||
StackValueType::Specific(stack_value_type) if stack_value_type == value_type => Ok(()),
|
|
||||||
StackValueType::Any | StackValueType::AnyUnlimited => Ok(()),
|
|
||||||
stack_value_type @ _ => Err(Error::Validation(format!("Expected value of type {:?} on top of stack. Got {:?}", value_type, stack_value_type))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop_any_value(&mut self) -> Result<StackValueType, Error> {
|
|
||||||
self.check_stack_access()?;
|
|
||||||
match self.value_stack.pop()? {
|
|
||||||
StackValueType::Specific(stack_value_type) => Ok(StackValueType::Specific(stack_value_type)),
|
|
||||||
StackValueType::Any => Ok(StackValueType::Any),
|
|
||||||
StackValueType::AnyUnlimited => {
|
|
||||||
self.value_stack.push(StackValueType::AnyUnlimited)?;
|
|
||||||
Ok(StackValueType::Any)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tee_any_value(&mut self) -> Result<StackValueType, Error> {
|
|
||||||
self.check_stack_access()?;
|
|
||||||
Ok(self.value_stack.top().map(Clone::clone)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unreachable(&mut self) -> Result<(), Error> {
|
|
||||||
Ok(self.value_stack.push(StackValueType::AnyUnlimited)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn top_label(&self) -> Result<&BlockFrame, Error> {
|
|
||||||
Ok(self.frame_stack.top()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_label(&mut self, frame_type: BlockFrameType, block_type: BlockType) -> Result<(), Error> {
|
|
||||||
Ok(self.frame_stack.push(BlockFrame {
|
|
||||||
frame_type: frame_type,
|
|
||||||
block_type: block_type,
|
|
||||||
begin_position: self.position,
|
|
||||||
branch_position: self.position,
|
|
||||||
end_position: self.position,
|
|
||||||
value_stack_len: self.value_stack.len(),
|
|
||||||
})?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop_label(&mut self) -> Result<InstructionOutcome, Error> {
|
|
||||||
let frame = self.frame_stack.pop()?;
|
|
||||||
let actual_value_type = if self.value_stack.len() > frame.value_stack_len {
|
|
||||||
Some(self.value_stack.pop()?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
self.value_stack.resize(frame.value_stack_len, StackValueType::Any);
|
|
||||||
|
|
||||||
match frame.block_type {
|
|
||||||
BlockType::NoResult if actual_value_type.map(|vt| vt.is_any_unlimited()).unwrap_or(true) => (),
|
|
||||||
BlockType::Value(required_value_type) if actual_value_type.map(|vt| vt == required_value_type).unwrap_or(false) => (),
|
|
||||||
_ => return Err(Error::Validation(format!("Expected block to return {:?} while it has returned {:?}", frame.block_type, actual_value_type))),
|
|
||||||
}
|
|
||||||
if !self.frame_stack.is_empty() {
|
|
||||||
self.labels.insert(frame.begin_position, self.position);
|
|
||||||
}
|
|
||||||
if let BlockType::Value(value_type) = frame.block_type {
|
|
||||||
self.push_value(value_type.into())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn require_label(&self, idx: u32) -> Result<&BlockFrame, Error> {
|
|
||||||
Ok(self.frame_stack.get(idx as usize)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn return_type(&self) -> Result<BlockType, Error> {
|
|
||||||
self.return_type.ok_or(Error::Validation("Trying to return from expression".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn require_local(&self, idx: u32) -> Result<StackValueType, Error> {
|
|
||||||
self.locals.get(idx as usize)
|
|
||||||
.cloned()
|
|
||||||
.map(Into::into)
|
|
||||||
.ok_or(Error::Validation(format!("Trying to access local with index {} when there are only {} locals", idx, self.locals.len())))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn require_global(&self, idx: u32, mutability: Option<bool>) -> Result<StackValueType, Error> {
|
|
||||||
self.module_instance
|
|
||||||
.global(ItemIndex::IndexSpace(idx), None, self.externals.clone())
|
|
||||||
.and_then(|g| match mutability {
|
|
||||||
Some(true) if !g.is_mutable() => Err(Error::Validation(format!("Expected global {} to be mutable", idx))),
|
|
||||||
Some(false) if g.is_mutable() => Err(Error::Validation(format!("Expected global {} to be immutable", idx))),
|
|
||||||
_ => match g.variable_type() {
|
|
||||||
VariableType::AnyFunc => Err(Error::Validation(format!("Expected global {} to have non-AnyFunc type", idx))),
|
|
||||||
VariableType::I32 => Ok(StackValueType::Specific(ValueType::I32)),
|
|
||||||
VariableType::I64 => Ok(StackValueType::Specific(ValueType::I64)),
|
|
||||||
VariableType::F32 => Ok(StackValueType::Specific(ValueType::F32)),
|
|
||||||
VariableType::F64 => Ok(StackValueType::Specific(ValueType::F64)),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn require_memory(&self, idx: u32) -> Result<(), Error> {
|
|
||||||
self.module_instance
|
|
||||||
.memory(ItemIndex::IndexSpace(idx))
|
|
||||||
.map(|_| ())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn require_table(&self, idx: u32, variable_type: VariableType) -> Result<(), Error> {
|
|
||||||
self.module_instance
|
|
||||||
.table(ItemIndex::IndexSpace(idx))
|
|
||||||
.and_then(|t| if t.variable_type() == variable_type {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(Error::Validation(format!("Table {} has element type {:?} while {:?} expected", idx, t.variable_type(), variable_type)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn require_function(&self, idx: u32) -> Result<(Vec<ValueType>, BlockType), Error> {
|
|
||||||
self.module_instance.function_type(ItemIndex::IndexSpace(idx))
|
|
||||||
.map(|ft| (ft.params().to_vec(), ft.return_type().map(BlockType::Value).unwrap_or(BlockType::NoResult)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn require_function_type(&self, idx: u32) -> Result<(Vec<ValueType>, BlockType), Error> {
|
|
||||||
self.module_instance.function_type_by_index(idx)
|
|
||||||
.map(|ft| (ft.params().to_vec(), ft.return_type().map(BlockType::Value).unwrap_or(BlockType::NoResult)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn function_labels(self) -> HashMap<usize, usize> {
|
|
||||||
self.labels
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_stack_access(&self) -> Result<(), Error> {
|
|
||||||
let value_stack_min = self.frame_stack.top().expect("at least 1 topmost block").value_stack_len;
|
|
||||||
if self.value_stack.len() > value_stack_min {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(Error::Validation("Trying to access parent frame stack values.".into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StackValueType {
|
|
||||||
pub fn is_any(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
&StackValueType::Any => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_any_unlimited(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
&StackValueType::AnyUnlimited => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn value_type(&self) -> ValueType {
|
|
||||||
match self {
|
|
||||||
&StackValueType::Any | &StackValueType::AnyUnlimited => unreachable!("must be checked by caller"),
|
|
||||||
&StackValueType::Specific(value_type) => value_type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ValueType> for StackValueType {
|
|
||||||
fn from(value_type: ValueType) -> Self {
|
|
||||||
StackValueType::Specific(value_type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<StackValueType> for StackValueType {
|
|
||||||
fn eq(&self, other: &StackValueType) -> bool {
|
|
||||||
if self.is_any() || other.is_any() || self.is_any_unlimited() || other.is_any_unlimited() {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
self.value_type() == other.value_type()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<ValueType> for StackValueType {
|
|
||||||
fn eq(&self, other: &ValueType) -> bool {
|
|
||||||
if self.is_any() || self.is_any_unlimited() {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
self.value_type() == *other
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<StackValueType> for ValueType {
|
|
||||||
fn eq(&self, other: &StackValueType) -> bool {
|
|
||||||
other == self
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,664 +0,0 @@
|
|||||||
use std::{i32, i64, u32, u64, f32};
|
|
||||||
use std::io;
|
|
||||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
|
||||||
use interpreter::Error;
|
|
||||||
use interpreter::variable::VariableType;
|
|
||||||
|
|
||||||
/// Runtime value.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum RuntimeValue {
|
|
||||||
/// Null value.
|
|
||||||
Null,
|
|
||||||
/// Reference to the function in the given module' function index space.
|
|
||||||
AnyFunc(String, u32),
|
|
||||||
/// 32b-length signed/unsigned int.
|
|
||||||
I32(i32),
|
|
||||||
/// 64b-length signed/unsigned int.
|
|
||||||
I64(i64),
|
|
||||||
/// 32b-length float.
|
|
||||||
F32(f32),
|
|
||||||
/// 64b-length float.
|
|
||||||
F64(f64),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to convert into trait.
|
|
||||||
pub trait TryInto<T, E> {
|
|
||||||
/// Try to convert self into other value.
|
|
||||||
fn try_into(self) -> Result<T, E>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert one type to another by wrapping.
|
|
||||||
pub trait WrapInto<T> {
|
|
||||||
/// Convert one type to another by wrapping.
|
|
||||||
fn wrap_into(self) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert one type to another by rounding to the nearest integer towards zero.
|
|
||||||
pub trait TryTruncateInto<T, E> {
|
|
||||||
/// Convert one type to another by rounding to the nearest integer towards zero.
|
|
||||||
fn try_truncate_into(self) -> Result<T, E>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert one type to another by extending with leading zeroes.
|
|
||||||
pub trait ExtendInto<T> {
|
|
||||||
/// Convert one type to another by extending with leading zeroes.
|
|
||||||
fn extend_into(self) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reinterprets the bits of a value of one type as another type.
|
|
||||||
pub trait TransmuteInto<T> {
|
|
||||||
/// Reinterprets the bits of a value of one type as another type.
|
|
||||||
fn transmute_into(self) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert from and to little endian.
|
|
||||||
pub trait LittleEndianConvert where Self: Sized {
|
|
||||||
/// Convert to little endian buffer.
|
|
||||||
fn into_little_endian(self) -> Vec<u8>;
|
|
||||||
/// Convert from little endian buffer.
|
|
||||||
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Arithmetic operations.
|
|
||||||
pub trait ArithmeticOps<T> {
|
|
||||||
/// Add two values.
|
|
||||||
fn add(self, other: T) -> T;
|
|
||||||
/// Subtract two values.
|
|
||||||
fn sub(self, other: T) -> T;
|
|
||||||
/// Multiply two values.
|
|
||||||
fn mul(self, other: T) -> T;
|
|
||||||
/// Divide two values.
|
|
||||||
fn div(self, other: T) -> Result<T, Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Integer value.
|
|
||||||
pub trait Integer<T>: ArithmeticOps<T> {
|
|
||||||
/// Counts leading zeros in the bitwise representation of the value.
|
|
||||||
fn leading_zeros(self) -> T;
|
|
||||||
/// Counts trailing zeros in the bitwise representation of the value.
|
|
||||||
fn trailing_zeros(self) -> T;
|
|
||||||
/// Counts 1-bits in the bitwise representation of the value.
|
|
||||||
fn count_ones(self) -> T;
|
|
||||||
/// Get left bit rotation result.
|
|
||||||
fn rotl(self, other: T) -> T;
|
|
||||||
/// Get right bit rotation result.
|
|
||||||
fn rotr(self, other: T) -> T;
|
|
||||||
/// Get division remainder.
|
|
||||||
fn rem(self, other: T) -> Result<T, Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Float-point value.
|
|
||||||
pub trait Float<T>: ArithmeticOps<T> {
|
|
||||||
/// Get absolute value.
|
|
||||||
fn abs(self) -> T;
|
|
||||||
/// Returns the largest integer less than or equal to a number.
|
|
||||||
fn floor(self) -> T;
|
|
||||||
/// Returns the smallest integer greater than or equal to a number.
|
|
||||||
fn ceil(self) -> T;
|
|
||||||
/// Returns the integer part of a number.
|
|
||||||
fn trunc(self) -> T;
|
|
||||||
/// Returns the nearest integer to a number. Round half-way cases away from 0.0.
|
|
||||||
fn round(self) -> T;
|
|
||||||
/// Returns the nearest integer to a number. Ties are round to even number.
|
|
||||||
fn nearest(self) -> T;
|
|
||||||
/// Takes the square root of a number.
|
|
||||||
fn sqrt(self) -> T;
|
|
||||||
/// Returns the minimum of the two numbers.
|
|
||||||
fn min(self, other: T) -> T;
|
|
||||||
/// Returns the maximum of the two numbers.
|
|
||||||
fn max(self, other: T) -> T;
|
|
||||||
/// Sets sign of this value to the sign of other value.
|
|
||||||
fn copysign(self, other: T) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RuntimeValue {
|
|
||||||
/// Creates new default value of given type.
|
|
||||||
pub fn default(variable_type: VariableType) -> Self {
|
|
||||||
match variable_type {
|
|
||||||
VariableType::AnyFunc => RuntimeValue::AnyFunc("".into(), 0),
|
|
||||||
VariableType::I32 => RuntimeValue::I32(0),
|
|
||||||
VariableType::I64 => RuntimeValue::I64(0),
|
|
||||||
VariableType::F32 => RuntimeValue::F32(0f32),
|
|
||||||
VariableType::F64 => RuntimeValue::F64(0f64),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates new value by interpreting passed u32 as f32.
|
|
||||||
pub fn decode_f32(val: u32) -> Self {
|
|
||||||
RuntimeValue::F32(f32_from_bits(val))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates new value by interpreting passed u64 as f64.
|
|
||||||
pub fn decode_f64(val: u64) -> Self {
|
|
||||||
RuntimeValue::F64(f64_from_bits(val))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if value is null.
|
|
||||||
pub fn is_null(&self) -> bool {
|
|
||||||
match *self {
|
|
||||||
RuntimeValue::Null => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get variable type for this value.
|
|
||||||
pub fn variable_type(&self) -> Option<VariableType> {
|
|
||||||
match *self {
|
|
||||||
RuntimeValue::Null => None,
|
|
||||||
RuntimeValue::AnyFunc(_, _) => Some(VariableType::AnyFunc),
|
|
||||||
RuntimeValue::I32(_) => Some(VariableType::I32),
|
|
||||||
RuntimeValue::I64(_) => Some(VariableType::I64),
|
|
||||||
RuntimeValue::F32(_) => Some(VariableType::F32),
|
|
||||||
RuntimeValue::F64(_) => Some(VariableType::F64),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<i32> for RuntimeValue {
|
|
||||||
fn from(val: i32) -> Self {
|
|
||||||
RuntimeValue::I32(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<i64> for RuntimeValue {
|
|
||||||
fn from(val: i64) -> Self {
|
|
||||||
RuntimeValue::I64(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<f32> for RuntimeValue {
|
|
||||||
fn from(val: f32) -> Self {
|
|
||||||
RuntimeValue::F32(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<f64> for RuntimeValue {
|
|
||||||
fn from(val: f64) -> Self {
|
|
||||||
RuntimeValue::F64(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryInto<bool, Error> for RuntimeValue {
|
|
||||||
fn try_into(self) -> Result<bool, Error> {
|
|
||||||
match self {
|
|
||||||
RuntimeValue::I32(val) => Ok(val != 0),
|
|
||||||
_ => Err(Error::Value(format!("32-bit int value expected"))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryInto<i32, Error> for RuntimeValue {
|
|
||||||
fn try_into(self) -> Result<i32, Error> {
|
|
||||||
match self {
|
|
||||||
RuntimeValue::I32(val) => Ok(val),
|
|
||||||
_ => Err(Error::Value(format!("32-bit int value expected"))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryInto<i64, Error> for RuntimeValue {
|
|
||||||
fn try_into(self) -> Result<i64, Error> {
|
|
||||||
match self {
|
|
||||||
RuntimeValue::I64(val) => Ok(val),
|
|
||||||
_ => Err(Error::Value(format!("64-bit int value expected"))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryInto<f32, Error> for RuntimeValue {
|
|
||||||
fn try_into(self) -> Result<f32, Error> {
|
|
||||||
match self {
|
|
||||||
RuntimeValue::F32(val) => Ok(val),
|
|
||||||
_ => Err(Error::Value(format!("32-bit float value expected"))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryInto<f64, Error> for RuntimeValue {
|
|
||||||
fn try_into(self) -> Result<f64, Error> {
|
|
||||||
match self {
|
|
||||||
RuntimeValue::F64(val) => Ok(val),
|
|
||||||
_ => Err(Error::Value(format!("64-bit float value expected"))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryInto<u32, Error> for RuntimeValue {
|
|
||||||
fn try_into(self) -> Result<u32, Error> {
|
|
||||||
match self {
|
|
||||||
RuntimeValue::I32(val) => Ok(val as u32),
|
|
||||||
_ => Err(Error::Value(format!("32-bit int value expected"))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryInto<u64, Error> for RuntimeValue {
|
|
||||||
fn try_into(self) -> Result<u64, Error> {
|
|
||||||
match self {
|
|
||||||
RuntimeValue::I64(val) => Ok(val as u64),
|
|
||||||
_ => Err(Error::Value(format!("64-bit int value expected"))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_wrap_into {
|
|
||||||
($from: ident, $into: ident) => {
|
|
||||||
impl WrapInto<$into> for $from {
|
|
||||||
fn wrap_into(self) -> $into {
|
|
||||||
self as $into
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_wrap_into!(i32, i8);
|
|
||||||
impl_wrap_into!(i32, i16);
|
|
||||||
impl_wrap_into!(i64, i8);
|
|
||||||
impl_wrap_into!(i64, i16);
|
|
||||||
impl_wrap_into!(i64, i32);
|
|
||||||
impl_wrap_into!(i64, f32);
|
|
||||||
impl_wrap_into!(u64, f32);
|
|
||||||
// Casting from an f64 to an f32 will produce the closest possible value (rounding strategy unspecified)
|
|
||||||
// NOTE: currently this will cause Undefined Behavior if the value is finite but larger or smaller than the
|
|
||||||
// largest or smallest finite value representable by f32. This is a bug and will be fixed.
|
|
||||||
impl_wrap_into!(f64, f32);
|
|
||||||
|
|
||||||
macro_rules! impl_try_truncate_into {
|
|
||||||
($from: ident, $into: ident) => {
|
|
||||||
impl TryTruncateInto<$into, Error> for $from {
|
|
||||||
fn try_truncate_into(self) -> Result<$into, Error> {
|
|
||||||
// Casting from a float to an integer will round the float towards zero
|
|
||||||
// NOTE: currently this will cause Undefined Behavior if the rounded value cannot be represented by the
|
|
||||||
// target integer type. This includes Inf and NaN. This is a bug and will be fixed.
|
|
||||||
if self.is_nan() || self.is_infinite() {
|
|
||||||
return Err(Error::Value("invalid float value for this operation".into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// range check
|
|
||||||
let result = self as $into;
|
|
||||||
if result as $from != self.trunc() {
|
|
||||||
return Err(Error::Value("invalid float value for this operation".into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self as $into)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_try_truncate_into!(f32, i32);
|
|
||||||
impl_try_truncate_into!(f32, i64);
|
|
||||||
impl_try_truncate_into!(f64, i32);
|
|
||||||
impl_try_truncate_into!(f64, i64);
|
|
||||||
impl_try_truncate_into!(f32, u32);
|
|
||||||
impl_try_truncate_into!(f32, u64);
|
|
||||||
impl_try_truncate_into!(f64, u32);
|
|
||||||
impl_try_truncate_into!(f64, u64);
|
|
||||||
|
|
||||||
macro_rules! impl_extend_into {
|
|
||||||
($from: ident, $into: ident) => {
|
|
||||||
impl ExtendInto<$into> for $from {
|
|
||||||
fn extend_into(self) -> $into {
|
|
||||||
self as $into
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_extend_into!(i8, i32);
|
|
||||||
impl_extend_into!(u8, i32);
|
|
||||||
impl_extend_into!(i16, i32);
|
|
||||||
impl_extend_into!(u16, i32);
|
|
||||||
impl_extend_into!(i8, i64);
|
|
||||||
impl_extend_into!(u8, i64);
|
|
||||||
impl_extend_into!(i16, i64);
|
|
||||||
impl_extend_into!(u16, i64);
|
|
||||||
impl_extend_into!(i32, i64);
|
|
||||||
impl_extend_into!(u32, i64);
|
|
||||||
impl_extend_into!(u32, u64);
|
|
||||||
impl_extend_into!(i32, f32);
|
|
||||||
impl_extend_into!(i32, f64);
|
|
||||||
impl_extend_into!(u32, f32);
|
|
||||||
impl_extend_into!(u32, f64);
|
|
||||||
impl_extend_into!(i64, f64);
|
|
||||||
impl_extend_into!(u64, f64);
|
|
||||||
impl_extend_into!(f32, f64);
|
|
||||||
|
|
||||||
macro_rules! impl_transmute_into_self {
|
|
||||||
($type: ident) => {
|
|
||||||
impl TransmuteInto<$type> for $type {
|
|
||||||
fn transmute_into(self) -> $type {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_transmute_into_self!(i32);
|
|
||||||
impl_transmute_into_self!(i64);
|
|
||||||
impl_transmute_into_self!(f32);
|
|
||||||
impl_transmute_into_self!(f64);
|
|
||||||
|
|
||||||
macro_rules! impl_transmute_into_as {
|
|
||||||
($from: ident, $into: ident) => {
|
|
||||||
impl TransmuteInto<$into> for $from {
|
|
||||||
fn transmute_into(self) -> $into {
|
|
||||||
self as $into
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_transmute_into_as!(i8, u8);
|
|
||||||
impl_transmute_into_as!(u8, i8);
|
|
||||||
impl_transmute_into_as!(i32, u32);
|
|
||||||
impl_transmute_into_as!(u32, i32);
|
|
||||||
impl_transmute_into_as!(i64, u64);
|
|
||||||
impl_transmute_into_as!(u64, i64);
|
|
||||||
|
|
||||||
// TODO: rewrite these safely when `f32/f32::to_bits/from_bits` stabilized.
|
|
||||||
impl TransmuteInto<i32> for f32 {
|
|
||||||
fn transmute_into(self) -> i32 { unsafe { ::std::mem::transmute(self) } }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TransmuteInto<i64> for f64 {
|
|
||||||
fn transmute_into(self) -> i64 { unsafe { ::std::mem::transmute(self) } }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TransmuteInto<f32> for i32 {
|
|
||||||
fn transmute_into(self) -> f32 { f32_from_bits(self as _) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TransmuteInto<f64> for i64 {
|
|
||||||
fn transmute_into(self) -> f64 { f64_from_bits(self as _) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LittleEndianConvert for i8 {
|
|
||||||
fn into_little_endian(self) -> Vec<u8> {
|
|
||||||
vec![self as u8]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error> {
|
|
||||||
buffer.get(0)
|
|
||||||
.map(|v| *v as i8)
|
|
||||||
.ok_or(Error::Value("invalid little endian buffer".into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LittleEndianConvert for u8 {
|
|
||||||
fn into_little_endian(self) -> Vec<u8> {
|
|
||||||
vec![self]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error> {
|
|
||||||
buffer.get(0)
|
|
||||||
.cloned()
|
|
||||||
.ok_or(Error::Value("invalid little endian buffer".into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LittleEndianConvert for i16 {
|
|
||||||
fn into_little_endian(self) -> Vec<u8> {
|
|
||||||
let mut vec = Vec::with_capacity(2);
|
|
||||||
vec.write_i16::<LittleEndian>(self)
|
|
||||||
.expect("i16 is written without any errors");
|
|
||||||
vec
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error> {
|
|
||||||
io::Cursor::new(buffer).read_i16::<LittleEndian>()
|
|
||||||
.map_err(|e| Error::Value(e.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LittleEndianConvert for u16 {
|
|
||||||
fn into_little_endian(self) -> Vec<u8> {
|
|
||||||
let mut vec = Vec::with_capacity(2);
|
|
||||||
vec.write_u16::<LittleEndian>(self)
|
|
||||||
.expect("u16 is written without any errors");
|
|
||||||
vec
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error> {
|
|
||||||
io::Cursor::new(buffer).read_u16::<LittleEndian>()
|
|
||||||
.map_err(|e| Error::Value(e.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LittleEndianConvert for i32 {
|
|
||||||
fn into_little_endian(self) -> Vec<u8> {
|
|
||||||
let mut vec = Vec::with_capacity(4);
|
|
||||||
vec.write_i32::<LittleEndian>(self)
|
|
||||||
.expect("i32 is written without any errors");
|
|
||||||
vec
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error> {
|
|
||||||
io::Cursor::new(buffer).read_i32::<LittleEndian>()
|
|
||||||
.map_err(|e| Error::Value(e.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LittleEndianConvert for u32 {
|
|
||||||
fn into_little_endian(self) -> Vec<u8> {
|
|
||||||
let mut vec = Vec::with_capacity(4);
|
|
||||||
vec.write_u32::<LittleEndian>(self)
|
|
||||||
.expect("u32 is written without any errors");
|
|
||||||
vec
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error> {
|
|
||||||
io::Cursor::new(buffer).read_u32::<LittleEndian>()
|
|
||||||
.map_err(|e| Error::Value(e.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LittleEndianConvert for i64 {
|
|
||||||
fn into_little_endian(self) -> Vec<u8> {
|
|
||||||
let mut vec = Vec::with_capacity(8);
|
|
||||||
vec.write_i64::<LittleEndian>(self)
|
|
||||||
.expect("i64 is written without any errors");
|
|
||||||
vec
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error> {
|
|
||||||
io::Cursor::new(buffer).read_i64::<LittleEndian>()
|
|
||||||
.map_err(|e| Error::Value(e.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LittleEndianConvert for f32 {
|
|
||||||
fn into_little_endian(self) -> Vec<u8> {
|
|
||||||
let mut vec = Vec::with_capacity(4);
|
|
||||||
vec.write_f32::<LittleEndian>(self)
|
|
||||||
.expect("f32 is written without any errors");
|
|
||||||
vec
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error> {
|
|
||||||
io::Cursor::new(buffer).read_u32::<LittleEndian>()
|
|
||||||
.map(f32_from_bits)
|
|
||||||
.map_err(|e| Error::Value(e.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LittleEndianConvert for f64 {
|
|
||||||
fn into_little_endian(self) -> Vec<u8> {
|
|
||||||
let mut vec = Vec::with_capacity(8);
|
|
||||||
vec.write_f64::<LittleEndian>(self)
|
|
||||||
.expect("i64 is written without any errors");
|
|
||||||
vec
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error> {
|
|
||||||
io::Cursor::new(buffer).read_u64::<LittleEndian>()
|
|
||||||
.map(f64_from_bits)
|
|
||||||
.map_err(|e| Error::Value(e.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert u32 to f32 safely, masking out sNAN
|
|
||||||
fn f32_from_bits(mut v: u32) -> f32 {
|
|
||||||
const EXP_MASK: u32 = 0x7F800000;
|
|
||||||
const QNAN_MASK: u32 = 0x00400000;
|
|
||||||
const FRACT_MASK: u32 = 0x007FFFFF;
|
|
||||||
|
|
||||||
if v & EXP_MASK == EXP_MASK && v & FRACT_MASK != 0 {
|
|
||||||
// If we have a NaN value, we
|
|
||||||
// convert signaling NaN values to quiet NaN
|
|
||||||
// by setting the the highest bit of the fraction
|
|
||||||
// TODO: remove when https://github.com/BurntSushi/byteorder/issues/71 closed.
|
|
||||||
// or `f32::from_bits` stabilized.
|
|
||||||
v |= QNAN_MASK;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe { ::std::mem::transmute(v) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert u64 to f64 safely, masking out sNAN
|
|
||||||
fn f64_from_bits(mut v: u64) -> f64 {
|
|
||||||
const EXP_MASK: u64 = 0x7FF0000000000000;
|
|
||||||
const QNAN_MASK: u64 = 0x0001000000000000;
|
|
||||||
const FRACT_MASK: u64 = 0x000FFFFFFFFFFFFF;
|
|
||||||
|
|
||||||
if v & EXP_MASK == EXP_MASK && v & FRACT_MASK != 0 {
|
|
||||||
// If we have a NaN value, we
|
|
||||||
// convert signaling NaN values to quiet NaN
|
|
||||||
// by setting the the highest bit of the fraction
|
|
||||||
// TODO: remove when https://github.com/BurntSushi/byteorder/issues/71 closed.
|
|
||||||
// or `f64::from_bits` stabilized.
|
|
||||||
v |= QNAN_MASK;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe { ::std::mem::transmute(v) }
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_integer_arithmetic_ops {
|
|
||||||
($type: ident) => {
|
|
||||||
impl ArithmeticOps<$type> for $type {
|
|
||||||
fn add(self, other: $type) -> $type { self.wrapping_add(other) }
|
|
||||||
fn sub(self, other: $type) -> $type { self.wrapping_sub(other) }
|
|
||||||
fn mul(self, other: $type) -> $type { self.wrapping_mul(other) }
|
|
||||||
fn div(self, other: $type) -> Result<$type, Error> {
|
|
||||||
if other == 0 { Err(Error::Value("Division by zero".to_owned())) }
|
|
||||||
else {
|
|
||||||
let (result, overflow) = self.overflowing_div(other);
|
|
||||||
if overflow {
|
|
||||||
return Err(Error::Value("Attempt to divide with overflow".to_owned()));
|
|
||||||
} else {
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_integer_arithmetic_ops!(i32);
|
|
||||||
impl_integer_arithmetic_ops!(u32);
|
|
||||||
impl_integer_arithmetic_ops!(i64);
|
|
||||||
impl_integer_arithmetic_ops!(u64);
|
|
||||||
|
|
||||||
macro_rules! impl_float_arithmetic_ops {
|
|
||||||
($type: ident) => {
|
|
||||||
impl ArithmeticOps<$type> for $type {
|
|
||||||
fn add(self, other: $type) -> $type { self + other }
|
|
||||||
fn sub(self, other: $type) -> $type { self - other }
|
|
||||||
fn mul(self, other: $type) -> $type { self * other }
|
|
||||||
fn div(self, other: $type) -> Result<$type, Error> { Ok(self / other) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_float_arithmetic_ops!(f32);
|
|
||||||
impl_float_arithmetic_ops!(f64);
|
|
||||||
|
|
||||||
macro_rules! impl_integer {
|
|
||||||
($type: ident) => {
|
|
||||||
impl Integer<$type> for $type {
|
|
||||||
fn leading_zeros(self) -> $type { self.leading_zeros() as $type }
|
|
||||||
fn trailing_zeros(self) -> $type { self.trailing_zeros() as $type }
|
|
||||||
fn count_ones(self) -> $type { self.count_ones() as $type }
|
|
||||||
fn rotl(self, other: $type) -> $type { self.rotate_left(other as u32) }
|
|
||||||
fn rotr(self, other: $type) -> $type { self.rotate_right(other as u32) }
|
|
||||||
fn rem(self, other: $type) -> Result<$type, Error> {
|
|
||||||
if other == 0 { Err(Error::Value("Division by zero".to_owned())) }
|
|
||||||
else { Ok(self.wrapping_rem(other)) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_integer!(i32);
|
|
||||||
impl_integer!(u32);
|
|
||||||
impl_integer!(i64);
|
|
||||||
impl_integer!(u64);
|
|
||||||
|
|
||||||
macro_rules! impl_float {
|
|
||||||
($type: ident, $int_type: ident) => {
|
|
||||||
impl Float<$type> for $type {
|
|
||||||
fn abs(self) -> $type { self.abs() }
|
|
||||||
fn floor(self) -> $type { self.floor() }
|
|
||||||
fn ceil(self) -> $type { self.ceil() }
|
|
||||||
fn trunc(self) -> $type { self.trunc() }
|
|
||||||
fn round(self) -> $type { self.round() }
|
|
||||||
fn nearest(self) -> $type {
|
|
||||||
let round = self.round();
|
|
||||||
if self.fract().abs() != 0.5 {
|
|
||||||
return round;
|
|
||||||
}
|
|
||||||
|
|
||||||
use std::ops::Rem;
|
|
||||||
if round.rem(2.0) == 1.0 {
|
|
||||||
self.floor()
|
|
||||||
} else if round.rem(2.0) == -1.0 {
|
|
||||||
self.ceil()
|
|
||||||
} else {
|
|
||||||
round
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn sqrt(self) -> $type { self.sqrt() }
|
|
||||||
// This instruction corresponds to what is sometimes called "minNaN" in other languages.
|
|
||||||
fn min(self, other: $type) -> $type {
|
|
||||||
if self.is_nan() || other.is_nan() {
|
|
||||||
use std::$type;
|
|
||||||
return $type::NAN;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.min(other)
|
|
||||||
}
|
|
||||||
// This instruction corresponds to what is sometimes called "maxNaN" in other languages.
|
|
||||||
fn max(self, other: $type) -> $type {
|
|
||||||
if self.is_nan() || other.is_nan() {
|
|
||||||
use std::$type;
|
|
||||||
return $type::NAN;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.max(other)
|
|
||||||
}
|
|
||||||
fn copysign(self, other: $type) -> $type {
|
|
||||||
use std::mem::size_of;
|
|
||||||
|
|
||||||
if self.is_nan() {
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
let sign_mask: $int_type = 1 << ((size_of::<$int_type>() << 3) - 1);
|
|
||||||
let self_int: $int_type = self.transmute_into();
|
|
||||||
let other_int: $int_type = other.transmute_into();
|
|
||||||
let is_self_sign_set = (self_int & sign_mask) != 0;
|
|
||||||
let is_other_sign_set = (other_int & sign_mask) != 0;
|
|
||||||
if is_self_sign_set == is_other_sign_set {
|
|
||||||
self
|
|
||||||
} else if is_other_sign_set {
|
|
||||||
(self_int | sign_mask).transmute_into()
|
|
||||||
} else {
|
|
||||||
(self_int & !sign_mask).transmute_into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_float!(f32, i32);
|
|
||||||
impl_float!(f64, i64);
|
|
@ -1,163 +0,0 @@
|
|||||||
use std::fmt;
|
|
||||||
use parking_lot::RwLock;
|
|
||||||
use elements::{GlobalType, ValueType, TableElementType};
|
|
||||||
use interpreter::Error;
|
|
||||||
use interpreter::value::RuntimeValue;
|
|
||||||
|
|
||||||
/// Variable type.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub enum VariableType {
|
|
||||||
/// Any func value.
|
|
||||||
AnyFunc,
|
|
||||||
/// i32 value.
|
|
||||||
I32,
|
|
||||||
/// i64 value.
|
|
||||||
I64,
|
|
||||||
/// f32 value.
|
|
||||||
F32,
|
|
||||||
/// f64 value.
|
|
||||||
F64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Externally stored variable value.
|
|
||||||
///
|
|
||||||
/// WebAssembly specificaiton [requires][0] that if a global variable is immutable, then
|
|
||||||
/// it should remain unchanged. To comply with specification you should ensure this invariant holds.
|
|
||||||
///
|
|
||||||
/// [0]: https://webassembly.github.io/spec/appendix/properties.html#global-instance
|
|
||||||
pub trait ExternalVariableValue {
|
|
||||||
/// Get variable value.
|
|
||||||
fn get(&self) -> RuntimeValue;
|
|
||||||
|
|
||||||
/// Set variable value.
|
|
||||||
fn set(&mut self, value: RuntimeValue) -> Result<(), Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Variable instance.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct VariableInstance {
|
|
||||||
/// Is mutable?
|
|
||||||
is_mutable: bool,
|
|
||||||
/// Variable type.
|
|
||||||
variable_type: VariableType,
|
|
||||||
/// Global value.
|
|
||||||
value: RwLock<VariableValue>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enum variable value.
|
|
||||||
enum VariableValue {
|
|
||||||
/// Internal value.
|
|
||||||
Internal(RuntimeValue),
|
|
||||||
/// External value.
|
|
||||||
External(Box<ExternalVariableValue>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VariableInstance {
|
|
||||||
/// New variable instance
|
|
||||||
pub fn new(is_mutable: bool, variable_type: VariableType, value: RuntimeValue) -> Result<Self, Error> {
|
|
||||||
// TODO: there is nothing about null value in specification + there is nothing about initializing missing table elements? => runtime check for nulls
|
|
||||||
if !value.is_null() && value.variable_type() != Some(variable_type) {
|
|
||||||
return Err(Error::Variable(format!("trying to initialize variable of type {:?} with value of type {:?}", variable_type, value.variable_type())));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(VariableInstance {
|
|
||||||
is_mutable: is_mutable,
|
|
||||||
variable_type: variable_type,
|
|
||||||
value: RwLock::new(VariableValue::Internal(value)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// New global variable
|
|
||||||
pub fn new_global(global_type: &GlobalType, value: RuntimeValue) -> Result<Self, Error> {
|
|
||||||
Self::new(global_type.is_mutable(), global_type.content_type().into(), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// New global with externally stored value.
|
|
||||||
pub fn new_external_global(is_mutable: bool, variable_type: VariableType, value: Box<ExternalVariableValue>) -> Result<Self, Error> {
|
|
||||||
// TODO: there is nothing about null value in specification + there is nothing about initializing missing table elements? => runtime check for nulls
|
|
||||||
let current_value = value.get();
|
|
||||||
if !current_value.is_null() && current_value.variable_type() != Some(variable_type) {
|
|
||||||
return Err(Error::Variable(format!("trying to initialize variable of type {:?} with value of type {:?}", variable_type, current_value.variable_type())));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(VariableInstance {
|
|
||||||
is_mutable: is_mutable,
|
|
||||||
variable_type: variable_type,
|
|
||||||
value: RwLock::new(VariableValue::External(value)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is mutable
|
|
||||||
pub fn is_mutable(&self) -> bool {
|
|
||||||
self.is_mutable
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get variable type.
|
|
||||||
pub fn variable_type(&self) -> VariableType {
|
|
||||||
self.variable_type
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the value of the variable instance
|
|
||||||
pub fn get(&self) -> RuntimeValue {
|
|
||||||
self.value.read().get()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the value of the variable instance
|
|
||||||
pub fn set(&self, value: RuntimeValue) -> Result<(), Error> {
|
|
||||||
if !self.is_mutable {
|
|
||||||
return Err(Error::Variable("trying to update immutable variable".into()));
|
|
||||||
}
|
|
||||||
if value.variable_type() != Some(self.variable_type) {
|
|
||||||
return Err(Error::Variable(format!("trying to update variable of type {:?} with value of type {:?}", self.variable_type, value.variable_type())));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.value.write().set(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VariableValue {
|
|
||||||
fn get(&self) -> RuntimeValue {
|
|
||||||
match *self {
|
|
||||||
VariableValue::Internal(ref value) => value.clone(),
|
|
||||||
VariableValue::External(ref value) => value.get(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set(&mut self, new_value: RuntimeValue) -> Result<(), Error> {
|
|
||||||
match *self {
|
|
||||||
VariableValue::Internal(ref mut value) => {
|
|
||||||
*value = new_value;
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
VariableValue::External(ref mut value) => value.set(new_value),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ValueType> for VariableType {
|
|
||||||
fn from(vt: ValueType) -> VariableType {
|
|
||||||
match vt {
|
|
||||||
ValueType::I32 => VariableType::I32,
|
|
||||||
ValueType::I64 => VariableType::I64,
|
|
||||||
ValueType::F32 => VariableType::F32,
|
|
||||||
ValueType::F64 => VariableType::F64,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<TableElementType> for VariableType {
|
|
||||||
fn from(tt: TableElementType) -> VariableType {
|
|
||||||
match tt {
|
|
||||||
TableElementType::AnyFunc => VariableType::AnyFunc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for VariableValue {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match *self {
|
|
||||||
VariableValue::Internal(ref value) => write!(f, "Variable.Internal({:?})", value),
|
|
||||||
VariableValue::External(ref value) => write!(f, "Variable.External({:?})", value.get()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
13
src/lib.rs
13
src/lib.rs
@ -2,16 +2,10 @@
|
|||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
extern crate parking_lot;
|
|
||||||
|
|
||||||
pub mod elements;
|
pub mod elements;
|
||||||
pub mod builder;
|
pub mod builder;
|
||||||
pub mod interpreter;
|
|
||||||
mod validation;
|
|
||||||
mod common;
|
|
||||||
|
|
||||||
pub use elements::{
|
pub use elements::{
|
||||||
Error as SerializationError,
|
Error as SerializationError,
|
||||||
@ -22,10 +16,3 @@ pub use elements::{
|
|||||||
peek_size,
|
peek_size,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(deprecated)]
|
|
||||||
pub use interpreter::{
|
|
||||||
ProgramInstance,
|
|
||||||
ModuleInstance,
|
|
||||||
ModuleInstanceInterface,
|
|
||||||
RuntimeValue,
|
|
||||||
};
|
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
use elements::{MemoryType, TableType, GlobalType, Type};
|
|
||||||
use elements::{BlockType, ValueType};
|
|
||||||
use validation::Error;
|
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub struct ModuleContext {
|
|
||||||
pub memories: Vec<MemoryType>,
|
|
||||||
pub tables: Vec<TableType>,
|
|
||||||
pub globals: Vec<GlobalType>,
|
|
||||||
pub types: Vec<Type>,
|
|
||||||
pub func_type_indexes: Vec<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ModuleContext {
|
|
||||||
pub fn memories(&self) -> &[MemoryType] {
|
|
||||||
&self.memories
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tables(&self) -> &[TableType] {
|
|
||||||
&self.tables
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn globals(&self) -> &[GlobalType] {
|
|
||||||
&self.globals
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn types(&self) -> &[Type] {
|
|
||||||
&self.types
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn func_type_indexes(&self) -> &[u32] {
|
|
||||||
&self.func_type_indexes
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn require_memory(&self, idx: u32) -> Result<(), Error> {
|
|
||||||
if self.memories().get(idx as usize).is_none() {
|
|
||||||
return Err(Error(format!("Memory at index {} doesn't exists", idx)));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn require_table(&self, idx: u32) -> Result<&TableType, Error> {
|
|
||||||
self.tables()
|
|
||||||
.get(idx as usize)
|
|
||||||
.ok_or_else(|| Error(format!("Table at index {} doesn't exists", idx)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn require_function(&self, idx: u32) -> Result<(&[ValueType], BlockType), Error> {
|
|
||||||
let ty_idx = self.func_type_indexes()
|
|
||||||
.get(idx as usize)
|
|
||||||
.ok_or_else(|| Error(format!("Function at index {} doesn't exists", idx)))?;
|
|
||||||
self.require_function_type(*ty_idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn require_function_type(&self, idx: u32) -> Result<(&[ValueType], BlockType), Error> {
|
|
||||||
let &Type::Function(ref ty) = self.types()
|
|
||||||
.get(idx as usize)
|
|
||||||
.ok_or_else(|| Error(format!("Type at index {} doesn't exists", idx)))?;
|
|
||||||
|
|
||||||
let params = ty.params();
|
|
||||||
let return_ty = ty.return_type()
|
|
||||||
.map(BlockType::Value)
|
|
||||||
.unwrap_or(BlockType::NoResult);
|
|
||||||
Ok((params, return_ty))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn require_global(&self, idx: u32, mutability: Option<bool>) -> Result<&GlobalType, Error> {
|
|
||||||
let global = self.globals()
|
|
||||||
.get(idx as usize)
|
|
||||||
.ok_or_else(|| Error(format!("Global at index {} doesn't exists", idx)))?;
|
|
||||||
|
|
||||||
if let Some(expected_mutable) = mutability {
|
|
||||||
if expected_mutable && !global.is_mutable() {
|
|
||||||
return Err(Error(format!("Expected global {} to be mutable", idx)));
|
|
||||||
}
|
|
||||||
if !expected_mutable && global.is_mutable() {
|
|
||||||
return Err(Error(format!("Expected global {} to be immutable", idx)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(global)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,774 +0,0 @@
|
|||||||
use std::u32;
|
|
||||||
use std::iter::repeat;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use elements::{Opcode, BlockType, ValueType, TableElementType, Func, FuncBody};
|
|
||||||
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
|
||||||
use validation::context::ModuleContext;
|
|
||||||
|
|
||||||
use validation::Error;
|
|
||||||
|
|
||||||
use common::stack::StackWithLimit;
|
|
||||||
use common::{BlockFrame, BlockFrameType};
|
|
||||||
|
|
||||||
/// Constant from wabt' validator.cc to skip alignment validation (not a part of spec).
|
|
||||||
const NATURAL_ALIGNMENT: u32 = 0xFFFFFFFF;
|
|
||||||
|
|
||||||
/// Maximum number of entries in value stack.
|
|
||||||
const DEFAULT_VALUE_STACK_LIMIT: usize = 16384;
|
|
||||||
/// Maximum number of entries in frame stack.
|
|
||||||
const DEFAULT_FRAME_STACK_LIMIT: usize = 1024;
|
|
||||||
|
|
||||||
/// Function validation context.
|
|
||||||
struct FunctionValidationContext<'a> {
|
|
||||||
/// Wasm module
|
|
||||||
module: &'a ModuleContext,
|
|
||||||
/// Current instruction position.
|
|
||||||
position: usize,
|
|
||||||
/// Local variables.
|
|
||||||
locals: &'a [ValueType],
|
|
||||||
/// Value stack.
|
|
||||||
value_stack: StackWithLimit<StackValueType>,
|
|
||||||
/// Frame stack.
|
|
||||||
frame_stack: StackWithLimit<BlockFrame>,
|
|
||||||
/// Function return type. None if validating expression.
|
|
||||||
return_type: Option<BlockType>,
|
|
||||||
/// Labels positions.
|
|
||||||
labels: HashMap<usize, usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Value type on the stack.
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
enum StackValueType {
|
|
||||||
/// Any value type.
|
|
||||||
Any,
|
|
||||||
/// Any number of any values of any type.
|
|
||||||
AnyUnlimited,
|
|
||||||
/// Concrete value type.
|
|
||||||
Specific(ValueType),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Function validator.
|
|
||||||
pub struct Validator;
|
|
||||||
|
|
||||||
/// Instruction outcome.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
enum InstructionOutcome {
|
|
||||||
/// Continue with next instruction.
|
|
||||||
ValidateNextInstruction,
|
|
||||||
/// Unreachable instruction reached.
|
|
||||||
Unreachable,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Validator {
|
|
||||||
pub fn validate_function(
|
|
||||||
module: &ModuleContext,
|
|
||||||
func: &Func,
|
|
||||||
body: &FuncBody,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let (params, result_ty) = module.require_function_type(func.type_ref())?;
|
|
||||||
|
|
||||||
// locals = (params + vars)
|
|
||||||
let mut locals = params.to_vec();
|
|
||||||
locals.extend(
|
|
||||||
body.locals()
|
|
||||||
.iter()
|
|
||||||
.flat_map(|l| repeat(l.value_type())
|
|
||||||
.take(l.count() as usize)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut context = FunctionValidationContext::new(
|
|
||||||
&module,
|
|
||||||
&locals,
|
|
||||||
DEFAULT_VALUE_STACK_LIMIT,
|
|
||||||
DEFAULT_FRAME_STACK_LIMIT,
|
|
||||||
result_ty,
|
|
||||||
);
|
|
||||||
|
|
||||||
context.push_label(BlockFrameType::Function, result_ty)?;
|
|
||||||
Validator::validate_function_block(&mut context, body.code().elements())?;
|
|
||||||
while !context.frame_stack.is_empty() {
|
|
||||||
context.pop_label()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_function_block(context: &mut FunctionValidationContext, body: &[Opcode]) -> Result<(), Error> {
|
|
||||||
let body_len = body.len();
|
|
||||||
if body_len == 0 {
|
|
||||||
return Err(Error("Non-empty function body expected".into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let opcode = &body[context.position];
|
|
||||||
match Validator::validate_instruction(context, opcode)? {
|
|
||||||
InstructionOutcome::ValidateNextInstruction => (),
|
|
||||||
InstructionOutcome::Unreachable => context.unreachable()?,
|
|
||||||
}
|
|
||||||
|
|
||||||
context.position += 1;
|
|
||||||
if context.position == body_len {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_instruction(context: &mut FunctionValidationContext, opcode: &Opcode) -> Result<InstructionOutcome, Error> {
|
|
||||||
use self::Opcode::*;
|
|
||||||
debug!(target: "validator", "validating {:?}", opcode);
|
|
||||||
match *opcode {
|
|
||||||
Unreachable => Ok(InstructionOutcome::Unreachable),
|
|
||||||
Nop => Ok(InstructionOutcome::ValidateNextInstruction),
|
|
||||||
Block(block_type) => Validator::validate_block(context, block_type),
|
|
||||||
Loop(block_type) => Validator::validate_loop(context, block_type),
|
|
||||||
If(block_type) => Validator::validate_if(context, block_type),
|
|
||||||
Else => Validator::validate_else(context),
|
|
||||||
End => Validator::validate_end(context),
|
|
||||||
Br(idx) => Validator::validate_br(context, idx),
|
|
||||||
BrIf(idx) => Validator::validate_br_if(context, idx),
|
|
||||||
BrTable(ref table, default) => Validator::validate_br_table(context, table, default),
|
|
||||||
Return => Validator::validate_return(context),
|
|
||||||
|
|
||||||
Call(index) => Validator::validate_call(context, index),
|
|
||||||
CallIndirect(index, _reserved) => Validator::validate_call_indirect(context, index),
|
|
||||||
|
|
||||||
Drop => Validator::validate_drop(context),
|
|
||||||
Select => Validator::validate_select(context),
|
|
||||||
|
|
||||||
GetLocal(index) => Validator::validate_get_local(context, index),
|
|
||||||
SetLocal(index) => Validator::validate_set_local(context, index),
|
|
||||||
TeeLocal(index) => Validator::validate_tee_local(context, index),
|
|
||||||
GetGlobal(index) => Validator::validate_get_global(context, index),
|
|
||||||
SetGlobal(index) => Validator::validate_set_global(context, index),
|
|
||||||
|
|
||||||
I32Load(align, _) => Validator::validate_load(context, align, 4, ValueType::I32),
|
|
||||||
I64Load(align, _) => Validator::validate_load(context, align, 8, ValueType::I64),
|
|
||||||
F32Load(align, _) => Validator::validate_load(context, align, 4, ValueType::F32),
|
|
||||||
F64Load(align, _) => Validator::validate_load(context, align, 8, ValueType::F64),
|
|
||||||
I32Load8S(align, _) => Validator::validate_load(context, align, 1, ValueType::I32),
|
|
||||||
I32Load8U(align, _) => Validator::validate_load(context, align, 1, ValueType::I32),
|
|
||||||
I32Load16S(align, _) => Validator::validate_load(context, align, 2, ValueType::I32),
|
|
||||||
I32Load16U(align, _) => Validator::validate_load(context, align, 2, ValueType::I32),
|
|
||||||
I64Load8S(align, _) => Validator::validate_load(context, align, 1, ValueType::I64),
|
|
||||||
I64Load8U(align, _) => Validator::validate_load(context, align, 1, ValueType::I64),
|
|
||||||
I64Load16S(align, _) => Validator::validate_load(context, align, 2, ValueType::I64),
|
|
||||||
I64Load16U(align, _) => Validator::validate_load(context, align, 2, ValueType::I64),
|
|
||||||
I64Load32S(align, _) => Validator::validate_load(context, align, 4, ValueType::I64),
|
|
||||||
I64Load32U(align, _) => Validator::validate_load(context, align, 4, ValueType::I64),
|
|
||||||
|
|
||||||
I32Store(align, _) => Validator::validate_store(context, align, 4, ValueType::I32),
|
|
||||||
I64Store(align, _) => Validator::validate_store(context, align, 8, ValueType::I64),
|
|
||||||
F32Store(align, _) => Validator::validate_store(context, align, 4, ValueType::F32),
|
|
||||||
F64Store(align, _) => Validator::validate_store(context, align, 8, ValueType::F64),
|
|
||||||
I32Store8(align, _) => Validator::validate_store(context, align, 1, ValueType::I32),
|
|
||||||
I32Store16(align, _) => Validator::validate_store(context, align, 2, ValueType::I32),
|
|
||||||
I64Store8(align, _) => Validator::validate_store(context, align, 1, ValueType::I64),
|
|
||||||
I64Store16(align, _) => Validator::validate_store(context, align, 2, ValueType::I64),
|
|
||||||
I64Store32(align, _) => Validator::validate_store(context, align, 4, ValueType::I64),
|
|
||||||
|
|
||||||
CurrentMemory(_) => Validator::validate_current_memory(context),
|
|
||||||
GrowMemory(_) => Validator::validate_grow_memory(context),
|
|
||||||
|
|
||||||
I32Const(_) => Validator::validate_const(context, ValueType::I32),
|
|
||||||
I64Const(_) => Validator::validate_const(context, ValueType::I64),
|
|
||||||
F32Const(_) => Validator::validate_const(context, ValueType::F32),
|
|
||||||
F64Const(_) => Validator::validate_const(context, ValueType::F64),
|
|
||||||
|
|
||||||
I32Eqz => Validator::validate_testop(context, ValueType::I32),
|
|
||||||
I32Eq => Validator::validate_relop(context, ValueType::I32),
|
|
||||||
I32Ne => Validator::validate_relop(context, ValueType::I32),
|
|
||||||
I32LtS => Validator::validate_relop(context, ValueType::I32),
|
|
||||||
I32LtU => Validator::validate_relop(context, ValueType::I32),
|
|
||||||
I32GtS => Validator::validate_relop(context, ValueType::I32),
|
|
||||||
I32GtU => Validator::validate_relop(context, ValueType::I32),
|
|
||||||
I32LeS => Validator::validate_relop(context, ValueType::I32),
|
|
||||||
I32LeU => Validator::validate_relop(context, ValueType::I32),
|
|
||||||
I32GeS => Validator::validate_relop(context, ValueType::I32),
|
|
||||||
I32GeU => Validator::validate_relop(context, ValueType::I32),
|
|
||||||
|
|
||||||
I64Eqz => Validator::validate_testop(context, ValueType::I64),
|
|
||||||
I64Eq => Validator::validate_relop(context, ValueType::I64),
|
|
||||||
I64Ne => Validator::validate_relop(context, ValueType::I64),
|
|
||||||
I64LtS => Validator::validate_relop(context, ValueType::I64),
|
|
||||||
I64LtU => Validator::validate_relop(context, ValueType::I64),
|
|
||||||
I64GtS => Validator::validate_relop(context, ValueType::I64),
|
|
||||||
I64GtU => Validator::validate_relop(context, ValueType::I64),
|
|
||||||
I64LeS => Validator::validate_relop(context, ValueType::I64),
|
|
||||||
I64LeU => Validator::validate_relop(context, ValueType::I64),
|
|
||||||
I64GeS => Validator::validate_relop(context, ValueType::I64),
|
|
||||||
I64GeU => Validator::validate_relop(context, ValueType::I64),
|
|
||||||
|
|
||||||
F32Eq => Validator::validate_relop(context, ValueType::F32),
|
|
||||||
F32Ne => Validator::validate_relop(context, ValueType::F32),
|
|
||||||
F32Lt => Validator::validate_relop(context, ValueType::F32),
|
|
||||||
F32Gt => Validator::validate_relop(context, ValueType::F32),
|
|
||||||
F32Le => Validator::validate_relop(context, ValueType::F32),
|
|
||||||
F32Ge => Validator::validate_relop(context, ValueType::F32),
|
|
||||||
|
|
||||||
F64Eq => Validator::validate_relop(context, ValueType::F64),
|
|
||||||
F64Ne => Validator::validate_relop(context, ValueType::F64),
|
|
||||||
F64Lt => Validator::validate_relop(context, ValueType::F64),
|
|
||||||
F64Gt => Validator::validate_relop(context, ValueType::F64),
|
|
||||||
F64Le => Validator::validate_relop(context, ValueType::F64),
|
|
||||||
F64Ge => Validator::validate_relop(context, ValueType::F64),
|
|
||||||
|
|
||||||
I32Clz => Validator::validate_unop(context, ValueType::I32),
|
|
||||||
I32Ctz => Validator::validate_unop(context, ValueType::I32),
|
|
||||||
I32Popcnt => Validator::validate_unop(context, ValueType::I32),
|
|
||||||
I32Add => Validator::validate_binop(context, ValueType::I32),
|
|
||||||
I32Sub => Validator::validate_binop(context, ValueType::I32),
|
|
||||||
I32Mul => Validator::validate_binop(context, ValueType::I32),
|
|
||||||
I32DivS => Validator::validate_binop(context, ValueType::I32),
|
|
||||||
I32DivU => Validator::validate_binop(context, ValueType::I32),
|
|
||||||
I32RemS => Validator::validate_binop(context, ValueType::I32),
|
|
||||||
I32RemU => Validator::validate_binop(context, ValueType::I32),
|
|
||||||
I32And => Validator::validate_binop(context, ValueType::I32),
|
|
||||||
I32Or => Validator::validate_binop(context, ValueType::I32),
|
|
||||||
I32Xor => Validator::validate_binop(context, ValueType::I32),
|
|
||||||
I32Shl => Validator::validate_binop(context, ValueType::I32),
|
|
||||||
I32ShrS => Validator::validate_binop(context, ValueType::I32),
|
|
||||||
I32ShrU => Validator::validate_binop(context, ValueType::I32),
|
|
||||||
I32Rotl => Validator::validate_binop(context, ValueType::I32),
|
|
||||||
I32Rotr => Validator::validate_binop(context, ValueType::I32),
|
|
||||||
|
|
||||||
I64Clz => Validator::validate_unop(context, ValueType::I64),
|
|
||||||
I64Ctz => Validator::validate_unop(context, ValueType::I64),
|
|
||||||
I64Popcnt => Validator::validate_unop(context, ValueType::I64),
|
|
||||||
I64Add => Validator::validate_binop(context, ValueType::I64),
|
|
||||||
I64Sub => Validator::validate_binop(context, ValueType::I64),
|
|
||||||
I64Mul => Validator::validate_binop(context, ValueType::I64),
|
|
||||||
I64DivS => Validator::validate_binop(context, ValueType::I64),
|
|
||||||
I64DivU => Validator::validate_binop(context, ValueType::I64),
|
|
||||||
I64RemS => Validator::validate_binop(context, ValueType::I64),
|
|
||||||
I64RemU => Validator::validate_binop(context, ValueType::I64),
|
|
||||||
I64And => Validator::validate_binop(context, ValueType::I64),
|
|
||||||
I64Or => Validator::validate_binop(context, ValueType::I64),
|
|
||||||
I64Xor => Validator::validate_binop(context, ValueType::I64),
|
|
||||||
I64Shl => Validator::validate_binop(context, ValueType::I64),
|
|
||||||
I64ShrS => Validator::validate_binop(context, ValueType::I64),
|
|
||||||
I64ShrU => Validator::validate_binop(context, ValueType::I64),
|
|
||||||
I64Rotl => Validator::validate_binop(context, ValueType::I64),
|
|
||||||
I64Rotr => Validator::validate_binop(context, ValueType::I64),
|
|
||||||
|
|
||||||
F32Abs => Validator::validate_unop(context, ValueType::F32),
|
|
||||||
F32Neg => Validator::validate_unop(context, ValueType::F32),
|
|
||||||
F32Ceil => Validator::validate_unop(context, ValueType::F32),
|
|
||||||
F32Floor => Validator::validate_unop(context, ValueType::F32),
|
|
||||||
F32Trunc => Validator::validate_unop(context, ValueType::F32),
|
|
||||||
F32Nearest => Validator::validate_unop(context, ValueType::F32),
|
|
||||||
F32Sqrt => Validator::validate_unop(context, ValueType::F32),
|
|
||||||
F32Add => Validator::validate_binop(context, ValueType::F32),
|
|
||||||
F32Sub => Validator::validate_binop(context, ValueType::F32),
|
|
||||||
F32Mul => Validator::validate_binop(context, ValueType::F32),
|
|
||||||
F32Div => Validator::validate_binop(context, ValueType::F32),
|
|
||||||
F32Min => Validator::validate_binop(context, ValueType::F32),
|
|
||||||
F32Max => Validator::validate_binop(context, ValueType::F32),
|
|
||||||
F32Copysign => Validator::validate_binop(context, ValueType::F32),
|
|
||||||
|
|
||||||
F64Abs => Validator::validate_unop(context, ValueType::F64),
|
|
||||||
F64Neg => Validator::validate_unop(context, ValueType::F64),
|
|
||||||
F64Ceil => Validator::validate_unop(context, ValueType::F64),
|
|
||||||
F64Floor => Validator::validate_unop(context, ValueType::F64),
|
|
||||||
F64Trunc => Validator::validate_unop(context, ValueType::F64),
|
|
||||||
F64Nearest => Validator::validate_unop(context, ValueType::F64),
|
|
||||||
F64Sqrt => Validator::validate_unop(context, ValueType::F64),
|
|
||||||
F64Add => Validator::validate_binop(context, ValueType::F64),
|
|
||||||
F64Sub => Validator::validate_binop(context, ValueType::F64),
|
|
||||||
F64Mul => Validator::validate_binop(context, ValueType::F64),
|
|
||||||
F64Div => Validator::validate_binop(context, ValueType::F64),
|
|
||||||
F64Min => Validator::validate_binop(context, ValueType::F64),
|
|
||||||
F64Max => Validator::validate_binop(context, ValueType::F64),
|
|
||||||
F64Copysign => Validator::validate_binop(context, ValueType::F64),
|
|
||||||
|
|
||||||
I32WrapI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::I32),
|
|
||||||
I32TruncSF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32),
|
|
||||||
I32TruncUF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32),
|
|
||||||
I32TruncSF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I32),
|
|
||||||
I32TruncUF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I32),
|
|
||||||
I64ExtendSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::I64),
|
|
||||||
I64ExtendUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::I64),
|
|
||||||
I64TruncSF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I64),
|
|
||||||
I64TruncUF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I64),
|
|
||||||
I64TruncSF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64),
|
|
||||||
I64TruncUF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64),
|
|
||||||
F32ConvertSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32),
|
|
||||||
F32ConvertUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32),
|
|
||||||
F32ConvertSI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F32),
|
|
||||||
F32ConvertUI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F32),
|
|
||||||
F32DemoteF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::F32),
|
|
||||||
F64ConvertSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F64),
|
|
||||||
F64ConvertUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F64),
|
|
||||||
F64ConvertSI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64),
|
|
||||||
F64ConvertUI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64),
|
|
||||||
F64PromoteF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::F64),
|
|
||||||
|
|
||||||
I32ReinterpretF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32),
|
|
||||||
I64ReinterpretF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64),
|
|
||||||
F32ReinterpretI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32),
|
|
||||||
F64ReinterpretI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_const(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.push_value(value_type.into())?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_unop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.pop_value(value_type.into())?;
|
|
||||||
context.push_value(value_type.into())?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_binop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.pop_value(value_type.into())?;
|
|
||||||
context.pop_value(value_type.into())?;
|
|
||||||
context.push_value(value_type.into())?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_testop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.pop_value(value_type.into())?;
|
|
||||||
context.push_value(ValueType::I32.into())?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_relop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.pop_value(value_type.into())?;
|
|
||||||
context.pop_value(value_type.into())?;
|
|
||||||
context.push_value(ValueType::I32.into())?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_cvtop(context: &mut FunctionValidationContext, value_type1: ValueType, value_type2: ValueType) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.pop_value(value_type1.into())?;
|
|
||||||
context.push_value(value_type2.into())?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_drop(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.pop_any_value().map(|_| ())?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_select(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.pop_value(ValueType::I32.into())?;
|
|
||||||
let select_type = context.pop_any_value()?;
|
|
||||||
context.pop_value(select_type)?;
|
|
||||||
context.push_value(select_type)?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_get_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
let local_type = context.require_local(index)?;
|
|
||||||
context.push_value(local_type)?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_set_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
let local_type = context.require_local(index)?;
|
|
||||||
let value_type = context.pop_any_value()?;
|
|
||||||
if local_type != value_type {
|
|
||||||
return Err(Error(format!("Trying to update local {} of type {:?} with value of type {:?}", index, local_type, value_type)));
|
|
||||||
}
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_tee_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
let local_type = context.require_local(index)?;
|
|
||||||
let value_type = context.tee_any_value()?;
|
|
||||||
if local_type != value_type {
|
|
||||||
return Err(Error(format!("Trying to update local {} of type {:?} with value of type {:?}", index, local_type, value_type)));
|
|
||||||
}
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_get_global(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
let global_type: StackValueType = {
|
|
||||||
let global = context.module.require_global(index, None)?;
|
|
||||||
global.content_type().into()
|
|
||||||
};
|
|
||||||
context.push_value(global_type)?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_set_global(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
let global_type: StackValueType = {
|
|
||||||
let global = context.module.require_global(index, Some(true))?;
|
|
||||||
global.content_type().into()
|
|
||||||
};
|
|
||||||
let value_type = context.pop_any_value()?;
|
|
||||||
if global_type != value_type {
|
|
||||||
return Err(Error(format!("Trying to update global {} of type {:?} with value of type {:?}", index, global_type, value_type)));
|
|
||||||
}
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_load(context: &mut FunctionValidationContext, align: u32, max_align: u32, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
|
||||||
if align != NATURAL_ALIGNMENT {
|
|
||||||
if 1u32.checked_shl(align).unwrap_or(u32::MAX) > max_align {
|
|
||||||
return Err(Error(format!("Too large memory alignment 2^{} (expected at most {})", align, max_align)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.pop_value(ValueType::I32.into())?;
|
|
||||||
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
|
|
||||||
context.push_value(value_type.into())?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_store(context: &mut FunctionValidationContext, align: u32, max_align: u32, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
|
||||||
if align != NATURAL_ALIGNMENT {
|
|
||||||
if 1u32.checked_shl(align).unwrap_or(u32::MAX) > max_align {
|
|
||||||
return Err(Error(format!("Too large memory alignment 2^{} (expected at most {})", align, max_align)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
|
|
||||||
context.pop_value(value_type.into())?;
|
|
||||||
context.pop_value(ValueType::I32.into())?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_block(context: &mut FunctionValidationContext, block_type: BlockType) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.push_label(BlockFrameType::Block, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_loop(context: &mut FunctionValidationContext, block_type: BlockType) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.push_label(BlockFrameType::Loop, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_if(context: &mut FunctionValidationContext, block_type: BlockType) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.pop_value(ValueType::I32.into())?;
|
|
||||||
context.push_label(BlockFrameType::IfTrue, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_else(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
|
||||||
let block_type = {
|
|
||||||
let top_frame = context.top_label()?;
|
|
||||||
if top_frame.frame_type != BlockFrameType::IfTrue {
|
|
||||||
return Err(Error("Misplaced else instruction".into()));
|
|
||||||
}
|
|
||||||
top_frame.block_type
|
|
||||||
};
|
|
||||||
context.pop_label()?;
|
|
||||||
|
|
||||||
if let BlockType::Value(value_type) = block_type {
|
|
||||||
context.pop_value(value_type.into())?;
|
|
||||||
}
|
|
||||||
context.push_label(BlockFrameType::IfFalse, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_end(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
|
||||||
{
|
|
||||||
let top_frame = context.top_label()?;
|
|
||||||
if top_frame.frame_type == BlockFrameType::IfTrue {
|
|
||||||
if top_frame.block_type != BlockType::NoResult {
|
|
||||||
return Err(Error(format!("If block without else required to have NoResult block type. But it have {:?} type", top_frame.block_type)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.pop_label().map(|_| InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_br(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
let (frame_type, frame_block_type) = {
|
|
||||||
let frame = context.require_label(idx)?;
|
|
||||||
(frame.frame_type, frame.block_type)
|
|
||||||
};
|
|
||||||
if frame_type != BlockFrameType::Loop {
|
|
||||||
if let BlockType::Value(value_type) = frame_block_type {
|
|
||||||
context.tee_value(value_type.into())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(InstructionOutcome::Unreachable)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_br_if(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.pop_value(ValueType::I32.into())?;
|
|
||||||
|
|
||||||
let (frame_type, frame_block_type) = {
|
|
||||||
let frame = context.require_label(idx)?;
|
|
||||||
(frame.frame_type, frame.block_type)
|
|
||||||
};
|
|
||||||
if frame_type != BlockFrameType::Loop {
|
|
||||||
if let BlockType::Value(value_type) = frame_block_type {
|
|
||||||
context.tee_value(value_type.into())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_br_table(context: &mut FunctionValidationContext, table: &[u32], default: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
let mut required_block_type = None;
|
|
||||||
|
|
||||||
{
|
|
||||||
let default_block = context.require_label(default)?;
|
|
||||||
if default_block.frame_type != BlockFrameType::Loop {
|
|
||||||
required_block_type = Some(default_block.block_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
for label in table {
|
|
||||||
let label_block = context.require_label(*label)?;
|
|
||||||
if label_block.frame_type != BlockFrameType::Loop {
|
|
||||||
if let Some(required_block_type) = required_block_type {
|
|
||||||
if required_block_type != label_block.block_type {
|
|
||||||
return Err(Error(format!("Labels in br_table points to block of different types: {:?} and {:?}", required_block_type, label_block.block_type)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
required_block_type = Some(label_block.block_type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.pop_value(ValueType::I32.into())?;
|
|
||||||
if let Some(required_block_type) = required_block_type {
|
|
||||||
if let BlockType::Value(value_type) = required_block_type {
|
|
||||||
context.tee_value(value_type.into())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(InstructionOutcome::Unreachable)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_return(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
|
||||||
if let BlockType::Value(value_type) = context.return_type()? {
|
|
||||||
context.tee_value(value_type.into())?;
|
|
||||||
}
|
|
||||||
Ok(InstructionOutcome::Unreachable)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_call(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
let (argument_types, return_type) = context.module.require_function(idx)?;
|
|
||||||
for argument_type in argument_types.iter().rev() {
|
|
||||||
context.pop_value((*argument_type).into())?;
|
|
||||||
}
|
|
||||||
if let BlockType::Value(value_type) = return_type {
|
|
||||||
context.push_value(value_type.into())?;
|
|
||||||
}
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_call_indirect(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
|
||||||
{
|
|
||||||
let table = context.module.require_table(DEFAULT_TABLE_INDEX)?;
|
|
||||||
if table.elem_type() != TableElementType::AnyFunc {
|
|
||||||
return Err(Error(format!(
|
|
||||||
"Table {} has element type {:?} while `anyfunc` expected",
|
|
||||||
idx,
|
|
||||||
table.elem_type()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.pop_value(ValueType::I32.into())?;
|
|
||||||
let (argument_types, return_type) = context.module.require_function_type(idx)?;
|
|
||||||
for argument_type in argument_types.iter().rev() {
|
|
||||||
context.pop_value((*argument_type).into())?;
|
|
||||||
}
|
|
||||||
if let BlockType::Value(value_type) = return_type {
|
|
||||||
context.push_value(value_type.into())?;
|
|
||||||
}
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_current_memory(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
|
|
||||||
context.push_value(ValueType::I32.into())?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_grow_memory(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
|
||||||
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
|
|
||||||
context.pop_value(ValueType::I32.into())?;
|
|
||||||
context.push_value(ValueType::I32.into())?;
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> FunctionValidationContext<'a> {
|
|
||||||
fn new(
|
|
||||||
module: &'a ModuleContext,
|
|
||||||
locals: &'a [ValueType],
|
|
||||||
value_stack_limit: usize,
|
|
||||||
frame_stack_limit: usize,
|
|
||||||
return_type: BlockType,
|
|
||||||
) -> Self {
|
|
||||||
FunctionValidationContext {
|
|
||||||
module: module,
|
|
||||||
position: 0,
|
|
||||||
locals: locals,
|
|
||||||
value_stack: StackWithLimit::with_limit(value_stack_limit),
|
|
||||||
frame_stack: StackWithLimit::with_limit(frame_stack_limit),
|
|
||||||
return_type: Some(return_type),
|
|
||||||
labels: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
|
||||||
Ok(self.value_stack.push(value_type.into())?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pop_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
|
||||||
self.check_stack_access()?;
|
|
||||||
match self.value_stack.pop()? {
|
|
||||||
StackValueType::Specific(stack_value_type) if stack_value_type == value_type => Ok(()),
|
|
||||||
StackValueType::Any => Ok(()),
|
|
||||||
StackValueType::AnyUnlimited => {
|
|
||||||
self.value_stack.push(StackValueType::AnyUnlimited)?;
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
stack_value_type @ _ => Err(Error(format!("Expected value of type {:?} on top of stack. Got {:?}", value_type, stack_value_type))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tee_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
|
||||||
self.check_stack_access()?;
|
|
||||||
match *self.value_stack.top()? {
|
|
||||||
StackValueType::Specific(stack_value_type) if stack_value_type == value_type => Ok(()),
|
|
||||||
StackValueType::Any | StackValueType::AnyUnlimited => Ok(()),
|
|
||||||
stack_value_type @ _ => Err(Error(format!("Expected value of type {:?} on top of stack. Got {:?}", value_type, stack_value_type))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pop_any_value(&mut self) -> Result<StackValueType, Error> {
|
|
||||||
self.check_stack_access()?;
|
|
||||||
match self.value_stack.pop()? {
|
|
||||||
StackValueType::Specific(stack_value_type) => Ok(StackValueType::Specific(stack_value_type)),
|
|
||||||
StackValueType::Any => Ok(StackValueType::Any),
|
|
||||||
StackValueType::AnyUnlimited => {
|
|
||||||
self.value_stack.push(StackValueType::AnyUnlimited)?;
|
|
||||||
Ok(StackValueType::Any)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tee_any_value(&mut self) -> Result<StackValueType, Error> {
|
|
||||||
self.check_stack_access()?;
|
|
||||||
Ok(self.value_stack.top().map(Clone::clone)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unreachable(&mut self) -> Result<(), Error> {
|
|
||||||
Ok(self.value_stack.push(StackValueType::AnyUnlimited)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn top_label(&self) -> Result<&BlockFrame, Error> {
|
|
||||||
Ok(self.frame_stack.top()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_label(&mut self, frame_type: BlockFrameType, block_type: BlockType) -> Result<(), Error> {
|
|
||||||
Ok(self.frame_stack.push(BlockFrame {
|
|
||||||
frame_type: frame_type,
|
|
||||||
block_type: block_type,
|
|
||||||
begin_position: self.position,
|
|
||||||
branch_position: self.position,
|
|
||||||
end_position: self.position,
|
|
||||||
value_stack_len: self.value_stack.len(),
|
|
||||||
})?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pop_label(&mut self) -> Result<InstructionOutcome, Error> {
|
|
||||||
let frame = self.frame_stack.pop()?;
|
|
||||||
let actual_value_type = if self.value_stack.len() > frame.value_stack_len {
|
|
||||||
Some(self.value_stack.pop()?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
self.value_stack.resize(frame.value_stack_len, StackValueType::Any);
|
|
||||||
|
|
||||||
match frame.block_type {
|
|
||||||
BlockType::NoResult if actual_value_type.map(|vt| vt.is_any_unlimited()).unwrap_or(true) => (),
|
|
||||||
BlockType::Value(required_value_type) if actual_value_type.map(|vt| vt == required_value_type).unwrap_or(false) => (),
|
|
||||||
_ => return Err(Error(format!("Expected block to return {:?} while it has returned {:?}", frame.block_type, actual_value_type))),
|
|
||||||
}
|
|
||||||
if !self.frame_stack.is_empty() {
|
|
||||||
self.labels.insert(frame.begin_position, self.position);
|
|
||||||
}
|
|
||||||
if let BlockType::Value(value_type) = frame.block_type {
|
|
||||||
self.push_value(value_type.into())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn require_label(&self, idx: u32) -> Result<&BlockFrame, Error> {
|
|
||||||
Ok(self.frame_stack.get(idx as usize)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn return_type(&self) -> Result<BlockType, Error> {
|
|
||||||
self.return_type.ok_or(Error("Trying to return from expression".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn require_local(&self, idx: u32) -> Result<StackValueType, Error> {
|
|
||||||
self.locals.get(idx as usize)
|
|
||||||
.cloned()
|
|
||||||
.map(Into::into)
|
|
||||||
.ok_or(Error(format!("Trying to access local with index {} when there are only {} locals", idx, self.locals.len())))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_stack_access(&self) -> Result<(), Error> {
|
|
||||||
let value_stack_min = self.frame_stack.top().expect("at least 1 topmost block").value_stack_len;
|
|
||||||
if self.value_stack.len() > value_stack_min {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(Error("Trying to access parent frame stack values.".into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StackValueType {
|
|
||||||
fn is_any(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
&StackValueType::Any => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_any_unlimited(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
&StackValueType::AnyUnlimited => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn value_type(&self) -> ValueType {
|
|
||||||
match self {
|
|
||||||
&StackValueType::Any | &StackValueType::AnyUnlimited => unreachable!("must be checked by caller"),
|
|
||||||
&StackValueType::Specific(value_type) => value_type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ValueType> for StackValueType {
|
|
||||||
fn from(value_type: ValueType) -> Self {
|
|
||||||
StackValueType::Specific(value_type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<StackValueType> for StackValueType {
|
|
||||||
fn eq(&self, other: &StackValueType) -> bool {
|
|
||||||
if self.is_any() || other.is_any() || self.is_any_unlimited() || other.is_any_unlimited() {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
self.value_type() == other.value_type()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<ValueType> for StackValueType {
|
|
||||||
fn eq(&self, other: &ValueType) -> bool {
|
|
||||||
if self.is_any() || self.is_any_unlimited() {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
self.value_type() == *other
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<StackValueType> for ValueType {
|
|
||||||
fn eq(&self, other: &StackValueType) -> bool {
|
|
||||||
other == self
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,326 +0,0 @@
|
|||||||
use std::error;
|
|
||||||
use std::fmt;
|
|
||||||
use elements::{
|
|
||||||
BlockType, External, GlobalEntry, GlobalType, Internal, MemoryType,
|
|
||||||
Module, Opcode, ResizableLimits, TableType, ValueType, InitExpr
|
|
||||||
};
|
|
||||||
use common::stack;
|
|
||||||
use self::context::ModuleContext;
|
|
||||||
use self::func::Validator;
|
|
||||||
|
|
||||||
pub use self::module::ValidatedModule;
|
|
||||||
|
|
||||||
mod context;
|
|
||||||
mod module;
|
|
||||||
mod func;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Error(String);
|
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl error::Error for Error {
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<stack::Error> for Error {
|
|
||||||
fn from(e: stack::Error) -> Error {
|
|
||||||
Error(format!("Stack: {}", e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn validate_module(module: &Module) -> Result<ValidatedModule, Error> {
|
|
||||||
let context = prepare_context(module)?;
|
|
||||||
|
|
||||||
let function_section_len = module
|
|
||||||
.function_section()
|
|
||||||
.map(|s| s.entries().len())
|
|
||||||
.unwrap_or(0);
|
|
||||||
let code_section_len = module.code_section().map(|s| s.bodies().len()).unwrap_or(0);
|
|
||||||
if function_section_len != code_section_len {
|
|
||||||
return Err(Error(format!(
|
|
||||||
"length of function section is {}, while len of code section is {}",
|
|
||||||
function_section_len,
|
|
||||||
code_section_len
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate every function body in user modules
|
|
||||||
if function_section_len != 0 {
|
|
||||||
// tests use invalid code
|
|
||||||
let function_section = module
|
|
||||||
.function_section()
|
|
||||||
.expect("function_section_len != 0; qed");
|
|
||||||
let code_section = module
|
|
||||||
.code_section()
|
|
||||||
.expect("function_section_len != 0; function_section_len == code_section_len; qed");
|
|
||||||
// check every function body
|
|
||||||
for (index, function) in function_section.entries().iter().enumerate() {
|
|
||||||
let function_body = code_section
|
|
||||||
.bodies()
|
|
||||||
.get(index as usize)
|
|
||||||
.ok_or(Error(format!("Missing body for function {}", index)))?;
|
|
||||||
Validator::validate_function(&context, function, function_body).map_err(|e| {
|
|
||||||
let Error(ref msg) = e;
|
|
||||||
Error(format!("Function #{} validation error: {}", index, msg))
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate start section
|
|
||||||
if let Some(start_function) = module.start_section() {
|
|
||||||
let (params, return_ty) = context.require_function(start_function)?;
|
|
||||||
if return_ty != BlockType::NoResult || params.len() != 0 {
|
|
||||||
return Err(Error(
|
|
||||||
"start function expected to have type [] -> []".into(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate export section
|
|
||||||
if let Some(export_section) = module.export_section() {
|
|
||||||
for export in export_section.entries() {
|
|
||||||
match *export.internal() {
|
|
||||||
Internal::Function(function_index) => {
|
|
||||||
context.require_function(function_index)?;
|
|
||||||
}
|
|
||||||
Internal::Global(global_index) => {
|
|
||||||
context.require_global(global_index, Some(false))?;
|
|
||||||
}
|
|
||||||
Internal::Memory(memory_index) => {
|
|
||||||
context.require_memory(memory_index)?;
|
|
||||||
}
|
|
||||||
Internal::Table(table_index) => {
|
|
||||||
context.require_table(table_index)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate import section
|
|
||||||
if let Some(import_section) = module.import_section() {
|
|
||||||
for import in import_section.entries() {
|
|
||||||
match *import.external() {
|
|
||||||
External::Function(function_type_index) => {
|
|
||||||
context.require_function(function_type_index)?;
|
|
||||||
},
|
|
||||||
External::Global(ref global_type) => {
|
|
||||||
if global_type.is_mutable() {
|
|
||||||
return Err(Error(format!("trying to import mutable global {}", import.field())));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
External::Memory(ref memory_type) => {
|
|
||||||
memory_type.validate()?;
|
|
||||||
},
|
|
||||||
External::Table(ref table_type) => {
|
|
||||||
table_type.validate()?;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// there must be no greater than 1 table in tables index space
|
|
||||||
if context.tables().len() > 1 {
|
|
||||||
return Err(Error(format!("too many tables in index space: {}", context.tables().len())));
|
|
||||||
}
|
|
||||||
|
|
||||||
// there must be no greater than 1 linear memory in memory index space
|
|
||||||
if context.memories().len() > 1 {
|
|
||||||
return Err(Error(format!("too many memory regions in index space: {}", context.memories().len())));
|
|
||||||
}
|
|
||||||
|
|
||||||
// use data section to initialize linear memory regions
|
|
||||||
if let Some(data_section) = module.data_section() {
|
|
||||||
for data_segment in data_section.entries() {
|
|
||||||
context.require_memory(data_segment.index())?;
|
|
||||||
let init_ty = data_segment.offset().expr_const_type(context.globals())?;
|
|
||||||
if init_ty != ValueType::I32 {
|
|
||||||
return Err(Error("segment offset should return I32".into()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// use element section to fill tables
|
|
||||||
if let Some(element_section) = module.elements_section() {
|
|
||||||
for element_segment in element_section.entries() {
|
|
||||||
context.require_table(element_segment.index())?;
|
|
||||||
|
|
||||||
let init_ty = element_segment.offset().expr_const_type(context.globals())?;
|
|
||||||
if init_ty != ValueType::I32 {
|
|
||||||
return Err(Error("segment offset should return I32".into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
for function_index in element_segment.members() {
|
|
||||||
context.require_function(*function_index)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let ModuleContext {
|
|
||||||
types,
|
|
||||||
tables,
|
|
||||||
memories,
|
|
||||||
globals,
|
|
||||||
func_type_indexes,
|
|
||||||
} = context;
|
|
||||||
|
|
||||||
Ok(ValidatedModule {
|
|
||||||
types,
|
|
||||||
tables,
|
|
||||||
memories,
|
|
||||||
globals,
|
|
||||||
func_type_indexes,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_context(module: &Module) -> Result<ModuleContext, Error> {
|
|
||||||
// Copy types from module as is.
|
|
||||||
let types = module
|
|
||||||
.type_section()
|
|
||||||
.map(|ts| ts.types().into_iter().cloned().collect())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
// Fill elements with imported values.
|
|
||||||
let mut func_type_indexes = Vec::new();
|
|
||||||
let mut tables = Vec::new();
|
|
||||||
let mut memories = Vec::new();
|
|
||||||
let mut globals = Vec::new();
|
|
||||||
|
|
||||||
for import_entry in module
|
|
||||||
.import_section()
|
|
||||||
.map(|i| i.entries())
|
|
||||||
.unwrap_or_default()
|
|
||||||
{
|
|
||||||
match *import_entry.external() {
|
|
||||||
External::Function(idx) => func_type_indexes.push(idx),
|
|
||||||
External::Table(ref table) => tables.push(table.clone()),
|
|
||||||
External::Memory(ref memory) => memories.push(memory.clone()),
|
|
||||||
External::Global(ref global) => globals.push(global.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concatenate elements with defined in the module.
|
|
||||||
if let Some(function_section) = module.function_section() {
|
|
||||||
for func_entry in function_section.entries() {
|
|
||||||
func_type_indexes.push(func_entry.type_ref());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(table_section) = module.table_section() {
|
|
||||||
for table_entry in table_section.entries() {
|
|
||||||
table_entry.validate()?;
|
|
||||||
tables.push(table_entry.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(mem_section) = module.memory_section() {
|
|
||||||
for mem_entry in mem_section.entries() {
|
|
||||||
mem_entry.validate()?;
|
|
||||||
memories.push(mem_entry.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(global_section) = module.global_section() {
|
|
||||||
// Validation of globals is defined over modified context C', which
|
|
||||||
// contains only imported globals. So we do globals validation
|
|
||||||
// in two passes, in first we validate globals and after all globals are validated
|
|
||||||
// add them in globals list.
|
|
||||||
for global_entry in global_section.entries() {
|
|
||||||
global_entry.validate(&globals)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
for global_entry in global_section.entries() {
|
|
||||||
globals.push(global_entry.global_type().clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ModuleContext {
|
|
||||||
types,
|
|
||||||
tables,
|
|
||||||
memories,
|
|
||||||
globals,
|
|
||||||
func_type_indexes,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResizableLimits {
|
|
||||||
fn validate(&self) -> Result<(), Error> {
|
|
||||||
if let Some(maximum) = self.maximum() {
|
|
||||||
if self.initial() > maximum {
|
|
||||||
return Err(Error(format!(
|
|
||||||
"maximum limit {} is lesser than minimum {}",
|
|
||||||
maximum,
|
|
||||||
self.initial()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MemoryType {
|
|
||||||
fn validate(&self) -> Result<(), Error> {
|
|
||||||
self.limits().validate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TableType {
|
|
||||||
fn validate(&self) -> Result<(), Error> {
|
|
||||||
self.limits().validate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GlobalEntry {
|
|
||||||
fn validate(&self, globals: &[GlobalType]) -> Result<(), Error> {
|
|
||||||
let init = self.init_expr();
|
|
||||||
let init_expr_ty = init.expr_const_type(globals)?;
|
|
||||||
if init_expr_ty != self.global_type().content_type() {
|
|
||||||
return Err(Error(format!(
|
|
||||||
"Trying to initialize variable of type {:?} with value of type {:?}",
|
|
||||||
self.global_type().content_type(),
|
|
||||||
init_expr_ty
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InitExpr {
|
|
||||||
/// Returns type of this constant expression.
|
|
||||||
fn expr_const_type(&self, globals: &[GlobalType]) -> Result<ValueType, Error> {
|
|
||||||
let code = self.code();
|
|
||||||
if code.len() != 2 {
|
|
||||||
return Err(Error("Init expression should always be with length 2".into()));
|
|
||||||
}
|
|
||||||
let expr_ty: ValueType = match code[0] {
|
|
||||||
Opcode::I32Const(_) => ValueType::I32,
|
|
||||||
Opcode::I64Const(_) => ValueType::I64,
|
|
||||||
Opcode::F32Const(_) => ValueType::F32,
|
|
||||||
Opcode::F64Const(_) => ValueType::F64,
|
|
||||||
Opcode::GetGlobal(idx) => match globals.get(idx as usize) {
|
|
||||||
Some(target_global) => {
|
|
||||||
if target_global.is_mutable() {
|
|
||||||
return Err(Error(format!("Global {} is mutable", idx)));
|
|
||||||
}
|
|
||||||
target_global.content_type()
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
return Err(Error(
|
|
||||||
format!("Global {} doesn't exists or not yet defined", idx),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => return Err(Error("Non constant opcode in init expr".into())),
|
|
||||||
};
|
|
||||||
if code[1] != Opcode::End {
|
|
||||||
return Err(Error("Expression doesn't ends with `end` opcode".into()));
|
|
||||||
}
|
|
||||||
Ok(expr_ty)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
use elements::{MemoryType, TableType, GlobalType, Type};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ValidatedModule {
|
|
||||||
pub memories: Vec<MemoryType>,
|
|
||||||
pub tables: Vec<TableType>,
|
|
||||||
pub globals: Vec<GlobalType>,
|
|
||||||
pub types: Vec<Type>,
|
|
||||||
pub func_type_indexes: Vec<u32>,
|
|
||||||
}
|
|
@ -1,301 +0,0 @@
|
|||||||
use super::validate_module;
|
|
||||||
use builder::module;
|
|
||||||
use elements::{
|
|
||||||
External, GlobalEntry, GlobalType, ImportEntry, InitExpr, MemoryType,
|
|
||||||
Opcode, Opcodes, TableType, ValueType, BlockType
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_is_valid() {
|
|
||||||
let module = module().build();
|
|
||||||
assert!(validate_module(&module).is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn limits() {
|
|
||||||
let test_cases = vec![
|
|
||||||
// min > max
|
|
||||||
(10, Some(9), false),
|
|
||||||
// min = max
|
|
||||||
(10, Some(10), true),
|
|
||||||
// table/memory is always valid without max
|
|
||||||
(10, None, true),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (min, max, is_valid) in test_cases {
|
|
||||||
// defined table
|
|
||||||
let m = module()
|
|
||||||
.table()
|
|
||||||
.with_min(min)
|
|
||||||
.with_max(max)
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
assert_eq!(validate_module(&m).is_ok(), is_valid);
|
|
||||||
|
|
||||||
// imported table
|
|
||||||
let m = module()
|
|
||||||
.with_import(
|
|
||||||
ImportEntry::new(
|
|
||||||
"core".into(),
|
|
||||||
"table".into(),
|
|
||||||
External::Table(TableType::new(min, max))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
assert_eq!(validate_module(&m).is_ok(), is_valid);
|
|
||||||
|
|
||||||
// defined memory
|
|
||||||
let m = module()
|
|
||||||
.memory()
|
|
||||||
.with_min(min)
|
|
||||||
.with_max(max)
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
assert_eq!(validate_module(&m).is_ok(), is_valid);
|
|
||||||
|
|
||||||
// imported table
|
|
||||||
let m = module()
|
|
||||||
.with_import(
|
|
||||||
ImportEntry::new(
|
|
||||||
"core".into(),
|
|
||||||
"memory".into(),
|
|
||||||
External::Memory(MemoryType::new(min, max))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
assert_eq!(validate_module(&m).is_ok(), is_valid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn global_init_const() {
|
|
||||||
let m = module()
|
|
||||||
.with_global(
|
|
||||||
GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I32, true),
|
|
||||||
InitExpr::new(
|
|
||||||
vec![Opcode::I32Const(42), Opcode::End]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(&m).is_ok());
|
|
||||||
|
|
||||||
// init expr type differs from declared global type
|
|
||||||
let m = module()
|
|
||||||
.with_global(
|
|
||||||
GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I64, true),
|
|
||||||
InitExpr::new(vec![Opcode::I32Const(42), Opcode::End])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(&m).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn global_init_global() {
|
|
||||||
let m = module()
|
|
||||||
.with_import(
|
|
||||||
ImportEntry::new(
|
|
||||||
"env".into(),
|
|
||||||
"ext_global".into(),
|
|
||||||
External::Global(GlobalType::new(ValueType::I32, false))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.with_global(
|
|
||||||
GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I32, true),
|
|
||||||
InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(&m).is_ok());
|
|
||||||
|
|
||||||
// get_global can reference only previously defined globals
|
|
||||||
let m = module()
|
|
||||||
.with_global(
|
|
||||||
GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I32, true),
|
|
||||||
InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(&m).is_err());
|
|
||||||
|
|
||||||
// get_global can reference only const globals
|
|
||||||
let m = module()
|
|
||||||
.with_import(
|
|
||||||
ImportEntry::new(
|
|
||||||
"env".into(),
|
|
||||||
"ext_global".into(),
|
|
||||||
External::Global(GlobalType::new(ValueType::I32, true))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.with_global(
|
|
||||||
GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I32, true),
|
|
||||||
InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(&m).is_err());
|
|
||||||
|
|
||||||
// get_global in init_expr can only refer to imported globals.
|
|
||||||
let m = module()
|
|
||||||
.with_global(
|
|
||||||
GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I32, false),
|
|
||||||
InitExpr::new(vec![Opcode::I32Const(0), Opcode::End])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.with_global(
|
|
||||||
GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I32, true),
|
|
||||||
InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(&m).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn global_init_misc() {
|
|
||||||
// without delimiting End opcode
|
|
||||||
let m = module()
|
|
||||||
.with_global(
|
|
||||||
GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I32, true),
|
|
||||||
InitExpr::new(vec![Opcode::I32Const(42)])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(&m).is_err());
|
|
||||||
|
|
||||||
// empty init expr
|
|
||||||
let m = module()
|
|
||||||
.with_global(
|
|
||||||
GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I32, true),
|
|
||||||
InitExpr::new(vec![Opcode::End])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(&m).is_err());
|
|
||||||
|
|
||||||
// not an constant opcode used
|
|
||||||
let m = module()
|
|
||||||
.with_global(
|
|
||||||
GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I32, true),
|
|
||||||
InitExpr::new(vec![Opcode::Unreachable, Opcode::End])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(&m).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn module_limits_validity() {
|
|
||||||
// module cannot contain more than 1 memory atm.
|
|
||||||
let m = module()
|
|
||||||
.with_import(
|
|
||||||
ImportEntry::new(
|
|
||||||
"core".into(),
|
|
||||||
"memory".into(),
|
|
||||||
External::Memory(MemoryType::new(10, None))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.memory()
|
|
||||||
.with_min(10)
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(&m).is_err());
|
|
||||||
|
|
||||||
// module cannot contain more than 1 table atm.
|
|
||||||
let m = module()
|
|
||||||
.with_import(
|
|
||||||
ImportEntry::new(
|
|
||||||
"core".into(),
|
|
||||||
"table".into(),
|
|
||||||
External::Table(TableType::new(10, None))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.table()
|
|
||||||
.with_min(10)
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(&m).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn funcs() {
|
|
||||||
// recursive function calls is legal.
|
|
||||||
let m = module()
|
|
||||||
.function()
|
|
||||||
.signature().return_type().i32().build()
|
|
||||||
.body().with_opcodes(Opcodes::new(vec![
|
|
||||||
Opcode::Call(1),
|
|
||||||
Opcode::End,
|
|
||||||
])).build()
|
|
||||||
.build()
|
|
||||||
.function()
|
|
||||||
.signature().return_type().i32().build()
|
|
||||||
.body().with_opcodes(Opcodes::new(vec![
|
|
||||||
Opcode::Call(0),
|
|
||||||
Opcode::End,
|
|
||||||
])).build()
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(&m).is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn globals() {
|
|
||||||
// import immutable global is legal.
|
|
||||||
let m = module()
|
|
||||||
.with_import(
|
|
||||||
ImportEntry::new(
|
|
||||||
"env".into(),
|
|
||||||
"ext_global".into(),
|
|
||||||
External::Global(GlobalType::new(ValueType::I32, false))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(&m).is_ok());
|
|
||||||
|
|
||||||
// import mutable global is invalid.
|
|
||||||
let m = module()
|
|
||||||
.with_import(
|
|
||||||
ImportEntry::new(
|
|
||||||
"env".into(),
|
|
||||||
"ext_global".into(),
|
|
||||||
External::Global(GlobalType::new(ValueType::I32, true))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(&m).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn if_else_with_return_type_validation() {
|
|
||||||
let m = module()
|
|
||||||
.function()
|
|
||||||
.signature().build()
|
|
||||||
.body().with_opcodes(Opcodes::new(vec![
|
|
||||||
Opcode::I32Const(1),
|
|
||||||
Opcode::If(BlockType::NoResult),
|
|
||||||
Opcode::I32Const(1),
|
|
||||||
Opcode::If(BlockType::Value(ValueType::I32)),
|
|
||||||
Opcode::I32Const(1),
|
|
||||||
Opcode::Else,
|
|
||||||
Opcode::I32Const(2),
|
|
||||||
Opcode::End,
|
|
||||||
Opcode::Drop,
|
|
||||||
Opcode::End,
|
|
||||||
Opcode::End,
|
|
||||||
])).build()
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
validate_module(&m).unwrap();
|
|
||||||
}
|
|
Reference in New Issue
Block a user