use cranelift::codegen::ir::{AtomicRmwOp, FuncRef};
use cranelift::prelude::*;
pub const V2_REFCOUNT_OFFSET: i32 = 0;
pub struct V2RefcountEmitter<'a, 'b> {
builder: &'a mut FunctionBuilder<'b>,
}
impl<'a, 'b> V2RefcountEmitter<'a, 'b> {
pub fn new(builder: &'a mut FunctionBuilder<'b>) -> Self {
Self { builder }
}
pub fn emit_retain(&mut self, ptr: Value) {
let one = self.builder.ins().iconst(types::I32, 1);
self.builder
.ins()
.atomic_rmw(types::I32, MemFlags::new(), AtomicRmwOp::Add, ptr, one);
}
pub fn emit_release(&mut self, ptr: Value) -> Value {
let one = self.builder.ins().iconst(types::I32, 1);
self.builder
.ins()
.atomic_rmw(types::I32, MemFlags::new(), AtomicRmwOp::Sub, ptr, one)
}
pub fn emit_drop(&mut self, ptr: Value, free_func: FuncRef) {
let old = self.emit_release(ptr);
let one = self.builder.ins().iconst(types::I32, 1);
let was_last = self.builder.ins().icmp(IntCC::Equal, old, one);
let free_block = self.builder.create_block();
let cont_block = self.builder.create_block();
self.builder
.ins()
.brif(was_last, free_block, &[], cont_block, &[]);
self.builder.switch_to_block(free_block);
self.builder.seal_block(free_block);
self.builder.ins().fence();
self.builder.ins().call(free_func, &[ptr]);
self.builder.ins().jump(cont_block, &[]);
self.builder.switch_to_block(cont_block);
self.builder.seal_block(cont_block);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn jit_v2_dealloc(ptr: *mut u8) {
if ptr.is_null() {
return;
}
unsafe {
let kind_offset = 4usize;
let _kind = *(ptr.add(kind_offset) as *const u16);
let _ = _kind;
}
}
#[cfg(test)]
mod tests {
use super::*;
use cranelift::codegen::ir::Function;
use cranelift::codegen::isa::CallConv;
fn build_test_ir<F>(name: &str, f: F) -> String
where
F: FnOnce(&mut FunctionBuilder, Value),
{
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(types::I64));
let mut func = Function::with_name_signature(
cranelift::codegen::ir::UserFuncName::testcase(name),
sig,
);
let mut func_ctx = FunctionBuilderContext::new();
let mut builder = FunctionBuilder::new(&mut func, &mut func_ctx);
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 ptr = builder.block_params(entry_block)[0];
f(&mut builder, ptr);
builder.ins().return_(&[]);
builder.finalize();
func.to_string()
}
fn build_test_ir_with_free<F>(name: &str, f: F) -> String
where
F: FnOnce(&mut FunctionBuilder, Value, FuncRef),
{
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(types::I64));
let mut func = Function::with_name_signature(
cranelift::codegen::ir::UserFuncName::testcase(name),
sig,
);
let mut dealloc_sig = Signature::new(CallConv::SystemV);
dealloc_sig.params.push(AbiParam::new(types::I64));
let dealloc_sig_ref = func.import_signature(dealloc_sig);
let mut func_ctx = FunctionBuilderContext::new();
let mut builder = FunctionBuilder::new(&mut func, &mut func_ctx);
let dealloc_name =
cranelift::codegen::ir::ExternalName::testcase("jit_v2_dealloc");
let dealloc_func_ref = builder.import_function(cranelift::codegen::ir::ExtFuncData {
name: dealloc_name,
signature: dealloc_sig_ref,
colocated: false,
});
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 ptr = builder.block_params(entry_block)[0];
f(&mut builder, ptr, dealloc_func_ref);
builder.ins().return_(&[]);
builder.finalize();
func.to_string()
}
#[test]
fn test_retain_emits_atomic_add() {
let ir = build_test_ir("retain", |builder, ptr| {
let mut emitter = V2RefcountEmitter::new(builder);
emitter.emit_retain(ptr);
});
assert!(
ir.contains("atomic_rmw") && ir.contains("add"),
"retain should emit atomic_rmw add. IR:\n{ir}"
);
assert!(
ir.contains("iconst.i32 1"),
"retain should load constant 1. IR:\n{ir}"
);
}
#[test]
fn test_release_emits_atomic_sub() {
let ir = build_test_ir("release", |builder, ptr| {
let mut emitter = V2RefcountEmitter::new(builder);
let _old = emitter.emit_release(ptr);
});
assert!(
ir.contains("atomic_rmw") && ir.contains("sub"),
"release should emit atomic_rmw sub. IR:\n{ir}"
);
}
#[test]
fn test_drop_emits_branch_and_fence() {
let ir = build_test_ir_with_free("drop", |builder, ptr, free_func| {
let mut emitter = V2RefcountEmitter::new(builder);
emitter.emit_drop(ptr, free_func);
});
assert!(
ir.contains("atomic_rmw") && ir.contains("sub"),
"drop should emit atomic_rmw sub. IR:\n{ir}"
);
assert!(
ir.contains("icmp"),
"drop should emit icmp. IR:\n{ir}"
);
assert!(
ir.contains("brif"),
"drop should emit brif. IR:\n{ir}"
);
assert!(
ir.contains("fence"),
"drop should emit fence. IR:\n{ir}"
);
assert!(
ir.contains("call"),
"drop should emit call to free. IR:\n{ir}"
);
}
#[test]
fn test_drop_has_three_blocks() {
let ir = build_test_ir_with_free("drop_blocks", |builder, ptr, free_func| {
let mut emitter = V2RefcountEmitter::new(builder);
emitter.emit_drop(ptr, free_func);
});
let block_decl_count = ir
.lines()
.filter(|line| {
let trimmed = line.trim();
trimmed.starts_with("block") && trimmed.ends_with(':')
})
.count();
assert!(
block_decl_count >= 3,
"drop should create at least 3 blocks (entry, free, cont). Found {block_decl_count}. IR:\n{ir}"
);
}
#[test]
fn test_v2_dealloc_null_safety() {
jit_v2_dealloc(std::ptr::null_mut());
}
#[test]
fn test_v2_dealloc_with_fake_object() {
let mut buf = [0u8; 32];
buf[4] = 0;
buf[5] = 0;
jit_v2_dealloc(buf.as_mut_ptr());
}
#[test]
fn test_refcount_offset_is_zero() {
assert_eq!(V2_REFCOUNT_OFFSET, 0);
}
#[test]
fn test_atomic_u32_has_correct_size() {
assert_eq!(
std::mem::size_of::<std::sync::atomic::AtomicU32>(),
4,
"AtomicU32 must be 4 bytes for inline refcounting"
);
}
#[test]
fn test_retain_is_single_atomic_op() {
let ir = build_test_ir("retain_single", |builder, ptr| {
let mut emitter = V2RefcountEmitter::new(builder);
emitter.emit_retain(ptr);
});
let atomic_count = ir.matches("atomic_rmw").count();
assert_eq!(
atomic_count, 1,
"retain should emit exactly 1 atomic_rmw. Found {atomic_count}. IR:\n{ir}"
);
}
#[test]
fn test_release_is_single_atomic_op() {
let ir = build_test_ir("release_single", |builder, ptr| {
let mut emitter = V2RefcountEmitter::new(builder);
let _old = emitter.emit_release(ptr);
});
let atomic_count = ir.matches("atomic_rmw").count();
assert_eq!(
atomic_count, 1,
"release should emit exactly 1 atomic_rmw. Found {atomic_count}. IR:\n{ir}"
);
}
}