diff --git a/Cargo.lock b/Cargo.lock index 4c84df1ef3..b29cd4b615 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/build.rs b/build.rs index 278bf8b018..ad7d10f465 100644 --- a/build.rs +++ b/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, _ => {} diff --git a/cranelift/wasm/src/environ/dummy.rs b/cranelift/wasm/src/environ/dummy.rs index c26e2b5f8f..53ecf5d259 100644 --- a/cranelift/wasm/src/environ/dummy.rs +++ b/cranelift/wasm/src/environ/dummy.rs @@ -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, + }, }) } diff --git a/cranelift/wasm/src/environ/spec.rs b/cranelift/wasm/src/environ/spec.rs index 333deac4c1..a4a0e2ea3e 100644 --- a/cranelift/wasm/src/environ/spec.rs +++ b/cranelift/wasm/src/environ/spec.rs @@ -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<()> { diff --git a/cranelift/wasm/src/sections_translator.rs b/cranelift/wasm/src/sections_translator.rs index eebca487ea..aeb8e1e55f 100644 --- a/cranelift/wasm/src/sections_translator.rs +++ b/cranelift/wasm/src/sections_translator.rs @@ -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 { +fn table(ty: TableType) -> WasmResult
{ 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 { +fn global(ty: GlobalType, initializer: GlobalInit) -> WasmResult { 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)?; } diff --git a/cranelift/wasm/src/translation_utils.rs b/cranelift/wasm/src/translation_utils.rs index 6d89ad398e..0a1d43c9eb 100644 --- a/cranelift/wasm/src/translation_utils.rs +++ b/cranelift/wasm/src/translation_utils.rs @@ -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. diff --git a/crates/cranelift/Cargo.toml b/crates/cranelift/Cargo.toml index 5df4ea5f38..7b5aa944be 100644 --- a/crates/cranelift/Cargo.toml +++ b/crates/cranelift/Cargo.toml @@ -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"] diff --git a/crates/cranelift/src/builder.rs b/crates/cranelift/src/builder.rs new file mode 100644 index 0000000000..f7f6c1dba2 --- /dev/null +++ b/crates/cranelift/src/builder.rs @@ -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 { + 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 { + 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 { + let isa = self + .isa_flags + .clone() + .finish(settings::Flags::new(self.flags.clone())); + Box::new(crate::compiler::Compiler::new(isa)) + } + + fn settings(&self) -> Vec { + 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() + } +} diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs new file mode 100644 index 0000000000..fe0f4cdb2a --- /dev/null +++ b/crates/cranelift/src/compiler.rs @@ -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>, + isa: Box, +} + +impl Compiler { + pub(crate) fn new(isa: Box) -> 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::>(); + 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 { + 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 = 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 { + let isa = &*self.isa; + let value_size = mem::size_of::(); + 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::>(); + + // 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 { + 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::(); + 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> { + 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 { + self.isa.create_systemv_cie() + } + + fn flags(&self) -> HashMap { + self.isa + .flags() + .iter() + .map(|val| (val.name.to_string(), to_flag_value(&val))) + .collect() + } + + fn isa_flags(&self) -> HashMap { + 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 { + 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, +) -> Vec { + 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, +} + +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, +} + +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, +} + +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 { + 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, +} + +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"); + } +} diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index 5fad01a24d..209a537853 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -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), }) } diff --git a/crates/cranelift/src/lib.rs b/crates/cranelift/src/lib.rs index 601cd4a496..29924451cb 100644 --- a/crates/cranelift/src/lib.rs +++ b/crates/cranelift/src/lib.rs @@ -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, -} - -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, -} - -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, -} - -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 { - 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::>(); - 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, -) -> Vec { - 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>, -} - -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 { - 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 = 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 { - let value_size = mem::size_of::(); - 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::>(); - - // 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 { - 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::(); - 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 { - 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, -} - -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"); - } -} diff --git a/crates/debug/src/lib.rs b/crates/debug/src/lib.rs index db9e664834..954674f3c0 100644 --- a/crates/debug/src/lib.rs +++ b/crates/debug/src/lib.rs @@ -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, - code_region: (*const u8, usize), - defined_funcs_offset: usize, - funcs: &[*const u8], -) -> Result, 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::(&mut bytes, code_region) - } - Endianness::Big => { - convert_object_elf_to_loadable_file::(&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::()?; - 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 { - 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::::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::>(), - Endianness::Big => size_of::>(), - }; - ensure!(e_shentsize as usize == req_shentsize, "size of sh"); - Ok(e) -} - -fn convert_object_elf_to_loadable_file( - bytes: &mut Vec, - 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 = 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 = - 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 = - 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::>(); - 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 = - 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 = - 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); -} diff --git a/crates/debug/src/write_debuginfo.rs b/crates/debug/src/write_debuginfo.rs index 491267b495..3d7b1451da 100644 --- a/crates/debug/src/write_debuginfo.rs +++ b/crates/debug/src/write_debuginfo.rs @@ -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, - pub relocs: Vec, -} +pub use wasmtime_environ::{DwarfSection, DwarfSectionReloc, DwarfSectionRelocTarget}; fn emit_dwarf_sections( isa: &dyn TargetIsa, diff --git a/crates/environ/Cargo.toml b/crates/environ/Cargo.toml index 7aec79ce10..b8a5e9869b 100644 --- a/crates/environ/Cargo.toml +++ b/crates/environ/Cargo.toml @@ -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" } diff --git a/crates/environ/src/compilation.rs b/crates/environ/src/compilation.rs index 5490e55cd9..551804aba7 100644 --- a/crates/environ/src/compilation.rs +++ b/crates/environ/src/compilation.rs @@ -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; + + /// 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; + + /// Builds a new [`Compiler`] object from this configuration. + fn build(&self) -> Box; +} + +/// 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; - /// 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; + /// 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; /// 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; + + /// 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>; + + /// 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; + + /// Returns a list of configured settings for this compiler. + fn flags(&self) -> HashMap; + + /// Same as [`Compiler::flags`], but ISA-specific (a cranelift-ism) + fn isa_flags(&self) -> HashMap; +} + +#[allow(missing_docs)] +pub struct DwarfSection { + pub name: &'static str, + pub body: Vec, + pub relocs: Vec, +} + +#[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), + } + } } diff --git a/crates/environ/src/data_structures.rs b/crates/environ/src/data_structures.rs index 29e87cb8d1..7f6d23d4e4 100644 --- a/crates/environ/src/data_structures.rs +++ b/crates/environ/src/data_structures.rs @@ -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, diff --git a/crates/environ/src/module_environ.rs b/crates/environ/src/module_environ.rs index a404701370..320d647f29 100644 --- a/crates/environ/src/module_environ.rs +++ b/crates/environ/src/module_environ.rs @@ -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> { diff --git a/crates/jit/Cargo.toml b/crates/jit/Cargo.toml index 5c96a98c23..c7db67080f 100644 --- a/crates/jit/Cargo.toml +++ b/crates/jit/Cargo.toml @@ -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 } diff --git a/crates/jit/src/code_memory.rs b/crates/jit/src/code_memory.rs index 89f790abc0..2b943d8f59 100644 --- a/crates/jit/src/code_memory.rs +++ b/crates/jit/src/code_memory.rs @@ -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() { diff --git a/crates/jit/src/compiler.rs b/crates/jit/src/compiler.rs index 02c7842b08..a94dc8eae4 100644 --- a/crates/jit/src/compiler.rs +++ b/crates/jit/src/compiler.rs @@ -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, compiler: Box, - 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 { + 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, - 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::(); } -fn transform_dwarf_data( - isa: &dyn TargetIsa, - module: &Module, - debug_data: &DebugInfoData, - funcs: &CompiledFunctions, -) -> Result, 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::(); 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(&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::>() + .hash(hasher); + compiler + .isa_flags() + .into_iter() + .collect::>() + .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. } } diff --git a/crates/jit/src/debug.rs b/crates/jit/src/debug.rs new file mode 100644 index 0000000000..f7bdd77b27 --- /dev/null +++ b/crates/jit/src/debug.rs @@ -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, + code_region: (*const u8, usize), + defined_funcs_offset: usize, + funcs: &[*const u8], +) -> Result, 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::(&mut bytes, code_region) + } + Endianness::Big => { + convert_object_elf_to_loadable_file::(&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::()?; + 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 { + 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::::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::>(), + Endianness::Big => size_of::>(), + }; + ensure!(e_shentsize as usize == req_shentsize, "size of sh"); + Ok(e) +} + +fn convert_object_elf_to_loadable_file( + bytes: &mut Vec, + 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 = 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 = + 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 = + 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::>(); + 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 = + 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 = + 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); +} diff --git a/crates/jit/src/instantiate.rs b/crates/jit/src/instantiate.rs index bc88ca638a..f333a0b9e4 100644 --- a/crates/jit/src/instantiate.rs +++ b/crates/jit/src/instantiate.rs @@ -5,6 +5,7 @@ use crate::code_memory::CodeMemory; use crate::compiler::{Compilation, Compiler}; +use crate::debug::create_gdbjit_image; use crate::link::link_module; use crate::object::ObjectUnwindInfo; use anyhow::{Context, Result}; @@ -13,9 +14,7 @@ use serde::{Deserialize, Serialize}; use std::ops::Range; use std::sync::Arc; use thiserror::Error; -use wasmtime_debug::create_gdbjit_image; use wasmtime_environ::entity::PrimaryMap; -use wasmtime_environ::isa::TargetIsa; use wasmtime_environ::wasm::{ DefinedFuncIndex, InstanceTypeIndex, ModuleTypeIndex, SignatureIndex, WasmFuncType, }; @@ -103,13 +102,10 @@ impl CompilationArtifacts { data: &[u8], use_paged_mem_init: bool, ) -> Result<(usize, Vec, TypeTables), SetupError> { - let (main_module, translations, types) = ModuleEnvironment::new( - compiler.frontend_config(), - compiler.tunables(), - compiler.features(), - ) - .translate(data) - .map_err(|error| SetupError::Compile(CompileError::Wasm(error)))?; + let (main_module, translations, types) = + ModuleEnvironment::new(compiler.tunables(), compiler.features()) + .translate(data) + .map_err(|error| SetupError::Compile(CompileError::Wasm(error)))?; let list = compiler.run_maybe_parallel::<_, _, SetupError, _>( translations, @@ -225,25 +221,24 @@ impl CompiledModule { /// artifacts. pub fn from_artifacts_list( artifacts: Vec, - isa: &dyn TargetIsa, - profiler: &dyn ProfilingAgent, compiler: &Compiler, + profiler: &dyn ProfilingAgent, ) -> Result>, 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, 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)) } diff --git a/crates/jit/src/lib.rs b/crates/jit/src/lib.rs index aa75e68236..f35010d1b3 100644 --- a/crates/jit/src/lib.rs +++ b/crates/jit/src/lib.rs @@ -22,6 +22,7 @@ mod code_memory; mod compiler; +mod debug; mod instantiate; mod link; mod object; diff --git a/crates/jit/src/object.rs b/crates/jit/src/object.rs index b20dc8c7ee..2953490324 100644 --- a/crates/jit/src/object.rs +++ b/crates/jit/src/object.rs @@ -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) diff --git a/crates/jit/src/unwind/systemv.rs b/crates/jit/src/unwind/systemv.rs index d29819d30f..c8a9142b06 100644 --- a/crates/jit/src/unwind/systemv.rs +++ b/crates/jit/src/unwind/systemv.rs @@ -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"), }); diff --git a/crates/jit/src/unwind/winx64.rs b/crates/jit/src/unwind/winx64.rs index f46d2edfd3..260a8cf629 100644 --- a/crates/jit/src/unwind/winx64.rs +++ b/crates/jit/src/unwind/winx64.rs @@ -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"); } diff --git a/crates/lightbeam/wasmtime/Cargo.toml b/crates/lightbeam/wasmtime/Cargo.toml index a2c2e48906..c316bbbac2 100644 --- a/crates/lightbeam/wasmtime/Cargo.toml +++ b/crates/lightbeam/wasmtime/Cargo.toml @@ -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" } diff --git a/crates/lightbeam/wasmtime/src/lib.rs b/crates/lightbeam/wasmtime/src/lib.rs index 338ccab118..3b98f719ac 100644 --- a/crates/lightbeam/wasmtime/src/lib.rs +++ b/crates/lightbeam/wasmtime/src/lib.rs @@ -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 { - 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 { 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 { unimplemented!() } + + fn emit_dwarf( + &self, + _debuginfo_data: &DebugInfoData, + _funcs: &CompiledFunctions, + _memory_offset: &crate::ModuleMemoryOffset, + ) -> Result> { + unimplemented!() + } + + fn triple(&self) -> &target_lexicon::Triple { + unimplemented!() + } + + fn create_systemv_cie(&self) -> Option { + unimplemented!() + } + + fn flags(&self) -> HashMap { + unimplemented!() + } + + fn isa_flags(&self) -> HashMap { + 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 { diff --git a/crates/obj/Cargo.toml b/crates/obj/Cargo.toml index 7d2bc21b96..5e7a4f4975 100644 --- a/crates/obj/Cargo.toml +++ b/crates/obj/Cargo.toml @@ -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" } diff --git a/crates/obj/src/builder.rs b/crates/obj/src/builder.rs index f606d72078..becc8264eb 100644 --- a/crates/obj/src/builder.rs +++ b/crates/obj/src/builder.rs @@ -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 + 'a, diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index 07311abb65..1324b771fa 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -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)?; } diff --git a/crates/runtime/src/instance/allocator/pooling.rs b/crates/runtime/src/instance/allocator/pooling.rs index a886ea1c06..9a792bfbad 100644 --- a/crates/runtime/src/instance/allocator/pooling.rs +++ b/crates/runtime/src/instance/allocator/pooling.rs @@ -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, }, diff --git a/crates/runtime/src/libcalls.rs b/crates/runtime/src/libcalls.rs index b5cf8874f7..17ee935faf 100644 --- a/crates/runtime/src/libcalls.rs +++ b/crates/runtime/src/libcalls.rs @@ -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 { diff --git a/crates/runtime/src/table.rs b/crates/runtime/src/table.rs index 6045af8301..d2dbcc5c56 100644 --- a/crates/runtime/src/table.rs +++ b/crates/runtime/src/table.rs @@ -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), } +#[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 { + 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::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::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 { diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index 8bf29e6a2f..4c0ee28516 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -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" } diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 5eb4b46c93..ca28bda5a9 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -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, pub(crate) tunables: Tunables, - pub(crate) strategy: CompilationStrategy, #[cfg(feature = "cache")] pub(crate) cache_config: CacheConfig, pub(crate) profiler: Arc, @@ -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 { - self.isa_flags - .clone() - .finish(settings::Flags::new(self.flags.clone())) - } - - pub(crate) fn target_isa_with_reference_types(&self) -> Box { - 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() } } diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index d6b3b6a7ba..a862d84244 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -279,7 +279,7 @@ impl Module { /// ``` pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result { // 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), &[]) diff --git a/crates/wasmtime/src/module/serialization.rs b/crates/wasmtime/src/module/serialization.rs index 2918d7ffc6..507af1fdba 100644 --- a/crates/wasmtime/src/module/serialization.rs +++ b/crates/wasmtime/src/module/serialization.rs @@ -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 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 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, isa_flags: HashMap, - strategy: CompilationStrategy, tunables: Tunables, features: WasmFeatures, artifacts: Vec>, @@ -250,21 +207,10 @@ impl<'a> SerializedModule<'a> { module_upvars: Vec, 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 { 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(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<()> { diff --git a/crates/wasmtime/src/trampoline/func.rs b/crates/wasmtime/src/trampoline/func.rs index dbe3299b07..e47f83d04b 100644 --- a/crates/wasmtime/src/trampoline/func.rs +++ b/crates/wasmtime/src/trampoline/func.rs @@ -77,19 +77,14 @@ pub fn create_function( func: Box 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()); diff --git a/crates/wasmtime/src/trampoline/global.rs b/crates/wasmtime/src/trampoline/global.rs index 20529de888..1f6080e229 100644 --- a/crates/wasmtime/src/trampoline/global.rs +++ b/crates/wasmtime/src/trampoline/global.rs @@ -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, diff --git a/crates/wasmtime/src/types.rs b/crates/wasmtime/src/types.rs index 9c46930342..03d96109e2 100644 --- a/crates/wasmtime/src/types.rs +++ b/crates/wasmtime/src/types.rs @@ -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) -> 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, diff --git a/crates/wasmtime/src/types/matching.rs b/crates/wasmtime/src/types/matching.rs index 851cff3fba..2fcc114f48 100644 --- a/crates/wasmtime/src/types/matching.rs +++ b/crates/wasmtime/src/types/matching.rs @@ -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 { diff --git a/src/commands/settings.rs b/src/commands/settings.rs index 28d53ac6f8..b063569029 100644 --- a/src/commands/settings.rs +++ b/src/commands/settings.rs @@ -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::>(); - 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); } } } diff --git a/src/lib.rs b/src/lib.rs index b8630d5a13..6403270ce2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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); diff --git a/src/obj.rs b/src/obj.rs index 0166c6c580..3ef7498dc2 100644 --- a/src/obj.rs +++ b/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,57 +15,44 @@ pub fn compile_to_obj( opt_level: wasmtime::OptLevel, debug_info: bool, ) -> Result { - let isa_builder = match target { - Some(target) => wasmtime_environ::isa::lookup(target.clone())?, - None => cranelift_native::builder().unwrap(), + let strategy = match strategy { + Strategy::Auto => wasmtime_jit::CompilationStrategy::Auto, + Strategy::Cranelift => wasmtime_jit::CompilationStrategy::Cranelift, + #[cfg(feature = "lightbeam")] + Strategy::Lightbeam => wasmtime_jit::CompilationStrategy::Lightbeam, + #[cfg(not(feature = "lightbeam"))] + Strategy::Lightbeam => bail!("lightbeam support not enabled"), + s => bail!("unknown compilation strategy {:?}", s), }; - let mut flag_builder = settings::builder(); + let mut builder = Compiler::builder(strategy); + if let Some(target) = target { + builder.target(target.clone())?; + } let mut features = WasmFeatures::default(); - // There are two possible traps for division, and this way - // we get the proper one if code traps. - flag_builder.enable("avoid_div_traps").unwrap(); - if enable_simd { - flag_builder.enable("enable_simd").unwrap(); + builder.enable("enable_simd").unwrap(); features.simd = true; } match opt_level { wasmtime::OptLevel::None => {} wasmtime::OptLevel::Speed => { - flag_builder.set("opt_level", "speed").unwrap(); + builder.set("opt_level", "speed").unwrap(); } wasmtime::OptLevel::SpeedAndSize => { - flag_builder.set("opt_level", "speed_and_size").unwrap(); + builder.set("opt_level", "speed_and_size").unwrap(); } other => bail!("unknown optimization level {:?}", other), } - let isa = isa_builder.finish(settings::Flags::new(flag_builder)); - // TODO: Expose the tunables as command-line flags. let mut tunables = Tunables::default(); tunables.generate_native_debuginfo = debug_info; tunables.parse_wasm_debuginfo = debug_info; - let compiler = Compiler::new( - isa, - match strategy { - Strategy::Auto => wasmtime_jit::CompilationStrategy::Auto, - Strategy::Cranelift => wasmtime_jit::CompilationStrategy::Cranelift, - #[cfg(feature = "lightbeam")] - Strategy::Lightbeam => wasmtime_jit::CompilationStrategy::Lightbeam, - #[cfg(not(feature = "lightbeam"))] - Strategy::Lightbeam => bail!("lightbeam support not enabled"), - s => bail!("unknown compilation strategy {:?}", s), - }, - tunables.clone(), - features.clone(), - true, // enable parallel compilation - ); - - let environ = ModuleEnvironment::new(compiler.isa().frontend_config(), &tunables, &features); + let compiler = Compiler::new(&*builder, tunables.clone(), features.clone(), true); + let environ = ModuleEnvironment::new(&tunables, &features); let (_main_module, mut translation, types) = environ .translate(wasm) .context("failed to translate module")?; diff --git a/tests/all/func.rs b/tests/all/func.rs index 8b000c1391..64d0b3e63e 100644 --- a/tests/all/func.rs +++ b/tests/all/func.rs @@ -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); diff --git a/tests/all/wast.rs b/tests/all/wast.rs index 078753b940..192e4c95b8 100644 --- a/tests/all/wast.rs +++ b/tests/all/wast.rs @@ -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(()); }