Begin internal reorganization.
This begins reorganizing how translation and compilation occur, and setting up infrastructure for imports/exports and relocations. It splits parts out of StandaloneRuntime, forming Module, Compilation, and Instance structs, which can be used more independently. It also simplifies the command-line interface, in a step towards making simple tools that just expose the functionality of the libraries.
This commit is contained in:
@@ -25,14 +25,9 @@ cretonne-native = { git = "https://github.com/stoklund/cretonne.git" }
|
|||||||
wasmstandalone_runtime = { path = "lib/runtime" }
|
wasmstandalone_runtime = { path = "lib/runtime" }
|
||||||
wasmstandalone_execute = { path = "lib/execute" }
|
wasmstandalone_execute = { path = "lib/execute" }
|
||||||
wasmstandalone_obj = { path = "lib/obj" }
|
wasmstandalone_obj = { path = "lib/obj" }
|
||||||
wasmparser = "0.11.2"
|
|
||||||
wasmtext = { git = "https://github.com/yurydelendik/wasmtext" }
|
|
||||||
filecheck = "0.0.1"
|
|
||||||
docopt = "0.8.0"
|
docopt = "0.8.0"
|
||||||
serde = "1.0.8"
|
serde = "1.0.8"
|
||||||
serde_derive = "1.0.8"
|
serde_derive = "1.0.8"
|
||||||
num_cpus = "1.5.1"
|
|
||||||
term = "0.4.6"
|
|
||||||
tempdir = "*"
|
tempdir = "*"
|
||||||
faerie = { git = "https://github.com/m4b/faerie" }
|
faerie = { git = "https://github.com/m4b/faerie" }
|
||||||
|
|
||||||
|
|||||||
@@ -7,115 +7,58 @@ extern crate cton_wasm;
|
|||||||
extern crate region;
|
extern crate region;
|
||||||
extern crate wasmstandalone_runtime;
|
extern crate wasmstandalone_runtime;
|
||||||
|
|
||||||
use cretonne::Context;
|
|
||||||
use cretonne::isa::TargetIsa;
|
use cretonne::isa::TargetIsa;
|
||||||
use cretonne::verify_function;
|
|
||||||
use cretonne::verifier;
|
|
||||||
use cretonne::result::CtonError;
|
|
||||||
use cretonne::ir::entities::AnyEntity;
|
|
||||||
use cretonne::ir::{Ebb, FuncRef, JumpTable, Function};
|
|
||||||
use cretonne::binemit::{RelocSink, Reloc, CodeOffset};
|
|
||||||
use cton_wasm::{TranslationResult, FunctionIndex};
|
|
||||||
use std::mem::transmute;
|
use std::mem::transmute;
|
||||||
use region::Protection;
|
use region::Protection;
|
||||||
use region::protect;
|
use region::protect;
|
||||||
use std::ptr::write_unaligned;
|
use std::ptr::write_unaligned;
|
||||||
use std::fmt::Write;
|
use wasmstandalone_runtime::Compilation;
|
||||||
|
|
||||||
type RelocRef = u16;
|
|
||||||
|
|
||||||
// Implementation of a relocation sink that just saves all the information for later
|
|
||||||
struct StandaloneRelocSink {
|
|
||||||
ebbs: Vec<(RelocRef, Ebb, CodeOffset)>,
|
|
||||||
funcs: Vec<(RelocRef, FuncRef, CodeOffset)>,
|
|
||||||
jts: Vec<(RelocRef, JumpTable, CodeOffset)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contains all the metadata necessary to perform relocations
|
|
||||||
struct FunctionMetaData {
|
|
||||||
relocs: StandaloneRelocSink,
|
|
||||||
il_func: Function,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RelocSink for StandaloneRelocSink {
|
|
||||||
fn reloc_ebb(&mut self, offset: CodeOffset, reloc: Reloc, ebb: Ebb) {
|
|
||||||
self.ebbs.push((reloc.0, ebb, offset));
|
|
||||||
}
|
|
||||||
fn reloc_func(&mut self, offset: CodeOffset, reloc: Reloc, func: FuncRef) {
|
|
||||||
self.funcs.push((reloc.0, func, offset));
|
|
||||||
}
|
|
||||||
fn reloc_jt(&mut self, offset: CodeOffset, reloc: Reloc, jt: JumpTable) {
|
|
||||||
self.jts.push((reloc.0, jt, offset));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StandaloneRelocSink {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
ebbs: Vec::new(),
|
|
||||||
funcs: Vec::new(),
|
|
||||||
jts: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Structure containing the compiled code of the functions, ready to be executed.
|
|
||||||
pub struct ExecutableCode {
|
|
||||||
functions_code: Vec<Vec<u8>>,
|
|
||||||
start_index: FunctionIndex,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Executes a module that has been translated with the `standalone::Runtime` runtime implementation.
|
/// Executes a module that has been translated with the `standalone::Runtime` runtime implementation.
|
||||||
pub fn compile_module(
|
pub fn compile_module<'data, 'module>(
|
||||||
trans_result: &TranslationResult,
|
|
||||||
isa: &TargetIsa,
|
isa: &TargetIsa,
|
||||||
runtime: &wasmstandalone_runtime::Runtime,
|
translation: &wasmstandalone_runtime::ModuleTranslation<'data, 'module>,
|
||||||
) -> Result<ExecutableCode, String> {
|
) -> Result<wasmstandalone_runtime::Compilation<'module>, String> {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
runtime.start_func.is_none() || runtime.start_func.unwrap() >= runtime.imported_funcs.len(),
|
translation.module.start_func.is_none() ||
|
||||||
|
translation.module.start_func.unwrap() >= translation.module.imported_funcs.len(),
|
||||||
"imported start functions not supported yet"
|
"imported start functions not supported yet"
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut functions_metatada = Vec::new();
|
let (mut compilation, relocations) = translation.compile(isa)?;
|
||||||
let mut functions_code = Vec::new();
|
|
||||||
for function in &trans_result.functions {
|
// Apply relocations, now that we have virtual addresses for everything.
|
||||||
let mut context = Context::new();
|
relocate(&mut compilation, &relocations);
|
||||||
verify_function(function, isa).unwrap();
|
|
||||||
context.func = function.clone(); // TODO: Avoid this clone.
|
Ok(compilation)
|
||||||
let code_size = context.compile(isa).map_err(|e| {
|
}
|
||||||
pretty_error(&context.func, Some(isa), e)
|
|
||||||
})? as usize;
|
/// Performs the relocations inside the function bytecode, provided the necessary metadata
|
||||||
if code_size == 0 {
|
fn relocate(compilation: &mut Compilation, relocations: &wasmstandalone_runtime::Relocations) {
|
||||||
return Err(String::from("no code generated by Cretonne"));
|
// The relocations are relative to the relocation's address plus four bytes
|
||||||
|
// TODO: Support architectures other than x64, and other reloc kinds.
|
||||||
|
for (i, function_relocs) in relocations.iter().enumerate() {
|
||||||
|
for &(_reloc, func_index, offset) in function_relocs {
|
||||||
|
let target_func_address: isize = compilation.functions[func_index].as_ptr() as isize;
|
||||||
|
let body = &mut compilation.functions[i];
|
||||||
|
unsafe {
|
||||||
|
let reloc_address: isize = body.as_mut_ptr().offset(offset as isize + 4) as isize;
|
||||||
|
let reloc_delta_i32: i32 = (target_func_address - reloc_address) as i32;
|
||||||
|
write_unaligned(reloc_address as *mut i32, reloc_delta_i32);
|
||||||
}
|
}
|
||||||
let mut code_buf: Vec<u8> = Vec::with_capacity(code_size);
|
|
||||||
code_buf.resize(code_size, 0);
|
|
||||||
let mut relocsink = StandaloneRelocSink::new();
|
|
||||||
context.emit_to_memory(code_buf.as_mut_ptr(), &mut relocsink, isa);
|
|
||||||
functions_metatada.push(FunctionMetaData {
|
|
||||||
relocs: relocsink,
|
|
||||||
il_func: context.func,
|
|
||||||
});
|
|
||||||
functions_code.push(code_buf);
|
|
||||||
}
|
|
||||||
relocate(&functions_metatada, &mut functions_code, runtime);
|
|
||||||
// After having emmitted the code to memory, we deal with relocations
|
|
||||||
match runtime.start_func {
|
|
||||||
None => Err(String::from(
|
|
||||||
"No start function defined, aborting execution",
|
|
||||||
)),
|
|
||||||
Some(index) => {
|
|
||||||
Ok(ExecutableCode {
|
|
||||||
functions_code,
|
|
||||||
start_index: index,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Jumps to the code region of memory and execute the start function of the module.
|
/// Jumps to the code region of memory and execute the start function of the module.
|
||||||
pub fn execute(exec: &ExecutableCode) -> Result<(), String> {
|
pub fn execute(
|
||||||
let code_buf = &exec.functions_code[exec.start_index];
|
compilation: &wasmstandalone_runtime::Compilation,
|
||||||
|
_instance: &wasmstandalone_runtime::Instance,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let start_index = compilation.module.start_func.ok_or_else(|| {
|
||||||
|
String::from("No start function defined, aborting execution")
|
||||||
|
})?;
|
||||||
|
let code_buf = &compilation.functions[start_index];
|
||||||
match unsafe {
|
match unsafe {
|
||||||
protect(
|
protect(
|
||||||
code_buf.as_ptr(),
|
code_buf.as_ptr(),
|
||||||
@@ -141,74 +84,3 @@ pub fn execute(exec: &ExecutableCode) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs the relocations inside the function bytecode, provided the necessary metadata
|
|
||||||
fn relocate(
|
|
||||||
functions_metatada: &[FunctionMetaData],
|
|
||||||
functions_code: &mut Vec<Vec<u8>>,
|
|
||||||
runtime: &wasmstandalone_runtime::Runtime,
|
|
||||||
) {
|
|
||||||
// The relocations are relative to the relocation's address plus four bytes
|
|
||||||
for (func_index, function_in_memory) in functions_metatada.iter().enumerate() {
|
|
||||||
let FunctionMetaData {
|
|
||||||
ref relocs,
|
|
||||||
ref il_func,
|
|
||||||
} = *function_in_memory;
|
|
||||||
for &(_reloc, func_ref, offset) in &relocs.funcs {
|
|
||||||
let target_func_index = runtime.func_indices[func_ref] - runtime.imported_funcs.len();
|
|
||||||
let target_func_address: isize = functions_code[target_func_index].as_ptr() as isize;
|
|
||||||
unsafe {
|
|
||||||
let reloc_address: isize = functions_code[func_index].as_mut_ptr().offset(
|
|
||||||
offset as isize +
|
|
||||||
4,
|
|
||||||
) as isize;
|
|
||||||
let reloc_delta_i32: i32 = (target_func_address - reloc_address) as i32;
|
|
||||||
write_unaligned(reloc_address as *mut i32, reloc_delta_i32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for &(_reloc, ebb, offset) in &relocs.ebbs {
|
|
||||||
unsafe {
|
|
||||||
let reloc_address: isize = functions_code[func_index].as_mut_ptr().offset(
|
|
||||||
offset as isize +
|
|
||||||
4,
|
|
||||||
) as isize;
|
|
||||||
let target_ebb_address: isize = functions_code[func_index].as_ptr().offset(
|
|
||||||
il_func.offsets[ebb] as
|
|
||||||
isize,
|
|
||||||
) as isize;
|
|
||||||
let reloc_delta_i32: i32 = (target_ebb_address - reloc_address) as i32;
|
|
||||||
write_unaligned(reloc_address as *mut i32, reloc_delta_i32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert!(
|
|
||||||
relocs.jts.is_empty(),
|
|
||||||
"TODO: deal with jumptable relocations"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pretty-print a verifier error.
|
|
||||||
pub fn pretty_verifier_error(
|
|
||||||
func: &Function,
|
|
||||||
isa: Option<&TargetIsa>,
|
|
||||||
err: &verifier::Error,
|
|
||||||
) -> String {
|
|
||||||
let mut msg = err.to_string();
|
|
||||||
match err.location {
|
|
||||||
AnyEntity::Inst(inst) => {
|
|
||||||
write!(msg, "\n{}: {}\n\n", inst, func.dfg.display_inst(inst, isa)).unwrap()
|
|
||||||
}
|
|
||||||
_ => msg.push('\n'),
|
|
||||||
}
|
|
||||||
write!(msg, "{}", func.display(isa)).unwrap();
|
|
||||||
msg
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pretty-print a Cretonne error.
|
|
||||||
pub fn pretty_error(func: &Function, isa: Option<&TargetIsa>, err: CtonError) -> String {
|
|
||||||
if let CtonError::Verifier(e) = err {
|
|
||||||
pretty_verifier_error(func, isa, &e)
|
|
||||||
} else {
|
|
||||||
err.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,59 +1,18 @@
|
|||||||
use cretonne::Context;
|
|
||||||
use cretonne::settings;
|
use cretonne::settings;
|
||||||
use cretonne::isa::TargetIsa;
|
|
||||||
use cretonne::verify_function;
|
|
||||||
use cretonne::verifier;
|
|
||||||
use cretonne::settings::Configurable;
|
use cretonne::settings::Configurable;
|
||||||
use cretonne::result::CtonError;
|
|
||||||
use cretonne::ir::entities::AnyEntity;
|
|
||||||
use cretonne::ir::{self, Ebb, FuncRef, JumpTable, Function};
|
|
||||||
use cretonne::binemit::{RelocSink, Reloc, CodeOffset};
|
|
||||||
use cton_wasm::TranslationResult;
|
|
||||||
use std::fmt::Write;
|
|
||||||
use faerie::Artifact;
|
use faerie::Artifact;
|
||||||
use wasmstandalone_runtime;
|
use wasmstandalone_runtime;
|
||||||
|
|
||||||
type RelocRef = u16;
|
|
||||||
|
|
||||||
// Implementation of a relocation sink that just saves all the information for later
|
|
||||||
struct FaerieRelocSink {
|
|
||||||
ebbs: Vec<(RelocRef, Ebb, CodeOffset)>,
|
|
||||||
funcs: Vec<(RelocRef, FuncRef, CodeOffset)>,
|
|
||||||
jts: Vec<(RelocRef, JumpTable, CodeOffset)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RelocSink for FaerieRelocSink {
|
|
||||||
fn reloc_ebb(&mut self, offset: CodeOffset, reloc: Reloc, ebb: Ebb) {
|
|
||||||
self.ebbs.push((reloc.0, ebb, offset));
|
|
||||||
}
|
|
||||||
fn reloc_func(&mut self, offset: CodeOffset, reloc: Reloc, func: FuncRef) {
|
|
||||||
self.funcs.push((reloc.0, func, offset));
|
|
||||||
}
|
|
||||||
fn reloc_jt(&mut self, offset: CodeOffset, reloc: Reloc, jt: JumpTable) {
|
|
||||||
self.jts.push((reloc.0, jt, offset));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FaerieRelocSink {
|
|
||||||
fn new() -> FaerieRelocSink {
|
|
||||||
FaerieRelocSink {
|
|
||||||
ebbs: Vec::new(),
|
|
||||||
funcs: Vec::new(),
|
|
||||||
jts: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Emits a module that has been emitted with the `WasmRuntime` runtime
|
/// Emits a module that has been emitted with the `WasmRuntime` runtime
|
||||||
/// implementation to a native object file.
|
/// implementation to a native object file.
|
||||||
pub fn emit_module(
|
pub fn emit_module<'module>(
|
||||||
trans_result: &TranslationResult,
|
|
||||||
obj: &mut Artifact,
|
obj: &mut Artifact,
|
||||||
isa: &TargetIsa,
|
compilation: &wasmstandalone_runtime::Compilation<'module>,
|
||||||
runtime: &wasmstandalone_runtime::Runtime,
|
relocations: &wasmstandalone_runtime::Relocations,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
runtime.start_func.is_none() || runtime.start_func.unwrap() >= runtime.imported_funcs.len(),
|
compilation.module.start_func.is_none() ||
|
||||||
|
compilation.module.start_func.unwrap() >= compilation.module.imported_funcs.len(),
|
||||||
"imported start functions not supported yet"
|
"imported start functions not supported yet"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -62,55 +21,15 @@ pub fn emit_module(
|
|||||||
"Missing enable_verifier setting",
|
"Missing enable_verifier setting",
|
||||||
);
|
);
|
||||||
|
|
||||||
for function in &trans_result.functions {
|
for (i, function_relocs) in relocations.iter().enumerate() {
|
||||||
let mut context = Context::new();
|
assert!(function_relocs.is_empty(), "relocations not supported yet");
|
||||||
verify_function(function, isa).unwrap();
|
let body = &compilation.functions[i];
|
||||||
context.func = function.clone(); // TODO: Avoid this clone.
|
|
||||||
let code_size = context.compile(&*isa).map_err(|e| {
|
|
||||||
pretty_error(&context.func, Some(isa), e)
|
|
||||||
})? as usize;
|
|
||||||
if code_size == 0 {
|
|
||||||
return Err(String::from("no code generated by Cretonne"));
|
|
||||||
}
|
|
||||||
let mut code_buf: Vec<u8> = Vec::with_capacity(code_size);
|
|
||||||
code_buf.resize(code_size, 0);
|
|
||||||
let mut relocsink = FaerieRelocSink::new();
|
|
||||||
context.emit_to_memory(code_buf.as_mut_ptr(), &mut relocsink, &*isa);
|
|
||||||
|
|
||||||
// FIXME: get the real linkage name of the function
|
obj.add_code(
|
||||||
obj.add_code("the_function_name", code_buf);
|
wasmstandalone_runtime::get_func_name(compilation.module.imported_funcs.len() + i),
|
||||||
|
body.clone(),
|
||||||
assert!(relocsink.jts.is_empty(), "jump tables not yet implemented");
|
|
||||||
assert!(relocsink.ebbs.is_empty(), "ebb relocs not yet implemented");
|
|
||||||
assert!(
|
|
||||||
relocsink.funcs.is_empty(),
|
|
||||||
"function relocs not yet implemented"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// FIXME: handle imports
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pretty-print a verifier error.
|
|
||||||
fn pretty_verifier_error(func: &Function, isa: Option<&TargetIsa>, err: verifier::Error) -> String {
|
|
||||||
let mut msg = err.to_string();
|
|
||||||
match err.location {
|
|
||||||
AnyEntity::Inst(inst) => {
|
|
||||||
write!(msg, "\n{}: {}\n\n", inst, func.dfg.display_inst(inst, isa)).unwrap()
|
|
||||||
}
|
|
||||||
_ => msg.push('\n'),
|
|
||||||
}
|
|
||||||
write!(msg, "{}", func.display(isa)).unwrap();
|
|
||||||
msg
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pretty-print a Cretonne error.
|
|
||||||
fn pretty_error(func: &ir::Function, isa: Option<&TargetIsa>, err: CtonError) -> String {
|
|
||||||
if let CtonError::Verifier(e) = err {
|
|
||||||
pretty_verifier_error(func, isa, e)
|
|
||||||
} else {
|
|
||||||
err.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,3 +10,4 @@ license = "Apache-2.0"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
cretonne = { git = "https://github.com/stoklund/cretonne.git" }
|
cretonne = { git = "https://github.com/stoklund/cretonne.git" }
|
||||||
cretonne-wasm = { git = "https://github.com/stoklund/cretonne.git" }
|
cretonne-wasm = { git = "https://github.com/stoklund/cretonne.git" }
|
||||||
|
wasmparser = "0.13.0"
|
||||||
|
|||||||
21
lib/runtime/src/compilation.rs
Normal file
21
lib/runtime/src/compilation.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
//! A `Compilation` contains the compiled function bodies for a WebAssembly
|
||||||
|
//! module.
|
||||||
|
|
||||||
|
use module::Module;
|
||||||
|
|
||||||
|
/// An Instance of a WebAssemby module.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Compilation<'module> {
|
||||||
|
/// The module this `Instance` is instantiated from.
|
||||||
|
pub module: &'module Module,
|
||||||
|
|
||||||
|
/// Compiled machine code for the function bodies.
|
||||||
|
pub functions: Vec<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'module> Compilation<'module> {
|
||||||
|
/// Allocates the runtime data structures with the given flags.
|
||||||
|
pub fn new(module: &'module Module, functions: Vec<Vec<u8>>) -> Self {
|
||||||
|
Self { module, functions }
|
||||||
|
}
|
||||||
|
}
|
||||||
90
lib/runtime/src/instance.rs
Normal file
90
lib/runtime/src/instance.rs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
//! An `Instance` contains all the runtime state used by execution of a wasm
|
||||||
|
//! module.
|
||||||
|
|
||||||
|
use cretonne::ir;
|
||||||
|
use cton_wasm::GlobalIndex;
|
||||||
|
use module::Module;
|
||||||
|
|
||||||
|
const PAGE_SIZE: usize = 65536;
|
||||||
|
|
||||||
|
/// An Instance of a WebAssemby module.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Instance {
|
||||||
|
/// WebAssembly table data.
|
||||||
|
pub tables: Vec<Vec<usize>>,
|
||||||
|
|
||||||
|
/// WebAssembly linear memory data.
|
||||||
|
pub memories: Vec<Vec<u8>>,
|
||||||
|
|
||||||
|
/// WebAssembly global variable data.
|
||||||
|
pub globals: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instance {
|
||||||
|
/// Create a new `Instance`.
|
||||||
|
pub fn new(module: &Module) -> Self {
|
||||||
|
let mut result = Self {
|
||||||
|
tables: Vec::new(),
|
||||||
|
memories: Vec::new(),
|
||||||
|
globals: Vec::new(),
|
||||||
|
};
|
||||||
|
result.instantiate_tables(module);
|
||||||
|
result.instantiate_memories(module);
|
||||||
|
result.instantiate_globals(module);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocate memory in `self` for just the tables of the current module,
|
||||||
|
/// without any initializers applied yet.
|
||||||
|
fn instantiate_tables(&mut self, module: &Module) {
|
||||||
|
debug_assert!(self.tables.is_empty());
|
||||||
|
self.tables.reserve(module.tables.len());
|
||||||
|
for table in &module.tables {
|
||||||
|
let len = table.size;
|
||||||
|
let mut v = Vec::with_capacity(len);
|
||||||
|
v.resize(len, 0);
|
||||||
|
self.tables.push(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocate memory in `instance` for just the memories of the current module,
|
||||||
|
/// without any initializers applied yet.
|
||||||
|
fn instantiate_memories(&mut self, module: &Module) {
|
||||||
|
debug_assert!(self.memories.is_empty());
|
||||||
|
// Allocate the underlying memory and initialize it to all zeros.
|
||||||
|
self.memories.reserve(module.memories.len());
|
||||||
|
for memory in &module.memories {
|
||||||
|
let len = memory.pages_count * PAGE_SIZE;
|
||||||
|
let mut v = Vec::with_capacity(len);
|
||||||
|
v.resize(len, 0);
|
||||||
|
self.memories.push(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocate memory in `instance` for just the globals of the current module,
|
||||||
|
/// without any initializers applied yet.
|
||||||
|
fn instantiate_globals(&mut self, module: &Module) {
|
||||||
|
debug_assert!(self.globals.is_empty());
|
||||||
|
// Allocate the underlying memory and initialize it to all zeros.
|
||||||
|
let globals_data_size = module.globals.len() * 8;
|
||||||
|
self.globals.resize(globals_data_size, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a slice of the contents of allocated linear memory.
|
||||||
|
pub fn inspect_memory(&self, memory_index: usize, address: usize, len: usize) -> &[u8] {
|
||||||
|
&self.memories.get(memory_index).expect(
|
||||||
|
format!(
|
||||||
|
"no memory for index {}",
|
||||||
|
memory_index
|
||||||
|
).as_str(),
|
||||||
|
)
|
||||||
|
[address..address + len]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows the value of a global variable.
|
||||||
|
pub fn inspect_global(&self, global_index: GlobalIndex, ty: ir::Type) -> &[u8] {
|
||||||
|
let offset = global_index * 8;
|
||||||
|
let len = ty.bytes() as usize;
|
||||||
|
&self.globals[offset..offset + len]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,64 +7,35 @@
|
|||||||
|
|
||||||
extern crate cretonne;
|
extern crate cretonne;
|
||||||
extern crate cton_wasm;
|
extern crate cton_wasm;
|
||||||
|
extern crate wasmparser;
|
||||||
|
|
||||||
use cton_wasm::{FunctionIndex, GlobalIndex, TableIndex, MemoryIndex, Global, GlobalInit, Table,
|
pub mod module;
|
||||||
Memory, WasmRuntime, FuncEnvironment, GlobalValue, SignatureIndex};
|
pub mod compilation;
|
||||||
|
pub mod instance;
|
||||||
|
|
||||||
|
pub use module::Module;
|
||||||
|
pub use compilation::Compilation;
|
||||||
|
pub use instance::Instance;
|
||||||
|
|
||||||
|
use cton_wasm::{FunctionIndex, GlobalIndex, TableIndex, MemoryIndex, Global, Table, Memory,
|
||||||
|
GlobalValue, SignatureIndex, FuncTranslator};
|
||||||
use cretonne::ir::{InstBuilder, FuncRef, ExtFuncData, FunctionName, Signature, ArgumentType,
|
use cretonne::ir::{InstBuilder, FuncRef, ExtFuncData, FunctionName, Signature, ArgumentType,
|
||||||
CallConv, ArgumentPurpose, ArgumentLoc, ArgumentExtension, Function};
|
CallConv, ArgumentPurpose, ArgumentLoc, ArgumentExtension, Function};
|
||||||
use cretonne::ir::types::*;
|
use cretonne::ir::types::*;
|
||||||
use cretonne::ir::immediates::Offset32;
|
use cretonne::ir::immediates::Offset32;
|
||||||
use cretonne::cursor::FuncCursor;
|
use cretonne::cursor::FuncCursor;
|
||||||
use cretonne::packed_option::PackedOption;
|
|
||||||
use cretonne::ir;
|
use cretonne::ir;
|
||||||
|
use cretonne::isa;
|
||||||
use cretonne::settings;
|
use cretonne::settings;
|
||||||
use cretonne::entity::EntityMap;
|
use cretonne::binemit;
|
||||||
use std::mem::transmute;
|
use std::str::{FromStr, from_utf8};
|
||||||
use std::ptr::copy_nonoverlapping;
|
use std::error::Error;
|
||||||
use std::ptr::write;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
/// Runtime state of a WebAssembly table element.
|
/// Compute a `ir::FunctionName` for a given wasm function index.
|
||||||
#[derive(Clone, Debug)]
|
pub fn get_func_name(func_index: FunctionIndex) -> cretonne::ir::FunctionName {
|
||||||
pub enum TableElement {
|
ir::FunctionName::new(format!("wasm_0x{:x}", func_index))
|
||||||
/// A element that, if called, produces a trap.
|
|
||||||
Trap(),
|
|
||||||
/// A function.
|
|
||||||
Function(FunctionIndex),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information about a WebAssembly global variable.
|
|
||||||
pub struct GlobalInfo {
|
|
||||||
global: Global,
|
|
||||||
offset: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runtime state of a WebAssembly global variable.
|
|
||||||
pub struct GlobalsData {
|
|
||||||
data: Vec<u8>,
|
|
||||||
info: Vec<GlobalInfo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A description of a WebAssembly table.
|
|
||||||
pub struct TableData {
|
|
||||||
/// The data stored in the table.
|
|
||||||
pub data: Vec<u8>,
|
|
||||||
/// Function indices to be stored in the table.
|
|
||||||
pub elements: Vec<TableElement>,
|
|
||||||
/// The description of the table.
|
|
||||||
pub info: Table,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A description of a WebAssembly linear memory.
|
|
||||||
pub struct MemoryData {
|
|
||||||
/// The data stored in the memory.
|
|
||||||
pub data: Vec<u8>,
|
|
||||||
/// The description of the memory.
|
|
||||||
pub info: Memory,
|
|
||||||
}
|
|
||||||
|
|
||||||
const PAGE_SIZE: usize = 65536;
|
|
||||||
|
|
||||||
/// An entity to export.
|
/// An entity to export.
|
||||||
pub enum Export {
|
pub enum Export {
|
||||||
/// Function export.
|
/// Function export.
|
||||||
@@ -77,86 +48,144 @@ pub enum Export {
|
|||||||
Global(GlobalIndex),
|
Global(GlobalIndex),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Object containing the standalone runtime information. To be passed after creation as argument
|
type RelocRef = u16;
|
||||||
/// to `cton_wasm::translatemodule`.
|
|
||||||
pub struct Runtime {
|
|
||||||
/// Compilation setting flags.
|
|
||||||
flags: settings::Flags,
|
|
||||||
|
|
||||||
/// Unprocessed signatures exactly as provided by `declare_signature()`.
|
// Implementation of a relocation sink that just saves all the information for later
|
||||||
signatures: Vec<ir::Signature>,
|
struct RelocSink<'func> {
|
||||||
|
func: &'func ir::Function,
|
||||||
/// Names of imported functions.
|
pub func_relocs: Vec<(RelocRef, FunctionIndex, binemit::CodeOffset)>,
|
||||||
pub imported_funcs: Vec<(String, String)>,
|
|
||||||
|
|
||||||
/// Types of functions, imported and local.
|
|
||||||
functions: Vec<SignatureIndex>,
|
|
||||||
|
|
||||||
/// WebAssembly tables.
|
|
||||||
pub tables: Vec<TableData>,
|
|
||||||
|
|
||||||
/// WebAssembly linear memories.
|
|
||||||
pub memories: Vec<MemoryData>,
|
|
||||||
|
|
||||||
/// WebAssembly global variables.
|
|
||||||
pub globals: GlobalsData,
|
|
||||||
|
|
||||||
/// Exported entities.
|
|
||||||
pub exports: HashMap<String, Export>,
|
|
||||||
|
|
||||||
instantiated: bool,
|
|
||||||
|
|
||||||
has_current_memory: Option<FuncRef>,
|
|
||||||
has_grow_memory: Option<FuncRef>,
|
|
||||||
|
|
||||||
/// Mapping from cretonne FuncRef to wasm FunctionIndex.
|
|
||||||
pub func_indices: EntityMap<FuncRef, FunctionIndex>,
|
|
||||||
|
|
||||||
the_heap: PackedOption<ir::Heap>,
|
|
||||||
|
|
||||||
/// The module "start" function, if present.
|
|
||||||
pub start_func: Option<FunctionIndex>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Runtime {
|
impl<'func> binemit::RelocSink for RelocSink<'func> {
|
||||||
/// Allocates the runtime data structures with default flags.
|
fn reloc_ebb(&mut self, _offset: binemit::CodeOffset, _reloc: binemit::Reloc, _ebb: ir::Ebb) {
|
||||||
pub fn default() -> Self {
|
// This should use the `offsets` field of `ir::Function`.
|
||||||
Self::with_flags(settings::Flags::new(&settings::builder()))
|
panic!("ebb headers not yet implemented");
|
||||||
}
|
}
|
||||||
|
fn reloc_func(&mut self, offset: binemit::CodeOffset, reloc: binemit::Reloc, func: FuncRef) {
|
||||||
|
let name_bytes: &[u8] = self.func.dfg.ext_funcs[func].name.as_ref();
|
||||||
|
let name = from_utf8(name_bytes).unwrap();
|
||||||
|
// See `get_func_name`; names are encoded as `wasm_0x...`, so grab the
|
||||||
|
// `0x...` part and convert it back to an integer to get the index.
|
||||||
|
let func_index = FunctionIndex::from_str(&name[5..]).unwrap();
|
||||||
|
self.func_relocs.push((reloc.0, func_index, offset));
|
||||||
|
}
|
||||||
|
fn reloc_jt(
|
||||||
|
&mut self,
|
||||||
|
_offset: binemit::CodeOffset,
|
||||||
|
_reloc: binemit::Reloc,
|
||||||
|
_jt: ir::JumpTable,
|
||||||
|
) {
|
||||||
|
panic!("jump tables not yet implemented");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Allocates the runtime data structures with the given flags.
|
impl<'func> RelocSink<'func> {
|
||||||
pub fn with_flags(flags: settings::Flags) -> Self {
|
fn new(func: &'func Function) -> RelocSink {
|
||||||
|
RelocSink {
|
||||||
|
func,
|
||||||
|
func_relocs: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// References to the input wasm data buffer to be decoded and processed later.
|
||||||
|
/// separately from the main module translation.
|
||||||
|
pub struct LazyContents<'data> {
|
||||||
|
/// References to the function bodies.
|
||||||
|
pub function_body_inputs: Vec<&'data [u8]>,
|
||||||
|
|
||||||
|
/// References to the data initializers.
|
||||||
|
pub data_initializers: Vec<(MemoryIndex, Option<GlobalIndex>, usize, &'data [u8])>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'data> LazyContents<'data> {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
function_body_inputs: Vec::new(),
|
||||||
|
data_initializers: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Object containing the standalone runtime information. To be passed after creation as argument
|
||||||
|
/// to `cton_wasm::translatemodule`.
|
||||||
|
pub struct ModuleEnvironment<'data, 'module> {
|
||||||
|
/// Compilation setting flags.
|
||||||
|
pub flags: &'module settings::Flags,
|
||||||
|
|
||||||
|
/// Module information.
|
||||||
|
pub module: &'module mut Module,
|
||||||
|
|
||||||
|
/// References to information to be decoded later.
|
||||||
|
pub lazy: LazyContents<'data>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'data, 'module> ModuleEnvironment<'data, 'module> {
|
||||||
|
/// Allocates the runtime data structures with the given isa.
|
||||||
|
pub fn new(flags: &'module settings::Flags, module: &'module mut Module) -> Self {
|
||||||
Self {
|
Self {
|
||||||
flags,
|
flags,
|
||||||
signatures: Vec::new(),
|
module,
|
||||||
imported_funcs: Vec::new(),
|
lazy: LazyContents::new(),
|
||||||
functions: Vec::new(),
|
|
||||||
tables: Vec::new(),
|
|
||||||
memories: Vec::new(),
|
|
||||||
globals: GlobalsData {
|
|
||||||
data: Vec::new(),
|
|
||||||
info: Vec::new(),
|
|
||||||
},
|
|
||||||
exports: HashMap::new(),
|
|
||||||
instantiated: false,
|
|
||||||
has_current_memory: None,
|
|
||||||
has_grow_memory: None,
|
|
||||||
func_indices: EntityMap::new(),
|
|
||||||
the_heap: PackedOption::default(),
|
|
||||||
start_func: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the offset from the VmCtx pointer where global `index` is allocated.
|
fn func_env(&self) -> FuncEnvironment {
|
||||||
fn global_offset(index: GlobalIndex) -> usize {
|
FuncEnvironment::new(&self.flags, &self.module)
|
||||||
// Add one for the hidden heap base global.
|
|
||||||
(index as usize + 1) * 8
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the size of the VmCtx area needed to hold all currently declared globals.
|
fn native_pointer(&self) -> ir::Type {
|
||||||
fn globals_data_size(&self) -> usize {
|
use cton_wasm::FuncEnvironment;
|
||||||
// Add one for the hidden heap base global.
|
self.func_env().native_pointer()
|
||||||
(self.globals.info.len() + 1) * 8
|
}
|
||||||
|
|
||||||
|
/// Declare that translation of the module is complete. This consumes the
|
||||||
|
/// `ModuleEnvironment` with its mutable reference to the `Module` and
|
||||||
|
/// produces a `ModuleTranslation` with an immutable reference to the
|
||||||
|
/// `Module`.
|
||||||
|
pub fn finish_translation(self) -> ModuleTranslation<'data, 'module> {
|
||||||
|
ModuleTranslation {
|
||||||
|
flags: self.flags,
|
||||||
|
module: self.module,
|
||||||
|
lazy: self.lazy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The FuncEnvironment implementation for use by the `ModuleEnvironment`.
|
||||||
|
pub struct FuncEnvironment<'module_environment> {
|
||||||
|
/// Compilation setting flags.
|
||||||
|
settings_flags: &'module_environment settings::Flags,
|
||||||
|
|
||||||
|
/// The module-level environment which this function-level environment belongs to.
|
||||||
|
pub module: &'module_environment Module,
|
||||||
|
|
||||||
|
/// The Cretonne global holding the base address of the memories vector.
|
||||||
|
pub memories_base: Option<ir::GlobalVar>,
|
||||||
|
|
||||||
|
/// The Cretonne global holding the base address of the globals vector.
|
||||||
|
pub globals_base: Option<ir::GlobalVar>,
|
||||||
|
|
||||||
|
/// The external function declaration for implementing wasm's `current_memory`.
|
||||||
|
pub current_memory_extfunc: Option<FuncRef>,
|
||||||
|
|
||||||
|
/// The external function declaration for implementing wasm's `grow_memory`.
|
||||||
|
pub grow_memory_extfunc: Option<FuncRef>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'module_environment> FuncEnvironment<'module_environment> {
|
||||||
|
fn new(
|
||||||
|
flags: &'module_environment settings::Flags,
|
||||||
|
module: &'module_environment Module,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
settings_flags: flags,
|
||||||
|
module,
|
||||||
|
memories_base: None,
|
||||||
|
globals_base: None,
|
||||||
|
current_memory_extfunc: None,
|
||||||
|
grow_memory_extfunc: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transform the call argument list in preparation for making a call.
|
/// Transform the call argument list in preparation for making a call.
|
||||||
@@ -166,56 +195,75 @@ impl Runtime {
|
|||||||
real_call_args.push(func.special_arg(ArgumentPurpose::VMContext).unwrap());
|
real_call_args.push(func.special_arg(ArgumentPurpose::VMContext).unwrap());
|
||||||
real_call_args
|
real_call_args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ptr_size(&self) -> usize {
|
||||||
|
if self.settings_flags.is_64bit() { 8 } else { 4 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FuncEnvironment for Runtime {
|
impl<'module_environment> cton_wasm::FuncEnvironment for FuncEnvironment<'module_environment> {
|
||||||
fn flags(&self) -> &settings::Flags {
|
fn flags(&self) -> &settings::Flags {
|
||||||
&self.flags
|
&self.settings_flags
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_global(&mut self, func: &mut ir::Function, index: GlobalIndex) -> GlobalValue {
|
fn make_global(&mut self, func: &mut ir::Function, index: GlobalIndex) -> GlobalValue {
|
||||||
let offset = Self::global_offset(index);
|
let ptr_size = self.ptr_size();
|
||||||
|
let globals_base = self.globals_base.unwrap_or_else(|| {
|
||||||
|
let offset = 0 * ptr_size;
|
||||||
let offset32 = offset as i32;
|
let offset32 = offset as i32;
|
||||||
debug_assert_eq!(offset32 as usize, offset);
|
debug_assert_eq!(offset32 as usize, offset);
|
||||||
let gv =
|
let new_base = func.create_global_var(
|
||||||
func.create_global_var(ir::GlobalVarData::VmCtx { offset: Offset32::new(offset32) });
|
ir::GlobalVarData::VmCtx { offset: Offset32::new(offset32) },
|
||||||
|
);
|
||||||
|
self.globals_base = Some(new_base);
|
||||||
|
new_base
|
||||||
|
});
|
||||||
|
let offset = index as usize * 8;
|
||||||
|
let offset32 = offset as i32;
|
||||||
|
debug_assert_eq!(offset32 as usize, offset);
|
||||||
|
let gv = func.create_global_var(ir::GlobalVarData::Deref {
|
||||||
|
base: globals_base,
|
||||||
|
offset: Offset32::new(offset32),
|
||||||
|
});
|
||||||
GlobalValue::Memory {
|
GlobalValue::Memory {
|
||||||
gv,
|
gv,
|
||||||
ty: self.globals.info[index].global.ty,
|
ty: self.module.globals[index].ty,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_heap(&mut self, func: &mut ir::Function, _index: MemoryIndex) -> ir::Heap {
|
fn make_heap(&mut self, func: &mut ir::Function, index: MemoryIndex) -> ir::Heap {
|
||||||
debug_assert!(self.the_heap.is_none(), "multiple heaps not supported yet");
|
let ptr_size = self.ptr_size();
|
||||||
|
let memories_base = self.memories_base.unwrap_or_else(|| {
|
||||||
let heap_base =
|
let new_base = func.create_global_var(ir::GlobalVarData::VmCtx {
|
||||||
func.create_global_var(ir::GlobalVarData::VmCtx { offset: Offset32::new(0) });
|
offset: Offset32::new(ptr_size as i32),
|
||||||
|
});
|
||||||
let heap = func.create_heap(ir::HeapData {
|
self.globals_base = Some(new_base);
|
||||||
|
new_base
|
||||||
|
});
|
||||||
|
let offset = index as usize * ptr_size;
|
||||||
|
let offset32 = offset as i32;
|
||||||
|
debug_assert_eq!(offset32 as usize, offset);
|
||||||
|
let heap_base = func.create_global_var(ir::GlobalVarData::Deref {
|
||||||
|
base: memories_base,
|
||||||
|
offset: Offset32::new(offset32),
|
||||||
|
});
|
||||||
|
func.create_heap(ir::HeapData {
|
||||||
base: ir::HeapBase::GlobalVar(heap_base),
|
base: ir::HeapBase::GlobalVar(heap_base),
|
||||||
min_size: 0.into(),
|
min_size: 0.into(),
|
||||||
guard_size: 0x8000_0000.into(),
|
guard_size: 0x8000_0000.into(),
|
||||||
style: ir::HeapStyle::Static { bound: 0x1_0000_0000.into() },
|
style: ir::HeapStyle::Static { bound: 0x1_0000_0000.into() },
|
||||||
});
|
})
|
||||||
|
|
||||||
self.the_heap = PackedOption::from(heap);
|
|
||||||
|
|
||||||
heap
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_indirect_sig(&mut self, func: &mut ir::Function, index: SignatureIndex) -> ir::SigRef {
|
fn make_indirect_sig(&mut self, func: &mut ir::Function, index: SignatureIndex) -> ir::SigRef {
|
||||||
func.import_signature(self.signatures[index].clone())
|
func.import_signature(self.module.signatures[index].clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_direct_func(&mut self, func: &mut ir::Function, index: FunctionIndex) -> ir::FuncRef {
|
fn make_direct_func(&mut self, func: &mut ir::Function, index: FunctionIndex) -> ir::FuncRef {
|
||||||
let sigidx = self.functions[index];
|
let sigidx = self.module.functions[index];
|
||||||
let signature = func.import_signature(self.signatures[sigidx].clone());
|
let signature = func.import_signature(self.module.signatures[sigidx].clone());
|
||||||
let name = self.get_func_name(index);
|
let name = get_func_name(index);
|
||||||
let func_ref = func.import_function(ir::ExtFuncData { name, signature });
|
func.import_function(ir::ExtFuncData { name, signature })
|
||||||
|
|
||||||
self.func_indices[func_ref] = index;
|
|
||||||
|
|
||||||
func_ref
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn translate_call_indirect(
|
fn translate_call_indirect(
|
||||||
@@ -228,7 +276,7 @@ impl FuncEnvironment for Runtime {
|
|||||||
call_args: &[ir::Value],
|
call_args: &[ir::Value],
|
||||||
) -> ir::Inst {
|
) -> ir::Inst {
|
||||||
debug_assert_eq!(table_index, 0, "non-default tables not supported yet");
|
debug_assert_eq!(table_index, 0, "non-default tables not supported yet");
|
||||||
let real_call_args = Self::get_real_call_args(pos.func, call_args);
|
let real_call_args = FuncEnvironment::get_real_call_args(pos.func, call_args);
|
||||||
pos.ins().call_indirect(sig_ref, callee, &real_call_args)
|
pos.ins().call_indirect(sig_ref, callee, &real_call_args)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,7 +287,7 @@ impl FuncEnvironment for Runtime {
|
|||||||
callee: ir::FuncRef,
|
callee: ir::FuncRef,
|
||||||
call_args: &[ir::Value],
|
call_args: &[ir::Value],
|
||||||
) -> ir::Inst {
|
) -> ir::Inst {
|
||||||
let real_call_args = Self::get_real_call_args(pos.func, call_args);
|
let real_call_args = FuncEnvironment::get_real_call_args(pos.func, call_args);
|
||||||
pos.ins().call(callee, &real_call_args)
|
pos.ins().call(callee, &real_call_args)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,17 +295,11 @@ impl FuncEnvironment for Runtime {
|
|||||||
&mut self,
|
&mut self,
|
||||||
mut pos: FuncCursor,
|
mut pos: FuncCursor,
|
||||||
index: MemoryIndex,
|
index: MemoryIndex,
|
||||||
heap: ir::Heap,
|
_heap: ir::Heap,
|
||||||
val: ir::Value,
|
val: ir::Value,
|
||||||
) -> ir::Value {
|
) -> ir::Value {
|
||||||
debug_assert!(self.instantiated);
|
|
||||||
debug_assert_eq!(index, 0, "non-default memories not supported yet");
|
debug_assert_eq!(index, 0, "non-default memories not supported yet");
|
||||||
debug_assert_eq!(
|
let grow_mem_func = self.grow_memory_extfunc.unwrap_or_else(|| {
|
||||||
heap,
|
|
||||||
self.the_heap.unwrap(),
|
|
||||||
"multiple heaps not supported yet"
|
|
||||||
);
|
|
||||||
let grow_mem_func = self.has_grow_memory.unwrap_or_else(|| {
|
|
||||||
let sig_ref = pos.func.import_signature(Signature {
|
let sig_ref = pos.func.import_signature(Signature {
|
||||||
call_conv: CallConv::Native,
|
call_conv: CallConv::Native,
|
||||||
argument_bytes: None,
|
argument_bytes: None,
|
||||||
@@ -269,7 +311,7 @@ impl FuncEnvironment for Runtime {
|
|||||||
signature: sig_ref,
|
signature: sig_ref,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
self.has_grow_memory = Some(grow_mem_func);
|
self.grow_memory_extfunc = Some(grow_mem_func);
|
||||||
let call_inst = pos.ins().call(grow_mem_func, &[val]);
|
let call_inst = pos.ins().call(grow_mem_func, &[val]);
|
||||||
*pos.func.dfg.inst_results(call_inst).first().unwrap()
|
*pos.func.dfg.inst_results(call_inst).first().unwrap()
|
||||||
}
|
}
|
||||||
@@ -278,16 +320,10 @@ impl FuncEnvironment for Runtime {
|
|||||||
&mut self,
|
&mut self,
|
||||||
mut pos: FuncCursor,
|
mut pos: FuncCursor,
|
||||||
index: MemoryIndex,
|
index: MemoryIndex,
|
||||||
heap: ir::Heap,
|
_heap: ir::Heap,
|
||||||
) -> ir::Value {
|
) -> ir::Value {
|
||||||
debug_assert!(self.instantiated);
|
|
||||||
debug_assert_eq!(index, 0, "non-default memories not supported yet");
|
debug_assert_eq!(index, 0, "non-default memories not supported yet");
|
||||||
debug_assert_eq!(
|
let cur_mem_func = self.current_memory_extfunc.unwrap_or_else(|| {
|
||||||
heap,
|
|
||||||
self.the_heap.unwrap(),
|
|
||||||
"multiple heaps not supported yet"
|
|
||||||
);
|
|
||||||
let cur_mem_func = self.has_current_memory.unwrap_or_else(|| {
|
|
||||||
let sig_ref = pos.func.import_signature(Signature {
|
let sig_ref = pos.func.import_signature(Signature {
|
||||||
call_conv: CallConv::Native,
|
call_conv: CallConv::Native,
|
||||||
argument_bytes: None,
|
argument_bytes: None,
|
||||||
@@ -299,7 +335,7 @@ impl FuncEnvironment for Runtime {
|
|||||||
signature: sig_ref,
|
signature: sig_ref,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
self.has_current_memory = Some(cur_mem_func);
|
self.current_memory_extfunc = Some(cur_mem_func);
|
||||||
let call_inst = pos.ins().call(cur_mem_func, &[]);
|
let call_inst = pos.ins().call(cur_mem_func, &[]);
|
||||||
*pos.func.dfg.inst_results(call_inst).first().unwrap()
|
*pos.func.dfg.inst_results(call_inst).first().unwrap()
|
||||||
}
|
}
|
||||||
@@ -309,9 +345,9 @@ impl FuncEnvironment for Runtime {
|
|||||||
/// `cton_wasm::translatemodule` because it
|
/// `cton_wasm::translatemodule` because it
|
||||||
/// tells how to translate runtime-dependent wasm instructions. These functions should not be
|
/// tells how to translate runtime-dependent wasm instructions. These functions should not be
|
||||||
/// called by the user.
|
/// called by the user.
|
||||||
impl WasmRuntime for Runtime {
|
impl<'data, 'module> cton_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data, 'module> {
|
||||||
fn get_func_name(&self, func_index: FunctionIndex) -> cretonne::ir::FunctionName {
|
fn get_func_name(&self, func_index: FunctionIndex) -> cretonne::ir::FunctionName {
|
||||||
ir::FunctionName::new(format!("wasm_0x{:x}", func_index))
|
get_func_name(func_index)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_signature(&mut self, sig: &ir::Signature) {
|
fn declare_signature(&mut self, sig: &ir::Signature) {
|
||||||
@@ -323,63 +359,49 @@ impl WasmRuntime for Runtime {
|
|||||||
location: ArgumentLoc::Unassigned,
|
location: ArgumentLoc::Unassigned,
|
||||||
});
|
});
|
||||||
// TODO: Deduplicate signatures.
|
// TODO: Deduplicate signatures.
|
||||||
self.signatures.push(sig);
|
self.module.signatures.push(sig);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_signature(&self, sig_index: SignatureIndex) -> &ir::Signature {
|
fn get_signature(&self, sig_index: SignatureIndex) -> &ir::Signature {
|
||||||
&self.signatures[sig_index]
|
&self.module.signatures[sig_index]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_func_import(&mut self, sig_index: SignatureIndex, module: &str, field: &str) {
|
fn declare_func_import(&mut self, sig_index: SignatureIndex, module: &str, field: &str) {
|
||||||
debug_assert_eq!(
|
debug_assert_eq!(
|
||||||
self.functions.len(),
|
self.module.functions.len(),
|
||||||
self.imported_funcs.len(),
|
self.module.imported_funcs.len(),
|
||||||
"Imported functions must be declared first"
|
"Imported functions must be declared first"
|
||||||
);
|
);
|
||||||
self.functions.push(sig_index);
|
self.module.functions.push(sig_index);
|
||||||
|
|
||||||
self.imported_funcs.push((
|
self.module.imported_funcs.push((
|
||||||
String::from(module),
|
String::from(module),
|
||||||
String::from(field),
|
String::from(field),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_num_func_imports(&self) -> usize {
|
fn get_num_func_imports(&self) -> usize {
|
||||||
self.imported_funcs.len()
|
self.module.imported_funcs.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_func_type(&mut self, sig_index: SignatureIndex) {
|
fn declare_func_type(&mut self, sig_index: SignatureIndex) {
|
||||||
self.functions.push(sig_index);
|
self.module.functions.push(sig_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_func_type(&self, func_index: FunctionIndex) -> usize {
|
fn get_func_type(&self, func_index: FunctionIndex) -> usize {
|
||||||
self.functions[func_index]
|
self.module.functions[func_index]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_global(&mut self, global: Global) {
|
fn declare_global(&mut self, global: Global) {
|
||||||
debug_assert!(!self.instantiated);
|
self.module.globals.push(global);
|
||||||
let index = self.globals.info.len() as GlobalIndex;
|
|
||||||
self.globals.info.push(GlobalInfo {
|
|
||||||
global: global,
|
|
||||||
offset: Self::global_offset(index),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_global(&self, global_index: GlobalIndex) -> &cton_wasm::Global {
|
fn get_global(&self, global_index: GlobalIndex) -> &cton_wasm::Global {
|
||||||
&self.globals.info[global_index].global
|
&self.module.globals[global_index]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_table(&mut self, table: Table) {
|
fn declare_table(&mut self, table: Table) {
|
||||||
debug_assert!(!self.instantiated);
|
self.module.tables.push(table);
|
||||||
let mut elements_vec = Vec::with_capacity(table.size);
|
|
||||||
elements_vec.resize(table.size, TableElement::Trap());
|
|
||||||
let mut addresses_vec = Vec::with_capacity(table.size);
|
|
||||||
addresses_vec.resize(table.size, 0);
|
|
||||||
self.tables.push(TableData {
|
|
||||||
info: table,
|
|
||||||
data: addresses_vec,
|
|
||||||
elements: elements_vec,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_table_elements(
|
fn declare_table_elements(
|
||||||
@@ -387,23 +409,19 @@ impl WasmRuntime for Runtime {
|
|||||||
table_index: TableIndex,
|
table_index: TableIndex,
|
||||||
base: Option<GlobalIndex>,
|
base: Option<GlobalIndex>,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
elements: &[FunctionIndex],
|
elements: Vec<FunctionIndex>,
|
||||||
) {
|
) {
|
||||||
debug_assert!(base.is_none(), "global-value offsets not supported yet");
|
debug_assert!(base.is_none(), "global-value offsets not supported yet");
|
||||||
debug_assert!(!self.instantiated);
|
self.module.table_elements.push(module::TableElements {
|
||||||
for (i, elt) in elements.iter().enumerate() {
|
table_index,
|
||||||
self.tables[table_index].elements[offset + i] = TableElement::Function(*elt);
|
base,
|
||||||
}
|
offset,
|
||||||
|
elements,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_memory(&mut self, memory: Memory) {
|
fn declare_memory(&mut self, memory: Memory) {
|
||||||
debug_assert!(!self.instantiated);
|
self.module.memories.push(memory);
|
||||||
let mut memory_vec = Vec::with_capacity(memory.pages_count * PAGE_SIZE);
|
|
||||||
memory_vec.resize(memory.pages_count * PAGE_SIZE, 0);
|
|
||||||
self.memories.push(MemoryData {
|
|
||||||
info: memory,
|
|
||||||
data: memory_vec,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_data_initialization(
|
fn declare_data_initialization(
|
||||||
@@ -411,133 +429,108 @@ impl WasmRuntime for Runtime {
|
|||||||
memory_index: MemoryIndex,
|
memory_index: MemoryIndex,
|
||||||
base: Option<GlobalIndex>,
|
base: Option<GlobalIndex>,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
data: &[u8],
|
data: &'data [u8],
|
||||||
) {
|
) {
|
||||||
debug_assert!(base.is_none(), "global-value offsets not supported yet");
|
debug_assert!(base.is_none(), "global-value offsets not supported yet");
|
||||||
debug_assert!(
|
self.lazy.data_initializers.push((
|
||||||
offset + data.len() <= self.memories[memory_index].info.pages_count * PAGE_SIZE,
|
memory_index,
|
||||||
"initialization data out of bounds"
|
base,
|
||||||
);
|
offset,
|
||||||
self.memories[memory_index].data[offset..offset + data.len()].copy_from_slice(data);
|
data,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_func_export(&mut self, func_index: FunctionIndex, name: &str) {
|
fn declare_func_export(&mut self, func_index: FunctionIndex, name: &str) {
|
||||||
self.exports.insert(
|
self.module.exports.insert(
|
||||||
String::from(name),
|
String::from(name),
|
||||||
Export::Function(func_index),
|
module::Export::Function(func_index),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_table_export(&mut self, table_index: TableIndex, name: &str) {
|
fn declare_table_export(&mut self, table_index: TableIndex, name: &str) {
|
||||||
self.exports.insert(
|
self.module.exports.insert(
|
||||||
String::from(name),
|
String::from(name),
|
||||||
Export::Table(table_index),
|
module::Export::Table(table_index),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_memory_export(&mut self, memory_index: MemoryIndex, name: &str) {
|
fn declare_memory_export(&mut self, memory_index: MemoryIndex, name: &str) {
|
||||||
self.exports.insert(
|
self.module.exports.insert(
|
||||||
String::from(name),
|
String::from(name),
|
||||||
Export::Memory(memory_index),
|
module::Export::Memory(memory_index),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_global_export(&mut self, global_index: GlobalIndex, name: &str) {
|
fn declare_global_export(&mut self, global_index: GlobalIndex, name: &str) {
|
||||||
self.exports.insert(
|
self.module.exports.insert(
|
||||||
String::from(name),
|
String::from(name),
|
||||||
Export::Global(global_index),
|
module::Export::Global(global_index),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_start_func(&mut self, func_index: FunctionIndex) {
|
fn declare_start_func(&mut self, func_index: FunctionIndex) {
|
||||||
debug_assert!(self.start_func.is_none());
|
debug_assert!(self.module.start_func.is_none());
|
||||||
self.start_func = Some(func_index);
|
self.module.start_func = Some(func_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn begin_translation(&mut self) {
|
fn define_function_body(&mut self, body_bytes: &'data [u8]) -> Result<(), String> {
|
||||||
debug_assert!(!self.instantiated);
|
self.lazy.function_body_inputs.push(body_bytes);
|
||||||
self.instantiated = true;
|
Ok(())
|
||||||
// At instantiation, we allocate memory for the globals, the memories and the tables
|
|
||||||
// First the globals
|
|
||||||
let globals_data_size = self.globals_data_size();
|
|
||||||
self.globals.data.resize(globals_data_size, 0);
|
|
||||||
for globalinfo in &self.globals.info {
|
|
||||||
match globalinfo.global.initializer {
|
|
||||||
GlobalInit::I32Const(val) => unsafe {
|
|
||||||
write(
|
|
||||||
self.globals.data.as_mut_ptr().offset(
|
|
||||||
globalinfo.offset as isize,
|
|
||||||
) as *mut i32,
|
|
||||||
val,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
GlobalInit::I64Const(val) => unsafe {
|
|
||||||
write(
|
|
||||||
self.globals.data.as_mut_ptr().offset(
|
|
||||||
globalinfo.offset as isize,
|
|
||||||
) as *mut i64,
|
|
||||||
val,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
GlobalInit::F32Const(val) => unsafe {
|
|
||||||
write(
|
|
||||||
self.globals.data.as_mut_ptr().offset(
|
|
||||||
globalinfo.offset as isize,
|
|
||||||
) as *mut f32,
|
|
||||||
transmute(val),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
GlobalInit::F64Const(val) => unsafe {
|
|
||||||
write(
|
|
||||||
self.globals.data.as_mut_ptr().offset(
|
|
||||||
globalinfo.offset as isize,
|
|
||||||
) as *mut f64,
|
|
||||||
transmute(val),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
GlobalInit::Import() => {
|
|
||||||
// We don't initialize, this is inter-module linking
|
|
||||||
// TODO: support inter-module imports
|
|
||||||
}
|
|
||||||
GlobalInit::GlobalRef(index) => {
|
|
||||||
let ref_offset = self.globals.info[index].offset;
|
|
||||||
let size = globalinfo.global.ty.bytes();
|
|
||||||
unsafe {
|
|
||||||
let dst = self.globals.data.as_mut_ptr().offset(
|
|
||||||
globalinfo.offset as isize,
|
|
||||||
);
|
|
||||||
let src = self.globals.data.as_ptr().offset(ref_offset as isize);
|
|
||||||
copy_nonoverlapping(src, dst, size as usize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn next_function(&mut self) {
|
/// Relocations to apply to function bodies.
|
||||||
self.has_current_memory = None;
|
pub type Relocations = Vec<Vec<(RelocRef, FunctionIndex, binemit::CodeOffset)>>;
|
||||||
self.has_grow_memory = None;
|
|
||||||
self.func_indices.clear();
|
/// The result of translating via `ModuleEnvironment`.
|
||||||
self.the_heap = PackedOption::default();
|
pub struct ModuleTranslation<'data, 'module> {
|
||||||
}
|
/// Compilation setting flags.
|
||||||
|
pub flags: &'module settings::Flags,
|
||||||
|
|
||||||
|
/// Module information.
|
||||||
|
pub module: &'module Module,
|
||||||
|
|
||||||
|
/// Pointers into the raw data buffer.
|
||||||
|
pub lazy: LazyContents<'data>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience functions for the user to be called after execution for debug purposes.
|
/// Convenience functions for the user to be called after execution for debug purposes.
|
||||||
impl Runtime {
|
impl<'data, 'module> ModuleTranslation<'data, 'module> {
|
||||||
/// Returns a slice of the contents of allocated linear memory.
|
fn func_env(&self) -> FuncEnvironment {
|
||||||
pub fn inspect_memory(&self, memory_index: usize, address: usize, len: usize) -> &[u8] {
|
FuncEnvironment::new(&self.flags, &self.module)
|
||||||
&self.memories
|
|
||||||
.get(memory_index)
|
|
||||||
.expect(format!("no memory for index {}", memory_index).as_str())
|
|
||||||
.data
|
|
||||||
[address..address + len]
|
|
||||||
}
|
}
|
||||||
/// Shows the value of a global variable.
|
|
||||||
pub fn inspect_global(&self, global_index: usize) -> &[u8] {
|
/// Compile the module, producing a compilation result with associated
|
||||||
let (offset, len) = (
|
/// relocations.
|
||||||
self.globals.info[global_index].offset,
|
pub fn compile(
|
||||||
self.globals.info[global_index].global.ty.bytes() as usize,
|
&self,
|
||||||
);
|
isa: &isa::TargetIsa,
|
||||||
&self.globals.data[offset..offset + len]
|
) -> Result<(Compilation<'module>, Relocations), String> {
|
||||||
|
let mut functions = Vec::new();
|
||||||
|
let mut relocations = Vec::new();
|
||||||
|
for input in &self.lazy.function_body_inputs {
|
||||||
|
let mut trans = FuncTranslator::new();
|
||||||
|
let mut context = cretonne::Context::new();
|
||||||
|
let reader = wasmparser::BinaryReader::new(input);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut func_environ = self.func_env();
|
||||||
|
|
||||||
|
trans
|
||||||
|
.translate_from_reader(reader, &mut context.func, &mut func_environ)
|
||||||
|
.map_err(|e| String::from(e.description()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let code_size = context.compile(isa).map_err(
|
||||||
|
|e| String::from(e.description()),
|
||||||
|
)? as usize;
|
||||||
|
let mut code_buf: Vec<u8> = Vec::with_capacity(code_size as usize);
|
||||||
|
let mut reloc_sink = RelocSink::new(&context.func);
|
||||||
|
code_buf.resize(code_size, 0);
|
||||||
|
context.emit_to_memory(code_buf.as_mut_ptr(), &mut reloc_sink, isa);
|
||||||
|
functions.push(code_buf);
|
||||||
|
relocations.push(reloc_sink.func_relocs);
|
||||||
|
}
|
||||||
|
Ok((Compilation::new(self.module, functions), relocations))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
91
lib/runtime/src/module.rs
Normal file
91
lib/runtime/src/module.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
//! A `Module` contains all the relevant information translated from a
|
||||||
|
//! WebAssembly module.
|
||||||
|
|
||||||
|
use cton_wasm::{FunctionIndex, GlobalIndex, TableIndex, MemoryIndex, Global, Table, Memory,
|
||||||
|
SignatureIndex};
|
||||||
|
use cretonne::ir;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Possible values for a WebAssembly table element.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum TableElement {
|
||||||
|
/// A element that, if called, produces a trap.
|
||||||
|
Trap(),
|
||||||
|
/// A function.
|
||||||
|
Function(FunctionIndex),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A WebAssembly table initializer.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct TableElements {
|
||||||
|
/// The index of a table to initialize.
|
||||||
|
pub table_index: TableIndex,
|
||||||
|
/// Optionally, a global variable giving a base index.
|
||||||
|
pub base: Option<GlobalIndex>,
|
||||||
|
/// The offset to add to the base.
|
||||||
|
pub offset: usize,
|
||||||
|
/// The values to write into the table elements.
|
||||||
|
pub elements: Vec<FunctionIndex>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An entity to export.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Export {
|
||||||
|
/// Function export.
|
||||||
|
Function(FunctionIndex),
|
||||||
|
/// Table export.
|
||||||
|
Table(TableIndex),
|
||||||
|
/// Memory export.
|
||||||
|
Memory(MemoryIndex),
|
||||||
|
/// Global export.
|
||||||
|
Global(GlobalIndex),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A translated WebAssembly module, excluding the function bodies and
|
||||||
|
/// memory initializers.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Module {
|
||||||
|
/// Unprocessed signatures exactly as provided by `declare_signature()`.
|
||||||
|
pub signatures: Vec<ir::Signature>,
|
||||||
|
|
||||||
|
/// Names of imported functions.
|
||||||
|
pub imported_funcs: Vec<(String, String)>,
|
||||||
|
|
||||||
|
/// Types of functions, imported and local.
|
||||||
|
pub functions: Vec<SignatureIndex>,
|
||||||
|
|
||||||
|
/// WebAssembly tables.
|
||||||
|
pub tables: Vec<Table>,
|
||||||
|
|
||||||
|
/// WebAssembly linear memories.
|
||||||
|
pub memories: Vec<Memory>,
|
||||||
|
|
||||||
|
/// WebAssembly global variables.
|
||||||
|
pub globals: Vec<Global>,
|
||||||
|
|
||||||
|
/// Exported entities.
|
||||||
|
pub exports: HashMap<String, Export>,
|
||||||
|
|
||||||
|
/// The module "start" function, if present.
|
||||||
|
pub start_func: Option<FunctionIndex>,
|
||||||
|
|
||||||
|
/// WebAssembly table initializers.
|
||||||
|
pub table_elements: Vec<TableElements>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Module {
|
||||||
|
/// Allocates the module data structures.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
signatures: Vec::new(),
|
||||||
|
imported_funcs: Vec::new(),
|
||||||
|
functions: Vec::new(),
|
||||||
|
tables: Vec::new(),
|
||||||
|
memories: Vec::new(),
|
||||||
|
globals: Vec::new(),
|
||||||
|
exports: HashMap::new(),
|
||||||
|
start_func: None,
|
||||||
|
table_elements: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
287
src/main.rs
287
src/main.rs
@@ -9,29 +9,17 @@ extern crate cton_wasm;
|
|||||||
extern crate cton_native;
|
extern crate cton_native;
|
||||||
extern crate wasmstandalone_runtime;
|
extern crate wasmstandalone_runtime;
|
||||||
extern crate wasmstandalone_execute;
|
extern crate wasmstandalone_execute;
|
||||||
extern crate wasmparser;
|
|
||||||
extern crate cretonne;
|
extern crate cretonne;
|
||||||
extern crate wasmtext;
|
|
||||||
extern crate docopt;
|
extern crate docopt;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
extern crate term;
|
|
||||||
extern crate tempdir;
|
extern crate tempdir;
|
||||||
|
|
||||||
use cton_wasm::{translate_module, TranslationResult};
|
use cton_wasm::translate_module;
|
||||||
use wasmstandalone_execute::{compile_module, execute};
|
use wasmstandalone_execute::{compile_module, execute};
|
||||||
|
use wasmstandalone_runtime::{Instance, Module, ModuleEnvironment};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use wasmparser::{Parser, ParserState, WasmDecoder, SectionCode};
|
|
||||||
use wasmtext::Writer;
|
|
||||||
use cretonne::loop_analysis::LoopAnalysis;
|
|
||||||
use cretonne::flowgraph::ControlFlowGraph;
|
|
||||||
use cretonne::dominator_tree::DominatorTree;
|
|
||||||
use cretonne::Context;
|
|
||||||
use cretonne::result::CtonError;
|
|
||||||
use cretonne::ir;
|
|
||||||
use cretonne::ir::entities::AnyEntity;
|
|
||||||
use cretonne::isa::TargetIsa;
|
use cretonne::isa::TargetIsa;
|
||||||
use cretonne::verifier;
|
|
||||||
use cretonne::settings;
|
use cretonne::settings;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
@@ -44,39 +32,17 @@ use std::process::{exit, Command};
|
|||||||
use tempdir::TempDir;
|
use tempdir::TempDir;
|
||||||
use cretonne::settings::Configurable;
|
use cretonne::settings::Configurable;
|
||||||
|
|
||||||
macro_rules! vprintln {
|
|
||||||
($x: expr, $($tts:tt)*) => {
|
|
||||||
if $x {
|
|
||||||
println!($($tts)*);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! vprint {
|
|
||||||
($x: expr, $($tts:tt)*) => {
|
|
||||||
if $x {
|
|
||||||
print!($($tts)*);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const USAGE: &str = "
|
const USAGE: &str = "
|
||||||
Wasm to Cretonne IL translation utility.
|
Wasm to Cretonne IL translation utility.
|
||||||
Takes a binary WebAssembly module and returns its functions in Cretonne IL format.
|
Takes a binary WebAssembly module and returns its functions in Cretonne IL format.
|
||||||
The translation is dependent on the runtime chosen.
|
The translation is dependent on the environment chosen.
|
||||||
The default is a dummy runtime that produces placeholder values.
|
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
wasmstandalone [-vcop] <file>...
|
wasmstandalone [-mop] <file>...
|
||||||
wasmstandalone -e [-mvcop] <file>...
|
|
||||||
wasmstandalone --help | --version
|
wasmstandalone --help | --version
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-v, --verbose displays info on the different steps
|
|
||||||
-p, --print displays the module and translated functions
|
|
||||||
-c, --check checks the corectness of the translated functions
|
|
||||||
-o, --optimize runs optimization passes on the translated functions
|
-o, --optimize runs optimization passes on the translated functions
|
||||||
-e, --execute enable the standalone runtime and executes the start function of the module
|
|
||||||
-m, --memory interactive memory inspector after execution
|
-m, --memory interactive memory inspector after execution
|
||||||
-h, --help print this help message
|
-h, --help print this help message
|
||||||
--version print the Cretonne version
|
--version print the Cretonne version
|
||||||
@@ -85,12 +51,8 @@ Options:
|
|||||||
#[derive(Deserialize, Debug, Clone)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
struct Args {
|
struct Args {
|
||||||
arg_file: Vec<String>,
|
arg_file: Vec<String>,
|
||||||
flag_verbose: bool,
|
|
||||||
flag_execute: bool,
|
|
||||||
flag_memory: bool,
|
flag_memory: bool,
|
||||||
flag_check: bool,
|
|
||||||
flag_optimize: bool,
|
flag_optimize: bool,
|
||||||
flag_print: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_to_end(path: PathBuf) -> Result<Vec<u8>, io::Error> {
|
fn read_to_end(path: PathBuf) -> Result<Vec<u8>, io::Error> {
|
||||||
@@ -100,7 +62,6 @@ fn read_to_end(path: PathBuf) -> Result<Vec<u8>, io::Error> {
|
|||||||
Ok(buf)
|
Ok(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args: Args = Docopt::new(USAGE)
|
let args: Args = Docopt::new(USAGE)
|
||||||
.and_then(|d| {
|
.and_then(|d| {
|
||||||
@@ -109,7 +70,6 @@ fn main() {
|
|||||||
.deserialize()
|
.deserialize()
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|e| e.exit());
|
.unwrap_or_else(|e| e.exit());
|
||||||
let mut terminal = term::stdout().unwrap();
|
|
||||||
let (mut flag_builder, isa_builder) = cton_native::builders().unwrap_or_else(|_| {
|
let (mut flag_builder, isa_builder) = cton_native::builders().unwrap_or_else(|_| {
|
||||||
panic!("host machine is not a supported target");
|
panic!("host machine is not a supported target");
|
||||||
});
|
});
|
||||||
@@ -119,32 +79,26 @@ fn main() {
|
|||||||
flag_builder.enable("enable_verifier").unwrap();
|
flag_builder.enable("enable_verifier").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enable optimization if requested.
|
||||||
|
if args.flag_optimize {
|
||||||
|
flag_builder.set("opt_level", "best").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
let isa = isa_builder.finish(settings::Flags::new(&flag_builder));
|
let isa = isa_builder.finish(settings::Flags::new(&flag_builder));
|
||||||
for filename in &args.arg_file {
|
for filename in &args.arg_file {
|
||||||
let path = Path::new(&filename);
|
let path = Path::new(&filename);
|
||||||
let name = path.as_os_str().to_string_lossy();
|
match handle_module(&args, path.to_path_buf(), &*isa) {
|
||||||
match handle_module(&args, path.to_path_buf(), &name, &*isa) {
|
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(message) => {
|
Err(message) => {
|
||||||
terminal.fg(term::color::RED).unwrap();
|
let name = path.as_os_str().to_string_lossy();
|
||||||
println!("error");
|
println!("error while processing {}: {}", name, message);
|
||||||
terminal.reset().unwrap();
|
|
||||||
println!("{}", message);
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_module(args: &Args, path: PathBuf, name: &str, isa: &TargetIsa) -> Result<(), String> {
|
fn handle_module(args: &Args, path: PathBuf, isa: &TargetIsa) -> Result<(), String> {
|
||||||
let mut terminal = term::stdout().unwrap();
|
|
||||||
terminal.fg(term::color::YELLOW).unwrap();
|
|
||||||
vprint!(args.flag_verbose, "Handling: ");
|
|
||||||
terminal.reset().unwrap();
|
|
||||||
vprintln!(args.flag_verbose, "\"{}\"", name);
|
|
||||||
terminal.fg(term::color::MAGENTA).unwrap();
|
|
||||||
vprint!(args.flag_verbose, "Translating...");
|
|
||||||
terminal.reset().unwrap();
|
|
||||||
let mut data = read_to_end(path.clone()).map_err(|err| {
|
let mut data = read_to_end(path.clone()).map_err(|err| {
|
||||||
String::from(err.description())
|
String::from(err.description())
|
||||||
})?;
|
})?;
|
||||||
@@ -166,105 +120,15 @@ fn handle_module(args: &Args, path: PathBuf, name: &str, isa: &TargetIsa) -> Res
|
|||||||
|err| String::from(err.description()),
|
|err| String::from(err.description()),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
let mut runtime = wasmstandalone_runtime::Runtime::with_flags(isa.flags().clone());
|
let mut module = Module::new();
|
||||||
let translation = {
|
let mut environ = ModuleEnvironment::new(isa.flags(), &mut module);
|
||||||
match translate_module(&data, &mut runtime) {
|
translate_module(&data, &mut environ)?;
|
||||||
Ok(x) => x,
|
let translation = environ.finish_translation();
|
||||||
Err(string) => {
|
let instance = match compile_module(isa, &translation) {
|
||||||
return Err(string);
|
Ok(compilation) => {
|
||||||
}
|
let mut instance = Instance::new(compilation.module);
|
||||||
}
|
execute(&compilation, &mut instance)?;
|
||||||
};
|
instance
|
||||||
terminal.fg(term::color::GREEN).unwrap();
|
|
||||||
vprintln!(args.flag_verbose, " ok");
|
|
||||||
terminal.reset().unwrap();
|
|
||||||
if args.flag_check {
|
|
||||||
terminal.fg(term::color::MAGENTA).unwrap();
|
|
||||||
vprint!(args.flag_verbose, "Checking... ");
|
|
||||||
terminal.reset().unwrap();
|
|
||||||
for func in &translation.functions {
|
|
||||||
verifier::verify_function(func, isa).map_err(|err| {
|
|
||||||
pretty_verifier_error(func, Some(isa), &err)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
terminal.fg(term::color::GREEN).unwrap();
|
|
||||||
vprintln!(args.flag_verbose, " ok");
|
|
||||||
terminal.reset().unwrap();
|
|
||||||
}
|
|
||||||
if args.flag_print {
|
|
||||||
let mut writer1 = stdout();
|
|
||||||
let mut writer2 = stdout();
|
|
||||||
match pretty_print_translation(name, &data, &translation, &mut writer1, &mut writer2, isa) {
|
|
||||||
Err(error) => return Err(String::from(error.description())),
|
|
||||||
Ok(()) => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if args.flag_optimize {
|
|
||||||
terminal.fg(term::color::MAGENTA).unwrap();
|
|
||||||
vprint!(args.flag_verbose, "Optimizing... ");
|
|
||||||
terminal.reset().unwrap();
|
|
||||||
for func in &translation.functions {
|
|
||||||
let mut loop_analysis = LoopAnalysis::new();
|
|
||||||
let mut cfg = ControlFlowGraph::new();
|
|
||||||
cfg.compute(func);
|
|
||||||
let mut domtree = DominatorTree::new();
|
|
||||||
domtree.compute(func, &cfg);
|
|
||||||
loop_analysis.compute(func, &cfg, &domtree);
|
|
||||||
let mut context = Context::new();
|
|
||||||
context.func = func.clone(); // TODO: Avoid this clone.
|
|
||||||
context.cfg = cfg;
|
|
||||||
context.domtree = domtree;
|
|
||||||
context.loop_analysis = loop_analysis;
|
|
||||||
match verifier::verify_context(&context.func, &context.cfg, &context.domtree, isa) {
|
|
||||||
Ok(()) => (),
|
|
||||||
Err(ref err) => {
|
|
||||||
return Err(pretty_verifier_error(&context.func, Some(isa), err));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match context.licm(isa) {
|
|
||||||
Ok(())=> (),
|
|
||||||
Err(error) => {
|
|
||||||
match error {
|
|
||||||
CtonError::Verifier(ref err) => {
|
|
||||||
return Err(pretty_verifier_error(&context.func, Some(isa), err));
|
|
||||||
}
|
|
||||||
CtonError::InvalidInput |
|
|
||||||
CtonError::ImplLimitExceeded |
|
|
||||||
CtonError::CodeTooLarge => return Err(String::from(error.description())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match verifier::verify_context(&context.func, &context.cfg, &context.domtree, isa) {
|
|
||||||
Ok(()) => (),
|
|
||||||
Err(ref err) => return Err(pretty_verifier_error(&context.func, Some(isa), err)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
terminal.fg(term::color::GREEN).unwrap();
|
|
||||||
vprintln!(args.flag_verbose, " ok");
|
|
||||||
terminal.reset().unwrap();
|
|
||||||
}
|
|
||||||
if args.flag_execute {
|
|
||||||
terminal.fg(term::color::MAGENTA).unwrap();
|
|
||||||
vprint!(args.flag_verbose, "Compiling... ");
|
|
||||||
terminal.reset().unwrap();
|
|
||||||
match compile_module(&translation, isa, &runtime) {
|
|
||||||
Ok(ref exec) => {
|
|
||||||
terminal.fg(term::color::GREEN).unwrap();
|
|
||||||
vprintln!(args.flag_verbose, "ok");
|
|
||||||
terminal.reset().unwrap();
|
|
||||||
terminal.fg(term::color::MAGENTA).unwrap();
|
|
||||||
vprint!(args.flag_verbose, "Executing... ");
|
|
||||||
terminal.reset().unwrap();
|
|
||||||
match execute(exec) {
|
|
||||||
Ok(()) => {
|
|
||||||
terminal.fg(term::color::GREEN).unwrap();
|
|
||||||
vprintln!(args.flag_verbose, "ok");
|
|
||||||
terminal.reset().unwrap();
|
|
||||||
}
|
|
||||||
Err(s) => {
|
|
||||||
return Err(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(s) => {
|
Err(s) => {
|
||||||
return Err(s);
|
return Err(s);
|
||||||
@@ -272,16 +136,11 @@ fn handle_module(args: &Args, path: PathBuf, name: &str, isa: &TargetIsa) -> Res
|
|||||||
};
|
};
|
||||||
if args.flag_memory {
|
if args.flag_memory {
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
terminal.fg(term::color::YELLOW).unwrap();
|
|
||||||
println!("Inspecting memory");
|
println!("Inspecting memory");
|
||||||
terminal.fg(term::color::MAGENTA).unwrap();
|
|
||||||
println!("Type 'quit' to exit.");
|
println!("Type 'quit' to exit.");
|
||||||
terminal.reset().unwrap();
|
|
||||||
loop {
|
loop {
|
||||||
input.clear();
|
input.clear();
|
||||||
terminal.fg(term::color::YELLOW).unwrap();
|
|
||||||
print!("Memory index, offset, length (e.g. 0,0,4): ");
|
print!("Memory index, offset, length (e.g. 0,0,4): ");
|
||||||
terminal.reset().unwrap();
|
|
||||||
let _ = stdout().flush();
|
let _ = stdout().flush();
|
||||||
match io::stdin().read_line(&mut input) {
|
match io::stdin().read_line(&mut input) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
@@ -293,7 +152,7 @@ fn handle_module(args: &Args, path: PathBuf, name: &str, isa: &TargetIsa) -> Res
|
|||||||
if split.len() != 3 {
|
if split.len() != 3 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let memory = runtime.inspect_memory(
|
let memory = instance.inspect_memory(
|
||||||
str::parse(split[0]).unwrap(),
|
str::parse(split[0]).unwrap(),
|
||||||
str::parse(split[1]).unwrap(),
|
str::parse(split[1]).unwrap(),
|
||||||
str::parse(split[2]).unwrap(),
|
str::parse(split[2]).unwrap(),
|
||||||
@@ -309,105 +168,5 @@ fn handle_module(args: &Args, path: PathBuf, name: &str, isa: &TargetIsa) -> Res
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prints out a Wasm module, and for each function the corresponding translation in Cretonne IL.
|
|
||||||
fn pretty_print_translation(
|
|
||||||
filename: &str,
|
|
||||||
data: &[u8],
|
|
||||||
translation: &TranslationResult,
|
|
||||||
writer_wat: &mut Write,
|
|
||||||
writer_cretonne: &mut Write,
|
|
||||||
isa: &TargetIsa,
|
|
||||||
) -> Result<(), io::Error> {
|
|
||||||
let mut terminal = term::stdout().unwrap();
|
|
||||||
let mut parser = Parser::new(data);
|
|
||||||
let mut parser_writer = Writer::new(writer_wat);
|
|
||||||
match parser.read() {
|
|
||||||
s @ &ParserState::BeginWasm { .. } => parser_writer.write(s)?,
|
|
||||||
_ => panic!("modules should begin properly"),
|
|
||||||
}
|
|
||||||
loop {
|
|
||||||
match parser.read() {
|
|
||||||
s @ &ParserState::BeginSection { code: SectionCode::Code, .. } => {
|
|
||||||
// The code section begins
|
|
||||||
parser_writer.write(s)?;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
&ParserState::EndWasm => return Ok(()),
|
|
||||||
s => parser_writer.write(s)?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut function_index = 0;
|
|
||||||
loop {
|
|
||||||
match parser.read() {
|
|
||||||
s @ &ParserState::BeginFunctionBody { .. } => {
|
|
||||||
terminal.fg(term::color::BLUE).unwrap();
|
|
||||||
write!(
|
|
||||||
writer_cretonne,
|
|
||||||
"====== Function No. {} of module \"{}\" ======\n",
|
|
||||||
function_index,
|
|
||||||
filename
|
|
||||||
)?;
|
|
||||||
terminal.fg(term::color::CYAN).unwrap();
|
|
||||||
write!(writer_cretonne, "Wast ---------->\n")?;
|
|
||||||
terminal.reset().unwrap();
|
|
||||||
parser_writer.write(s)?;
|
|
||||||
}
|
|
||||||
s @ &ParserState::EndSection => {
|
|
||||||
parser_writer.write(s)?;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => panic!("wrong content in code section"),
|
|
||||||
}
|
|
||||||
loop {
|
|
||||||
match parser.read() {
|
|
||||||
s @ &ParserState::EndFunctionBody => {
|
|
||||||
parser_writer.write(s)?;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
s => {
|
|
||||||
parser_writer.write(s)?;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
let mut function_string =
|
|
||||||
format!(" {}", translation.functions[function_index].display(isa));
|
|
||||||
function_string.pop();
|
|
||||||
let function_str = str::replace(function_string.as_str(), "\n", "\n ");
|
|
||||||
terminal.fg(term::color::CYAN).unwrap();
|
|
||||||
write!(writer_cretonne, "Cretonne IL --->\n")?;
|
|
||||||
terminal.reset().unwrap();
|
|
||||||
write!(writer_cretonne, "{}\n", function_str)?;
|
|
||||||
function_index += 1;
|
|
||||||
}
|
|
||||||
loop {
|
|
||||||
match parser.read() {
|
|
||||||
&ParserState::EndWasm => return Ok(()),
|
|
||||||
s => parser_writer.write(s)?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pretty-print a verifier error.
|
|
||||||
pub fn pretty_verifier_error(
|
|
||||||
func: &ir::Function,
|
|
||||||
isa: Option<&TargetIsa>,
|
|
||||||
err: &verifier::Error,
|
|
||||||
) -> String {
|
|
||||||
let msg = err.to_string();
|
|
||||||
let str1 = match err.location {
|
|
||||||
AnyEntity::Inst(inst) => {
|
|
||||||
format!(
|
|
||||||
"{}\n{}: {}\n\n",
|
|
||||||
msg,
|
|
||||||
inst,
|
|
||||||
func.dfg.display_inst(inst, isa)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_ => String::from(format!("{}\n", msg)),
|
|
||||||
};
|
|
||||||
format!("{}{}", str1, func.display(isa))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -91,33 +91,31 @@ fn handle_module(path: PathBuf, output: &str) -> Result<(), String> {
|
|||||||
});
|
});
|
||||||
let isa = isa_builder.finish(settings::Flags::new(&flag_builder));
|
let isa = isa_builder.finish(settings::Flags::new(&flag_builder));
|
||||||
|
|
||||||
let mut runtime = wasmstandalone_runtime::Runtime::with_flags(isa.flags().clone());
|
let mut module = wasmstandalone_runtime::Module::new();
|
||||||
|
let mut environ = wasmstandalone_runtime::ModuleEnvironment::new(isa.flags(), &mut module);
|
||||||
let translation = {
|
translate_module(&data, &mut environ)?;
|
||||||
match translate_module(&data, &mut runtime) {
|
|
||||||
Ok(x) => x,
|
|
||||||
Err(string) => {
|
|
||||||
return Err(string);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut obj = Artifact::new(faerie_target(&*isa)?, Some(String::from(output)));
|
let mut obj = Artifact::new(faerie_target(&*isa)?, Some(String::from(output)));
|
||||||
|
|
||||||
emit_module(&translation, &mut obj, &*isa, &runtime)?;
|
let translation = environ.finish_translation();
|
||||||
|
|
||||||
if !runtime.tables.is_empty() {
|
let (compilation, relocations) = translation.compile(&*isa)?;
|
||||||
if runtime.tables.len() > 1 {
|
|
||||||
|
emit_module(&mut obj, &compilation, &relocations)?;
|
||||||
|
|
||||||
|
if !compilation.module.tables.is_empty() {
|
||||||
|
if compilation.module.tables.len() > 1 {
|
||||||
return Err(String::from("multiple tables not supported yet"));
|
return Err(String::from("multiple tables not supported yet"));
|
||||||
}
|
}
|
||||||
obj.add_data("table", runtime.tables[0].data.clone());
|
return Err(String::from("FIXME: implement tables"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !runtime.memories.is_empty() {
|
if !compilation.module.memories.is_empty() {
|
||||||
if runtime.memories.len() > 1 {
|
if compilation.module.memories.len() > 1 {
|
||||||
return Err(String::from("multiple memories not supported yet"));
|
return Err(String::from("multiple memories not supported yet"));
|
||||||
}
|
}
|
||||||
obj.add_data("memory", runtime.memories[0].data.clone());
|
//obj.add_data("memory", initializer);
|
||||||
|
return Err(String::from("FIXME: implement tables"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Make the format a parameter.
|
// FIXME: Make the format a parameter.
|
||||||
|
|||||||
Reference in New Issue
Block a user