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:
@@ -7,115 +7,58 @@ extern crate cton_wasm;
|
||||
extern crate region;
|
||||
extern crate wasmstandalone_runtime;
|
||||
|
||||
use cretonne::Context;
|
||||
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 region::Protection;
|
||||
use region::protect;
|
||||
use std::ptr::write_unaligned;
|
||||
use std::fmt::Write;
|
||||
|
||||
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,
|
||||
}
|
||||
use wasmstandalone_runtime::Compilation;
|
||||
|
||||
/// Executes a module that has been translated with the `standalone::Runtime` runtime implementation.
|
||||
pub fn compile_module(
|
||||
trans_result: &TranslationResult,
|
||||
pub fn compile_module<'data, 'module>(
|
||||
isa: &TargetIsa,
|
||||
runtime: &wasmstandalone_runtime::Runtime,
|
||||
) -> Result<ExecutableCode, String> {
|
||||
translation: &wasmstandalone_runtime::ModuleTranslation<'data, 'module>,
|
||||
) -> Result<wasmstandalone_runtime::Compilation<'module>, String> {
|
||||
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"
|
||||
);
|
||||
|
||||
let mut functions_metatada = Vec::new();
|
||||
let mut functions_code = Vec::new();
|
||||
for function in &trans_result.functions {
|
||||
let mut context = Context::new();
|
||||
verify_function(function, isa).unwrap();
|
||||
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 = 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,
|
||||
})
|
||||
let (mut compilation, relocations) = translation.compile(isa)?;
|
||||
|
||||
// Apply relocations, now that we have virtual addresses for everything.
|
||||
relocate(&mut compilation, &relocations);
|
||||
|
||||
Ok(compilation)
|
||||
}
|
||||
|
||||
/// Performs the relocations inside the function bytecode, provided the necessary metadata
|
||||
fn relocate(compilation: &mut Compilation, relocations: &wasmstandalone_runtime::Relocations) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Jumps to the code region of memory and execute the start function of the module.
|
||||
pub fn execute(exec: &ExecutableCode) -> Result<(), String> {
|
||||
let code_buf = &exec.functions_code[exec.start_index];
|
||||
pub fn execute(
|
||||
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 {
|
||||
protect(
|
||||
code_buf.as_ptr(),
|
||||
@@ -141,74 +84,3 @@ pub fn execute(exec: &ExecutableCode) -> Result<(), String> {
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user