use inkwell::AddressSpace;
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::module::{Linkage, Module};
use inkwell::types::AnyType;
use inkwell::values::PointerValue;
use sv_sim_abi::accessor::{SV_GET_SCALAR_SYMBOL, SV_SET_SCALAR_SYMBOL};
use sv_sim_abi::entry::{SIM_EVAL_REGION_SYMBOL, SIM_INIT_SYMBOL};
pub const COUNTER_SLOT: u32 = 0;
pub const REQUEST_SLOT: u32 = 3;
pub fn counter_fixture_bitcode() -> Vec<u8> {
let ctx = Context::create();
let module = build_counter_fixture_module(&ctx);
module.write_bitcode_to_memory().as_slice().to_vec()
}
fn build_counter_fixture_module(ctx: &Context) -> Module<'_> {
let module = ctx.create_module("cross_mode_counter_fixture");
let builder = ctx.create_builder();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let i8_ty = ctx.i8_type();
let i32_ty = ctx.i32_type();
let i64_ty = ctx.i64_type();
let i128_ty = ctx.i128_type();
let void_ty = ctx.void_type();
let counter_global = module.add_global(i128_ty, None, "counter_cell");
counter_global.set_initializer(&i128_ty.const_int(0, false));
counter_global.set_linkage(Linkage::Private);
let request_global = module.add_global(i128_ty, None, "request_cell");
request_global.set_initializer(&i128_ty.const_int(0, false));
request_global.set_linkage(Linkage::Private);
build_sim_init(
ctx,
&module,
&builder,
ptr_ty,
i32_ty,
i64_ty,
i128_ty,
counter_global,
request_global,
);
build_sim_eval_region(
ctx,
&module,
&builder,
ptr_ty,
i8_ty,
i32_ty,
i64_ty,
i128_ty,
void_ty,
counter_global,
request_global,
);
build_accessor(
ctx,
&module,
&builder,
SV_GET_SCALAR_SYMBOL,
ptr_ty,
i32_ty,
i128_ty,
Some(void_ty), counter_global,
request_global,
true,
);
build_accessor(
ctx,
&module,
&builder,
SV_SET_SCALAR_SYMBOL,
ptr_ty,
i32_ty,
i128_ty,
Some(void_ty),
counter_global,
request_global,
false,
);
assert!(
module.verify().is_ok(),
"counter_fixture module failed to verify:\n{}",
module.print_to_string().to_string()
);
module
}
#[allow(clippy::too_many_arguments)]
fn build_sim_init<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
builder: &Builder<'ctx>,
ptr_ty: inkwell::types::PointerType<'ctx>,
i32_ty: inkwell::types::IntType<'ctx>,
i64_ty: inkwell::types::IntType<'ctx>,
i128_ty: inkwell::types::IntType<'ctx>,
counter_global: inkwell::values::GlobalValue<'ctx>,
request_global: inkwell::values::GlobalValue<'ctx>,
) {
let fn_ty = ptr_ty.fn_type(&[i32_ty.into()], false);
let f = module.add_function(SIM_INIT_SYMBOL, fn_ty, None);
let entry = ctx.append_basic_block(f, "entry");
builder.position_at_end(entry);
builder
.build_store(
counter_global.as_pointer_value(),
i128_ty.const_int(0, false),
)
.unwrap();
builder
.build_store(
request_global.as_pointer_value(),
i128_ty.const_int(0, false),
)
.unwrap();
let fixed_addr = i64_ty.const_int(0xC0DE_C0DE, false);
let handle_ptr = builder
.build_int_to_ptr(fixed_addr, ptr_ty, "handle")
.unwrap();
builder.build_return(Some(&handle_ptr)).unwrap();
assert!(f.verify(true), "sim_init failed to verify");
}
#[allow(clippy::too_many_arguments)]
fn build_sim_eval_region<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
builder: &Builder<'ctx>,
ptr_ty: inkwell::types::PointerType<'ctx>,
i8_ty: inkwell::types::IntType<'ctx>,
i32_ty: inkwell::types::IntType<'ctx>,
i64_ty: inkwell::types::IntType<'ctx>,
i128_ty: inkwell::types::IntType<'ctx>,
void_ty: inkwell::types::VoidType<'ctx>,
counter_global: inkwell::values::GlobalValue<'ctx>,
request_global: inkwell::values::GlobalValue<'ctx>,
) {
let eval_result_ty = ctx.struct_type(&[i32_ty.into(), i8_ty.array_type(32).into()], false);
let simtime_ty = ctx.struct_type(&[i64_ty.into(), i64_ty.into()], false);
let fn_ty = void_ty.fn_type(
&[
ptr_ty.into(), ptr_ty.into(), i32_ty.into(), simtime_ty.into(), ],
false,
);
let f = module.add_function(SIM_EVAL_REGION_SYMBOL, fn_ty, None);
let sret_kind_id = Attribute::get_named_enum_kind_id("sret");
f.add_attribute(
AttributeLoc::Param(0),
ctx.create_type_attribute(sret_kind_id, eval_result_ty.as_any_type_enum()),
);
let sret = f.get_nth_param(0).unwrap().into_pointer_value();
let region = f.get_nth_param(2).unwrap().into_int_value();
let entry_bb = ctx.append_basic_block(f, "entry");
let active_bb = ctx.append_basic_block(f, "active");
let increment_bb = ctx.append_basic_block(f, "increment");
let exit_bb = ctx.append_basic_block(f, "exit");
let trap_bb = ctx.append_basic_block(f, "trap");
builder.position_at_end(entry_bb);
builder
.build_switch(
region,
trap_bb,
&[
(i32_ty.const_int(0, false), active_bb),
(i32_ty.const_int(1, false), exit_bb),
(i32_ty.const_int(2, false), exit_bb),
(i32_ty.const_int(3, false), exit_bb),
(i32_ty.const_int(4, false), exit_bb),
],
)
.unwrap();
builder.position_at_end(active_bb);
let request = builder
.build_load(i128_ty, request_global.as_pointer_value(), "request")
.unwrap()
.into_int_value();
let nonzero = builder
.build_int_compare(
inkwell::IntPredicate::NE,
request,
i128_ty.const_int(0, false),
"request_nonzero",
)
.unwrap();
builder
.build_conditional_branch(nonzero, increment_bb, exit_bb)
.unwrap();
builder.position_at_end(increment_bb);
let counter = builder
.build_load(i128_ty, counter_global.as_pointer_value(), "counter")
.unwrap()
.into_int_value();
let incremented = builder
.build_int_add(counter, i128_ty.const_int(1, false), "incremented")
.unwrap();
builder
.build_store(counter_global.as_pointer_value(), incremented)
.unwrap();
builder.build_unconditional_branch(exit_bb).unwrap();
builder.position_at_end(exit_bb);
store_zeroed_eval_result(ctx, builder, sret);
builder.position_at_end(trap_bb);
builder.build_unreachable().unwrap();
assert!(f.verify(true), "sim_eval_region failed to verify");
}
fn store_zeroed_eval_result<'ctx>(ctx: &'ctx Context, b: &Builder<'ctx>, sret: PointerValue<'ctx>) {
let i8_ty = ctx.i8_type();
let zero = i8_ty.const_int(0, false);
let elems = vec![zero; 36];
let arr = i8_ty.const_array(&elems);
b.build_store(sret, arr).unwrap();
b.build_return(None).unwrap();
}
#[allow(clippy::too_many_arguments)]
fn build_accessor<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
builder: &Builder<'ctx>,
symbol: &str,
ptr_ty: inkwell::types::PointerType<'ctx>,
i32_ty: inkwell::types::IntType<'ctx>,
i128_ty: inkwell::types::IntType<'ctx>,
void_ty: Option<inkwell::types::VoidType<'ctx>>,
counter_global: inkwell::values::GlobalValue<'ctx>,
request_global: inkwell::values::GlobalValue<'ctx>,
is_get: bool,
) {
let f = if is_get {
let fn_ty = i128_ty.fn_type(&[ptr_ty.into(), i32_ty.into()], false);
module.add_function(symbol, fn_ty, None)
} else {
let fn_ty = void_ty
.expect("setter needs void_ty")
.fn_type(&[ptr_ty.into(), i32_ty.into(), i128_ty.into()], false);
module.add_function(symbol, fn_ty, None)
};
let slot = f.get_nth_param(1).unwrap().into_int_value();
let value = if is_get {
None
} else {
Some(f.get_nth_param(2).unwrap())
};
let entry_bb = ctx.append_basic_block(f, "entry");
let counter_bb = ctx.append_basic_block(f, "counter_slot");
let request_bb = ctx.append_basic_block(f, "request_slot");
let trap_bb = ctx.append_basic_block(f, "trap");
builder.position_at_end(entry_bb);
builder
.build_switch(
slot,
trap_bb,
&[
(i32_ty.const_int(u64::from(COUNTER_SLOT), false), counter_bb),
(i32_ty.const_int(u64::from(REQUEST_SLOT), false), request_bb),
],
)
.unwrap();
for (bb, global) in [(counter_bb, counter_global), (request_bb, request_global)] {
builder.position_at_end(bb);
if is_get {
let v = builder
.build_load(i128_ty, global.as_pointer_value(), "v")
.unwrap();
builder.build_return(Some(&v)).unwrap();
} else {
builder
.build_store(global.as_pointer_value(), value.unwrap())
.unwrap();
builder.build_return(None).unwrap();
}
}
builder.position_at_end(trap_bb);
builder.build_unreachable().unwrap();
assert!(f.verify(true), "{symbol} failed to verify");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn counter_fixture_bitcode_is_nonempty_and_module_defines_all_four_symbols() {
let ctx = Context::create();
let module = build_counter_fixture_module(&ctx);
for symbol in [
SIM_INIT_SYMBOL,
SIM_EVAL_REGION_SYMBOL,
SV_GET_SCALAR_SYMBOL,
SV_SET_SCALAR_SYMBOL,
] {
assert!(
module.get_function(symbol).is_some(),
"counter_fixture module must define {symbol:?}"
);
}
let bitcode = counter_fixture_bitcode();
assert!(!bitcode.is_empty());
}
#[test]
fn sim_eval_region_only_increments_the_counter_when_the_request_gate_is_nonzero() {
let bitcode = counter_fixture_bitcode();
let backend = crate::orc::JitBackend::new().expect("JitBackend::new");
backend
.add_module_from_bitcode(&bitcode)
.expect("add_module_from_bitcode");
let sim_init: sv_sim_abi::entry::SimInitFn =
unsafe { backend.get_fn(SIM_INIT_SYMBOL) }.expect("sim_init must resolve");
let sim_eval_region: sv_sim_abi::entry::SimEvalRegionFn =
unsafe { backend.get_fn(SIM_EVAL_REGION_SYMBOL) }
.expect("sim_eval_region must resolve");
let sv_get_scalar: sv_sim_abi::accessor::SvGetScalarFn =
unsafe { backend.get_fn(SV_GET_SCALAR_SYMBOL) }.expect("sv_get_scalar must resolve");
let sv_set_scalar: sv_sim_abi::accessor::SvSetScalarFn =
unsafe { backend.get_fn(SV_SET_SCALAR_SYMBOL) }.expect("sv_set_scalar must resolve");
let h = sim_init(0);
let t = sv_sim_abi::SimTime {
sim_units: 0,
delta: 0,
};
let r = sim_eval_region(h, sv_sim_abi::Region::Active, t);
assert_eq!(r.outcome, sv_sim_abi::EvalOutcome::Completed);
assert_eq!(
sv_get_scalar(h, sv_sim_abi::SlotId(COUNTER_SLOT)),
0,
"the counter must NOT increment while the request gate (REQUEST_SLOT) is zero"
);
sv_set_scalar(h, sv_sim_abi::SlotId(REQUEST_SLOT), 1);
let r2 = sim_eval_region(h, sv_sim_abi::Region::Active, t);
assert_eq!(r2.outcome, sv_sim_abi::EvalOutcome::Completed);
assert_eq!(
sv_get_scalar(h, sv_sim_abi::SlotId(COUNTER_SLOT)),
1,
"the counter must increment by exactly one once the request gate is nonzero"
);
}
}