Skip to main content

Compiled

Struct Compiled 

Source
pub struct Compiled { /* private fields */ }
Expand description

A compiled function: native machine code living in executable memory, ready to run.

A Jit produces one of these by lowering an ir_lang::Function to host machine code and placing it in a page-aligned, read-execute Region. Compiled owns that region, so the code stays mapped and runnable for exactly as long as the value is alive and is freed when it is dropped — calling into a function whose Compiled has been dropped is a use-after-free.

The signature accessors — name, params, and ret — report the function as it was compiled, so a caller can pick the right function-pointer type before calling. Running the code is inherently unsafe: the machine has no way to check that the type you transmute the entry point to matches what was compiled, so that final step is your responsibility through entry. See its documentation for the contract.

Compiled is Send and Sync: the region it owns is, and the rest of its fields are plain data. Moving it between threads moves the mapping with it.

§Examples

Compile a function and read back what it is:

use jit_lang::compile;
use ir_lang::{Builder, BinOp, Type};

// fn double(x: int) -> int { x + x }
let mut b = Builder::new("double", &[Type::Int], Type::Int);
let x = b.block_params(b.entry())[0];
let sum = b.bin(BinOp::Add, x, x);
b.ret(Some(sum));

let f = compile(&b.finish()).expect("double is well-formed");
assert_eq!(f.name(), "double");
assert_eq!(f.params(), &[Type::Int]);
assert_eq!(f.ret(), Type::Int);
assert!(f.code_len() > 0);

Implementations§

Source§

impl Compiled

Source

pub fn name(&self) -> &str

The compiled function’s name, as it was given to the IR builder.

§Examples
use jit_lang::compile;
use ir_lang::{Builder, Type};

let mut b = Builder::new("noop", &[], Type::Unit);
b.ret(None);
assert_eq!(compile(&b.finish()).unwrap().name(), "noop");
Source

pub fn params(&self) -> &[Type]

The parameter types, in declaration order. These tell you the argument types of the function-pointer you transmute to with entry.

§Examples
use jit_lang::compile;
use ir_lang::{Builder, Type};

let mut b = Builder::new("f", &[Type::Int, Type::Float], Type::Int);
let x = b.block_params(b.entry())[0];
b.ret(Some(x));
assert_eq!(compile(&b.finish()).unwrap().params(), &[Type::Int, Type::Float]);
Source

pub fn ret(&self) -> Type

The return type. A Unit return means the function yields no value, so the matching function-pointer type has no return.

§Examples
use jit_lang::compile;
use ir_lang::{Builder, Type};

let mut b = Builder::new("f", &[], Type::Unit);
b.ret(None);
assert_eq!(compile(&b.finish()).unwrap().ret(), Type::Unit);
Source

pub fn code_len(&self) -> usize

The length of the emitted machine code, in bytes.

This is the size of the function body, which is at most the length of the region holding it — the region is rounded up to whole pages, so it is usually larger. Useful for inspecting or logging how much code a function compiled to.

§Examples
use jit_lang::compile;
use ir_lang::{Builder, Type};

let mut b = Builder::new("f", &[Type::Int], Type::Int);
let x = b.block_params(b.entry())[0];
b.ret(Some(x));
// An identity function still compiles to at least one instruction.
assert!(compile(&b.finish()).unwrap().code_len() > 0);
Source

pub fn as_ptr(&self) -> *const u8

A raw pointer to the first byte of the compiled code — the function’s entry point.

The pointer is valid to obtain and to compare; it is into read-execute memory, so reading or running through it is unsafe. For the common case of calling the function, prefer entry, which produces a typed function-pointer. The pointer is valid only while this Compiled is alive.

§Examples
use jit_lang::compile;
use ir_lang::{Builder, Type};

let mut b = Builder::new("f", &[], Type::Unit);
b.ret(None);
let f = compile(&b.finish()).unwrap();
assert!(!f.as_ptr().is_null());
Source

pub unsafe fn entry<F: Copy>(&self) -> F

Reinterprets the entry point as a function pointer of type F so the compiled code can be called.

This is how you run a compiled function: pick an extern "C" function-pointer type whose signature matches what was compiled, get it from entry, and call it. The mapping from IR types to the C ABI is int to a 64-bit integer, float to f64, bool to a byte that is 0 or 1, and a unit return to no return value. The compiled code uses the host C calling convention, which is what extern "C" denotes, so the two line up.

§Safety

The caller guarantees all of the following:

  • F is a function-pointer type (so it is pointer-sized, the size of the entry address). Passing a non-function or differently-sized type is undefined behavior.
  • F’s signature matches the compiled function: one extern "C" argument per entry in params with the ABI type above, and a return that matches ret. A mismatch is undefined behavior.
  • Every call through the returned pointer happens while self is alive. Once self is dropped the code is unmapped and the pointer dangles.
§Examples

Call a compiled fn double(x: int) -> int:

use jit_lang::compile;
use ir_lang::{Builder, BinOp, Type};

let mut b = Builder::new("double", &[Type::Int], Type::Int);
let x = b.block_params(b.entry())[0];
let sum = b.bin(BinOp::Add, x, x);
b.ret(Some(sum));
let f = compile(&b.finish()).unwrap();

// SAFETY: the signature matches `fn double(x: int) -> int`, and `f` outlives the call.
let double: extern "C" fn(i64) -> i64 = unsafe { f.entry() };
assert_eq!(double(21), 42);
assert_eq!(double(-5), -10);

Call one that returns a bool:

use jit_lang::compile;
use ir_lang::{Builder, BinOp, Type};

// fn lt(a: int, b: int) -> bool { a < b }
let mut b = Builder::new("lt", &[Type::Int, Type::Int], Type::Bool);
let a = b.block_params(b.entry())[0];
let c = b.block_params(b.entry())[1];
let lt = b.bin(BinOp::Lt, a, c);
b.ret(Some(lt));
let f = compile(&b.finish()).unwrap();

// SAFETY: a `bool` return is a 0/1 byte, which `extern "C" fn(..) -> bool` reads correctly.
let lt: extern "C" fn(i64, i64) -> bool = unsafe { f.entry() };
assert!(lt(1, 2));
assert!(!lt(2, 1));

Trait Implementations§

Source§

impl Debug for Compiled

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.