mirror of
https://github.com/fluencelabs/wasmer
synced 2024-12-12 22:05:33 +00:00
Add special error types for compile, linking, and runtime errors. (#99)
* Add error types and convert most results to wasmer-runtime results * Fix spectests * Fix umbrella project to work with new error types
This commit is contained in:
parent
9c0d78ae46
commit
8a9f5fa61a
@ -11,7 +11,11 @@ use cranelift_codegen::{
|
||||
settings::{self, Configurable},
|
||||
};
|
||||
use target_lexicon::Triple;
|
||||
use wasmer_runtime::{backend::Compiler, module::ModuleInner};
|
||||
use wasmer_runtime::{
|
||||
backend::Compiler,
|
||||
error::{CompileError, CompileResult},
|
||||
module::ModuleInner,
|
||||
};
|
||||
use wasmparser::{self, WasmDecoder};
|
||||
|
||||
pub struct CraneliftCompiler {}
|
||||
@ -24,7 +28,7 @@ impl CraneliftCompiler {
|
||||
|
||||
impl Compiler for CraneliftCompiler {
|
||||
// Compiles wasm binary to a wasmer module.
|
||||
fn compile(&self, wasm: &[u8]) -> Result<ModuleInner, String> {
|
||||
fn compile(&self, wasm: &[u8]) -> CompileResult<ModuleInner> {
|
||||
validate(wasm)?;
|
||||
|
||||
let isa = get_isa();
|
||||
@ -53,15 +57,15 @@ fn get_isa() -> Box<isa::TargetIsa> {
|
||||
isa::lookup(Triple::host()).unwrap().finish(flags)
|
||||
}
|
||||
|
||||
fn validate(bytes: &[u8]) -> Result<(), String> {
|
||||
fn validate(bytes: &[u8]) -> CompileResult<()> {
|
||||
let mut parser = wasmparser::ValidatingParser::new(bytes, None);
|
||||
loop {
|
||||
let state = parser.read();
|
||||
match *state {
|
||||
wasmparser::ParserState::EndWasm => break Ok(()),
|
||||
wasmparser::ParserState::Error(err) => {
|
||||
return Err(format!("Validation error: {}", err.message));
|
||||
}
|
||||
wasmparser::ParserState::Error(err) => Err(CompileError::ValidationError {
|
||||
msg: err.message.to_string(),
|
||||
})?,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ use std::{
|
||||
use wasmer_runtime::{
|
||||
backend::FuncResolver,
|
||||
backend::SigRegistry,
|
||||
error::CompileResult,
|
||||
module::ModuleInner,
|
||||
structures::{Map, TypedIndex},
|
||||
types::{
|
||||
@ -67,7 +68,7 @@ impl Module {
|
||||
mut self,
|
||||
isa: &isa::TargetIsa,
|
||||
functions: Map<LocalFuncIndex, ir::Function>,
|
||||
) -> Result<ModuleInner, String> {
|
||||
) -> CompileResult<ModuleInner> {
|
||||
// we have to deduplicate `module.func_assoc`
|
||||
let func_assoc = &mut self.module.func_assoc;
|
||||
let sig_registry = &self.module.sig_registry;
|
||||
|
@ -5,6 +5,7 @@ use crate::{
|
||||
use cranelift_codegen::{ir, isa};
|
||||
use cranelift_wasm::{self, translate_module, FuncTranslator, ModuleEnvironment};
|
||||
use wasmer_runtime::{
|
||||
error::{CompileError, CompileResult},
|
||||
module::{DataInitializer, ExportIndex, ImportName, TableInitializer},
|
||||
structures::{Map, TypedIndex},
|
||||
types::{
|
||||
@ -32,8 +33,9 @@ impl<'module, 'isa> ModuleEnv<'module, 'isa> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn translate(mut self, wasm: &[u8]) -> Result<Map<LocalFuncIndex, ir::Function>, String> {
|
||||
translate_module(wasm, &mut self).map_err(|e| e.to_string())?;
|
||||
pub fn translate(mut self, wasm: &[u8]) -> CompileResult<Map<LocalFuncIndex, ir::Function>> {
|
||||
translate_module(wasm, &mut self)
|
||||
.map_err(|e| CompileError::InternalError { msg: e.to_string() })?;
|
||||
Ok(self.func_bodies)
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use std::ptr::{write_unaligned, NonNull};
|
||||
use wasmer_runtime::{
|
||||
self,
|
||||
backend::{self, Mmap, Protect},
|
||||
error::{CompileError, CompileResult},
|
||||
structures::Map,
|
||||
types::LocalFuncIndex,
|
||||
vm, vmcalls,
|
||||
@ -23,7 +24,7 @@ impl FuncResolverBuilder {
|
||||
pub fn new(
|
||||
isa: &isa::TargetIsa,
|
||||
function_bodies: Map<LocalFuncIndex, ir::Function>,
|
||||
) -> Result<Self, String> {
|
||||
) -> CompileResult<Self> {
|
||||
let mut compiled_functions: Vec<Vec<u8>> = Vec::with_capacity(function_bodies.len());
|
||||
let mut relocations = Map::with_capacity(function_bodies.len());
|
||||
let mut trap_sinks = Map::with_capacity(function_bodies.len());
|
||||
@ -38,7 +39,7 @@ impl FuncResolverBuilder {
|
||||
let mut trap_sink = TrapSink::new();
|
||||
|
||||
ctx.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink)
|
||||
.map_err(|e| format!("compile error: {}", e.to_string()))?;
|
||||
.map_err(|e| CompileError::InternalError { msg: e.to_string() })?;
|
||||
ctx.clear();
|
||||
// Round up each function's size to pointer alignment.
|
||||
total_size += round_up(code_buf.len(), mem::size_of::<usize>());
|
||||
@ -48,11 +49,24 @@ impl FuncResolverBuilder {
|
||||
trap_sinks.push(trap_sink);
|
||||
}
|
||||
|
||||
let mut memory = Mmap::with_size(total_size)?;
|
||||
let mut memory = Mmap::with_size(total_size)
|
||||
.map_err(|e| CompileError::InternalError { msg: e.to_string() })?;
|
||||
unsafe {
|
||||
memory.protect(0..memory.size(), Protect::ReadWrite)?;
|
||||
memory
|
||||
.protect(0..memory.size(), Protect::ReadWrite)
|
||||
.map_err(|e| CompileError::InternalError { msg: e.to_string() })?;
|
||||
}
|
||||
|
||||
// Normally, excess memory due to alignment and page-rounding would
|
||||
// be filled with null-bytes. On x86 (and x86_64),
|
||||
// "\x00\x00" disassembles to "add byte ptr [eax],al".
|
||||
//
|
||||
// If the instruction pointer falls out of its designated area,
|
||||
// it would be better if it would immediately crash instead of
|
||||
// continuing on and causing non-local issues.
|
||||
//
|
||||
// "\xCC" disassembles to "int3", which will immediately cause
|
||||
// an interrupt that we can catch if we want.
|
||||
for i in unsafe { memory.as_slice_mut() } {
|
||||
*i = 0xCC;
|
||||
}
|
||||
@ -77,7 +91,7 @@ impl FuncResolverBuilder {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn finalize(mut self) -> Result<FuncResolver, String> {
|
||||
pub fn finalize(mut self) -> CompileResult<FuncResolver> {
|
||||
for (index, relocs) in self.relocations.iter() {
|
||||
for ref reloc in relocs {
|
||||
let target_func_address: isize = match reloc.target {
|
||||
@ -98,11 +112,17 @@ impl FuncResolverBuilder {
|
||||
ir::LibCall::NearestF64 => libcalls::nearbyintf64 as isize,
|
||||
ir::LibCall::Probestack => libcalls::__rust_probestack as isize,
|
||||
_ => {
|
||||
panic!("unexpected libcall {}", libcall);
|
||||
Err(CompileError::InternalError {
|
||||
msg: format!("unexpected libcall: {}", libcall),
|
||||
})?
|
||||
// panic!("unexpected libcall {}", libcall);
|
||||
}
|
||||
},
|
||||
RelocationType::Intrinsic(ref name) => {
|
||||
panic!("unexpected intrinsic {}", name);
|
||||
Err(CompileError::InternalError {
|
||||
msg: format!("unexpected intrinsic: {}", name),
|
||||
})?
|
||||
// panic!("unexpected intrinsic {}", name);
|
||||
}
|
||||
RelocationType::VmCall(vmcall) => match vmcall {
|
||||
VmCall::LocalStaticMemoryGrow => vmcalls::local_static_memory_grow as _,
|
||||
@ -141,7 +161,9 @@ impl FuncResolverBuilder {
|
||||
(target_func_address - reloc_address + reloc_addend) as i32;
|
||||
write_unaligned(reloc_address as *mut i32, reloc_delta_i32);
|
||||
},
|
||||
_ => panic!("unsupported reloc kind"),
|
||||
_ => Err(CompileError::InternalError {
|
||||
msg: format!("unsupported reloc kind: {}", reloc.reloc),
|
||||
})?,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -149,7 +171,8 @@ impl FuncResolverBuilder {
|
||||
unsafe {
|
||||
self.resolver
|
||||
.memory
|
||||
.protect(0..self.resolver.memory.size(), Protect::ReadExec)?;
|
||||
.protect(0..self.resolver.memory.size(), Protect::ReadExec)
|
||||
.map_err(|e| CompileError::InternalError { msg: e.to_string() })?;;
|
||||
}
|
||||
|
||||
Ok(self.resolver)
|
||||
|
@ -8,7 +8,7 @@ use std::os::raw::c_char;
|
||||
use std::slice;
|
||||
use std::sync::Arc;
|
||||
/// We check if a provided module is an Emscripten generated one
|
||||
pub fn is_emscripten_module(module: &Arc<Module>) -> bool {
|
||||
pub fn is_emscripten_module(module: &Module) -> bool {
|
||||
for (_, import_name) in &module.0.imported_functions {
|
||||
if import_name.name == "_emscripten_memcpy_big" && import_name.namespace == "env" {
|
||||
return true;
|
||||
|
@ -81,6 +81,7 @@ use wasmer_clif_backend::CraneliftCompiler;
|
||||
use wasmer_runtime::import::Imports;
|
||||
use wasmer_runtime::types::Value;
|
||||
use wasmer_runtime::{{Instance, module::Module}};
|
||||
use wasmer_runtime::error::Result;
|
||||
|
||||
static IMPORT_MODULE: &str = r#"
|
||||
(module
|
||||
@ -610,11 +611,12 @@ fn {}_assert_malformed() {{
|
||||
let func_name = format!("{}_action_invoke", self.command_name());
|
||||
self.buffer.push_str(
|
||||
format!(
|
||||
"fn {func_name}(instance: &mut Instance) -> Result<(), String> {{
|
||||
"fn {func_name}(instance: &mut Instance) -> Result<()> {{
|
||||
println!(\"Executing function {{}}\", \"{func_name}\");
|
||||
let result = instance.call(\"{field}\", &[{args_values}]);
|
||||
{assertion}
|
||||
result.map(|_| ())
|
||||
result?;
|
||||
Ok(())
|
||||
}}\n",
|
||||
func_name = func_name,
|
||||
field = field,
|
||||
|
@ -2,6 +2,7 @@ use wabt::wat2wasm;
|
||||
use wasmer_clif_backend::CraneliftCompiler;
|
||||
use wasmer_runtime::{
|
||||
self as runtime,
|
||||
error::Result,
|
||||
export::{Context, Export, FuncPointer},
|
||||
import::{Imports, NamespaceMap},
|
||||
types::{FuncSig, Type, Value},
|
||||
@ -10,7 +11,7 @@ use wasmer_runtime::{
|
||||
|
||||
static EXAMPLE_WASM: &'static [u8] = include_bytes!("simple.wasm");
|
||||
|
||||
fn main() -> Result<(), String> {
|
||||
fn main() -> Result<()> {
|
||||
let wasm_binary = wat2wasm(IMPORT_MODULE.as_bytes()).expect("WAST not valid or malformed");
|
||||
let inner_module = runtime::compile(&wasm_binary, &CraneliftCompiler::new())?;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{module::ModuleInner, types::LocalFuncIndex, vm};
|
||||
use crate::{error::CompileResult, module::ModuleInner, types::LocalFuncIndex, vm};
|
||||
use std::ptr::NonNull;
|
||||
|
||||
pub use crate::mmap::{Mmap, Protect};
|
||||
@ -6,7 +6,7 @@ pub use crate::sig_registry::SigRegistry;
|
||||
|
||||
pub trait Compiler {
|
||||
/// Compiles a `Module` from WebAssembly binary format
|
||||
fn compile(&self, wasm: &[u8]) -> Result<ModuleInner, String>;
|
||||
fn compile(&self, wasm: &[u8]) -> CompileResult<ModuleInner>;
|
||||
}
|
||||
|
||||
pub trait FuncResolver {
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::{
|
||||
error::{LinkError, LinkResult},
|
||||
export::{Context, Export},
|
||||
import::Imports,
|
||||
memory::LinearMemory,
|
||||
@ -102,7 +103,6 @@ impl LocalBacking {
|
||||
LocalOrImport::Local(local_memory_index) => {
|
||||
let memory_desc = &module.memories[local_memory_index];
|
||||
let data_top = init_base + init.data.len();
|
||||
println!("data_top: {}", data_top);
|
||||
assert!((memory_desc.min * LinearMemory::PAGE_SIZE) as usize >= data_top);
|
||||
let mem: &mut LinearMemory = &mut memories[local_memory_index];
|
||||
|
||||
@ -306,7 +306,7 @@ impl ImportBacking {
|
||||
module: &ModuleInner,
|
||||
imports: &mut Imports,
|
||||
vmctx: *mut vm::Ctx,
|
||||
) -> Result<Self, String> {
|
||||
) -> LinkResult<Self> {
|
||||
Ok(ImportBacking {
|
||||
functions: import_functions(module, imports, vmctx)?,
|
||||
memories: import_memories(module, imports, vmctx)?,
|
||||
@ -324,7 +324,7 @@ fn import_functions(
|
||||
module: &ModuleInner,
|
||||
imports: &mut Imports,
|
||||
vmctx: *mut vm::Ctx,
|
||||
) -> Result<BoxedMap<ImportedFuncIndex, vm::ImportedFunc>, String> {
|
||||
) -> LinkResult<BoxedMap<ImportedFuncIndex, vm::ImportedFunc>> {
|
||||
let mut functions = Map::with_capacity(module.imported_functions.len());
|
||||
for (index, ImportName { namespace, name }) in &module.imported_functions {
|
||||
let sig_index = module.func_assoc[index.convert_up(module)];
|
||||
@ -347,18 +347,33 @@ fn import_functions(
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return Err(format!(
|
||||
"unexpected signature for {:?}:{:?}",
|
||||
namespace, name
|
||||
));
|
||||
Err(LinkError::IncorrectImportSignature {
|
||||
namespace: namespace.clone(),
|
||||
name: name.clone(),
|
||||
expected: expected_sig.clone(),
|
||||
found: signature.clone(),
|
||||
})?
|
||||
}
|
||||
}
|
||||
Some(_) => {
|
||||
return Err(format!("incorrect import type for {}:{}", namespace, name));
|
||||
}
|
||||
None => {
|
||||
return Err(format!("import not found: {}:{}", namespace, name));
|
||||
Some(export_type) => {
|
||||
let export_type_name = match export_type {
|
||||
Export::Function { .. } => "function",
|
||||
Export::Memory { .. } => "memory",
|
||||
Export::Table { .. } => "table",
|
||||
Export::Global { .. } => "global",
|
||||
}
|
||||
.to_string();
|
||||
Err(LinkError::IncorrectImportType {
|
||||
namespace: namespace.clone(),
|
||||
name: name.clone(),
|
||||
expected: "function".to_string(),
|
||||
found: export_type_name,
|
||||
})?
|
||||
}
|
||||
None => Err(LinkError::ImportNotFound {
|
||||
namespace: namespace.clone(),
|
||||
name: name.clone(),
|
||||
})?,
|
||||
}
|
||||
}
|
||||
Ok(functions.into_boxed_map())
|
||||
@ -368,7 +383,7 @@ fn import_memories(
|
||||
module: &ModuleInner,
|
||||
imports: &mut Imports,
|
||||
vmctx: *mut vm::Ctx,
|
||||
) -> Result<BoxedMap<ImportedMemoryIndex, vm::ImportedMemory>, String> {
|
||||
) -> LinkResult<BoxedMap<ImportedMemoryIndex, vm::ImportedMemory>> {
|
||||
let mut memories = Map::with_capacity(module.imported_memories.len());
|
||||
for (_index, (ImportName { namespace, name }, expected_memory_desc)) in
|
||||
&module.imported_memories
|
||||
@ -391,18 +406,33 @@ fn import_memories(
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return Err(format!(
|
||||
"incorrect memory description for {}:{}",
|
||||
namespace, name,
|
||||
));
|
||||
Err(LinkError::IncorrectMemoryDescription {
|
||||
namespace: namespace.clone(),
|
||||
name: name.clone(),
|
||||
expected: expected_memory_desc.clone(),
|
||||
found: memory_desc.clone(),
|
||||
})?
|
||||
}
|
||||
}
|
||||
Some(_) => {
|
||||
return Err(format!("incorrect import type for {}:{}", namespace, name));
|
||||
}
|
||||
None => {
|
||||
return Err(format!("import not found: {}:{}", namespace, name));
|
||||
Some(export_type) => {
|
||||
let export_type_name = match export_type {
|
||||
Export::Function { .. } => "function",
|
||||
Export::Memory { .. } => "memory",
|
||||
Export::Table { .. } => "table",
|
||||
Export::Global { .. } => "global",
|
||||
}
|
||||
.to_string();
|
||||
Err(LinkError::IncorrectImportType {
|
||||
namespace: namespace.clone(),
|
||||
name: name.clone(),
|
||||
expected: "memory".to_string(),
|
||||
found: export_type_name,
|
||||
})?
|
||||
}
|
||||
None => Err(LinkError::ImportNotFound {
|
||||
namespace: namespace.clone(),
|
||||
name: name.clone(),
|
||||
})?,
|
||||
}
|
||||
}
|
||||
Ok(memories.into_boxed_map())
|
||||
@ -412,7 +442,7 @@ fn import_tables(
|
||||
module: &ModuleInner,
|
||||
imports: &mut Imports,
|
||||
vmctx: *mut vm::Ctx,
|
||||
) -> Result<BoxedMap<ImportedTableIndex, vm::ImportedTable>, String> {
|
||||
) -> LinkResult<BoxedMap<ImportedTableIndex, vm::ImportedTable>> {
|
||||
let mut tables = Map::with_capacity(module.imported_tables.len());
|
||||
for (_index, (ImportName { namespace, name }, expected_table_desc)) in &module.imported_tables {
|
||||
let table_import = imports
|
||||
@ -433,18 +463,33 @@ fn import_tables(
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return Err(format!(
|
||||
"incorrect table description for {}:{}",
|
||||
namespace, name,
|
||||
));
|
||||
Err(LinkError::IncorrectTableDescription {
|
||||
namespace: namespace.clone(),
|
||||
name: name.clone(),
|
||||
expected: expected_table_desc.clone(),
|
||||
found: table_desc.clone(),
|
||||
})?
|
||||
}
|
||||
}
|
||||
Some(_) => {
|
||||
return Err(format!("incorrect import type for {}:{}", namespace, name));
|
||||
}
|
||||
None => {
|
||||
return Err(format!("import not found: {}:{}", namespace, name));
|
||||
Some(export_type) => {
|
||||
let export_type_name = match export_type {
|
||||
Export::Function { .. } => "function",
|
||||
Export::Memory { .. } => "memory",
|
||||
Export::Table { .. } => "table",
|
||||
Export::Global { .. } => "global",
|
||||
}
|
||||
.to_string();
|
||||
Err(LinkError::IncorrectImportType {
|
||||
namespace: namespace.clone(),
|
||||
name: name.clone(),
|
||||
expected: "table".to_string(),
|
||||
found: export_type_name,
|
||||
})?
|
||||
}
|
||||
None => Err(LinkError::ImportNotFound {
|
||||
namespace: namespace.clone(),
|
||||
name: name.clone(),
|
||||
})?,
|
||||
}
|
||||
}
|
||||
Ok(tables.into_boxed_map())
|
||||
@ -453,7 +498,7 @@ fn import_tables(
|
||||
fn import_globals(
|
||||
module: &ModuleInner,
|
||||
imports: &mut Imports,
|
||||
) -> Result<BoxedMap<ImportedGlobalIndex, vm::ImportedGlobal>, String> {
|
||||
) -> LinkResult<BoxedMap<ImportedGlobalIndex, vm::ImportedGlobal>> {
|
||||
let mut globals = Map::with_capacity(module.imported_globals.len());
|
||||
for (_, (ImportName { namespace, name }, imported_global_desc)) in &module.imported_globals {
|
||||
let import = imports
|
||||
@ -466,18 +511,33 @@ fn import_globals(
|
||||
global: local.inner(),
|
||||
});
|
||||
} else {
|
||||
return Err(format!(
|
||||
"unexpected global description for {:?}:{:?}",
|
||||
namespace, name
|
||||
));
|
||||
Err(LinkError::IncorrectGlobalDescription {
|
||||
namespace: namespace.clone(),
|
||||
name: name.clone(),
|
||||
expected: imported_global_desc.clone(),
|
||||
found: global.clone(),
|
||||
})?
|
||||
}
|
||||
}
|
||||
Some(_) => {
|
||||
return Err(format!("incorrect import type for {}:{}", namespace, name));
|
||||
}
|
||||
None => {
|
||||
return Err(format!("import not found: {}:{}", namespace, name));
|
||||
Some(export_type) => {
|
||||
let export_type_name = match export_type {
|
||||
Export::Function { .. } => "function",
|
||||
Export::Memory { .. } => "memory",
|
||||
Export::Table { .. } => "table",
|
||||
Export::Global { .. } => "global",
|
||||
}
|
||||
.to_string();
|
||||
Err(LinkError::IncorrectImportType {
|
||||
namespace: namespace.clone(),
|
||||
name: name.clone(),
|
||||
expected: "global".to_string(),
|
||||
found: export_type_name,
|
||||
})?
|
||||
}
|
||||
None => Err(LinkError::ImportNotFound {
|
||||
namespace: namespace.clone(),
|
||||
name: name.clone(),
|
||||
})?,
|
||||
}
|
||||
}
|
||||
Ok(globals.into_boxed_map())
|
||||
|
162
lib/runtime/src/error.rs
Normal file
162
lib/runtime/src/error.rs
Normal file
@ -0,0 +1,162 @@
|
||||
use crate::types::{FuncSig, GlobalDesc, Memory, MemoryIndex, Table, TableIndex, Type};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Box<Error>>;
|
||||
pub type CompileResult<T> = std::result::Result<T, Box<CompileError>>;
|
||||
pub type LinkResult<T> = std::result::Result<T, Box<LinkError>>;
|
||||
pub type RuntimeResult<T> = std::result::Result<T, Box<RuntimeError>>;
|
||||
pub type CallResult<T> = std::result::Result<T, Box<CallError>>;
|
||||
|
||||
/// This is returned when the chosen compiler is unable to
|
||||
/// successfully compile the provided webassembly module into
|
||||
/// a `Module`.
|
||||
///
|
||||
/// Comparing two `CompileError`s always evaluates to false.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CompileError {
|
||||
ValidationError { msg: String },
|
||||
InternalError { msg: String },
|
||||
}
|
||||
|
||||
impl PartialEq for CompileError {
|
||||
fn eq(&self, _other: &CompileError) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// This is returned when the runtime is unable to
|
||||
/// correctly link the module with the provided imports.
|
||||
///
|
||||
/// Comparing two `LinkError`s always evaluates to false.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LinkError {
|
||||
IncorrectImportType {
|
||||
namespace: String,
|
||||
name: String,
|
||||
expected: String,
|
||||
found: String,
|
||||
},
|
||||
IncorrectImportSignature {
|
||||
namespace: String,
|
||||
name: String,
|
||||
expected: FuncSig,
|
||||
found: FuncSig,
|
||||
},
|
||||
ImportNotFound {
|
||||
namespace: String,
|
||||
name: String,
|
||||
},
|
||||
IncorrectMemoryDescription {
|
||||
namespace: String,
|
||||
name: String,
|
||||
expected: Memory,
|
||||
found: Memory,
|
||||
},
|
||||
IncorrectTableDescription {
|
||||
namespace: String,
|
||||
name: String,
|
||||
expected: Table,
|
||||
found: Table,
|
||||
},
|
||||
IncorrectGlobalDescription {
|
||||
namespace: String,
|
||||
name: String,
|
||||
expected: GlobalDesc,
|
||||
found: GlobalDesc,
|
||||
},
|
||||
}
|
||||
|
||||
impl PartialEq for LinkError {
|
||||
fn eq(&self, _other: &LinkError) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the error type returned when calling
|
||||
/// a webassembly function.
|
||||
///
|
||||
/// The main way to do this is `Instance.call`.
|
||||
///
|
||||
/// Comparing two `RuntimeError`s always evaluates to false.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RuntimeError {
|
||||
OutOfBoundsAccess { memory: MemoryIndex, addr: u32 },
|
||||
IndirectCallSignature { table: TableIndex },
|
||||
IndirectCallToNull { table: TableIndex },
|
||||
Unknown { msg: String },
|
||||
}
|
||||
|
||||
impl PartialEq for RuntimeError {
|
||||
fn eq(&self, _other: &RuntimeError) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// This error type is produced by calling a wasm function
|
||||
/// exported from a module.
|
||||
///
|
||||
/// If the module traps in some way while running, this will
|
||||
/// be the `CallError::Runtime(RuntimeError)` variant.
|
||||
///
|
||||
/// Comparing two `CallError`s always evaluates to false.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CallError {
|
||||
Signature { expected: FuncSig, found: Vec<Type> },
|
||||
NoSuchExport { name: String },
|
||||
ExportNotFunc { name: String },
|
||||
Runtime(RuntimeError),
|
||||
}
|
||||
|
||||
impl PartialEq for CallError {
|
||||
fn eq(&self, _other: &CallError) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// The amalgamation of all errors that can occur
|
||||
/// during the compilation, instantiation, or execution
|
||||
/// of a webassembly module.
|
||||
///
|
||||
/// Comparing two `Error`s always evaluates to false.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Error {
|
||||
CompileError(CompileError),
|
||||
LinkError(LinkError),
|
||||
RuntimeError(RuntimeError),
|
||||
CallError(CallError),
|
||||
}
|
||||
|
||||
impl PartialEq for Error {
|
||||
fn eq(&self, _other: &Error) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<CompileError>> for Box<Error> {
|
||||
fn from(compile_err: Box<CompileError>) -> Self {
|
||||
Box::new(Error::CompileError(*compile_err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<LinkError>> for Box<Error> {
|
||||
fn from(link_err: Box<LinkError>) -> Self {
|
||||
Box::new(Error::LinkError(*link_err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<RuntimeError>> for Box<Error> {
|
||||
fn from(runtime_err: Box<RuntimeError>) -> Self {
|
||||
Box::new(Error::RuntimeError(*runtime_err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<CallError>> for Box<Error> {
|
||||
fn from(call_err: Box<CallError>) -> Self {
|
||||
Box::new(Error::CallError(*call_err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<RuntimeError>> for Box<CallError> {
|
||||
fn from(runtime_err: Box<RuntimeError>) -> Self {
|
||||
Box::new(CallError::Runtime(*runtime_err))
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
use crate::recovery::call_protected;
|
||||
use crate::{
|
||||
backing::{ImportBacking, LocalBacking},
|
||||
error::{CallError, CallResult, Result},
|
||||
export::{
|
||||
Context, Export, ExportIter, FuncPointer, GlobalPointer, MemoryPointer, TablePointer,
|
||||
},
|
||||
@ -31,10 +32,7 @@ pub struct Instance {
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
pub(crate) fn new(
|
||||
module: Rc<ModuleInner>,
|
||||
mut imports: Box<Imports>,
|
||||
) -> Result<Instance, String> {
|
||||
pub(crate) fn new(module: Rc<ModuleInner>, mut imports: Box<Imports>) -> Result<Instance> {
|
||||
// We need the backing and import_backing to create a vm::Ctx, but we need
|
||||
// a vm::Ctx to create a backing and an import_backing. The solution is to create an
|
||||
// uninitialized vm::Ctx and then initialize it in-place.
|
||||
@ -73,17 +71,22 @@ impl Instance {
|
||||
///
|
||||
/// This will eventually return `Result<Option<Vec<Value>>, String>` in
|
||||
/// order to support multi-value returns.
|
||||
pub fn call(&mut self, name: &str, args: &[Value]) -> Result<Option<Value>, String> {
|
||||
let export_index = self
|
||||
.module
|
||||
.exports
|
||||
.get(name)
|
||||
.ok_or_else(|| format!("there is no export with that name: {}", name))?;
|
||||
pub fn call(&mut self, name: &str, args: &[Value]) -> CallResult<Option<Value>> {
|
||||
let export_index =
|
||||
self.module
|
||||
.exports
|
||||
.get(name)
|
||||
.ok_or_else(|| CallError::NoSuchExport {
|
||||
name: name.to_string(),
|
||||
})?;
|
||||
|
||||
let func_index = if let ExportIndex::Func(func_index) = export_index {
|
||||
*func_index
|
||||
} else {
|
||||
return Err("that export is not a function".to_string());
|
||||
return Err(CallError::ExportNotFunc {
|
||||
name: name.to_string(),
|
||||
}
|
||||
.into());
|
||||
};
|
||||
|
||||
self.call_with_index(func_index, args)
|
||||
@ -103,7 +106,7 @@ impl Instance {
|
||||
&mut self,
|
||||
func_index: FuncIndex,
|
||||
args: &[Value],
|
||||
) -> Result<Option<Value>, String> {
|
||||
) -> CallResult<Option<Value>> {
|
||||
let (func_ref, ctx, signature) = self.inner.get_func_from_index(&self.module, func_index);
|
||||
|
||||
let func_ptr = CodePtr::from_ptr(func_ref.inner() as _);
|
||||
@ -118,7 +121,10 @@ impl Instance {
|
||||
);
|
||||
|
||||
if !signature.check_sig(args) {
|
||||
return Err("incorrect signature".to_string());
|
||||
Err(CallError::Signature {
|
||||
expected: signature.clone(),
|
||||
found: args.iter().map(|val| val.ty()).collect(),
|
||||
})?
|
||||
}
|
||||
|
||||
let libffi_args: Vec<_> = args
|
||||
@ -132,7 +138,7 @@ impl Instance {
|
||||
.chain(iter::once(libffi_arg(&vmctx_ptr)))
|
||||
.collect();
|
||||
|
||||
call_protected(|| {
|
||||
Ok(call_protected(|| {
|
||||
signature
|
||||
.returns
|
||||
.first()
|
||||
@ -149,7 +155,7 @@ impl Instance {
|
||||
}
|
||||
None
|
||||
})
|
||||
})
|
||||
})?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ pub mod macros;
|
||||
#[doc(hidden)]
|
||||
pub mod backend;
|
||||
mod backing;
|
||||
pub mod error;
|
||||
pub mod export;
|
||||
pub mod import;
|
||||
pub mod instance;
|
||||
@ -23,14 +24,15 @@ pub mod vm;
|
||||
#[doc(hidden)]
|
||||
pub mod vmcalls;
|
||||
|
||||
pub use self::import::Imports;
|
||||
use self::error::CompileResult;
|
||||
pub use self::instance::Instance;
|
||||
#[doc(inline)]
|
||||
pub use self::module::Module;
|
||||
pub use self::error::Result;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Compile a webassembly module using the provided compiler.
|
||||
pub fn compile(wasm: &[u8], compiler: &dyn backend::Compiler) -> Result<module::Module, String> {
|
||||
pub fn compile(wasm: &[u8], compiler: &dyn backend::Compiler) -> CompileResult<module::Module> {
|
||||
compiler
|
||||
.compile(wasm)
|
||||
.map(|inner| module::Module::new(Rc::new(inner)))
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
backend::FuncResolver,
|
||||
error::Result,
|
||||
import::Imports,
|
||||
sig_registry::SigRegistry,
|
||||
structures::Map,
|
||||
@ -47,7 +48,7 @@ impl Module {
|
||||
}
|
||||
|
||||
/// Instantiate a webassembly module with the provided imports.
|
||||
pub fn instantiate(&self, imports: Imports) -> Result<Instance, String> {
|
||||
pub fn instantiate(&self, imports: Imports) -> Result<Instance> {
|
||||
Instance::new(Rc::clone(&self.0), Box::new(imports))
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,10 @@
|
||||
//! are very special, the async signal unsafety of Rust's TLS implementation generally does not affect the correctness here
|
||||
//! unless you have memory unsafety elsewhere in your code.
|
||||
|
||||
use crate::sighandler::install_sighandler;
|
||||
use crate::{
|
||||
error::{RuntimeError, RuntimeResult},
|
||||
sighandler::install_sighandler,
|
||||
};
|
||||
use nix::libc::siginfo_t;
|
||||
use nix::sys::signal::{Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV};
|
||||
use std::cell::{Cell, UnsafeCell};
|
||||
@ -23,7 +26,7 @@ thread_local! {
|
||||
pub static CAUGHT_ADDRESS: Cell<usize> = Cell::new(0);
|
||||
}
|
||||
|
||||
pub fn call_protected<T>(f: impl FnOnce() -> T) -> Result<T, String> {
|
||||
pub fn call_protected<T>(f: impl FnOnce() -> T) -> RuntimeResult<T> {
|
||||
unsafe {
|
||||
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
|
||||
let prev_jmp_buf = *jmp_buf;
|
||||
@ -45,7 +48,11 @@ pub fn call_protected<T>(f: impl FnOnce() -> T) -> Result<T, String> {
|
||||
Err(_) => "error while getting the Signal",
|
||||
_ => "unkown trapped signal",
|
||||
};
|
||||
Err(format!("trap at {:#x} - {}", addr, signal))
|
||||
// When the trap-handler is fully implemented, this will return more information.
|
||||
Err(RuntimeError::Unknown {
|
||||
msg: format!("trap at {:#x} - {}", addr, signal),
|
||||
}
|
||||
.into())
|
||||
} else {
|
||||
let ret = f(); // TODO: Switch stack?
|
||||
*jmp_buf = prev_jmp_buf;
|
||||
|
@ -2,7 +2,10 @@
|
||||
mod tests {
|
||||
use wabt::wat2wasm;
|
||||
use wasmer_clif_backend::CraneliftCompiler;
|
||||
use wasmer_runtime::import::Imports;
|
||||
use wasmer_runtime::{
|
||||
error::{CallError, RuntimeError},
|
||||
import::Imports,
|
||||
};
|
||||
|
||||
// The semantics of stack overflow are documented at:
|
||||
// https://webassembly.org/docs/semantics/#stack-overflow
|
||||
@ -25,15 +28,16 @@ mod tests {
|
||||
.instantiate(Imports::new())
|
||||
.expect("WASM can't be instantiated");
|
||||
let result = instance.call("stack-overflow", &[]);
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"should fail with error due to stack overflow"
|
||||
);
|
||||
// TODO The kind of error and message needs to be defined, not spec defined, maybe RuntimeError or RangeError
|
||||
if let Err(message) = result {
|
||||
assert!(!message.contains("segmentation violation"));
|
||||
assert!(!message.contains("bus error"));
|
||||
|
||||
match result {
|
||||
Err(err) => match *err {
|
||||
CallError::Runtime(RuntimeError::Unknown { msg }) => {
|
||||
assert!(!msg.contains("segmentation violation"));
|
||||
assert!(!msg.contains("bus error"));
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
},
|
||||
Ok(_) => panic!("should fail with error due to stack overflow"),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ use structopt::StructOpt;
|
||||
|
||||
use wasmer::*;
|
||||
use wasmer_emscripten;
|
||||
use wasmer_runtime;
|
||||
use wasmer_runtime as runtime;
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[structopt(name = "wasmer", about = "WASM execution runtime.")]
|
||||
@ -65,14 +65,14 @@ fn execute_wasm(options: &Run) -> Result<(), String> {
|
||||
|
||||
if !webassembly::utils::is_wasm_binary(&wasm_binary) {
|
||||
wasm_binary = wabt::wat2wasm(wasm_binary)
|
||||
.map_err(|err| format!("Can't convert from wast to wasm: {:?}", err))?;
|
||||
.map_err(|e| format!("Can't convert from wast to wasm: {:?}", e))?;
|
||||
}
|
||||
|
||||
let isa = webassembly::get_isa();
|
||||
|
||||
debug!("webassembly - creating module");
|
||||
let module = webassembly::compile(&wasm_binary[..])
|
||||
.map_err(|err| format!("Can't create the WebAssembly module: {}", err))?;
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
|
||||
let abi = if wasmer_emscripten::is_emscripten_module(&module) {
|
||||
webassembly::InstanceABI::Emscripten
|
||||
@ -100,14 +100,14 @@ fn execute_wasm(options: &Run) -> Result<(), String> {
|
||||
|
||||
let mut instance = module
|
||||
.instantiate(import_object)
|
||||
.map_err(|err| format!("Can't instantiate the WebAssembly module: {}", err))?;
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
|
||||
webassembly::start_instance(
|
||||
Arc::clone(&module),
|
||||
Ok(webassembly::start_instance(
|
||||
&module,
|
||||
&mut instance,
|
||||
options.path.to_str().unwrap(),
|
||||
options.args.iter().map(|arg| arg.as_str()).collect(),
|
||||
)
|
||||
).map_err(|e| format!("{:?}", e))?)
|
||||
}
|
||||
|
||||
fn run(options: Run) {
|
||||
@ -115,7 +115,7 @@ fn run(options: Run) {
|
||||
Ok(()) => {}
|
||||
Err(message) => {
|
||||
// let name = options.path.as_os_str().to_string_lossy();
|
||||
println!("{}", message);
|
||||
println!("{:?}", message);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +0,0 @@
|
||||
//! The webassembly::CompileError() constructor creates a new WebAssembly
|
||||
//! CompileError object, which indicates an error during WebAssembly
|
||||
//! decoding or validation
|
||||
|
||||
//! The webassembly::LinkError() constructor creates a new WebAssembly
|
||||
//! LinkError object, which indicates an error during module instantiation
|
||||
//! (besides traps from the start function).
|
||||
|
||||
//! The webassembly::RuntimeError() constructor creates a new WebAssembly
|
||||
//! RuntimeError object — the type that is thrown whenever WebAssembly
|
||||
//! specifies a trap.
|
||||
|
||||
error_chain! {
|
||||
errors {
|
||||
CompileError(reason: String) {
|
||||
description("WebAssembly compilation error")
|
||||
display("Compilation error: {}", reason)
|
||||
}
|
||||
|
||||
LinkError(reason: String) {
|
||||
description("WebAssembly link error")
|
||||
display("Link error: {}", reason)
|
||||
}
|
||||
|
||||
RuntimeError(reason: String) {
|
||||
description("WebAssembly runtime error")
|
||||
display("Runtime error: {}", reason)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
pub mod errors;
|
||||
pub mod libcalls;
|
||||
pub mod relocation;
|
||||
pub mod utils;
|
||||
|
||||
use wasmer_clif_backend::CraneliftCompiler;
|
||||
use wasmer_runtime::{
|
||||
backend::Compiler,
|
||||
self as runtime,
|
||||
import::Imports,
|
||||
instance::Instance,
|
||||
module::{Module, ModuleInner},
|
||||
error::{Result, CallResult},
|
||||
};
|
||||
|
||||
use cranelift_codegen::{
|
||||
@ -23,14 +23,12 @@ use target_lexicon;
|
||||
use wasmparser;
|
||||
use wasmparser::WasmDecoder;
|
||||
|
||||
pub use self::errors::{Error, ErrorKind};
|
||||
|
||||
use wasmer_emscripten::{allocate_cstr_on_stack, allocate_on_stack, is_emscripten_module};
|
||||
|
||||
pub struct ResultObject {
|
||||
/// A webassembly::Module object representing the compiled WebAssembly module.
|
||||
/// This Module can be instantiated again
|
||||
pub module: Arc<Module>,
|
||||
pub module: Module,
|
||||
/// A webassembly::Instance object that contains all the Exported WebAssembly
|
||||
/// functions.
|
||||
pub instance: Box<Instance>,
|
||||
@ -70,7 +68,7 @@ pub fn instantiate(
|
||||
buffer_source: &[u8],
|
||||
import_object: &Imports,
|
||||
options: Option<InstanceOptions>,
|
||||
) -> Result<ResultObject, ErrorKind> {
|
||||
) -> Result<ResultObject> {
|
||||
debug!("webassembly - creating instance");
|
||||
|
||||
//let instance = Instance::new(&module, import_object, options)?;
|
||||
@ -107,7 +105,7 @@ pub fn instantiate(
|
||||
pub fn instantiate_streaming(
|
||||
_buffer_source: Vec<u8>,
|
||||
_import_object: Imports,
|
||||
) -> Result<ResultObject, ErrorKind> {
|
||||
) -> Result<ResultObject> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
@ -121,40 +119,11 @@ pub fn instantiate_streaming(
|
||||
/// Errors:
|
||||
/// If the operation fails, the Result rejects with a
|
||||
/// webassembly::CompileError.
|
||||
pub fn compile(buffer_source: &[u8]) -> Result<Arc<Module>, ErrorKind> {
|
||||
let compiler = &CraneliftCompiler {};
|
||||
let module_inner = compiler
|
||||
.compile(buffer_source)
|
||||
.map_err(|e| ErrorKind::CompileError(e))?;
|
||||
pub fn compile(buffer_source: &[u8]) -> Result<Module> {
|
||||
let compiler = CraneliftCompiler::new();
|
||||
let module = runtime::compile(buffer_source, &compiler)?;
|
||||
|
||||
Ok(Arc::new(Module(Rc::new(module_inner))))
|
||||
}
|
||||
|
||||
/// The webassembly::validate() function validates a given typed
|
||||
/// array of WebAssembly binary code, returning whether the bytes
|
||||
/// form a valid wasm module (true) or not (false).
|
||||
/// Params:
|
||||
/// * `buffer_source`: A `&[u8]` containing the
|
||||
/// binary code of the .wasm module you want to compile.
|
||||
pub fn validate(buffer_source: &[u8]) -> bool {
|
||||
validate_or_error(buffer_source).is_ok()
|
||||
}
|
||||
|
||||
pub fn validate_or_error(bytes: &[u8]) -> Result<(), ErrorKind> {
|
||||
let mut parser = wasmparser::ValidatingParser::new(bytes, None);
|
||||
loop {
|
||||
let state = parser.read();
|
||||
match *state {
|
||||
wasmparser::ParserState::EndWasm => return Ok(()),
|
||||
wasmparser::ParserState::Error(err) => {
|
||||
return Err(ErrorKind::CompileError(format!(
|
||||
"Validation error: {}",
|
||||
err.message
|
||||
)));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
pub fn get_isa() -> Box<isa::TargetIsa> {
|
||||
@ -226,19 +195,19 @@ pub fn get_isa() -> Box<isa::TargetIsa> {
|
||||
// }
|
||||
|
||||
pub fn start_instance(
|
||||
module: Arc<Module>,
|
||||
module: &Module,
|
||||
instance: &mut Instance,
|
||||
path: &str,
|
||||
args: Vec<&str>,
|
||||
) -> Result<(), String> {
|
||||
let main_name = if is_emscripten_module(&module) {
|
||||
) -> CallResult<()> {
|
||||
let main_name = if is_emscripten_module(module) {
|
||||
"_main"
|
||||
} else {
|
||||
"main"
|
||||
};
|
||||
|
||||
// TODO handle args
|
||||
instance.call(main_name, &[]).map(|o| ())
|
||||
instance.call(main_name, &[])?;
|
||||
// TODO atinit and atexit for emscripten
|
||||
|
||||
// if let Some(ref emscripten_data) = &instance.emscripten_data {
|
||||
@ -284,4 +253,5 @@ pub fn start_instance(
|
||||
// let main: extern "C" fn(&Instance) = get_instance_function!(instance, func_index);
|
||||
// call_protected!(main(&instance)).map_err(|err| format!("{}", err))
|
||||
// }
|
||||
Ok(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user