Refactor where results of compilation are stored (#2086)

* Refactor where results of compilation are stored

This commit refactors the internals of compilation in Wasmtime to change
where results of individual function compilation are stored. Previously
compilation resulted in many maps being returned, and compilation
results generally held all these maps together. This commit instead
switches this to have all metadata stored in a `CompiledFunction`
instead of having a separate map for each item that can be stored.

The motivation for this is primarily to help out with future
module-linking-related PRs. What exactly "module level" is depends on
how we interpret modules and how many modules are in play, so it's a bit
easier for operations in wasmtime to work at the function level where
possible. This means that we don't have to pass around multiple
different maps and a function index, but instead just one map or just
one entry representing a compiled function.

Additionally this change updates where the parallelism of compilation
happens, pushing it into `wasmtime-jit` instead of `wasmtime-environ`.
This is another goal where `wasmtime-jit` will have more knowledge about
module-level pieces with module linking in play. User-facing-wise this
should be the same in terms of parallel compilation, though.

The ultimate goal of this refactoring is to make it easier for the
results of compilation to actually be a set of wasm modules. This means
we won't be able to have a map-per-metadata where the primary key is the
function index, because there will be many modules within one "object
file".

* Don't clear out fields, just don't store them

Persist a smaller set of fields in `CompilationArtifacts` instead of
trying to clear fields out and dynamically not accessing them.
This commit is contained in:
Alex Crichton
2020-08-03 12:20:51 -05:00
committed by GitHub
parent 026fb8d388
commit 65eaca35dd
31 changed files with 466 additions and 757 deletions

2
Cargo.lock generated
View File

@@ -2474,7 +2474,6 @@ dependencies = [
"log",
"more-asserts",
"pretty_env_logger",
"rayon",
"serde",
"sha2",
"tempfile",
@@ -2531,6 +2530,7 @@ dependencies = [
"log",
"more-asserts",
"object 0.20.0",
"rayon",
"region",
"serde",
"target-lexicon",

View File

@@ -182,7 +182,7 @@ pub struct StackLayoutInfo {
/// Stack frame manager.
///
/// Keep track of all the stack slots used by a function.
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Default)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct StackSlots {
/// All allocated stack slots.
@@ -202,12 +202,7 @@ pub struct StackSlots {
impl StackSlots {
/// Create an empty stack slot manager.
pub fn new() -> Self {
Self {
slots: PrimaryMap::new(),
outgoing: Vec::new(),
emergency: Vec::new(),
layout_info: None,
}
StackSlots::default()
}
/// Clear out everything.

View File

@@ -1,6 +1,7 @@
//! A double-ended iterator over entity references and entities.
use crate::EntityRef;
use alloc::vec;
use core::iter::Enumerate;
use core::marker::PhantomData;
use core::slice;
@@ -84,3 +85,40 @@ impl<'a, K: EntityRef, V> DoubleEndedIterator for IterMut<'a, K, V> {
}
impl<'a, K: EntityRef, V> ExactSizeIterator for IterMut<'a, K, V> {}
/// Iterate over all keys in order.
pub struct IntoIter<K: EntityRef, V> {
enumerate: Enumerate<vec::IntoIter<V>>,
unused: PhantomData<K>,
}
impl<K: EntityRef, V> IntoIter<K, V> {
/// Create an `IntoIter` iterator that visits the `PrimaryMap` keys and values
/// of `iter`.
pub fn new(iter: vec::IntoIter<V>) -> Self {
Self {
enumerate: iter.enumerate(),
unused: PhantomData,
}
}
}
impl<K: EntityRef, V> Iterator for IntoIter<K, V> {
type Item = (K, V);
fn next(&mut self) -> Option<Self::Item> {
self.enumerate.next().map(|(i, v)| (K::new(i), v))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.enumerate.size_hint()
}
}
impl<K: EntityRef, V> DoubleEndedIterator for IntoIter<K, V> {
fn next_back(&mut self) -> Option<Self::Item> {
self.enumerate.next_back().map(|(i, v)| (K::new(i), v))
}
}
impl<K: EntityRef, V> ExactSizeIterator for IntoIter<K, V> {}

View File

@@ -131,6 +131,16 @@ where
}
}
impl<K, V> Default for SecondaryMap<K, V>
where
K: EntityRef,
V: Clone + Default,
{
fn default() -> SecondaryMap<K, V> {
SecondaryMap::new()
}
}
/// Immutable indexing into an `SecondaryMap`.
///
/// All keys are permitted. Untouched entries have the default value.

View File

@@ -1,6 +1,6 @@
//! Densely numbered entity references as mapping keys.
use crate::boxed_slice::BoxedSlice;
use crate::iter::{Iter, IterMut};
use crate::iter::{IntoIter, Iter, IterMut};
use crate::keys::Keys;
use crate::EntityRef;
use alloc::boxed::Box;
@@ -150,6 +150,15 @@ where
}
}
impl<K, V> Default for PrimaryMap<K, V>
where
K: EntityRef,
{
fn default() -> PrimaryMap<K, V> {
PrimaryMap::new()
}
}
/// Immutable indexing into an `PrimaryMap`.
/// The indexed value must be in the map.
impl<K, V> Index<K> for PrimaryMap<K, V>
@@ -173,6 +182,18 @@ where
}
}
impl<K, V> IntoIterator for PrimaryMap<K, V>
where
K: EntityRef,
{
type Item = (K, V);
type IntoIter = IntoIter<K, V>;
fn into_iter(self) -> Self::IntoIter {
IntoIter::new(self.elems.into_iter())
}
}
impl<'a, K, V> IntoIterator for &'a PrimaryMap<K, V>
where
K: EntityRef,

View File

@@ -5,8 +5,7 @@ use std::iter::FromIterator;
use wasmtime_environ::entity::{EntityRef, PrimaryMap};
use wasmtime_environ::ir::SourceLoc;
use wasmtime_environ::wasm::DefinedFuncIndex;
use wasmtime_environ::WasmFileInfo;
use wasmtime_environ::{FunctionAddressMap, ModuleAddressMap};
use wasmtime_environ::{CompiledFunctions, FunctionAddressMap, WasmFileInfo};
pub type GeneratedAddress = usize;
pub type WasmAddress = u64;
@@ -187,11 +186,12 @@ fn build_function_lookup(
}
fn build_function_addr_map(
at: &ModuleAddressMap,
funcs: &CompiledFunctions,
code_section_offset: u64,
) -> PrimaryMap<DefinedFuncIndex, FunctionMap> {
let mut map = PrimaryMap::new();
for (_, ft) in at {
for (_, f) in funcs {
let ft = &f.address_map;
let mut fn_map = Vec::new();
for t in &ft.instructions {
if t.srcloc.is_default() {
@@ -447,11 +447,12 @@ impl<'a> Iterator for TransformRangeIter<'a> {
}
impl AddressTransform {
pub fn new(at: &ModuleAddressMap, wasm_file: &WasmFileInfo) -> Self {
pub fn new(funcs: &CompiledFunctions, wasm_file: &WasmFileInfo) -> Self {
let code_section_offset = wasm_file.code_section_offset;
let mut func = BTreeMap::new();
for (i, ft) in at {
for (i, f) in funcs {
let ft = &f.address_map;
let (fn_start, fn_end, lookup) = build_function_lookup(ft, code_section_offset);
func.insert(
@@ -465,7 +466,7 @@ impl AddressTransform {
);
}
let map = build_function_addr_map(at, code_section_offset);
let map = build_function_addr_map(funcs, code_section_offset);
let func = Vec::from_iter(func.into_iter());
AddressTransform { map, func }
}
@@ -606,8 +607,8 @@ mod tests {
use std::iter::FromIterator;
use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::ir::SourceLoc;
use wasmtime_environ::WasmFileInfo;
use wasmtime_environ::{FunctionAddressMap, InstructionAddressMap, ModuleAddressMap};
use wasmtime_environ::{CompiledFunction, WasmFileInfo};
use wasmtime_environ::{CompiledFunctions, FunctionAddressMap, InstructionAddressMap};
#[test]
fn test_get_wasm_code_offset() {
@@ -640,8 +641,11 @@ mod tests {
}
}
fn create_simple_module(func: FunctionAddressMap) -> ModuleAddressMap {
PrimaryMap::from_iter(vec![func])
fn create_simple_module(address_map: FunctionAddressMap) -> CompiledFunctions {
PrimaryMap::from_iter(vec![CompiledFunction {
address_map,
..Default::default()
}])
}
#[test]

View File

@@ -601,6 +601,7 @@ mod tests {
use super::compile_expression;
use super::{AddressTransform, FunctionFrameInfo, ValueLabel, ValueLabelsRanges};
use gimli::{self, Encoding, EndianSlice, Expression, RunTimeEndian};
use wasmtime_environ::CompiledFunction;
macro_rules! expression {
($($i:literal),*) => {
@@ -689,7 +690,8 @@ mod tests {
use wasmtime_environ::{FunctionAddressMap, InstructionAddressMap};
let mut module_map = PrimaryMap::new();
let code_section_offset: u32 = 100;
module_map.push(FunctionAddressMap {
module_map.push(CompiledFunction {
address_map: FunctionAddressMap {
instructions: vec![
InstructionAddressMap {
srcloc: SourceLoc::new(code_section_offset + 12),
@@ -706,6 +708,8 @@ mod tests {
end_srcloc: SourceLoc::new(code_section_offset + 20),
body_offset: 0,
body_len: 30,
},
..Default::default()
});
let fi = WasmFileInfo {
code_section_offset: code_section_offset.into(),

View File

@@ -10,8 +10,7 @@ use gimli::{
use std::collections::HashSet;
use thiserror::Error;
use wasmtime_environ::isa::TargetIsa;
use wasmtime_environ::DebugInfoData;
use wasmtime_environ::{ModuleAddressMap, ModuleVmctxInfo, ValueLabelsRanges};
use wasmtime_environ::{CompiledFunctions, DebugInfoData, ModuleMemoryOffset};
pub use address_transform::AddressTransform;
@@ -50,11 +49,10 @@ where
pub fn transform_dwarf(
isa: &dyn TargetIsa,
di: &DebugInfoData,
at: &ModuleAddressMap,
vmctx_info: &ModuleVmctxInfo,
ranges: &ValueLabelsRanges,
funcs: &CompiledFunctions,
memory_offset: &ModuleMemoryOffset,
) -> Result<write::Dwarf, Error> {
let addr_tr = AddressTransform::new(at, &di.wasm_file);
let addr_tr = AddressTransform::new(funcs, &di.wasm_file);
let reachable = build_dependencies(&di.dwarf, &addr_tr)?.get_reachable();
let context = DebugInputContext {
@@ -90,9 +88,9 @@ pub fn transform_dwarf(
unit,
&context,
&addr_tr,
&ranges,
funcs,
memory_offset,
out_encoding,
&vmctx_info,
&mut out_units,
&mut out_strings,
&mut translated,
@@ -107,8 +105,8 @@ pub fn transform_dwarf(
generate_simulated_dwarf(
&addr_tr,
di,
&vmctx_info,
&ranges,
memory_offset,
funcs,
&translated,
out_encoding,
&mut out_units,

View File

@@ -11,9 +11,9 @@ use wasmparser::Type as WasmType;
use wasmtime_environ::entity::EntityRef;
use wasmtime_environ::isa::TargetIsa;
use wasmtime_environ::wasm::{get_vmctx_value_label, DefinedFuncIndex};
use wasmtime_environ::WasmFileInfo;
use wasmtime_environ::{DebugInfoData, FunctionMetadata};
use wasmtime_environ::{ModuleVmctxInfo, ValueLabelsRanges};
use wasmtime_environ::{
CompiledFunctions, DebugInfoData, FunctionMetadata, ModuleMemoryOffset, WasmFileInfo,
};
const PRODUCER_NAME: &str = "wasmtime";
@@ -120,9 +120,9 @@ fn add_wasm_types(
unit: &mut write::Unit,
root_id: write::UnitEntryId,
out_strings: &mut write::StringTable,
vmctx_info: &ModuleVmctxInfo,
memory_offset: &ModuleMemoryOffset,
) -> WasmTypesDieRefs {
let (_wp_die_id, vmctx_die_id) = add_internal_types(unit, root_id, out_strings, vmctx_info);
let (_wp_die_id, vmctx_die_id) = add_internal_types(unit, root_id, out_strings, memory_offset);
macro_rules! def_type {
($id:literal, $size:literal, $enc:path) => {{
@@ -280,8 +280,8 @@ fn check_invalid_chars_in_path(path: PathBuf) -> Option<PathBuf> {
pub fn generate_simulated_dwarf(
addr_tr: &AddressTransform,
di: &DebugInfoData,
vmctx_info: &ModuleVmctxInfo,
ranges: &ValueLabelsRanges,
memory_offset: &ModuleMemoryOffset,
funcs: &CompiledFunctions,
translated: &HashSet<DefinedFuncIndex>,
out_encoding: gimli::Encoding,
out_units: &mut write::UnitTable,
@@ -342,7 +342,7 @@ pub fn generate_simulated_dwarf(
(unit, root_id, name_id)
};
let wasm_types = add_wasm_types(unit, root_id, out_strings, vmctx_info);
let wasm_types = add_wasm_types(unit, root_id, out_strings, memory_offset);
for (i, map) in addr_tr.map().iter() {
let index = i.index();
@@ -389,7 +389,7 @@ pub fn generate_simulated_dwarf(
write::AttributeValue::Udata(wasm_offset),
);
if let Some(frame_info) = get_function_frame_info(vmctx_info, i, ranges) {
if let Some(frame_info) = get_function_frame_info(memory_offset, funcs, i) {
let source_range = addr_tr.func_source_range(i);
generate_vars(
unit,

View File

@@ -12,7 +12,7 @@ use gimli::{AttributeValue, DebuggingInformationEntry, Unit};
use std::collections::HashSet;
use wasmtime_environ::isa::TargetIsa;
use wasmtime_environ::wasm::DefinedFuncIndex;
use wasmtime_environ::{ModuleVmctxInfo, ValueLabelsRanges};
use wasmtime_environ::{CompiledFunctions, ModuleMemoryOffset};
struct InheritedAttr<T> {
stack: Vec<(usize, T)>,
@@ -246,9 +246,9 @@ pub(crate) fn clone_unit<'a, R>(
unit: Unit<R, R::Offset>,
context: &DebugInputContext<R>,
addr_tr: &'a AddressTransform,
value_ranges: &'a ValueLabelsRanges,
funcs: &'a CompiledFunctions,
memory_offset: &ModuleMemoryOffset,
out_encoding: gimli::Encoding,
module_info: &ModuleVmctxInfo,
out_units: &mut write::UnitTable,
out_strings: &mut write::StringTable,
translated: &mut HashSet<DefinedFuncIndex>,
@@ -319,7 +319,7 @@ where
)?;
let (wp_die_id, vmctx_die_id) =
add_internal_types(comp_unit, root_id, out_strings, module_info);
add_internal_types(comp_unit, root_id, out_strings, memory_offset);
stack.push(root_id);
(
@@ -371,8 +371,7 @@ where
let range_builder =
RangeInfoBuilder::from_subprogram_die(&unit, entry, context, addr_tr, cu_low_pc)?;
if let RangeInfoBuilder::Function(func_index) = range_builder {
if let Some(frame_info) =
get_function_frame_info(module_info, func_index, value_ranges)
if let Some(frame_info) = get_function_frame_info(memory_offset, funcs, func_index)
{
current_value_range.push(new_stack_len, frame_info);
}

View File

@@ -4,7 +4,7 @@ use anyhow::Error;
use gimli::write;
use wasmtime_environ::isa::TargetIsa;
use wasmtime_environ::wasm::DefinedFuncIndex;
use wasmtime_environ::{ModuleMemoryOffset, ModuleVmctxInfo, ValueLabelsRanges};
use wasmtime_environ::{CompiledFunctions, ModuleMemoryOffset};
/// Adds internal Wasm utility types DIEs such as WebAssemblyPtr and
/// WasmtimeVMContext.
@@ -17,7 +17,7 @@ pub(crate) fn add_internal_types(
comp_unit: &mut write::Unit,
root_id: write::UnitEntryId,
out_strings: &mut write::StringTable,
module_info: &ModuleVmctxInfo,
memory_offset: &ModuleMemoryOffset,
) -> (write::UnitEntryId, write::UnitEntryId) {
const WASM_PTR_LEN: u8 = 4;
@@ -70,7 +70,7 @@ pub(crate) fn add_internal_types(
);
// TODO multiple memories
match module_info.memory_offset {
match *memory_offset {
ModuleMemoryOffset::Defined(memory_offset) => {
// The context has defined memory: extend the WasmtimeVMContext size
// past the "memory" field.
@@ -165,19 +165,19 @@ pub(crate) fn append_vmctx_info(
}
pub(crate) fn get_function_frame_info<'a, 'b, 'c>(
module_info: &'b ModuleVmctxInfo,
memory_offset: &ModuleMemoryOffset,
funcs: &'b CompiledFunctions,
func_index: DefinedFuncIndex,
value_ranges: &'c ValueLabelsRanges,
) -> Option<FunctionFrameInfo<'a>>
where
'b: 'a,
'c: 'a,
{
if let Some(value_ranges) = value_ranges.get(func_index) {
if let Some(func) = funcs.get(func_index) {
let frame_info = FunctionFrameInfo {
value_ranges,
memory_offset: module_info.memory_offset.clone(),
stack_slots: &module_info.stack_slots[func_index],
value_ranges: &func.value_labels_ranges,
memory_offset: memory_offset.clone(),
stack_slots: &func.stack_slots,
};
Some(frame_info)
} else {

View File

@@ -1,11 +1,9 @@
pub use crate::transform::transform_dwarf;
use gimli::write::{Address, Dwarf, EndianVec, FrameTable, Result, Sections, Writer};
use gimli::{RunTimeEndian, SectionId};
use wasmtime_environ::entity::{EntityRef, PrimaryMap};
use wasmtime_environ::entity::EntityRef;
use wasmtime_environ::isa::{unwind::UnwindInfo, TargetIsa};
use wasmtime_environ::wasm::DefinedFuncIndex;
use wasmtime_environ::DebugInfoData;
use wasmtime_environ::{ModuleAddressMap, ModuleVmctxInfo, ValueLabelsRanges};
use wasmtime_environ::{CompiledFunctions, DebugInfoData, ModuleMemoryOffset};
#[derive(Clone)]
pub enum DwarfSectionRelocTarget {
@@ -130,16 +128,13 @@ impl Writer for WriterRelocate {
}
}
fn create_frame_table<'a>(
isa: &dyn TargetIsa,
infos: &PrimaryMap<DefinedFuncIndex, &Option<UnwindInfo>>,
) -> Option<FrameTable> {
fn create_frame_table<'a>(isa: &dyn TargetIsa, funcs: &CompiledFunctions) -> Option<FrameTable> {
let mut table = FrameTable::default();
let cie_id = table.add_cie(isa.create_systemv_cie()?);
for (i, info) in infos {
if let Some(UnwindInfo::SystemV(info)) = info {
for (i, f) in funcs {
if let Some(UnwindInfo::SystemV(info)) = &f.unwind_info {
table.add_fde(
cie_id,
info.to_fde(Address::Symbol {
@@ -156,13 +151,11 @@ fn create_frame_table<'a>(
pub fn emit_dwarf<'a>(
isa: &dyn TargetIsa,
debuginfo_data: &DebugInfoData,
at: &ModuleAddressMap,
vmctx_info: &ModuleVmctxInfo,
ranges: &ValueLabelsRanges,
unwind_info: &PrimaryMap<DefinedFuncIndex, &Option<UnwindInfo>>,
funcs: &CompiledFunctions,
memory_offset: &ModuleMemoryOffset,
) -> anyhow::Result<Vec<DwarfSection>> {
let dwarf = transform_dwarf(isa, debuginfo_data, at, vmctx_info, ranges)?;
let frame_table = create_frame_table(isa, unwind_info);
let dwarf = transform_dwarf(isa, debuginfo_data, funcs, memory_offset)?;
let frame_table = create_frame_table(isa, funcs);
let sections = emit_dwarf_sections(dwarf, frame_table)?;
Ok(sections)
}

View File

@@ -20,7 +20,6 @@ cranelift-wasm = { path = "../../cranelift/wasm", version = "0.66.0", features =
wasmparser = "0.59.0"
lightbeam = { path = "../lightbeam", optional = true, version = "0.19.0" }
indexmap = { version = "1.0.2", features = ["serde-1"] }
rayon = { version = "1.2.1", optional = true }
thiserror = "1.0.4"
directories = "2.0.1"
sha2 = "0.8.0"
@@ -48,8 +47,5 @@ pretty_env_logger = "0.4.0"
filetime = "0.2.7"
lazy_static = "1.3.0"
[features]
parallel-compilation = ["rayon"]
[badges]
maintenance = { status = "actively-developed" }

View File

@@ -2,8 +2,6 @@
// addresses of a WebAssembly module into the native code.
use cranelift_codegen::ir;
use cranelift_entity::PrimaryMap;
use cranelift_wasm::DefinedFuncIndex;
use serde::{Deserialize, Serialize};
/// Single source location to generated address mapping.
@@ -20,7 +18,7 @@ pub struct InstructionAddressMap {
}
/// Function and its instructions addresses mappings.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
pub struct FunctionAddressMap {
/// Instructions maps.
/// The array is sorted by the InstructionAddressMap::code_offset field.
@@ -39,15 +37,6 @@ pub struct FunctionAddressMap {
pub body_len: usize,
}
/// Module functions addresses mappings.
pub type ModuleAddressMap = PrimaryMap<DefinedFuncIndex, FunctionAddressMap>;
/// Value ranges for functions.
pub type ValueLabelsRanges = PrimaryMap<DefinedFuncIndex, cranelift_codegen::ValueLabelsRanges>;
/// Stack slots for functions.
pub type StackSlots = PrimaryMap<DefinedFuncIndex, ir::StackSlots>;
/// Memory definition offset in the VMContext structure.
#[derive(Debug, Clone)]
pub enum ModuleMemoryOffset {
@@ -58,13 +47,3 @@ pub enum ModuleMemoryOffset {
/// Offset to the imported memory.
Imported(u32),
}
/// Module `vmctx` related info.
#[derive(Debug, Clone)]
pub struct ModuleVmctxInfo {
/// The memory definition offset in the VMContext structure.
pub memory_offset: ModuleMemoryOffset,
/// The functions stack slots.
pub stack_slots: StackSlots,
}

View File

@@ -1,19 +1,21 @@
//! A `Compilation` contains the compiled function bodies for a WebAssembly
//! module.
use crate::address_map::{ModuleAddressMap, ValueLabelsRanges};
use crate::ModuleTranslation;
use crate::{FunctionAddressMap, FunctionBodyData, ModuleTranslation};
use cranelift_codegen::{binemit, ir, isa, isa::unwind::UnwindInfo};
use cranelift_entity::PrimaryMap;
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, WasmError};
use serde::{Deserialize, Serialize};
use std::ops::Range;
use thiserror::Error;
#[allow(missing_docs)]
pub type CompiledFunctions = PrimaryMap<DefinedFuncIndex, CompiledFunction>;
/// Compiled function: machine code body, jump table offsets, and unwind information.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
#[allow(missing_docs)]
pub struct CompiledFunction {
/// The function body.
/// The machine code for this function.
pub body: Vec<u8>,
/// The jump tables offsets (in the body).
@@ -21,93 +23,13 @@ pub struct CompiledFunction {
/// The unwind information.
pub unwind_info: Option<UnwindInfo>,
}
type Functions = PrimaryMap<DefinedFuncIndex, CompiledFunction>;
/// The result of compiling a WebAssembly module's functions.
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)]
pub struct Compilation {
/// Compiled machine code for the function bodies.
functions: Functions,
}
impl Compilation {
/// Creates a compilation artifact from a contiguous function buffer and a set of ranges
pub fn new(functions: Functions) -> Self {
Self { functions }
}
/// Allocates the compilation result with the given function bodies.
pub fn from_buffer(
buffer: Vec<u8>,
functions: impl IntoIterator<Item = (Range<usize>, ir::JumpTableOffsets)>,
) -> Self {
Self::new(
functions
.into_iter()
.map(|(body_range, jt_offsets)| CompiledFunction {
body: buffer[body_range].to_vec(),
jt_offsets,
unwind_info: None, // not implemented for lightbeam currently
})
.collect(),
)
}
/// Gets the bytes of a single function
pub fn get(&self, func: DefinedFuncIndex) -> &CompiledFunction {
&self.functions[func]
}
/// Gets the number of functions defined.
pub fn len(&self) -> usize {
self.functions.len()
}
/// Returns whether there are no functions defined.
pub fn is_empty(&self) -> bool {
self.functions.is_empty()
}
/// Returns unwind info for all defined functions.
pub fn unwind_info(&self) -> PrimaryMap<DefinedFuncIndex, &Option<UnwindInfo>> {
self.functions
.iter()
.map(|(_, func)| &func.unwind_info)
.collect::<PrimaryMap<DefinedFuncIndex, _>>()
}
/// Gets functions jump table offsets.
pub fn get_jt_offsets(&self) -> PrimaryMap<DefinedFuncIndex, ir::JumpTableOffsets> {
self.functions
.iter()
.map(|(_, func)| func.jt_offsets.clone())
.collect::<PrimaryMap<DefinedFuncIndex, _>>()
}
}
impl<'a> IntoIterator for &'a Compilation {
type IntoIter = Iter<'a>;
type Item = <Self::IntoIter as Iterator>::Item;
fn into_iter(self) -> Self::IntoIter {
Iter {
iterator: self.functions.iter(),
}
}
}
pub struct Iter<'a> {
iterator: <&'a Functions as IntoIterator>::IntoIter,
}
impl<'a> Iterator for Iter<'a> {
type Item = &'a CompiledFunction;
fn next(&mut self) -> Option<Self::Item> {
self.iterator.next().map(|(_, b)| b)
}
pub relocations: Vec<Relocation>,
pub address_map: FunctionAddressMap,
pub value_labels_ranges: cranelift_codegen::ValueLabelsRanges,
pub stack_slots: ir::StackSlots,
pub traps: Vec<TrapInformation>,
pub stack_maps: Vec<StackMapInformation>,
}
/// A record of a relocation to perform.
@@ -134,9 +56,6 @@ pub enum RelocationTarget {
JumpTable(FuncIndex, ir::JumpTable),
}
/// Relocations to apply to function bodies.
pub type Relocations = PrimaryMap<DefinedFuncIndex, Vec<Relocation>>;
/// Information about trap.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct TrapInformation {
@@ -148,9 +67,6 @@ pub struct TrapInformation {
pub trap_code: ir::TrapCode,
}
/// Information about traps associated with the functions where the traps are placed.
pub type Traps = PrimaryMap<DefinedFuncIndex, Vec<TrapInformation>>;
/// The offset within a function of a GC safepoint, and its associated stack
/// map.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
@@ -163,10 +79,6 @@ pub struct StackMapInformation {
pub stack_map: binemit::Stackmap,
}
/// Information about GC safepoints and their associated stack maps within each
/// function.
pub type StackMaps = PrimaryMap<DefinedFuncIndex, Vec<StackMapInformation>>;
/// An error while compiling WebAssembly to machine code.
#[derive(Error, Debug)]
pub enum CompileError {
@@ -183,22 +95,15 @@ pub enum CompileError {
DebugInfoNotSupported,
}
/// A type alias for the result of `Compiler::compile_module`
pub type CompileResult = (
Compilation,
Relocations,
ModuleAddressMap,
ValueLabelsRanges,
PrimaryMap<DefinedFuncIndex, ir::StackSlots>,
Traps,
StackMaps,
);
/// An implementation of a compiler from parsed WebAssembly module to native code.
pub trait Compiler {
/// Compile a parsed module with the given `TargetIsa`.
fn compile_module(
translation: &ModuleTranslation,
/// An implementation of a compiler from parsed WebAssembly module to native
/// code.
pub trait Compiler: Send + Sync {
/// Compile a function with the given `TargetIsa`.
fn compile_function(
&self,
translation: &ModuleTranslation<'_>,
index: DefinedFuncIndex,
data: &FunctionBodyData<'_>,
isa: &dyn isa::TargetIsa,
) -> Result<CompileResult, CompileError>;
) -> Result<CompiledFunction, CompileError>;
}

View File

@@ -85,23 +85,21 @@
// also need to actually catch stack overflow, so for now 32k is chosen and it's
// assume no valid stack pointer will ever be `usize::max_value() - 32k`.
use crate::address_map::{FunctionAddressMap, InstructionAddressMap};
use crate::compilation::{
Compilation, CompileError, CompiledFunction, Relocation, RelocationTarget, StackMapInformation,
use crate::func_environ::{get_func_name, FuncEnvironment};
use crate::Compiler;
use crate::{
CompileError, CompiledFunction, Relocation, RelocationTarget, StackMapInformation,
TrapInformation,
};
use crate::compilation::{CompileResult, Compiler};
use crate::func_environ::{get_func_name, FuncEnvironment};
use crate::{FunctionBodyData, ModuleLocal, ModuleTranslation, Tunables};
use crate::{FunctionAddressMap, InstructionAddressMap};
use crate::{FunctionBodyData, ModuleTranslation};
use cranelift_codegen::ir::{self, ExternalName};
use cranelift_codegen::machinst::buffer::MachSrcLoc;
use cranelift_codegen::print_errors::pretty_error;
use cranelift_codegen::{binemit, isa, Context};
use cranelift_entity::PrimaryMap;
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, FuncTranslator, ModuleTranslationState};
#[cfg(feature = "parallel-compilation")]
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, FuncTranslator};
use std::convert::TryFrom;
use std::sync::Mutex;
/// Implementation of a relocation sink that just saves all the information for later
pub struct RelocSink {
@@ -280,45 +278,33 @@ fn get_function_address_map<'data>(
/// A compiler that compiles a WebAssembly module with Cranelift, translating the Wasm to Cranelift IR,
/// optimizing it and then translating to assembly.
pub struct Cranelift;
#[derive(Default)]
pub struct Cranelift {
translators: Mutex<Vec<FuncTranslator>>,
}
impl Cranelift {
fn take_translator(&self) -> FuncTranslator {
let candidate = self.translators.lock().unwrap().pop();
candidate.unwrap_or_else(FuncTranslator::new)
}
fn save_translator(&self, translator: FuncTranslator) {
self.translators.lock().unwrap().push(translator);
}
}
impl Compiler for Cranelift {
/// Compile the module using Cranelift, producing a compilation result with
/// associated relocations.
fn compile_module(
translation: &ModuleTranslation,
fn compile_function(
&self,
translation: &ModuleTranslation<'_>,
func_index: DefinedFuncIndex,
input: &FunctionBodyData<'_>,
isa: &dyn isa::TargetIsa,
) -> Result<CompileResult, CompileError> {
compile(
isa,
&translation.module.local,
translation.module_translation.as_ref().unwrap(),
&translation.function_body_inputs,
&translation.tunables,
)
}
}
fn compile(
isa: &dyn isa::TargetIsa,
local: &ModuleLocal,
module_translation: &ModuleTranslationState,
function_body_inputs: &PrimaryMap<DefinedFuncIndex, FunctionBodyData<'_>>,
tunables: &Tunables,
) -> Result<CompileResult, CompileError> {
let mut functions = PrimaryMap::with_capacity(function_body_inputs.len());
let mut relocations = PrimaryMap::with_capacity(function_body_inputs.len());
let mut address_transforms = PrimaryMap::with_capacity(function_body_inputs.len());
let mut value_ranges = PrimaryMap::with_capacity(function_body_inputs.len());
let mut stack_slots = PrimaryMap::with_capacity(function_body_inputs.len());
let mut traps = PrimaryMap::with_capacity(function_body_inputs.len());
let mut stack_maps = PrimaryMap::with_capacity(function_body_inputs.len());
type FunctionBodyInput<'a> = (DefinedFuncIndex, &'a FunctionBodyData<'a>);
let compile_function = |func_translator: &mut FuncTranslator,
(i, input): &FunctionBodyInput| {
let func_index = local.func_index(*i);
) -> Result<CompiledFunction, CompileError> {
let local = &translation.module.local;
let tunables = &translation.tunables;
let func_index = local.func_index(func_index);
let mut context = Context::new();
context.func.name = get_func_name(func_index);
context.func.signature = local.native_func_signature(func_index).clone();
@@ -361,13 +347,16 @@ fn compile(
readonly: false,
});
context.func.stack_limit = Some(stack_limit);
func_translator.translate(
module_translation,
let mut func_translator = self.take_translator();
let result = func_translator.translate(
translation.module_translation.as_ref().unwrap(),
input.data,
input.module_offset,
&mut context.func,
&mut func_env,
)?;
);
self.save_translator(func_translator);
result?;
let mut code_buf: Vec<u8> = Vec::new();
let mut reloc_sink = RelocSink::new(func_index);
@@ -400,73 +389,16 @@ fn compile(
None
};
Ok((
code_buf,
context.func.jt_offsets,
reloc_sink.func_relocs,
address_transform,
ranges,
context.func.stack_slots,
trap_sink.traps,
Ok(CompiledFunction {
body: code_buf,
jt_offsets: context.func.jt_offsets,
relocations: reloc_sink.func_relocs,
address_map: address_transform,
value_labels_ranges: ranges.unwrap_or(Default::default()),
stack_slots: context.func.stack_slots,
traps: trap_sink.traps,
unwind_info,
stack_map_sink.finish(),
))
};
let inputs: Vec<FunctionBodyInput> = function_body_inputs.into_iter().collect();
let results: Result<Vec<_>, CompileError> = {
cfg_if::cfg_if! {
if #[cfg(feature = "parallel-compilation")] {
inputs
.par_iter()
.map_init(FuncTranslator::new, compile_function)
.collect()
} else {
let mut func_translator = FuncTranslator::new();
inputs
.iter()
.map(|input| compile_function(&mut func_translator, input))
.collect()
stack_maps: stack_map_sink.finish(),
})
}
}
};
results?.into_iter().for_each(
|(
function,
func_jt_offsets,
relocs,
address_transform,
ranges,
sss,
function_traps,
unwind_info,
stack_map,
)| {
functions.push(CompiledFunction {
body: function,
jt_offsets: func_jt_offsets,
unwind_info,
});
relocations.push(relocs);
address_transforms.push(address_transform);
value_ranges.push(ranges.unwrap_or_default());
stack_slots.push(sss);
traps.push(function_traps);
stack_maps.push(stack_map);
},
);
// TODO: Reorganize where we create the Vec for the resolved imports.
Ok((
Compilation::new(functions),
relocations,
address_transforms,
value_ranges,
stack_slots,
traps,
stack_maps,
))
}

View File

@@ -39,16 +39,10 @@ pub mod cranelift;
#[cfg(feature = "lightbeam")]
pub mod lightbeam;
pub use crate::address_map::{
FunctionAddressMap, InstructionAddressMap, ModuleAddressMap, ModuleMemoryOffset,
ModuleVmctxInfo, ValueLabelsRanges,
};
pub use crate::address_map::*;
pub use crate::cache::create_new_config as cache_create_new_config;
pub use crate::cache::{CacheConfig, ModuleCacheEntry};
pub use crate::compilation::{
Compilation, CompileError, CompiledFunction, Compiler, Relocation, RelocationTarget,
Relocations, StackMapInformation, StackMaps, TrapInformation, Traps,
};
pub use crate::compilation::*;
pub use crate::cranelift::Cranelift;
pub use crate::data_structures::*;
pub use crate::func_environ::BuiltinFunctionIndex;

View File

@@ -1,48 +1,40 @@
//! Support for compiling with Lightbeam.
use crate::compilation::{Compilation, CompileError, CompileResult, Compiler};
use crate::func_environ::FuncEnvironment;
use crate::CacheConfig;
use crate::ModuleTranslation;
// TODO: Put this in `compilation`
use crate::address_map::{ModuleAddressMap, ValueLabelsRanges};
use crate::compilation::{CompileError, CompiledFunction, Compiler};
use crate::cranelift::{RelocSink, TrapSink};
use crate::func_environ::FuncEnvironment;
use crate::{FunctionBodyData, ModuleTranslation};
use cranelift_codegen::isa;
use cranelift_entity::{PrimaryMap, SecondaryMap};
use cranelift_wasm::DefinedFuncIndex;
use lightbeam::{CodeGenSession, NullOffsetSink, Sinks};
/// A compiler that compiles a WebAssembly module with Lightbeam, directly translating the Wasm file.
pub struct Lightbeam;
impl Compiler for Lightbeam {
/// Compile the module using Lightbeam, producing a compilation result with
/// associated relocations.
fn compile_module(
fn compile_function(
&self,
translation: &ModuleTranslation,
i: DefinedFuncIndex,
function_body: &FunctionBodyData<'_>,
isa: &dyn isa::TargetIsa,
) -> Result<CompileResult, CompileError> {
) -> Result<CompiledFunction, CompileError> {
if translation.tunables.debug_info {
return Err(CompileError::DebugInfoNotSupported);
}
let func_index = translation.module.local.func_index(i);
let env = FuncEnvironment::new(
isa.frontend_config(),
&translation.module.local,
&translation.tunables,
);
let mut relocations = PrimaryMap::with_capacity(translation.function_body_inputs.len());
let mut traps = PrimaryMap::with_capacity(translation.function_body_inputs.len());
let stack_maps = PrimaryMap::with_capacity(translation.function_body_inputs.len());
let mut codegen_session: CodeGenSession<_> = CodeGenSession::new(
translation.function_body_inputs.len() as u32,
&env,
lightbeam::microwasm::I32,
);
for (i, function_body) in &translation.function_body_inputs {
let func_index = translation.module.local.func_index(i);
let mut reloc_sink = RelocSink::new(func_index);
let mut trap_sink = TrapSink::new();
lightbeam::translate_function(
@@ -57,30 +49,23 @@ impl Compiler for Lightbeam {
)
.map_err(|e| CompileError::Codegen(format!("Failed to translate function: {}", e)))?;
relocations.push(reloc_sink.func_relocs);
traps.push(trap_sink.traps);
}
let code_section = codegen_session
.into_translated_code_section()
.map_err(|e| CompileError::Codegen(format!("Failed to generate output code: {}", e)))?;
// TODO pass jump table offsets to Compilation::from_buffer() when they
// are implemented in lightbeam -- using empty set of offsets for now.
// TODO: pass an empty range for the unwind information until lightbeam emits it
let code_section_ranges_and_jt = code_section
.funcs()
.into_iter()
.map(|r| (r, SecondaryMap::new()));
Ok(CompiledFunction {
// TODO: try to remove copy here (?)
body: code_section.buffer().to_vec(),
traps: trap_sink.traps,
relocations: reloc_sink.func_relocs,
Ok((
Compilation::from_buffer(code_section.buffer().to_vec(), code_section_ranges_and_jt),
relocations,
ModuleAddressMap::new(),
ValueLabelsRanges::new(),
PrimaryMap::new(),
traps,
stack_maps,
))
// not implemented for lightbeam currently
unwind_info: None,
stack_maps: Default::default(),
stack_slots: Default::default(),
value_labels_ranges: Default::default(),
address_map: Default::default(),
jt_offsets: Default::default(),
})
}
}

View File

@@ -22,6 +22,7 @@ wasmtime-runtime = { path = "../runtime", version = "0.19.0" }
wasmtime-debug = { path = "../debug", version = "0.19.0" }
wasmtime-profiling = { path = "../profiling", version = "0.19.0" }
wasmtime-obj = { path = "../obj", version = "0.19.0" }
rayon = { version = "1.0", optional = true }
region = "2.1.0"
thiserror = "1.0.4"
target-lexicon = { version = "0.10.0", default-features = false }
@@ -41,6 +42,7 @@ winapi = { version = "0.3.8", features = ["winnt", "impl-default"] }
lightbeam = ["wasmtime-environ/lightbeam"]
jitdump = ["wasmtime-profiling/jitdump"]
vtune = ["wasmtime-profiling/vtune"]
parallel-compilation = ["rayon"]
# Try the experimental, work-in-progress new x86_64 backend. This is not stable
# as of June 2020.

View File

@@ -13,7 +13,7 @@ use std::{cmp, mem};
use wasmtime_environ::{
isa::{unwind::UnwindInfo, TargetIsa},
wasm::{FuncIndex, SignatureIndex},
Compilation, CompiledFunction,
CompiledFunction,
};
use wasmtime_runtime::{Mmap, VMFunctionBody};
@@ -119,31 +119,6 @@ impl CodeMemory {
Ok(vmfunc)
}
/// Allocate a continuous memory block for a compilation.
pub fn allocate_for_compilation(
&mut self,
compilation: &Compilation,
) -> Result<Box<[&mut [VMFunctionBody]]>, String> {
let total_len = compilation
.into_iter()
.fold(0, |acc, func| acc + Self::function_allocation_size(func));
let (mut buf, registry, start) = self.allocate(total_len)?;
let mut result = Vec::with_capacity(compilation.len());
let mut start = start as u32;
for func in compilation.into_iter() {
let (next_start, next_buf, vmfunc) = Self::copy_function(func, start, buf, registry);
result.push(vmfunc);
start = next_start;
buf = next_buf;
}
Ok(result.into_boxed_slice())
}
/// Make all allocated memory executable.
pub fn publish(&mut self, isa: &dyn TargetIsa) {
self.push_current(0)

View File

@@ -2,16 +2,15 @@
use crate::instantiate::SetupError;
use crate::object::{build_object, ObjectUnwindInfo};
use cranelift_codegen::ir;
use object::write::Object;
use std::hash::{Hash, Hasher};
use wasmtime_debug::{emit_dwarf, DwarfSection};
use wasmtime_environ::entity::{EntityRef, PrimaryMap};
use wasmtime_environ::isa::{unwind::UnwindInfo, TargetFrontendConfig, TargetIsa};
use wasmtime_environ::wasm::{DefinedFuncIndex, DefinedMemoryIndex, MemoryIndex};
use wasmtime_environ::entity::EntityRef;
use wasmtime_environ::isa::{TargetFrontendConfig, TargetIsa};
use wasmtime_environ::wasm::{DefinedMemoryIndex, MemoryIndex};
use wasmtime_environ::{
Compiler as _C, DebugInfoData, Module, ModuleAddressMap, ModuleMemoryOffset, ModuleTranslation,
ModuleVmctxInfo, StackMaps, Traps, Tunables, VMOffsets, ValueLabelsRanges,
CompiledFunctions, Compiler as EnvCompiler, DebugInfoData, Module, ModuleMemoryOffset,
ModuleTranslation, Tunables, VMOffsets,
};
/// Select which kind of compilation to use.
@@ -38,6 +37,7 @@ pub enum CompilationStrategy {
/// TODO: Consider using cranelift-module.
pub struct Compiler {
isa: Box<dyn TargetIsa>,
compiler: Box<dyn EnvCompiler>,
strategy: CompilationStrategy,
tunables: Tunables,
}
@@ -48,6 +48,13 @@ impl Compiler {
Self {
isa,
strategy,
compiler: match strategy {
CompilationStrategy::Auto | CompilationStrategy::Cranelift => {
Box::new(wasmtime_environ::cranelift::Cranelift::default())
}
#[cfg(feature = "lightbeam")]
CompilationStrategy::Lightbeam => Box::new(wasmtime_environ::lightbeam::Lightbeam),
},
tunables,
}
}
@@ -62,46 +69,26 @@ fn transform_dwarf_data(
isa: &dyn TargetIsa,
module: &Module,
debug_data: &DebugInfoData,
address_transform: &ModuleAddressMap,
value_ranges: &ValueLabelsRanges,
stack_slots: PrimaryMap<DefinedFuncIndex, ir::StackSlots>,
unwind_info: PrimaryMap<DefinedFuncIndex, &Option<UnwindInfo>>,
funcs: &CompiledFunctions,
) -> Result<Vec<DwarfSection>, SetupError> {
let target_config = isa.frontend_config();
let ofs = VMOffsets::new(target_config.pointer_bytes(), &module.local);
let module_vmctx_info = {
ModuleVmctxInfo {
memory_offset: if ofs.num_imported_memories > 0 {
let memory_offset = if ofs.num_imported_memories > 0 {
ModuleMemoryOffset::Imported(ofs.vmctx_vmmemory_import(MemoryIndex::new(0)))
} else if ofs.num_defined_memories > 0 {
ModuleMemoryOffset::Defined(
ofs.vmctx_vmmemory_definition_base(DefinedMemoryIndex::new(0)),
)
ModuleMemoryOffset::Defined(ofs.vmctx_vmmemory_definition_base(DefinedMemoryIndex::new(0)))
} else {
ModuleMemoryOffset::None
},
stack_slots,
}
};
emit_dwarf(
isa,
debug_data,
&address_transform,
&module_vmctx_info,
&value_ranges,
&unwind_info,
)
.map_err(SetupError::DebugInfo)
emit_dwarf(isa, debug_data, funcs, &memory_offset).map_err(SetupError::DebugInfo)
}
#[allow(missing_docs)]
pub struct Compilation {
pub obj: Object,
pub unwind_info: Vec<ObjectUnwindInfo>,
pub traps: Traps,
pub stack_maps: StackMaps,
pub address_transform: ModuleAddressMap,
pub funcs: CompiledFunctions,
}
impl Compiler {
@@ -120,66 +107,49 @@ impl Compiler {
&self.tunables
}
/// Return the compilation strategy.
pub fn strategy(&self) -> CompilationStrategy {
self.strategy
}
/// Compile the given function bodies.
pub(crate) fn compile<'data>(
pub fn compile<'data>(
&self,
translation: &ModuleTranslation,
) -> Result<Compilation, SetupError> {
let (
compilation,
relocations,
address_transform,
value_ranges,
stack_slots,
traps,
stack_maps,
) = match self.strategy {
// For now, interpret `Auto` as `Cranelift` since that's the most stable
// implementation.
CompilationStrategy::Auto | CompilationStrategy::Cranelift => {
wasmtime_environ::cranelift::Cranelift::compile_module(translation, &*self.isa)
}
#[cfg(feature = "lightbeam")]
CompilationStrategy::Lightbeam => {
wasmtime_environ::lightbeam::Lightbeam::compile_module(translation, &*self.isa)
cfg_if::cfg_if! {
if #[cfg(feature = "parallel-compilation")] {
use rayon::prelude::*;
let iter = translation.function_body_inputs
.iter()
.collect::<Vec<_>>()
.into_par_iter();
} else {
let iter = translation.function_body_inputs.iter();
}
}
.map_err(SetupError::Compile)?;
let funcs = iter
.map(|(index, func)| {
self.compiler
.compile_function(translation, index, func, &*self.isa)
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.collect::<CompiledFunctions>();
let dwarf_sections = if translation.debuginfo.is_some() && !compilation.is_empty() {
let unwind_info = compilation.unwind_info();
let dwarf_sections = if translation.debuginfo.is_some() && !funcs.is_empty() {
transform_dwarf_data(
&*self.isa,
&translation.module,
translation.debuginfo.as_ref().unwrap(),
&address_transform,
&value_ranges,
stack_slots,
unwind_info,
&funcs,
)?
} else {
vec![]
};
let (obj, unwind_info) = build_object(
&*self.isa,
&translation.module,
compilation,
relocations,
dwarf_sections,
)?;
let (obj, unwind_info) =
build_object(&*self.isa, &translation.module, &funcs, dwarf_sections)?;
Ok(Compilation {
obj,
unwind_info,
traps,
stack_maps,
address_transform,
funcs,
})
}
}
@@ -188,6 +158,7 @@ impl Hash for Compiler {
fn hash<H: Hasher>(&self, hasher: &mut H) {
let Compiler {
strategy,
compiler: _,
isa,
tunables,
} = self;

View File

@@ -20,8 +20,8 @@ use wasmtime_environ::entity::{BoxedSlice, PrimaryMap};
use wasmtime_environ::isa::TargetIsa;
use wasmtime_environ::wasm::{DefinedFuncIndex, SignatureIndex};
use wasmtime_environ::{
CompileError, DataInitializer, DataInitializerLocation, Module, ModuleAddressMap,
ModuleEnvironment, ModuleTranslation, StackMaps, Traps,
CompileError, DataInitializer, DataInitializerLocation, FunctionAddressMap, Module,
ModuleEnvironment, ModuleTranslation, StackMapInformation, TrapInformation,
};
use wasmtime_profiling::ProfilingAgent;
use wasmtime_runtime::VMInterrupts;
@@ -67,19 +67,20 @@ pub struct CompilationArtifacts {
/// Data initiailizers.
data_initializers: Box<[OwnedDataInitializer]>,
/// Traps descriptors.
traps: Traps,
/// Stack map descriptors.
stack_maps: StackMaps,
/// Wasm to function code address map.
address_transform: ModuleAddressMap,
/// Descriptions of compiled functions
funcs: PrimaryMap<DefinedFuncIndex, FunctionInfo>,
/// Debug info presence flags.
debug_info: bool,
}
#[derive(Serialize, Deserialize, Clone)]
struct FunctionInfo {
traps: Vec<TrapInformation>,
address_map: FunctionAddressMap,
stack_maps: Vec<StackMapInformation>,
}
impl CompilationArtifacts {
/// Builds compilation artifacts.
pub fn build(compiler: &Compiler, data: &[u8]) -> Result<Self, SetupError> {
@@ -92,9 +93,7 @@ impl CompilationArtifacts {
let Compilation {
obj,
unwind_info,
traps,
stack_maps,
address_transform,
funcs,
} = compiler.compile(&translation)?;
let ModuleTranslation {
@@ -120,9 +119,14 @@ impl CompilationArtifacts {
obj: obj.into_boxed_slice(),
unwind_info: unwind_info.into_boxed_slice(),
data_initializers,
traps,
stack_maps,
address_transform,
funcs: funcs
.into_iter()
.map(|(_, func)| FunctionInfo {
stack_maps: func.stack_maps,
traps: func.traps,
address_map: func.address_map,
})
.collect(),
debug_info: compiler.tunables().debug_info,
})
}
@@ -147,9 +151,7 @@ pub struct CompiledModule {
finished_functions: FinishedFunctions,
trampolines: PrimaryMap<SignatureIndex, VMTrampoline>,
data_initializers: Box<[OwnedDataInitializer]>,
traps: Traps,
stack_maps: StackMaps,
address_transform: ModuleAddressMap,
funcs: PrimaryMap<DefinedFuncIndex, FunctionInfo>,
obj: Box<[u8]>,
unwind_info: Box<[ObjectUnwindInfo]>,
}
@@ -176,9 +178,7 @@ impl CompiledModule {
obj,
unwind_info,
data_initializers,
traps,
stack_maps,
address_transform,
funcs,
debug_info,
} = artifacts;
@@ -216,9 +216,7 @@ impl CompiledModule {
finished_functions,
trampolines,
data_initializers,
traps,
stack_maps,
address_transform,
funcs,
obj,
unwind_info,
})
@@ -231,9 +229,7 @@ impl CompiledModule {
obj: self.obj.clone(),
unwind_info: self.unwind_info.clone(),
data_initializers: self.data_initializers.clone(),
traps: self.traps.clone(),
stack_maps: self.stack_maps.clone(),
address_transform: self.address_transform.clone(),
funcs: self.funcs.clone(),
debug_info: self.code.dbg_jit_registration.is_some(),
}
}
@@ -318,19 +314,36 @@ impl CompiledModule {
&self.finished_functions.0
}
/// Returns the map for all traps in this module.
pub fn traps(&self) -> &Traps {
&self.traps
/// Returns the stack map information for all functions defined in this
/// module.
///
/// The iterator returned iterates over the span of the compiled function in
/// memory with the stack maps associated with those bytes.
pub fn stack_maps(
&self,
) -> impl Iterator<Item = (*mut [VMFunctionBody], &[StackMapInformation])> {
self.finished_functions()
.values()
.copied()
.zip(self.funcs.values().map(|f| f.stack_maps.as_slice()))
}
/// Returns the map for each of this module's stack maps.
pub fn stack_maps(&self) -> &StackMaps {
&self.stack_maps
}
/// Returns a map of compiled addresses back to original bytecode offsets.
pub fn address_transform(&self) -> &ModuleAddressMap {
&self.address_transform
/// Iterates over all functions in this module, returning information about
/// how to decode traps which happen in the function.
pub fn trap_information(
&self,
) -> impl Iterator<
Item = (
DefinedFuncIndex,
*mut [VMFunctionBody],
&[TrapInformation],
&FunctionAddressMap,
),
> {
self.finished_functions()
.iter()
.zip(self.funcs.values())
.map(|((i, alloc), func)| (i, *alloc, func.traps.as_slice(), &func.address_map))
}
/// Returns all ranges convered by JIT code.

View File

@@ -5,10 +5,10 @@ use cranelift_frontend::FunctionBuilderContext;
use object::write::Object;
use serde::{Deserialize, Serialize};
use wasmtime_debug::DwarfSection;
use wasmtime_environ::entity::{EntityRef, PrimaryMap};
use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::isa::{unwind::UnwindInfo, TargetIsa};
use wasmtime_environ::wasm::{FuncIndex, SignatureIndex};
use wasmtime_environ::{Compilation, Module, Relocations};
use wasmtime_environ::{CompiledFunctions, Module};
use wasmtime_obj::{ObjectBuilder, ObjectBuilderTarget};
pub use wasmtime_obj::utils;
@@ -24,8 +24,7 @@ pub enum ObjectUnwindInfo {
pub(crate) fn build_object(
isa: &dyn TargetIsa,
module: &Module,
compilation: Compilation,
relocations: Relocations,
funcs: &CompiledFunctions,
dwarf_sections: Vec<DwarfSection>,
) -> Result<(Object, Vec<ObjectUnwindInfo>), anyhow::Error> {
const CODE_SECTION_ALIGNMENT: u64 = 0x1000;
@@ -37,38 +36,28 @@ pub(crate) fn build_object(
let mut unwind_info = Vec::new();
// Preserve function unwind info.
unwind_info.extend(
compilation
.into_iter()
.enumerate()
.filter_map(|(index, func)| {
func.unwind_info.as_ref().map(|info| {
ObjectUnwindInfo::Func(
FuncIndex::new(module.local.num_imported_funcs + index),
info.clone(),
)
})
}),
);
unwind_info.extend(funcs.iter().filter_map(|(index, func)| {
func.unwind_info
.as_ref()
.map(|info| ObjectUnwindInfo::Func(module.local.func_index(index), info.clone()))
}));
let mut trampolines = PrimaryMap::with_capacity(module.local.signatures.len());
let mut cx = FunctionBuilderContext::new();
// Build trampolines for every signature.
for (i, (_, native_sig)) in module.local.signatures.iter() {
let (func, relocs) =
build_trampoline(isa, &mut cx, native_sig, std::mem::size_of::<u128>())?;
let func = build_trampoline(isa, &mut cx, native_sig, std::mem::size_of::<u128>())?;
// Preserve trampoline function unwind info.
if let Some(info) = &func.unwind_info {
unwind_info.push(ObjectUnwindInfo::Trampoline(i, info.clone()))
}
trampolines.push((func, relocs));
trampolines.push(func);
}
let target = ObjectBuilderTarget::new(isa.triple().architecture)?;
let mut builder = ObjectBuilder::new(target, module);
let mut builder = ObjectBuilder::new(target, module, funcs);
builder
.set_code_alignment(CODE_SECTION_ALIGNMENT)
.set_compilation(compilation, relocations)
.set_trampolines(trampolines)
.set_dwarf_sections(dwarf_sections);
let obj = builder.build()?;

View File

@@ -33,9 +33,9 @@ pub fn make_trampoline(
signature: &ir::Signature,
value_size: usize,
) -> Result<VMTrampoline, SetupError> {
let (compiled_function, relocs) = build_trampoline(isa, fn_builder_ctx, signature, value_size)?;
let compiled_function = build_trampoline(isa, fn_builder_ctx, signature, value_size)?;
assert!(relocs.is_empty());
assert!(compiled_function.relocations.is_empty());
let ptr = code_memory
.allocate_for_function(&compiled_function)
.map_err(|message| SetupError::Instantiate(InstantiationError::Resource(message)))?
@@ -48,7 +48,7 @@ pub(crate) fn build_trampoline(
fn_builder_ctx: &mut FunctionBuilderContext,
signature: &ir::Signature,
value_size: usize,
) -> Result<(CompiledFunction, Vec<Relocation>), SetupError> {
) -> Result<CompiledFunction, SetupError> {
let pointer_type = isa.pointer_type();
let mut wrapper_sig = ir::Signature::new(isa.frontend_config().default_call_conv);
@@ -155,14 +155,17 @@ pub(crate) fn build_trampoline(
)))
})?;
Ok((
CompiledFunction {
Ok(CompiledFunction {
body: code_buf,
jt_offsets: context.func.jt_offsets,
unwind_info,
},
reloc_sink.relocs,
))
relocations: reloc_sink.relocs,
stack_maps: Default::default(),
stack_slots: Default::default(),
traps: Default::default(),
value_labels_ranges: Default::default(),
address_map: Default::default(),
})
}
/// We don't expect trampoline compilation to produce many relocations, so

View File

@@ -28,12 +28,10 @@ use std::collections::HashMap;
use target_lexicon::Triple;
use wasmtime_debug::{DwarfSection, DwarfSectionRelocTarget};
use wasmtime_environ::entity::{EntityRef, PrimaryMap};
use wasmtime_environ::ir::{JumpTableOffsets, LibCall, Reloc};
use wasmtime_environ::ir::{LibCall, Reloc};
use wasmtime_environ::isa::unwind::UnwindInfo;
use wasmtime_environ::wasm::{DefinedFuncIndex, FuncIndex, SignatureIndex};
use wasmtime_environ::{
Compilation, CompiledFunction, Module, Relocation, RelocationTarget, Relocations,
};
use wasmtime_environ::{CompiledFunction, CompiledFunctions, Module, Relocation, RelocationTarget};
fn to_object_relocations<'a>(
it: impl Iterator<Item = &'a Relocation> + 'a,
@@ -41,7 +39,7 @@ fn to_object_relocations<'a>(
module: &'a Module,
funcs: &'a PrimaryMap<FuncIndex, SymbolId>,
libcalls: &'a HashMap<LibCall, SymbolId>,
jt_offsets: &'a PrimaryMap<DefinedFuncIndex, JumpTableOffsets>,
compiled_funcs: &'a CompiledFunctions,
) -> impl Iterator<Item = ObjectRelocation> + 'a {
it.filter_map(move |r| {
let (symbol, symbol_offset) = match r.reloc_target {
@@ -49,9 +47,9 @@ fn to_object_relocations<'a>(
RelocationTarget::LibCall(call) => (libcalls[&call], 0),
RelocationTarget::JumpTable(f, jt) => {
let df = module.local.defined_func_index(f).unwrap();
let offset = *jt_offsets
let offset = *compiled_funcs
.get(df)
.and_then(|ofs| ofs.get(jt))
.and_then(|f| f.jt_offsets.get(jt))
.expect("func jump table");
(funcs[f], offset)
}
@@ -258,20 +256,24 @@ pub struct ObjectBuilder<'a> {
target: ObjectBuilderTarget,
module: &'a Module,
code_alignment: u64,
compilation: Option<(Compilation, Relocations)>,
trampolines: PrimaryMap<SignatureIndex, (CompiledFunction, Vec<Relocation>)>,
compilation: &'a CompiledFunctions,
trampolines: PrimaryMap<SignatureIndex, CompiledFunction>,
dwarf_sections: Vec<DwarfSection>,
}
impl<'a> ObjectBuilder<'a> {
pub fn new(target: ObjectBuilderTarget, module: &'a Module) -> Self {
pub fn new(
target: ObjectBuilderTarget,
module: &'a Module,
compilation: &'a CompiledFunctions,
) -> Self {
Self {
target,
module,
code_alignment: 1,
compilation: None,
trampolines: PrimaryMap::new(),
dwarf_sections: vec![],
compilation,
}
}
@@ -280,18 +282,9 @@ impl<'a> ObjectBuilder<'a> {
self
}
pub fn set_compilation(
&mut self,
compilation: Compilation,
relocations: Relocations,
) -> &mut Self {
self.compilation = Some((compilation, relocations));
self
}
pub fn set_trampolines(
&mut self,
trampolines: PrimaryMap<SignatureIndex, (CompiledFunction, Vec<Relocation>)>,
trampolines: PrimaryMap<SignatureIndex, CompiledFunction>,
) -> &mut Self {
self.trampolines = trampolines;
self
@@ -319,17 +312,8 @@ impl<'a> ObjectBuilder<'a> {
SectionKind::Text,
);
let (compilation, jt_offsets, relocations) = self.compilation.map_or_else(
|| (None, PrimaryMap::new(), PrimaryMap::new()),
|(c, relocations)| {
let jt_offsets = c.get_jt_offsets();
(Some(c), jt_offsets, relocations)
},
);
// Create symbols for imports -- needed during linking.
let mut func_symbols =
PrimaryMap::with_capacity(compilation.as_ref().map_or_else(|| 0, |c| c.len()));
let mut func_symbols = PrimaryMap::with_capacity(self.compilation.len());
for index in 0..module.local.num_imported_funcs {
let symbol_id = obj.add_symbol(Symbol {
name: utils::func_symbol_name(FuncIndex::new(index))
@@ -346,16 +330,10 @@ impl<'a> ObjectBuilder<'a> {
func_symbols.push(symbol_id);
}
if let Some(compilation) = compilation {
// Create symbols and section data for the compiled functions.
for (index, func) in compilation.into_iter().enumerate() {
let mut append_func = |name: Vec<u8>, func: &CompiledFunction| {
let off = obj.append_section_data(section_id, &func.body, 1);
let symbol_id = obj.add_symbol(Symbol {
name: utils::func_symbol_name(
module.local.func_index(DefinedFuncIndex::new(index)),
)
.as_bytes()
.to_vec(),
name,
value: off,
size: func.body.len() as u64,
kind: SymbolKind::Text,
@@ -364,33 +342,25 @@ impl<'a> ObjectBuilder<'a> {
section: SymbolSection::Section(section_id),
flags: SymbolFlags::None,
});
func_symbols.push(symbol_id);
// Preserve function unwind info.
if let Some(info) = &func.unwind_info {
process_unwind_info(info, &mut obj, section_id);
}
}
}
symbol_id
};
// Create trampoline symbols for every signature.
let mut trampoline_relocs = HashMap::new();
for (i, (func, relocs)) in self.trampolines.into_iter() {
let off = obj.append_section_data(section_id, &func.body, 1);
let symbol_id = obj.add_symbol(Symbol {
name: utils::trampoline_symbol_name(i).as_bytes().to_vec(),
value: off,
size: func.body.len() as u64,
kind: SymbolKind::Text,
scope: SymbolScope::Compilation,
weak: false,
section: SymbolSection::Section(section_id),
flags: SymbolFlags::None,
});
trampoline_relocs.insert(symbol_id, relocs);
// Preserve trampoline function unwind info.
if let Some(info) = &func.unwind_info {
process_unwind_info(info, &mut obj, section_id);
// Create symbols and section data for the compiled functions.
for (index, func) in self.compilation.iter() {
let name = utils::func_symbol_name(module.local.func_index(index))
.as_bytes()
.to_vec();
let symbol_id = append_func(name, func);
func_symbols.push(symbol_id);
}
let mut trampolines = Vec::new();
for (i, func) in self.trampolines.iter() {
let name = utils::trampoline_symbol_name(i).as_bytes().to_vec();
trampolines.push(append_func(name, func));
}
obj.append_section_data(section_id, &[], self.code_alignment);
@@ -412,32 +382,32 @@ impl<'a> ObjectBuilder<'a> {
let libcalls = write_libcall_symbols(&mut obj);
// Write all functions relocations.
for (index, relocs) in relocations.into_iter() {
for (index, func) in self.compilation.into_iter() {
let func_index = module.local.func_index(index);
let (_, off) = obj
.symbol_section_and_offset(func_symbols[func_index])
.unwrap();
for r in to_object_relocations(
relocs.iter(),
func.relocations.iter(),
off,
module,
&func_symbols,
&libcalls,
&jt_offsets,
&self.compilation,
) {
obj.add_relocation(section_id, r)?;
}
}
for (symbol, relocs) in trampoline_relocs {
for (func, symbol) in self.trampolines.values().zip(trampolines) {
let (_, off) = obj.symbol_section_and_offset(symbol).unwrap();
for r in to_object_relocations(
relocs.iter(),
func.relocations.iter(),
off,
module,
&func_symbols,
&libcalls,
&jt_offsets,
&self.compilation,
) {
obj.add_relocation(section_id, r)?;
}

View File

@@ -7,7 +7,7 @@ use object::write::{Object, Relocation, StandardSection, Symbol, SymbolSection};
use object::{RelocationEncoding, RelocationKind, SymbolFlags, SymbolKind, SymbolScope};
use wasmtime_debug::DwarfSection;
use wasmtime_environ::isa::TargetFrontendConfig;
use wasmtime_environ::{Compilation, DataInitializer, Module, Relocations};
use wasmtime_environ::{CompiledFunctions, DataInitializer, Module};
fn emit_vmcontext_init(
obj: &mut Object,
@@ -52,13 +52,11 @@ pub fn emit_module(
target: ObjectBuilderTarget,
module: &Module,
target_config: &TargetFrontendConfig,
compilation: Compilation,
relocations: Relocations,
compilation: CompiledFunctions,
dwarf_sections: Vec<DwarfSection>,
data_initializers: &[DataInitializer],
) -> Result<Object> {
let mut builder = ObjectBuilder::new(target, module);
builder.set_compilation(compilation, relocations);
let mut builder = ObjectBuilder::new(target, module, &compilation);
builder.set_dwarf_sections(dwarf_sections);
let mut obj = builder.build()?;

View File

@@ -54,4 +54,4 @@ jitdump = ["wasmtime-jit/jitdump"]
vtune = ["wasmtime-jit/vtune"]
# Enables parallel compilation of WebAssembly code
parallel-compilation = ["wasmtime-environ/parallel-compilation"]
parallel-compilation = ["wasmtime-jit/parallel-compilation"]

View File

@@ -152,15 +152,10 @@ pub fn register(module: &CompiledModule) -> Option<GlobalFrameInfoRegistration>
let mut min = usize::max_value();
let mut max = 0;
let mut functions = BTreeMap::new();
for (((i, allocated), traps), instrs) in module
.finished_functions()
.iter()
.zip(module.traps().values())
.zip(module.address_transform().values())
{
for (i, allocated, traps, address_map) in module.trap_information() {
let (start, end) = unsafe {
let ptr = (**allocated).as_ptr();
let len = (**allocated).len();
let ptr = (*allocated).as_ptr();
let len = (*allocated).len();
(ptr as usize, ptr as usize + len)
};
min = cmp::min(min, start);
@@ -169,7 +164,7 @@ pub fn register(module: &CompiledModule) -> Option<GlobalFrameInfoRegistration>
start,
index: module.module().local.func_index(i),
traps: traps.to_vec(),
instr_map: (*instrs).clone(),
instr_map: address_map.clone(),
};
assert!(functions.insert(end, func).is_none());
}

View File

@@ -982,20 +982,15 @@ impl Store {
pub(crate) fn register_stack_maps(&self, module: &Module) {
let module = &module.compiled_module();
self.stack_map_registry().register_stack_maps(
module
.finished_functions()
.values()
.zip(module.stack_maps().values())
.map(|(func, stack_maps)| unsafe {
let ptr = (**func).as_ptr();
let len = (**func).len();
self.stack_map_registry()
.register_stack_maps(module.stack_maps().map(|(func, stack_maps)| unsafe {
let ptr = (*func).as_ptr();
let len = (*func).len();
let start = ptr as usize;
let end = ptr as usize + len;
let range = start..end;
(range, &stack_maps[..])
}),
);
(range, stack_maps)
}));
}
pub(crate) unsafe fn add_instance(&self, handle: InstanceHandle) -> StoreInstanceHandle {

View File

@@ -197,6 +197,12 @@ fn make_trampoline(
body: code_buf,
jt_offsets: context.func.jt_offsets,
unwind_info,
relocations: Default::default(),
address_map: Default::default(),
stack_maps: Default::default(),
stack_slots: Default::default(),
traps: Default::default(),
value_labels_ranges: Default::default(),
})
.expect("allocate_for_function")
}

View File

@@ -1,17 +1,11 @@
use anyhow::{anyhow, bail, Context as _, Result};
use anyhow::{bail, Context as _, Result};
use object::write::Object;
use target_lexicon::Triple;
use wasmtime::Strategy;
use wasmtime_debug::emit_dwarf;
#[cfg(feature = "lightbeam")]
use wasmtime_environ::Lightbeam;
use wasmtime_environ::{
entity::EntityRef, settings, settings::Configurable, wasm::DefinedMemoryIndex,
wasm::MemoryIndex, Compiler, Cranelift, ModuleEnvironment, ModuleMemoryOffset, ModuleVmctxInfo,
Tunables, VMOffsets,
};
use wasmtime_jit::native;
use wasmtime_obj::{emit_module, ObjectBuilderTarget};
use wasmtime_environ::{settings, settings::Configurable, ModuleEnvironment, Tunables};
use wasmtime_jit::{native, Compiler};
/// Creates object file from binary wasm data.
pub fn compile_to_obj(
@@ -49,83 +43,28 @@ pub fn compile_to_obj(
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
let target = ObjectBuilderTarget::from_triple(isa.triple())?;
// TODO: Expose the tunables as command-line flags.
let mut tunables = Tunables::default();
tunables.debug_info = debug_info;
let environ = ModuleEnvironment::new(isa.frontend_config(), &tunables);
let compiler = Compiler::new(
isa,
match strategy {
Strategy::Auto => wasmtime_jit::CompilationStrategy::Auto,
Strategy::Cranelift => wasmtime_jit::CompilationStrategy::Cranelift,
#[cfg(feature = "lightbeam")]
Strategy::Lightbeam => wasmtime_jit::CompilationStrategy::Lightbeam,
#[cfg(not(feature = "lightbeam"))]
Strategy::Lightbeam => bail!("lightbeam support not enabled"),
s => bail!("unknown compilation strategy {:?}", s),
},
tunables.clone(),
);
let environ = ModuleEnvironment::new(compiler.isa().frontend_config(), &tunables);
let translation = environ
.translate(wasm)
.context("failed to translate module")?;
// TODO: use the traps and stack maps information.
let (
compilation,
relocations,
address_transform,
value_ranges,
stack_slots,
_traps,
_stack_maps,
) = match strategy {
Strategy::Auto | Strategy::Cranelift => Cranelift::compile_module(&translation, &*isa),
#[cfg(feature = "lightbeam")]
Strategy::Lightbeam => Lightbeam::compile_module(&translation, &*isa),
#[cfg(not(feature = "lightbeam"))]
Strategy::Lightbeam => bail!("lightbeam support not enabled"),
other => bail!("unsupported compilation strategy {:?}", other),
}
.context("failed to compile module")?;
if compilation.is_empty() {
bail!("no functions were found/compiled");
}
let module_vmctx_info = {
let ofs = VMOffsets::new(
translation.target_config.pointer_bytes(),
&translation.module.local,
);
ModuleVmctxInfo {
memory_offset: if ofs.num_imported_memories > 0 {
ModuleMemoryOffset::Imported(ofs.vmctx_vmmemory_import(MemoryIndex::new(0)))
} else if ofs.num_defined_memories > 0 {
ModuleMemoryOffset::Defined(
ofs.vmctx_vmmemory_definition_base(DefinedMemoryIndex::new(0)),
)
} else {
ModuleMemoryOffset::None
},
stack_slots,
}
};
let dwarf_sections = if let Some(debug_data) = &translation.debuginfo {
emit_dwarf(
&*isa,
&debug_data,
&address_transform,
&module_vmctx_info,
&value_ranges,
&compilation.unwind_info(),
)
.context("failed to emit debug sections")?
} else {
vec![]
};
Ok(emit_module(
target,
&translation.module,
&translation.target_config,
compilation,
relocations,
dwarf_sections,
&translation.data_initializers,
)
.map_err(|e| anyhow!(e))
.context("failed to emit module")?)
let compilation = compiler.compile(&translation)?;
Ok(compilation.obj)
}