This commit is contained in:
Sergey Pepyakin
2017-11-28 17:48:50 +03:00
parent 9c79d48855
commit 2690ca2018
10 changed files with 102 additions and 320 deletions

View File

@ -17,6 +17,7 @@ script:
- cargo build --release --verbose
- cargo test --release --verbose
- cargo test --release --manifest-path=spec/Cargo.toml
- cargo test --manifest-path=pwasm-emscripten/Cargo.toml
after_success: |-
[ $TRAVIS_BRANCH = master ] &&
[ $TRAVIS_PULL_REQUEST = false ] &&

View File

@ -4,7 +4,7 @@ extern crate parity_wasm;
use std::env::args;
use parity_wasm::{interpreter, ModuleInstanceInterface};
use parity_wasm::ModuleInstanceInterface;
fn main() {
let args: Vec<_> = args().collect();

View File

@ -251,8 +251,26 @@ impl EnvParams {
}
}
pub fn program_with_emscripten(params: EnvParams) -> ProgramInstance {
pub fn program_with_emscripten_env(params: EnvParams) -> Result<ProgramInstance, Error> {
let program = ProgramInstance::new();
program.insert_loaded_module("env", env_module(params).unwrap()).unwrap();
program
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();
}
}

View File

@ -1,248 +0,0 @@
//! This module provides some of the simplest exports
//! from the Emscripten runtime, such as `STACKTOP` or `abort`.
use std::sync::{Arc, Weak};
use builder::module;
use elements::{ExportEntry, Internal, ValueType};
use interpreter::Error;
use interpreter::native::{native_module, UserDefinedElements, UserFunctionDescriptor, UserFunctionExecutor};
use interpreter::module::{CallerContext, ModuleInstance, ModuleInstanceInterface};
use interpreter::memory::LINEAR_MEMORY_PAGE_SIZE;
use interpreter::value::RuntimeValue;
use interpreter::variable::{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;
/// Emscripten environment parameters.
pub struct EnvParams {
/// 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 {
abort_global: Arc<VariableInstance>,
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" => {
self.abort_global.set(RuntimeValue::I32(1))?;
Err(Error::Trap("abort".into()).into())
}
"assert" => {
let condition = context.value_stack.pop_as::<i32>()?;
if condition == 0 {
self.abort_global.set(RuntimeValue::I32(1))?;
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: EnvParams) -> 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 abort_global = Arc::new(
VariableInstance::new(
false,
VariableType::I32,
RuntimeValue::I32(0)
).unwrap()
);
let total_mem_global = Arc::new(
VariableInstance::new(
false,
VariableType::I32,
RuntimeValue::I32(params.total_memory as i32),
).unwrap(),
);
let function_executor = EmscriptenFunctionExecutor {
abort_global: Arc::clone(&abort_global),
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),
("ABORT".into(), abort_global),
].into_iter()
.collect(),
functions: ::std::borrow::Cow::from(SIGNATURES),
};
Ok(native_module(instance, elements)?)
}
impl Default for EnvParams {
fn default() -> Self {
EnvParams {
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 EnvParams {
fn max_memory(&self) -> Option<u32> {
if self.allow_memory_growth { None } else { Some(self.total_memory) }
}
}

View File

@ -116,7 +116,6 @@ impl<U> From<U> for Error where U: UserError + Sized {
}
}
mod emscripten;
mod native;
mod imports;
mod memory;
@ -140,4 +139,3 @@ 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};
pub use self::emscripten::EmscriptenParams;

View File

@ -1,17 +1,16 @@
use std::sync::{Arc, Weak};
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::{ModuleInstance, ModuleInstanceInterface, ExecutionParams, ItemIndex,
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};
use builder::module;
/// Min index of native function.
pub const NATIVE_INDEX_FUNC_MIN: u32 = 10001;
@ -285,12 +284,6 @@ pub fn native_module<'a, E: UserFunctionExecutor + 'a>(base: Arc<ModuleInstanceI
Ok(Arc::new(NativeModuleInstance::new(base, user_elements)?))
}
pub fn simple_native_module<'a, E: UserFunctionExecutor + 'a>(user_elements: UserDefinedElements<E>) -> Result<Arc<ModuleInstanceInterface + 'a>, Error> {
let mut instance = ModuleInstance::new(Weak::default(), "env".into(), module().build())?;
instance.instantiate(None)?;
native_module(Arc::new(instance), user_elements)
}
impl<'a> PartialEq for UserFunctionDescriptor {
fn eq(&self, other: &Self) -> bool {
self.params() == other.params()

View File

@ -3,7 +3,6 @@ use std::collections::HashMap;
use parking_lot::RwLock;
use elements::Module;
use interpreter::Error;
use interpreter::emscripten::{self, env_module};
use interpreter::module::{ModuleInstance, ModuleInstanceInterface};
/// Program instance. Program is a set of instantiated modules.
@ -26,17 +25,6 @@ impl ProgramInstance {
}
}
/// Create new program instance with added Emscripten's `env` module.
///
/// You can specify desired environment params. Or you can just pass `Default::default()`.
pub fn with_emscripten_env(params: emscripten::EnvParams) -> Result<Self, Error> {
let instance = ProgramInstance {
essence: Arc::new(ProgramInstanceEssence::new()),
};
// instance.essence.modules.write().insert("env".into(), env_module(params)?);
Ok(instance)
}
/// 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)?;

View File

@ -7,12 +7,13 @@ use builder::module;
use elements::{ExportEntry, Internal, ImportEntry, External, GlobalEntry, GlobalType,
InitExpr, ValueType, BlockType, Opcodes, Opcode, FunctionType, TableType, MemoryType};
use interpreter::{Error, UserError, ProgramInstance};
use interpreter::native::{simple_native_module, native_module, UserDefinedElements, UserFunctionExecutor, UserFunctionDescriptor};
use interpreter::native::{native_module, UserDefinedElements, UserFunctionExecutor, UserFunctionDescriptor};
use interpreter::memory::MemoryInstance;
use interpreter::module::{ModuleInstance, ModuleInstanceInterface, CallerContext, ItemIndex, ExecutionParams, ExportEntryType, FunctionSignature};
use interpreter::validator::{FunctionValidationContext, Validator};
use interpreter::value::{RuntimeValue, TryInto};
use interpreter::variable::{VariableInstance, ExternalVariableValue, VariableType};
use super::utils::program_with_default_env;
#[test]
fn import_function() {
@ -195,14 +196,11 @@ impl<'a> UserFunctionExecutor for &'a mut FunctionExecutor {
#[test]
fn native_env_function() {
let program = ProgramInstance::new();
let env_module = module()
.memory()
.with_min(1)
.build()
.with_export(ExportEntry::new("memory".into(), Internal::Memory(0)))
.build();
let env_instance = program.add_module("env", env_module, None).unwrap();
// 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
@ -259,7 +257,7 @@ fn native_env_function() {
#[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 = ProgramInstance::with_emscripten_env(Default::default()).unwrap();
let program = program_with_default_env();
struct OwnMemoryReference {
pub memory: RefCell<Option<Arc<MemoryInstance>>>,
@ -287,9 +285,10 @@ fn native_env_function_own_memory() {
}
}
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 = simple_native_module(UserDefinedElements {
let native_env_instance = native_module(env_instance, UserDefinedElements {
executor: Some(&mut executor),
globals: HashMap::new(),
functions: ::std::borrow::Cow::from(SIGNATURES),
@ -336,8 +335,9 @@ fn native_env_global() {
}
let module_constructor = |elements: UserDefinedElements<DummyExecutor>| {
let program = ProgramInstance::new();
let native_env_instance = simple_native_module(elements).unwrap();
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()
@ -384,14 +384,8 @@ fn native_env_global() {
#[test]
fn native_custom_error() {
let program = ProgramInstance::new();
let env_module = module()
.memory()
.with_min(1)
.build()
.with_export(ExportEntry::new("memory".into(), Internal::Memory(0)))
.build();
let env_instance = program.add_module("env", env_module, None).unwrap();
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() };
@ -442,29 +436,10 @@ fn native_custom_error() {
assert_eq!(user_error2.downcast_ref::<UserErrorWithCode>().unwrap(), &UserErrorWithCode { error_code: 777 });
}
// TODO: Move into pwasm-emscripten
#[ignore]
#[test]
fn import_env_mutable_global() {
let program = ProgramInstance::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();
}
#[test]
fn env_native_export_entry_type_check() {
let program = ProgramInstance::new();
let env_module = module()
.memory()
.with_min(1)
.build()
.with_export(ExportEntry::new("memory".into(), Internal::Memory(0)))
.build();
let env_instance = program.add_module("env", env_module, None).unwrap();
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,

View File

@ -1,3 +1,29 @@
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)])))
.with_export(ExportEntry::new("tableBase".into(), Internal::Global(0)))
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, false), InitExpr::new(vec![Opcode::I32Const(0)])))
.with_export(ExportEntry::new("memoryBase".into(), Internal::Global(1)))
.build();
program.add_module("env", env_module, None).unwrap();
program
}
}

View File

@ -1,8 +1,9 @@
use elements::deserialize_file;
use elements::Module;
use elements::{Module, Internal, ExportEntry, InitExpr, Opcode, ValueType, GlobalType, GlobalEntry};
use interpreter::{ExecutionParams, ProgramInstance};
use interpreter::value::RuntimeValue;
use interpreter::module::{ModuleInstanceInterface, ItemIndex};
use builder::module;
#[test]
fn interpreter_inc_i32() {
@ -11,7 +12,22 @@ fn interpreter_inc_i32() {
// The WASM file containing the module and function
const WASM_FILE: &str = &"res/cases/v1/inc_i32.wasm";
let program = ProgramInstance::with_emscripten_env(Default::default()).expect("Failed to instanciate program");
let program = ProgramInstance::new();
let env_module = module()
.memory()
.with_min(256)
.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)])))
.with_export(ExportEntry::new("tableBase".into(), Internal::Global(0)))
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, false), InitExpr::new(vec![Opcode::I32Const(0)])))
.with_export(ExportEntry::new("memoryBase".into(), Internal::Global(1)))
.build();
let _env_instance = program.add_module("env", env_module, None).unwrap();
let module: Module =
deserialize_file(WASM_FILE).expect("Failed to deserialize module from buffer");
@ -43,7 +59,22 @@ fn interpreter_accumulate_u8() {
const BUF: &[u8] = &[9,8,7,6,5,4,3,2,1];
// Declare the memory limits of the runtime-environment
let program = ProgramInstance::with_emscripten_env(Default::default()).expect("Failed to instanciate program");
let program = ProgramInstance::new();
let env_module = module()
.memory()
.with_min(256)
.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)])))
.with_export(ExportEntry::new("tableBase".into(), Internal::Global(0)))
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, false), InitExpr::new(vec![Opcode::I32Const(0)])))
.with_export(ExportEntry::new("memoryBase".into(), Internal::Global(1)))
.build();
let _env_instance = program.add_module("env", env_module, None).unwrap();
// Load the module-structure from wasm-file and add to program
let module: Module =