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:
12
Cargo.lock
generated
12
Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
3
build.rs
3
build.rs
@@ -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,
|
||||
_ => {}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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<()> {
|
||||
|
||||
@@ -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)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"]
|
||||
|
||||
113
crates/cranelift/src/builder.rs
Normal file
113
crates/cranelift/src/builder.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
660
crates/cranelift/src/compiler.rs
Normal file
660
crates/cranelift/src/compiler.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
212
crates/jit/src/debug.rs
Normal 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);
|
||||
}
|
||||
@@ -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,11 +102,8 @@ 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(),
|
||||
)
|
||||
let (main_module, translations, types) =
|
||||
ModuleEnvironment::new(compiler.tunables(), compiler.features())
|
||||
.translate(data)
|
||||
.map_err(|error| SetupError::Compile(CompileError::Wasm(error)))?;
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
mod code_memory;
|
||||
mod compiler;
|
||||
mod debug;
|
||||
mod instantiate;
|
||||
mod link;
|
||||
mod object;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"),
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)?;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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), &[])
|
||||
|
||||
@@ -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<()> {
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
75
src/obj.rs
75
src/obj.rs
@@ -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,43 +15,7 @@ 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 mut flag_builder = settings::builder();
|
||||
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();
|
||||
features.simd = true;
|
||||
}
|
||||
|
||||
match opt_level {
|
||||
wasmtime::OptLevel::None => {}
|
||||
wasmtime::OptLevel::Speed => {
|
||||
flag_builder.set("opt_level", "speed").unwrap();
|
||||
}
|
||||
wasmtime::OptLevel::SpeedAndSize => {
|
||||
flag_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 {
|
||||
let strategy = match strategy {
|
||||
Strategy::Auto => wasmtime_jit::CompilationStrategy::Auto,
|
||||
Strategy::Cranelift => wasmtime_jit::CompilationStrategy::Cranelift,
|
||||
#[cfg(feature = "lightbeam")]
|
||||
@@ -59,13 +23,36 @@ pub fn compile_to_obj(
|
||||
#[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 mut builder = Compiler::builder(strategy);
|
||||
if let Some(target) = target {
|
||||
builder.target(target.clone())?;
|
||||
}
|
||||
let mut features = WasmFeatures::default();
|
||||
|
||||
let environ = ModuleEnvironment::new(compiler.isa().frontend_config(), &tunables, &features);
|
||||
if enable_simd {
|
||||
builder.enable("enable_simd").unwrap();
|
||||
features.simd = true;
|
||||
}
|
||||
|
||||
match opt_level {
|
||||
wasmtime::OptLevel::None => {}
|
||||
wasmtime::OptLevel::Speed => {
|
||||
builder.set("opt_level", "speed").unwrap();
|
||||
}
|
||||
wasmtime::OptLevel::SpeedAndSize => {
|
||||
builder.set("opt_level", "speed_and_size").unwrap();
|
||||
}
|
||||
other => bail!("unknown optimization level {:?}", other),
|
||||
}
|
||||
|
||||
// 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(&*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")?;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user