[WIP] Module API (#294)

* Initial skeleton.

* Add basic faerie support.

This adds enough functionality to enable simple .o file writing through
faerie. This included adding the functionality to Module to support
RelocSink implementations.

* Add basic SimpleJIT support.

This adds enough functionality to enable a simple program to be jitted
and executed.

* Make declare_func_in_func take a Function instead of a Context.

It only needs the Function, and sometimes it's useful to call it from
places that don't have a full Context.

* Temporarily disable local and exported global variables in the Faerie backend.

Faerie assumes these variables use pc-relative offset instructions, and
Cretonne is not yet emitting those instructions.

* FaerieBackend depends on PIC.

Faerie itself only supports PIC objects for now, so add an assert to
Cretonne to check that it's using a PIC target flag.

* SimpleJIT support for data objects.

* Preliminary faerie support for data objects.

* Support for data objects in faerie using the new colocated flag.

* Unit tests for DataContext and friends.

* Add a Module::consume() function.

This consumes the Module and returns the contained Backend, so that
users can call Backend-specific functions with it. For example, the
Faerie backend has functions to write an object file.

* Update the new crates to version 0.4.4.

* Make FaerieBackend own its TargetIsa.

This simplifies its interface, as it eliminates a lifetime parameter.
While we may eventually want to look into allowing multiple clients to
share a TargetIsa, it isn't worth the complexity for FaerieBackend
right now.

* Don't try to protect faerie from multiple declarations.

Let faerie decide for itself whether it wants to consider two
declarations to be compatible.

* Use debug_assert_eq rather than debug_assert with ==.

* Fix FaerieRelocSink's reloc_external to handle data object names.

* Relax the asserts in get_function_definition and get_data_definition.

These functions don't require definable symbols, but they do require
that definable symbols be defined. This is needed for the simplejit
backend.

* Add a function to the faerie backend to retrieve the artifact name.

* Sync up with cretonne changes.
This commit is contained in:
Dan Gohman
2018-04-17 10:52:36 -07:00
committed by GitHub
parent a9edb28414
commit 76db9f022d
21 changed files with 1793 additions and 7 deletions

View File

@@ -19,6 +19,9 @@ cretonne-frontend = { path = "lib/frontend", version = "0.4.4" }
cretonne-wasm = { path = "lib/wasm", version = "0.4.4" } cretonne-wasm = { path = "lib/wasm", version = "0.4.4" }
cretonne-native = { path = "lib/native", version = "0.4.4" } cretonne-native = { path = "lib/native", version = "0.4.4" }
cretonne-filetests = { path = "lib/filetests", version = "0.4.4" } cretonne-filetests = { path = "lib/filetests", version = "0.4.4" }
cretonne-module = { path = "lib/module", version = "0.4.4" }
cretonne-faerie = { path = "lib/faerie", version = "0.4.4" }
cretonne-simplejit = { path = "lib/simplejit", version = "0.4.4" }
cretonne = { path = "lib/umbrella", version = "0.4.4" } cretonne = { path = "lib/umbrella", version = "0.4.4" }
filecheck = "0.2.1" filecheck = "0.2.1"
docopt = "0.8.0" docopt = "0.8.0"

View File

@@ -23,7 +23,7 @@ pub type CodeOffset = u32;
pub type Addend = i64; pub type Addend = i64;
/// Relocation kinds for every ISA /// Relocation kinds for every ISA
#[derive(Debug)] #[derive(Copy, Clone, Debug)]
pub enum Reloc { pub enum Reloc {
/// absolute 4-byte /// absolute 4-byte
Abs4, Abs4,

View File

@@ -19,7 +19,7 @@ use std::vec::Vec;
/// ///
/// A signature can optionally include ISA-specific ABI information which specifies exactly how /// A signature can optionally include ISA-specific ABI information which specifies exactly how
/// arguments and return values are passed. /// arguments and return values are passed.
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Signature { pub struct Signature {
/// The arguments passed to the function. /// The arguments passed to the function.
pub params: Vec<AbiParam>, pub params: Vec<AbiParam>,
@@ -123,7 +123,7 @@ impl fmt::Display for Signature {
/// ///
/// This describes the value type being passed to or from a function along with flags that affect /// This describes the value type being passed to or from a function along with flags that affect
/// how the argument is passed. /// how the argument is passed.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct AbiParam { pub struct AbiParam {
/// Type of the argument value. /// Type of the argument value.
pub value_type: Type, pub value_type: Type,
@@ -225,7 +225,7 @@ impl fmt::Display for AbiParam {
/// ///
/// On some architectures, small integer function arguments are extended to the width of a /// On some architectures, small integer function arguments are extended to the width of a
/// general-purpose register. /// general-purpose register.
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub enum ArgumentExtension { pub enum ArgumentExtension {
/// No extension, high bits are indeterminate. /// No extension, high bits are indeterminate.
None, None,
@@ -242,7 +242,7 @@ pub enum ArgumentExtension {
/// frame pointers and callee-saved registers. /// frame pointers and callee-saved registers.
/// ///
/// The argument purpose is used to indicate any special meaning of an argument or return value. /// The argument purpose is used to indicate any special meaning of an argument or return value.
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub enum ArgumentPurpose { pub enum ArgumentPurpose {
/// A normal user program value passed to or from a function. /// A normal user program value passed to or from a function.
Normal, Normal,
@@ -348,7 +348,7 @@ impl fmt::Display for ExtFuncData {
/// and how stack frames are managed. Since all of these details depend on both the instruction set /// and how stack frames are managed. Since all of these details depend on both the instruction set
/// architecture and possibly the operating system, a function's calling convention is only fully /// architecture and possibly the operating system, a function's calling convention is only fully
/// determined by a `(TargetIsa, CallConv)` tuple. /// determined by a `(TargetIsa, CallConv)` tuple.
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CallConv { pub enum CallConv {
/// The System V-style calling convention. /// The System V-style calling convention.
/// ///

View File

@@ -95,7 +95,7 @@ impl<'a> fmt::Display for DisplayValueLoc<'a> {
/// outgoing arguments. /// outgoing arguments.
/// - For register arguments, there is usually no difference, but if we ever add support for a /// - For register arguments, there is usually no difference, but if we ever add support for a
/// register-window ISA like SPARC, register arguments would also need to be translated. /// register-window ISA like SPARC, register arguments would also need to be translated.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum ArgumentLoc { pub enum ArgumentLoc {
/// This argument has not been assigned to a location yet. /// This argument has not been assigned to a location yet.
Unassigned, Unassigned,

20
lib/faerie/Cargo.toml Normal file
View File

@@ -0,0 +1,20 @@
[package]
name = "cretonne-faerie"
version = "0.4.4"
authors = ["The Cretonne Project Developers"]
description = "Emit Cretonne output to native object files with Faerie"
repository = "https://github.com/Cretonne/cretonne"
documentation = "https://cretonne.readthedocs.io/"
license = "Apache-2.0"
readme = "README.md"
[dependencies]
cretonne-codegen = { path = "../codegen", version = "0.4.4" }
cretonne-module = { path = "../module", version = "0.4.4" }
faerie = "0.1.0"
goblin = "0.0.14"
failure = "0.1.1"
[badges]
maintenance = { status = "experimental" }
travis-ci = { repository = "Cretonne/cretonne" }

4
lib/faerie/README.md Normal file
View File

@@ -0,0 +1,4 @@
This crate contains a library that enables
[Cretonne](https://crates.io/crates/cretonne)
to emit native object (".o") files, using the
[Faerie](https://crates.io/crates/faerie) library.

298
lib/faerie/src/backend.rs Normal file
View File

@@ -0,0 +1,298 @@
//! Defines `FaerieBackend`.
use container;
use cretonne_codegen::binemit::{Addend, CodeOffset, Reloc, RelocSink, TrapSink};
use cretonne_codegen::isa::TargetIsa;
use cretonne_codegen::result::CtonError;
use cretonne_codegen::{self, binemit, ir};
use cretonne_module::{Backend, DataContext, Linkage, ModuleNamespace, Init, DataDescription};
use faerie;
use failure::Error;
use std::fs::File;
use target;
pub struct FaerieCompiledFunction {}
pub struct FaerieCompiledData {}
/// A `FaerieBackend` implements `Backend` and emits ".o" files using the `faerie` library.
pub struct FaerieBackend {
isa: Box<TargetIsa>,
artifact: faerie::Artifact,
format: container::Format,
}
impl FaerieBackend {
/// Create a new `FaerieBackend` using the given Cretonne target.
pub fn new(
isa: Box<TargetIsa>,
name: String,
format: container::Format,
) -> Result<Self, Error> {
debug_assert!(isa.flags().is_pic(), "faerie requires PIC");
let faerie_target = target::translate(&*isa)?;
Ok(Self {
isa,
artifact: faerie::Artifact::new(faerie_target, name),
format,
})
}
/// Return the name of the output file. This is the name passed into `new`.
pub fn name(&self) -> &str {
&self.artifact.name
}
/// Call `emit` on the faerie `Artifact`, producing bytes in memory.
pub fn emit(&self) -> Result<Vec<u8>, Error> {
match self.format {
container::Format::ELF => self.artifact.emit::<faerie::Elf>(),
container::Format::MachO => self.artifact.emit::<faerie::Mach>(),
}
}
/// Call `write` on the faerie `Artifact`, writing to a file.
pub fn write(&self, sink: File) -> Result<(), Error> {
match self.format {
container::Format::ELF => self.artifact.write::<faerie::Elf>(sink),
container::Format::MachO => self.artifact.write::<faerie::Mach>(sink),
}
}
}
impl Backend for FaerieBackend {
type CompiledFunction = FaerieCompiledFunction;
type CompiledData = FaerieCompiledData;
// There's no need to return invidual artifacts; we're writing them into
// the output file instead.
type FinalizedFunction = ();
type FinalizedData = ();
fn isa(&self) -> &TargetIsa {
&*self.isa
}
fn declare_function(&mut self, name: &str, linkage: Linkage) {
self.artifact
.declare(name, translate_function_linkage(linkage))
.expect("inconsistent declarations");
}
fn declare_data(&mut self, name: &str, linkage: Linkage, writable: bool) {
self.artifact
.declare(name, translate_data_linkage(linkage, writable))
.expect("inconsistent declarations");
}
fn define_function(
&mut self,
name: &str,
ctx: &cretonne_codegen::Context,
namespace: &ModuleNamespace<Self>,
code_size: u32,
) -> Result<FaerieCompiledFunction, CtonError> {
let mut code: Vec<u8> = Vec::with_capacity(code_size as usize);
code.resize(code_size as usize, 0);
// Non-lexical lifetimes would obviate the braces here.
{
let mut reloc_sink = FaerieRelocSink {
format: self.format,
artifact: &mut self.artifact,
name,
namespace,
};
let mut trap_sink = FaerieTrapSink {};
ctx.emit_to_memory(
code.as_mut_ptr(),
&mut reloc_sink,
&mut trap_sink,
&*self.isa,
);
}
self.artifact.define(name, code).expect(
"inconsistent declaration",
);
Ok(FaerieCompiledFunction {})
}
fn define_data(
&mut self,
name: &str,
data_ctx: &DataContext,
namespace: &ModuleNamespace<Self>,
) -> Result<FaerieCompiledData, CtonError> {
let &DataDescription {
writable: _writable,
ref init,
ref function_decls,
ref data_decls,
ref function_relocs,
ref data_relocs,
} = data_ctx.description();
let size = init.size();
let mut bytes = Vec::with_capacity(size);
match *init {
Init::Uninitialized => {
panic!("data is not initialized yet");
}
Init::Zeros { .. } => {
bytes.resize(size, 0);
}
Init::Bytes { ref contents } => {
bytes.extend_from_slice(contents);
}
}
// TODO: Change the signature of this function to use something other
// than `CtonError`, as `CtonError` can't convey faerie's errors.
for &(offset, id) in function_relocs {
let to = &namespace.get_function_decl(&function_decls[id]).name;
self.artifact
.link(faerie::Link {
from: name,
to,
at: offset as usize,
})
.map_err(|_e| CtonError::InvalidInput)?;
}
for &(offset, id, addend) in data_relocs {
debug_assert_eq!(
addend,
0,
"faerie doesn't support addends in data section relocations yet"
);
let to = &namespace.get_data_decl(&data_decls[id]).name;
self.artifact
.link(faerie::Link {
from: name,
to,
at: offset as usize,
})
.map_err(|_e| CtonError::InvalidInput)?;
}
self.artifact.define(name, bytes).expect(
"inconsistent declaration",
);
Ok(FaerieCompiledData {})
}
fn write_data_funcaddr(
&mut self,
_data: &mut FaerieCompiledData,
_offset: usize,
_what: ir::FuncRef,
) {
unimplemented!()
}
fn write_data_dataaddr(
&mut self,
_data: &mut FaerieCompiledData,
_offset: usize,
_what: ir::GlobalVar,
_usize: binemit::Addend,
) {
unimplemented!()
}
fn finalize_function(
&mut self,
_func: &FaerieCompiledFunction,
_namespace: &ModuleNamespace<Self>,
) {
// Nothing to do.
}
fn finalize_data(&mut self, _data: &FaerieCompiledData, _namespace: &ModuleNamespace<Self>) {
// Nothing to do.
}
}
fn translate_function_linkage(linkage: Linkage) -> faerie::Decl {
match linkage {
Linkage::Import => faerie::Decl::FunctionImport,
Linkage::Local => faerie::Decl::Function { global: false },
Linkage::Preemptible | Linkage::Export => faerie::Decl::Function { global: true },
}
}
fn translate_data_linkage(linkage: Linkage, writable: bool) -> faerie::Decl {
match linkage {
Linkage::Import => faerie::Decl::DataImport,
Linkage::Local => {
faerie::Decl::Data {
global: false,
writeable: writable,
}
}
Linkage::Export => {
faerie::Decl::Data {
global: true,
writeable: writable,
}
}
Linkage::Preemptible => {
unimplemented!("faerie doesn't support preemptible globals yet");
}
}
}
struct FaerieRelocSink<'a> {
format: container::Format,
artifact: &'a mut faerie::Artifact,
name: &'a str,
namespace: &'a ModuleNamespace<'a, FaerieBackend>,
}
impl<'a> RelocSink for FaerieRelocSink<'a> {
fn reloc_ebb(&mut self, _offset: CodeOffset, _reloc: Reloc, _ebb_offset: CodeOffset) {
unimplemented!();
}
fn reloc_external(
&mut self,
offset: CodeOffset,
reloc: Reloc,
name: &ir::ExternalName,
addend: Addend,
) {
let ref_name = if self.namespace.is_function(name) {
&self.namespace.get_function_decl(name).name
} else {
&self.namespace.get_data_decl(name).name
};
let addend_i32 = addend as i32;
debug_assert!(addend_i32 as i64 == addend);
let raw_reloc = container::raw_relocation(reloc, self.format);
self.artifact
.link_with(
faerie::Link {
from: self.name,
to: ref_name,
at: offset as usize,
},
faerie::RelocOverride {
reloc: raw_reloc,
addend: addend_i32,
},
)
.expect("faerie relocation error");
}
fn reloc_jt(&mut self, _offset: CodeOffset, _reloc: Reloc, _jt: ir::JumpTable) {
unimplemented!();
}
}
struct FaerieTrapSink {}
impl TrapSink for FaerieTrapSink {
// Ignore traps for now. For now, frontends should just avoid generating code that traps.
fn trap(&mut self, _offset: CodeOffset, _srcloc: ir::SourceLoc, _code: ir::TrapCode) {}
}

View File

@@ -0,0 +1,33 @@
//! Utilities for working with Faerie container formats.
use cretonne_codegen::binemit::Reloc;
/// An object file format.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Format {
/// The ELF object file format.
ELF,
/// The Mach-O object file format.
MachO,
}
/// Translate from a Cretonne `Reloc` to a raw object-file-format-specific
/// relocation code.
pub fn raw_relocation(reloc: Reloc, format: Format) -> u32 {
match format {
Format::ELF => {
use goblin::elf;
match reloc {
Reloc::Abs4 => elf::reloc::R_X86_64_32,
Reloc::Abs8 => elf::reloc::R_X86_64_64,
Reloc::X86PCRel4 => elf::reloc::R_X86_64_PC32,
// TODO: Get Cretonne to tell us when we can use
// R_X86_64_GOTPCRELX/R_X86_64_REX_GOTPCRELX.
Reloc::X86GOTPCRel4 => elf::reloc::R_X86_64_GOTPCREL,
Reloc::X86PLTRel4 => elf::reloc::R_X86_64_PLT32,
_ => unimplemented!(),
}
}
Format::MachO => unimplemented!(),
}
}

19
lib/faerie/src/lib.rs Normal file
View File

@@ -0,0 +1,19 @@
//! Top-level lib.rs for `cretonne_faerie`.
//!
//! Users of this module should not have to depend on faerie directly.
#![deny(missing_docs, trivial_numeric_casts, unused_extern_crates)]
extern crate cretonne_codegen;
extern crate cretonne_module;
extern crate faerie;
#[macro_use]
extern crate failure;
extern crate goblin;
mod backend;
mod container;
mod target;
pub use backend::FaerieBackend;
pub use container::Format;

18
lib/faerie/src/target.rs Normal file
View File

@@ -0,0 +1,18 @@
use cretonne_codegen::isa;
use faerie::Target;
use failure::Error;
/// Translate from a Cretonne `TargetIsa` to a Faerie `Target`.
pub fn translate(isa: &isa::TargetIsa) -> Result<Target, Error> {
let name = isa.name();
match name {
"x86" => Ok(if isa.flags().is_64bit() {
Target::X86_64
} else {
Target::X86
}),
"arm32" => Ok(Target::ARMv7),
"arm64" => Ok(Target::ARM64),
_ => Err(format_err!("unsupported isa: {}", name)),
}
}

17
lib/module/Cargo.toml Normal file
View File

@@ -0,0 +1,17 @@
[package]
name = "cretonne-module"
version = "0.4.4"
authors = ["The Cretonne Project Developers"]
description = "Support for linking functions and data with Cretonne"
repository = "https://github.com/Cretonne/cretonne"
documentation = "https://cretonne.readthedocs.io/"
license = "Apache-2.0"
readme = "README.md"
[dependencies]
cretonne-codegen = { path = "../codegen", version = "0.4.4" }
cretonne-entity = { path = "../entity", version = "0.4.4" }
[badges]
maintenance = { status = "experimental" }
travis-ci = { repository = "Cretonne/cretonne" }

3
lib/module/README.md Normal file
View File

@@ -0,0 +1,3 @@
This crate provides the `Module` trait, which provides an interface for
multiple functions and data to be emitted with
[Cretonne](https://crates.io/crates/cretonne) and then linked together.

97
lib/module/src/backend.rs Normal file
View File

@@ -0,0 +1,97 @@
//! Defines the `Backend` trait.
use DataContext;
use Linkage;
use ModuleNamespace;
use cretonne_codegen::Context;
use cretonne_codegen::isa::TargetIsa;
use cretonne_codegen::result::CtonError;
use cretonne_codegen::{binemit, ir};
use std::marker;
/// A `Backend` implements the functionality needed to support a `Module`.
pub trait Backend
where
Self: marker::Sized,
{
/// The results of compiling a function.
type CompiledFunction;
/// The results of "compiling" a data object.
type CompiledData;
/// The completed output artifact for a function, if this is meaningful for
/// the Backend.
type FinalizedFunction;
/// The completed output artifact for a data object, if this is meaningful for
/// the Backend.
type FinalizedData;
/// Return the `TargetIsa` to compile for.
fn isa(&self) -> &TargetIsa;
/// Declare a function.
fn declare_function(&mut self, name: &str, linkage: Linkage);
/// Declare a data object.
fn declare_data(&mut self, name: &str, linkage: Linkage, writable: bool);
/// Define a function, producing the function body from the given `Context`.
///
/// Functions must be declared before being defined.
fn define_function(
&mut self,
name: &str,
ctx: &Context,
namespace: &ModuleNamespace<Self>,
code_size: u32,
) -> Result<Self::CompiledFunction, CtonError>;
/// Define a zero-initialized data object of the given size.
///
/// Data objects must be declared before being defined.
///
/// TODO: Is CtonError the right error code here?
fn define_data(
&mut self,
name: &str,
data_ctx: &DataContext,
namespace: &ModuleNamespace<Self>,
) -> Result<Self::CompiledData, CtonError>;
/// Write the address of `what` into the data for `data` at `offset`. `data` must refer to a
/// defined data object.
fn write_data_funcaddr(
&mut self,
data: &mut Self::CompiledData,
offset: usize,
what: ir::FuncRef,
);
/// Write the address of `what` plus `addend` into the data for `data` at `offset`. `data` must
/// refer to a defined data object.
fn write_data_dataaddr(
&mut self,
data: &mut Self::CompiledData,
offset: usize,
what: ir::GlobalVar,
addend: binemit::Addend,
);
/// Perform all outstanding relocations on the given function. This requires all `Local`
/// and `Export` entities referenced to be defined.
fn finalize_function(
&mut self,
func: &Self::CompiledFunction,
namespace: &ModuleNamespace<Self>,
) -> Self::FinalizedFunction;
/// Perform all outstanding relocations on the given data object. This requires all
/// `Local` and `Export` entities referenced to be defined.
fn finalize_data(
&mut self,
data: &Self::CompiledData,
namespace: &ModuleNamespace<Self>,
) -> Self::FinalizedData;
}

View File

@@ -0,0 +1,205 @@
//! Defines `DataContext`.
use cretonne_codegen::entity::PrimaryMap;
use cretonne_codegen::binemit::{CodeOffset, Addend};
use cretonne_codegen::ir;
/// This specifies how data is to be initialized.
#[derive(PartialEq, Eq, Debug)]
pub enum Init {
/// This indicates that no initialization has been specified yet.
Uninitialized,
/// Initialize the data with all zeros.
Zeros {
/// The size of the data.
size: usize,
},
/// Initialize the data with the specified contents.
Bytes {
/// The contents, which also implies the size of the data.
contents: Box<[u8]>,
},
}
impl Init {
/// Return the size of the data to be initialized.
pub fn size(&self) -> usize {
match *self {
Init::Uninitialized => panic!("data size not initialized yet"),
Init::Zeros { size } => size,
Init::Bytes { ref contents } => contents.len(),
}
}
}
/// A flag specifying whether data is readonly or may be written to.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Writability {
/// Data is readonly, meaning writes to it will trap.
Readonly,
/// Data is writable.
Writable,
}
/// A description of a data object.
pub struct DataDescription {
/// Whether the data readonly or writable.
pub writable: Writability,
/// How the data should be initialized.
pub init: Init,
/// External function declarations.
pub function_decls: PrimaryMap<ir::FuncRef, ir::ExternalName>,
/// External data object declarations.
pub data_decls: PrimaryMap<ir::GlobalVar, ir::ExternalName>,
/// Function addresses to write at specified offsets.
pub function_relocs: Vec<(CodeOffset, ir::FuncRef)>,
/// Data addresses to write at specified offsets.
pub data_relocs: Vec<(CodeOffset, ir::GlobalVar, Addend)>,
}
/// This is to data objects what cretonne_codegen::Context is to functions.
pub struct DataContext {
description: DataDescription,
}
impl DataContext {
/// Allocate a new context.
pub fn new() -> Self {
Self {
description: DataDescription {
writable: Writability::Readonly,
init: Init::Uninitialized,
function_decls: PrimaryMap::new(),
data_decls: PrimaryMap::new(),
function_relocs: Vec::new(),
data_relocs: Vec::new(),
},
}
}
/// Clear all data structures in this context.
pub fn clear(&mut self) {
self.description.writable = Writability::Readonly;
self.description.init = Init::Uninitialized;
self.description.function_decls.clear();
self.description.data_decls.clear();
self.description.function_relocs.clear();
self.description.data_relocs.clear();
}
/// Define a zero-initialized object with the given size.
pub fn define_zeroinit(&mut self, size: usize, writable: Writability) {
debug_assert_eq!(self.description.init, Init::Uninitialized);
self.description.writable = writable;
self.description.init = Init::Zeros { size };
}
/// Define a zero-initialized object with the given size.
///
/// TODO: Can we avoid a Box here?
pub fn define(&mut self, contents: Box<[u8]>, writable: Writability) {
debug_assert_eq!(self.description.init, Init::Uninitialized);
self.description.writable = writable;
self.description.init = Init::Bytes { contents };
}
/// Declare an external function import.
pub fn import_function(&mut self, name: ir::ExternalName) -> ir::FuncRef {
self.description.function_decls.push(name)
}
/// Declares a global variable import.
///
/// TODO: Rename to import_data?
pub fn import_global_var(&mut self, name: ir::ExternalName) -> ir::GlobalVar {
self.description.data_decls.push(name)
}
/// Write the address of `func` into the data at offset `offset`.
pub fn write_function_addr(&mut self, offset: CodeOffset, func: ir::FuncRef) {
self.description.function_relocs.push((offset, func))
}
/// Write the address of `data` into the data at offset `offset`.
pub fn write_data_addr(&mut self, offset: CodeOffset, data: ir::GlobalVar, addend: Addend) {
self.description.data_relocs.push((offset, data, addend))
}
/// Reference the initializer data.
pub fn description(&self) -> &DataDescription {
debug_assert!(
self.description.init != Init::Uninitialized,
"data must be initialized first"
);
&self.description
}
}
#[cfg(test)]
mod tests {
use {DataContext, Writability, Init};
use cretonne_codegen::ir;
#[test]
fn basic_data_context() {
let mut data_ctx = DataContext::new();
{
let description = data_ctx.description();
assert_eq!(description.writable, Writability::Readonly);
assert_eq!(description.init, Init::Uninitialized);
assert!(description.function_decls.is_empty());
assert!(description.data_decls.is_empty());
assert!(description.function_relocs.is_empty());
assert!(description.data_relocs.is_empty());
}
data_ctx.define_zeroinit(256, Writability::Writable);
let _func_a = data_ctx.import_function(ir::ExternalName::user(0, 0));
let func_b = data_ctx.import_function(ir::ExternalName::user(0, 1));
let func_c = data_ctx.import_function(ir::ExternalName::user(1, 0));
let _data_a = data_ctx.import_global_var(ir::ExternalName::user(2, 2));
let data_b = data_ctx.import_global_var(ir::ExternalName::user(2, 3));
data_ctx.write_function_addr(8, func_b);
data_ctx.write_function_addr(16, func_c);
data_ctx.write_data_addr(32, data_b, 27);
{
let description = data_ctx.description();
assert_eq!(description.writable, Writability::Writable);
assert_eq!(description.init, Init::Zeros { size: 256 });
assert_eq!(description.function_decls.len(), 3);
assert_eq!(description.data_decls.len(), 2);
assert_eq!(description.function_relocs.len(), 2);
assert_eq!(description.data_relocs.len(), 1);
}
data_ctx.clear();
{
let description = data_ctx.description();
assert_eq!(description.writable, Writability::Readonly);
assert_eq!(description.init, Init::Uninitialized);
assert!(description.function_decls.is_empty());
assert!(description.data_decls.is_empty());
assert!(description.function_relocs.is_empty());
assert!(description.data_relocs.is_empty());
}
let contents = vec![33, 34, 35, 36];
let contents_clone = contents.clone();
data_ctx.define(contents.into_boxed_slice(), Writability::Readonly);
{
let description = data_ctx.description();
assert_eq!(description.writable, Writability::Readonly);
assert_eq!(
description.init,
Init::Bytes { contents: contents_clone.into_boxed_slice() }
);
assert_eq!(description.function_decls.len(), 0);
assert_eq!(description.data_decls.len(), 0);
assert_eq!(description.function_relocs.len(), 0);
assert_eq!(description.data_relocs.len(), 0);
}
}
}

15
lib/module/src/lib.rs Normal file
View File

@@ -0,0 +1,15 @@
//! Top-level lib.rs for `cretonne_module`.
#![deny(missing_docs, trivial_numeric_casts, unused_extern_crates)]
extern crate cretonne_codegen;
#[macro_use]
extern crate cretonne_entity;
mod backend;
mod data_context;
mod module;
pub use backend::Backend;
pub use data_context::{DataContext, Writability, DataDescription, Init};
pub use module::{DataId, FuncId, Linkage, Module, ModuleNamespace};

541
lib/module/src/module.rs Normal file
View File

@@ -0,0 +1,541 @@
//! Defines `Module` and related types.
// TODO: Should `ir::Function` really have a `name`?
// TODO: Factor out `ir::Function`'s `ext_funcs` and `global_vars` into a struct
// shared with `DataContext`?
use Backend;
use cretonne_codegen::entity::{EntityRef, PrimaryMap};
use cretonne_codegen::result::{CtonError, CtonResult};
use cretonne_codegen::{binemit, ir, Context};
use data_context::DataContext;
use std::collections::HashMap;
/// A function identifier for use in the `Module` interface.
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct FuncId(u32);
entity_impl!(FuncId, "funcid");
/// A data object identifier for use in the `Module` interface.
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct DataId(u32);
entity_impl!(DataId, "dataid");
/// Linkage refers to where an entity is defined and who can see it.
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Linkage {
/// Defined outside of a module.
Import,
/// Defined inside the module, but not visible outside it.
Local,
/// Defined inside the module, visible outside it, and may be preempted.
Preemptible,
/// Defined inside the module, and visible outside it.
Export,
}
impl Linkage {
fn merge(a: Linkage, b: Linkage) -> Linkage {
match a {
Linkage::Export => Linkage::Export,
Linkage::Preemptible => {
match b {
Linkage::Export => Linkage::Export,
_ => Linkage::Preemptible,
}
}
Linkage::Local => {
match b {
Linkage::Export => Linkage::Export,
Linkage::Preemptible => Linkage::Preemptible,
_ => Linkage::Local,
}
}
Linkage::Import => b,
}
}
/// Test whether this linkage can have a definition.
pub fn is_definable(&self) -> bool {
match *self {
Linkage::Import => false,
Linkage::Local | Linkage::Preemptible | Linkage::Export => true,
}
}
/// Test whether this linkage will have a definition that cannot be preempted.
pub fn is_final(&self) -> bool {
match *self {
Linkage::Import | Linkage::Preemptible => false,
Linkage::Local | Linkage::Export => true,
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
enum FuncOrDataId {
Func(FuncId),
Data(DataId),
}
pub struct FunctionDeclaration {
pub name: String,
pub linkage: Linkage,
pub signature: ir::Signature,
}
struct ModuleFunction<B>
where
B: Backend,
{
decl: FunctionDeclaration,
compiled: Option<B::CompiledFunction>,
finalized: bool,
}
impl<B> ModuleFunction<B>
where
B: Backend,
{
fn merge(&mut self, linkage: Linkage) {
self.decl.linkage = Linkage::merge(self.decl.linkage, linkage);
}
}
pub struct DataDeclaration {
pub name: String,
pub linkage: Linkage,
pub writable: bool,
}
struct ModuleData<B>
where
B: Backend,
{
decl: DataDeclaration,
compiled: Option<B::CompiledData>,
finalized: bool,
}
impl<B> ModuleData<B>
where
B: Backend,
{
fn merge(&mut self, linkage: Linkage, writable: bool) {
self.decl.linkage = Linkage::merge(self.decl.linkage, linkage);
self.decl.writable = self.decl.writable || writable;
}
}
struct ModuleContents<B>
where
B: Backend,
{
functions: PrimaryMap<FuncId, ModuleFunction<B>>,
data_objects: PrimaryMap<DataId, ModuleData<B>>,
}
impl<B> ModuleContents<B>
where
B: Backend,
{
fn get_function_info(&self, name: &ir::ExternalName) -> &ModuleFunction<B> {
if let ir::ExternalName::User { namespace, index } = *name {
debug_assert_eq!(namespace, 0);
let func = FuncId::new(index as usize);
&self.functions[func]
} else {
panic!("unexpected ExternalName kind")
}
}
/// Get the `DataDeclaration` for the function named by `name`.
fn get_data_info(&self, name: &ir::ExternalName) -> &ModuleData<B> {
if let ir::ExternalName::User { namespace, index } = *name {
debug_assert_eq!(namespace, 1);
let data = DataId::new(index as usize);
&self.data_objects[data]
} else {
panic!("unexpected ExternalName kind")
}
}
}
/// This provides a view to the state of a module which allows `ir::ExternalName`s to be translated
/// into `FunctionDeclaration`s and `DataDeclaration`s.
pub struct ModuleNamespace<'a, B: 'a>
where
B: Backend,
{
contents: &'a ModuleContents<B>,
}
impl<'a, B> ModuleNamespace<'a, B>
where
B: Backend,
{
/// Get the `FunctionDeclaration` for the function named by `name`.
pub fn get_function_decl(&self, name: &ir::ExternalName) -> &FunctionDeclaration {
&self.contents.get_function_info(name).decl
}
/// Get the `DataDeclaration` for the function named by `name`.
pub fn get_data_decl(&self, name: &ir::ExternalName) -> &DataDeclaration {
&self.contents.get_data_info(name).decl
}
/// Get the definition for the function named by `name`, along with its name
/// and signature.
pub fn get_function_definition(
&self,
name: &ir::ExternalName,
) -> (Option<&B::CompiledFunction>, &str, &ir::Signature) {
let info = self.contents.get_function_info(name);
debug_assert_eq!(info.decl.linkage.is_definable(), info.compiled.is_some());
(
info.compiled.as_ref(),
&info.decl.name,
&info.decl.signature,
)
}
/// Get the definition for the data object named by `name`, along with its name
/// and writable flag
pub fn get_data_definition(
&self,
name: &ir::ExternalName,
) -> (Option<&B::CompiledData>, &str, bool) {
let info = self.contents.get_data_info(name);
debug_assert_eq!(info.decl.linkage.is_definable(), info.compiled.is_some());
(info.compiled.as_ref(), &info.decl.name, info.decl.writable)
}
/// Return whether `name` names a function, rather than a data object.
pub fn is_function(&self, name: &ir::ExternalName) -> bool {
if let ir::ExternalName::User { namespace, .. } = *name {
namespace == 0
} else {
panic!("unexpected ExternalName kind")
}
}
}
/// A `Module` is a utility for collecting functions and data objects, and linking them together.
pub struct Module<B>
where
B: Backend,
{
names: HashMap<String, FuncOrDataId>,
contents: ModuleContents<B>,
backend: B,
}
impl<B> Module<B>
where
B: Backend,
{
/// Create a new `Module`.
pub fn new(backend: B) -> Self {
Self {
names: HashMap::new(),
contents: ModuleContents {
functions: PrimaryMap::new(),
data_objects: PrimaryMap::new(),
},
backend,
}
}
/// Return then pointer type for the current target.
pub fn pointer_type(&self) -> ir::types::Type {
if self.backend.isa().flags().is_64bit() {
ir::types::I64
} else {
ir::types::I32
}
}
/// Declare a function in this module.
pub fn declare_function(
&mut self,
name: &str,
linkage: Linkage,
signature: &ir::Signature,
) -> Result<FuncId, CtonError> {
// TODO: Can we avoid allocating names so often?
use std::collections::hash_map::Entry::*;
match self.names.entry(name.to_owned()) {
Occupied(entry) => {
match *entry.get() {
FuncOrDataId::Func(id) => {
let existing = &mut self.contents.functions[id];
existing.merge(linkage);
self.backend.declare_function(name, existing.decl.linkage);
Ok(id)
}
FuncOrDataId::Data(..) => unimplemented!(),
}
}
Vacant(entry) => {
let id = self.contents.functions.push(ModuleFunction {
decl: FunctionDeclaration {
name: name.to_owned(),
linkage,
signature: signature.clone(),
},
compiled: None,
finalized: false,
});
entry.insert(FuncOrDataId::Func(id));
self.backend.declare_function(name, linkage);
Ok(id)
}
}
}
/// Declare a data object in this module.
pub fn declare_data(
&mut self,
name: &str,
linkage: Linkage,
writable: bool,
) -> Result<DataId, CtonError> {
// TODO: Can we avoid allocating names so often?
use std::collections::hash_map::Entry::*;
match self.names.entry(name.to_owned()) {
Occupied(entry) => {
match *entry.get() {
FuncOrDataId::Data(id) => {
let existing = &mut self.contents.data_objects[id];
existing.merge(linkage, writable);
self.backend.declare_data(
name,
existing.decl.linkage,
existing.decl.writable,
);
Ok(id)
}
FuncOrDataId::Func(..) => unimplemented!(),
}
}
Vacant(entry) => {
let id = self.contents.data_objects.push(ModuleData {
decl: DataDeclaration {
name: name.to_owned(),
linkage,
writable,
},
compiled: None,
finalized: false,
});
entry.insert(FuncOrDataId::Data(id));
self.backend.declare_data(name, linkage, writable);
Ok(id)
}
}
}
/// Use this when you're building the IR of a function to reference a function.
///
/// TODO: Coalesce redundant decls and signatures.
/// TODO: Look into ways to reduce the risk of using a FuncRef in the wrong function.
pub fn declare_func_in_func(&self, func: FuncId, in_func: &mut ir::Function) -> ir::FuncRef {
let decl = &self.contents.functions[func].decl;
let signature = in_func.import_signature(decl.signature.clone());
let colocated = decl.linkage.is_final();
in_func.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(0, func.index() as u32),
signature,
colocated,
})
}
/// Use this when you're building the IR of a function to reference a data object.
///
/// TODO: Same as above.
pub fn declare_data_in_func(&self, data: DataId, func: &mut ir::Function) -> ir::GlobalVar {
let decl = &self.contents.data_objects[data].decl;
let colocated = decl.linkage.is_final();
func.create_global_var(ir::GlobalVarData::Sym {
name: ir::ExternalName::user(1, data.index() as u32),
colocated,
})
}
/// TODO: Same as above.
pub fn declare_func_in_data(&self, func: FuncId, ctx: &mut DataContext) -> ir::FuncRef {
ctx.import_function(ir::ExternalName::user(0, func.index() as u32))
}
/// TODO: Same as above.
pub fn declare_data_in_data(&self, data: DataId, ctx: &mut DataContext) -> ir::GlobalVar {
ctx.import_global_var(ir::ExternalName::user(1, data.index() as u32))
}
/// Define a function, producing the function body from the given `Context`.
pub fn define_function(&mut self, func: FuncId, ctx: &mut Context) -> CtonResult {
let compiled = {
let code_size = ctx.compile(self.backend.isa())?;
let info = &self.contents.functions[func];
debug_assert!(
info.compiled.is_none(),
"functions can be defined only once"
);
debug_assert!(
info.decl.linkage.is_definable(),
"imported functions cannot be defined"
);
Some(self.backend.define_function(
&info.decl.name,
ctx,
&ModuleNamespace::<B> {
contents: &self.contents,
},
code_size,
)?)
};
self.contents.functions[func].compiled = compiled;
Ok(())
}
/// Define a function, producing the data contents from the given `DataContext`.
pub fn define_data(&mut self, data: DataId, data_ctx: &DataContext) -> CtonResult {
let compiled = {
let info = &self.contents.data_objects[data];
debug_assert!(
info.compiled.is_none(),
"functions can be defined only once"
);
debug_assert!(
info.decl.linkage.is_definable(),
"imported functions cannot be defined"
);
Some(self.backend.define_data(
&info.decl.name,
data_ctx,
&ModuleNamespace::<B> {
contents: &self.contents,
},
)?)
};
self.contents.data_objects[data].compiled = compiled;
Ok(())
}
/// Write the address of `what` into the data for `data` at `offset`. `data` must refer to a
/// defined data object.
pub fn write_data_funcaddr(&mut self, data: DataId, offset: usize, what: ir::FuncRef) {
let info = &mut self.contents.data_objects[data];
debug_assert!(
info.decl.linkage.is_definable(),
"imported data cannot contain references"
);
self.backend.write_data_funcaddr(
&mut info.compiled.as_mut().expect(
"`data` must refer to a defined data object",
),
offset,
what,
);
}
/// Write the address of `what` plus `addend` into the data for `data` at `offset`. `data` must
/// refer to a defined data object.
pub fn write_data_dataaddr(
&mut self,
data: DataId,
offset: usize,
what: ir::GlobalVar,
addend: binemit::Addend,
) {
let info = &mut self.contents.data_objects[data];
debug_assert!(
info.decl.linkage.is_definable(),
"imported data cannot contain references"
);
self.backend.write_data_dataaddr(
&mut info.compiled.as_mut().expect(
"`data` must refer to a defined data object",
),
offset,
what,
addend,
);
}
/// Perform all outstanding relocations on the given function. This requires all `Local`
/// and `Export` entities referenced to be defined.
pub fn finalize_function(&mut self, func: FuncId) -> B::FinalizedFunction {
let output = {
let info = &self.contents.functions[func];
debug_assert!(
info.decl.linkage.is_definable(),
"imported data cannot be finalized"
);
self.backend.finalize_function(
info.compiled.as_ref().expect(
"function must be compiled before it can be finalized",
),
&ModuleNamespace::<B> { contents: &self.contents },
)
};
self.contents.functions[func].finalized = true;
output
}
/// Perform all outstanding relocations on the given data object. This requires all
/// `Local` and `Export` entities referenced to be defined.
pub fn finalize_data(&mut self, data: DataId) -> B::FinalizedData {
let output = {
let info = &self.contents.data_objects[data];
debug_assert!(
info.decl.linkage.is_definable(),
"imported data cannot be finalized"
);
self.backend.finalize_data(
info.compiled.as_ref().expect(
"data object must be compiled before it can be finalized",
),
&ModuleNamespace::<B> { contents: &self.contents },
)
};
self.contents.data_objects[data].finalized = true;
output
}
/// Finalize all functions and data objects. Note that this doesn't return the
/// final artifacts returned from `finalize_function` or `finalize_data`.
pub fn finalize_all(&mut self) {
// TODO: Could we use something like `into_iter()` here?
for info in self.contents.functions.values() {
if info.decl.linkage.is_definable() && !info.finalized {
self.backend.finalize_function(
info.compiled.as_ref().expect(
"function must be compiled before it can be finalized",
),
&ModuleNamespace::<B> { contents: &self.contents },
);
}
}
for info in self.contents.data_objects.values() {
if info.decl.linkage.is_definable() && !info.finalized {
self.backend.finalize_data(
info.compiled.as_ref().expect(
"data object must be compiled before it can be finalized",
),
&ModuleNamespace::<B> { contents: &self.contents },
);
}
}
}
/// Consume the module and return its contained `Backend`. Some `Backend`
/// implementations have additional features not available through the
/// `Module` interface.
pub fn consume(self) -> B {
self.backend
}
}

21
lib/simplejit/Cargo.toml Normal file
View File

@@ -0,0 +1,21 @@
[package]
name = "cretonne-simplejit"
version = "0.4.4"
authors = ["The Cretonne Project Developers"]
description = "A simple JIT library backed by Cretonne"
repository = "https://github.com/Cretonne/cretonne"
documentation = "https://cretonne.readthedocs.io/"
license = "Apache-2.0"
readme = "README.md"
[dependencies]
cretonne-codegen = { path = "../codegen", version = "0.4.4" }
cretonne-module = { path = "../module", version = "0.4.4" }
cretonne-native = { path = "../native", version = "0.4.4" }
region = "0.2.0"
libc = "0.2.40"
errno = "0.2.3"
[badges]
maintenance = { status = "experimental" }
travis-ci = { repository = "Cretonne/cretonne" }

4
lib/simplejit/README.md Normal file
View File

@@ -0,0 +1,4 @@
This crate provides a simple JIT library that uses
[Cretonne](https://crates.io/crates/cretonne).
This crate is extremely experimental.

View File

@@ -0,0 +1,365 @@
//! Defines `SimpleJITBackend`.
use cretonne_codegen::binemit::{Addend, CodeOffset, Reloc, RelocSink, TrapSink};
use cretonne_codegen::isa::TargetIsa;
use cretonne_codegen::result::CtonError;
use cretonne_codegen::{self, ir, settings};
use cretonne_module::{Backend, DataContext, Linkage, ModuleNamespace, Writability,
DataDescription, Init};
use cretonne_native;
use std::ffi::CString;
use std::ptr;
use libc;
use memory::Memory;
/// A record of a relocation to perform.
struct RelocRecord {
offset: CodeOffset,
reloc: Reloc,
name: ir::ExternalName,
addend: Addend,
}
pub struct SimpleJITCompiledFunction {
code: *mut u8,
size: usize,
relocs: Vec<RelocRecord>,
}
pub struct SimpleJITCompiledData {
storage: *mut u8,
size: usize,
relocs: Vec<RelocRecord>,
}
/// A `SimpleJITBackend` implements `Backend` and emits code and data into memory where it can be
/// directly called and accessed.
pub struct SimpleJITBackend {
isa: Box<TargetIsa>,
code_memory: Memory,
readonly_memory: Memory,
writable_memory: Memory,
}
impl SimpleJITBackend {
/// Create a new `SimpleJITBackend`.
pub fn new() -> Self {
let (flag_builder, isa_builder) = cretonne_native::builders().unwrap_or_else(|_| {
panic!("host machine is not a supported target");
});
let isa = isa_builder.finish(settings::Flags::new(&flag_builder));
Self::with_isa(isa)
}
/// Create a new `SimpleJITBackend` with an arbitrary target. This is mainly
/// useful for testing.
///
/// SimpleJIT requires a `TargetIsa` configured for non-PIC.
///
/// To create a `SimpleJITBackend` for native use, use the `new` constructor
/// instead.
pub fn with_isa(isa: Box<TargetIsa>) -> Self {
debug_assert!(!isa.flags().is_pic(), "SimpleJIT requires non-PIC code");
Self {
isa,
code_memory: Memory::new(),
readonly_memory: Memory::new(),
writable_memory: Memory::new(),
}
}
}
impl<'simple_jit_backend> Backend for SimpleJITBackend {
type CompiledFunction = SimpleJITCompiledFunction;
type CompiledData = SimpleJITCompiledData;
type FinalizedFunction = *const u8;
type FinalizedData = (*mut u8, usize);
fn isa(&self) -> &TargetIsa {
&*self.isa
}
fn declare_function(&mut self, _name: &str, _linkage: Linkage) {
// Nothing to do.
}
fn declare_data(&mut self, _name: &str, _linkage: Linkage, _writable: bool) {
// Nothing to do.
}
fn define_function(
&mut self,
_name: &str,
ctx: &cretonne_codegen::Context,
_namespace: &ModuleNamespace<Self>,
code_size: u32,
) -> Result<Self::CompiledFunction, CtonError> {
let size = code_size as usize;
let ptr = self.code_memory.allocate(size).expect(
"TODO: handle OOM etc.",
);
let mut reloc_sink = SimpleJITRelocSink::new();
let mut trap_sink = SimpleJITTrapSink {};
ctx.emit_to_memory(ptr, &mut reloc_sink, &mut trap_sink, &*self.isa);
Ok(Self::CompiledFunction {
code: ptr,
size,
relocs: reloc_sink.relocs,
})
}
fn define_data(
&mut self,
_name: &str,
data: &DataContext,
_namespace: &ModuleNamespace<Self>,
) -> Result<Self::CompiledData, CtonError> {
let &DataDescription {
writable,
ref init,
ref function_decls,
ref data_decls,
ref function_relocs,
ref data_relocs,
} = data.description();
let size = init.size();
let storage = match writable {
Writability::Readonly => {
self.writable_memory.allocate(size).expect(
"TODO: handle OOM etc.",
)
}
Writability::Writable => {
self.writable_memory.allocate(size).expect(
"TODO: handle OOM etc.",
)
}
};
match *init {
Init::Uninitialized => {
panic!("data is not initialized yet");
}
Init::Zeros { .. } => {
unsafe { ptr::write_bytes(storage, 0, size) };
}
Init::Bytes { ref contents } => {
let src = contents.as_ptr();
unsafe { ptr::copy_nonoverlapping(src, storage, size) };
}
}
let reloc = if self.isa.flags().is_64bit() {
Reloc::Abs8
} else {
Reloc::Abs4
};
let mut relocs = Vec::new();
for &(offset, id) in function_relocs {
relocs.push(RelocRecord {
reloc,
offset,
name: function_decls[id].clone(),
addend: 0,
});
}
for &(offset, id, addend) in data_relocs {
relocs.push(RelocRecord {
reloc,
offset,
name: data_decls[id].clone(),
addend,
});
}
Ok(Self::CompiledData {
storage,
size,
relocs,
})
}
fn write_data_funcaddr(
&mut self,
_data: &mut Self::CompiledData,
_offset: usize,
_what: ir::FuncRef,
) {
unimplemented!();
}
fn write_data_dataaddr(
&mut self,
_data: &mut Self::CompiledData,
_offset: usize,
_what: ir::GlobalVar,
_usize: Addend,
) {
unimplemented!();
}
fn finalize_function(
&mut self,
func: &Self::CompiledFunction,
namespace: &ModuleNamespace<Self>,
) -> Self::FinalizedFunction {
use std::ptr::write_unaligned;
for &RelocRecord {
reloc,
offset,
ref name,
addend,
} in &func.relocs
{
let ptr = func.code;
debug_assert!((offset as usize) < func.size);
let at = unsafe { ptr.offset(offset as isize) };
let base = if namespace.is_function(name) {
let (def, name_str, _signature) = namespace.get_function_definition(&name);
match def {
Some(compiled) => compiled.code,
None => lookup_with_dlsym(name_str),
}
} else {
let (def, name_str, _writable) = namespace.get_data_definition(&name);
match def {
Some(compiled) => compiled.storage,
None => lookup_with_dlsym(name_str),
}
};
// TODO: Handle overflow.
let what = unsafe { base.offset(addend as isize) };
match reloc {
Reloc::Abs4 => {
// TODO: Handle overflow.
unsafe { write_unaligned(at as *mut u32, what as u32) };
}
Reloc::Abs8 => {
unsafe { write_unaligned(at as *mut u64, what as u64) };
}
Reloc::X86PCRel4 => {
// TODO: Handle overflow.
let pcrel = ((what as isize) - (at as isize)) as i32;
unsafe { write_unaligned(at as *mut i32, pcrel) };
}
Reloc::X86GOTPCRel4 |
Reloc::X86PLTRel4 => panic!("unexpected PIC relocation"),
_ => unimplemented!(),
}
}
// Now that we're done patching, make the memory executable.
self.code_memory.set_executable();
func.code
}
fn finalize_data(
&mut self,
data: &Self::CompiledData,
namespace: &ModuleNamespace<Self>,
) -> Self::FinalizedData {
use std::ptr::write_unaligned;
for record in &data.relocs {
match *record {
RelocRecord {
reloc,
offset,
ref name,
addend,
} => {
let ptr = data.storage;
debug_assert!((offset as usize) < data.size);
let at = unsafe { ptr.offset(offset as isize) };
let base = if namespace.is_function(name) {
let (def, name_str, _signature) = namespace.get_function_definition(&name);
match def {
Some(compiled) => compiled.code,
None => lookup_with_dlsym(name_str),
}
} else {
let (def, name_str, _writable) = namespace.get_data_definition(&name);
match def {
Some(compiled) => compiled.storage,
None => lookup_with_dlsym(name_str),
}
};
// TODO: Handle overflow.
let what = unsafe { base.offset(addend as isize) };
match reloc {
Reloc::Abs4 => {
// TODO: Handle overflow.
unsafe { write_unaligned(at as *mut u32, what as u32) };
}
Reloc::Abs8 => {
unsafe { write_unaligned(at as *mut u64, what as u64) };
}
Reloc::X86PCRel4 |
Reloc::X86GOTPCRel4 |
Reloc::X86PLTRel4 => panic!("unexpected text relocation in data"),
_ => unimplemented!(),
}
}
}
}
self.readonly_memory.set_readonly();
(data.storage, data.size)
}
}
fn lookup_with_dlsym(name: &str) -> *const u8 {
let c_str = CString::new(name).unwrap();
let c_str_ptr = c_str.as_ptr();
let sym = unsafe { libc::dlsym(libc::RTLD_DEFAULT, c_str_ptr) };
if sym.is_null() {
panic!("can't resolve symbol {}", name);
}
sym as *const u8
}
struct SimpleJITRelocSink {
pub relocs: Vec<RelocRecord>,
}
impl SimpleJITRelocSink {
pub fn new() -> Self {
Self { relocs: Vec::new() }
}
}
impl RelocSink for SimpleJITRelocSink {
fn reloc_ebb(&mut self, _offset: CodeOffset, _reloc: Reloc, _ebb_offset: CodeOffset) {
unimplemented!();
}
fn reloc_external(
&mut self,
offset: CodeOffset,
reloc: Reloc,
name: &ir::ExternalName,
addend: Addend,
) {
self.relocs.push(RelocRecord {
offset,
reloc,
name: name.clone(),
addend,
});
}
fn reloc_jt(&mut self, _offset: CodeOffset, _reloc: Reloc, _jt: ir::JumpTable) {
unimplemented!();
}
}
struct SimpleJITTrapSink {}
impl TrapSink for SimpleJITTrapSink {
// Ignore traps for now. For now, frontends should just avoid generating code that traps.
fn trap(&mut self, _offset: CodeOffset, _srcloc: ir::SourceLoc, _code: ir::TrapCode) {}
}

15
lib/simplejit/src/lib.rs Normal file
View File

@@ -0,0 +1,15 @@
//! Top-level lib.rs for `cretonne_simplejit`.
#![deny(missing_docs, trivial_numeric_casts, unused_extern_crates)]
extern crate cretonne_codegen;
extern crate cretonne_module;
extern crate cretonne_native;
extern crate errno;
extern crate region;
extern crate libc;
mod backend;
mod memory;
pub use backend::SimpleJITBackend;

108
lib/simplejit/src/memory.rs Normal file
View File

@@ -0,0 +1,108 @@
use std::mem;
use std::ptr;
use errno;
use libc;
use region;
struct PtrLen {
ptr: *mut u8,
len: usize,
}
impl PtrLen {
fn new() -> Self {
Self {
ptr: ptr::null_mut(),
len: 0,
}
}
fn with_size(size: usize) -> Result<Self, String> {
let page_size = region::page::size();
let alloc_size = (size + (page_size - 1)) & (page_size - 1);
unsafe {
let mut ptr: *mut libc::c_void = mem::uninitialized();
let err = libc::posix_memalign(&mut ptr, page_size, alloc_size);
if err == 0 {
Ok(Self {
ptr: mem::transmute(ptr),
len: alloc_size,
})
} else {
Err(errno::Errno(err).to_string())
}
}
}
}
pub struct Memory {
allocations: Vec<PtrLen>,
executable: usize,
current: PtrLen,
position: usize,
}
impl Memory {
pub fn new() -> Self {
Self {
allocations: Vec::new(),
executable: 0,
current: PtrLen::new(),
position: 0,
}
}
fn finish_current(&mut self) {
self.allocations.push(mem::replace(
&mut self.current,
PtrLen::new(),
));
self.position = 0;
}
/// TODO: Use a proper error type.
pub fn allocate(&mut self, size: usize) -> Result<*mut u8, String> {
if size <= self.current.len - self.position {
// TODO: Ensure overflow is not possible.
let ptr = unsafe { self.current.ptr.offset(self.position as isize) };
self.position += size;
return Ok(ptr);
}
self.finish_current();
// TODO: Allocate more at a time.
self.current = PtrLen::with_size(size)?;
self.position = size;
Ok(self.current.ptr)
}
pub fn set_executable(&mut self) {
self.finish_current();
for &PtrLen { ptr, len } in &self.allocations[self.executable..] {
if len != 0 {
unsafe {
region::protect(ptr, len, region::Protection::Execute)
.expect("unable to make memory executable");
}
}
}
}
pub fn set_readonly(&mut self) {
self.finish_current();
for &PtrLen { ptr, len } in &self.allocations[self.executable..] {
if len != 0 {
unsafe {
region::protect(ptr, len, region::Protection::Read).expect(
"unable to make memory readonly",
);
}
}
}
}
}
// TODO: Implement Drop to unprotect and deallocate the memory?