use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
use cranelift_codegen::ir::{
AbiParam, BlockArg, Function as ClifFunction, InstBuilder, Signature, UserFuncName, types,
};
use cranelift_codegen::ir::{Block as ClifBlock, Value as ClifValue};
use cranelift_codegen::isa::TargetIsa;
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use ir_lang::{BinOp, Block, Function, Inst, Terminator, Type, UnOp, Value};
use crate::error::JitError;
pub(crate) fn translate(func: &Function, isa: &dyn TargetIsa) -> Result<ClifFunction, JitError> {
let order = reachable_rpo(func);
let signature = signature(func, isa)?;
let mut clif = ClifFunction::with_name_signature(UserFuncName::user(0, 0), signature);
let mut context = FunctionBuilderContext::new();
let mut builder = FunctionBuilder::new(&mut clif, &mut context);
let mut blocks: Vec<Option<ClifBlock>> = vec![None; func.block_count()];
for &ir_block in &order {
blocks[ir_block.index()] = Some(builder.create_block());
}
let entry = clif_block(&blocks, func.entry())?;
builder.append_block_params_for_function_params(entry);
for &ir_block in &order {
if ir_block == func.entry() {
continue;
}
let block = clif_block(&blocks, ir_block)?;
for ¶m in func.block_params(ir_block) {
let ty = abi_type(value_type(func, param)?)?;
builder.append_block_param(block, ty);
}
}
let mut values: Vec<Option<ClifValue>> = vec![None; func.value_count()];
for &ir_block in &order {
let block = clif_block(&blocks, ir_block)?;
let params = builder.block_params(block).to_vec();
for (&ir_value, &clif_value) in func.block_params(ir_block).iter().zip(params.iter()) {
values[ir_value.index()] = Some(clif_value);
}
}
for &ir_block in &order {
let block = clif_block(&blocks, ir_block)?;
builder.switch_to_block(block);
for &ir_value in func.insts(ir_block) {
if let Some(inst) = func.inst(ir_value) {
let produced = emit_inst(&mut builder, func, inst, &values)?;
values[ir_value.index()] = Some(produced);
}
}
let term = func
.terminator(ir_block)
.ok_or_else(|| internal("a reachable block has no terminator"))?;
emit_terminator(&mut builder, term, &blocks, &values)?;
}
builder.seal_all_blocks();
builder.finalize();
Ok(clif)
}
fn signature(func: &Function, isa: &dyn TargetIsa) -> Result<Signature, JitError> {
let mut sig = Signature::new(isa.default_call_conv());
for &ty in func.params() {
sig.params.push(AbiParam::new(abi_type(ty)?));
}
if func.ret() != Type::Unit {
sig.returns.push(AbiParam::new(abi_type(func.ret())?));
}
Ok(sig)
}
fn abi_type(ty: Type) -> Result<types::Type, JitError> {
match ty {
Type::Int => Ok(types::I64),
Type::Float => Ok(types::F64),
Type::Bool => Ok(types::I8),
Type::Unit => Err(JitError::Unsupported(
"a parameter or block parameter has type unit, which has no machine representation",
)),
}
}
fn emit_inst(
builder: &mut FunctionBuilder,
func: &Function,
inst: &Inst,
values: &[Option<ClifValue>],
) -> Result<ClifValue, JitError> {
match inst {
Inst::Iconst(value) => Ok(builder.ins().iconst(types::I64, *value)),
Inst::Fconst(value) => Ok(builder.ins().f64const(*value)),
Inst::Bconst(value) => Ok(builder.ins().iconst(types::I8, i64::from(*value))),
Inst::Bin(op, lhs, rhs) => {
let operand_ty = value_type(func, *lhs)?;
let l = clif_value(values, *lhs)?;
let r = clif_value(values, *rhs)?;
Ok(emit_bin(builder, *op, l, r, operand_ty))
}
Inst::Un(op, operand) => {
let operand_ty = value_type(func, *operand)?;
let x = clif_value(values, *operand)?;
Ok(emit_un(builder, *op, x, operand_ty))
}
}
}
fn emit_bin(
builder: &mut FunctionBuilder,
op: BinOp,
l: ClifValue,
r: ClifValue,
operand_ty: Type,
) -> ClifValue {
let is_float = operand_ty == Type::Float;
match op {
BinOp::Add if is_float => builder.ins().fadd(l, r),
BinOp::Add => builder.ins().iadd(l, r),
BinOp::Sub if is_float => builder.ins().fsub(l, r),
BinOp::Sub => builder.ins().isub(l, r),
BinOp::Mul if is_float => builder.ins().fmul(l, r),
BinOp::Mul => builder.ins().imul(l, r),
BinOp::Div if is_float => builder.ins().fdiv(l, r),
BinOp::Div => builder.ins().sdiv(l, r),
BinOp::Eq if is_float => builder.ins().fcmp(FloatCC::Equal, l, r),
BinOp::Eq => builder.ins().icmp(IntCC::Equal, l, r),
BinOp::Ne if is_float => builder.ins().fcmp(FloatCC::NotEqual, l, r),
BinOp::Ne => builder.ins().icmp(IntCC::NotEqual, l, r),
BinOp::Lt if is_float => builder.ins().fcmp(FloatCC::LessThan, l, r),
BinOp::Lt => builder.ins().icmp(IntCC::SignedLessThan, l, r),
BinOp::Le if is_float => builder.ins().fcmp(FloatCC::LessThanOrEqual, l, r),
BinOp::Le => builder.ins().icmp(IntCC::SignedLessThanOrEqual, l, r),
BinOp::Gt if is_float => builder.ins().fcmp(FloatCC::GreaterThan, l, r),
BinOp::Gt => builder.ins().icmp(IntCC::SignedGreaterThan, l, r),
BinOp::Ge if is_float => builder.ins().fcmp(FloatCC::GreaterThanOrEqual, l, r),
BinOp::Ge => builder.ins().icmp(IntCC::SignedGreaterThanOrEqual, l, r),
BinOp::And => builder.ins().band(l, r),
BinOp::Or => builder.ins().bor(l, r),
}
}
fn emit_un(builder: &mut FunctionBuilder, op: UnOp, x: ClifValue, operand_ty: Type) -> ClifValue {
match op {
UnOp::Neg if operand_ty == Type::Float => builder.ins().fneg(x),
UnOp::Neg => builder.ins().ineg(x),
UnOp::Not => builder.ins().bxor_imm(x, 1),
}
}
fn emit_terminator(
builder: &mut FunctionBuilder,
term: &Terminator,
blocks: &[Option<ClifBlock>],
values: &[Option<ClifValue>],
) -> Result<(), JitError> {
match term {
Terminator::Return(None) => {
builder.ins().return_(&[]);
}
Terminator::Return(Some(value)) => {
let v = clif_value(values, *value)?;
builder.ins().return_(&[v]);
}
Terminator::Jump(target, args) => {
let block = clif_block(blocks, *target)?;
let block_args = block_args(values, args)?;
builder.ins().jump(block, &block_args);
}
Terminator::Branch {
cond,
then_block,
then_args,
else_block,
else_args,
} => {
let condition = clif_value(values, *cond)?;
let then_b = clif_block(blocks, *then_block)?;
let else_b = clif_block(blocks, *else_block)?;
let then_a = block_args(values, then_args)?;
let else_a = block_args(values, else_args)?;
builder
.ins()
.brif(condition, then_b, &then_a, else_b, &else_a);
}
}
Ok(())
}
fn block_args(values: &[Option<ClifValue>], args: &[Value]) -> Result<Vec<BlockArg>, JitError> {
args.iter()
.map(|&arg| clif_value(values, arg).map(BlockArg::from))
.collect()
}
fn clif_value(values: &[Option<ClifValue>], value: Value) -> Result<ClifValue, JitError> {
values
.get(value.index())
.copied()
.flatten()
.ok_or_else(|| internal("a value was referenced before it was defined"))
}
fn clif_block(blocks: &[Option<ClifBlock>], block: Block) -> Result<ClifBlock, JitError> {
blocks
.get(block.index())
.copied()
.flatten()
.ok_or_else(|| internal("a reachable terminator named an unreachable block"))
}
fn value_type(func: &Function, value: Value) -> Result<Type, JitError> {
func.value_type(value)
.ok_or_else(|| internal("a referenced value has no recorded type"))
}
fn internal(what: &str) -> JitError {
JitError::Codegen(format!("internal translator invariant violated: {what}"))
}
fn reachable_rpo(func: &Function) -> Vec<Block> {
let n = func.block_count();
let entry = func.entry();
let mut order = Vec::new();
if entry.index() >= n {
return order;
}
let mut visited = vec![false; n];
let mut stack: Vec<(Block, Vec<Block>, usize)> = Vec::new();
visited[entry.index()] = true;
stack.push((entry, succs_of(func, entry), 0));
while !stack.is_empty() {
let top = stack.len() - 1;
let (block, next) = (stack[top].0, stack[top].2);
if next < stack[top].1.len() {
let succ = stack[top].1[next];
stack[top].2 += 1;
if succ.index() < n && !visited[succ.index()] {
visited[succ.index()] = true;
let succs = succs_of(func, succ);
stack.push((succ, succs, 0));
}
} else {
order.push(block);
let _ = stack.pop();
}
}
order.reverse();
order
}
fn succs_of(func: &Function, block: Block) -> Vec<Block> {
let mut out = Vec::new();
if let Some(term) = func.terminator(block) {
term.each_successor(|s| out.push(s));
}
out
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "tests build known-valid functions and assert on specific outcomes"
)]
mod tests {
use super::translate;
use crate::JitError;
use cranelift_codegen::settings::{self, Configurable};
use cranelift_codegen::verifier::verify_function;
use ir_lang::{BinOp, Builder, Type};
fn host_isa() -> cranelift_codegen::isa::OwnedTargetIsa {
let mut fb = settings::builder();
fb.set("opt_level", "speed").unwrap();
fb.set("is_pic", "false").unwrap();
let flags = settings::Flags::new(fb);
cranelift_native::builder()
.expect("host is a supported target")
.finish(flags)
.expect("flags are valid")
}
#[test]
fn test_straight_line_translation_verifies() {
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 isa = host_isa();
let clif = translate(&b.finish(), &*isa).expect("translates");
assert_eq!(clif.signature.params.len(), 1);
assert_eq!(clif.signature.returns.len(), 1);
verify_function(&clif, &*isa).expect("the translated function is well-formed CLIF");
}
#[test]
fn test_diamond_with_block_parameter_verifies() {
let mut b = Builder::new("max", &[Type::Int, Type::Int], Type::Int);
let a = b.block_params(b.entry())[0];
let c = b.block_params(b.entry())[1];
let join = b.create_block(&[Type::Int]);
let then_blk = b.create_block(&[]);
let else_blk = b.create_block(&[]);
let cond = b.bin(BinOp::Lt, a, c);
b.branch(cond, then_blk, &[], else_blk, &[]);
b.switch_to(then_blk);
b.jump(join, &[c]);
b.switch_to(else_blk);
b.jump(join, &[a]);
b.switch_to(join);
let r = b.block_params(join)[0];
b.ret(Some(r));
let isa = host_isa();
let clif = translate(&b.finish(), &*isa).expect("translates");
verify_function(&clif, &*isa).expect("the translated function is well-formed CLIF");
}
#[test]
fn test_unit_parameter_is_unsupported() {
let mut b = Builder::new("f", &[Type::Unit], Type::Unit);
b.ret(None);
let isa = host_isa();
assert!(matches!(
translate(&b.finish(), &*isa),
Err(JitError::Unsupported(_))
));
}
}