From 76db9f022d3903f6f93a0b641c14cfb621f69c82 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 17 Apr 2018 10:52:36 -0700 Subject: [PATCH] [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. --- cranelift/Cargo.toml | 3 + lib/codegen/src/binemit/mod.rs | 2 +- lib/codegen/src/ir/extfunc.rs | 10 +- lib/codegen/src/ir/valueloc.rs | 2 +- lib/faerie/Cargo.toml | 20 ++ lib/faerie/README.md | 4 + lib/faerie/src/backend.rs | 298 ++++++++++++++++++ lib/faerie/src/container.rs | 33 ++ lib/faerie/src/lib.rs | 19 ++ lib/faerie/src/target.rs | 18 ++ lib/module/Cargo.toml | 17 ++ lib/module/README.md | 3 + lib/module/src/backend.rs | 97 ++++++ lib/module/src/data_context.rs | 205 +++++++++++++ lib/module/src/lib.rs | 15 + lib/module/src/module.rs | 541 +++++++++++++++++++++++++++++++++ lib/simplejit/Cargo.toml | 21 ++ lib/simplejit/README.md | 4 + lib/simplejit/src/backend.rs | 365 ++++++++++++++++++++++ lib/simplejit/src/lib.rs | 15 + lib/simplejit/src/memory.rs | 108 +++++++ 21 files changed, 1793 insertions(+), 7 deletions(-) create mode 100644 lib/faerie/Cargo.toml create mode 100644 lib/faerie/README.md create mode 100644 lib/faerie/src/backend.rs create mode 100644 lib/faerie/src/container.rs create mode 100644 lib/faerie/src/lib.rs create mode 100644 lib/faerie/src/target.rs create mode 100644 lib/module/Cargo.toml create mode 100644 lib/module/README.md create mode 100644 lib/module/src/backend.rs create mode 100644 lib/module/src/data_context.rs create mode 100644 lib/module/src/lib.rs create mode 100644 lib/module/src/module.rs create mode 100644 lib/simplejit/Cargo.toml create mode 100644 lib/simplejit/README.md create mode 100644 lib/simplejit/src/backend.rs create mode 100644 lib/simplejit/src/lib.rs create mode 100644 lib/simplejit/src/memory.rs diff --git a/cranelift/Cargo.toml b/cranelift/Cargo.toml index 4a28857088..4ca0fa7760 100644 --- a/cranelift/Cargo.toml +++ b/cranelift/Cargo.toml @@ -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" diff --git a/lib/codegen/src/binemit/mod.rs b/lib/codegen/src/binemit/mod.rs index 9c78e4f784..8549b77aad 100644 --- a/lib/codegen/src/binemit/mod.rs +++ b/lib/codegen/src/binemit/mod.rs @@ -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, diff --git a/lib/codegen/src/ir/extfunc.rs b/lib/codegen/src/ir/extfunc.rs index 474b463727..afb32fee81 100644 --- a/lib/codegen/src/ir/extfunc.rs +++ b/lib/codegen/src/ir/extfunc.rs @@ -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, @@ -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. /// diff --git a/lib/codegen/src/ir/valueloc.rs b/lib/codegen/src/ir/valueloc.rs index aecf23d0bb..7f68e09792 100644 --- a/lib/codegen/src/ir/valueloc.rs +++ b/lib/codegen/src/ir/valueloc.rs @@ -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, diff --git a/lib/faerie/Cargo.toml b/lib/faerie/Cargo.toml new file mode 100644 index 0000000000..32434077f7 --- /dev/null +++ b/lib/faerie/Cargo.toml @@ -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" } diff --git a/lib/faerie/README.md b/lib/faerie/README.md new file mode 100644 index 0000000000..7817ccb21e --- /dev/null +++ b/lib/faerie/README.md @@ -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. diff --git a/lib/faerie/src/backend.rs b/lib/faerie/src/backend.rs new file mode 100644 index 0000000000..a284883927 --- /dev/null +++ b/lib/faerie/src/backend.rs @@ -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, + artifact: faerie::Artifact, + format: container::Format, +} + +impl FaerieBackend { + /// Create a new `FaerieBackend` using the given Cretonne target. + pub fn new( + isa: Box, + name: String, + format: container::Format, + ) -> Result { + 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, Error> { + match self.format { + container::Format::ELF => self.artifact.emit::(), + container::Format::MachO => self.artifact.emit::(), + } + } + + /// 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::(sink), + container::Format::MachO => self.artifact.write::(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, + code_size: u32, + ) -> Result { + let mut code: Vec = 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, + ) -> Result { + 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, + ) { + // Nothing to do. + } + + fn finalize_data(&mut self, _data: &FaerieCompiledData, _namespace: &ModuleNamespace) { + // 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) {} +} diff --git a/lib/faerie/src/container.rs b/lib/faerie/src/container.rs new file mode 100644 index 0000000000..c78f05878c --- /dev/null +++ b/lib/faerie/src/container.rs @@ -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!(), + } +} diff --git a/lib/faerie/src/lib.rs b/lib/faerie/src/lib.rs new file mode 100644 index 0000000000..dc1dd5a0c3 --- /dev/null +++ b/lib/faerie/src/lib.rs @@ -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; diff --git a/lib/faerie/src/target.rs b/lib/faerie/src/target.rs new file mode 100644 index 0000000000..e1116dd08d --- /dev/null +++ b/lib/faerie/src/target.rs @@ -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 { + 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)), + } +} diff --git a/lib/module/Cargo.toml b/lib/module/Cargo.toml new file mode 100644 index 0000000000..04e7b6e4b6 --- /dev/null +++ b/lib/module/Cargo.toml @@ -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" } diff --git a/lib/module/README.md b/lib/module/README.md new file mode 100644 index 0000000000..370bde4333 --- /dev/null +++ b/lib/module/README.md @@ -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. diff --git a/lib/module/src/backend.rs b/lib/module/src/backend.rs new file mode 100644 index 0000000000..0d6a6c6672 --- /dev/null +++ b/lib/module/src/backend.rs @@ -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, + code_size: u32, + ) -> Result; + + /// 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, + ) -> Result; + + /// 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::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::FinalizedData; +} diff --git a/lib/module/src/data_context.rs b/lib/module/src/data_context.rs new file mode 100644 index 0000000000..510b549e90 --- /dev/null +++ b/lib/module/src/data_context.rs @@ -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, + /// External data object declarations. + pub data_decls: PrimaryMap, + /// 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); + } + } +} diff --git a/lib/module/src/lib.rs b/lib/module/src/lib.rs new file mode 100644 index 0000000000..ebc44420cc --- /dev/null +++ b/lib/module/src/lib.rs @@ -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}; diff --git a/lib/module/src/module.rs b/lib/module/src/module.rs new file mode 100644 index 0000000000..69e9d7432f --- /dev/null +++ b/lib/module/src/module.rs @@ -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 +where + B: Backend, +{ + decl: FunctionDeclaration, + compiled: Option, + finalized: bool, +} + +impl ModuleFunction +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 +where + B: Backend, +{ + decl: DataDeclaration, + compiled: Option, + finalized: bool, +} + +impl ModuleData +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 +where + B: Backend, +{ + functions: PrimaryMap>, + data_objects: PrimaryMap>, +} + +impl ModuleContents +where + B: Backend, +{ + fn get_function_info(&self, name: &ir::ExternalName) -> &ModuleFunction { + 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 { + 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, +} + +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 +where + B: Backend, +{ + names: HashMap, + contents: ModuleContents, + backend: B, +} + +impl Module +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 { + // 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 { + // 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:: { + 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:: { + 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:: { 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:: { 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:: { 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:: { 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 + } +} diff --git a/lib/simplejit/Cargo.toml b/lib/simplejit/Cargo.toml new file mode 100644 index 0000000000..edc87d8b64 --- /dev/null +++ b/lib/simplejit/Cargo.toml @@ -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" } diff --git a/lib/simplejit/README.md b/lib/simplejit/README.md new file mode 100644 index 0000000000..8b0aea6d28 --- /dev/null +++ b/lib/simplejit/README.md @@ -0,0 +1,4 @@ +This crate provides a simple JIT library that uses +[Cretonne](https://crates.io/crates/cretonne). + +This crate is extremely experimental. diff --git a/lib/simplejit/src/backend.rs b/lib/simplejit/src/backend.rs new file mode 100644 index 0000000000..7bc5708556 --- /dev/null +++ b/lib/simplejit/src/backend.rs @@ -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, +} + +pub struct SimpleJITCompiledData { + storage: *mut u8, + size: usize, + relocs: Vec, +} + +/// A `SimpleJITBackend` implements `Backend` and emits code and data into memory where it can be +/// directly called and accessed. +pub struct SimpleJITBackend { + isa: Box, + 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) -> 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, + code_size: u32, + ) -> Result { + 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, + ) -> Result { + 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::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::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, +} + +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) {} +} diff --git a/lib/simplejit/src/lib.rs b/lib/simplejit/src/lib.rs new file mode 100644 index 0000000000..0157466cac --- /dev/null +++ b/lib/simplejit/src/lib.rs @@ -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; diff --git a/lib/simplejit/src/memory.rs b/lib/simplejit/src/memory.rs new file mode 100644 index 0000000000..235a3976d7 --- /dev/null +++ b/lib/simplejit/src/memory.rs @@ -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 { + 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, + 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?