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`
This commit is contained in:
Nathan Froyd
2020-02-21 18:14:37 -05:00
committed by GitHub
parent c5d6805284
commit 09c6c5db44
10 changed files with 188 additions and 42 deletions

View File

@@ -10,9 +10,10 @@ use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::{self, binemit, ir}; use cranelift_codegen::{self, binemit, ir};
use cranelift_module::{ use cranelift_module::{
Backend, DataContext, DataDescription, DataId, FuncId, Init, Linkage, ModuleError, Backend, DataContext, DataDescription, DataId, FuncId, Init, Linkage, ModuleError,
ModuleNamespace, ModuleResult, ModuleNamespace, ModuleResult, TrapSite,
}; };
use faerie; use faerie;
use std::convert::TryInto;
use std::fs::File; use std::fs::File;
use target_lexicon::Triple; use target_lexicon::Triple;
@@ -200,6 +201,31 @@ impl Backend for FaerieBackend {
Ok(FaerieCompiledFunction { code_length }) Ok(FaerieCompiledFunction { code_length })
} }
fn define_function_bytes(
&mut self,
_id: FuncId,
name: &str,
bytes: &[u8],
_namespace: &ModuleNamespace<Self>,
traps: Vec<TrapSite>,
) -> ModuleResult<FaerieCompiledFunction> {
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( fn define_data(
&mut self, &mut self,
_id: DataId, _id: DataId,

View File

@@ -2,17 +2,7 @@
//! for every function in the module. This data may be useful at runtime. //! for every function in the module. This data may be useful at runtime.
use cranelift_codegen::{binemit, ir}; use cranelift_codegen::{binemit, ir};
use cranelift_module::TrapSite;
/// 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,
}
/// Record of the trap sites for a given function /// Record of the trap sites for a given function
#[derive(Debug)] #[derive(Debug)]
@@ -22,7 +12,7 @@ pub struct FaerieTrapSink {
/// Total code size of function /// Total code size of function
pub code_size: u32, pub code_size: u32,
/// All trap sites collected in function /// All trap sites collected in function
pub sites: Vec<FaerieTrapSite>, pub sites: Vec<TrapSite>,
} }
impl FaerieTrapSink { impl FaerieTrapSink {
@@ -34,11 +24,20 @@ impl FaerieTrapSink {
code_size, code_size,
} }
} }
/// Create a `FaerieTrapSink` pre-populated with `traps`
pub fn new_with_sites(name: &str, code_size: u32, traps: Vec<TrapSite>) -> Self {
Self {
sites: traps,
name: name.to_owned(),
code_size,
}
}
} }
impl binemit::TrapSink for FaerieTrapSink { impl binemit::TrapSink for FaerieTrapSink {
fn trap(&mut self, offset: binemit::CodeOffset, srcloc: ir::SourceLoc, code: ir::TrapCode) { fn trap(&mut self, offset: binemit::CodeOffset, srcloc: ir::SourceLoc, code: ir::TrapCode) {
self.sites.push(FaerieTrapSite { self.sites.push(TrapSite {
offset, offset,
srcloc, srcloc,
code, code,

View File

@@ -6,6 +6,7 @@ use crate::FuncId;
use crate::Linkage; use crate::Linkage;
use crate::ModuleNamespace; use crate::ModuleNamespace;
use crate::ModuleResult; use crate::ModuleResult;
use crate::TrapSite;
use core::marker; use core::marker;
use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::Context; use cranelift_codegen::Context;
@@ -14,6 +15,7 @@ use cranelift_codegen::{binemit, ir};
use std::borrow::ToOwned; use std::borrow::ToOwned;
use std::boxed::Box; use std::boxed::Box;
use std::string::String; use std::string::String;
use std::vec::Vec;
/// A `Backend` implements the functionality needed to support a `Module`. /// A `Backend` implements the functionality needed to support a `Module`.
/// ///
@@ -85,6 +87,18 @@ where
code_size: u32, code_size: u32,
) -> ModuleResult<Self::CompiledFunction>; ) -> ModuleResult<Self::CompiledFunction>;
/// 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<Self>,
traps: Vec<TrapSite>,
) -> ModuleResult<Self::CompiledFunction>;
/// Define a zero-initialized data object of the given size. /// Define a zero-initialized data object of the given size.
/// ///
/// Data objects must be declared before being defined. /// Data objects must be declared before being defined.

View File

@@ -35,6 +35,7 @@ use std::collections::{hash_map, HashMap};
mod backend; mod backend;
mod data_context; mod data_context;
mod module; mod module;
mod traps;
pub use crate::backend::{default_libcall_names, Backend}; pub use crate::backend::{default_libcall_names, Backend};
pub use crate::data_context::{DataContext, DataDescription, Init}; pub use crate::data_context::{DataContext, DataDescription, Init};
@@ -42,6 +43,7 @@ pub use crate::module::{
DataId, FuncId, FuncOrDataId, Linkage, Module, ModuleError, ModuleFunction, ModuleNamespace, DataId, FuncId, FuncOrDataId, Linkage, Module, ModuleError, ModuleFunction, ModuleNamespace,
ModuleResult, ModuleResult,
}; };
pub use crate::traps::TrapSite;
/// Version number of this crate. /// Version number of this crate.
pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const VERSION: &str = env!("CARGO_PKG_VERSION");

View File

@@ -7,12 +7,14 @@
use super::HashMap; use super::HashMap;
use crate::data_context::DataContext; use crate::data_context::DataContext;
use crate::traps::TrapSite;
use crate::Backend; use crate::Backend;
use cranelift_codegen::binemit::{self, CodeInfo}; use cranelift_codegen::binemit::{self, CodeInfo};
use cranelift_codegen::entity::{entity_impl, PrimaryMap}; use cranelift_codegen::entity::{entity_impl, PrimaryMap};
use cranelift_codegen::{ir, isa, CodegenError, Context}; use cranelift_codegen::{ir, isa, CodegenError, Context};
use log::info; use log::info;
use std::borrow::ToOwned; use std::borrow::ToOwned;
use std::convert::TryInto;
use std::string::String; use std::string::String;
use std::vec::Vec; use std::vec::Vec;
use thiserror::Error; use thiserror::Error;
@@ -139,6 +141,9 @@ pub enum ModuleError {
/// Indicates an identifier was defined, but was declared as an import /// Indicates an identifier was defined, but was declared as an import
#[error("Invalid to define identifier declared as an import: {0}")] #[error("Invalid to define identifier declared as an import: {0}")]
InvalidImportDefinition(String), InvalidImportDefinition(String),
/// Indicates a too-long function was defined
#[error("Function {0} exceeds the maximum function size")]
FunctionTooLarge(String),
/// Wraps a `cranelift-codegen` error /// Wraps a `cranelift-codegen` error
#[error("Compilation error: {0}")] #[error("Compilation error: {0}")]
Compilation(#[from] CodegenError), Compilation(#[from] CodegenError),
@@ -573,6 +578,48 @@ where
Ok(total_size) 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<TrapSite>,
) -> ModuleResult<binemit::CodeOffset> {
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::<B> {
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`. /// Define a data object, producing the data contents from the given `DataContext`.
pub fn define_data(&mut self, data: DataId, data_ctx: &DataContext) -> ModuleResult<()> { pub fn define_data(&mut self, data: DataId, data_ctx: &DataContext) -> ModuleResult<()> {
let compiled = { let compiled = {

View File

@@ -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,
}

View File

@@ -1,6 +1,6 @@
//! Defines `ObjectBackend`. //! Defines `ObjectBackend`.
use crate::traps::{ObjectTrapSink, ObjectTrapSite}; use crate::traps::ObjectTrapSink;
use cranelift_codegen::binemit::{ use cranelift_codegen::binemit::{
Addend, CodeOffset, NullStackmapSink, NullTrapSink, Reloc, RelocSink, Addend, CodeOffset, NullStackmapSink, NullTrapSink, Reloc, RelocSink,
}; };
@@ -9,7 +9,7 @@ use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::{self, binemit, ir}; use cranelift_codegen::{self, binemit, ir};
use cranelift_module::{ use cranelift_module::{
Backend, DataContext, DataDescription, DataId, FuncId, Init, Linkage, ModuleNamespace, Backend, DataContext, DataDescription, DataId, FuncId, Init, Linkage, ModuleNamespace,
ModuleResult, ModuleResult, TrapSite,
}; };
use object::write::{ use object::write::{
Object, Relocation, SectionId, StandardSection, Symbol, SymbolId, SymbolSection, Object, Relocation, SectionId, StandardSection, Symbol, SymbolId, SymbolSection,
@@ -79,7 +79,7 @@ pub struct ObjectBackend {
object: Object, object: Object,
functions: SecondaryMap<FuncId, Option<SymbolId>>, functions: SecondaryMap<FuncId, Option<SymbolId>>,
data_objects: SecondaryMap<DataId, Option<SymbolId>>, data_objects: SecondaryMap<DataId, Option<SymbolId>>,
traps: SecondaryMap<FuncId, Vec<ObjectTrapSite>>, traps: SecondaryMap<FuncId, Vec<TrapSite>>,
relocs: Vec<SymbolRelocs>, relocs: Vec<SymbolRelocs>,
libcalls: HashMap<ir::LibCall, SymbolId>, libcalls: HashMap<ir::LibCall, SymbolId>,
libcall_names: Box<dyn Fn(ir::LibCall) -> String>, libcall_names: Box<dyn Fn(ir::LibCall) -> String>,
@@ -226,6 +226,23 @@ impl Backend for ObjectBackend {
Ok(ObjectCompiledFunction) Ok(ObjectCompiledFunction)
} }
fn define_function_bytes(
&mut self,
func_id: FuncId,
_name: &str,
bytes: &[u8],
_namespace: &ModuleNamespace<Self>,
traps: Vec<TrapSite>,
) -> ModuleResult<ObjectCompiledFunction> {
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( fn define_data(
&mut self, &mut self,
data_id: DataId, data_id: DataId,
@@ -462,7 +479,7 @@ pub struct ObjectProduct {
/// Symbol IDs for data objects (both declared and defined). /// Symbol IDs for data objects (both declared and defined).
pub data_objects: SecondaryMap<DataId, Option<SymbolId>>, pub data_objects: SecondaryMap<DataId, Option<SymbolId>>,
/// Trap sites for defined functions. /// Trap sites for defined functions.
pub traps: SecondaryMap<FuncId, Vec<ObjectTrapSite>>, pub traps: SecondaryMap<FuncId, Vec<TrapSite>>,
} }
impl ObjectProduct { impl ObjectProduct {

View File

@@ -29,7 +29,7 @@ mod backend;
mod traps; mod traps;
pub use crate::backend::{ObjectBackend, ObjectBuilder, ObjectProduct, ObjectTrapCollection}; pub use crate::backend::{ObjectBackend, ObjectBuilder, ObjectProduct, ObjectTrapCollection};
pub use crate::traps::{ObjectTrapSink, ObjectTrapSite}; pub use crate::traps::ObjectTrapSink;
/// Version number of this crate. /// Version number of this crate.
pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const VERSION: &str = env!("CARGO_PKG_VERSION");

View File

@@ -2,28 +2,18 @@
//! for every function in the module. This data may be useful at runtime. //! for every function in the module. This data may be useful at runtime.
use cranelift_codegen::{binemit, ir}; use cranelift_codegen::{binemit, ir};
use cranelift_module::TrapSite;
/// 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,
}
/// Record of the trap sites for a given function /// Record of the trap sites for a given function
#[derive(Default, Clone)] #[derive(Default, Clone)]
pub struct ObjectTrapSink { pub struct ObjectTrapSink {
/// All trap sites collected in function /// All trap sites collected in function
pub sites: Vec<ObjectTrapSite>, pub sites: Vec<TrapSite>,
} }
impl binemit::TrapSink for ObjectTrapSink { impl binemit::TrapSink for ObjectTrapSink {
fn trap(&mut self, offset: binemit::CodeOffset, srcloc: ir::SourceLoc, code: ir::TrapCode) { fn trap(&mut self, offset: binemit::CodeOffset, srcloc: ir::SourceLoc, code: ir::TrapCode) {
self.sites.push(ObjectTrapSite { self.sites.push(TrapSite {
offset, offset,
srcloc, srcloc,
code, code,

View File

@@ -8,7 +8,7 @@ use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::{self, ir, settings}; use cranelift_codegen::{self, ir, settings};
use cranelift_module::{ use cranelift_module::{
Backend, DataContext, DataDescription, DataId, FuncId, Init, Linkage, ModuleNamespace, Backend, DataContext, DataDescription, DataId, FuncId, Init, Linkage, ModuleNamespace,
ModuleResult, ModuleResult, TrapSite,
}; };
use cranelift_native; use cranelift_native;
#[cfg(not(windows))] #[cfg(not(windows))]
@@ -191,6 +191,23 @@ impl SimpleJITBackend {
_ => panic!("invalid ExternalName {}", name), _ => 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 { impl<'simple_jit_backend> Backend for SimpleJITBackend {
@@ -267,15 +284,7 @@ impl<'simple_jit_backend> Backend for SimpleJITBackend {
.allocate(size, EXECUTABLE_DATA_ALIGNMENT) .allocate(size, EXECUTABLE_DATA_ALIGNMENT)
.expect("TODO: handle OOM etc."); .expect("TODO: handle OOM etc.");
if cfg!(target_os = "linux") && ::std::env::var_os("PERF_BUILDID_DIR").is_some() { self.record_function_for_perf(ptr, size, name);
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);
}
let mut reloc_sink = SimpleJITRelocSink::new(); let mut reloc_sink = SimpleJITRelocSink::new();
// Ignore traps for now. For now, frontends should just avoid generating code // 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<Self>,
_traps: Vec<TrapSite>,
) -> ModuleResult<Self::CompiledFunction> {
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( fn define_data(
&mut self, &mut self,
_id: DataId, _id: DataId,