Allow jump tables in wasmtime.
This commit is contained in:
committed by
Dan Gohman
parent
fb9d6061e4
commit
210e959333
@@ -9,7 +9,17 @@ use cranelift_wasm::{DefinedFuncIndex, FuncIndex, WasmError};
|
|||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|
||||||
type Functions = PrimaryMap<DefinedFuncIndex, Vec<u8>>;
|
/// Compiled machine code: body and jump table offsets.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CodeAndJTOffsets {
|
||||||
|
/// The function body.
|
||||||
|
pub body: Vec<u8>,
|
||||||
|
|
||||||
|
/// The jump tables offsets (in the body).
|
||||||
|
pub jt_offsets: ir::JumpTableOffsets,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Functions = PrimaryMap<DefinedFuncIndex, CodeAndJTOffsets>;
|
||||||
|
|
||||||
/// The result of compiling a WebAssembly module's functions.
|
/// The result of compiling a WebAssembly module's functions.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -25,17 +35,23 @@ impl Compilation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Allocates the compilation result with the given function bodies.
|
/// Allocates the compilation result with the given function bodies.
|
||||||
pub fn from_buffer(buffer: Vec<u8>, functions: impl IntoIterator<Item = Range<usize>>) -> Self {
|
pub fn from_buffer(
|
||||||
|
buffer: Vec<u8>,
|
||||||
|
functions: impl IntoIterator<Item = (Range<usize>, ir::JumpTableOffsets)>,
|
||||||
|
) -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
functions
|
functions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|range| buffer[range].to_vec())
|
.map(|(range, jt_offsets)| CodeAndJTOffsets {
|
||||||
|
body: buffer[range].to_vec(),
|
||||||
|
jt_offsets,
|
||||||
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the bytes of a single function
|
/// Gets the bytes of a single function
|
||||||
pub fn get(&self, func: DefinedFuncIndex) -> &[u8] {
|
pub fn get(&self, func: DefinedFuncIndex) -> &CodeAndJTOffsets {
|
||||||
&self.functions[func]
|
&self.functions[func]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,6 +59,14 @@ impl Compilation {
|
|||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.functions.len()
|
self.functions.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets functions jump table offsets.
|
||||||
|
pub fn get_jt_offsets(&self) -> PrimaryMap<DefinedFuncIndex, ir::JumpTableOffsets> {
|
||||||
|
self.functions
|
||||||
|
.iter()
|
||||||
|
.map(|(_, code_and_jt)| code_and_jt.jt_offsets.clone())
|
||||||
|
.collect::<PrimaryMap<DefinedFuncIndex, _>>()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IntoIterator for &'a Compilation {
|
impl<'a> IntoIterator for &'a Compilation {
|
||||||
@@ -61,10 +85,10 @@ pub struct Iter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for Iter<'a> {
|
impl<'a> Iterator for Iter<'a> {
|
||||||
type Item = &'a [u8];
|
type Item = &'a CodeAndJTOffsets;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
self.iterator.next().map(|(_, b)| &b[..])
|
self.iterator.next().map(|(_, b)| b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +120,8 @@ pub enum RelocationTarget {
|
|||||||
Memory32Size,
|
Memory32Size,
|
||||||
/// Function for query current size of an imported 32-bit linear memory.
|
/// Function for query current size of an imported 32-bit linear memory.
|
||||||
ImportedMemory32Size,
|
ImportedMemory32Size,
|
||||||
|
/// Jump table index.
|
||||||
|
JumpTable(FuncIndex, ir::JumpTable),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Relocations to apply to function bodies.
|
/// Relocations to apply to function bodies.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Support for compiling with Cranelift.
|
//! Support for compiling with Cranelift.
|
||||||
|
|
||||||
use crate::compilation::{
|
use crate::compilation::{
|
||||||
AddressTransforms, Compilation, CompileError, FunctionAddressTransform,
|
AddressTransforms, CodeAndJTOffsets, Compilation, CompileError, FunctionAddressTransform,
|
||||||
InstructionAddressTransform, Relocation, RelocationTarget, Relocations,
|
InstructionAddressTransform, Relocation, RelocationTarget, Relocations,
|
||||||
};
|
};
|
||||||
use crate::func_environ::{
|
use crate::func_environ::{
|
||||||
@@ -22,6 +22,9 @@ use std::vec::Vec;
|
|||||||
|
|
||||||
/// Implementation of a relocation sink that just saves all the information for later
|
/// Implementation of a relocation sink that just saves all the information for later
|
||||||
pub struct RelocSink {
|
pub struct RelocSink {
|
||||||
|
/// Current function index.
|
||||||
|
func_index: FuncIndex,
|
||||||
|
|
||||||
/// Relocations recorded for the function.
|
/// Relocations recorded for the function.
|
||||||
pub func_relocs: Vec<Relocation>,
|
pub func_relocs: Vec<Relocation>,
|
||||||
}
|
}
|
||||||
@@ -66,20 +69,21 @@ impl binemit::RelocSink for RelocSink {
|
|||||||
addend,
|
addend,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
fn reloc_jt(
|
fn reloc_jt(&mut self, offset: binemit::CodeOffset, reloc: binemit::Reloc, jt: ir::JumpTable) {
|
||||||
&mut self,
|
self.func_relocs.push(Relocation {
|
||||||
_offset: binemit::CodeOffset,
|
reloc,
|
||||||
_reloc: binemit::Reloc,
|
reloc_target: RelocationTarget::JumpTable(self.func_index, jt),
|
||||||
_jt: ir::JumpTable,
|
offset,
|
||||||
) {
|
addend: 0,
|
||||||
panic!("jump tables not yet implemented");
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RelocSink {
|
impl RelocSink {
|
||||||
/// Return a new `RelocSink` instance.
|
/// Return a new `RelocSink` instance.
|
||||||
pub fn new() -> Self {
|
pub fn new(func_index: FuncIndex) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
func_index,
|
||||||
func_relocs: Vec::new(),
|
func_relocs: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,12 +151,14 @@ impl crate::compilation::Compiler for Cranelift {
|
|||||||
.map_err(CompileError::Wasm)?;
|
.map_err(CompileError::Wasm)?;
|
||||||
|
|
||||||
let mut code_buf: Vec<u8> = Vec::new();
|
let mut code_buf: Vec<u8> = Vec::new();
|
||||||
let mut reloc_sink = RelocSink::new();
|
let mut reloc_sink = RelocSink::new(func_index);
|
||||||
let mut trap_sink = binemit::NullTrapSink {};
|
let mut trap_sink = binemit::NullTrapSink {};
|
||||||
context
|
context
|
||||||
.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink)
|
.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink)
|
||||||
.map_err(CompileError::Codegen)?;
|
.map_err(CompileError::Codegen)?;
|
||||||
|
|
||||||
|
let jt_offsets = context.func.jt_offsets.clone();
|
||||||
|
|
||||||
let address_transform = if generate_debug_info {
|
let address_transform = if generate_debug_info {
|
||||||
let body_len = code_buf.len();
|
let body_len = code_buf.len();
|
||||||
let at = get_address_transform(&context, isa);
|
let at = get_address_transform(&context, isa);
|
||||||
@@ -165,12 +171,20 @@ impl crate::compilation::Compiler for Cranelift {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((code_buf, reloc_sink.func_relocs, address_transform))
|
Ok((
|
||||||
|
code_buf,
|
||||||
|
jt_offsets,
|
||||||
|
reloc_sink.func_relocs,
|
||||||
|
address_transform,
|
||||||
|
))
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, CompileError>>()?
|
.collect::<Result<Vec<_>, CompileError>>()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.for_each(|(function, relocs, address_transform)| {
|
.for_each(|(function, func_jt_offsets, relocs, address_transform)| {
|
||||||
functions.push(function);
|
functions.push(CodeAndJTOffsets {
|
||||||
|
body: function,
|
||||||
|
jt_offsets: func_jt_offsets,
|
||||||
|
});
|
||||||
relocations.push(relocs);
|
relocations.push(relocs);
|
||||||
if let Some(address_transform) = address_transform {
|
if let Some(address_transform) = address_transform {
|
||||||
address_transforms.push(address_transform);
|
address_transforms.push(address_transform);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use crate::module_environ::FunctionBodyData;
|
|||||||
// TODO: Put this in `compilation`
|
// TODO: Put this in `compilation`
|
||||||
use crate::cranelift::RelocSink;
|
use crate::cranelift::RelocSink;
|
||||||
use cranelift_codegen::isa;
|
use cranelift_codegen::isa;
|
||||||
use cranelift_entity::PrimaryMap;
|
use cranelift_entity::{PrimaryMap, SecondaryMap};
|
||||||
use cranelift_wasm::DefinedFuncIndex;
|
use cranelift_wasm::DefinedFuncIndex;
|
||||||
use lightbeam;
|
use lightbeam;
|
||||||
|
|
||||||
@@ -30,7 +30,8 @@ impl crate::compilation::Compiler for Lightbeam {
|
|||||||
lightbeam::CodeGenSession::new(function_body_inputs.len() as u32, &env);
|
lightbeam::CodeGenSession::new(function_body_inputs.len() as u32, &env);
|
||||||
|
|
||||||
for (i, function_body) in &function_body_inputs {
|
for (i, function_body) in &function_body_inputs {
|
||||||
let mut reloc_sink = RelocSink::new();
|
let func_index = module.func_index(i);
|
||||||
|
let mut reloc_sink = RelocSink::new(func_index);
|
||||||
|
|
||||||
lightbeam::translate_function(
|
lightbeam::translate_function(
|
||||||
&mut codegen_session,
|
&mut codegen_session,
|
||||||
@@ -46,8 +47,15 @@ impl crate::compilation::Compiler for Lightbeam {
|
|||||||
.into_translated_code_section()
|
.into_translated_code_section()
|
||||||
.expect("Failed to generate output code. TODO: Stop this from panicking");
|
.expect("Failed to generate output code. TODO: Stop this from panicking");
|
||||||
|
|
||||||
|
// TODO pass jump table offsets to Compilation::from_buffer() when they
|
||||||
|
// are implemented in lightbeam -- using empty set of offsets for now.
|
||||||
|
let code_section_ranges_and_jt = code_section
|
||||||
|
.funcs()
|
||||||
|
.into_iter()
|
||||||
|
.map(|r| (r, SecondaryMap::new()));
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
Compilation::from_buffer(code_section.buffer().to_vec(), code_section.funcs()),
|
Compilation::from_buffer(code_section.buffer().to_vec(), code_section_ranges_and_jt),
|
||||||
relocations,
|
relocations,
|
||||||
AddressTransforms::new(),
|
AddressTransforms::new(),
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ impl Compiler {
|
|||||||
) -> Result<
|
) -> Result<
|
||||||
(
|
(
|
||||||
PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
||||||
|
PrimaryMap<DefinedFuncIndex, ir::JumpTableOffsets>,
|
||||||
Relocations,
|
Relocations,
|
||||||
Option<Vec<u8>>,
|
Option<Vec<u8>>,
|
||||||
),
|
),
|
||||||
@@ -104,7 +105,7 @@ impl Compiler {
|
|||||||
let mut funcs = Vec::new();
|
let mut funcs = Vec::new();
|
||||||
for (i, allocated) in allocated_functions.into_iter() {
|
for (i, allocated) in allocated_functions.into_iter() {
|
||||||
let ptr = (*allocated) as *const u8;
|
let ptr = (*allocated) as *const u8;
|
||||||
let body_len = compilation.get(i).len();
|
let body_len = compilation.get(i).body.len();
|
||||||
funcs.push((ptr, body_len));
|
funcs.push((ptr, body_len));
|
||||||
}
|
}
|
||||||
let bytes = emit_debugsections_image(
|
let bytes = emit_debugsections_image(
|
||||||
@@ -120,7 +121,9 @@ impl Compiler {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((allocated_functions, relocations, dbg))
|
let jt_offsets = compilation.get_jt_offsets();
|
||||||
|
|
||||||
|
Ok((allocated_functions, jt_offsets, relocations, dbg))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a trampoline for invoking a function.
|
/// Create a trampoline for invoking a function.
|
||||||
@@ -259,7 +262,10 @@ fn allocate_functions(
|
|||||||
// Allocate code for all function in one continuous memory block.
|
// Allocate code for all function in one continuous memory block.
|
||||||
// First, collect all function bodies into vector to pass to the
|
// First, collect all function bodies into vector to pass to the
|
||||||
// allocate_copy_of_byte_slices.
|
// allocate_copy_of_byte_slices.
|
||||||
let bodies = compilation.into_iter().collect::<Vec<&[u8]>>();
|
let bodies = compilation
|
||||||
|
.into_iter()
|
||||||
|
.map(|code_and_jt| &code_and_jt.body[..])
|
||||||
|
.collect::<Vec<&[u8]>>();
|
||||||
let fat_ptrs = code_memory.allocate_copy_of_byte_slices(&bodies)?;
|
let fat_ptrs = code_memory.allocate_copy_of_byte_slices(&bodies)?;
|
||||||
// Second, create a PrimaryMap from result vector of pointers.
|
// Second, create a PrimaryMap from result vector of pointers.
|
||||||
let mut result = PrimaryMap::with_capacity(compilation.len());
|
let mut result = PrimaryMap::with_capacity(compilation.len());
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ impl<'data> RawCompiledModule<'data> {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let (allocated_functions, relocations, dbg_image) = compiler.compile(
|
let (allocated_functions, jt_offsets, relocations, dbg_image) = compiler.compile(
|
||||||
&translation.module,
|
&translation.module,
|
||||||
translation.function_body_inputs,
|
translation.function_body_inputs,
|
||||||
debug_data,
|
debug_data,
|
||||||
@@ -86,6 +86,7 @@ impl<'data> RawCompiledModule<'data> {
|
|||||||
let imports = link_module(
|
let imports = link_module(
|
||||||
&translation.module,
|
&translation.module,
|
||||||
&allocated_functions,
|
&allocated_functions,
|
||||||
|
&jt_offsets,
|
||||||
relocations,
|
relocations,
|
||||||
resolver,
|
resolver,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use crate::resolver::Resolver;
|
use crate::resolver::Resolver;
|
||||||
use core::ptr::write_unaligned;
|
use core::ptr::write_unaligned;
|
||||||
use cranelift_codegen::binemit::Reloc;
|
use cranelift_codegen::binemit::Reloc;
|
||||||
|
use cranelift_codegen::ir::JumpTableOffsets;
|
||||||
use cranelift_entity::PrimaryMap;
|
use cranelift_entity::PrimaryMap;
|
||||||
use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType};
|
use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
@@ -20,6 +21,7 @@ use wasmtime_runtime::{
|
|||||||
pub fn link_module(
|
pub fn link_module(
|
||||||
module: &Module,
|
module: &Module,
|
||||||
allocated_functions: &PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
allocated_functions: &PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
||||||
|
jt_offsets: &PrimaryMap<DefinedFuncIndex, JumpTableOffsets>,
|
||||||
relocations: Relocations,
|
relocations: Relocations,
|
||||||
resolver: &mut dyn Resolver,
|
resolver: &mut dyn Resolver,
|
||||||
) -> Result<Imports, LinkError> {
|
) -> Result<Imports, LinkError> {
|
||||||
@@ -194,7 +196,7 @@ pub fn link_module(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply relocations, now that we have virtual addresses for everything.
|
// Apply relocations, now that we have virtual addresses for everything.
|
||||||
relocate(allocated_functions, relocations, module);
|
relocate(allocated_functions, jt_offsets, relocations, module);
|
||||||
|
|
||||||
Ok(Imports::new(
|
Ok(Imports::new(
|
||||||
dependencies,
|
dependencies,
|
||||||
@@ -299,6 +301,7 @@ fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool {
|
|||||||
/// Performs the relocations inside the function bytecode, provided the necessary metadata.
|
/// Performs the relocations inside the function bytecode, provided the necessary metadata.
|
||||||
fn relocate(
|
fn relocate(
|
||||||
allocated_functions: &PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
allocated_functions: &PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
||||||
|
jt_offsets: &PrimaryMap<DefinedFuncIndex, JumpTableOffsets>,
|
||||||
relocations: PrimaryMap<DefinedFuncIndex, Vec<Relocation>>,
|
relocations: PrimaryMap<DefinedFuncIndex, Vec<Relocation>>,
|
||||||
module: &Module,
|
module: &Module,
|
||||||
) {
|
) {
|
||||||
@@ -335,6 +338,19 @@ fn relocate(
|
|||||||
other => panic!("unexpected libcall: {}", other),
|
other => panic!("unexpected libcall: {}", other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RelocationTarget::JumpTable(func_index, jt) => {
|
||||||
|
match module.defined_func_index(func_index) {
|
||||||
|
Some(f) => {
|
||||||
|
let offset = *jt_offsets
|
||||||
|
.get(f)
|
||||||
|
.and_then(|ofs| ofs.get(jt))
|
||||||
|
.expect("func jump table");
|
||||||
|
let fatptr: *const [VMFunctionBody] = allocated_functions[f];
|
||||||
|
fatptr as *const VMFunctionBody as usize + offset as usize
|
||||||
|
}
|
||||||
|
None => panic!("func index of jump table"),
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let fatptr: *const [VMFunctionBody] = allocated_functions[i];
|
let fatptr: *const [VMFunctionBody] = allocated_functions[i];
|
||||||
@@ -363,6 +379,9 @@ fn relocate(
|
|||||||
Reloc::X86CallPCRel4 => {
|
Reloc::X86CallPCRel4 => {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
Reloc::X86PCRelRodata4 => {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
_ => panic!("unsupported reloc kind"),
|
_ => panic!("unsupported reloc kind"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,11 +62,11 @@ pub fn emit_functions(
|
|||||||
.expect("Missing enable_verifier setting");
|
.expect("Missing enable_verifier setting");
|
||||||
|
|
||||||
for (i, _function_relocs) in relocations.iter() {
|
for (i, _function_relocs) in relocations.iter() {
|
||||||
let body = compilation.get(i);
|
let body = &compilation.get(i).body;
|
||||||
let func_index = module.func_index(i);
|
let func_index = module.func_index(i);
|
||||||
let string_name = format!("_wasm_function_{}", func_index.index());
|
let string_name = format!("_wasm_function_{}", func_index.index());
|
||||||
|
|
||||||
obj.define(string_name, body.to_vec())
|
obj.define(string_name, body.clone())
|
||||||
.map_err(|err| format!("{}", err))?;
|
.map_err(|err| format!("{}", err))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user