From 09c6c5db4472823c32c46e192fee5eb3f950c620 Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Fri, 21 Feb 2020 18:14:37 -0500 Subject: [PATCH] add a "raw" function definition interface to cranelift-module (#1400) * move trap site definitions into cranelift-module `cranelift-faerie` and `cranelift-object` already have identical definitions of structures to represent trap sites. We might as well merge them ahead of work to define functions via a raw slice of bytes with associated traps, which will need some kind of common structure for representing traps anyway. * cranelift-module: add `define_function_bytes` interface This interface is useful when the client needs to precisely specify the ordering of bytes in a particular function. * add comment about saving files for `perf` --- cranelift/faerie/src/backend.rs | 28 ++++++++++++++- cranelift/faerie/src/traps.rs | 25 +++++++------ cranelift/module/src/backend.rs | 14 ++++++++ cranelift/module/src/lib.rs | 2 ++ cranelift/module/src/module.rs | 47 ++++++++++++++++++++++++ cranelift/module/src/traps.rs | 14 ++++++++ cranelift/object/src/backend.rs | 25 ++++++++++--- cranelift/object/src/lib.rs | 2 +- cranelift/object/src/traps.rs | 16 ++------- cranelift/simplejit/src/backend.rs | 57 ++++++++++++++++++++++++------ 10 files changed, 188 insertions(+), 42 deletions(-) create mode 100644 cranelift/module/src/traps.rs diff --git a/cranelift/faerie/src/backend.rs b/cranelift/faerie/src/backend.rs index c0cb086a6d..e6439a69b9 100644 --- a/cranelift/faerie/src/backend.rs +++ b/cranelift/faerie/src/backend.rs @@ -10,9 +10,10 @@ use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::{self, binemit, ir}; use cranelift_module::{ Backend, DataContext, DataDescription, DataId, FuncId, Init, Linkage, ModuleError, - ModuleNamespace, ModuleResult, + ModuleNamespace, ModuleResult, TrapSite, }; use faerie; +use std::convert::TryInto; use std::fs::File; use target_lexicon::Triple; @@ -200,6 +201,31 @@ impl Backend for FaerieBackend { Ok(FaerieCompiledFunction { code_length }) } + fn define_function_bytes( + &mut self, + _id: FuncId, + name: &str, + bytes: &[u8], + _namespace: &ModuleNamespace, + traps: Vec, + ) -> ModuleResult { + let code_length: u32 = match bytes.len().try_into() { + Ok(code_length) => code_length, + _ => Err(ModuleError::FunctionTooLarge(name.to_string()))?, + }; + + if let Some(ref mut trap_manifest) = self.trap_manifest { + let trap_sink = FaerieTrapSink::new_with_sites(name, code_length, traps); + trap_manifest.add_sink(trap_sink); + } + + self.artifact + .define(name, bytes.to_vec()) + .expect("inconsistent declaration"); + + Ok(FaerieCompiledFunction { code_length }) + } + fn define_data( &mut self, _id: DataId, diff --git a/cranelift/faerie/src/traps.rs b/cranelift/faerie/src/traps.rs index d01619967b..b84f171955 100644 --- a/cranelift/faerie/src/traps.rs +++ b/cranelift/faerie/src/traps.rs @@ -2,17 +2,7 @@ //! for every function in the module. This data may be useful at runtime. use cranelift_codegen::{binemit, ir}; - -/// Record of the arguments cranelift passes to `TrapSink::trap` -#[derive(Debug)] -pub struct FaerieTrapSite { - /// Offset into function - pub offset: binemit::CodeOffset, - /// Source location given to cranelift - pub srcloc: ir::SourceLoc, - /// Trap code, as determined by cranelift - pub code: ir::TrapCode, -} +use cranelift_module::TrapSite; /// Record of the trap sites for a given function #[derive(Debug)] @@ -22,7 +12,7 @@ pub struct FaerieTrapSink { /// Total code size of function pub code_size: u32, /// All trap sites collected in function - pub sites: Vec, + pub sites: Vec, } impl FaerieTrapSink { @@ -34,11 +24,20 @@ impl FaerieTrapSink { code_size, } } + + /// Create a `FaerieTrapSink` pre-populated with `traps` + pub fn new_with_sites(name: &str, code_size: u32, traps: Vec) -> Self { + Self { + sites: traps, + name: name.to_owned(), + code_size, + } + } } impl binemit::TrapSink for FaerieTrapSink { fn trap(&mut self, offset: binemit::CodeOffset, srcloc: ir::SourceLoc, code: ir::TrapCode) { - self.sites.push(FaerieTrapSite { + self.sites.push(TrapSite { offset, srcloc, code, diff --git a/cranelift/module/src/backend.rs b/cranelift/module/src/backend.rs index 218070a5c0..1464c4aefc 100644 --- a/cranelift/module/src/backend.rs +++ b/cranelift/module/src/backend.rs @@ -6,6 +6,7 @@ use crate::FuncId; use crate::Linkage; use crate::ModuleNamespace; use crate::ModuleResult; +use crate::TrapSite; use core::marker; use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::Context; @@ -14,6 +15,7 @@ use cranelift_codegen::{binemit, ir}; use std::borrow::ToOwned; use std::boxed::Box; use std::string::String; +use std::vec::Vec; /// A `Backend` implements the functionality needed to support a `Module`. /// @@ -85,6 +87,18 @@ where code_size: u32, ) -> ModuleResult; + /// Define a function, taking the function body from the given `bytes`. + /// + /// Functions must be declared before being defined. + fn define_function_bytes( + &mut self, + id: FuncId, + name: &str, + bytes: &[u8], + namespace: &ModuleNamespace, + traps: Vec, + ) -> ModuleResult; + /// Define a zero-initialized data object of the given size. /// /// Data objects must be declared before being defined. diff --git a/cranelift/module/src/lib.rs b/cranelift/module/src/lib.rs index 0122171e9a..25a2759be4 100644 --- a/cranelift/module/src/lib.rs +++ b/cranelift/module/src/lib.rs @@ -35,6 +35,7 @@ use std::collections::{hash_map, HashMap}; mod backend; mod data_context; mod module; +mod traps; pub use crate::backend::{default_libcall_names, Backend}; pub use crate::data_context::{DataContext, DataDescription, Init}; @@ -42,6 +43,7 @@ pub use crate::module::{ DataId, FuncId, FuncOrDataId, Linkage, Module, ModuleError, ModuleFunction, ModuleNamespace, ModuleResult, }; +pub use crate::traps::TrapSite; /// Version number of this crate. pub const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/cranelift/module/src/module.rs b/cranelift/module/src/module.rs index 6c2f109c20..10bca75ac7 100644 --- a/cranelift/module/src/module.rs +++ b/cranelift/module/src/module.rs @@ -7,12 +7,14 @@ use super::HashMap; use crate::data_context::DataContext; +use crate::traps::TrapSite; use crate::Backend; use cranelift_codegen::binemit::{self, CodeInfo}; use cranelift_codegen::entity::{entity_impl, PrimaryMap}; use cranelift_codegen::{ir, isa, CodegenError, Context}; use log::info; use std::borrow::ToOwned; +use std::convert::TryInto; use std::string::String; use std::vec::Vec; use thiserror::Error; @@ -139,6 +141,9 @@ pub enum ModuleError { /// Indicates an identifier was defined, but was declared as an import #[error("Invalid to define identifier declared as an import: {0}")] InvalidImportDefinition(String), + /// Indicates a too-long function was defined + #[error("Function {0} exceeds the maximum function size")] + FunctionTooLarge(String), /// Wraps a `cranelift-codegen` error #[error("Compilation error: {0}")] Compilation(#[from] CodegenError), @@ -573,6 +578,48 @@ where Ok(total_size) } + /// Define a function, taking the function body from the given `bytes`. + /// + /// This function is generally only useful if you need to precisely specify + /// the emitted instructions for some reason; otherwise, you should use + /// `define_function`. + /// + /// Returns the size of the function's code. + pub fn define_function_bytes( + &mut self, + func: FuncId, + bytes: &[u8], + traps: Vec, + ) -> ModuleResult { + info!("defining function {} with bytes", func); + let info = &self.contents.functions[func]; + if info.compiled.is_some() { + return Err(ModuleError::DuplicateDefinition(info.decl.name.clone())); + } + if !info.decl.linkage.is_definable() { + return Err(ModuleError::InvalidImportDefinition(info.decl.name.clone())); + } + + let total_size: u32 = match bytes.len().try_into() { + Ok(total_size) => total_size, + _ => Err(ModuleError::FunctionTooLarge(info.decl.name.clone()))?, + }; + + let compiled = Some(self.backend.define_function_bytes( + func, + &info.decl.name, + bytes, + &ModuleNamespace:: { + contents: &self.contents, + }, + traps, + )?); + + self.contents.functions[func].compiled = compiled; + self.functions_to_finalize.push(func); + Ok(total_size) + } + /// Define a data object, producing the data contents from the given `DataContext`. pub fn define_data(&mut self, data: DataId, data_ctx: &DataContext) -> ModuleResult<()> { let compiled = { diff --git a/cranelift/module/src/traps.rs b/cranelift/module/src/traps.rs new file mode 100644 index 0000000000..2344d4189b --- /dev/null +++ b/cranelift/module/src/traps.rs @@ -0,0 +1,14 @@ +//! Defines `TrapSite`. + +use cranelift_codegen::{binemit, ir}; + +/// Record of the arguments cranelift passes to `TrapSink::trap`. +#[derive(Clone, Debug)] +pub struct TrapSite { + /// Offset into function. + pub offset: binemit::CodeOffset, + /// Source location given to cranelift. + pub srcloc: ir::SourceLoc, + /// Trap code, as determined by cranelift. + pub code: ir::TrapCode, +} diff --git a/cranelift/object/src/backend.rs b/cranelift/object/src/backend.rs index f8baf1452c..c4fa5eaf72 100644 --- a/cranelift/object/src/backend.rs +++ b/cranelift/object/src/backend.rs @@ -1,6 +1,6 @@ //! Defines `ObjectBackend`. -use crate::traps::{ObjectTrapSink, ObjectTrapSite}; +use crate::traps::ObjectTrapSink; use cranelift_codegen::binemit::{ Addend, CodeOffset, NullStackmapSink, NullTrapSink, Reloc, RelocSink, }; @@ -9,7 +9,7 @@ use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::{self, binemit, ir}; use cranelift_module::{ Backend, DataContext, DataDescription, DataId, FuncId, Init, Linkage, ModuleNamespace, - ModuleResult, + ModuleResult, TrapSite, }; use object::write::{ Object, Relocation, SectionId, StandardSection, Symbol, SymbolId, SymbolSection, @@ -79,7 +79,7 @@ pub struct ObjectBackend { object: Object, functions: SecondaryMap>, data_objects: SecondaryMap>, - traps: SecondaryMap>, + traps: SecondaryMap>, relocs: Vec, libcalls: HashMap, libcall_names: Box String>, @@ -226,6 +226,23 @@ impl Backend for ObjectBackend { Ok(ObjectCompiledFunction) } + fn define_function_bytes( + &mut self, + func_id: FuncId, + _name: &str, + bytes: &[u8], + _namespace: &ModuleNamespace, + traps: Vec, + ) -> ModuleResult { + let symbol = self.functions[func_id].unwrap(); + let section = self.object.section_id(StandardSection::Text); + let _offset = self + .object + .add_symbol_data(symbol, section, bytes, self.function_alignment); + self.traps[func_id] = traps; + Ok(ObjectCompiledFunction) + } + fn define_data( &mut self, data_id: DataId, @@ -462,7 +479,7 @@ pub struct ObjectProduct { /// Symbol IDs for data objects (both declared and defined). pub data_objects: SecondaryMap>, /// Trap sites for defined functions. - pub traps: SecondaryMap>, + pub traps: SecondaryMap>, } impl ObjectProduct { diff --git a/cranelift/object/src/lib.rs b/cranelift/object/src/lib.rs index 241dcb6316..1542c0a191 100644 --- a/cranelift/object/src/lib.rs +++ b/cranelift/object/src/lib.rs @@ -29,7 +29,7 @@ mod backend; mod traps; pub use crate::backend::{ObjectBackend, ObjectBuilder, ObjectProduct, ObjectTrapCollection}; -pub use crate::traps::{ObjectTrapSink, ObjectTrapSite}; +pub use crate::traps::ObjectTrapSink; /// Version number of this crate. pub const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/cranelift/object/src/traps.rs b/cranelift/object/src/traps.rs index a64f1e13fa..2fabfc626d 100644 --- a/cranelift/object/src/traps.rs +++ b/cranelift/object/src/traps.rs @@ -2,28 +2,18 @@ //! for every function in the module. This data may be useful at runtime. use cranelift_codegen::{binemit, ir}; - -/// Record of the arguments cranelift passes to `TrapSink::trap` -#[derive(Clone)] -pub struct ObjectTrapSite { - /// Offset into function - pub offset: binemit::CodeOffset, - /// Source location given to cranelift - pub srcloc: ir::SourceLoc, - /// Trap code, as determined by cranelift - pub code: ir::TrapCode, -} +use cranelift_module::TrapSite; /// Record of the trap sites for a given function #[derive(Default, Clone)] pub struct ObjectTrapSink { /// All trap sites collected in function - pub sites: Vec, + pub sites: Vec, } impl binemit::TrapSink for ObjectTrapSink { fn trap(&mut self, offset: binemit::CodeOffset, srcloc: ir::SourceLoc, code: ir::TrapCode) { - self.sites.push(ObjectTrapSite { + self.sites.push(TrapSite { offset, srcloc, code, diff --git a/cranelift/simplejit/src/backend.rs b/cranelift/simplejit/src/backend.rs index a4b7d1a1da..8d37b99920 100644 --- a/cranelift/simplejit/src/backend.rs +++ b/cranelift/simplejit/src/backend.rs @@ -8,7 +8,7 @@ use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::{self, ir, settings}; use cranelift_module::{ Backend, DataContext, DataDescription, DataId, FuncId, Init, Linkage, ModuleNamespace, - ModuleResult, + ModuleResult, TrapSite, }; use cranelift_native; #[cfg(not(windows))] @@ -191,6 +191,23 @@ impl SimpleJITBackend { _ => panic!("invalid ExternalName {}", name), } } + + fn record_function_for_perf(&self, ptr: *mut u8, size: usize, name: &str) { + // The Linux perf tool supports JIT code via a /tmp/perf-$PID.map file, + // which contains memory regions and their associated names. If we + // are profiling with perf and saving binaries to PERF_BUILDID_DIR + // for post-profile analysis, write information about each function + // we define. + if cfg!(target_os = "linux") && ::std::env::var_os("PERF_BUILDID_DIR").is_some() { + let mut map_file = ::std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(format!("/tmp/perf-{}.map", ::std::process::id())) + .unwrap(); + + let _ = writeln!(map_file, "{:x} {:x} {}", ptr as usize, size, name); + } + } } impl<'simple_jit_backend> Backend for SimpleJITBackend { @@ -267,15 +284,7 @@ impl<'simple_jit_backend> Backend for SimpleJITBackend { .allocate(size, EXECUTABLE_DATA_ALIGNMENT) .expect("TODO: handle OOM etc."); - if cfg!(target_os = "linux") && ::std::env::var_os("PERF_BUILDID_DIR").is_some() { - let mut map_file = ::std::fs::OpenOptions::new() - .create(true) - .append(true) - .open(format!("/tmp/perf-{}.map", ::std::process::id())) - .unwrap(); - - let _ = writeln!(map_file, "{:x} {:x} {}", ptr as usize, code_size, name); - } + self.record_function_for_perf(ptr, size, name); let mut reloc_sink = SimpleJITRelocSink::new(); // Ignore traps for now. For now, frontends should just avoid generating code @@ -299,6 +308,34 @@ impl<'simple_jit_backend> Backend for SimpleJITBackend { }) } + fn define_function_bytes( + &mut self, + _id: FuncId, + name: &str, + bytes: &[u8], + _namespace: &ModuleNamespace, + _traps: Vec, + ) -> ModuleResult { + let size = bytes.len(); + let ptr = self + .memory + .code + .allocate(size, EXECUTABLE_DATA_ALIGNMENT) + .expect("TODO: handle OOM etc."); + + self.record_function_for_perf(ptr, size, name); + + unsafe { + ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, size); + } + + Ok(Self::CompiledFunction { + code: ptr, + size, + relocs: vec![], + }) + } + fn define_data( &mut self, _id: DataId,