use crate::symid::SymbolUid;
use sv_lang::{Expression, MethodFlags, Symbol};
use sv_lang_kinds::{ExpressionKind, SymbolKind};
#[derive(Clone)]
pub struct ExternTargets<'d> {
pub proto: Symbol<'d>,
pub impls: Vec<Symbol<'d>>,
pub fork_join: bool,
}
pub fn extern_prototype<'d>(sym: &Symbol<'d>) -> Option<Symbol<'d>> {
let proto = if sym.kind() == SymbolKind::MethodPrototype {
*sym
} else if sym.kind() == SymbolKind::Subroutine {
sym.subroutine_prototype()?
} else {
return None;
};
(proto.kind() == SymbolKind::MethodPrototype
&& proto
.method_prototype_flags()
.contains(MethodFlags::INTERFACE_EXTERN))
.then_some(proto)
}
pub fn extern_targets<'d>(callee: &Symbol<'d>) -> Option<ExternTargets<'d>> {
let proto = extern_prototype(callee)?;
let mut impls = Vec::new();
let mut cur = proto.method_prototype_first_extern_impl();
while let Some(i) = cur {
impls.push(i.implementation());
cur = i.next();
}
impls.reverse();
Some(ExternTargets {
proto,
impls,
fork_join: proto
.method_prototype_flags()
.contains(MethodFlags::FORK_JOIN),
})
}
pub fn extern_impls_in<'d>(body: &Symbol<'d>) -> Vec<Symbol<'d>> {
fn protos_of<'d>(inst: Symbol<'d>, out: &mut Vec<Symbol<'d>>) {
if inst.kind() == SymbolKind::InstanceArray {
for e in inst.members() {
protos_of(e, out);
}
} else if let Some(b) = inst.instance_body() {
out.extend(b.members().filter(|m| extern_prototype(m).is_some()));
}
}
let mut protos = Vec::new();
for port in body.members() {
if port.kind() == SymbolKind::InterfacePort
&& let (Some(inst), _) = port.interface_port_connection()
{
protos_of(inst, &mut protos);
}
}
let mut out: Vec<Symbol<'d>> = Vec::new();
for p in protos {
let mut cur = p.method_prototype_first_extern_impl();
while let Some(i) = cur {
let imp = i.implementation();
if imp.parent().is_some_and(|b| b.uid() == body.uid())
&& !out.iter().any(|o| o.uid() == imp.uid())
{
out.push(imp);
}
cur = i.next();
}
}
out.reverse();
out
}
pub fn force_extern_impl_bodies(design: &mut sv_lang::Design) {
{
let guard = design.unfreeze();
for imp in all_impls(guard.design()) {
imp.visit(|s| {
if let Some(t) = s.declared_type() {
let _ = t.canonical();
}
let _ = s.initializer();
sv_lang::Walk::Continue
});
if let Some(body) = imp.body() {
let mut stack = vec![body.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression()
&& let Some(t) = e.expr_type()
{
let _ = t.canonical();
}
stack.extend(n.children());
}
}
}
}
let eval = design.eval_session();
for imp in all_impls(eval.design()) {
let Some(body) = imp.body() else { continue };
let mut stack = vec![body.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression() {
let _ = eval.eval_constant(e);
}
stack.extend(n.children());
}
}
}
fn all_impls<'d>(design: &'d sv_lang::Design) -> Vec<Symbol<'d>> {
let mut impls = Vec::new();
design.root().visit(|s| {
if s.kind() == SymbolKind::MethodPrototype {
let mut cur = s.method_prototype_first_extern_impl();
while let Some(i) = cur {
impls.push(i.implementation());
cur = i.next();
}
}
sv_lang::Walk::Continue
});
impls
}
pub fn invalid_impl_bodies(design: &sv_lang::Design) -> Vec<(String, usize)> {
let mut impls = Vec::new();
design.root().visit(|s| {
if s.kind() == SymbolKind::MethodPrototype {
let mut cur = s.method_prototype_first_extern_impl();
while let Some(i) = cur {
impls.push(i.implementation());
cur = i.next();
}
}
sv_lang::Walk::Continue
});
let mut out = Vec::new();
for imp in impls {
let Some(body) = imp.body() else { continue };
let mut stack = vec![body.as_sem_node()];
while let Some(n) = stack.pop() {
let bad = n
.as_statement()
.map(|s| {
(s.kind() == sv_lang_kinds::StatementKind::Invalid)
.then(|| s.source_range().start.offset)
})
.or_else(|| {
n.as_expression().map(|e| {
(e.kind() == ExpressionKind::Invalid).then(|| e.source_range().start.offset)
})
})
.flatten();
if let Some(off) = bad {
out.push((imp.hierarchical_path(), off as usize));
break;
}
stack.extend(n.children());
}
}
out
}
pub fn is_vif_extern_call(e: &Expression<'_>) -> bool {
super::vif::call_targets(e).is_some()
&& e.call_subroutine()
.is_some_and(|c| extern_prototype(&c).is_some())
}
pub fn vif_extern_message(e: &Expression<'_>) -> String {
format!(
"call to the interface extern task `{}` through a virtual interface is not supported: \
selecting the implementations of the chosen instance (IEEE 1800-2023 25.7.5, 25.9) \
is not implemented; call it through the interface instance instead",
e.call_subroutine()
.map(|c| c.name().to_string())
.unwrap_or_default()
)
}
pub fn call_extern_targets<'d>(e: &Expression<'d>) -> Option<ExternTargets<'d>> {
if e.kind() != ExpressionKind::Call
|| e.is_system_call() == Some(true)
|| super::vif::call_targets(e).is_some()
{
return None;
}
let mut t = extern_targets(&e.call_subroutine()?)?;
if let Some(route) = module_route(e) {
let routed: Vec<Symbol<'d>> = t
.impls
.iter()
.copied()
.filter(|imp| imp.parent().is_some_and(|b| route.names(&b)))
.collect();
if !routed.is_empty() {
t.impls = routed;
}
}
Some(t)
}
struct ModuleRoute {
segments: Vec<String>,
port: String,
}
impl ModuleRoute {
fn names(&self, body: &Symbol<'_>) -> bool {
let path = body.hierarchical_path();
let want = self.segments.join(".");
(path == want || path.ends_with(&format!(".{want}")))
&& body
.members()
.any(|m| m.kind() == SymbolKind::InterfacePort && m.name() == self.port)
}
}
fn module_route(e: &Expression<'_>) -> Option<ModuleRoute> {
let text = crate::process::cfg::source_text_range(e.source_range())?;
let head = text.split('(').next()?.trim();
let mut parts: Vec<&str> = head.split('.').map(str::trim).collect();
let _method = parts.pop()?;
if parts.len() < 2 {
return None;
}
if !parts.iter().all(|p| {
!p.is_empty()
&& p.chars()
.all(|c| matches!(c, '_' | '$' | '[' | ']') || c.is_ascii_alphanumeric())
}) {
return None;
}
let port = parts.pop()?.to_owned();
Some(ModuleRoute {
segments: parts.into_iter().map(str::to_owned).collect(),
port,
})
}
pub fn is_extern_impl(sub: &Symbol<'_>) -> bool {
sub.kind() == SymbolKind::Subroutine
&& sub.body().is_some()
&& sub
.subroutine_prototype()
.is_some_and(|p| extern_prototype(&p).is_some())
&& extern_prototype(sub)
.and_then(|p| p.method_prototype_subroutine())
.is_none_or(|stub| stub.uid() != sub.uid())
}
pub fn call_callees<'d>(e: &Expression<'d>) -> Vec<Symbol<'d>> {
if e.kind() != ExpressionKind::Call || e.is_system_call() == Some(true) {
return Vec::new();
}
match call_extern_targets(e) {
Some(t) => t.impls,
None => e.call_subroutine().into_iter().collect(),
}
}
pub fn call_target<'d>(e: &Expression<'d>) -> Option<Symbol<'d>> {
match call_extern_targets(e) {
Some(t) => t.impls.into_iter().next(),
None => e.call_subroutine(),
}
}
fn scope_tag(kind: char, inst: &Symbol<'_>, leaf: &str) -> String {
format!("\u{1}ext-{kind}:{}.{leaf}", inst.hierarchical_path())
}
pub fn is_scope_tag(label: &str) -> bool {
label.starts_with('\u{1}')
}
pub fn extern_scope_tags(task: &Symbol<'_>) -> Vec<String> {
let Some(proto) = extern_prototype(task) else {
return Vec::new();
};
fn inst_of<'d>(s: &Symbol<'d>) -> Option<Symbol<'d>> {
s.parent()?.instance_body_parent_instance()
}
let (Some(module), Some(ifc)) = (inst_of(task), inst_of(&proto)) else {
return Vec::new();
};
let leaf = task.name().to_string();
vec![scope_tag('m', &module, &leaf), scope_tag('i', &ifc, &leaf)]
}
pub fn extern_disable_tag(requester: &Symbol<'_>, path: &[String]) -> Option<String> {
let (leaf, hops) = path.split_last()?;
let mut cur = *requester;
let mut via_port_of: Option<Symbol<'_>> = None;
for (k, seg) in hops.iter().enumerate() {
let body = cur.instance_body()?;
let mut next = None;
for m in body.members() {
if m.name() != seg.as_str() {
continue;
}
let kind = m.kind();
if kind == SymbolKind::Instance {
next = Some((m, None));
} else if kind == SymbolKind::InterfacePort
&& let (Some(i), _) = m.interface_port_connection()
&& i.kind() == SymbolKind::Instance
{
next = Some((i, (k > 0).then_some(cur)));
}
}
let (n, via) = next?;
via_port_of = via;
cur = n;
}
if let Some(module) = via_port_of {
return Some(scope_tag('m', &module, leaf));
}
let body = cur.instance_body()?;
body.members()
.any(|m| m.name() == leaf.as_str() && extern_prototype(&m).is_some())
.then(|| scope_tag('i', &cur, leaf))
}