Remove dependency on TargetIsa from Wasmtime crates (#3178)

This commit started off by deleting the `cranelift_codegen::settings`
reexport in the `wasmtime-environ` crate and then basically played
whack-a-mole until everything compiled again. The main result of this is
that the `wasmtime-*` family of crates have generally less of a
dependency on the `TargetIsa` trait and type from Cranelift. While the
dependency isn't entirely severed yet this is at least a significant
start.

This commit is intended to be largely refactorings, no functional
changes are intended here. The refactorings are:

* A `CompilerBuilder` trait has been added to `wasmtime_environ` which
  server as an abstraction used to create compilers and configure them
  in a uniform fashion. The `wasmtime::Config` type now uses this
  instead of cranelift-specific settings. The `wasmtime-jit` crate
  exports the ability to create a compiler builder from a
  `CompilationStrategy`, which only works for Cranelift right now. In a
  cranelift-less build of Wasmtime this is expected to return a trait
  object that fails all requests to compile.

* The `Compiler` trait in the `wasmtime_environ` crate has been souped
  up with a number of methods that Wasmtime and other crates needed.

* The `wasmtime-debug` crate is now moved entirely behind the
  `wasmtime-cranelift` crate.

* The `wasmtime-cranelift` crate is now only depended on by the
  `wasmtime-jit` crate.

* Wasm types in `cranelift-wasm` no longer contain their IR type,
  instead they only contain the `WasmType`. This is required to get
  everything to align correctly but will also be required in a future
  refactoring where the types used by `cranelift-wasm` will be extracted
  to a separate crate.

* I moved around a fair bit of code in `wasmtime-cranelift`.

* Some gdb-specific jit-specific code has moved from `wasmtime-debug` to
  `wasmtime-jit`.
This commit is contained in:
Alex Crichton
2021-08-16 09:55:39 -05:00
committed by GitHub
parent 7c0948fe0b
commit 0313e30d76
47 changed files with 1529 additions and 1384 deletions

12
Cargo.lock generated
View File

@@ -3564,7 +3564,6 @@ dependencies = [
"bincode",
"cfg-if 1.0.0",
"cpp_demangle",
"cranelift-native",
"indexmap",
"lazy_static",
"libc",
@@ -3700,12 +3699,16 @@ dependencies = [
name = "wasmtime-cranelift"
version = "0.29.0"
dependencies = [
"anyhow",
"cranelift-codegen",
"cranelift-entity",
"cranelift-frontend",
"cranelift-native",
"cranelift-wasm",
"gimli",
"target-lexicon",
"wasmparser",
"wasmtime-debug",
"wasmtime-environ",
]
@@ -3727,6 +3730,7 @@ dependencies = [
name = "wasmtime-environ"
version = "0.29.0"
dependencies = [
"anyhow",
"cfg-if 1.0.0",
"cranelift-codegen",
"cranelift-entity",
@@ -3736,6 +3740,7 @@ dependencies = [
"log",
"more-asserts",
"serde",
"target-lexicon",
"thiserror",
"wasmparser",
]
@@ -3806,7 +3811,6 @@ dependencies = [
"thiserror",
"wasmparser",
"wasmtime-cranelift",
"wasmtime-debug",
"wasmtime-environ",
"wasmtime-lightbeam",
"wasmtime-obj",
@@ -3819,8 +3823,11 @@ dependencies = [
name = "wasmtime-lightbeam"
version = "0.29.0"
dependencies = [
"anyhow",
"cranelift-codegen",
"gimli",
"lightbeam",
"target-lexicon",
"wasmparser",
"wasmtime-environ",
]
@@ -3833,7 +3840,6 @@ dependencies = [
"more-asserts",
"object",
"target-lexicon",
"wasmtime-debug",
"wasmtime-environ",
]

View File

@@ -199,9 +199,6 @@ fn ignore(testsuite: &str, testname: &str, strategy: &str) -> bool {
// for reference counts on `externref`, but the old backend does not
// implement atomic instructions.
("reference_types", _) if cfg!(feature = "old-x86-backend") => return true,
// Skip all SIMD tests on old backend, there are instructions not
// implemented there and support is generally not maintained.
("simd", _) if cfg!(feature = "old-x86-backend") => return true,
// No simd support yet for s390x.
("simd", _) if platform_is_s390x() => return true,
_ => {}

View File

@@ -284,7 +284,14 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
Ok(GlobalVariable::Memory {
gv: vmctx,
offset,
ty: self.mod_info.globals[index].entity.ty,
ty: match self.mod_info.globals[index].entity.wasm_ty {
WasmType::I32 => ir::types::I32,
WasmType::I64 => ir::types::I64,
WasmType::F32 => ir::types::F32,
WasmType::F64 => ir::types::F64,
WasmType::V128 => ir::types::I8X16,
WasmType::ExnRef | WasmType::FuncRef | WasmType::ExternRef => ir::types::R64,
},
})
}

View File

@@ -706,7 +706,7 @@ pub trait FuncEnvironment: TargetEnvironment {
/// An object satisfying the `ModuleEnvironment` trait can be passed as argument to the
/// [`translate_module`](fn.translate_module.html) function. These methods should not be called
/// by the user, they are only for `cranelift-wasm` internal use.
pub trait ModuleEnvironment<'data>: TargetEnvironment {
pub trait ModuleEnvironment<'data> {
/// Provides the number of types up front. By default this does nothing, but
/// implementations can use this to preallocate memory if desired.
fn reserve_types(&mut self, _num: u32) -> WasmResult<()> {

View File

@@ -10,9 +10,8 @@
use crate::environ::{Alias, ModuleEnvironment, WasmError, WasmResult};
use crate::state::ModuleTranslationState;
use crate::translation_utils::{
tabletype_to_type, type_to_type, DataIndex, ElemIndex, EntityIndex, EntityType, FuncIndex,
Global, GlobalIndex, GlobalInit, InstanceIndex, Memory, MemoryIndex, ModuleIndex, Table,
TableElementType, TableIndex, Tag, TagIndex, TypeIndex,
DataIndex, ElemIndex, EntityIndex, EntityType, FuncIndex, Global, GlobalIndex, GlobalInit,
InstanceIndex, Memory, MemoryIndex, ModuleIndex, Table, TableIndex, Tag, TagIndex, TypeIndex,
};
use crate::wasm_unsupported;
use core::convert::TryFrom;
@@ -46,10 +45,8 @@ fn entity_type(
}
ImportSectionEntryType::Memory(ty) => EntityType::Memory(memory(ty)),
ImportSectionEntryType::Tag(t) => EntityType::Tag(tag(t)),
ImportSectionEntryType::Global(ty) => {
EntityType::Global(global(ty, environ, GlobalInit::Import)?)
}
ImportSectionEntryType::Table(ty) => EntityType::Table(table(ty, environ)?),
ImportSectionEntryType::Global(ty) => EntityType::Global(global(ty, GlobalInit::Import)?),
ImportSectionEntryType::Table(ty) => EntityType::Table(table(ty)?),
})
}
@@ -68,26 +65,17 @@ fn tag(e: TagType) -> Tag {
}
}
fn table(ty: TableType, environ: &mut dyn ModuleEnvironment<'_>) -> WasmResult<Table> {
fn table(ty: TableType) -> WasmResult<Table> {
Ok(Table {
wasm_ty: ty.element_type.try_into()?,
ty: match tabletype_to_type(ty.element_type, environ)? {
Some(t) => TableElementType::Val(t),
None => TableElementType::Func,
},
minimum: ty.initial,
maximum: ty.maximum,
})
}
fn global(
ty: GlobalType,
environ: &mut dyn ModuleEnvironment<'_>,
initializer: GlobalInit,
) -> WasmResult<Global> {
fn global(ty: GlobalType, initializer: GlobalInit) -> WasmResult<Global> {
Ok(Global {
wasm_ty: ty.content_type.try_into()?,
ty: type_to_type(ty.content_type, environ).unwrap(),
mutability: ty.mutable,
initializer,
})
@@ -175,11 +163,11 @@ pub fn parse_import_section<'data>(
environ.declare_tag_import(tag(e), import.module, import.field)?;
}
ImportSectionEntryType::Global(ty) => {
let ty = global(ty, environ, GlobalInit::Import)?;
let ty = global(ty, GlobalInit::Import)?;
environ.declare_global_import(ty, import.module, import.field)?;
}
ImportSectionEntryType::Table(ty) => {
let ty = table(ty, environ)?;
let ty = table(ty)?;
environ.declare_table_import(ty, import.module, import.field)?;
}
}
@@ -218,7 +206,7 @@ pub fn parse_table_section(
environ.reserve_tables(tables.get_count())?;
for entry in tables {
let ty = table(entry?, environ)?;
let ty = table(entry?)?;
environ.declare_table(ty)?;
}
@@ -287,7 +275,7 @@ pub fn parse_global_section(
));
}
};
let ty = global(ty, environ, initializer)?;
let ty = global(ty, initializer)?;
environ.declare_global(ty)?;
}

View File

@@ -165,8 +165,6 @@ pub enum EntityType {
pub struct Global {
/// The Wasm type of the value stored in the global.
pub wasm_ty: crate::WasmType,
/// The Cranelift IR type of the value stored in the global.
pub ty: ir::Type,
/// A flag indicating whether the value may change at runtime.
pub mutability: bool,
/// The source of the initial value.
@@ -203,8 +201,6 @@ pub enum GlobalInit {
pub struct Table {
/// The table elements' Wasm type.
pub wasm_ty: WasmType,
/// The table elements' Cranelift type.
pub ty: TableElementType,
/// The minimum number of elements in the table.
pub minimum: u32,
/// The maximum number of elements in the table.

View File

@@ -11,13 +11,17 @@ keywords = ["webassembly", "wasm"]
edition = "2018"
[dependencies]
anyhow = "1.0"
wasmtime-environ = { path = "../environ", version = "0.29.0" }
cranelift-wasm = { path = "../../cranelift/wasm", version = "0.76.0" }
cranelift-codegen = { path = "../../cranelift/codegen", version = "0.76.0" }
cranelift-frontend = { path = "../../cranelift/frontend", version = "0.76.0" }
cranelift-entity = { path = "../../cranelift/entity", version = "0.76.0" }
cranelift-native = { path = '../../cranelift/native', version = '0.76.0' }
wasmtime-debug = { path = '../debug', version = '0.29.0' }
wasmparser = "0.80.0"
target-lexicon = "0.12"
gimli = "0.25.0"
[features]
all-arch = ["cranelift-codegen/all-arch"]

View File

@@ -0,0 +1,113 @@
//! Implementation of a "compiler builder" for cranelift
//!
//! This module contains the implementation of how Cranelift is configured, as
//! well as providing a function to return the default configuration to build.
use anyhow::Result;
use cranelift_codegen::isa;
use cranelift_codegen::settings::{self, Configurable, SetError};
use std::fmt;
use wasmtime_environ::{CompilerBuilder, Setting, SettingKind};
#[derive(Clone)]
struct Builder {
flags: settings::Builder,
isa_flags: isa::Builder,
}
pub fn builder() -> Box<dyn CompilerBuilder> {
let mut flags = settings::builder();
// There are two possible traps for division, and this way
// we get the proper one if code traps.
flags
.enable("avoid_div_traps")
.expect("should be valid flag");
// We don't use probestack as a stack limit mechanism
flags
.set("enable_probestack", "false")
.expect("should be valid flag");
Box::new(Builder {
flags,
isa_flags: cranelift_native::builder().expect("host machine is not a supported target"),
})
}
impl CompilerBuilder for Builder {
fn triple(&self) -> &target_lexicon::Triple {
self.isa_flags.triple()
}
fn clone(&self) -> Box<dyn CompilerBuilder> {
Box::new(Clone::clone(self))
}
fn target(&mut self, target: target_lexicon::Triple) -> Result<()> {
self.isa_flags = isa::lookup(target)?;
Ok(())
}
fn set(&mut self, name: &str, value: &str) -> Result<()> {
if let Err(err) = self.flags.set(name, value) {
match err {
SetError::BadName(_) => {
// Try the target-specific flags.
self.isa_flags.set(name, value)?;
}
_ => return Err(err.into()),
}
}
Ok(())
}
fn enable(&mut self, name: &str) -> Result<()> {
if let Err(err) = self.flags.enable(name) {
match err {
SetError::BadName(_) => {
// Try the target-specific flags.
self.isa_flags.enable(name)?;
}
_ => return Err(err.into()),
}
}
Ok(())
}
fn build(&self) -> Box<dyn wasmtime_environ::Compiler> {
let isa = self
.isa_flags
.clone()
.finish(settings::Flags::new(self.flags.clone()));
Box::new(crate::compiler::Compiler::new(isa))
}
fn settings(&self) -> Vec<Setting> {
self.isa_flags
.iter()
.map(|s| Setting {
description: s.description,
name: s.name,
values: s.values,
kind: match s.kind {
settings::SettingKind::Preset => SettingKind::Preset,
settings::SettingKind::Enum => SettingKind::Enum,
settings::SettingKind::Num => SettingKind::Num,
settings::SettingKind::Bool => SettingKind::Bool,
},
})
.collect()
}
}
impl fmt::Debug for Builder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Builder")
.field(
"flags",
&settings::Flags::new(self.flags.clone()).to_string(),
)
.finish()
}
}

View File

@@ -0,0 +1,660 @@
use crate::func_environ::{get_func_name, FuncEnvironment};
use crate::{blank_sig, func_signature, indirect_signature, value_type, wasmtime_call_conv};
use anyhow::Result;
use cranelift_codegen::ir::{self, ExternalName, InstBuilder, MemFlags};
use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::print_errors::pretty_error;
use cranelift_codegen::settings;
use cranelift_codegen::MachSrcLoc;
use cranelift_codegen::{binemit, Context};
use cranelift_frontend::FunctionBuilder;
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, FuncTranslator, WasmFuncType};
use std::cmp;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::mem;
use std::sync::Mutex;
use wasmtime_environ::{
CompileError, CompiledFunction, CompiledFunctions, DebugInfoData, DwarfSection, FlagValue,
FunctionAddressMap, FunctionBodyData, InstructionAddressMap, ModuleMemoryOffset,
ModuleTranslation, Relocation, RelocationTarget, StackMapInformation, TrapInformation,
Tunables, TypeTables,
};
/// A compiler that compiles a WebAssembly module with Compiler, translating
/// the Wasm to Compiler IR, optimizing it and then translating to assembly.
pub(crate) struct Compiler {
translators: Mutex<Vec<FuncTranslator>>,
isa: Box<dyn TargetIsa>,
}
impl Compiler {
pub(crate) fn new(isa: Box<dyn TargetIsa>) -> Compiler {
Compiler {
translators: Default::default(),
isa,
}
}
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);
}
fn get_function_address_map(
&self,
context: &Context,
data: &FunctionBodyData<'_>,
body_len: u32,
) -> FunctionAddressMap {
// Generate artificial srcloc for function start/end to identify boundary
// within module.
let data = data.body.get_binary_reader();
let offset = data.original_position();
let len = data.bytes_remaining();
assert!((offset + len) <= u32::max_value() as usize);
let start_srcloc = ir::SourceLoc::new(offset as u32);
let end_srcloc = ir::SourceLoc::new((offset + len) as u32);
let instructions = if let Some(ref mcr) = &context.mach_compile_result {
// New-style backend: we have a `MachCompileResult` that will give us `MachSrcLoc` mapping
// tuples.
collect_address_maps(
body_len,
mcr.buffer
.get_srclocs_sorted()
.into_iter()
.map(|&MachSrcLoc { start, end, loc }| (loc, start, (end - start))),
)
} else {
// Old-style backend: we need to traverse the instruction/encoding info in the function.
let func = &context.func;
let mut blocks = func.layout.blocks().collect::<Vec<_>>();
blocks.sort_by_key(|block| func.offsets[*block]); // Ensure inst offsets always increase
let encinfo = self.isa.encoding_info();
collect_address_maps(
body_len,
blocks
.into_iter()
.flat_map(|block| func.inst_offsets(block, &encinfo))
.map(|(offset, inst, size)| (func.srclocs[inst], offset, size)),
)
};
FunctionAddressMap {
instructions: instructions.into(),
start_srcloc,
end_srcloc,
body_offset: 0,
body_len,
}
}
}
impl wasmtime_environ::Compiler for Compiler {
fn compile_function(
&self,
translation: &ModuleTranslation<'_>,
func_index: DefinedFuncIndex,
mut input: FunctionBodyData<'_>,
tunables: &Tunables,
types: &TypeTables,
) -> Result<CompiledFunction, CompileError> {
let isa = &*self.isa;
let module = &translation.module;
let func_index = module.func_index(func_index);
let mut context = Context::new();
context.func.name = get_func_name(func_index);
context.func.signature = func_signature(isa, module, types, func_index);
if tunables.generate_native_debuginfo {
context.func.collect_debug_info();
}
let mut func_env = FuncEnvironment::new(isa, module, types, tunables);
// We use these as constant offsets below in
// `stack_limit_from_arguments`, so assert their values here. This
// allows the closure below to get coerced to a function pointer, as
// needed by `ir::Function`.
//
// Otherwise our stack limit is specially calculated from the vmctx
// argument, where we need to load the `*const VMInterrupts`
// pointer, and then from that pointer we need to load the stack
// limit itself. Note that manual register allocation is needed here
// too due to how late in the process this codegen happens.
//
// For more information about interrupts and stack checks, see the
// top of this file.
let vmctx = context
.func
.create_global_value(ir::GlobalValueData::VMContext);
let interrupts_ptr = context.func.create_global_value(ir::GlobalValueData::Load {
base: vmctx,
offset: i32::try_from(func_env.offsets.vmctx_interrupts())
.unwrap()
.into(),
global_type: isa.pointer_type(),
readonly: true,
});
let stack_limit = context.func.create_global_value(ir::GlobalValueData::Load {
base: interrupts_ptr,
offset: i32::try_from(func_env.offsets.vminterrupts_stack_limit())
.unwrap()
.into(),
global_type: isa.pointer_type(),
readonly: false,
});
context.func.stack_limit = Some(stack_limit);
let mut func_translator = self.take_translator();
func_translator.translate_body(
&mut input.validator,
input.body.clone(),
&mut context.func,
&mut func_env,
)?;
self.save_translator(func_translator);
let mut code_buf: Vec<u8> = Vec::new();
let mut reloc_sink = RelocSink::new(func_index);
let mut trap_sink = TrapSink::new();
let mut stack_map_sink = StackMapSink::default();
context
.compile_and_emit(
isa,
&mut code_buf,
&mut reloc_sink,
&mut trap_sink,
&mut stack_map_sink,
)
.map_err(|error| {
CompileError::Codegen(pretty_error(&context.func, Some(isa), error))
})?;
let unwind_info = context.create_unwind_info(isa).map_err(|error| {
CompileError::Codegen(pretty_error(&context.func, Some(isa), error))
})?;
let address_transform =
self.get_function_address_map(&context, &input, code_buf.len() as u32);
let ranges = if tunables.generate_native_debuginfo {
let ranges = context.build_value_labels_ranges(isa).map_err(|error| {
CompileError::Codegen(pretty_error(&context.func, Some(isa), error))
})?;
Some(ranges)
} else {
None
};
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_maps: stack_map_sink.finish(),
})
}
fn host_to_wasm_trampoline(&self, ty: &WasmFuncType) -> Result<CompiledFunction, CompileError> {
let isa = &*self.isa;
let value_size = mem::size_of::<u128>();
let pointer_type = isa.pointer_type();
// The wasm signature we're calling in this trampoline has the actual
// ABI of the function signature described by `ty`
let wasm_signature = indirect_signature(isa, ty);
// The host signature has the `VMTrampoline` signature where the ABI is
// fixed.
let mut host_signature = blank_sig(isa, wasmtime_call_conv(isa));
host_signature.params.push(ir::AbiParam::new(pointer_type));
host_signature.params.push(ir::AbiParam::new(pointer_type));
let mut func_translator = self.take_translator();
let mut context = Context::new();
context.func = ir::Function::with_name_signature(ExternalName::user(0, 0), host_signature);
// This trampoline will load all the parameters from the `values_vec`
// that is passed in and then call the real function (also passed
// indirectly) with the specified ABI.
//
// All the results are then stored into the same `values_vec`.
let mut builder = FunctionBuilder::new(&mut context.func, func_translator.context());
let block0 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
builder.seal_block(block0);
let (vmctx_ptr_val, caller_vmctx_ptr_val, callee_value, values_vec_ptr_val) = {
let params = builder.func.dfg.block_params(block0);
(params[0], params[1], params[2], params[3])
};
// Load the argument values out of `values_vec`.
let mflags = ir::MemFlags::trusted();
let callee_args = wasm_signature
.params
.iter()
.enumerate()
.map(|(i, r)| {
match i {
0 => vmctx_ptr_val,
1 => caller_vmctx_ptr_val,
_ =>
// i - 2 because vmctx and caller vmctx aren't passed through `values_vec`.
{
builder.ins().load(
r.value_type,
mflags,
values_vec_ptr_val,
((i - 2) * value_size) as i32,
)
}
}
})
.collect::<Vec<_>>();
// Call the indirect function pointer we were given
let new_sig = builder.import_signature(wasm_signature);
let call = builder
.ins()
.call_indirect(new_sig, callee_value, &callee_args);
let results = builder.func.dfg.inst_results(call).to_vec();
// Store the return values into `values_vec`.
let mflags = ir::MemFlags::trusted();
for (i, r) in results.iter().enumerate() {
builder
.ins()
.store(mflags, *r, values_vec_ptr_val, (i * value_size) as i32);
}
builder.ins().return_(&[]);
builder.finalize();
let func = self.finish_trampoline(context, isa)?;
self.save_translator(func_translator);
Ok(func)
}
fn wasm_to_host_trampoline(
&self,
ty: &WasmFuncType,
host_fn: usize,
) -> Result<CompiledFunction, CompileError> {
let isa = &*self.isa;
let pointer_type = isa.pointer_type();
let wasm_signature = indirect_signature(isa, ty);
// The host signature has an added parameter for the `values_vec` input
// and output.
let mut host_signature = blank_sig(isa, wasmtime_call_conv(isa));
host_signature.params.push(ir::AbiParam::new(pointer_type));
// Compute the size of the values vector. The vmctx and caller vmctx are passed separately.
let value_size = mem::size_of::<u128>();
let values_vec_len = (value_size * cmp::max(ty.params.len(), ty.returns.len())) as u32;
let mut context = Context::new();
context.func =
ir::Function::with_name_signature(ir::ExternalName::user(0, 0), wasm_signature);
let ss = context.func.create_stack_slot(ir::StackSlotData::new(
ir::StackSlotKind::ExplicitSlot,
values_vec_len,
));
let mut func_translator = self.take_translator();
let mut builder = FunctionBuilder::new(&mut context.func, func_translator.context());
let block0 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
builder.seal_block(block0);
let values_vec_ptr_val = builder.ins().stack_addr(pointer_type, ss, 0);
let mflags = MemFlags::trusted();
for i in 0..ty.params.len() {
let val = builder.func.dfg.block_params(block0)[i + 2];
builder
.ins()
.store(mflags, val, values_vec_ptr_val, (i * value_size) as i32);
}
let block_params = builder.func.dfg.block_params(block0);
let vmctx_ptr_val = block_params[0];
let caller_vmctx_ptr_val = block_params[1];
let callee_args = vec![vmctx_ptr_val, caller_vmctx_ptr_val, values_vec_ptr_val];
let new_sig = builder.import_signature(host_signature);
let callee_value = builder.ins().iconst(pointer_type, host_fn as i64);
builder
.ins()
.call_indirect(new_sig, callee_value, &callee_args);
let mflags = MemFlags::trusted();
let mut results = Vec::new();
for (i, r) in ty.returns.iter().enumerate() {
let load = builder.ins().load(
value_type(isa, *r),
mflags,
values_vec_ptr_val,
(i * value_size) as i32,
);
results.push(load);
}
builder.ins().return_(&results);
builder.finalize();
let func = self.finish_trampoline(context, isa)?;
self.save_translator(func_translator);
Ok(func)
}
fn emit_dwarf(
&self,
debuginfo_data: &DebugInfoData,
funcs: &CompiledFunctions,
memory_offset: &ModuleMemoryOffset,
) -> Result<Vec<DwarfSection>> {
wasmtime_debug::emit_dwarf(&*self.isa, debuginfo_data, funcs, memory_offset)
}
fn triple(&self) -> &target_lexicon::Triple {
self.isa.triple()
}
fn create_systemv_cie(&self) -> Option<gimli::write::CommonInformationEntry> {
self.isa.create_systemv_cie()
}
fn flags(&self) -> HashMap<String, FlagValue> {
self.isa
.flags()
.iter()
.map(|val| (val.name.to_string(), to_flag_value(&val)))
.collect()
}
fn isa_flags(&self) -> HashMap<String, FlagValue> {
self.isa
.isa_flags()
.iter()
.map(|val| (val.name.to_string(), to_flag_value(val)))
.collect()
}
}
fn to_flag_value(v: &settings::Value) -> FlagValue {
match v.kind() {
settings::SettingKind::Enum => FlagValue::Enum(v.as_enum().unwrap().into()),
settings::SettingKind::Num => FlagValue::Num(v.as_num().unwrap()),
settings::SettingKind::Bool => FlagValue::Bool(v.as_bool().unwrap()),
settings::SettingKind::Preset => unreachable!(),
}
}
impl Compiler {
fn finish_trampoline(
&self,
mut context: Context,
isa: &dyn TargetIsa,
) -> Result<CompiledFunction, CompileError> {
let mut code_buf = Vec::new();
let mut reloc_sink = TrampolineRelocSink::default();
let mut trap_sink = binemit::NullTrapSink {};
let mut stack_map_sink = binemit::NullStackMapSink {};
context
.compile_and_emit(
isa,
&mut code_buf,
&mut reloc_sink,
&mut trap_sink,
&mut stack_map_sink,
)
.map_err(|error| {
CompileError::Codegen(pretty_error(&context.func, Some(isa), error))
})?;
let unwind_info = context.create_unwind_info(isa).map_err(|error| {
CompileError::Codegen(pretty_error(&context.func, Some(isa), error))
})?;
Ok(CompiledFunction {
body: code_buf,
jt_offsets: context.func.jt_offsets,
unwind_info,
relocations: reloc_sink.relocs,
stack_maps: Default::default(),
stack_slots: Default::default(),
traps: Default::default(),
value_labels_ranges: Default::default(),
address_map: Default::default(),
})
}
}
// Collects an iterator of `InstructionAddressMap` into a `Vec` for insertion
// into a `FunctionAddressMap`. This will automatically coalesce adjacent
// instructions which map to the same original source position.
fn collect_address_maps(
code_size: u32,
iter: impl IntoIterator<Item = (ir::SourceLoc, u32, u32)>,
) -> Vec<InstructionAddressMap> {
let mut iter = iter.into_iter();
let (mut cur_loc, mut cur_offset, mut cur_len) = match iter.next() {
Some(i) => i,
None => return Vec::new(),
};
let mut ret = Vec::new();
for (loc, offset, len) in iter {
// If this instruction is adjacent to the previous and has the same
// source location then we can "coalesce" it with the current
// instruction.
if cur_offset + cur_len == offset && loc == cur_loc {
cur_len += len;
continue;
}
// Push an entry for the previous source item.
ret.push(InstructionAddressMap {
srcloc: cur_loc,
code_offset: cur_offset,
});
// And push a "dummy" entry if necessary to cover the span of ranges,
// if any, between the previous source offset and this one.
if cur_offset + cur_len != offset {
ret.push(InstructionAddressMap {
srcloc: ir::SourceLoc::default(),
code_offset: cur_offset + cur_len,
});
}
// Update our current location to get extended later or pushed on at
// the end.
cur_loc = loc;
cur_offset = offset;
cur_len = len;
}
ret.push(InstructionAddressMap {
srcloc: cur_loc,
code_offset: cur_offset,
});
if cur_offset + cur_len != code_size {
ret.push(InstructionAddressMap {
srcloc: ir::SourceLoc::default(),
code_offset: cur_offset + cur_len,
});
}
return ret;
}
/// Implementation of a relocation sink that just saves all the information for later
struct RelocSink {
/// Current function index.
func_index: FuncIndex,
/// Relocations recorded for the function.
func_relocs: Vec<Relocation>,
}
impl binemit::RelocSink for RelocSink {
fn reloc_external(
&mut self,
offset: binemit::CodeOffset,
_srcloc: ir::SourceLoc,
reloc: binemit::Reloc,
name: &ExternalName,
addend: binemit::Addend,
) {
let reloc_target = if let ExternalName::User { namespace, index } = *name {
debug_assert_eq!(namespace, 0);
RelocationTarget::UserFunc(FuncIndex::from_u32(index))
} else if let ExternalName::LibCall(libcall) = *name {
RelocationTarget::LibCall(libcall)
} else {
panic!("unrecognized external name")
};
self.func_relocs.push(Relocation {
reloc,
reloc_target,
offset,
addend,
});
}
fn reloc_constant(
&mut self,
_code_offset: binemit::CodeOffset,
_reloc: binemit::Reloc,
_constant_offset: ir::ConstantOffset,
) {
// Do nothing for now: cranelift emits constant data after the function code and also emits
// function code with correct relative offsets to the constant data.
}
fn reloc_jt(&mut self, offset: binemit::CodeOffset, reloc: binemit::Reloc, jt: ir::JumpTable) {
self.func_relocs.push(Relocation {
reloc,
reloc_target: RelocationTarget::JumpTable(self.func_index, jt),
offset,
addend: 0,
});
}
}
impl RelocSink {
/// Return a new `RelocSink` instance.
fn new(func_index: FuncIndex) -> Self {
Self {
func_index,
func_relocs: Vec::new(),
}
}
}
/// Implementation of a trap sink that simply stores all trap info in-memory
#[derive(Default)]
struct TrapSink {
/// The in-memory vector of trap info
traps: Vec<TrapInformation>,
}
impl TrapSink {
/// Create a new `TrapSink`
fn new() -> Self {
Self::default()
}
}
impl binemit::TrapSink for TrapSink {
fn trap(
&mut self,
code_offset: binemit::CodeOffset,
_source_loc: ir::SourceLoc,
trap_code: ir::TrapCode,
) {
self.traps.push(TrapInformation {
code_offset,
trap_code,
});
}
}
#[derive(Default)]
struct StackMapSink {
infos: Vec<StackMapInformation>,
}
impl binemit::StackMapSink for StackMapSink {
fn add_stack_map(&mut self, code_offset: binemit::CodeOffset, stack_map: binemit::StackMap) {
self.infos.push(StackMapInformation {
code_offset,
stack_map,
});
}
}
impl StackMapSink {
fn finish(mut self) -> Vec<StackMapInformation> {
self.infos.sort_unstable_by_key(|info| info.code_offset);
self.infos
}
}
/// We don't expect trampoline compilation to produce many relocations, so
/// this `RelocSink` just asserts that it doesn't recieve most of them, but
/// handles libcall ones.
#[derive(Default)]
struct TrampolineRelocSink {
relocs: Vec<Relocation>,
}
impl binemit::RelocSink for TrampolineRelocSink {
fn reloc_external(
&mut self,
offset: binemit::CodeOffset,
_srcloc: ir::SourceLoc,
reloc: binemit::Reloc,
name: &ir::ExternalName,
addend: binemit::Addend,
) {
let reloc_target = if let ir::ExternalName::LibCall(libcall) = *name {
RelocationTarget::LibCall(libcall)
} else {
panic!("unrecognized external name")
};
self.relocs.push(Relocation {
reloc,
reloc_target,
offset,
addend,
});
}
fn reloc_constant(
&mut self,
_code_offset: binemit::CodeOffset,
_reloc: binemit::Reloc,
_constant_offset: ir::ConstantOffset,
) {
panic!("trampoline compilation should not produce constant relocs");
}
fn reloc_jt(
&mut self,
_offset: binemit::CodeOffset,
_reloc: binemit::Reloc,
_jt: ir::JumpTable,
) {
panic!("trampoline compilation should not produce jump table relocs");
}
}

View File

@@ -1267,7 +1267,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
Ok(GlobalVariable::Memory {
gv,
offset: offset.into(),
ty: self.module.globals[index].ty,
ty: super::value_type(self.isa, self.module.globals[index].wasm_ty),
})
}

View File

@@ -1,7 +1,7 @@
//! Support for compiling with Cranelift.
//!
//! This crate provides an implementation of [`Compiler`] in the form of
//! [`Cranelift`].
//! This crate provides an implementation of the `wasmtime_environ::Compiler`
//! and `wasmtime_environ::CompilerBuilder` traits.
// # How does Wasmtime prevent stack overflow?
//
@@ -88,573 +88,23 @@
// 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::func_environ::{get_func_name, FuncEnvironment};
use cranelift_codegen::ir::{self, ExternalName, InstBuilder, MemFlags};
use cranelift_codegen::ir;
use cranelift_codegen::isa::{CallConv, TargetIsa};
use cranelift_codegen::print_errors::pretty_error;
use cranelift_codegen::MachSrcLoc;
use cranelift_codegen::{binemit, isa, Context};
use cranelift_frontend::FunctionBuilder;
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, FuncTranslator, WasmFuncType, WasmType};
use std::cmp;
use std::convert::TryFrom;
use std::mem;
use std::sync::Mutex;
use cranelift_wasm::{FuncIndex, WasmFuncType, WasmType};
use target_lexicon::CallingConvention;
use wasmtime_environ::{
CompileError, CompiledFunction, Compiler, FunctionAddressMap, FunctionBodyData,
InstructionAddressMap, Module, ModuleTranslation, Relocation, RelocationTarget,
StackMapInformation, TrapInformation, Tunables, TypeTables,
};
use wasmtime_environ::{Module, TypeTables};
pub use builder::builder;
mod builder;
mod compiler;
mod func_environ;
/// Implementation of a relocation sink that just saves all the information for later
struct RelocSink {
/// Current function index.
func_index: FuncIndex,
/// Relocations recorded for the function.
func_relocs: Vec<Relocation>,
}
impl binemit::RelocSink for RelocSink {
fn reloc_external(
&mut self,
offset: binemit::CodeOffset,
_srcloc: ir::SourceLoc,
reloc: binemit::Reloc,
name: &ExternalName,
addend: binemit::Addend,
) {
let reloc_target = if let ExternalName::User { namespace, index } = *name {
debug_assert_eq!(namespace, 0);
RelocationTarget::UserFunc(FuncIndex::from_u32(index))
} else if let ExternalName::LibCall(libcall) = *name {
RelocationTarget::LibCall(libcall)
} else {
panic!("unrecognized external name")
};
self.func_relocs.push(Relocation {
reloc,
reloc_target,
offset,
addend,
});
}
fn reloc_constant(
&mut self,
_code_offset: binemit::CodeOffset,
_reloc: binemit::Reloc,
_constant_offset: ir::ConstantOffset,
) {
// Do nothing for now: cranelift emits constant data after the function code and also emits
// function code with correct relative offsets to the constant data.
}
fn reloc_jt(&mut self, offset: binemit::CodeOffset, reloc: binemit::Reloc, jt: ir::JumpTable) {
self.func_relocs.push(Relocation {
reloc,
reloc_target: RelocationTarget::JumpTable(self.func_index, jt),
offset,
addend: 0,
});
}
}
impl RelocSink {
/// Return a new `RelocSink` instance.
fn new(func_index: FuncIndex) -> Self {
Self {
func_index,
func_relocs: Vec::new(),
}
}
}
/// Implementation of a trap sink that simply stores all trap info in-memory
#[derive(Default)]
struct TrapSink {
/// The in-memory vector of trap info
traps: Vec<TrapInformation>,
}
impl TrapSink {
/// Create a new `TrapSink`
fn new() -> Self {
Self::default()
}
}
impl binemit::TrapSink for TrapSink {
fn trap(
&mut self,
code_offset: binemit::CodeOffset,
_source_loc: ir::SourceLoc,
trap_code: ir::TrapCode,
) {
self.traps.push(TrapInformation {
code_offset,
trap_code,
});
}
}
#[derive(Default)]
struct StackMapSink {
infos: Vec<StackMapInformation>,
}
impl binemit::StackMapSink for StackMapSink {
fn add_stack_map(&mut self, code_offset: binemit::CodeOffset, stack_map: binemit::StackMap) {
self.infos.push(StackMapInformation {
code_offset,
stack_map,
});
}
}
impl StackMapSink {
fn finish(mut self) -> Vec<StackMapInformation> {
self.infos.sort_unstable_by_key(|info| info.code_offset);
self.infos
}
}
fn get_function_address_map<'data>(
context: &Context,
data: &FunctionBodyData<'data>,
body_len: u32,
isa: &dyn isa::TargetIsa,
) -> FunctionAddressMap {
// Generate artificial srcloc for function start/end to identify boundary
// within module.
let data = data.body.get_binary_reader();
let offset = data.original_position();
let len = data.bytes_remaining();
assert!((offset + len) <= u32::max_value() as usize);
let start_srcloc = ir::SourceLoc::new(offset as u32);
let end_srcloc = ir::SourceLoc::new((offset + len) as u32);
let instructions = if let Some(ref mcr) = &context.mach_compile_result {
// New-style backend: we have a `MachCompileResult` that will give us `MachSrcLoc` mapping
// tuples.
collect_address_maps(
body_len,
mcr.buffer
.get_srclocs_sorted()
.into_iter()
.map(|&MachSrcLoc { start, end, loc }| (loc, start, (end - start))),
)
} else {
// Old-style backend: we need to traverse the instruction/encoding info in the function.
let func = &context.func;
let mut blocks = func.layout.blocks().collect::<Vec<_>>();
blocks.sort_by_key(|block| func.offsets[*block]); // Ensure inst offsets always increase
let encinfo = isa.encoding_info();
collect_address_maps(
body_len,
blocks
.into_iter()
.flat_map(|block| func.inst_offsets(block, &encinfo))
.map(|(offset, inst, size)| (func.srclocs[inst], offset, size)),
)
};
FunctionAddressMap {
instructions: instructions.into(),
start_srcloc,
end_srcloc,
body_offset: 0,
body_len,
}
}
// Collects an iterator of `InstructionAddressMap` into a `Vec` for insertion
// into a `FunctionAddressMap`. This will automatically coalesce adjacent
// instructions which map to the same original source position.
fn collect_address_maps(
code_size: u32,
iter: impl IntoIterator<Item = (ir::SourceLoc, u32, u32)>,
) -> Vec<InstructionAddressMap> {
let mut iter = iter.into_iter();
let (mut cur_loc, mut cur_offset, mut cur_len) = match iter.next() {
Some(i) => i,
None => return Vec::new(),
};
let mut ret = Vec::new();
for (loc, offset, len) in iter {
// If this instruction is adjacent to the previous and has the same
// source location then we can "coalesce" it with the current
// instruction.
if cur_offset + cur_len == offset && loc == cur_loc {
cur_len += len;
continue;
}
// Push an entry for the previous source item.
ret.push(InstructionAddressMap {
srcloc: cur_loc,
code_offset: cur_offset,
});
// And push a "dummy" entry if necessary to cover the span of ranges,
// if any, between the previous source offset and this one.
if cur_offset + cur_len != offset {
ret.push(InstructionAddressMap {
srcloc: ir::SourceLoc::default(),
code_offset: cur_offset + cur_len,
});
}
// Update our current location to get extended later or pushed on at
// the end.
cur_loc = loc;
cur_offset = offset;
cur_len = len;
}
ret.push(InstructionAddressMap {
srcloc: cur_loc,
code_offset: cur_offset,
});
if cur_offset + cur_len != code_size {
ret.push(InstructionAddressMap {
srcloc: ir::SourceLoc::default(),
code_offset: cur_offset + cur_len,
});
}
return ret;
}
/// A compiler that compiles a WebAssembly module with Cranelift, translating the Wasm to Cranelift IR,
/// optimizing it and then translating to assembly.
#[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 {
fn compile_function(
&self,
translation: &ModuleTranslation<'_>,
func_index: DefinedFuncIndex,
mut input: FunctionBodyData<'_>,
isa: &dyn isa::TargetIsa,
tunables: &Tunables,
types: &TypeTables,
) -> Result<CompiledFunction, CompileError> {
let module = &translation.module;
let func_index = module.func_index(func_index);
let mut context = Context::new();
context.func.name = get_func_name(func_index);
context.func.signature = func_signature(isa, module, types, func_index);
if tunables.generate_native_debuginfo {
context.func.collect_debug_info();
}
let mut func_env = FuncEnvironment::new(isa, module, types, tunables);
// We use these as constant offsets below in
// `stack_limit_from_arguments`, so assert their values here. This
// allows the closure below to get coerced to a function pointer, as
// needed by `ir::Function`.
//
// Otherwise our stack limit is specially calculated from the vmctx
// argument, where we need to load the `*const VMInterrupts`
// pointer, and then from that pointer we need to load the stack
// limit itself. Note that manual register allocation is needed here
// too due to how late in the process this codegen happens.
//
// For more information about interrupts and stack checks, see the
// top of this file.
let vmctx = context
.func
.create_global_value(ir::GlobalValueData::VMContext);
let interrupts_ptr = context.func.create_global_value(ir::GlobalValueData::Load {
base: vmctx,
offset: i32::try_from(func_env.offsets.vmctx_interrupts())
.unwrap()
.into(),
global_type: isa.pointer_type(),
readonly: true,
});
let stack_limit = context.func.create_global_value(ir::GlobalValueData::Load {
base: interrupts_ptr,
offset: i32::try_from(func_env.offsets.vminterrupts_stack_limit())
.unwrap()
.into(),
global_type: isa.pointer_type(),
readonly: false,
});
context.func.stack_limit = Some(stack_limit);
let mut func_translator = self.take_translator();
func_translator.translate_body(
&mut input.validator,
input.body.clone(),
&mut context.func,
&mut func_env,
)?;
self.save_translator(func_translator);
let mut code_buf: Vec<u8> = Vec::new();
let mut reloc_sink = RelocSink::new(func_index);
let mut trap_sink = TrapSink::new();
let mut stack_map_sink = StackMapSink::default();
context
.compile_and_emit(
isa,
&mut code_buf,
&mut reloc_sink,
&mut trap_sink,
&mut stack_map_sink,
)
.map_err(|error| {
CompileError::Codegen(pretty_error(&context.func, Some(isa), error))
})?;
let unwind_info = context.create_unwind_info(isa).map_err(|error| {
CompileError::Codegen(pretty_error(&context.func, Some(isa), error))
})?;
let address_transform =
get_function_address_map(&context, &input, code_buf.len() as u32, isa);
let ranges = if tunables.generate_native_debuginfo {
let ranges = context.build_value_labels_ranges(isa).map_err(|error| {
CompileError::Codegen(pretty_error(&context.func, Some(isa), error))
})?;
Some(ranges)
} else {
None
};
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_maps: stack_map_sink.finish(),
})
}
fn host_to_wasm_trampoline(
&self,
isa: &dyn isa::TargetIsa,
ty: &WasmFuncType,
) -> Result<CompiledFunction, CompileError> {
let value_size = mem::size_of::<u128>();
let pointer_type = isa.pointer_type();
// The wasm signature we're calling in this trampoline has the actual
// ABI of the function signature described by `ty`
let wasm_signature = indirect_signature(isa, ty);
// The host signature has the `VMTrampoline` signature where the ABI is
// fixed.
let mut host_signature = blank_sig(isa, wasmtime_call_conv(isa));
host_signature.params.push(ir::AbiParam::new(pointer_type));
host_signature.params.push(ir::AbiParam::new(pointer_type));
let mut func_translator = self.take_translator();
let mut context = Context::new();
context.func = ir::Function::with_name_signature(ExternalName::user(0, 0), host_signature);
// This trampoline will load all the parameters from the `values_vec`
// that is passed in and then call the real function (also passed
// indirectly) with the specified ABI.
//
// All the results are then stored into the same `values_vec`.
let mut builder = FunctionBuilder::new(&mut context.func, func_translator.context());
let block0 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
builder.seal_block(block0);
let (vmctx_ptr_val, caller_vmctx_ptr_val, callee_value, values_vec_ptr_val) = {
let params = builder.func.dfg.block_params(block0);
(params[0], params[1], params[2], params[3])
};
// Load the argument values out of `values_vec`.
let mflags = ir::MemFlags::trusted();
let callee_args = wasm_signature
.params
.iter()
.enumerate()
.map(|(i, r)| {
match i {
0 => vmctx_ptr_val,
1 => caller_vmctx_ptr_val,
_ =>
// i - 2 because vmctx and caller vmctx aren't passed through `values_vec`.
{
builder.ins().load(
r.value_type,
mflags,
values_vec_ptr_val,
((i - 2) * value_size) as i32,
)
}
}
})
.collect::<Vec<_>>();
// Call the indirect function pointer we were given
let new_sig = builder.import_signature(wasm_signature);
let call = builder
.ins()
.call_indirect(new_sig, callee_value, &callee_args);
let results = builder.func.dfg.inst_results(call).to_vec();
// Store the return values into `values_vec`.
let mflags = ir::MemFlags::trusted();
for (i, r) in results.iter().enumerate() {
builder
.ins()
.store(mflags, *r, values_vec_ptr_val, (i * value_size) as i32);
}
builder.ins().return_(&[]);
builder.finalize();
let func = self.finish_trampoline(context, isa)?;
self.save_translator(func_translator);
Ok(func)
}
fn wasm_to_host_trampoline(
&self,
isa: &dyn isa::TargetIsa,
ty: &WasmFuncType,
host_fn: usize,
) -> Result<CompiledFunction, CompileError> {
let pointer_type = isa.pointer_type();
let wasm_signature = indirect_signature(isa, ty);
// The host signature has an added parameter for the `values_vec` input
// and output.
let mut host_signature = blank_sig(isa, wasmtime_call_conv(isa));
host_signature.params.push(ir::AbiParam::new(pointer_type));
// Compute the size of the values vector. The vmctx and caller vmctx are passed separately.
let value_size = mem::size_of::<u128>();
let values_vec_len = (value_size * cmp::max(ty.params.len(), ty.returns.len())) as u32;
let mut context = Context::new();
context.func =
ir::Function::with_name_signature(ir::ExternalName::user(0, 0), wasm_signature);
let ss = context.func.create_stack_slot(ir::StackSlotData::new(
ir::StackSlotKind::ExplicitSlot,
values_vec_len,
));
let mut func_translator = self.take_translator();
let mut builder = FunctionBuilder::new(&mut context.func, func_translator.context());
let block0 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
builder.seal_block(block0);
let values_vec_ptr_val = builder.ins().stack_addr(pointer_type, ss, 0);
let mflags = MemFlags::trusted();
for i in 0..ty.params.len() {
let val = builder.func.dfg.block_params(block0)[i + 2];
builder
.ins()
.store(mflags, val, values_vec_ptr_val, (i * value_size) as i32);
}
let block_params = builder.func.dfg.block_params(block0);
let vmctx_ptr_val = block_params[0];
let caller_vmctx_ptr_val = block_params[1];
let callee_args = vec![vmctx_ptr_val, caller_vmctx_ptr_val, values_vec_ptr_val];
let new_sig = builder.import_signature(host_signature);
let callee_value = builder.ins().iconst(pointer_type, host_fn as i64);
builder
.ins()
.call_indirect(new_sig, callee_value, &callee_args);
let mflags = MemFlags::trusted();
let mut results = Vec::new();
for (i, r) in ty.returns.iter().enumerate() {
let load = builder.ins().load(
value_type(isa, *r),
mflags,
values_vec_ptr_val,
(i * value_size) as i32,
);
results.push(load);
}
builder.ins().return_(&results);
builder.finalize();
let func = self.finish_trampoline(context, isa)?;
self.save_translator(func_translator);
Ok(func)
}
}
impl Cranelift {
fn finish_trampoline(
&self,
mut context: Context,
isa: &dyn TargetIsa,
) -> Result<CompiledFunction, CompileError> {
let mut code_buf = Vec::new();
let mut reloc_sink = TrampolineRelocSink::default();
let mut trap_sink = binemit::NullTrapSink {};
let mut stack_map_sink = binemit::NullStackMapSink {};
context
.compile_and_emit(
isa,
&mut code_buf,
&mut reloc_sink,
&mut trap_sink,
&mut stack_map_sink,
)
.map_err(|error| {
CompileError::Codegen(pretty_error(&context.func, Some(isa), error))
})?;
let unwind_info = context.create_unwind_info(isa).map_err(|error| {
CompileError::Codegen(pretty_error(&context.func, Some(isa), error))
})?;
Ok(CompiledFunction {
body: code_buf,
jt_offsets: context.func.jt_offsets,
unwind_info,
relocations: reloc_sink.relocs,
stack_maps: Default::default(),
stack_slots: Default::default(),
traps: Default::default(),
value_labels_ranges: Default::default(),
address_map: Default::default(),
})
}
}
pub fn blank_sig(isa: &dyn TargetIsa, call_conv: CallConv) -> ir::Signature {
/// Creates a new cranelift `Signature` with no wasm params/results for the
/// given calling convention.
///
/// This will add the default vmctx/etc parameters to the signature returned.
fn blank_sig(isa: &dyn TargetIsa, call_conv: CallConv) -> ir::Signature {
let pointer_type = isa.pointer_type();
let mut sig = ir::Signature::new(call_conv);
// Add the caller/callee `vmctx` parameters.
@@ -666,7 +116,10 @@ pub fn blank_sig(isa: &dyn TargetIsa, call_conv: CallConv) -> ir::Signature {
return sig;
}
pub fn wasmtime_call_conv(isa: &dyn TargetIsa) -> CallConv {
/// Returns the default calling convention for the `isa` provided.
///
/// Note that this calling convention is used for exported functions.
fn wasmtime_call_conv(isa: &dyn TargetIsa) -> CallConv {
match isa.triple().default_calling_convention() {
Ok(CallingConvention::AppleAarch64) => CallConv::WasmtimeAppleAarch64,
Ok(CallingConvention::SystemV) | Err(()) => CallConv::WasmtimeSystemV,
@@ -675,12 +128,18 @@ pub fn wasmtime_call_conv(isa: &dyn TargetIsa) -> CallConv {
}
}
pub fn push_types(isa: &dyn TargetIsa, sig: &mut ir::Signature, wasm: &WasmFuncType) {
/// Appends the types of the `wasm` function signature into the `sig` signature
/// provided.
///
/// Typically the `sig` signature will have been created from [`blank_sig`]
/// above.
fn push_types(isa: &dyn TargetIsa, sig: &mut ir::Signature, wasm: &WasmFuncType) {
let cvt = |ty: &WasmType| ir::AbiParam::new(value_type(isa, *ty));
sig.params.extend(wasm.params.iter().map(&cvt));
sig.returns.extend(wasm.returns.iter().map(&cvt));
}
/// Returns the corresponding cranelift type for the provided wasm type.
fn value_type(isa: &dyn TargetIsa, ty: WasmType) -> ir::types::Type {
match ty {
WasmType::I32 => ir::types::I32,
@@ -695,13 +154,25 @@ fn value_type(isa: &dyn TargetIsa, ty: WasmType) -> ir::types::Type {
}
}
pub fn indirect_signature(isa: &dyn TargetIsa, wasm: &WasmFuncType) -> ir::Signature {
/// Returns a cranelift signature suitable to indirectly call the wasm signature
/// specified by `wasm`.
///
/// This will implicitly use the default calling convention for `isa` since to
/// indirectly call a wasm function it must be possibly exported somehow (e.g.
/// this assumes the function target to call doesn't use the "fast" calling
/// convention).
fn indirect_signature(isa: &dyn TargetIsa, wasm: &WasmFuncType) -> ir::Signature {
let mut sig = blank_sig(isa, wasmtime_call_conv(isa));
push_types(isa, &mut sig, wasm);
return sig;
}
pub fn func_signature(
/// Returns the cranelift fucntion signature of the function specified.
///
/// Note that this will determine the calling convention for the function, and
/// namely includes an optimization where functions never exported from a module
/// use a custom theoretically faster calling convention instead of the default.
fn func_signature(
isa: &dyn TargetIsa,
module: &Module,
types: &TypeTables,
@@ -727,50 +198,3 @@ pub fn func_signature(
);
return sig;
}
/// We don't expect trampoline compilation to produce many relocations, so
/// this `RelocSink` just asserts that it doesn't recieve most of them, but
/// handles libcall ones.
#[derive(Default)]
struct TrampolineRelocSink {
relocs: Vec<Relocation>,
}
impl binemit::RelocSink for TrampolineRelocSink {
fn reloc_external(
&mut self,
offset: binemit::CodeOffset,
_srcloc: ir::SourceLoc,
reloc: binemit::Reloc,
name: &ir::ExternalName,
addend: binemit::Addend,
) {
let reloc_target = if let ir::ExternalName::LibCall(libcall) = *name {
RelocationTarget::LibCall(libcall)
} else {
panic!("unrecognized external name")
};
self.relocs.push(Relocation {
reloc,
reloc_target,
offset,
addend,
});
}
fn reloc_constant(
&mut self,
_code_offset: binemit::CodeOffset,
_reloc: binemit::Reloc,
_constant_offset: ir::ConstantOffset,
) {
panic!("trampoline compilation should not produce constant relocs");
}
fn reloc_jt(
&mut self,
_offset: binemit::CodeOffset,
_reloc: binemit::Reloc,
_jt: ir::JumpTable,
) {
panic!("trampoline compilation should not produce jump table relocs");
}
}

View File

@@ -2,221 +2,8 @@
#![allow(clippy::cast_ptr_alignment)]
use anyhow::{bail, ensure, Error};
use object::endian::{BigEndian, Endian, Endianness, LittleEndian};
use object::{RelocationEncoding, RelocationKind};
use std::collections::HashMap;
pub use crate::write_debuginfo::{emit_dwarf, DwarfSection, DwarfSectionRelocTarget};
mod gc;
mod transform;
mod write_debuginfo;
pub fn create_gdbjit_image(
mut bytes: Vec<u8>,
code_region: (*const u8, usize),
defined_funcs_offset: usize,
funcs: &[*const u8],
) -> Result<Vec<u8>, Error> {
let e = ensure_supported_elf_format(&bytes)?;
// patch relocs
relocate_dwarf_sections(&bytes, defined_funcs_offset, funcs)?;
// elf is still missing details...
match e {
Endianness::Little => {
convert_object_elf_to_loadable_file::<LittleEndian>(&mut bytes, code_region)
}
Endianness::Big => {
convert_object_elf_to_loadable_file::<BigEndian>(&mut bytes, code_region)
}
}
// let mut file = ::std::fs::File::create(::std::path::Path::new("test.o")).expect("file");
// ::std::io::Write::write_all(&mut file, &bytes).expect("write");
Ok(bytes)
}
fn relocate_dwarf_sections(
bytes: &[u8],
defined_funcs_offset: usize,
funcs: &[*const u8],
) -> Result<(), Error> {
use object::read::{File, Object, ObjectSection, ObjectSymbol, RelocationTarget};
let obj = File::parse(bytes)?;
let mut func_symbols = HashMap::new();
for sym in obj.symbols() {
match (sym.name(), sym.section_index()) {
(Ok(name), Some(_section_index)) if name.starts_with("_wasm_function_") => {
let index = name["_wasm_function_".len()..].parse::<usize>()?;
let data = funcs[index - defined_funcs_offset];
func_symbols.insert(sym.index(), data);
}
_ => (),
}
}
for section in obj.sections() {
for (off, r) in section.relocations() {
if r.kind() != RelocationKind::Absolute
|| r.encoding() != RelocationEncoding::Generic
|| r.size() != 64
{
continue;
}
let data = match r.target() {
RelocationTarget::Symbol(ref index) => func_symbols.get(index),
_ => None,
};
let data: *const u8 = match data {
Some(data) => *data,
None => {
continue;
}
};
let target = (data as u64).wrapping_add(r.addend() as u64);
let entry_ptr = section.data_range(off, 8).unwrap().unwrap().as_ptr();
unsafe {
std::ptr::write(entry_ptr as *mut u64, target);
}
}
}
Ok(())
}
fn ensure_supported_elf_format(bytes: &[u8]) -> Result<Endianness, Error> {
use object::elf::*;
use object::read::elf::*;
use std::mem::size_of;
let kind = match object::FileKind::parse(bytes) {
Ok(file) => file,
Err(err) => {
bail!("Failed to parse file: {}", err);
}
};
let header = match kind {
object::FileKind::Elf64 => match object::elf::FileHeader64::<Endianness>::parse(bytes) {
Ok(header) => header,
Err(err) => {
bail!("Unsupported ELF file: {}", err);
}
},
_ => {
bail!("only 64-bit ELF files currently supported")
}
};
let e = header.endian().unwrap();
match header.e_machine.get(e) {
EM_AARCH64 => (),
EM_X86_64 => (),
EM_S390 => (),
machine => {
bail!("Unsupported ELF target machine: {:x}", machine);
}
}
ensure!(
header.e_phoff.get(e) == 0 && header.e_phnum.get(e) == 0,
"program header table is empty"
);
let e_shentsize = header.e_shentsize.get(e);
let req_shentsize = match e {
Endianness::Little => size_of::<SectionHeader64<LittleEndian>>(),
Endianness::Big => size_of::<SectionHeader64<BigEndian>>(),
};
ensure!(e_shentsize as usize == req_shentsize, "size of sh");
Ok(e)
}
fn convert_object_elf_to_loadable_file<E: Endian>(
bytes: &mut Vec<u8>,
code_region: (*const u8, usize),
) {
use object::elf::*;
use std::ffi::CStr;
use std::mem::size_of;
use std::os::raw::c_char;
let e = E::default();
let header: &FileHeader64<E> = unsafe { &*(bytes.as_mut_ptr() as *const FileHeader64<_>) };
let e_shentsize = header.e_shentsize.get(e);
let e_shoff = header.e_shoff.get(e);
let e_shnum = header.e_shnum.get(e);
let mut shstrtab_off = 0;
for i in 0..e_shnum {
let off = e_shoff as isize + i as isize * e_shentsize as isize;
let section: &SectionHeader64<E> =
unsafe { &*(bytes.as_ptr().offset(off) as *const SectionHeader64<_>) };
if section.sh_type.get(e) != SHT_STRTAB {
continue;
}
shstrtab_off = section.sh_offset.get(e);
}
let mut segment: Option<_> = None;
for i in 0..e_shnum {
let off = e_shoff as isize + i as isize * e_shentsize as isize;
let section: &mut SectionHeader64<E> =
unsafe { &mut *(bytes.as_mut_ptr().offset(off) as *mut SectionHeader64<_>) };
if section.sh_type.get(e) != SHT_PROGBITS {
continue;
}
// It is a SHT_PROGBITS, but we need to check sh_name to ensure it is our function
let sh_name_off = section.sh_name.get(e);
let sh_name = unsafe {
CStr::from_ptr(
bytes
.as_ptr()
.offset((shstrtab_off + sh_name_off as u64) as isize)
as *const c_char,
)
.to_str()
.expect("name")
};
if sh_name != ".text" {
continue;
}
assert!(segment.is_none());
// Patch vaddr, and save file location and its size.
section.sh_addr.set(e, code_region.0 as u64);
let sh_offset = section.sh_offset.get(e);
let sh_size = section.sh_size.get(e);
segment = Some((sh_offset, sh_size));
}
// LLDB wants segment with virtual address set, placing them at the end of ELF.
let ph_off = bytes.len();
let e_phentsize = size_of::<ProgramHeader64<E>>();
let e_phnum = 1;
bytes.resize(ph_off + e_phentsize * e_phnum, 0);
if let Some((sh_offset, sh_size)) = segment {
let (v_offset, size) = code_region;
let program: &mut ProgramHeader64<E> =
unsafe { &mut *(bytes.as_ptr().add(ph_off) as *mut ProgramHeader64<_>) };
program.p_type.set(e, PT_LOAD);
program.p_offset.set(e, sh_offset);
program.p_vaddr.set(e, v_offset as u64);
program.p_paddr.set(e, v_offset as u64);
program.p_filesz.set(e, sh_size as u64);
program.p_memsz.set(e, size as u64);
} else {
unreachable!();
}
// It is somewhat loadable ELF file at this moment.
let header: &mut FileHeader64<E> =
unsafe { &mut *(bytes.as_mut_ptr() as *mut FileHeader64<_>) };
header.e_type.set(e, ET_DYN);
header.e_phoff.set(e, ph_off as u64);
header.e_phentsize.set(e, e_phentsize as u16);
header.e_phnum.set(e, e_phnum as u16);
}

View File

@@ -6,25 +6,7 @@ use wasmtime_environ::ir::Endianness;
use wasmtime_environ::isa::{unwind::UnwindInfo, TargetIsa};
use wasmtime_environ::{CompiledFunctions, DebugInfoData, ModuleMemoryOffset};
#[derive(Clone)]
pub enum DwarfSectionRelocTarget {
Func(usize),
Section(&'static str),
}
#[derive(Clone)]
pub struct DwarfSectionReloc {
pub target: DwarfSectionRelocTarget,
pub offset: u32,
pub addend: i32,
pub size: u8,
}
pub struct DwarfSection {
pub name: &'static str,
pub body: Vec<u8>,
pub relocs: Vec<DwarfSectionReloc>,
}
pub use wasmtime_environ::{DwarfSection, DwarfSectionReloc, DwarfSectionRelocTarget};
fn emit_dwarf_sections(
isa: &dyn TargetIsa,

View File

@@ -11,6 +11,7 @@ keywords = ["webassembly", "wasm"]
edition = "2018"
[dependencies]
anyhow = "1.0"
cranelift-codegen = { path = "../../cranelift/codegen", version = "0.76.0", features = ["enable-serde"] }
cranelift-entity = { path = "../../cranelift/entity", version = "0.76.0", features = ["enable-serde"] }
cranelift-wasm = { path = "../../cranelift/wasm", version = "0.76.0", features = ["enable-serde"] }
@@ -22,6 +23,7 @@ log = { version = "0.4.8", default-features = false }
more-asserts = "0.2.1"
cfg-if = "1.0"
gimli = "0.25.0"
target-lexicon = "0.12"
[badges]
maintenance = { status = "actively-developed" }

View File

@@ -2,10 +2,14 @@
//! module.
use crate::{FunctionAddressMap, FunctionBodyData, ModuleTranslation, Tunables, TypeTables};
use cranelift_codegen::{binemit, ir, isa, isa::unwind::UnwindInfo};
use anyhow::Result;
use cranelift_codegen::{binemit, ir, isa::unwind::UnwindInfo};
use cranelift_entity::PrimaryMap;
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, WasmError, WasmFuncType};
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt;
use thiserror::Error;
#[allow(missing_docs)]
@@ -93,37 +97,173 @@ pub enum CompileError {
DebugInfoNotSupported,
}
/// An implementation of a compiler from parsed WebAssembly module to native
/// code.
/// Abstract trait representing the ability to create a `Compiler` below.
///
/// This is used in Wasmtime to separate compiler implementations, currently
/// mostly used to separate Cranelift from Wasmtime itself.
pub trait CompilerBuilder: Send + Sync + fmt::Debug {
/// Like the `Clone` trait, but for the boxed trait object.
fn clone(&self) -> Box<dyn CompilerBuilder>;
/// Sets the target of compilation to the target specified.
fn target(&mut self, target: target_lexicon::Triple) -> Result<()>;
/// Returns the currently configured target triple that compilation will
/// produce artifacts for.
fn triple(&self) -> &target_lexicon::Triple;
/// Compiler-specific method to configure various settings in the compiler
/// itself.
///
/// This is expected to be defined per-compiler. Compilers should return
/// errors for unknown names/values.
fn set(&mut self, name: &str, val: &str) -> Result<()>;
/// Compiler-specific method for configuring settings.
///
/// Same as [`CompilerBuilder::set`] except for enabling boolean flags.
/// Currently cranelift uses this to sometimes enable a family of settings.
fn enable(&mut self, name: &str) -> Result<()>;
/// Returns a list of all possible settings that can be configured with
/// [`CompilerBuilder::set`] and [`CompilerBuilder::enable`].
fn settings(&self) -> Vec<Setting>;
/// Builds a new [`Compiler`] object from this configuration.
fn build(&self) -> Box<dyn Compiler>;
}
/// Description of compiler settings returned by [`CompilerBuilder::settings`].
#[derive(Clone, Copy, Debug)]
pub struct Setting {
/// The name of the setting.
pub name: &'static str,
/// The description of the setting.
pub description: &'static str,
/// The kind of the setting.
pub kind: SettingKind,
/// The supported values of the setting (for enum values).
pub values: Option<&'static [&'static str]>,
}
/// Different kinds of [`Setting`] values that can be configured in a
/// [`CompilerBuilder`]
#[derive(Clone, Copy, Debug)]
pub enum SettingKind {
/// The setting is an enumeration, meaning it's one of a set of values.
Enum,
/// The setting is a number.
Num,
/// The setting is a boolean.
Bool,
/// The setting is a preset.
Preset,
}
/// An implementation of a compiler which can compile WebAssembly functions to
/// machine code and perform other miscellaneous tasks needed by the JIT runtime.
pub trait Compiler: Send + Sync {
/// Compile a function with the given `TargetIsa`.
/// Compiles the function `index` within `translation`.
///
/// The body of the function is available in `data` and configuration
/// values are also passed in via `tunables`. Type information in
/// `translation` is all relative to `types`.
fn compile_function(
&self,
translation: &ModuleTranslation<'_>,
index: DefinedFuncIndex,
data: FunctionBodyData<'_>,
isa: &dyn isa::TargetIsa,
tunables: &Tunables,
types: &TypeTables,
) -> Result<CompiledFunction, CompileError>;
/// Creates a trampoline which the host can use to enter wasm. The
/// trampoline has type `VMTrampoline` and will call a function of type `ty`
/// specified.
fn host_to_wasm_trampoline(
&self,
isa: &dyn isa::TargetIsa,
ty: &WasmFuncType,
) -> Result<CompiledFunction, CompileError>;
/// Creates a trampoline which the host can use to enter wasm.
///
/// The generated trampoline will have the type `VMTrampoline` and will
/// call a function of type `ty` specified.
fn host_to_wasm_trampoline(&self, ty: &WasmFuncType) -> Result<CompiledFunction, CompileError>;
/// Creates a trampoline suitable for a wasm module to import.
///
/// The trampoline has the type specified by `ty` and will call the function
/// `host_fn` which has type `VMTrampoline`.
/// `host_fn` which has type `VMTrampoline`. Note that `host_fn` is
/// directly embedded into the generated code so this is not suitable for a
/// cached value or if `host_fn` does not live as long as the compiled
/// function.
///
/// This is primarily used for `Func::new` in `wasmtime`.
fn wasm_to_host_trampoline(
&self,
isa: &dyn isa::TargetIsa,
ty: &WasmFuncType,
host_fn: usize,
) -> Result<CompiledFunction, CompileError>;
/// Creates DWARF debugging data for a compilation unit.
///
/// This function maps DWARF information found in a wasm module to native
/// DWARF debugging information. This is currently implemented by the
/// `wasmtime-debug` crate.
fn emit_dwarf(
&self,
debuginfo_data: &crate::DebugInfoData,
funcs: &CompiledFunctions,
memory_offset: &crate::ModuleMemoryOffset,
) -> Result<Vec<DwarfSection>>;
/// Returns the target triple that this compiler is compiling for.
fn triple(&self) -> &target_lexicon::Triple;
/// If supported by the target creates a SystemV CIE used for dwarf
/// unwinding information.
fn create_systemv_cie(&self) -> Option<gimli::write::CommonInformationEntry>;
/// Returns a list of configured settings for this compiler.
fn flags(&self) -> HashMap<String, FlagValue>;
/// Same as [`Compiler::flags`], but ISA-specific (a cranelift-ism)
fn isa_flags(&self) -> HashMap<String, FlagValue>;
}
#[allow(missing_docs)]
pub struct DwarfSection {
pub name: &'static str,
pub body: Vec<u8>,
pub relocs: Vec<DwarfSectionReloc>,
}
#[allow(missing_docs)]
#[derive(Clone)]
pub struct DwarfSectionReloc {
pub target: DwarfSectionRelocTarget,
pub offset: u32,
pub addend: i32,
pub size: u8,
}
#[allow(missing_docs)]
#[derive(Clone)]
pub enum DwarfSectionRelocTarget {
Func(usize),
Section(&'static str),
}
/// Value of a configured setting for a [`Compiler`]
#[derive(Serialize, Deserialize, Hash, Eq, PartialEq)]
pub enum FlagValue {
/// Name of the value that has been configured for this setting.
Enum(Cow<'static, str>),
/// The numerical value of the configured settings.
Num(u8),
/// Whether the setting is on or off.
Bool(bool),
}
impl fmt::Display for FlagValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Enum(v) => v.fmt(f),
Self::Num(v) => v.fmt(f),
Self::Bool(v) => v.fmt(f),
}
}
}

View File

@@ -9,12 +9,6 @@ pub mod ir {
pub use cranelift_codegen::{ValueLabelsRanges, ValueLocRange};
}
pub mod settings {
pub use cranelift_codegen::settings::{
builder, Builder, Configurable, Flags, OptLevel, SetError, Setting, SettingKind, Value,
};
}
pub mod isa {
pub use cranelift_codegen::isa::{
lookup, unwind, Builder, CallConv, RegUnit, TargetFrontendConfig, TargetIsa,

View File

@@ -3,15 +3,13 @@ use crate::module::{
ModuleSignature, ModuleType, ModuleUpvar, TableInitializer, TablePlan, TypeTables,
};
use crate::tunables::Tunables;
use cranelift_codegen::ir;
use cranelift_codegen::isa::TargetFrontendConfig;
use cranelift_codegen::packed_option::ReservedValue;
use cranelift_entity::PrimaryMap;
use cranelift_wasm::{
self, translate_module, Alias, DataIndex, DefinedFuncIndex, ElemIndex, EntityIndex, EntityType,
FuncIndex, Global, GlobalIndex, GlobalInit, InstanceIndex, InstanceTypeIndex, Memory,
MemoryIndex, ModuleIndex, ModuleTypeIndex, SignatureIndex, Table, TableIndex,
TargetEnvironment, TypeIndex, WasmError, WasmFuncType, WasmResult,
MemoryIndex, ModuleIndex, ModuleTypeIndex, SignatureIndex, Table, TableIndex, TypeIndex,
WasmError, WasmFuncType, WasmResult,
};
use std::collections::{hash_map::Entry, HashMap};
use std::convert::TryFrom;
@@ -45,7 +43,6 @@ pub struct ModuleEnvironment<'data> {
// Various bits and pieces of configuration
features: WasmFeatures,
target_config: TargetFrontendConfig,
tunables: Tunables,
first_module: bool,
}
@@ -134,18 +131,13 @@ pub struct FunctionMetadata {
impl<'data> ModuleEnvironment<'data> {
/// Allocates the environment data structures.
pub fn new(
target_config: TargetFrontendConfig,
tunables: &Tunables,
features: &WasmFeatures,
) -> Self {
pub fn new(tunables: &Tunables, features: &WasmFeatures) -> Self {
Self {
result: ModuleTranslation::default(),
results: Vec::with_capacity(1),
in_progress: Vec::new(),
modules_to_be: 1,
types: Default::default(),
target_config,
tunables: tunables.clone(),
features: *features,
first_module: true,
@@ -153,10 +145,6 @@ impl<'data> ModuleEnvironment<'data> {
}
}
fn pointer_type(&self) -> ir::Type {
self.target_config.pointer_type()
}
/// Translate a wasm module using this environment.
///
/// This consumes the `ModuleEnvironment` and produces a list of
@@ -378,16 +366,6 @@ impl<'data> ModuleEnvironment<'data> {
}
}
impl<'data> TargetEnvironment for ModuleEnvironment<'data> {
fn target_config(&self) -> TargetFrontendConfig {
self.target_config
}
fn reference_type(&self, ty: cranelift_wasm::WasmType) -> ir::Type {
crate::reference_type(ty, self.pointer_type())
}
}
/// This trait is useful for `translate_module` because it tells how to translate
/// environment-dependent wasm instructions. These functions should not be called by the user.
impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data> {

View File

@@ -15,7 +15,6 @@ wasmtime-environ = { path = "../environ", version = "0.29.0" }
wasmtime-runtime = { path = "../runtime", version = "0.29.0" }
wasmtime-cranelift = { path = "../cranelift", version = "0.29.0" }
wasmtime-lightbeam = { path = "../lightbeam/wasmtime", version = "0.29.0", optional = true }
wasmtime-debug = { path = "../debug", version = "0.29.0" }
wasmtime-profiling = { path = "../profiling", version = "0.29.0" }
wasmtime-obj = { path = "../obj", version = "0.29.0" }
rayon = { version = "1.0", optional = true }

View File

@@ -5,6 +5,7 @@ use crate::object::{
ObjectUnwindInfo,
};
use crate::unwind::UnwindRegistry;
use crate::Compiler;
use anyhow::{Context, Result};
use object::read::{File as ObjectFile, Object, ObjectSection, ObjectSymbol};
use region;
@@ -12,7 +13,7 @@ use std::collections::BTreeMap;
use std::mem::ManuallyDrop;
use std::{cmp, mem};
use wasmtime_environ::{
isa::{unwind::UnwindInfo, TargetIsa},
isa::unwind::UnwindInfo,
wasm::{FuncIndex, SignatureIndex},
CompiledFunction,
};
@@ -131,7 +132,7 @@ impl CodeMemory {
}
/// Make all allocated memory executable.
pub fn publish(&mut self, isa: &dyn TargetIsa) {
pub fn publish(&mut self, compiler: &Compiler) {
self.push_current(0)
.expect("failed to push current memory map");
@@ -142,7 +143,7 @@ impl CodeMemory {
} in &mut self.entries[self.published..]
{
// Remove write access to the pages due to the relocation fixups.
r.publish(isa)
r.publish(compiler)
.expect("failed to publish function unwind registry");
if !m.is_empty() {

View File

@@ -6,15 +6,14 @@ use object::write::Object;
#[cfg(feature = "parallel-compilation")]
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::hash::{Hash, Hasher};
use std::mem;
use wasmparser::WasmFeatures;
use wasmtime_debug::{emit_dwarf, DwarfSection};
use wasmtime_environ::entity::EntityRef;
use wasmtime_environ::isa::{TargetFrontendConfig, TargetIsa};
use wasmtime_environ::wasm::{DefinedMemoryIndex, MemoryIndex};
use wasmtime_environ::{
CompiledFunctions, Compiler as EnvCompiler, DebugInfoData, Module, ModuleMemoryOffset,
CompiledFunctions, Compiler as EnvCompiler, CompilerBuilder, ModuleMemoryOffset,
ModuleTranslation, Tunables, TypeTables, VMOffsets,
};
@@ -33,41 +32,35 @@ pub enum CompilationStrategy {
}
/// A WebAssembly code JIT compiler.
///
/// A `Compiler` instance owns the executable memory that it allocates.
///
/// TODO: Evolve this to support streaming rather than requiring a `&[u8]`
/// containing a whole wasm module at once.
///
/// TODO: Consider using cranelift-module.
pub struct Compiler {
isa: Box<dyn TargetIsa>,
compiler: Box<dyn EnvCompiler>,
strategy: CompilationStrategy,
tunables: Tunables,
features: WasmFeatures,
parallel_compilation: bool,
}
impl Compiler {
/// Construct a new `Compiler`.
/// Creates a new compiler builder for the provided compilation strategy.
pub fn builder(strategy: CompilationStrategy) -> Box<dyn CompilerBuilder> {
match strategy {
CompilationStrategy::Auto | CompilationStrategy::Cranelift => {
wasmtime_cranelift::builder()
}
#[cfg(feature = "lightbeam")]
CompilationStrategy::Lightbeam => unimplemented!(),
}
}
/// Creates a new instance of a `Compiler` from the provided compiler
/// builder.
pub fn new(
isa: Box<dyn TargetIsa>,
strategy: CompilationStrategy,
builder: &dyn CompilerBuilder,
tunables: Tunables,
features: WasmFeatures,
parallel_compilation: bool,
) -> Self {
Self {
isa,
strategy,
compiler: match strategy {
CompilationStrategy::Auto | CompilationStrategy::Cranelift => {
Box::new(wasmtime_cranelift::Cranelift::default())
}
#[cfg(feature = "lightbeam")]
CompilationStrategy::Lightbeam => Box::new(wasmtime_lightbeam::Lightbeam),
},
) -> Compiler {
Compiler {
compiler: builder.build(),
tunables,
features,
parallel_compilation,
@@ -80,25 +73,6 @@ fn _assert_compiler_send_sync() {
_assert::<Compiler>();
}
fn transform_dwarf_data(
isa: &dyn TargetIsa,
module: &Module,
debug_data: &DebugInfoData,
funcs: &CompiledFunctions,
) -> Result<Vec<DwarfSection>, SetupError> {
let target_config = isa.frontend_config();
let ofs = VMOffsets::new(target_config.pointer_bytes(), &module);
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)))
} else {
ModuleMemoryOffset::None
};
emit_dwarf(isa, debug_data, funcs, &memory_offset).map_err(SetupError::DebugInfo)
}
#[allow(missing_docs)]
pub struct Compilation {
pub obj: Object,
@@ -107,21 +81,6 @@ pub struct Compilation {
}
impl Compiler {
/// Return the isa.
pub fn isa(&self) -> &dyn TargetIsa {
self.isa.as_ref()
}
/// Return the compiler's strategy.
pub fn strategy(&self) -> CompilationStrategy {
self.strategy
}
/// Return the target's frontend configuration settings.
pub fn frontend_config(&self) -> TargetFrontendConfig {
self.isa.frontend_config()
}
/// Return the tunables in use by this engine.
pub fn tunables(&self) -> &Tunables {
&self.tunables
@@ -137,6 +96,11 @@ impl Compiler {
&*self.compiler
}
/// Returns the target this compiler is compiling for.
pub fn triple(&self) -> &target_lexicon::Triple {
self.compiler.triple()
}
/// Compile the given function bodies.
pub fn compile<'data>(
&self,
@@ -148,25 +112,35 @@ impl Compiler {
let funcs = self
.run_maybe_parallel(functions, |(index, func)| {
self.compiler.compile_function(
translation,
index,
func,
&*self.isa,
&self.tunables,
types,
)
self.compiler
.compile_function(translation, index, func, &self.tunables, types)
})?
.into_iter()
.collect::<CompiledFunctions>();
let dwarf_sections = if self.tunables.generate_native_debuginfo && !funcs.is_empty() {
transform_dwarf_data(
&*self.isa,
let ofs = VMOffsets::new(
self.compiler
.triple()
.architecture
.pointer_width()
.unwrap()
.bytes(),
&translation.module,
&translation.debuginfo,
&funcs,
)?
);
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)),
)
} else {
ModuleMemoryOffset::None
};
self.compiler
.emit_dwarf(&translation.debuginfo, &funcs, &memory_offset)
.map_err(SetupError::DebugInfo)?
} else {
vec![]
};
@@ -211,29 +185,27 @@ impl Compiler {
impl Hash for Compiler {
fn hash<H: Hasher>(&self, hasher: &mut H) {
let Compiler {
strategy,
compiler: _,
isa,
compiler,
tunables,
features,
parallel_compilation: _,
} = self;
// Hash compiler's flags: compilation strategy, isa, frontend config,
// misc tunables.
strategy.hash(hasher);
isa.triple().hash(hasher);
isa.hash_all_flags(hasher);
isa.frontend_config().hash(hasher);
compiler.triple().hash(hasher);
compiler
.flags()
.into_iter()
.collect::<BTreeMap<_, _>>()
.hash(hasher);
compiler
.isa_flags()
.into_iter()
.collect::<BTreeMap<_, _>>()
.hash(hasher);
tunables.hash(hasher);
features.hash(hasher);
// Catch accidental bugs of reusing across crate versions.
env!("CARGO_PKG_VERSION").hash(hasher);
// TODO: ... and should we hash anything else? There's a lot of stuff in
// `TargetIsa`, like registers/encodings/etc. Should we be hashing that
// too? It seems like wasmtime doesn't configure it too too much, but
// this may become an issue at some point.
}
}

212
crates/jit/src/debug.rs Normal file
View File

@@ -0,0 +1,212 @@
use anyhow::{bail, ensure, Error};
use object::endian::{BigEndian, Endian, Endianness, LittleEndian};
use object::{RelocationEncoding, RelocationKind};
use std::collections::HashMap;
pub fn create_gdbjit_image(
mut bytes: Vec<u8>,
code_region: (*const u8, usize),
defined_funcs_offset: usize,
funcs: &[*const u8],
) -> Result<Vec<u8>, Error> {
let e = ensure_supported_elf_format(&bytes)?;
// patch relocs
relocate_dwarf_sections(&bytes, defined_funcs_offset, funcs)?;
// elf is still missing details...
match e {
Endianness::Little => {
convert_object_elf_to_loadable_file::<LittleEndian>(&mut bytes, code_region)
}
Endianness::Big => {
convert_object_elf_to_loadable_file::<BigEndian>(&mut bytes, code_region)
}
}
// let mut file = ::std::fs::File::create(::std::path::Path::new("test.o")).expect("file");
// ::std::io::Write::write_all(&mut file, &bytes).expect("write");
Ok(bytes)
}
fn relocate_dwarf_sections(
bytes: &[u8],
defined_funcs_offset: usize,
funcs: &[*const u8],
) -> Result<(), Error> {
use object::read::{File, Object, ObjectSection, ObjectSymbol, RelocationTarget};
let obj = File::parse(bytes)?;
let mut func_symbols = HashMap::new();
for sym in obj.symbols() {
match (sym.name(), sym.section_index()) {
(Ok(name), Some(_section_index)) if name.starts_with("_wasm_function_") => {
let index = name["_wasm_function_".len()..].parse::<usize>()?;
let data = funcs[index - defined_funcs_offset];
func_symbols.insert(sym.index(), data);
}
_ => (),
}
}
for section in obj.sections() {
for (off, r) in section.relocations() {
if r.kind() != RelocationKind::Absolute
|| r.encoding() != RelocationEncoding::Generic
|| r.size() != 64
{
continue;
}
let data = match r.target() {
RelocationTarget::Symbol(ref index) => func_symbols.get(index),
_ => None,
};
let data: *const u8 = match data {
Some(data) => *data,
None => {
continue;
}
};
let target = (data as u64).wrapping_add(r.addend() as u64);
let entry_ptr = section.data_range(off, 8).unwrap().unwrap().as_ptr();
unsafe {
std::ptr::write(entry_ptr as *mut u64, target);
}
}
}
Ok(())
}
fn ensure_supported_elf_format(bytes: &[u8]) -> Result<Endianness, Error> {
use object::elf::*;
use object::read::elf::*;
use std::mem::size_of;
let kind = match object::FileKind::parse(bytes) {
Ok(file) => file,
Err(err) => {
bail!("Failed to parse file: {}", err);
}
};
let header = match kind {
object::FileKind::Elf64 => match object::elf::FileHeader64::<Endianness>::parse(bytes) {
Ok(header) => header,
Err(err) => {
bail!("Unsupported ELF file: {}", err);
}
},
_ => {
bail!("only 64-bit ELF files currently supported")
}
};
let e = header.endian().unwrap();
match header.e_machine.get(e) {
EM_AARCH64 => (),
EM_X86_64 => (),
EM_S390 => (),
machine => {
bail!("Unsupported ELF target machine: {:x}", machine);
}
}
ensure!(
header.e_phoff.get(e) == 0 && header.e_phnum.get(e) == 0,
"program header table is empty"
);
let e_shentsize = header.e_shentsize.get(e);
let req_shentsize = match e {
Endianness::Little => size_of::<SectionHeader64<LittleEndian>>(),
Endianness::Big => size_of::<SectionHeader64<BigEndian>>(),
};
ensure!(e_shentsize as usize == req_shentsize, "size of sh");
Ok(e)
}
fn convert_object_elf_to_loadable_file<E: Endian>(
bytes: &mut Vec<u8>,
code_region: (*const u8, usize),
) {
use object::elf::*;
use std::ffi::CStr;
use std::mem::size_of;
use std::os::raw::c_char;
let e = E::default();
let header: &FileHeader64<E> = unsafe { &*(bytes.as_mut_ptr() as *const FileHeader64<_>) };
let e_shentsize = header.e_shentsize.get(e);
let e_shoff = header.e_shoff.get(e);
let e_shnum = header.e_shnum.get(e);
let mut shstrtab_off = 0;
for i in 0..e_shnum {
let off = e_shoff as isize + i as isize * e_shentsize as isize;
let section: &SectionHeader64<E> =
unsafe { &*(bytes.as_ptr().offset(off) as *const SectionHeader64<_>) };
if section.sh_type.get(e) != SHT_STRTAB {
continue;
}
shstrtab_off = section.sh_offset.get(e);
}
let mut segment: Option<_> = None;
for i in 0..e_shnum {
let off = e_shoff as isize + i as isize * e_shentsize as isize;
let section: &mut SectionHeader64<E> =
unsafe { &mut *(bytes.as_mut_ptr().offset(off) as *mut SectionHeader64<_>) };
if section.sh_type.get(e) != SHT_PROGBITS {
continue;
}
// It is a SHT_PROGBITS, but we need to check sh_name to ensure it is our function
let sh_name_off = section.sh_name.get(e);
let sh_name = unsafe {
CStr::from_ptr(
bytes
.as_ptr()
.offset((shstrtab_off + sh_name_off as u64) as isize)
as *const c_char,
)
.to_str()
.expect("name")
};
if sh_name != ".text" {
continue;
}
assert!(segment.is_none());
// Patch vaddr, and save file location and its size.
section.sh_addr.set(e, code_region.0 as u64);
let sh_offset = section.sh_offset.get(e);
let sh_size = section.sh_size.get(e);
segment = Some((sh_offset, sh_size));
}
// LLDB wants segment with virtual address set, placing them at the end of ELF.
let ph_off = bytes.len();
let e_phentsize = size_of::<ProgramHeader64<E>>();
let e_phnum = 1;
bytes.resize(ph_off + e_phentsize * e_phnum, 0);
if let Some((sh_offset, sh_size)) = segment {
let (v_offset, size) = code_region;
let program: &mut ProgramHeader64<E> =
unsafe { &mut *(bytes.as_ptr().add(ph_off) as *mut ProgramHeader64<_>) };
program.p_type.set(e, PT_LOAD);
program.p_offset.set(e, sh_offset);
program.p_vaddr.set(e, v_offset as u64);
program.p_paddr.set(e, v_offset as u64);
program.p_filesz.set(e, sh_size);
program.p_memsz.set(e, size as u64);
} else {
unreachable!();
}
// It is somewhat loadable ELF file at this moment.
let header: &mut FileHeader64<E> =
unsafe { &mut *(bytes.as_mut_ptr() as *mut FileHeader64<_>) };
header.e_type.set(e, ET_DYN);
header.e_phoff.set(e, ph_off as u64);
header.e_phentsize.set(e, e_phentsize as u16);
header.e_phnum.set(e, e_phnum as u16);
}

View File

@@ -5,6 +5,7 @@
use crate::code_memory::CodeMemory;
use crate::compiler::{Compilation, Compiler};
use crate::debug::create_gdbjit_image;
use crate::link::link_module;
use crate::object::ObjectUnwindInfo;
use anyhow::{Context, Result};
@@ -13,9 +14,7 @@ use serde::{Deserialize, Serialize};
use std::ops::Range;
use std::sync::Arc;
use thiserror::Error;
use wasmtime_debug::create_gdbjit_image;
use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::isa::TargetIsa;
use wasmtime_environ::wasm::{
DefinedFuncIndex, InstanceTypeIndex, ModuleTypeIndex, SignatureIndex, WasmFuncType,
};
@@ -103,13 +102,10 @@ impl CompilationArtifacts {
data: &[u8],
use_paged_mem_init: bool,
) -> Result<(usize, Vec<CompilationArtifacts>, TypeTables), SetupError> {
let (main_module, translations, types) = ModuleEnvironment::new(
compiler.frontend_config(),
compiler.tunables(),
compiler.features(),
)
.translate(data)
.map_err(|error| SetupError::Compile(CompileError::Wasm(error)))?;
let (main_module, translations, types) =
ModuleEnvironment::new(compiler.tunables(), compiler.features())
.translate(data)
.map_err(|error| SetupError::Compile(CompileError::Wasm(error)))?;
let list = compiler.run_maybe_parallel::<_, _, SetupError, _>(
translations,
@@ -225,25 +221,24 @@ impl CompiledModule {
/// artifacts.
pub fn from_artifacts_list(
artifacts: Vec<CompilationArtifacts>,
isa: &dyn TargetIsa,
profiler: &dyn ProfilingAgent,
compiler: &Compiler,
profiler: &dyn ProfilingAgent,
) -> Result<Vec<Arc<Self>>, SetupError> {
compiler.run_maybe_parallel(artifacts, |a| {
CompiledModule::from_artifacts(a, isa, profiler)
CompiledModule::from_artifacts(a, compiler, profiler)
})
}
/// Creates `CompiledModule` directly from `CompilationArtifacts`.
pub fn from_artifacts(
artifacts: CompilationArtifacts,
isa: &dyn TargetIsa,
compiler: &Compiler,
profiler: &dyn ProfilingAgent,
) -> Result<Arc<Self>, SetupError> {
// Allocate all of the compiled functions into executable memory,
// copying over their contents.
let (code_memory, code_range, finished_functions, trampolines) = build_code_memory(
isa,
compiler,
&artifacts.obj,
&artifacts.module,
&artifacts.unwind_info,
@@ -480,7 +475,7 @@ fn create_dbg_image(
}
fn build_code_memory(
isa: &dyn TargetIsa,
compiler: &Compiler,
obj: &[u8],
module: &Module,
unwind_info: &[ObjectUnwindInfo],
@@ -531,7 +526,7 @@ fn build_code_memory(
let code_range = (code_range.as_ptr(), code_range.len());
// Make all code compiled thus far executable.
code_memory.publish(isa);
code_memory.publish(compiler);
Ok((code_memory, code_range, finished_functions, trampolines))
}

View File

@@ -22,6 +22,7 @@
mod code_memory;
mod compiler;
mod debug;
mod instantiate;
mod link;
mod object;

View File

@@ -4,10 +4,9 @@ use crate::Compiler;
use object::write::Object;
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use wasmtime_debug::DwarfSection;
use wasmtime_environ::isa::unwind::UnwindInfo;
use wasmtime_environ::wasm::{FuncIndex, SignatureIndex};
use wasmtime_environ::{CompiledFunctions, ModuleTranslation, TypeTables};
use wasmtime_environ::{CompiledFunctions, DwarfSection, ModuleTranslation, TypeTables};
use wasmtime_obj::{ObjectBuilder, ObjectBuilderTarget};
pub use wasmtime_obj::utils;
@@ -52,7 +51,7 @@ pub(crate) fn build_object(
for i in signatures {
let func = compiler
.compiler()
.host_to_wasm_trampoline(compiler.isa(), &types.wasm_signatures[i])?;
.host_to_wasm_trampoline(&types.wasm_signatures[i])?;
// Preserve trampoline function unwind info.
if let Some(info) = &func.unwind_info {
unwind_info.push(ObjectUnwindInfo::Trampoline(i, info.clone()))
@@ -60,7 +59,7 @@ pub(crate) fn build_object(
trampolines.push((i, func));
}
let target = ObjectBuilderTarget::new(compiler.isa().triple().architecture)?;
let target = ObjectBuilderTarget::new(compiler.compiler().triple().architecture)?;
let mut builder = ObjectBuilder::new(target, &translation.module, funcs);
builder
.set_code_alignment(CODE_SECTION_ALIGNMENT)

View File

@@ -1,11 +1,12 @@
//! Module for System V ABI unwind registry.
use crate::Compiler;
use anyhow::{bail, Result};
use gimli::{
write::{Address, EhFrame, EndianVec, FrameTable, Writer},
RunTimeEndian,
};
use wasmtime_environ::isa::{unwind::UnwindInfo, TargetIsa};
use wasmtime_environ::isa::unwind::UnwindInfo;
/// Represents a registry of function unwind information for System V ABI.
pub struct UnwindRegistry {
@@ -53,7 +54,7 @@ impl UnwindRegistry {
}
/// Publishes all registered functions.
pub fn publish(&mut self, isa: &dyn TargetIsa) -> Result<()> {
pub fn publish(&mut self, compiler: &Compiler) -> Result<()> {
if self.published {
bail!("unwind registry has already been published");
}
@@ -63,7 +64,7 @@ impl UnwindRegistry {
return Ok(());
}
self.set_frame_table(isa)?;
self.set_frame_table(compiler)?;
unsafe {
self.register_frames();
@@ -74,9 +75,9 @@ impl UnwindRegistry {
Ok(())
}
fn set_frame_table(&mut self, isa: &dyn TargetIsa) -> Result<()> {
fn set_frame_table(&mut self, compiler: &Compiler) -> Result<()> {
let mut table = FrameTable::default();
let cie_id = table.add_cie(match isa.create_systemv_cie() {
let cie_id = table.add_cie(match compiler.compiler().create_systemv_cie() {
Some(cie) => cie,
None => bail!("ISA does not support System V unwind information"),
});

View File

@@ -1,7 +1,8 @@
//! Module for Windows x64 ABI unwind registry.
use crate::Compiler;
use anyhow::{bail, Result};
use wasmtime_environ::isa::{unwind::UnwindInfo, TargetIsa};
use wasmtime_environ::isa::unwind::UnwindInfo;
use winapi::um::winnt;
/// Represents a registry of function unwind information for Windows x64 ABI.
@@ -49,7 +50,7 @@ impl UnwindRegistry {
}
/// Publishes all registered functions.
pub fn publish(&mut self, _isa: &dyn TargetIsa) -> Result<()> {
pub fn publish(&mut self, _compiler: &Compiler) -> Result<()> {
if self.published {
bail!("unwind registry has already been published");
}

View File

@@ -12,6 +12,9 @@ readme = "README.md"
edition = "2018"
[dependencies]
anyhow = "1.0"
target-lexicon = "0.12"
gimli = "0.25"
lightbeam = { path = "..", version = "0.29.0" }
wasmparser = "0.80"
cranelift-codegen = { path = "../../../cranelift/codegen", version = "0.76.0" }

View File

@@ -3,16 +3,19 @@
//! This crates provides an implementation of [`Compiler`] in the form of
//! [`Lightbeam`].
#![allow(dead_code)]
use anyhow::Result;
use cranelift_codegen::binemit;
use cranelift_codegen::ir::{self, ExternalName};
use cranelift_codegen::isa;
use lightbeam::{CodeGenSession, NullOffsetSink, Sinks};
use std::collections::HashMap;
use wasmtime_environ::wasm::{
DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex,
GlobalIndex, MemoryIndex, TableIndex, TypeIndex, WasmFuncType,
};
use wasmtime_environ::{
BuiltinFunctionIndex, CompileError, CompiledFunction, Compiler, FunctionBodyData, Module,
BuiltinFunctionIndex, CompileError, CompiledFunction, CompiledFunctions, Compiler,
DebugInfoData, DwarfSection, FlagValue, FunctionBodyData, Module, ModuleMemoryOffset,
ModuleTranslation, Relocation, RelocationTarget, TrapInformation, Tunables, TypeTables,
VMOffsets,
};
@@ -23,62 +26,61 @@ pub struct Lightbeam;
impl Compiler for Lightbeam {
fn compile_function(
&self,
translation: &ModuleTranslation,
i: DefinedFuncIndex,
function_body: FunctionBodyData<'_>,
isa: &dyn isa::TargetIsa,
tunables: &Tunables,
_translation: &ModuleTranslation,
_i: DefinedFuncIndex,
_function_body: FunctionBodyData<'_>,
_tunables: &Tunables,
_types: &TypeTables,
) -> Result<CompiledFunction, CompileError> {
if tunables.generate_native_debuginfo {
return Err(CompileError::DebugInfoNotSupported);
}
let func_index = translation.module.func_index(i);
unimplemented!()
// if tunables.generate_native_debuginfo {
// return Err(CompileError::DebugInfoNotSupported);
// }
// let func_index = translation.module.func_index(i);
let env = FuncEnvironment::new(isa.frontend_config().pointer_bytes(), translation);
let mut codegen_session: CodeGenSession<_> = CodeGenSession::new(
translation.function_body_inputs.len() as u32,
&env,
lightbeam::microwasm::I32,
);
// let env = FuncEnvironment::new(isa.frontend_config().pointer_bytes(), translation);
// let mut codegen_session: CodeGenSession<_> = CodeGenSession::new(
// translation.function_body_inputs.len() as u32,
// &env,
// lightbeam::microwasm::I32,
// );
let mut reloc_sink = RelocSink::new(func_index);
let mut trap_sink = TrapSink::new();
lightbeam::translate_function(
&mut codegen_session,
Sinks {
relocs: &mut reloc_sink,
traps: &mut trap_sink,
offsets: &mut NullOffsetSink,
},
i.as_u32(),
function_body.body,
)
.map_err(|e| CompileError::Codegen(format!("Failed to translate function: {}", e)))?;
// let mut reloc_sink = RelocSink::new(func_index);
// let mut trap_sink = TrapSink::new();
// lightbeam::translate_function(
// &mut codegen_session,
// Sinks {
// relocs: &mut reloc_sink,
// traps: &mut trap_sink,
// offsets: &mut NullOffsetSink,
// },
// i.as_u32(),
// function_body.body,
// )
// .map_err(|e| CompileError::Codegen(format!("Failed to translate function: {}", e)))?;
let code_section = codegen_session
.into_translated_code_section()
.map_err(|e| CompileError::Codegen(format!("Failed to generate output code: {}", e)))?;
// let code_section = codegen_session
// .into_translated_code_section()
// .map_err(|e| CompileError::Codegen(format!("Failed to generate output code: {}", e)))?;
Ok(CompiledFunction {
// TODO: try to remove copy here (?)
body: code_section.buffer().to_vec(),
traps: trap_sink.traps,
relocations: reloc_sink.func_relocs,
// Ok(CompiledFunction {
// // TODO: try to remove copy here (?)
// body: code_section.buffer().to_vec(),
// traps: trap_sink.traps,
// relocations: reloc_sink.func_relocs,
// 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(),
})
// // 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(),
// })
}
fn host_to_wasm_trampoline(
&self,
_isa: &dyn isa::TargetIsa,
_ty: &WasmFuncType,
) -> Result<CompiledFunction, CompileError> {
unimplemented!()
@@ -86,12 +88,36 @@ impl Compiler for Lightbeam {
fn wasm_to_host_trampoline(
&self,
_isa: &dyn isa::TargetIsa,
_ty: &WasmFuncType,
_host_fn: usize,
) -> Result<CompiledFunction, CompileError> {
unimplemented!()
}
fn emit_dwarf(
&self,
_debuginfo_data: &DebugInfoData,
_funcs: &CompiledFunctions,
_memory_offset: &crate::ModuleMemoryOffset,
) -> Result<Vec<DwarfSection>> {
unimplemented!()
}
fn triple(&self) -> &target_lexicon::Triple {
unimplemented!()
}
fn create_systemv_cie(&self) -> Option<gimli::write::CommonInformationEntry> {
unimplemented!()
}
fn flags(&self) -> HashMap<String, FlagValue> {
unimplemented!()
}
fn isa_flags(&self) -> HashMap<String, FlagValue> {
unimplemented!()
}
}
/// Implementation of a relocation sink that just saves all the information for later
@@ -229,8 +255,9 @@ impl lightbeam::ModuleContext for FuncEnvironment<'_> {
.map(DefinedGlobalIndex::as_u32)
}
fn global_type(&self, global_index: u32) -> &Self::GlobalType {
&self.module.globals[GlobalIndex::from_u32(global_index)].ty
fn global_type(&self, _global_index: u32) -> &Self::GlobalType {
unimplemented!()
// &self.module.globals[GlobalIndex::from_u32(global_index)].ty
}
fn func_type_index(&self, func_idx: u32) -> u32 {

View File

@@ -15,7 +15,6 @@ wasmtime-environ = { path = "../environ", version = "0.29.0" }
object = { version = "0.26.0", default-features = false, features = ["write"] }
more-asserts = "0.2.1"
target-lexicon = { version = "0.12.0", default-features = false }
wasmtime-debug = { path = "../debug", version = "0.29.0" }
[badges]
maintenance = { status = "experimental" }

View File

@@ -26,12 +26,14 @@ use object::{
};
use std::collections::HashMap;
use target_lexicon::Triple;
use wasmtime_debug::{DwarfSection, DwarfSectionRelocTarget};
use wasmtime_environ::entity::{EntityRef, PrimaryMap};
use wasmtime_environ::ir::{LibCall, Reloc};
use wasmtime_environ::isa::unwind::UnwindInfo;
use wasmtime_environ::wasm::{DefinedFuncIndex, FuncIndex, SignatureIndex};
use wasmtime_environ::{CompiledFunction, CompiledFunctions, Module, Relocation, RelocationTarget};
use wasmtime_environ::{
CompiledFunction, CompiledFunctions, DwarfSection, DwarfSectionRelocTarget, Module, Relocation,
RelocationTarget,
};
fn to_object_relocations<'a>(
it: impl Iterator<Item = &'a Relocation> + 'a,

View File

@@ -5,7 +5,7 @@
use crate::export::Export;
use crate::externref::VMExternRefActivationsTable;
use crate::memory::{Memory, RuntimeMemoryCreator};
use crate::table::{Table, TableElement};
use crate::table::{Table, TableElement, TableElementType};
use crate::traphandlers::Trap;
use crate::vmcontext::{
VMCallerCheckedAnyfunc, VMContext, VMFunctionImport, VMGlobalDefinition, VMGlobalImport,
@@ -25,7 +25,7 @@ use std::{mem, ptr, slice};
use wasmtime_environ::entity::{packed_option::ReservedValue, EntityRef, EntitySet, PrimaryMap};
use wasmtime_environ::wasm::{
DataIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, ElemIndex, EntityIndex,
FuncIndex, GlobalIndex, MemoryIndex, TableElementType, TableIndex, WasmType,
FuncIndex, GlobalIndex, MemoryIndex, TableIndex, WasmType,
};
use wasmtime_environ::{ir, HostPtr, Module, VMOffsets};
@@ -590,7 +590,7 @@ impl Instance {
)?;
},
TableElementType::Val(_) => {
TableElementType::Extern => {
debug_assert!(elements.iter().all(|e| *e == FuncIndex::reserved_value()));
table.fill(dst, TableElement::ExternRef(None), len)?;
}

View File

@@ -1051,8 +1051,7 @@ mod test {
use crate::{Imports, VMSharedSignatureIndex};
use wasmtime_environ::{
entity::EntityRef,
ir::Type,
wasm::{Global, GlobalInit, Memory, SignatureIndex, Table, TableElementType, WasmType},
wasm::{Global, GlobalInit, Memory, SignatureIndex, Table, WasmType},
MemoryPlan, ModuleType, TablePlan, TableStyle,
};
@@ -1088,7 +1087,6 @@ mod test {
style: TableStyle::CallerChecksSignature,
table: Table {
wasm_ty: WasmType::FuncRef,
ty: TableElementType::Func,
minimum: 0,
maximum: None,
},
@@ -1144,7 +1142,6 @@ mod test {
module.globals.push(Global {
wasm_ty: WasmType::I32,
ty: Type::int(32).unwrap(),
mutability: false,
initializer: GlobalInit::I32Const(0),
});
@@ -1208,7 +1205,6 @@ mod test {
style: TableStyle::CallerChecksSignature,
table: Table {
wasm_ty: WasmType::FuncRef,
ty: TableElementType::Func,
minimum: 0,
maximum: None,
},
@@ -1258,7 +1254,6 @@ mod test {
module.globals.push(Global {
wasm_ty: WasmType::I32,
ty: Type::int(32).unwrap(),
mutability: false,
initializer: GlobalInit::I32Const(0),
});
@@ -1281,7 +1276,6 @@ mod test {
style: TableStyle::CallerChecksSignature,
table: Table {
wasm_ty: WasmType::FuncRef,
ty: TableElementType::Func,
minimum: 11,
maximum: None,
},

View File

@@ -58,16 +58,14 @@
use crate::externref::VMExternRef;
use crate::instance::Instance;
use crate::table::Table;
use crate::table::{Table, TableElementType};
use crate::traphandlers::{raise_lib_trap, Trap};
use crate::vmcontext::{VMCallerCheckedAnyfunc, VMContext};
use backtrace::Backtrace;
use std::mem;
use std::ptr::{self, NonNull};
use wasmtime_environ::ir::TrapCode;
use wasmtime_environ::wasm::{
DataIndex, ElemIndex, GlobalIndex, MemoryIndex, TableElementType, TableIndex,
};
use wasmtime_environ::wasm::{DataIndex, ElemIndex, GlobalIndex, MemoryIndex, TableIndex};
const TOINT_32: f32 = 1.0 / f32::EPSILON;
const TOINT_64: f64 = 1.0 / f64::EPSILON;
@@ -214,9 +212,7 @@ pub unsafe extern "C" fn wasmtime_table_grow(
let table_index = TableIndex::from_u32(table_index);
let element = match instance.table_element_type(table_index) {
TableElementType::Func => (init_value as *mut VMCallerCheckedAnyfunc).into(),
TableElementType::Val(ty) => {
debug_assert_eq!(ty, crate::ref_type());
TableElementType::Extern => {
let init_value = if init_value.is_null() {
None
} else {
@@ -249,8 +245,7 @@ pub unsafe extern "C" fn wasmtime_table_fill(
let val = val as *mut VMCallerCheckedAnyfunc;
table.fill(dst, val.into(), len)
}
TableElementType::Val(ty) => {
debug_assert_eq!(ty, crate::ref_type());
TableElementType::Extern => {
let val = if val.is_null() {
None
} else {

View File

@@ -8,7 +8,7 @@ use anyhow::{bail, Result};
use std::convert::{TryFrom, TryInto};
use std::ops::Range;
use std::ptr;
use wasmtime_environ::wasm::TableElementType;
use wasmtime_environ::wasm::WasmType;
use wasmtime_environ::{ir, TablePlan};
/// An element going into or coming out of a table.
@@ -22,6 +22,12 @@ pub enum TableElement {
ExternRef(Option<VMExternRef>),
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum TableElementType {
Func,
Extern,
}
// The usage of `*mut VMCallerCheckedAnyfunc` is safe w.r.t. thread safety, this
// just relies on thread-safety of `VMExternRef` itself.
unsafe impl Send for TableElement where VMExternRef: Send {}
@@ -38,7 +44,7 @@ impl TableElement {
unsafe fn from_raw(ty: TableElementType, ptr: usize) -> Self {
match ty {
TableElementType::Func => Self::FuncRef(ptr as _),
TableElementType::Val(_) => Self::ExternRef(if ptr == 0 {
TableElementType::Extern => Self::ExternRef(if ptr == 0 {
None
} else {
Some(VMExternRef::from_raw(ptr as *mut u8))
@@ -54,7 +60,7 @@ impl TableElement {
unsafe fn clone_from_raw(ty: TableElementType, ptr: usize) -> Self {
match ty {
TableElementType::Func => Self::FuncRef(ptr as _),
TableElementType::Val(_) => Self::ExternRef(if ptr == 0 {
TableElementType::Extern => Self::ExternRef(if ptr == 0 {
None
} else {
Some(VMExternRef::clone_from_raw(ptr as *mut u8))
@@ -122,6 +128,14 @@ pub enum Table {
},
}
fn wasm_to_table_type(ty: WasmType) -> Result<TableElementType> {
match ty {
WasmType::FuncRef => Ok(TableElementType::Func),
WasmType::ExternRef => Ok(TableElementType::Extern),
ty => bail!("invalid table element type {:?}", ty),
}
}
impl Table {
/// Create a new dynamic (movable) table instance for the specified table plan.
pub fn new_dynamic(
@@ -130,7 +144,7 @@ impl Table {
) -> Result<Self> {
Self::limit_new(plan, limiter)?;
let elements = vec![0; plan.table.minimum as usize];
let ty = plan.table.ty.clone();
let ty = wasm_to_table_type(plan.table.wasm_ty)?;
let maximum = plan.table.maximum;
Ok(Table::Dynamic {
@@ -148,7 +162,7 @@ impl Table {
) -> Result<Self> {
Self::limit_new(plan, limiter)?;
let size = plan.table.minimum;
let ty = plan.table.ty.clone();
let ty = wasm_to_table_type(plan.table.wasm_ty)?;
let data = match plan.table.maximum {
Some(max) if (max as usize) < data.len() => &mut data[..max as usize],
_ => data,
@@ -403,7 +417,7 @@ impl Table {
fn type_matches(&self, val: &TableElement) -> bool {
match (&val, self.element_type()) {
(TableElement::FuncRef(_), TableElementType::Func) => true,
(TableElement::ExternRef(_), TableElementType::Val(_)) => true,
(TableElement::ExternRef(_), TableElementType::Extern) => true,
_ => false,
}
}
@@ -449,7 +463,7 @@ impl Table {
dst_table.elements_mut()[dst_range]
.copy_from_slice(&src_table.elements()[src_range]);
}
TableElementType::Val(_) => {
TableElementType::Extern => {
// We need to clone each `externref`
let dst = dst_table.elements_mut();
let src = src_table.elements();
@@ -469,7 +483,7 @@ impl Table {
// `funcref` are `Copy`, so just do a memmove
dst.copy_within(src_range, dst_range.start);
}
TableElementType::Val(_) => {
TableElementType::Extern => {
// We need to clone each `externref` while handling overlapping
// ranges
if dst_range.start <= src_range.start {

View File

@@ -13,7 +13,6 @@ edition = "2018"
rustdoc-args = ["--cfg", "nightlydoc"]
[dependencies]
cranelift-native = { path = '../../cranelift/native', version = '0.76.0' }
wasmtime-runtime = { path = "../runtime", version = "0.29.0" }
wasmtime-environ = { path = "../environ", version = "0.29.0" }
wasmtime-jit = { path = "../jit", version = "0.29.0" }

View File

@@ -10,8 +10,7 @@ use std::sync::Arc;
use wasmparser::WasmFeatures;
#[cfg(feature = "cache")]
use wasmtime_cache::CacheConfig;
use wasmtime_environ::settings::{self, Configurable, SetError};
use wasmtime_environ::{isa, isa::TargetIsa, Tunables};
use wasmtime_environ::{CompilerBuilder, Tunables};
use wasmtime_jit::{CompilationStrategy, Compiler};
use wasmtime_profiling::{JitDumpAgent, NullProfilerAgent, ProfilingAgent, VTuneAgent};
use wasmtime_runtime::{
@@ -337,12 +336,9 @@ impl Default for InstanceAllocationStrategy {
///
/// This structure exposed a builder-like interface and is primarily consumed by
/// [`Engine::new()`](crate::Engine::new)
#[derive(Clone)]
pub struct Config {
pub(crate) flags: settings::Builder,
pub(crate) isa_flags: isa::Builder,
pub(crate) compiler: Box<dyn CompilerBuilder>,
pub(crate) tunables: Tunables,
pub(crate) strategy: CompilationStrategy,
#[cfg(feature = "cache")]
pub(crate) cache_config: CacheConfig,
pub(crate) profiler: Arc<dyn ProfilingAgent>,
@@ -362,24 +358,9 @@ impl Config {
/// Creates a new configuration object with the default configuration
/// specified.
pub fn new() -> Self {
let mut flags = settings::builder();
// There are two possible traps for division, and this way
// we get the proper one if code traps.
flags
.enable("avoid_div_traps")
.expect("should be valid flag");
// We don't use probestack as a stack limit mechanism
flags
.set("enable_probestack", "false")
.expect("should be valid flag");
let mut ret = Self {
tunables: Tunables::default(),
flags,
isa_flags: cranelift_native::builder().expect("host machine is not a supported target"),
strategy: CompilationStrategy::Auto,
compiler: Compiler::builder(CompilationStrategy::Auto),
#[cfg(feature = "cache")]
cache_config: CacheConfig::new_cache_disabled(),
profiler: Arc::new(NullProfilerAgent),
@@ -417,9 +398,8 @@ impl Config {
/// This method will error if the given target triple is not supported.
pub fn target(&mut self, target: &str) -> Result<&mut Self> {
use std::str::FromStr;
self.isa_flags = wasmtime_environ::isa::lookup(
target_lexicon::Triple::from_str(target).map_err(|e| anyhow::anyhow!(e))?,
)?;
self.compiler
.target(target_lexicon::Triple::from_str(target).map_err(|e| anyhow::anyhow!(e))?)?;
Ok(self)
}
@@ -675,7 +655,7 @@ impl Config {
pub fn wasm_reference_types(&mut self, enable: bool) -> &mut Self {
self.features.reference_types = enable;
self.flags
self.compiler
.set("enable_safepoints", if enable { "true" } else { "false" })
.unwrap();
@@ -710,7 +690,7 @@ impl Config {
pub fn wasm_simd(&mut self, enable: bool) -> &mut Self {
self.features.simd = enable;
let val = if enable { "true" } else { "false" };
self.flags
self.compiler
.set("enable_simd", val)
.expect("should be valid flag");
self
@@ -801,7 +781,7 @@ impl Config {
/// itself to be set, but if they're not set and the strategy is specified
/// here then an error will be returned.
pub fn strategy(&mut self, strategy: Strategy) -> Result<&mut Self> {
self.strategy = match strategy {
let strategy = match strategy {
Strategy::Auto => CompilationStrategy::Auto,
Strategy::Cranelift => CompilationStrategy::Cranelift,
#[cfg(feature = "lightbeam")]
@@ -811,6 +791,7 @@ impl Config {
anyhow::bail!("lightbeam compilation strategy wasn't enabled at compile time");
}
};
self.compiler = Compiler::builder(strategy);
Ok(self)
}
@@ -837,7 +818,7 @@ impl Config {
/// The default value for this is `false`
pub fn cranelift_debug_verifier(&mut self, enable: bool) -> &mut Self {
let val = if enable { "true" } else { "false" };
self.flags
self.compiler
.set("enable_verifier", val)
.expect("should be valid flag");
self
@@ -856,7 +837,7 @@ impl Config {
OptLevel::Speed => "speed",
OptLevel::SpeedAndSize => "speed_and_size",
};
self.flags
self.compiler
.set("opt_level", val)
.expect("should be valid flag");
self
@@ -872,7 +853,7 @@ impl Config {
/// The default value for this is `false`
pub fn cranelift_nan_canonicalization(&mut self, enable: bool) -> &mut Self {
let val = if enable { "true" } else { "false" };
self.flags
self.compiler
.set("enable_nan_canonicalization", val)
.expect("should be valid flag");
self
@@ -893,15 +874,7 @@ impl Config {
///
/// This method can fail if the flag's name does not exist.
pub unsafe fn cranelift_flag_enable(&mut self, flag: &str) -> Result<&mut Self> {
if let Err(err) = self.flags.enable(flag) {
match err {
SetError::BadName(_) => {
// Try the target-specific flags.
self.isa_flags.enable(flag)?;
}
_ => bail!(err),
}
}
self.compiler.enable(flag)?;
Ok(self)
}
@@ -919,15 +892,7 @@ impl Config {
/// This method can fail if the flag's name does not exist, or the value is not appropriate for
/// the flag type.
pub unsafe fn cranelift_flag_set(&mut self, name: &str, value: &str) -> Result<&mut Self> {
if let Err(err) = self.flags.set(name, value) {
match err {
SetError::BadName(_) => {
// Try the target-specific flags.
self.isa_flags.set(name, value)?;
}
_ => bail!(err),
}
}
self.compiler.set(name, value)?;
Ok(self)
}
@@ -1238,25 +1203,11 @@ impl Config {
self
}
pub(crate) fn target_isa(&self) -> Box<dyn TargetIsa> {
self.isa_flags
.clone()
.finish(settings::Flags::new(self.flags.clone()))
}
pub(crate) fn target_isa_with_reference_types(&self) -> Box<dyn TargetIsa> {
let mut flags = self.flags.clone();
flags.set("enable_safepoints", "true").unwrap();
self.isa_flags.clone().finish(settings::Flags::new(flags))
}
pub(crate) fn build_compiler(&self, allocator: &dyn InstanceAllocator) -> Compiler {
let isa = self.target_isa();
let mut tunables = self.tunables.clone();
allocator.adjust_tunables(&mut tunables);
Compiler::new(
isa,
self.strategy,
&*self.compiler,
tunables,
self.features,
self.parallel_compilation,
@@ -1304,12 +1255,33 @@ impl Default for Config {
}
}
impl Clone for Config {
fn clone(&self) -> Config {
Config {
compiler: self.compiler.clone(),
tunables: self.tunables.clone(),
#[cfg(feature = "cache")]
cache_config: self.cache_config.clone(),
profiler: self.profiler.clone(),
features: self.features.clone(),
mem_creator: self.mem_creator.clone(),
allocation_strategy: self.allocation_strategy.clone(),
max_wasm_stack: self.max_wasm_stack,
wasm_backtrace_details_env_used: self.wasm_backtrace_details_env_used,
async_support: self.async_support,
#[cfg(feature = "async")]
async_stack_size: self.async_stack_size,
deserialize_check_wasmtime_version: self.deserialize_check_wasmtime_version,
parallel_compilation: self.parallel_compilation,
}
}
}
impl fmt::Debug for Config {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Config")
.field("debug_info", &self.tunables.generate_native_debuginfo)
.field("parse_wasm_debuginfo", &self.tunables.parse_wasm_debuginfo)
.field("strategy", &self.strategy)
.field("wasm_threads", &self.features.threads)
.field("wasm_reference_types", &self.features.reference_types)
.field("wasm_bulk_memory", &self.features.bulk_memory)
@@ -1333,10 +1305,8 @@ impl fmt::Debug for Config {
"guard_before_linear_memory",
&self.tunables.guard_before_linear_memory,
)
.field(
"flags",
&settings::Flags::new(self.flags.clone()).to_string(),
)
.field("parallel_compilation", &self.parallel_compilation)
.field("compiler", &self.compiler)
.finish()
}
}

View File

@@ -279,7 +279,7 @@ impl Module {
/// ```
pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result<Module> {
// Check to see that the config's target matches the host
let target = engine.config().isa_flags.triple();
let target = engine.compiler().compiler().triple();
if *target != target_lexicon::Triple::host() {
bail!(
"target '{}' specified in the configuration does not match the host",
@@ -318,9 +318,8 @@ impl Module {
let modules = CompiledModule::from_artifacts_list(
artifacts,
engine.compiler().isa(),
&*engine.config().profiler,
engine.compiler(),
&*engine.config().profiler,
)?;
Self::from_parts(engine, modules, main_module, Arc::new(types), &[])

View File

@@ -1,18 +1,14 @@
//! Implements module serialization.
use crate::{Engine, Module, OptLevel};
use crate::{Engine, Module};
use anyhow::{anyhow, bail, Context, Result};
use bincode::Options;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::fmt;
use std::collections::HashMap;
use std::str::FromStr;
use std::sync::Arc;
use std::{collections::HashMap, fmt::Display};
use wasmtime_environ::{isa::TargetIsa, settings, Tunables};
use wasmtime_jit::{
CompilationArtifacts, CompilationStrategy, CompiledModule, Compiler, TypeTables,
};
use wasmtime_environ::{FlagValue, Tunables};
use wasmtime_jit::{CompilationArtifacts, CompiledModule, Compiler, TypeTables};
const HEADER: &[u8] = b"\0wasmtime-aot";
@@ -111,16 +107,6 @@ impl<'a, 'b, T: Deserialize<'a>> Deserialize<'a> for MyCow<'b, T> {
}
}
impl From<settings::OptLevel> for OptLevel {
fn from(level: settings::OptLevel) -> Self {
match level {
settings::OptLevel::Speed => OptLevel::Speed,
settings::OptLevel::SpeedAndSize => OptLevel::SpeedAndSize,
settings::OptLevel::None => OptLevel::None,
}
}
}
/// A small helper struct for serialized module upvars.
#[derive(Serialize, Deserialize)]
pub struct SerializedModuleUpvar {
@@ -163,40 +149,11 @@ impl SerializedModuleUpvar {
}
}
#[derive(Serialize, Deserialize, Eq, PartialEq)]
enum FlagValue {
Enum(Cow<'static, str>),
Num(u8),
Bool(bool),
}
impl From<settings::Value> for FlagValue {
fn from(v: settings::Value) -> Self {
match v.kind() {
settings::SettingKind::Enum => Self::Enum(v.as_enum().unwrap().into()),
settings::SettingKind::Num => Self::Num(v.as_num().unwrap()),
settings::SettingKind::Bool => Self::Bool(v.as_bool().unwrap()),
settings::SettingKind::Preset => unreachable!(),
}
}
}
impl Display for FlagValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Enum(v) => v.fmt(f),
Self::Num(v) => v.fmt(f),
Self::Bool(v) => v.fmt(f),
}
}
}
#[derive(Serialize, Deserialize)]
pub struct SerializedModule<'a> {
target: String,
shared_flags: HashMap<String, FlagValue>,
isa_flags: HashMap<String, FlagValue>,
strategy: CompilationStrategy,
tunables: Tunables,
features: WasmFeatures,
artifacts: Vec<MyCow<'a, CompilationArtifacts>>,
@@ -250,21 +207,10 @@ impl<'a> SerializedModule<'a> {
module_upvars: Vec<SerializedModuleUpvar>,
types: MyCow<'a, TypeTables>,
) -> Self {
let isa = compiler.isa();
Self {
target: isa.triple().to_string(),
shared_flags: isa
.flags()
.iter()
.map(|v| (v.name.to_owned(), v.into()))
.collect(),
isa_flags: isa
.isa_flags()
.into_iter()
.map(|v| (v.name.to_owned(), v.into()))
.collect(),
strategy: compiler.strategy(),
target: compiler.triple().to_string(),
shared_flags: compiler.compiler().flags(),
isa_flags: compiler.compiler().isa_flags(),
tunables: compiler.tunables().clone(),
features: compiler.features().into(),
artifacts,
@@ -275,12 +221,10 @@ impl<'a> SerializedModule<'a> {
pub fn into_module(mut self, engine: &Engine) -> Result<Module> {
let compiler = engine.compiler();
let isa = compiler.isa();
self.check_triple(isa)?;
self.check_shared_flags(isa)?;
self.check_isa_flags(isa)?;
self.check_strategy(compiler)?;
self.check_triple(compiler)?;
self.check_shared_flags(compiler)?;
self.check_isa_flags(compiler)?;
self.check_tunables(compiler)?;
self.check_features(compiler)?;
@@ -289,9 +233,8 @@ impl<'a> SerializedModule<'a> {
.into_iter()
.map(|i| i.unwrap_owned())
.collect(),
engine.compiler().isa(),
&*engine.config().profiler,
engine.compiler(),
&*engine.config().profiler,
)?;
assert!(!modules.is_empty());
@@ -361,17 +304,17 @@ impl<'a> SerializedModule<'a> {
.context("deserialize compilation artifacts")?)
}
fn check_triple(&self, isa: &dyn TargetIsa) -> Result<()> {
fn check_triple(&self, compiler: &Compiler) -> Result<()> {
let triple = target_lexicon::Triple::from_str(&self.target).map_err(|e| anyhow!(e))?;
if triple.architecture != isa.triple().architecture {
if triple.architecture != compiler.triple().architecture {
bail!(
"Module was compiled for architecture '{}'",
triple.architecture
);
}
if triple.operating_system != isa.triple().operating_system {
if triple.operating_system != compiler.triple().operating_system {
bail!(
"Module was compiled for operating system '{}'",
triple.operating_system
@@ -381,13 +324,11 @@ impl<'a> SerializedModule<'a> {
Ok(())
}
fn check_shared_flags(&mut self, isa: &dyn TargetIsa) -> Result<()> {
fn check_shared_flags(&mut self, compiler: &Compiler) -> Result<()> {
let mut shared_flags = std::mem::take(&mut self.shared_flags);
for value in isa.flags().iter() {
let name = value.name;
match shared_flags.remove(name) {
for (name, host) in compiler.compiler().flags() {
match shared_flags.remove(&name) {
Some(v) => {
let host: FlagValue = value.into();
if v != host {
bail!("Module was compiled with a different '{}' setting: expected '{}' but host has '{}'", name, v, host);
}
@@ -406,12 +347,10 @@ impl<'a> SerializedModule<'a> {
Ok(())
}
fn check_isa_flags(&mut self, isa: &dyn TargetIsa) -> Result<()> {
fn check_isa_flags(&mut self, compiler: &Compiler) -> Result<()> {
let mut isa_flags = std::mem::take(&mut self.isa_flags);
for value in isa.isa_flags().into_iter() {
let name = value.name;
let host: FlagValue = value.into();
match isa_flags.remove(name) {
for (name, host) in compiler.compiler().isa_flags() {
match isa_flags.remove(&name) {
Some(v) => match (&v, &host) {
(FlagValue::Bool(v), FlagValue::Bool(host)) => {
// ISA flags represent CPU features; for boolean values, only
@@ -441,25 +380,6 @@ impl<'a> SerializedModule<'a> {
Ok(())
}
fn check_strategy(&self, compiler: &Compiler) -> Result<()> {
#[allow(unreachable_patterns)]
let matches = match (self.strategy, compiler.strategy()) {
(CompilationStrategy::Auto, CompilationStrategy::Auto)
| (CompilationStrategy::Auto, CompilationStrategy::Cranelift)
| (CompilationStrategy::Cranelift, CompilationStrategy::Auto)
| (CompilationStrategy::Cranelift, CompilationStrategy::Cranelift) => true,
#[cfg(feature = "lightbeam")]
(CompilationStrategy::Lightbeam, CompilationStrategy::Lightbeam) => true,
_ => false,
};
if !matches {
bail!("Module was compiled with strategy '{:?}'", self.strategy);
}
Ok(())
}
fn check_int<T: Eq + std::fmt::Display>(found: T, expected: T, feature: &str) -> Result<()> {
if found == expected {
return Ok(());
@@ -610,6 +530,7 @@ impl<'a> SerializedModule<'a> {
mod test {
use super::*;
use crate::Config;
use std::borrow::Cow;
#[test]
fn test_architecture_mismatch() -> Result<()> {

View File

@@ -77,19 +77,14 @@ pub fn create_function(
func: Box<dyn Fn(*mut VMContext, *mut u128) -> Result<(), Trap> + Send + Sync>,
engine: &Engine,
) -> Result<(InstanceHandle, VMTrampoline)> {
// Note that we specifically enable reference types here in our ISA because
// `Func::new` is intended to be infallible, but our signature may use
// reference types which requires safepoints.
let isa = &*engine.config().target_isa_with_reference_types();
let wasm_trampoline = engine.compiler().compiler().wasm_to_host_trampoline(
isa,
ft.as_wasm_func_type(),
stub_fn as usize,
)?;
let wasm_trampoline = engine
.compiler()
.compiler()
.wasm_to_host_trampoline(ft.as_wasm_func_type(), stub_fn as usize)?;
let host_trampoline = engine
.compiler()
.compiler()
.host_to_wasm_trampoline(isa, ft.as_wasm_func_type())?;
.host_to_wasm_trampoline(ft.as_wasm_func_type())?;
let mut code_memory = CodeMemory::new();
let host_trampoline = code_memory
@@ -98,7 +93,7 @@ pub fn create_function(
let wasm_trampoline =
code_memory.allocate_for_function(&wasm_trampoline)? as *mut [VMFunctionBody];
code_memory.publish(isa);
code_memory.publish(engine.compiler());
let sig = engine.signatures().register(ft.as_wasm_func_type());

View File

@@ -17,7 +17,6 @@ pub fn create_global(store: &mut StoreOpaque<'_>, gt: &GlobalType, val: Val) ->
let global = wasm::Global {
wasm_ty: gt.content().to_wasm_type(),
ty: gt.content().get_wasmtime_type(),
mutability: match gt.mutability() {
Mutability::Const => false,
Mutability::Var => true,

View File

@@ -1,6 +1,6 @@
use std::fmt;
use wasmtime_environ::wasm;
use wasmtime_environ::wasm::{EntityType, WasmFuncType};
use wasmtime_environ::{ir, wasm};
use wasmtime_jit::TypeTables;
pub(crate) mod matching;
@@ -71,18 +71,6 @@ impl ValType {
}
}
pub(crate) fn get_wasmtime_type(&self) -> ir::Type {
match self {
ValType::I32 => ir::types::I32,
ValType::I64 => ir::types::I64,
ValType::F32 => ir::types::F32,
ValType::F64 => ir::types::F64,
ValType::V128 => ir::types::I8X16,
ValType::ExternRef => wasmtime_runtime::ref_type(),
ValType::FuncRef => wasmtime_runtime::pointer_type(),
}
}
pub(crate) fn to_wasm_type(&self) -> wasm::WasmType {
match self {
Self::I32 => wasm::WasmType::I32,
@@ -334,10 +322,6 @@ impl TableType {
pub fn new(element: ValType, min: u32, max: Option<u32>) -> TableType {
TableType {
ty: wasm::Table {
ty: match element {
ValType::FuncRef => wasm::TableElementType::Func,
_ => wasm::TableElementType::Val(element.get_wasmtime_type()),
},
wasm_ty: element.to_wasm_type(),
minimum: min,
maximum: max,

View File

@@ -22,10 +22,7 @@ impl MatchCx<'_> {
}
fn global_ty(&self, expected: &Global, actual: &Global) -> Result<()> {
if expected.ty == actual.ty
&& expected.wasm_ty == actual.wasm_ty
&& expected.mutability == actual.mutability
{
if expected.wasm_ty == actual.wasm_ty && expected.mutability == actual.mutability {
Ok(())
} else {
bail!("global types incompatible")
@@ -38,7 +35,6 @@ impl MatchCx<'_> {
fn table_ty(&self, expected: &Table, actual: &Table) -> Result<()> {
if expected.wasm_ty == actual.wasm_ty
&& expected.ty == actual.ty
&& expected.minimum <= actual.minimum
&& match expected.maximum {
Some(expected) => match actual.maximum {

View File

@@ -1,9 +1,11 @@
//! The module that implements the `wasmtime settings` command.
use anyhow::{anyhow, Result};
use std::collections::BTreeMap;
use std::str::FromStr;
use structopt::StructOpt;
use wasmtime_environ::settings::{self, Setting, SettingKind};
use wasmtime_environ::{FlagValue, Setting, SettingKind};
use wasmtime_jit::Compiler;
/// Displays available Cranelift settings for a target.
#[derive(StructOpt)]
@@ -17,19 +19,18 @@ pub struct SettingsCommand {
impl SettingsCommand {
/// Executes the command.
pub fn execute(self) -> Result<()> {
let settings = match &self.target {
Some(target) => wasmtime_environ::isa::lookup(
target_lexicon::Triple::from_str(target).map_err(|e| anyhow!(e))?,
)?,
None => cranelift_native::builder().unwrap(),
};
let mut builder = Compiler::builder(wasmtime_jit::CompilationStrategy::Auto);
if let Some(target) = &self.target {
let target = target_lexicon::Triple::from_str(target).map_err(|e| anyhow!(e))?;
builder.target(target)?;
}
let mut enums = (Vec::new(), 0, "Enum settings:");
let mut nums = (Vec::new(), 0, "Numerical settings:");
let mut bools = (Vec::new(), 0, "Boolean settings:");
let mut presets = (Vec::new(), 0, "Presets:");
for setting in settings.iter() {
for setting in builder.settings() {
let (collection, max, _) = match setting.kind {
SettingKind::Enum => &mut enums,
SettingKind::Num => &mut nums,
@@ -45,11 +46,11 @@ impl SettingsCommand {
}
if enums.0.is_empty() && nums.0.is_empty() && bools.0.is_empty() && presets.0.is_empty() {
println!("Target '{}' has no settings.", settings.triple());
println!("Target '{}' has no settings.", builder.triple());
return Ok(());
}
println!("Cranelift settings for target '{}':", settings.triple());
println!("Cranelift settings for target '{}':", builder.triple());
for (collection, max, header) in &mut [enums, nums, bools, presets] {
if collection.is_empty() {
@@ -62,16 +63,15 @@ impl SettingsCommand {
}
if self.target.is_none() {
let isa = settings.finish(settings::Flags::new(settings::builder()));
let compiler = builder.build();
println!();
println!("Settings inferred for the current host:");
let mut values = isa.isa_flags();
values.sort_by_key(|k| k.name);
let values = compiler.isa_flags().into_iter().collect::<BTreeMap<_, _>>();
for value in values {
if value.as_bool().unwrap_or(false) {
println!(" {}", value.name);
for (name, value) in values {
if let FlagValue::Bool(true) = value {
println!(" {}", name);
}
}
}

View File

@@ -304,10 +304,10 @@ impl CommonOptions {
}
config
.strategy(pick_compilation_strategy(self.cranelift, self.lightbeam)?)?
.cranelift_debug_verifier(self.enable_cranelift_debug_verifier)
.debug_info(self.debug_info)
.cranelift_opt_level(self.opt_level())
.strategy(pick_compilation_strategy(self.cranelift, self.lightbeam)?)?
.profiler(pick_profiling_strategy(self.jitdump, self.vtune)?)?
.cranelift_nan_canonicalization(self.enable_cranelift_nan_canonicalization);

View File

@@ -3,7 +3,7 @@ use object::write::Object;
use target_lexicon::Triple;
use wasmparser::WasmFeatures;
use wasmtime::Strategy;
use wasmtime_environ::{settings, settings::Configurable, ModuleEnvironment, Tunables};
use wasmtime_environ::{ModuleEnvironment, Tunables};
use wasmtime_jit::Compiler;
/// Creates object file from binary wasm data.
@@ -15,57 +15,44 @@ pub fn compile_to_obj(
opt_level: wasmtime::OptLevel,
debug_info: bool,
) -> Result<Object> {
let isa_builder = match target {
Some(target) => wasmtime_environ::isa::lookup(target.clone())?,
None => cranelift_native::builder().unwrap(),
let strategy = 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),
};
let mut flag_builder = settings::builder();
let mut builder = Compiler::builder(strategy);
if let Some(target) = target {
builder.target(target.clone())?;
}
let mut features = WasmFeatures::default();
// There are two possible traps for division, and this way
// we get the proper one if code traps.
flag_builder.enable("avoid_div_traps").unwrap();
if enable_simd {
flag_builder.enable("enable_simd").unwrap();
builder.enable("enable_simd").unwrap();
features.simd = true;
}
match opt_level {
wasmtime::OptLevel::None => {}
wasmtime::OptLevel::Speed => {
flag_builder.set("opt_level", "speed").unwrap();
builder.set("opt_level", "speed").unwrap();
}
wasmtime::OptLevel::SpeedAndSize => {
flag_builder.set("opt_level", "speed_and_size").unwrap();
builder.set("opt_level", "speed_and_size").unwrap();
}
other => bail!("unknown optimization level {:?}", other),
}
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
// TODO: Expose the tunables as command-line flags.
let mut tunables = Tunables::default();
tunables.generate_native_debuginfo = debug_info;
tunables.parse_wasm_debuginfo = debug_info;
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(),
features.clone(),
true, // enable parallel compilation
);
let environ = ModuleEnvironment::new(compiler.isa().frontend_config(), &tunables, &features);
let compiler = Compiler::new(&*builder, tunables.clone(), features.clone(), true);
let environ = ModuleEnvironment::new(&tunables, &features);
let (_main_module, mut translation, types) = environ
.translate(wasm)
.context("failed to translate module")?;

View File

@@ -514,6 +514,7 @@ fn pass_cross_store_arg() -> anyhow::Result<()> {
}
#[test]
#[cfg_attr(feature = "old-x86-backend", ignore)]
fn externref_signature_no_reference_types() -> anyhow::Result<()> {
let mut config = Config::new();
config.wasm_reference_types(false);

View File

@@ -25,8 +25,9 @@ fn run_wast(wast: &str, strategy: Strategy, pooling: bool) -> anyhow::Result<()>
// by reference types.
let reftypes = simd || feature_found(wast, "reference-types");
// Threads aren't implemented in the old backend, so skip those tests.
if threads && cfg!(feature = "old-x86-backend") {
// Threads & simd aren't implemented in the old backend, so skip those
// tests.
if (threads || simd) && cfg!(feature = "old-x86-backend") {
return Ok(());
}