use crate::Error::{InternalError, InvalidBlockAddress};
use crate::Result;
use crate::instruction::{ThrowContext, emit_bci};
use crate::jit_value;
use crate::operand_stack::OperandStack;
use crate::runtime_helpers::RuntimeHelpers;
use cranelift::codegen::ir::{Block, BlockArg, Value};
use cranelift::prelude::{FunctionBuilder, InstBuilder, MemFlags, types};
use ristretto_classfile::attributes::ExceptionTableEntry;
fn prepare_throw_target(
function_builder: &mut FunctionBuilder,
throw_context: &ThrowContext<'_>,
) -> Result<(Block, bool)> {
let pc_u16 = u16::try_from(throw_context.program_counter).map_err(|_| {
InternalError(format!(
"program_counter {} overflows u16",
throw_context.program_counter
))
})?;
let covers = throw_context
.exception_table
.iter()
.any(|entry| entry.range_pc.contains(&pc_u16));
if !covers {
return Ok((throw_context.exception_block, false));
}
Ok((function_builder.create_block(), true))
}
fn populate_dispatch_block(
function_builder: &mut FunctionBuilder,
helpers: &RuntimeHelpers,
context_pointer: Value,
throw_context: &ThrowContext<'_>,
dispatch_block: Block,
) -> Result<()> {
let pc_u16 = u16::try_from(throw_context.program_counter).map_err(|_| {
InternalError(format!(
"program_counter {} overflows u16",
throw_context.program_counter
))
})?;
let covering: Vec<&ExceptionTableEntry> = throw_context
.exception_table
.iter()
.filter(|entry| entry.range_pc.contains(&pc_u16))
.collect();
function_builder.switch_to_block(dispatch_block);
for entry in covering {
let handler_pc = usize::from(entry.handler_pc);
let handler_block = *throw_context
.blocks
.get(&handler_pc)
.ok_or(InvalidBlockAddress(handler_pc))?;
if entry.catch_type == 0 {
let call = function_builder
.ins()
.call(helpers.take_pending_exception, &[context_pointer]);
let Some(&exception_ref) = function_builder.inst_results(call).first() else {
return Err(InternalError(
"take_pending_exception returned no value".to_string(),
));
};
function_builder
.ins()
.jump(handler_block, &[BlockArg::Value(exception_ref)]);
return Ok(());
}
let catch_type = function_builder
.ins()
.iconst(types::I32, i64::from(entry.catch_type));
let call = function_builder
.ins()
.call(helpers.exception_matches, &[context_pointer, catch_type]);
let Some(&matches) = function_builder.inst_results(call).first() else {
return Err(InternalError(
"exception_matches returned no value".to_string(),
));
};
let take_block = function_builder.create_block();
let next_block = function_builder.create_block();
function_builder
.ins()
.brif(matches, take_block, &[], next_block, &[]);
function_builder.switch_to_block(take_block);
let call = function_builder
.ins()
.call(helpers.take_pending_exception, &[context_pointer]);
let Some(&exception_ref) = function_builder.inst_results(call).first() else {
return Err(InternalError(
"take_pending_exception returned no value".to_string(),
));
};
function_builder
.ins()
.jump(handler_block, &[BlockArg::Value(exception_ref)]);
function_builder.switch_to_block(next_block);
}
function_builder
.ins()
.jump(throw_context.exception_block, &[]);
Ok(())
}
pub(crate) fn emit_pending_exception_check(
function_builder: &mut FunctionBuilder,
stack: &mut OperandStack,
helpers: &RuntimeHelpers,
context_pointer: Value,
throw_context: &ThrowContext<'_>,
) -> Result<()> {
let call = function_builder
.ins()
.call(helpers.pending_exception, &[context_pointer]);
let Some(&pending) = function_builder.inst_results(call).first() else {
return Err(InternalError(
"pending_exception helper returned no value".to_string(),
));
};
let (throw_target, is_new) = prepare_throw_target(function_builder, throw_context)?;
let continue_block = function_builder.create_block();
for ty in stack.to_type_vec(function_builder) {
function_builder.append_block_param(continue_block, ty);
}
let stack_args = stack.as_block_arguments();
function_builder
.ins()
.brif(pending, throw_target, &[], continue_block, &stack_args);
if is_new {
populate_dispatch_block(
function_builder,
helpers,
context_pointer,
throw_context,
throw_target,
)?;
}
function_builder.switch_to_block(continue_block);
stack.reset(function_builder)?;
Ok(())
}
pub(crate) fn emit_null_check(
function_builder: &mut FunctionBuilder,
stack: &mut OperandStack,
helpers: &RuntimeHelpers,
context_pointer: Value,
throw_context: &ThrowContext<'_>,
value_to_check: Value,
) -> Result<()> {
let null_block = function_builder.create_block();
let continue_block = function_builder.create_block();
for ty in stack.to_type_vec(function_builder) {
function_builder.append_block_param(continue_block, ty);
}
let stack_args = stack.as_block_arguments();
function_builder
.ins()
.brif(value_to_check, continue_block, &stack_args, null_block, &[]);
function_builder.switch_to_block(null_block);
let bci_value = function_builder.ins().iconst(
types::I32,
i64::try_from(throw_context.program_counter).unwrap_or(0),
);
function_builder
.ins()
.call(helpers.throw_npe, &[context_pointer, bci_value]);
let (throw_target, is_new) = prepare_throw_target(function_builder, throw_context)?;
function_builder.ins().jump(throw_target, &[]);
if is_new {
populate_dispatch_block(
function_builder,
helpers,
context_pointer,
throw_context,
throw_target,
)?;
}
function_builder.switch_to_block(continue_block);
stack.reset(function_builder)?;
Ok(())
}
pub(crate) fn emit_exception_return(function_builder: &mut FunctionBuilder, return_pointer: Value) {
let none_discriminant = function_builder
.ins()
.iconst(types::I8, i64::from(jit_value::NONE));
function_builder
.ins()
.store(MemFlags::new(), none_discriminant, return_pointer, 0);
function_builder.ins().return_(&[]);
}
pub(crate) fn athrow(
function_builder: &mut FunctionBuilder,
stack: &mut OperandStack,
helpers: &RuntimeHelpers,
context_pointer: Value,
throw_context: &ThrowContext<'_>,
) -> Result<()> {
let throwable = stack.pop_object(function_builder)?;
let bci = emit_bci(function_builder, throw_context);
function_builder
.ins()
.call(helpers.athrow, &[context_pointer, bci, throwable]);
let (throw_target, is_new) = prepare_throw_target(function_builder, throw_context)?;
function_builder.ins().jump(throw_target, &[]);
if is_new {
populate_dispatch_block(
function_builder,
helpers,
context_pointer,
throw_context,
throw_target,
)?;
}
Ok(())
}