Skip to content
Merged
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
81 changes: 63 additions & 18 deletions crates/nera-codegen/src/func_translator/ffi_emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,67 @@ impl FuncTranslator {
Ok(builder.inst_results(call)[0])
}

/// All migrated runtime-helper and inline slow paths share this check.
pub(super) fn emit_checked_arithmetic_call(
&mut self,
module: &mut ObjectModule,
builder: &mut FunctionBuilder,
name: &str,
ctx_var: Variable,
operands: &[Value],
failure: Option<cranelift_codegen::ir::Block>,
) -> Result<Value, TranslateError> {
let value = self.emit_ffi_call(module, builder, name, ctx_var, operands)?;
let ctx = builder.use_var(ctx_var);
let status = builder.ins().load(types::I32, MemFlags::trusted(), ctx, CTX_OFFSET_STATUS);
let failed = builder.ins().icmp_imm(IntCC::Equal, status, STATUS_EXCEPTION as i64);
let next = builder.create_block();
match failure {
Some(target) => { builder.ins().brif(failed, target, &[], next, &[]); }
None => {
let leave = builder.create_block();
builder.ins().brif(failed, leave, &[], next, &[]);
builder.switch_to_block(leave);
let nil = builder.ins().iconst(types::I64, NIL_VALUE as i64);
self.emit_stack_pop_and_return(module, builder, ctx_var, nil)?;
}
}
builder.switch_to_block(next);
Ok(value)
}

pub(super) fn emit_inline_not(
&mut self,
module: &mut ObjectModule,
builder: &mut FunctionBuilder,
ctx_var: Variable,
operand: Value,
failure: Option<cranelift_codegen::ir::Block>,
) -> Result<Value, TranslateError> {
let true_atom = self.load_true_atom(module, builder)?;
let false_atom = self.load_false_atom(module, builder)?;
let is_true = builder.ins().icmp(IntCC::Equal, operand, true_atom);
let is_false = builder.ins().icmp(IntCC::Equal, operand, false_atom);
let is_bool = builder.ins().bor(is_true, is_false);
let fast = builder.create_block();
let slow = builder.create_block();
builder.set_cold_block(slow);
let merge = builder.create_block();
builder.append_block_param(merge, types::I64);
builder.ins().brif(is_bool, fast, &[], slow, &[]);
builder.switch_to_block(fast);
builder.seal_block(fast);
let flipped = builder.ins().select(is_true, false_atom, true_atom);
builder.ins().jump(merge, &[BlockArg::Value(flipped)]);
builder.switch_to_block(slow);
builder.seal_block(slow);
let value = self.emit_checked_arithmetic_call(module, builder, "nera_rt_bool_not", ctx_var, &[operand], failure)?;
builder.ins().jump(merge, &[BlockArg::Value(value)]);
builder.switch_to_block(merge);
builder.seal_block(merge);
Ok(builder.block_params(merge)[0])
}

/// Emit a call to a void FFI function (no return value used).
pub(super) fn emit_ffi_call_void(
&mut self,
Expand Down Expand Up @@ -176,7 +237,7 @@ impl FuncTranslator {
lhs: Value,
rhs: Value,
operation: InlineArithmetic,
check_exception_in_slow_path: bool,
failure: Option<cranelift_codegen::ir::Block>,
) -> Result<Value, TranslateError> {


Expand Down Expand Up @@ -244,23 +305,7 @@ impl FuncTranslator {
// Slow path: FFI fallback
builder.switch_to_block(slow_block);
builder.seal_block(slow_block);
let slow_ret = self.emit_ffi_call(module, builder, ffi_name, ctx_var, &[lhs, rhs])?;
if check_exception_in_slow_path {
// Check for exception here so the fast path (which never raises) avoids the cost.
let ctx_val = builder.use_var(ctx_var);
let status = builder.ins().load(types::I32, MemFlags::trusted(), ctx_val, CTX_OFFSET_STATUS);
let exc_const = builder.ins().iconst(types::I32, STATUS_EXCEPTION as i64);
let is_exc = builder.ins().icmp(IntCC::Equal, status, exc_const);
let exc_block = builder.create_block();
let slow_ok_block = builder.create_block();
builder.ins().brif(is_exc, exc_block, &[], slow_ok_block, &[]);

builder.switch_to_block(exc_block);
let nil = builder.ins().iconst(types::I64, NIL_VALUE as i64);
self.emit_stack_pop_and_return(module, builder, ctx_var, nil)?;

builder.switch_to_block(slow_ok_block);
}
let slow_ret = self.emit_checked_arithmetic_call(module, builder, ffi_name, ctx_var, &[lhs, rhs], failure)?;
builder.ins().jump(merge_block, &[BlockArg::Value(slow_ret)]);

// Merge
Expand Down
109 changes: 42 additions & 67 deletions crates/nera-codegen/src/func_translator/instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use cranelift_frontend::{FunctionBuilder, Variable};
use cranelift_module::Module;
use cranelift_object::ObjectModule;

use nera_ir::ir::{CallTarget, Instruction, Literal, VarId};
use nera_ir::ir::{ArithmeticOp, FailureDestination, CallTarget, Instruction, Literal, VarId};
use crate::term_layout::*;
use super::ffi_emit::SegmentSizeKind;
use super::{ModuleContext, declare_runtime_func,
Expand Down Expand Up @@ -42,6 +42,39 @@ impl FuncTranslator {
};
let is_try_protected = protected_result.is_some_and(|v| self.current_try_protected_values.contains(v));
match inst {
Instruction::Arithmetic(site) => {
let failure = match site.failure {
FailureDestination::Propagate => None,
FailureDestination::Block(target) => Some(*self.current_blocks.get(&target)
.ok_or_else(|| TranslateError::Module("missing arithmetic failure destination".into()))?),
};
let operands: Vec<_> = site.operation.operands().iter()
.map(|v| builder.use_var(var_map[v])).collect();
let value = match site.operation {
ArithmeticOp::Add(..) | ArithmeticOp::Subtract(..) | ArithmeticOp::Multiply(..) => {
use super::ffi_emit::InlineArithmetic;
let operation = match site.operation {
ArithmeticOp::Add(..) => InlineArithmetic::Add,
ArithmeticOp::Subtract(..) => InlineArithmetic::Subtract,
_ => InlineArithmetic::Multiply,
};
self.emit_inline_arith(module, builder, ctx_var, operands[0], operands[1], operation, failure)?
}
ArithmeticOp::Not(_) => self.emit_inline_not(module, builder, ctx_var, operands[0], failure)?,
_ => {
let name = match site.operation {
ArithmeticOp::Divide(..) => "nera_rt_fdiv",
ArithmeticOp::Negate(_) => "nera_rt_negate",
ArithmeticOp::BitNot(_) => "nera_rt_bnot",
_ => unreachable!(),
};
self.emit_checked_arithmetic_call(module, builder, name, ctx_var, &operands, failure)?
}
};
if let Some(var) = site.result.and_then(|v| var_map.get(&v)) {
builder.def_var(*var, value);
}
}
Instruction::Const(result, lit) => {
let val = match lit {
Literal::Integer(n) => {
Expand Down Expand Up @@ -91,16 +124,9 @@ impl FuncTranslator {
let check_exc_in_slow = !is_try_protected;

let val = match op {
// --- Inline small-int arithmetic fast paths ---
nera_syntax::ast::BinOp::Add | nera_syntax::ast::BinOp::Sub | nera_syntax::ast::BinOp::Mul => {
use super::ffi_emit::InlineArithmetic;
let operation = match op {
nera_syntax::ast::BinOp::Add => InlineArithmetic::Add,
nera_syntax::ast::BinOp::Sub => InlineArithmetic::Subtract,
_ => InlineArithmetic::Multiply,
};
self.emit_inline_arith(module, builder, ctx_var, lhs_val, rhs_val,
operation, check_exc_in_slow)?
nera_syntax::ast::BinOp::Add | nera_syntax::ast::BinOp::Sub
| nera_syntax::ast::BinOp::Mul | nera_syntax::ast::BinOp::Div => {
return Err(TranslateError::Module("arithmetic requires an explicit operation site".into()));
}
nera_syntax::ast::BinOp::IntDiv => {
self.emit_inline_div_rem(module, builder, ctx_var, lhs_val, rhs_val, "nera_rt_int_div", false, check_exc_in_slow)?
Expand Down Expand Up @@ -230,10 +256,6 @@ impl FuncTranslator {
builder.seal_block(merge_block);
builder.block_params(merge_block)[0]
}
nera_syntax::ast::BinOp::Div => {
needs_exception_check = true;
self.emit_ffi_call(module, builder, "nera_rt_fdiv", ctx_var, &[lhs_val, rhs_val])?
}
nera_syntax::ast::BinOp::ListAppend => {
needs_exception_check = true;
self.emit_ffi_call(module, builder, "nera_rt_list_append", ctx_var, &[lhs_val, rhs_val])?
Expand All @@ -256,62 +278,15 @@ impl FuncTranslator {
}

Instruction::UnaryOp(result, op, operand) => {
if !matches!(op, nera_syntax::ast::UnaryOp::Pos) {
return Err(TranslateError::Module("arithmetic requires an explicit operation site".into()));
}
let operand_val = builder.use_var(var_map[operand]);

let needs_exception_check = true;

let val = match op {
nera_syntax::ast::UnaryOp::Pos => {
self.emit_ffi_call(module, builder, "nera_rt_unary_plus", ctx_var, &[operand_val])?
}
nera_syntax::ast::UnaryOp::Neg => {
self.emit_ffi_call(module, builder, "nera_rt_negate", ctx_var, &[operand_val])?
}
nera_syntax::ast::UnaryOp::Not => {
// Logical not: true -> false, false -> true.
// Raises badarg if operand is not a boolean atom.
let true_atom = self.load_true_atom(module, builder)?;
let false_atom = self.load_false_atom(module, builder)?;
let is_true = builder.ins().icmp(IntCC::Equal, operand_val, true_atom);
let is_false = builder.ins().icmp(IntCC::Equal, operand_val, false_atom);
let is_bool = builder.ins().bor(is_true, is_false);

let fast_block = builder.create_block();
let slow_block = builder.create_block();
builder.set_cold_block(slow_block);
let merge_block = builder.create_block();
builder.append_block_param(merge_block, types::I64);

builder.ins().brif(is_bool, fast_block, &[], slow_block, &[]);

// Fast path: flip true<->false
builder.switch_to_block(fast_block);
builder.seal_block(fast_block);
let fast_result = builder.ins().select(is_true, false_atom, true_atom);
builder.ins().jump(merge_block, &[BlockArg::Value(fast_result)]);

// Slow path: FFI call that raises badarg
builder.switch_to_block(slow_block);
builder.seal_block(slow_block);
let slow_ret = self.emit_ffi_call(module, builder, "nera_rt_bool_not", ctx_var, &[operand_val])?;
builder.ins().jump(merge_block, &[BlockArg::Value(slow_ret)]);

builder.switch_to_block(merge_block);
builder.seal_block(merge_block);
builder.block_params(merge_block)[0]
}
nera_syntax::ast::UnaryOp::Bnot => {
self.emit_ffi_call(module, builder, "nera_rt_bnot", ctx_var, &[operand_val])?
}
};

let val = self.emit_ffi_call(module, builder, "nera_rt_unary_plus", ctx_var, &[operand_val])?;
if let Some(var) = var_map.get(result) {
builder.def_var(*var, val);
}

// Check for exception after the unary operation
// (skip if inside a try block — CheckException handles it)
if needs_exception_check && !is_try_protected {
if !is_try_protected {
self.emit_ffi_exception_check(module, builder, ctx_var)?;
}
}
Expand Down
5 changes: 4 additions & 1 deletion crates/nera-codegen/src/func_translator/liveness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ pub(super) fn compute_suspend_points(

let mut successors: HashMap<BlockId, Vec<BlockId>> = HashMap::new();
for block in &ir_func.blocks {
let succs = terminator_successors(&block.terminator);
let succs = block.successors();
successors.insert(block.id, succs);
}

Expand Down Expand Up @@ -195,6 +195,7 @@ pub(super) fn compute_suspend_points(
/// Collect VarIds defined (written) by an instruction.
pub(super) fn collect_instruction_defs(inst: &Instruction, defs: &mut HashSet<VarId>) {
match inst {
Instruction::Arithmetic(site) => defs.extend(site.result),
Instruction::Const(v, _)
| Instruction::BinOp(v, _, _, _)
| Instruction::UnaryOp(v, _, _)
Expand Down Expand Up @@ -286,6 +287,7 @@ pub(super) fn collect_instruction_defs(inst: &Instruction, defs: &mut HashSet<Va
/// Collect VarIds used (read) by an instruction.
pub(super) fn collect_instruction_uses(inst: &Instruction, uses: &mut HashSet<VarId>) {
match inst {
Instruction::Arithmetic(site) => uses.extend(site.operation.operands()),
Instruction::Const(_, _) => {}
Instruction::BinOp(_, _, a, b) => { uses.insert(*a); uses.insert(*b); }
Instruction::UnaryOp(_, _, a) => { uses.insert(*a); }
Expand Down Expand Up @@ -414,6 +416,7 @@ pub(super) fn collect_terminator_uses(term: &Terminator, uses: &mut HashSet<VarI

/// Get successor BlockIds from a terminator.
/// Delegates to `Terminator::successors()` in the IR crate.
#[cfg(test)]
pub(super) fn terminator_successors(term: &Terminator) -> Vec<BlockId> {
term.successors()
}
15 changes: 12 additions & 3 deletions crates/nera-codegen/src/func_translator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,10 @@ pub struct FuncTranslator {
pub(super) current_ir_block_id: Option<BlockId>,
/// Per-function: the instruction index within the current block.
pub(super) current_inst_idx: usize,
/// Per-function: set of block IDs that contain CheckException.
/// BinOp/UnaryOp FFI calls in these blocks should not emit exception
/// checks because try/catch will handle them.
pub(super) current_blocks: HashMap<BlockId, cranelift_codegen::ir::Block>,
/// Per-function: protected result registers for calls and unmigrated
/// operations whose existing IR checks handle exceptions. Arithmetic sites
/// never consult this set; their destinations are instruction-local.
pub(super) current_try_protected_values: std::collections::HashSet<VarId>,
/// Per-function: Cranelift Variable that holds the reduction counter.
/// Kept in a CPU register across self-tail-call loop iterations;
Expand Down Expand Up @@ -177,6 +178,7 @@ impl FuncTranslator {
current_call_result_vars: Vec::new(),
current_ir_block_id: None,
current_inst_idx: 0,
current_blocks: HashMap::new(),
current_try_protected_values: std::collections::HashSet::new(),
reds_var: None,
last_branch_predicate: None,
Expand Down Expand Up @@ -293,10 +295,17 @@ impl FuncTranslator {
var_map.entry(var_id).or_insert_with(|| builder.declare_var(types::I64));
};

self.current_blocks = block_map.clone();

// Pre-declare all variables used in the function
for ir_block in &ir_func.blocks {
for inst in &ir_block.instructions {
match inst {
Instruction::Arithmetic(site) => {
if let Some(v) = site.result {
declare_var(v, &mut var_map, &mut builder);
}
}
Instruction::Const(v, _)
| Instruction::BinOp(v, _, _, _)
| Instruction::UnaryOp(v, _, _)
Expand Down
2 changes: 2 additions & 0 deletions crates/nera-ir/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ impl fmt::Display for BasicBlock {
impl fmt::Display for Instruction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Instruction::Arithmetic(site) => write!(f, "site{} {:?} = {:?} [{:?}, failure {:?}]",
site.id.0, site.result, site.operation, site.context, site.failure),
Instruction::Const(v, lit) => write!(f, "v{} = const {}", v.0, lit),
Instruction::BinOp(v, op, a, b) => {
write!(f, "v{} = {:?}(v{}, v{})", v.0, op, a.0, b.0)
Expand Down
Loading
Loading