use inkwell::IntPredicate;
use inkwell::values::IntValue;
use sv_lang::Symbol;
use sv_lang::kinds::ExpressionKind;
use sv_sim_runtime::semaphore_rt::SemaphoreOp;
use sv_sim_sim::symid::SymbolUid;
use crate::error::LoweringError;
use crate::expr::{LowerCtx, LoweredValue};
use crate::ops::OpBuilder;
fn op<'a, 'ctx>(cx: &'a LowerCtx<'ctx, '_, '_>) -> OpBuilder<'a, 'ctx> {
OpBuilder {
ctx: cx.ctx,
b: &cx.b,
}
}
fn strip<'d>(mut e: sv_lang::Expression<'d>) -> sv_lang::Expression<'d> {
while e.kind() == ExpressionKind::Conversion
&& let Some(inner) = e.conversion_operand()
{
e = inner;
}
e
}
pub(crate) fn is_class_event(e: sv_lang::Expression<'_>) -> bool {
sv_sim_sim::process::cfg::is_class_event_expr(&e)
}
fn is_null(e: sv_lang::Expression<'_>) -> bool {
strip(e).kind() == ExpressionKind::NullLiteral
}
fn call<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, op: SemaphoreOp, id: IntValue<'ctx>) -> IntValue<'ctx> {
crate::classes::semaphore_call(cx, op, id, cx.ctx.i64_type().const_zero())
}
pub(crate) fn new_event<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> IntValue<'ctx> {
call(cx, SemaphoreOp::EventNew, cx.ctx.i64_type().const_zero())
}
fn property<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<Option<(IntValue<'ctx>, sv_sim_sim::objects::FieldSlot, String)>, LoweringError> {
let member = if e.kind() == ExpressionKind::MemberAccess {
e.member_symbol()
} else if matches!(
e.kind(),
ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue
) {
e.referenced_symbol()
} else {
None
};
let Some(member) = member else {
return Ok(None);
};
if crate::classes::is_static_field(&member) {
let (h, slot, _) = crate::classes::static_location(cx, &member)?;
return Ok(Some((h, slot, member.name().to_owned())));
}
if !crate::classes::is_instance_field(&member) {
return Ok(None);
}
let h = if e.kind() == ExpressionKind::MemberAccess {
let base = e.operand().ok_or_else(|| {
LoweringError::unsupported_at("member-access event missing base", &cx.fn_sym)
})?;
crate::classes::receiver_handle(cx, base)?
} else {
crate::classes::current_this_handle(cx)?
};
let (slot, _) = crate::classes::member_slot_and_type(&member)?;
Ok(Some((h, slot, member.name().to_owned())))
}
fn load_id<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
let e = strip(e);
if let Some((h, slot, name)) = property(cx, e)? {
let (pv, live) = crate::classes::field_get_call(cx, h, slot, 64, false);
crate::classes::report_dead_handle_access(cx, live, e.source_range().start, &name, "read")?;
return Ok(op(cx).lane_val(pv));
}
let sym = e
.referenced_symbol()
.ok_or_else(|| LoweringError::unsupported_at("an unresolved class event", &cx.fn_sym))?;
match cx.locals.get(&sym.uid()) {
Some(&(ptr, 64, _)) => Ok(crate::events::frame_load(cx, ptr)),
_ => Err(LoweringError::unsupported_at(
"a class event that is neither a property nor a local/formal of its method",
&sym,
)),
}
}
pub(crate) fn id_of<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
if is_null(e) {
return Ok(cx.ctx.i64_type().const_zero());
}
if !is_class_event(e) {
return Err(LoweringError::unsupported_at(
"a class event used together with an event that is not a class event (a module \
event cannot be assigned to, compared with or passed as a class event, LRM \
1800-2023 15.5)",
&cx.fn_sym,
));
}
load_id(cx, e)
}
pub(crate) fn store_id<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: sv_lang::Expression<'d>,
id: IntValue<'ctx>,
) -> Result<(), LoweringError> {
let lhs = strip(lhs);
if let Some((h, slot, name)) = property(cx, lhs)? {
let v = op(cx).pack(id, op(cx).i_n(64).const_zero(), 64, cx.repr, false);
let live = crate::classes::field_set_call(cx, h, slot, v);
return crate::classes::report_dead_handle_access(
cx,
live,
lhs.source_range().start,
&name,
"write",
);
}
let sym = lhs
.referenced_symbol()
.ok_or_else(|| LoweringError::unsupported_at("an unresolved class event", &cx.fn_sym))?;
match cx.locals.get(&sym.uid()) {
Some(&(ptr, 64, _)) => {
crate::events::frame_store(cx, ptr, id);
Ok(())
}
_ => Err(LoweringError::unsupported_at(
"an assignment to a class event that is neither a property nor a local/formal of \
its method",
&sym,
)),
}
}
pub(crate) fn lower_assignment<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: sv_lang::Expression<'d>,
rhs: sv_lang::Expression<'d>,
) -> Result<Option<LoweredValue<'ctx>>, LoweringError> {
if !is_class_event(lhs) {
if is_class_event(rhs) {
return Err(LoweringError::unsupported_at(
"a class event assigned to an event that is not a class event (LRM 1800-2023 \
15.5)",
&cx.fn_sym,
));
}
return Ok(None);
}
let id = id_of(cx, rhs)?;
store_id(cx, lhs, id)?;
let o = op(cx);
Ok(Some(LoweredValue::Packed(o.pack(
cx.ctx.bool_type().const_zero(),
cx.ctx.bool_type().const_zero(),
1,
cx.repr,
false,
))))
}
pub(crate) fn lower_equality<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
l: sv_lang::Expression<'d>,
r: sv_lang::Expression<'d>,
negate: bool,
) -> Result<Option<LoweredValue<'ctx>>, LoweringError> {
if !is_class_event(l) && !is_class_event(r) {
return Ok(None);
}
let a = id_of(cx, l)?;
let b = id_of(cx, r)?;
let pred = if negate {
IntPredicate::NE
} else {
IntPredicate::EQ
};
let same = cx.b.build_int_compare(pred, a, b, "cev_cmp").unwrap();
let o = op(cx);
Ok(Some(LoweredValue::Packed(o.pack(
same,
cx.ctx.bool_type().const_zero(),
1,
cx.repr,
false,
))))
}
pub(crate) fn lower_non_null<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<Option<IntValue<'ctx>>, LoweringError> {
if !is_class_event(e) {
return Ok(None);
}
let id = load_id(cx, e)?;
Ok(Some(
cx.b.build_int_compare(
IntPredicate::NE,
id,
cx.ctx.i64_type().const_zero(),
"cev_nonnull",
)
.unwrap(),
))
}
pub(crate) fn lower_trigger<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
target: sv_lang::Expression<'d>,
) -> Result<bool, LoweringError> {
if !is_class_event(target) {
return Ok(false);
}
let id = load_id(cx, target)?;
call(cx, SemaphoreOp::EventTrigger, id);
Ok(true)
}
pub(crate) fn lower_trigger_nba<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
target: sv_lang::Expression<'d>,
delay: IntValue<'ctx>,
) -> Result<bool, LoweringError> {
if !is_class_event(target) {
return Ok(false);
}
let id = load_id(cx, target)?;
crate::classes::semaphore_call(cx, SemaphoreOp::EventTriggerNba, id, delay);
Ok(true)
}
pub(crate) fn lower_triggered<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
target: sv_lang::Expression<'d>,
) -> Result<Option<LoweredValue<'ctx>>, LoweringError> {
if !is_class_event(target) {
return Ok(None);
}
let id = load_id(cx, target)?;
let r = call(cx, SemaphoreOp::EventTriggered, id);
let bit =
cx.b.build_int_truncate(r, cx.ctx.bool_type(), "cev_triggered")
.unwrap();
let o = op(cx);
Ok(Some(LoweredValue::Packed(o.pack(
bit,
cx.ctx.bool_type().const_zero(),
1,
cx.repr,
false,
))))
}
pub(crate) fn lower_wait<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
stmt: &sv_lang::Statement<'d>,
) -> Result<(), LoweringError> {
let tc = stmt.timing_control().ok_or_else(|| {
LoweringError::unsupported_at("a class event wait with no event", &cx.fn_sym)
})?;
let terms = sv_sim_sim::process::cfg::class_event_wait_terms(&tc)
.map_err(|e| LoweringError::unsupported_at(&e.to_string(), &cx.fn_sym))?;
if sv_sim_sim::process::cfg::class_event_wait_is_mixed(&tc) {
for sig in &terms {
let id = load_id(cx, *sig)?;
call(cx, SemaphoreOp::EventEnqueueDual, id);
}
return Ok(());
}
let Some((last, first)) = terms.split_last() else {
return Err(LoweringError::unsupported_at(
"a class event wait with no event",
&cx.fn_sym,
));
};
for sig in first {
let id = load_id(cx, *sig)?;
call(cx, SemaphoreOp::EventEnqueue, id);
}
let id = load_id(cx, *last)?;
call(cx, SemaphoreOp::EventWait, id);
Ok(())
}
pub(crate) fn lower_leave(cx: &LowerCtx<'_, '_, '_>) {
call(cx, SemaphoreOp::EventLeave, cx.ctx.i64_type().const_zero());
}
pub(crate) fn init_local<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
var: &Symbol<'d>,
) -> Result<bool, LoweringError> {
if !var.value_type().is_some_and(|t| t.is_event()) || !is_class_method_local(var) {
return Ok(false);
}
let &(ptr, _, _) = cx
.locals
.get(&var.uid())
.ok_or_else(|| LoweringError::unsupported_at("a local event has no frame slot", var))?;
let id = match var.initializer() {
Some(init) => id_of(cx, init)?,
None => new_event(cx),
};
crate::events::frame_store(cx, ptr, id);
Ok(true)
}
pub(crate) fn is_class_method_local(var: &Symbol<'_>) -> bool {
let mut scope = var.parent();
while let Some(s) = scope {
if s.kind() == sv_lang::kinds::SymbolKind::Subroutine {
return s.parent().is_some_and(|p| {
matches!(
p.kind(),
sv_lang::kinds::SymbolKind::ClassType
| sv_lang::kinds::SymbolKind::GenericClassDef
)
});
}
scope = s.parent();
}
false
}
pub(crate) fn bind_formal<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
formal: &Symbol<'d>,
actual: sv_lang::Expression<'d>,
) -> Result<bool, LoweringError> {
if !formal.value_type().is_some_and(|t| t.is_event()) || !is_class_method_local(formal) {
return Ok(false);
}
let &(ptr, _, _) = cx.locals.get(&formal.uid()).ok_or_else(|| {
LoweringError::unsupported_at("an event formal has no frame slot", formal)
})?;
let id = id_of(cx, actual)?;
crate::events::frame_store(cx, ptr, id);
Ok(true)
}