[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:
@@ -19,6 +19,9 @@ cretonne-frontend = { path = "lib/frontend", version = "0.4.4" }
|
||||
cretonne-wasm = { path = "lib/wasm", version = "0.4.4" }
|
||||
cretonne-native = { path = "lib/native", 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" }
|
||||
filecheck = "0.2.1"
|
||||
docopt = "0.8.0"
|
||||
|
||||
@@ -23,7 +23,7 @@ pub type CodeOffset = u32;
|
||||
pub type Addend = i64;
|
||||
|
||||
/// Relocation kinds for every ISA
|
||||
#[derive(Debug)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Reloc {
|
||||
/// absolute 4-byte
|
||||
Abs4,
|
||||
|
||||
@@ -19,7 +19,7 @@ use std::vec::Vec;
|
||||
///
|
||||
/// A signature can optionally include ISA-specific ABI information which specifies exactly how
|
||||
/// arguments and return values are passed.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Signature {
|
||||
/// The arguments passed to the function.
|
||||
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
|
||||
/// how the argument is passed.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct AbiParam {
|
||||
/// Type of the argument value.
|
||||
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
|
||||
/// general-purpose register.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum ArgumentExtension {
|
||||
/// No extension, high bits are indeterminate.
|
||||
None,
|
||||
@@ -242,7 +242,7 @@ pub enum ArgumentExtension {
|
||||
/// frame pointers and callee-saved registers.
|
||||
///
|
||||
/// 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 {
|
||||
/// A normal user program value passed to or from a function.
|
||||
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
|
||||
/// architecture and possibly the operating system, a function's calling convention is only fully
|
||||
/// determined by a `(TargetIsa, CallConv)` tuple.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum CallConv {
|
||||
/// The System V-style calling convention.
|
||||
///
|
||||
|
||||
@@ -95,7 +95,7 @@ impl<'a> fmt::Display for DisplayValueLoc<'a> {
|
||||
/// outgoing arguments.
|
||||
/// - 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.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum ArgumentLoc {
|
||||
/// This argument has not been assigned to a location yet.
|
||||
Unassigned,
|
||||
|
||||
20
lib/faerie/Cargo.toml
Normal file
20
lib/faerie/Cargo.toml
Normal 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
4
lib/faerie/README.md
Normal 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
298
lib/faerie/src/backend.rs
Normal 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) {}
|
||||
}
|
||||
33
lib/faerie/src/container.rs
Normal file
33
lib/faerie/src/container.rs
Normal 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
19
lib/faerie/src/lib.rs
Normal 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
18
lib/faerie/src/target.rs
Normal 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
17
lib/module/Cargo.toml
Normal 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
3
lib/module/README.md
Normal 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
97
lib/module/src/backend.rs
Normal 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;
|
||||
}
|
||||
205
lib/module/src/data_context.rs
Normal file
205
lib/module/src/data_context.rs
Normal 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
15
lib/module/src/lib.rs
Normal 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
541
lib/module/src/module.rs
Normal 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
21
lib/simplejit/Cargo.toml
Normal 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
4
lib/simplejit/README.md
Normal 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.
|
||||
365
lib/simplejit/src/backend.rs
Normal file
365
lib/simplejit/src/backend.rs
Normal 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
15
lib/simplejit/src/lib.rs
Normal 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
108
lib/simplejit/src/memory.rs
Normal 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?
|
||||
Reference in New Issue
Block a user