#![allow(dead_code)]
use sv_lang::kinds::ExpressionKind;
use sv_lang::{ArgumentDirection, Expression, SubroutineKind, Symbol};
use sv_sim_sim::sva::ir::{self, CallArg, CallId, Transition};
use super::build::{LeafTable, collect_local_reads};
use super::leaf::validate_boolean_leaf;
use crate::error::LoweringError;
use crate::types::TypeWidth;
#[derive(Clone, Debug)]
pub struct CallSite<'d> {
pub call: Expression<'d>,
pub args: Vec<(CallArg, Expression<'d>)>,
pub spawns: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Callee {
User,
System,
}
pub(super) fn is_call(e: Expression<'_>) -> bool {
e.kind() == ExpressionKind::Call
}
pub(super) fn lower_call<'d>(
a: &mut ir::Automaton,
leaves: &mut LeafTable<'d>,
e: Expression<'d>,
scope: &Symbol<'d>,
) -> Result<Transition, LoweringError> {
let name = e
.call_subroutine_name()
.unwrap_or_else(|| "<unnamed>".to_string());
let callee = classify(e, &name, scope)?;
let spawns = callee == Callee::User
&& e.call_subroutine()
.is_some_and(|sub| sv_sim_sim::process::cfg::subroutine_can_suspend(&sub));
let formals: Vec<Symbol<'d>> = match callee {
Callee::User => e
.call_subroutine()
.ok_or_else(|| {
LoweringError::unsupported_at(
&format!(
"sva automaton builder: the user call `{name}` has no subroutine symbol"
),
scope,
)
})?
.subroutine_arguments()
.collect(),
Callee::System => Vec::new(),
};
let mut actuals = e.operands();
if callee == Callee::User && e.call_this_class().is_some() && !actuals.is_empty() {
actuals.remove(0);
}
if callee == Callee::System
&& !actuals.is_empty()
&& crate::collections::coll_of_expr(&actuals[0]).is_some()
{
actuals.remove(0);
}
if callee == Callee::User && actuals.len() != formals.len() {
return Err(LoweringError::unsupported_at(
&format!(
"sva automaton builder: the call `{name}` has {} actual arguments for {} \
formals (slang binds a defaulted or named argument to its own slot, so they \
must agree)",
actuals.len(),
formals.len()
),
scope,
));
}
let mut args = Vec::with_capacity(actuals.len());
let mut sites = Vec::with_capacity(actuals.len());
for (i, actual) in actuals.iter().enumerate() {
let by_ref = match formals.get(i) {
None => false,
Some(formal) => match formal.formal_argument_direction() {
ArgumentDirection::In => false,
ArgumentDirection::Ref => true,
dir @ (ArgumentDirection::Out | ArgumentDirection::InOut) => {
return Err(LoweringError::unsupported_at(
&format!(
"the call `{name}` is a sequence match item, but its formal \
argument `{}` is {} (LRM 16.11: a subroutine called from a match \
item takes input and ref arguments only)",
formal.name(),
if dir == ArgumentDirection::Out {
"output"
} else {
"inout"
}
),
scope,
));
}
},
};
validate_boolean_leaf(*actual, scope)?;
if !by_ref && is_constant_actual(*actual) {
args.push(CallArg::Const);
} else if by_ref {
let mut syms = Vec::new();
let mut reads_lvalue = false;
collect_local_reads(actual.as_sem_node(), &mut syms, &mut reads_lvalue);
if let Some(local) = syms.first() {
return Err(LoweringError::unsupported_at(
&format!(
"the call `{name}` passes local variable `{}` to a `ref` formal (LRM \
16.11: if a local variable appears in an actual argument expression, \
that argument shall be passed by value)",
local.name()
),
scope,
));
}
args.push(CallArg::Ref);
} else {
let width = actual.expr_type().map_or(1, |t| t.value_width());
let words = leaves.push_arg_leaves(a, *actual, width, scope)?;
args.extend(words.iter().map(|l| CallArg::Value(*l)));
sites.push((CallArg::Value(words[0]), *actual));
continue;
}
sites.push((*args.last().expect("pushed above"), *actual));
}
let id = CallId(leaves.calls.len() as u32);
leaves.calls.push(CallSite {
call: e,
args: sites,
spawns,
});
Ok(Transition::SubroutineCall { call: id, args })
}
fn is_constant_actual(e: Expression<'_>) -> bool {
e.kind() == ExpressionKind::StringLiteral || e.constant_value().is_some()
}
fn classify(e: Expression<'_>, name: &str, scope: &Symbol<'_>) -> Result<Callee, LoweringError> {
let system = e.is_system_call().unwrap_or(false);
let is_task = e.subroutine_kind() == Some(SubroutineKind::Task);
let is_void = e.expr_type().is_some_and(|t| t.is_void());
if !(is_task || is_void) {
return Err(LoweringError::unsupported_at(
&format!(
"the call `{name}` is a sequence match item, but it is a function that returns a \
value (LRM 16.11: only a task or a void function may be called from a match \
item)"
),
scope,
));
}
Ok(if system { Callee::System } else { Callee::User })
}