use inkwell::IntPredicate;
use inkwell::values::IntValue;
use sv_lang::kinds::{ExpressionKind, SymbolKind};
use vj_runtime::objects_rt::ObjOp;
use vj_sim::constraints::build::property_is_random;
use vj_sim::netlist::classify_members;
use crate::classes::{
ProcessCall, current_this_handle, current_this_var, is_static_field, member_slot_and_type,
obj_call, receiver_handle, static_location, static_singleton_handle,
};
use crate::classes_randomize::{class_constraint_blocks, class_levels};
use crate::error::LoweringError;
use crate::expr::{LowerCtx, LoweredValue};
use crate::ops::OpBuilder;
fn on_flag<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
let i64t = cx.ctx.i64_type();
let v = crate::strings::index_i64(cx, e)?;
let nz =
cx.b.build_int_compare(IntPredicate::NE, v, i64t.const_zero(), "mode_on")
.unwrap();
Ok(cx.b.build_int_z_extend(nz, i64t, "mode_on64").unwrap())
}
fn with_flags<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, on: IntValue<'ctx>, flags: u64) -> IntValue<'ctx> {
cx.b.build_or(on, cx.ctx.i64_type().const_int(flags, false), "mode_idx2")
.unwrap()
}
fn int_result<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, value: IntValue<'ctx>) -> LoweredValue<'ctx> {
let i32t = cx.ctx.i32_type();
let raw = cx.b.build_int_truncate(value, i32t, "mode_ret").unwrap();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
LoweredValue::Packed(op.pack(raw, i32t.const_zero(), 32, cx.repr, true))
}
fn error_when<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
value: IntValue<'ctx>,
bad: u64,
at: sv_lang::SourceLoc,
msg: &str,
) -> Result<(), LoweringError> {
let hit =
cx.b.build_int_compare(
IntPredicate::EQ,
value,
cx.ctx.i64_type().const_int(bad, false),
"mode_err",
)
.unwrap();
crate::stmt_shared::error_if(cx, hit, at, msg)
}
struct PropSite<'ctx> {
h: IntValue<'ctx>,
offset: u64,
is_static: bool,
name: String,
leaf: IntValue<'ctx>,
}
fn peel_elements<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
mut target: sv_lang::Expression<'d>,
at: sv_lang::SourceLoc,
) -> Result<(sv_lang::Expression<'d>, Option<IntValue<'ctx>>), LoweringError> {
let fail =
|why: &str| LoweringError::unsupported_at(&format!("rand_mode(): {why}"), &cx.fn_sym);
let i64t = cx.ctx.i64_type();
let mut bit: Option<IntValue<'ctx>> = None;
while target.kind() == ExpressionKind::ElementSelect {
let base = target
.select_value()
.ok_or_else(|| fail("an element select with no array"))?;
let t = base
.expr_type()
.map(|t| t.canonical())
.ok_or_else(|| fail("an element select of an untyped array"))?;
if !t.is_unpacked_array()
|| t.is_associative_array()
|| t.is_dynamically_sized_array()
|| t.is_queue()
{
return Err(fail(
"only an element of a fixed-size unpacked array can be named (a dynamic \
array, queue or associative array element is not supported)",
));
}
let sel = target
.selector()
.ok_or_else(|| fail("an element select with no index"))?;
let r = t.fixed_unpacked_array_range();
let lo = i64::from(r.left).min(i64::from(r.right));
let hi = i64::from(r.left).max(i64::from(r.right));
let ew = t
.element_type()
.and_then(|e| vj_sim::state::storage::storage_width(&e))
.ok_or_else(|| fail("the element has no stored width"))?;
let step = if let Some(idx) = sel.constant_value().and_then(|c| c.as_i64()) {
if !(lo..=hi).contains(&idx) {
return Err(fail(&format!(
"index {idx} is outside [{}:{}]",
r.left, r.right
)));
}
i64t.const_int((idx - lo) as u64 * ew, false)
} else {
let idx = crate::strings::index_i64(cx, sel)?;
let below =
cx.b.build_int_compare(
IntPredicate::SLT,
idx,
i64t.const_int(lo as u64, true),
"rm_lo",
)
.unwrap();
let above =
cx.b.build_int_compare(
IntPredicate::SGT,
idx,
i64t.const_int(hi as u64, true),
"rm_hi",
)
.unwrap();
let out = cx.b.build_or(below, above, "rm_out").unwrap();
crate::stmt_shared::error_if(
cx,
out,
at,
&format!(
"rand_mode(): the element index is outside [{}:{}]",
r.left, r.right
),
)?;
let rel =
cx.b.build_int_sub(idx, i64t.const_int(lo as u64, true), "rm_rel")
.unwrap();
cx.b.build_int_mul(rel, i64t.const_int(ew, false), "rm_step")
.unwrap()
};
bit = Some(match bit {
Some(b) => cx.b.build_int_add(b, step, "rm_bit").unwrap(),
None => step,
});
target = base;
}
Ok((target, bit))
}
fn locate_property<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
at: sv_lang::SourceLoc,
target: sv_lang::Expression<'d>,
) -> Result<PropSite<'ctx>, LoweringError> {
let fail =
|why: &str| LoweringError::unsupported_at(&format!("rand_mode(): {why}"), &cx.fn_sym);
let (target, elem_bit) = peel_elements(cx, target, at)?;
let (member, base) = if target.kind() == ExpressionKind::MemberAccess {
(target.member_symbol(), target.operand())
} else if matches!(
target.kind(),
ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue
) {
(target.referenced_symbol(), None)
} else {
(None, None)
};
let Some(member) = member.filter(|m| m.kind() == SymbolKind::ClassProperty) else {
return Err(fail(
"only a `rand` property of a class object, or a constant element of a fixed-size \
array property, can be named (a struct member or any other expression is not \
supported)",
));
};
if member.class_property_rand_mode() == sv_lang::RandMode::None && !property_is_random(member) {
return Err(fail(&format!(
"`{}` is not a `rand` or `randc` property (LRM 18.8)",
member.name()
)));
}
if is_static_field(&member) {
if elem_bit.is_some() {
return Err(fail(
"an element of a `static` array property cannot be switched alone",
));
}
let (h, slot, _) = static_location(cx, &member)?;
return Ok(PropSite {
h,
offset: slot.offset as u64,
is_static: true,
name: member.name().to_string(),
leaf: cx.ctx.i64_type().const_zero(),
});
}
let h = match base {
Some(b) => receiver_handle(cx, b)?,
None => current_this_handle(cx)?,
};
let (slot, _) = member_slot_and_type(&member)?;
Ok(PropSite {
h,
offset: slot.offset as u64,
is_static: false,
name: member.name().to_string(),
leaf: match elem_bit {
Some(b) => {
cx.b.build_int_add(b, cx.ctx.i64_type().const_int(1, false), "rm_leaf")
.unwrap()
}
None => cx.ctx.i64_type().const_zero(),
},
})
}
pub(crate) fn lower_rand_mode_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let ops = e.operands();
let Some(&target) = ops.first() else {
return Err(LoweringError::unsupported_at(
"rand_mode(): the call has no property operand",
&cx.fn_sym,
));
};
if ops.len() > 2 {
return Err(LoweringError::unsupported_at(
"rand_mode(): too many arguments",
&cx.fn_sym,
));
}
rand_mode_of_property(cx, e.source_range().start, target, ops.get(1).copied())
}
fn rand_mode_of_property<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
at: sv_lang::SourceLoc,
target: sv_lang::Expression<'d>,
value: Option<sv_lang::Expression<'d>>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let site = locate_property(cx, at, target)?;
let i64t = cx.ctx.i64_type();
let null_ptr = cx
.ctx
.ptr_type(inkwell::AddressSpace::default())
.const_null();
let leaf_shifted =
cx.b.build_left_shift(site.leaf, i64t.const_int(2, false), "rm_leaf4")
.unwrap();
let static_flag =
cx.b.build_or(
leaf_shifted,
i64t.const_int(if site.is_static { 2 } else { 0 }, false),
"rm_flags",
)
.unwrap();
let dead = format!("null object handle: rand_mode of member {}", site.name);
match value {
None => {
let ret = obj_call(
cx,
ObjOp::RandModeGet,
site.h,
i64t.const_int(site.offset, false),
static_flag,
null_ptr,
);
error_when(cx, ret, 2, at, &dead)?;
Ok(int_result(cx, ret))
}
Some(value) => {
let on = on_flag(cx, value)?;
let ret = obj_call(
cx,
ObjOp::RandModeSet,
site.h,
i64t.const_int(site.offset, false),
cx.b.build_or(on, static_flag, "mode_idx2").unwrap(),
null_ptr,
);
error_when(cx, ret, 0, at, &dead)?;
Ok(int_result(cx, i64t.const_zero()))
}
}
}
fn is_rand_handle_property<'d>(recv: sv_lang::Expression<'d>) -> bool {
let member = if recv.kind() == ExpressionKind::MemberAccess {
recv.member_symbol()
} else if matches!(
recv.kind(),
ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue
) {
recv.referenced_symbol()
} else {
None
};
member.is_some_and(|m| {
m.kind() == SymbolKind::ClassProperty
&& (m.class_property_rand_mode() != sv_lang::RandMode::None || property_is_random(m))
})
}
pub(crate) fn lower_constraint_mode_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let at = e.source_range().start;
let fail =
|why: &str| LoweringError::unsupported_at(&format!("constraint_mode(): {why}"), &cx.fn_sym);
let ops = e.operands();
let Some(&target) = ops.first() else {
return Err(fail("the call has no constraint operand"));
};
if ops.len() > 2 {
return Err(fail("too many arguments"));
}
let (block, base) = if target.kind() == ExpressionKind::MemberAccess {
(target.member_symbol(), target.operand())
} else if target.kind() == ExpressionKind::ArbitrarySymbol {
(target.arbitrary_symbol(), None)
} else {
(None, None)
};
let Some(block) = block.filter(|b| b.kind() == SymbolKind::ConstraintBlock) else {
return Err(fail(
"the operand is not a named constraint block of a class object",
));
};
let ty = match base {
Some(b) => b.expr_type().map(|t| t.canonical()),
None => current_this_var(cx)?.value_type().map(|t| t.canonical()),
}
.ok_or_else(|| fail("the object's class is not known"))?;
let levels = class_levels(ty);
let blocks = class_constraint_blocks(&levels);
let Some((ordinal, blk)) = blocks
.iter()
.enumerate()
.find(|(_, b)| b.name == block.name())
else {
return Err(fail(&format!(
"constraint `{}` has no body in the object's class (a `pure constraint` \
prototype)",
block.name()
)));
};
if blk.static_flipped {
return Err(fail(&crate::classes_randomize::flipped_static_reason(
&blk.name,
)));
}
let h = if blk.is_static {
static_singleton_handle(cx, levels[blk.home_level])?
} else {
match base {
Some(b) => receiver_handle(cx, b)?,
None => current_this_handle(cx)?,
}
};
let i64t = cx.ctx.i64_type();
let null_ptr = cx
.ctx
.ptr_type(inkwell::AddressSpace::default())
.const_null();
let dead = format!(
"null object handle: constraint_mode of constraint {}",
blk.name
);
match ops.get(1) {
None => {
let ret = obj_call(
cx,
ObjOp::ConstraintModeGet,
h,
i64t.const_int(ordinal as u64, false),
i64t.const_zero(),
null_ptr,
);
error_when(cx, ret, 2, at, &dead)?;
Ok(int_result(cx, ret))
}
Some(&value) => {
let on = on_flag(cx, value)?;
let ret = obj_call(
cx,
ObjOp::ConstraintModeSet,
h,
i64t.const_int(ordinal as u64, false),
with_flags(cx, on, 0),
null_ptr,
);
error_when(cx, ret, 0, at, &dead)?;
Ok(int_result(cx, i64t.const_zero()))
}
}
}
pub(crate) fn lower_object_mode_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
call: sv_lang::Expression<'d>,
callee: sv_lang::Symbol<'d>,
) -> Result<ProcessCall<'ctx>, LoweringError> {
let method = callee.name();
let constraint = method == "constraint_mode";
let at = call.source_range().start;
let fail = |why: &str| LoweringError::unsupported_at(&format!("{method}(): {why}"), &cx.fn_sym);
let recv = call.call_this_class();
let mut actuals = call.operands();
if recv.is_some() {
actuals.remove(0);
}
let [on_expr] = actuals[..] else {
return Err(fail("expected exactly one argument, the new mode"));
};
if !constraint && let Some(r) = recv.filter(|r| is_rand_handle_property(*r)) {
rand_mode_of_property(cx, at, r, Some(on_expr))?;
return Ok(ProcessCall::Lowered(None));
}
let ty = match recv {
Some(r) => r.expr_type().map(|t| t.canonical()),
None => current_this_var(cx)?.value_type().map(|t| t.canonical()),
}
.ok_or_else(|| fail("the object's class is not known"))?;
let h = match recv {
Some(r) => receiver_handle(cx, r)?,
None => current_this_handle(cx)?,
};
let i64t = cx.ctx.i64_type();
let null_ptr = cx
.ctx
.ptr_type(inkwell::AddressSpace::default())
.const_null();
error_when(
cx,
h,
0,
at,
&format!("null object handle: call of {method}"),
)?;
let on = on_flag(cx, on_expr)?;
let kind = u64::from(constraint);
let on_shifted =
cx.b.build_left_shift(on, i64t.const_int(1, false), "on_shl")
.unwrap();
let ret = obj_call(
cx,
ObjOp::ModeAll,
h,
i64t.const_zero(),
with_flags(cx, on_shifted, kind),
null_ptr,
);
error_when(
cx,
ret,
0,
at,
&format!("null object handle: call of {method}"),
)?;
let levels = class_levels(ty);
if constraint {
let blocks = class_constraint_blocks(&levels);
for (ordinal, blk) in blocks.iter().enumerate().filter(|(_, b)| b.is_static) {
let singleton = static_singleton_handle(cx, levels[blk.home_level])?;
obj_call(
cx,
ObjOp::ConstraintModeSet,
singleton,
i64t.const_int(ordinal as u64, false),
with_flags(cx, on, 0),
null_ptr,
);
}
} else {
for &level in &levels {
let members = classify_members(level)
.map_err(|e| LoweringError::unsupported_at(&e.to_string(), &level))?;
let random: Vec<_> = members
.statics
.iter()
.filter(|s| {
s.kind() == SymbolKind::ClassProperty
&& (s.class_property_rand_mode() != sv_lang::RandMode::None
|| property_is_random(**s))
})
.collect();
if random.is_empty() {
continue;
}
let layout = crate::classes::ObjectLayout::for_statics(level)?;
let singleton = static_singleton_handle(cx, level)?;
for s in random {
let slot = layout.slot_of(s).ok_or_else(|| {
LoweringError::unsupported_at(
"internal: static rand property missing from its class's static layout",
s,
)
})?;
obj_call(
cx,
ObjOp::RandModeSet,
singleton,
i64t.const_int(slot.offset as u64, false),
with_flags(cx, on, 2),
null_ptr,
);
}
}
}
Ok(ProcessCall::Lowered(None))
}