use std::mem;
use cranelift::{
codegen::ir::{FuncRef, JumpTable, StackSlot},
prelude::*,
};
use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::{FuncId, Linkage, Module};
use itertools::{EitherOrBoth, Itertools};
use super::{
errors::jit::JitResult,
parser::{InsType, Instruction, Register},
rt::{put_char, put_value},
};
#[derive(Debug)]
struct Stack {
stack: StackSlot,
ptr: Variable,
start: Variable,
end: Variable,
overflow_trap: Block,
}
const STACK_SIZE: u32 = 128;
pub struct JIT {
builder_context: FunctionBuilderContext,
ctx: codegen::Context,
module: JITModule,
}
impl Default for JIT {
fn default() -> Self {
let mut builder = JITBuilder::new(cranelift_module::default_libcall_names());
let put_val_addr: *const u8 = unsafe { mem::transmute(put_value as fn(_)) };
builder.symbol("put_value", put_val_addr);
let put_char_addr: *const u8 = unsafe { mem::transmute(put_char as fn(_)) };
builder.symbol("put_char", put_char_addr);
let module = JITModule::new(builder);
Self {
builder_context: FunctionBuilderContext::new(),
ctx: module.make_context(),
module,
}
}
}
impl JIT {
pub fn compile(&mut self, ast: &[Instruction]) -> JitResult<fn()> {
let int = self.module.target_config().pointer_type();
let put_val_id = self.make_put_value()?;
let put_char_id = self.make_put_char()?;
let mut builder = FunctionBuilder::new(&mut self.ctx.func, &mut self.builder_context);
let put_val_func = self
.module
.declare_func_in_func(put_val_id, &mut builder.func);
let put_char_func = self
.module
.declare_func_in_func(put_char_id, &mut builder.func);
let stack_byte_size = STACK_SIZE * int.bytes();
let stack_slot = builder.create_stack_slot(StackSlotData::new(
StackSlotKind::ExplicitSlot,
stack_byte_size,
));
let stack_ptr = Variable::new(0);
let stack_start = Variable::new(1);
let stack_end = Variable::new(2);
builder.declare_var(stack_ptr, int);
builder.declare_var(stack_start, int);
builder.declare_var(stack_end, int);
let entry_block = builder.create_block();
builder.append_block_params_for_function_params(entry_block);
builder.switch_to_block(entry_block);
builder.seal_block(entry_block);
let stack_ptr_val = builder.ins().stack_addr(int, stack_slot, 0);
builder.def_var(stack_ptr, stack_ptr_val);
let stack_start_val = builder.use_var(stack_ptr);
builder.def_var(stack_start, stack_start_val);
let stack_start_val = builder.use_var(stack_ptr);
let stack_size_val = builder.ins().iconst(int, stack_byte_size as i64);
let stack_end_val = builder.ins().iadd(stack_start_val, stack_size_val);
builder.def_var(stack_end, stack_end_val);
let stack_overflow_trap = builder.create_block();
let stack = Stack {
stack: stack_slot,
ptr: stack_ptr,
start: stack_start,
end: stack_end,
overflow_trap: stack_overflow_trap,
};
let r0 = Variable::new(3);
let r1 = Variable::new(4);
builder.declare_var(r0, int);
builder.declare_var(r1, int);
let zero1 = builder.ins().iconst(int, 0);
builder.def_var(r0, zero1);
let zero2 = builder.ins().iconst(int, 0);
builder.def_var(r1, zero2);
let mut jump_table_data = JumpTableData::new();
let mut blocks = Vec::new();
for _ in ast {
let block = builder.create_block();
jump_table_data.push_entry(block);
blocks.push(block);
}
let jump_table = builder.create_jump_table(jump_table_data);
Self::connect_end(&mut builder, blocks.first().copied());
builder.switch_to_block(stack_overflow_trap);
builder.ins().trap(TrapCode::StackOverflow);
let unreach_trap_block = builder.create_block();
builder.switch_to_block(unreach_trap_block);
builder.ins().trap(TrapCode::UnreachableCodeReached);
if !blocks.is_empty() {
for (node, block_and_next) in ast
.iter()
.zip(blocks.iter().zip_longest(blocks[1..].iter()))
{
let (block, next) = match block_and_next {
EitherOrBoth::Left(l) => (*l, None),
EitherOrBoth::Both(l, r) => (*l, Some(*r)),
EitherOrBoth::Right(_) => unreachable!(),
};
builder.switch_to_block(block);
Self::translate_instruction(
node,
int,
&stack,
jump_table,
unreach_trap_block,
blocks.len() as i64,
next,
&mut builder,
put_val_func,
put_char_func,
r0,
r1,
);
}
}
builder.seal_all_blocks();
let id = self
.module
.declare_function("main", Linkage::Export, &self.ctx.func.signature)?;
self.module
.define_function(id, &mut self.ctx, &mut codegen::binemit::NullTrapSink {})?;
self.module.clear_context(&mut self.ctx);
self.module.finalize_definitions();
let ptr = self.module.get_finalized_function(id);
Ok(unsafe { std::mem::transmute::<_, fn()>(ptr) })
}
pub fn make_put_value(&mut self) -> JitResult<FuncId> {
let int = self.module.target_config().pointer_type();
self.ctx.func.signature.params.push(AbiParam::new(int));
let put_value =
self.module
.declare_function("put_value", Linkage::Import, &self.ctx.func.signature)?;
self.module.clear_context(&mut self.ctx);
Ok(put_value)
}
pub fn make_put_char(&mut self) -> JitResult<FuncId> {
let int = self.module.target_config().pointer_type();
self.ctx.func.signature.params.push(AbiParam::new(int));
let put_char =
self.module
.declare_function("put_char", Linkage::Import, &self.ctx.func.signature)?;
self.module.clear_context(&mut self.ctx);
Ok(put_char)
}
fn translate_instruction(
ins: &Instruction,
int: Type,
stack: &Stack,
jump_table: JumpTable,
unreach_trap: Block,
max_lines: i64,
next_block: Option<Block>,
builder: &mut FunctionBuilder,
put_val_func: FuncRef,
put_char_func: FuncRef,
r0: Variable,
r1: Variable,
) {
let Instruction {
instruction: kind,
register: reg,
line: _line,
} = ins;
let active_reg = match reg {
Register::Register0 => r0,
Register::Register1 => r1,
};
let inactive_reg = match reg {
Register::Register0 => r1,
Register::Register1 => r0,
};
match kind {
InsType::Store(syl) => {
let store_val = builder.ins().iconst(int, *syl as i64);
builder.def_var(active_reg, store_val);
Self::connect_end(builder, next_block);
}
InsType::Negate => {
let reg_val = builder.use_var(active_reg);
let neg = builder.ins().ineg(reg_val);
builder.def_var(active_reg, neg);
Self::connect_end(builder, next_block);
}
InsType::Multiply => {
let active_val = builder.use_var(active_reg);
let inactive_val = builder.use_var(inactive_reg);
let mult = builder.ins().imul(active_val, inactive_val);
builder.def_var(active_reg, mult);
Self::connect_end(builder, next_block);
}
InsType::Add => {
let active_val = builder.use_var(active_reg);
let inactive_val = builder.use_var(inactive_reg);
let add = builder.ins().iadd(active_val, inactive_val);
builder.def_var(active_reg, add);
Self::connect_end(builder, next_block);
}
InsType::Goto => {
Self::translate_goto(
int,
active_reg,
builder,
unreach_trap,
jump_table,
max_lines,
);
}
InsType::ConditionalGoto(syl) => {
let syl_val = builder.ins().iconst(int, *syl as i64);
let reg_val = builder.use_var(active_reg);
let cond_val = builder
.ins()
.icmp(IntCC::SignedGreaterThan, reg_val, syl_val);
let then_block = builder.create_block();
let merge_block = builder.create_block();
builder.ins().brnz(cond_val, then_block, &[]);
builder.ins().jump(merge_block, &[]);
builder.switch_to_block(then_block);
Self::translate_goto(
int,
inactive_reg,
builder,
unreach_trap,
jump_table,
max_lines,
);
builder.switch_to_block(merge_block);
Self::connect_end(builder, next_block);
}
InsType::Push => {
Self::translate_push(int, active_reg, builder, stack);
Self::connect_end(builder, next_block);
}
InsType::Pop => {
Self::translate_pop(int, active_reg, builder, stack);
Self::connect_end(builder, next_block);
}
InsType::ConditionalPush {
prev_syllables,
cur_syllables,
} => {
let active_val = builder.use_var(active_reg);
let inactive_val = builder.use_var(inactive_reg);
let cond_val = builder
.ins()
.icmp(IntCC::SignedLessThan, active_val, inactive_val);
let then_block = builder.create_block();
let else_block = builder.create_block();
let merge_block = builder.create_block();
builder.ins().brz(cond_val, else_block, &[]);
builder.ins().jump(then_block, &[]);
builder.switch_to_block(else_block);
let cur_val = builder.ins().iconst(int, *cur_syllables as i64);
Self::translate_push_val(int, cur_val, builder, stack);
builder.ins().jump(merge_block, &[]);
builder.switch_to_block(then_block);
let prev_val = builder.ins().iconst(int, *prev_syllables as i64);
Self::translate_push_val(int, prev_val, builder, stack);
builder.ins().jump(merge_block, &[]);
Self::connect_end(builder, next_block);
}
InsType::PrintValue => {
let reg_val = builder.use_var(active_reg);
builder.ins().call(put_val_func, &[reg_val]);
Self::connect_end(builder, next_block);
}
InsType::PrintChar => {
let reg_val = builder.use_var(active_reg);
builder.ins().call(put_char_func, &[reg_val]);
Self::connect_end(builder, next_block);
}
InsType::Noop => Self::connect_end(builder, next_block),
}
}
fn translate_goto(
int: Type,
reg: Variable,
builder: &mut FunctionBuilder,
unreach_trap: Block,
jump_table: JumpTable,
max_lines: i64,
) {
let index_val = builder.use_var(reg);
let abs_block = builder.create_block();
builder.append_block_param(abs_block, int);
let merge_block = builder.create_block();
builder.append_block_param(merge_block, int);
let cond = builder.ins().icmp_imm(IntCC::SignedLessThan, index_val, 0);
builder.ins().brnz(cond, abs_block, &[index_val]);
builder.ins().jump(merge_block, &[index_val]);
builder.switch_to_block(abs_block);
let abs_param = builder.block_params(abs_block)[0];
let abs_index_val = builder.ins().ineg(abs_param);
builder.ins().jump(merge_block, &[abs_index_val]);
builder.switch_to_block(merge_block);
let abs_index_val = builder.block_params(merge_block)[0];
let mod_index_val = builder.ins().srem_imm(abs_index_val, max_lines);
builder
.ins()
.br_table(mod_index_val, unreach_trap, jump_table);
}
fn translate_pop(int: Type, reg: Variable, builder: &mut FunctionBuilder, stack: &Stack) {
let top_val = builder.use_var(stack.ptr);
let stack_start_val = builder.use_var(stack.start);
let comp = builder
.ins()
.icmp(IntCC::SignedLessThanOrEqual, top_val, stack_start_val);
let then_block = builder.create_block();
let merge_block = builder.create_block();
builder.ins().brnz(comp, merge_block, &[]);
builder.ins().jump(then_block, &[]);
builder.switch_to_block(then_block);
let ptr_size = builder.ins().iconst(int, int.bytes() as i64);
let dec = builder.ins().isub(top_val, ptr_size);
builder.def_var(stack.ptr, dec);
let top_val = builder.use_var(stack.ptr);
let loaded_val = builder.ins().load(int, MemFlags::new(), top_val, 0);
builder.def_var(reg, loaded_val);
builder.ins().jump(merge_block, &[]);
builder.switch_to_block(merge_block);
}
fn translate_push_val(int: Type, value: Value, builder: &mut FunctionBuilder, stack: &Stack) {
let merge_block = builder.create_block();
let ptr_val = builder.use_var(stack.ptr);
builder.ins().store(MemFlags::new(), value, ptr_val, 0);
let end_val = builder.use_var(stack.end);
builder.ins().br_icmp(
IntCC::SignedGreaterThan,
ptr_val,
end_val,
stack.overflow_trap,
&[],
);
builder.ins().jump(merge_block, &[]);
builder.switch_to_block(merge_block);
let size = builder.ins().iconst(int, int.bytes() as i64);
let inc = builder.ins().iadd(ptr_val, size);
builder.def_var(stack.ptr, inc);
}
fn translate_push(int: Type, reg: Variable, builder: &mut FunctionBuilder, stack: &Stack) {
let store_val = builder.use_var(reg);
Self::translate_push_val(int, store_val, builder, stack);
}
fn connect_end(builder: &mut FunctionBuilder, next_block: Option<Block>) {
if let Some(next) = next_block {
builder.ins().jump(next, &[]);
} else {
builder.ins().return_(&[]);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser;
#[test]
fn basic_goto() {
let source = include_str!("../poems/goto-test.eso");
let tokens = parser::parse(source);
let mut jit = JIT::default();
jit.compile(&tokens).unwrap()();
}
#[test]
fn factorial() {
let source = include_str!("../poems/original-factorial.eso");
let tokens = parser::parse(source);
let mut jit = JIT::default();
jit.compile(&tokens).unwrap()();
}
#[test]
fn stack() {
let source = include_str!("../poems/stack-test.eso");
let tokens = parser::parse(source);
let mut jit = JIT::default();
jit.compile(&tokens).unwrap()();
}
#[test]
fn cond_goto() {
let source = include_str!("../poems/cond-goto-test.eso");
let tokens = parser::parse(source);
let mut jit = JIT::default();
jit.compile(&tokens).unwrap()();
}
#[test]
fn math() {
let source = include_str!("../poems/math-test.eso");
let tokens = parser::parse(source);
let mut jit = JIT::default();
jit.compile(&tokens).unwrap()();
}
#[test]
fn empty() {
let tokens = parser::parse("");
let mut jit = JIT::default();
jit.compile(&tokens).unwrap()();
}
}