Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create one example call_builtin_symbol, add workflow for auto-test and update dependencies. #71

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/toy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Run the tests

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

env:
CARGO_TERM_COLOR: always

jobs:
run_unit_tests:
runs-on: ubuntu-latest
steps:
- name: Check out the repository
uses: actions/checkout@v2

- name: Perform the tests
run: cargo run --package cranelift-jit-demo --bin toy
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ description = "Toy language implemented using cranelift-jit"
edition = "2018"

[dependencies]
peg = "0.6"
cranelift = "0.85.1"
cranelift-module = "0.85.1"
cranelift-jit = "0.85.1"
peg = "0.8.1"
cranelift = "0.89.1"
cranelift-module = "0.89.1"
cranelift-jit = "0.89.1"
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ and it makes efficient use of memory.
And Cranelift is being architected to allow flexibility in how one uses it.
Sometimes that flexibility can be a burden, which we've recently started to
address in a new set of crates, `cranelift-module`, `cranelift-jit`, and
`cranelift-faerie`, which put the pieces together in some easy-to-use
`cranelift-object`, which put the pieces together in some easy-to-use
configurations for working with multiple functions at once. `cranelift-module`
is a common interface for working with multiple functions and data interfaces
at once. This interface can sit on top of `cranelift-jit`, which writes
code and data to memory where they can be executed and accessed. And, it can
sit on top of `cranelift-faerie`, which writes code and data to native .o files
sit on top of `cranelift-object`, which writes code and data to native .o files
which can be linked into native executables.

This post introduces Cranelift by walking through a JIT demo, using
Expand Down
23 changes: 21 additions & 2 deletions src/bin/toy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ fn main() -> Result<(), String> {
"iterative_fib(10) = {}",
run_iterative_fib_code(&mut jit, 10)?
);
run_call_builtin_symbol(&mut jit)?;
run_hello(&mut jit)?;
Ok(())
}
Expand All @@ -29,11 +30,20 @@ fn run_iterative_fib_code(jit: &mut jit::JIT, input: isize) -> Result<isize, Str
unsafe { run_code(jit, ITERATIVE_FIB_CODE, input) }
}

fn run_call_builtin_symbol(jit: &mut jit::JIT) -> Result<isize, String> {
unsafe {
jit.create_data("example_string", *b"Example String\0")?;
run_code(jit, CALL_BUILTIN_SYMBOL_CODE, ())
}
}
fn run_hello(jit: &mut jit::JIT) -> Result<isize, String> {
jit.create_data("hello_string", "hello world!\0".as_bytes().to_vec())?;
unsafe { run_code(jit, HELLO_CODE, ()) }
unsafe {
jit.create_data("hello_string", *b"hello world!\0")?;
run_code(jit, HELLO_CODE, ())
}
}


/// Executes the given code using the cranelift JIT compiler.
///
/// Feeds the given input into the JIT compiled function and returns the resulting output.
Expand Down Expand Up @@ -71,6 +81,7 @@ const FOO_CODE: &str = r#"
50
}
c = c + 2
assert_int(c, 42)
}
"#;

Expand Down Expand Up @@ -105,9 +116,17 @@ const ITERATIVE_FIB_CODE: &str = r#"
n = n - 1
}
}
assert_int(r, 55)
}
"#;

const CALL_BUILTIN_SYMBOL_CODE: &str = r#"
fn call_builtin_symbol() -> (r) {
println_string(&example_string)
println_int(10)
}
"#;

/// Let's say hello, by calling into libc. The puts function is resolved by
/// dlsym to the libc function, and the string &hello_string is defined below.
const HELLO_CODE: &str = r#"
Expand Down
14 changes: 14 additions & 0 deletions src/builtins.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use std::ffi::{c_char, CStr};

pub unsafe extern fn println_string(string: *const c_char) {
let cstr = CStr::from_ptr(string);
println!("{}", cstr.to_str().unwrap())
}

pub extern fn println_int(int: isize) {
println!("{}", int)
}

pub extern fn assert_int(int: isize, pre: isize) {
assert_eq!(int, pre)
}
28 changes: 17 additions & 11 deletions src/jit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use cranelift::prelude::*;
use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::{DataContext, Linkage, Module};
use std::collections::HashMap;
use std::slice;
use crate::builtins::{assert_int, println_int, println_string};

/// The basic JIT class.
pub struct JIT {
Expand All @@ -26,8 +26,11 @@ pub struct JIT {

impl Default for JIT {
fn default() -> Self {
let builder = JITBuilder::new(cranelift_module::default_libcall_names());
let module = JITModule::new(builder.unwrap());
let mut builder = JITBuilder::new(cranelift_module::default_libcall_names()).unwrap();
builder.symbol("println_string", println_string as *const u8);
builder.symbol("println_int", println_int as *const u8);
builder.symbol("assert_int", assert_int as *const u8);
let module = JITModule::new(builder);
Self {
builder_context: FunctionBuilderContext::new(),
ctx: module.make_context(),
Expand Down Expand Up @@ -84,12 +87,12 @@ impl JIT {
}

/// Create a zero-initialized data section.
pub fn create_data(&mut self, name: &str, contents: Vec<u8>) -> Result<&[u8], String> {
pub unsafe fn create_data<D>(&mut self, name: &str, data: D) -> Result<(*mut D, usize), String> {
// println!("CreateData: Name: {}, Type: {}", name ,std::any::type_name::<D>());
// The steps here are analogous to `compile`, except that data is much
// simpler than functions.
self.data_ctx.define(contents.into_boxed_slice());
let id = self
.module
self.data_ctx.define_zeroinit(std::mem::size_of::<D>());
let id = self.module
.declare_data(name, Linkage::Export, true, false)
.map_err(|e| e.to_string())?;

Expand All @@ -98,9 +101,12 @@ impl JIT {
.map_err(|e| e.to_string())?;
self.data_ctx.clear();
self.module.finalize_definitions();
let buffer = self.module.get_finalized_data(id);
// TODO: Can we move the unsafe into cranelift?
Ok(unsafe { slice::from_raw_parts(buffer.0, buffer.1) })
let (ptr, size) = self.module.get_finalized_data(id);
let ptr = ptr as *const D as *mut D;
unsafe {
ptr.write(data);
}
Ok((ptr, size))
}

// Translate from toy-language AST nodes into Cranelift IR.
Expand Down Expand Up @@ -176,7 +182,7 @@ impl JIT {
/// A collection of state used for translating from toy-language AST nodes
/// into Cranelift IR.
struct FunctionTranslator<'a> {
int: types::Type,
int: Type,
builder: FunctionBuilder<'a>,
variables: HashMap<String, Variable>,
module: &'a mut JITModule,
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod frontend;
pub mod jit;
pub mod builtins;