diff --git a/.github/workflows/toy.yml b/.github/workflows/toy.yml new file mode 100644 index 0000000..257cc31 --- /dev/null +++ b/.github/workflows/toy.yml @@ -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 diff --git a/Cargo.toml b/Cargo.toml index 425921a..75058d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 04214f4..bd79b9a 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/bin/toy.rs b/src/bin/toy.rs index a12bace..65724f0 100644 --- a/src/bin/toy.rs +++ b/src/bin/toy.rs @@ -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(()) } @@ -29,11 +30,20 @@ fn run_iterative_fib_code(jit: &mut jit::JIT, input: isize) -> Result Result { + 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 { - 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. @@ -71,6 +81,7 @@ const FOO_CODE: &str = r#" 50 } c = c + 2 + assert_int(c, 42) } "#; @@ -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#" diff --git a/src/builtins.rs b/src/builtins.rs new file mode 100644 index 0000000..e701a39 --- /dev/null +++ b/src/builtins.rs @@ -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) +} diff --git a/src/jit.rs b/src/jit.rs index 9818a30..fa956df 100644 --- a/src/jit.rs +++ b/src/jit.rs @@ -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 { @@ -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(), @@ -84,12 +87,12 @@ impl JIT { } /// Create a zero-initialized data section. - pub fn create_data(&mut self, name: &str, contents: Vec) -> Result<&[u8], String> { + pub unsafe fn create_data(&mut self, name: &str, data: D) -> Result<(*mut D, usize), String> { + // println!("CreateData: Name: {}, Type: {}", name ,std::any::type_name::()); // 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::()); + let id = self.module .declare_data(name, Linkage::Export, true, false) .map_err(|e| e.to_string())?; @@ -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. @@ -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, module: &'a mut JITModule, diff --git a/src/lib.rs b/src/lib.rs index 9af7cb3..8a78dd9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod frontend; pub mod jit; +pub mod builtins;