use crate::symid::SymbolUid;
use std::collections::HashSet;
use std::fmt;
use sv_lang::kinds::{ExpressionKind, StatementKind, SymbolKind, TimingControlKind};
use sv_lang::{
EdgeKind, Expression, KnownSystemName, SemNode, Statement, StatementBlockKind, Symbol,
TimingControl, UniquePriorityCheck,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LoweringError {
pub message: String,
}
impl LoweringError {
pub(crate) fn unsupported(what: impl Into<String>) -> Self {
Self {
message: format!("unsupported: {}", what.into()),
}
}
pub(crate) fn malformed(what: impl Into<String>) -> Self {
Self {
message: format!("malformed AST: {}", what.into()),
}
}
pub(crate) fn liveness_oracle_mismatch(symbol_name: impl Into<String>) -> Self {
Self {
message: format!(
"LivenessOracleMismatch: computed live-in symbol {:?} is absent from the \
whole-procedure read_set -- this is a hard bug in the liveness walk, never \
a legitimate program (PR-022)",
symbol_name.into()
),
}
}
}
impl fmt::Display for LoweringError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for LoweringError {}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct BlockId(pub u32);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct SegId(pub u32);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct RepeatCounterId(pub u32);
#[derive(Debug, Clone)]
pub enum SuspendPoint<'d> {
Delay(Expression<'d>, Option<Symbol<'d>>),
Edge {
signal: Expression<'d>,
edge: EdgeKind,
iff: Option<Expression<'d>>,
},
EdgeList(Vec<(Expression<'d>, EdgeKind)>, Vec<Vec<Expression<'d>>>),
Level(Expression<'d>),
CycleDelay {
count: u32,
clock: Symbol<'d>,
edge: EdgeKind,
via: Option<Expression<'d>>,
},
ImplicitComb(Vec<Expression<'d>>),
OneStep,
WaitFork,
ByRequest,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum SuspendPointKind {
Delay,
Edge,
EdgeList,
Level,
CycleDelay,
ImplicitComb,
OneStep,
WaitFork,
ByRequest,
}
impl<'d> SuspendPoint<'d> {
pub fn kind(&self) -> SuspendPointKind {
match self {
SuspendPoint::Delay(..) => SuspendPointKind::Delay,
SuspendPoint::Edge { .. } => SuspendPointKind::Edge,
SuspendPoint::EdgeList(..) => SuspendPointKind::EdgeList,
SuspendPoint::Level(_) => SuspendPointKind::Level,
SuspendPoint::CycleDelay { .. } => SuspendPointKind::CycleDelay,
SuspendPoint::ImplicitComb(_) => SuspendPointKind::ImplicitComb,
SuspendPoint::OneStep => SuspendPointKind::OneStep,
SuspendPoint::WaitFork => SuspendPointKind::WaitFork,
SuspendPoint::ByRequest => SuspendPointKind::ByRequest,
}
}
}
pub fn suspending_statement_kinds() -> &'static [SuspendPointKind] {
&[
SuspendPointKind::Delay,
SuspendPointKind::Edge,
SuspendPointKind::EdgeList,
SuspendPointKind::Level,
SuspendPointKind::CycleDelay,
SuspendPointKind::ImplicitComb,
SuspendPointKind::OneStep,
SuspendPointKind::WaitFork,
SuspendPointKind::ByRequest,
]
}
#[derive(Debug, Clone)]
pub enum Terminator<'d> {
Fallthrough(BlockId),
Switch {
on: SwitchOn<'d>,
arms: Vec<BlockId>,
default_blk: BlockId,
report: Option<sv_lang::SourceLoc>,
},
Branch {
cond: Expression<'d>,
then_blk: BlockId,
else_blk: BlockId,
},
Assertion {
stmt: Statement<'d>,
pass_blk: BlockId,
fail_blk: BlockId,
done_blk: BlockId,
},
Suspend {
point: SuspendPoint<'d>,
resume_blk: BlockId,
},
End { loop_back: Option<BlockId> },
Loop {
header: BlockId,
body: BlockId,
exit: BlockId,
entry: BlockId,
},
ForkJoin {
kind: StatementBlockKind,
branches: Vec<ProcessCfg<'d>>,
join_blk: BlockId,
},
RepeatCheck {
counter: RepeatCounterId,
then_blk: BlockId,
else_blk: BlockId,
},
Case {
stmt: Statement<'d>,
arms: Vec<BlockId>,
default_blk: BlockId,
},
}
#[derive(Debug, Clone)]
pub enum ArgBinding<'d> {
In {
formal: Symbol<'d>,
actual: Expression<'d>,
},
Init { var: Symbol<'d> },
Out {
actual: Expression<'d>,
formal: Symbol<'d>,
},
Const { var: Symbol<'d>, value: i64 },
ThisOf { var: Symbol<'d> },
Release { var: Symbol<'d> },
Step { var: Symbol<'d>, by: i64 },
AssocKey {
var: Symbol<'d>,
arr: Expression<'d>,
path: Vec<NestStep<'d>>,
next: bool,
},
Capture { temp: u32, value: Expression<'d> },
AssignCaptured {
temp: u32,
target: Expression<'d>,
nba: bool,
},
FromCaptured { temp: u32, formal: Symbol<'d> },
ToCaptured { temp: u32, formal: Symbol<'d> },
CallResult { temp: u32, call: Expression<'d> },
ClassEventLeave,
CaptureThis { temp: u32 },
RefOpen {
temp: u32,
formal: Symbol<'d>,
actual: Expression<'d>,
},
RefClose {
temp: u32,
formal: Symbol<'d>,
actual: Expression<'d>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InheritedSlot {
Temp { parent: u32, child: u32 },
Counter {
parent: RepeatCounterId,
child: RepeatCounterId,
},
Return { parent: u32, child: u32 },
}
#[derive(Clone)]
pub enum SwitchOn<'d> {
FiredTerm,
FiredBit(u32),
ClassFired(Option<u32>),
VifId(Expression<'d>),
RandCase(Statement<'d>),
ObjClass {
receiver: Option<Expression<'d>>,
ids: Vec<Vec<u32>>,
},
NoTarget { what: String },
FnDisable { labels: Vec<String> },
}
impl std::fmt::Debug for SwitchOn<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SwitchOn::NoTarget { what } => write!(f, "NoTarget({what})"),
SwitchOn::FnDisable { labels } => write!(f, "FnDisable({labels:?})"),
SwitchOn::FiredTerm => write!(f, "FiredTerm"),
SwitchOn::FiredBit(k) => write!(f, "FiredBit({k})"),
SwitchOn::ClassFired(k) => write!(f, "ClassFired({k:?})"),
SwitchOn::VifId(e) => write!(f, "VifId({:?})", e.kind()),
SwitchOn::RandCase(_) => write!(f, "RandCase"),
SwitchOn::ObjClass { ids, .. } => write!(f, "ObjClass({ids:?})"),
}
}
}
impl<'d> SwitchOn<'d> {
pub fn reads(&self) -> Vec<Expression<'d>> {
match self {
SwitchOn::FiredTerm
| SwitchOn::FiredBit(_)
| SwitchOn::ClassFired(_)
| SwitchOn::NoTarget { .. }
| SwitchOn::FnDisable { .. } => Vec::new(),
SwitchOn::VifId(e) => vec![*e],
SwitchOn::RandCase(s) => s.randcase_items().iter().map(|i| i.expr()).collect(),
SwitchOn::ObjClass { receiver, .. } => receiver.iter().copied().collect(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct NestStep<'d> {
pub var: Symbol<'d>,
pub fixed: Option<(i32, i32)>,
pub assoc: bool,
}
#[derive(Debug, Clone)]
pub enum RepeatCount<'d> {
Expr(Expression<'d>),
Const(u64),
Size(Expression<'d>),
NestedSize {
arr: Expression<'d>,
path: Vec<NestStep<'d>>,
},
Inherited(Expression<'d>),
}
impl<'d> RepeatCount<'d> {
pub fn expr(&self) -> Option<Expression<'d>> {
match self {
RepeatCount::Expr(e) | RepeatCount::Size(e) => Some(*e),
RepeatCount::NestedSize { arr, .. } => Some(*arr),
RepeatCount::Const(_) | RepeatCount::Inherited(_) => None,
}
}
}
#[derive(Debug, Clone)]
pub struct BasicBlock<'d> {
pub bindings: Vec<ArgBinding<'d>>,
pub this_var: Option<Symbol<'d>>,
pub ref_aliases: Vec<(Symbol<'d>, Symbol<'d>)>,
pub stmts: Vec<Statement<'d>>,
pub probes: Vec<(usize, Statement<'d>)>,
pub disable_probes: Vec<(usize, Statement<'d>)>,
pub frame_probes: Vec<(usize, FrameProbe<'d>)>,
pub block_events: Vec<(usize, Symbol<'d>, bool)>,
pub stmt_scopes: Vec<(usize, Symbol<'d>)>,
pub step_exprs: Vec<Expression<'d>>,
pub repeat_init: Option<(RepeatCounterId, RepeatCount<'d>)>,
pub repeat_decrement: Option<RepeatCounterId>,
pub expect_resume: bool,
pub terminator: Terminator<'d>,
}
#[derive(Debug, Clone)]
pub struct ProcessCfg<'d> {
pub blocks: Vec<BasicBlock<'d>>,
pub entry: BlockId,
pub body: Statement<'d>,
pub inlined_locals: Vec<Symbol<'d>>,
pub captures: Vec<Expression<'d>>,
pub captured: Vec<Symbol<'d>>,
pub ref_bindings: Vec<(Symbol<'d>, Symbol<'d>)>,
pub disable_names: Vec<String>,
pub fork_branch_names: Vec<String>,
pub disable_scopes: Vec<(BlockId, String, BlockId)>,
pub disable_broadcast: Vec<(String, bool)>,
pub inherited: Vec<InheritedSlot>,
pub capture_types: Vec<Option<Symbol<'d>>>,
pub recursion: Recursion,
pub share_id: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Recursion {
None,
Root,
SelfSpawn,
AncestorSpawn(u32),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcessLoopKind {
OneShot,
Looping,
}
pub fn build_cfg<'d>(
body: &Statement<'d>,
loop_kind: ProcessLoopKind,
) -> Result<ProcessCfg<'d>, LoweringError> {
build_cfg_in(
body,
loop_kind,
None,
None,
None,
None,
Inherited::default(),
None,
)
}
pub fn build_export_task_cfg<'d>(
task: &Symbol<'d>,
global_clock: Option<InferredClockEvent<'d>>,
) -> Result<ProcessCfg<'d>, LoweringError> {
let body = proc_body(task).ok_or_else(|| {
LoweringError::malformed(format!("exported task `{}` has no body", task.name()))
})?;
let mut b = CfgBuilder::new();
b.global_clock = global_clock;
let entry = b.alloc_block();
b.rec_outer.push(task.uid());
for f in task.subroutine_arguments() {
if !crate::netlist::is_static_procedural_var(&f) {
b.inlined_locals.push(f);
}
}
for local in task_locals(task) {
if crate::netlist::is_static_procedural_var(&local) {
continue;
}
if !is_automatic_block_var(&local) {
b.blocks[entry.0 as usize]
.bindings
.push(ArgBinding::Init { var: local });
}
b.inlined_locals.push(local);
}
for v in crate::patterns::pattern_vars(body.as_sem_node()) {
if !b.inlined_locals.iter().any(|l| l.uid() == v.uid()) {
b.inlined_locals.push(v);
}
}
let after = b.alloc_block();
b.target_depth.insert(after, 0);
b.return_targets.push(after);
let exit = b.walk_stmt(body, entry)?;
b.close(exit, Terminator::Fallthrough(after));
b.close(after, Terminator::End { loop_back: None });
let inlined_locals = std::mem::take(&mut b.inlined_locals);
let captures = std::mem::take(&mut b.captures);
let capture_types = std::mem::take(&mut b.capture_types);
let ref_bindings = b.take_ref_bindings();
let disable_scopes = b.take_disable_scopes();
let disable_broadcast = std::mem::take(&mut b.disable_broadcast);
let fork_branch_names = std::mem::take(&mut b.fork_branch_names);
Ok(ProcessCfg {
blocks: b.finish(),
entry,
body,
inlined_locals,
captures,
captured: Vec::new(),
ref_bindings,
disable_names: Vec::new(),
fork_branch_names,
disable_scopes,
disable_broadcast,
inherited: Vec::new(),
capture_types,
recursion: Recursion::None,
share_id: None,
})
}
pub fn build_match_item_task_cfg<'d>(
call: Expression<'d>,
) -> Result<ProcessCfg<'d>, LoweringError> {
let task = call.call_subroutine().ok_or_else(|| {
LoweringError::malformed("a match-item call of a task has no subroutine symbol")
})?;
let body = proc_body(&task)
.ok_or_else(|| LoweringError::malformed(format!("task `{}` has no body", task.name())))?;
let mut b = CfgBuilder::new();
let entry = b.alloc_block();
let exit = b.inline_task_call(call, task, entry)?;
b.close(exit, Terminator::End { loop_back: None });
let inlined_locals = std::mem::take(&mut b.inlined_locals);
let captures = std::mem::take(&mut b.captures);
let capture_types = std::mem::take(&mut b.capture_types);
let ref_bindings = b.take_ref_bindings();
let disable_scopes = b.take_disable_scopes();
let disable_broadcast = std::mem::take(&mut b.disable_broadcast);
let fork_branch_names = std::mem::take(&mut b.fork_branch_names);
Ok(ProcessCfg {
blocks: b.finish(),
entry,
body,
inlined_locals,
captures,
captured: Vec::new(),
ref_bindings,
disable_names: Vec::new(),
fork_branch_names,
disable_scopes,
disable_broadcast,
inherited: Vec::new(),
capture_types,
recursion: Recursion::None,
share_id: None,
})
}
pub type InferredClockEvent<'d> = (Expression<'d>, EdgeKind, Option<Expression<'d>>);
pub fn is_global_clock_call(signal: &Expression<'_>) -> bool {
signal.kind() == ExpressionKind::Call
&& signal.is_system_call() == Some(true)
&& signal
.system_subroutine()
.is_some_and(|s| s.known_name_id() == sv_lang::KnownSystemName::GlobalClock)
}
pub fn is_inferred_clock_call(signal: &Expression<'_>) -> bool {
signal.kind() == ExpressionKind::Call
&& signal.is_system_call() == Some(true)
&& signal
.system_subroutine()
.is_some_and(|s| s.known_name_id() == sv_lang::KnownSystemName::InferredClock)
}
fn drive_clocking_of<'d>(mut target: Expression<'d>) -> Option<Symbol<'d>> {
while matches!(
target.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
) {
target = target.select_value()?;
}
let var = target.referenced_symbol()?;
if var.kind() != SymbolKind::ClockVar {
return None;
}
var.parent()
.filter(|p| p.kind() == SymbolKind::ClockingBlock)
}
fn drive_via_of<'d>(mut target: Expression<'d>) -> Option<Expression<'d>> {
while matches!(
target.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
) {
target = target.select_value()?;
}
(crate::netlist::vif::is_member_access(&target)).then_some(target)
}
pub fn build_cfg_clocked<'d>(
body: &Statement<'d>,
loop_kind: ProcessLoopKind,
default_clocking: Option<Symbol<'d>>,
) -> Result<ProcessCfg<'d>, LoweringError> {
build_cfg_scoped(body, loop_kind, default_clocking, &[], None)
}
pub fn build_cfg_scoped<'d>(
body: &Statement<'d>,
loop_kind: ProcessLoopKind,
default_clocking: Option<Symbol<'d>>,
peers: &[PeerBlock],
inferred_clock: Option<InferredClockEvent<'d>>,
) -> Result<ProcessCfg<'d>, LoweringError> {
build_cfg_in(
body,
loop_kind,
None,
default_clocking,
Some(peers),
inferred_clock,
Inherited::default(),
None,
)
}
pub fn build_cfg_scoped_with_virtuals<'d>(
body: &Statement<'d>,
loop_kind: ProcessLoopKind,
default_clocking: Option<Symbol<'d>>,
peers: &[PeerBlock],
inferred_clock: Option<InferredClockEvent<'d>>,
global_clock: Option<InferredClockEvent<'d>>,
virtual_tasks: Option<std::rc::Rc<VirtualTasks<'d>>>,
) -> Result<ProcessCfg<'d>, LoweringError> {
build_cfg_in(
body,
loop_kind,
None,
default_clocking,
Some(peers),
inferred_clock,
Inherited {
global_clock,
..Inherited::default()
},
virtual_tasks,
)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PeerBlock {
pub name: String,
pub can_suspend: bool,
pub fork_branch: bool,
}
pub fn outermost_block<'d>(body: &Statement<'d>) -> Option<Statement<'d>> {
let mut cur = *body;
loop {
let kind = cur.kind();
if kind == StatementKind::Block {
return Some(cur);
}
if kind == StatementKind::Timed {
cur = cur.body()?;
} else if kind == StatementKind::List {
let list = live_statements(&cur);
let [only] = list.as_slice() else {
return None;
};
cur = *only;
} else {
return None;
}
}
}
pub fn peer_block<'d>(body: &Statement<'d>) -> Option<PeerBlock> {
let block = outermost_block(body)?;
let name = block_name(&block);
if name.is_empty() {
return None;
}
Some(PeerBlock {
name,
can_suspend: contains_suspend_trigger_in(
block.as_sem_node(),
&mut std::collections::HashSet::new(),
true,
false,
),
fork_branch: false,
})
}
pub fn named_block_paths(body: &Statement<'_>) -> Vec<String> {
let mut out = Vec::new();
let mut stack: Vec<(SemNode<'_>, String)> = vec![(body.as_sem_node(), String::new())];
while let Some((n, prefix)) = stack.pop() {
let mut here = prefix.clone();
if let Some(s) = n.as_statement()
&& s.kind() == StatementKind::Block
{
let name = block_name(&s);
if !name.is_empty() {
here = if prefix.is_empty() {
name
} else {
format!("{prefix}.{name}")
};
out.push(here.clone());
}
}
stack.extend(live_children(&n).into_iter().map(|c| (c, here.clone())));
}
out
}
pub fn nested_peer_blocks<'d>(body: &Statement<'d>) -> Vec<PeerBlock> {
let outermost = outermost_block(body).map(|b| b.source_range().start.offset);
let mut out = Vec::new();
let mut stack = vec![body.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(s) = n.as_statement()
&& s.kind() == StatementKind::Block
{
match s.block_kind() {
Some(StatementBlockKind::Sequential) => {
let name = block_name(&s);
if !name.is_empty() && outermost != Some(s.source_range().start.offset) {
out.push(PeerBlock {
name,
can_suspend: contains_suspend_trigger_in(
n,
&mut std::collections::HashSet::new(),
true,
false,
),
fork_branch: false,
});
}
}
Some(
StatementBlockKind::JoinAll
| StatementBlockKind::JoinAny
| StatementBlockKind::JoinNone,
) => {
fork_branch_peers(n, &mut out);
continue;
}
None => {}
}
}
stack.extend(live_children(&n));
}
out
}
fn fork_branch_peers(fork: SemNode<'_>, out: &mut Vec<PeerBlock>) {
let mut branches = fork.children();
if let [only] = branches.as_slice()
&& only
.as_statement()
.is_some_and(|s| s.kind() == StatementKind::List)
{
branches = only.children();
}
for b in branches {
let Some(s) = b.as_statement() else { continue };
if let Some(mut p) = peer_block(&s) {
p.can_suspend = true;
p.fork_branch = true;
out.push(p);
}
out.extend(nested_peer_blocks(&s));
}
}
impl ProcessCfg<'_> {
pub fn branch_disable_labels(&self) -> Vec<String> {
let mut out = Vec::new();
for b in &self.blocks {
if let Terminator::ForkJoin { branches, .. } = &b.terminator {
for br in branches {
out.extend(br.disable_scopes.iter().map(|(_, l, _)| l.clone()));
out.extend(br.branch_disable_labels());
}
}
}
out
}
pub fn branch_disable_broadcast(&self) -> Vec<(String, bool)> {
let mut out = Vec::new();
for b in &self.blocks {
if let Terminator::ForkJoin { branches, .. } = &b.terminator {
for br in branches {
out.extend(br.disable_broadcast.iter().cloned());
out.extend(br.branch_disable_broadcast());
}
}
}
out
}
}
pub fn assertion_labels<'d>(body: &Statement<'d>) -> Vec<(String, Statement<'d>)> {
let mut out = Vec::new();
let mut stack = vec![body.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(s) = n.as_statement()
&& s.kind() == StatementKind::Block
&& let name = block_name(&s)
&& !name.is_empty()
&& let [only] = s.statements().as_slice()
&& only.kind() == StatementKind::ConcurrentAssertion
&& !is_expect(only)
{
out.push((name, *only));
}
stack.extend(live_children(&n));
}
out
}
pub fn deferred_assertion_labels<'d>(body: &Statement<'d>) -> Vec<(String, Statement<'d>)> {
let mut out = Vec::new();
let mut stack = vec![body.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(s) = n.as_statement()
&& s.kind() == StatementKind::Block
&& let name = block_name(&s)
&& !name.is_empty()
&& let [only] = s.statements().as_slice()
&& only.kind() == StatementKind::ImmediateAssertion
&& only.is_deferred_assertion() == Some(true)
{
out.push((name, *only));
}
stack.extend(live_children(&n));
}
out
}
pub fn immediate_assertion_labels(body: &Statement<'_>) -> Vec<String> {
let mut out = Vec::new();
let mut stack = vec![body.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(s) = n.as_statement()
&& s.kind() == StatementKind::Block
&& let name = block_name(&s)
&& !name.is_empty()
&& let [only] = s.statements().as_slice()
&& only.kind() == StatementKind::ImmediateAssertion
&& only.is_deferred_assertion() != Some(true)
{
out.push(name);
}
stack.extend(live_children(&n));
}
out
}
pub fn self_disabling_assertion_block(stmt: &Statement<'_>) -> bool {
if stmt.kind() != StatementKind::Block {
return false;
}
let name = block_name(stmt);
if name.is_empty() {
return false;
}
let children = stmt.statements();
let [only] = children.as_slice() else {
return false;
};
if only.kind() != StatementKind::ImmediateAssertion {
return false;
}
let mut disables = 0usize;
let mut stack = vec![only.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(s) = n.as_statement()
&& s.kind() == StatementKind::Disable
{
match disable_label(&s) {
Ok(l) if l == name => disables += 1,
_ => return false,
}
}
stack.extend(live_children(&n));
}
disables > 0
&& !contains_suspend_trigger_in(
only.as_sem_node(),
&mut std::collections::HashSet::new(),
true,
false,
)
}
pub fn build_cfg_hosted_assertion<'d>(
stmt: &Statement<'d>,
default_clocking: Option<Symbol<'d>>,
) -> Result<ProcessCfg<'d>, LoweringError> {
let mut builder = CfgBuilder::new();
builder.default_clocking = default_clocking;
builder.expect_as_leaf = true;
let entry = builder.alloc_block();
let exit = builder.walk_stmt(*stmt, entry)?;
builder.close(exit, Terminator::End { loop_back: None });
Ok(ProcessCfg {
blocks: builder.finish(),
entry,
body: *stmt,
inlined_locals: Vec::new(),
captures: Vec::new(),
captured: Vec::new(),
ref_bindings: Vec::new(),
disable_names: Vec::new(),
fork_branch_names: Vec::new(),
disable_scopes: Vec::new(),
disable_broadcast: Vec::new(),
inherited: Vec::new(),
capture_types: Vec::new(),
recursion: Recursion::None,
share_id: None,
})
}
pub type SuperDetector = fn(Expression<'_>) -> bool;
thread_local! {
static EXPLICIT_SUPER: std::cell::Cell<Option<SuperDetector>> =
const { std::cell::Cell::new(None) };
}
pub fn set_explicit_super_detector(f: Option<SuperDetector>) {
EXPLICIT_SUPER.with(|c| c.set(f));
}
fn is_explicit_super(call: Expression<'_>) -> bool {
EXPLICIT_SUPER.with(|c| c.get().is_some_and(|f| f(call)))
}
pub struct VirtualTasks<'d> {
pub classes: Vec<(u32, sv_lang::Type<'d>)>,
chains: std::cell::OnceCell<Vec<ClassChain<'d>>>,
self_reach: std::cell::RefCell<std::collections::HashMap<crate::symid::SymbolId, bool>>,
}
struct ClassChain<'d> {
chain: Vec<sv_lang::Type<'d>>,
keys: std::collections::HashSet<crate::symid::SymbolId>,
}
thread_local! {
static SUSPENDING_VIRTUALS: std::cell::RefCell<std::collections::HashSet<crate::symid::SymbolId>> =
std::cell::RefCell::new(std::collections::HashSet::new());
}
fn is_suspending_virtual(sub: &Symbol<'_>) -> bool {
SUSPENDING_VIRTUALS.with(|s| s.borrow().contains(&sub.uid()))
}
pub fn set_suspending_virtuals(vt: &VirtualTasks<'_>) {
SUSPENDING_VIRTUALS.with(|s| s.borrow_mut().clear());
let mut methods: Vec<(crate::symid::SymbolId, Symbol<'_>)> = Vec::new();
for (_, ty) in &vt.classes {
for m in ty.as_symbol().members() {
let m = if m.kind() == SymbolKind::MethodPrototype {
match m.method_prototype_subroutine() {
Some(real) => real,
None => continue,
}
} else {
m
};
if m.kind() == SymbolKind::Subroutine
&& (m.subroutine_this_var().is_some()
|| m.subroutine_flags().contains(sv_lang::MethodFlags::PURE))
&& (m.subroutine_flags().contains(sv_lang::MethodFlags::VIRTUAL)
|| m.subroutine_override().is_some())
&& !methods.iter().any(|(_, x)| *x == m)
{
methods.push((m.uid(), m));
}
}
}
loop {
let mut changed = false;
for (uid, m) in &methods {
if SUSPENDING_VIRTUALS.with(|s| s.borrow().contains(uid)) {
continue;
}
if vt
.implementations(m)
.iter()
.any(|(_, imp)| subroutine_can_suspend(imp))
{
SUSPENDING_VIRTUALS.with(|s| s.borrow_mut().insert(uid.clone()));
changed = true;
}
}
if !changed {
break;
}
}
}
impl<'d> VirtualTasks<'d> {
pub fn new(classes: Vec<(u32, sv_lang::Type<'d>)>) -> Self {
VirtualTasks {
classes,
chains: std::cell::OnceCell::new(),
self_reach: std::cell::RefCell::new(std::collections::HashMap::new()),
}
}
pub fn implementations(&self, method: &Symbol<'d>) -> Vec<(Vec<u32>, Symbol<'d>)> {
let Some(decl) = method.parent() else {
return Vec::new();
};
let name = method.name();
let decl_key = decl.uid();
let chains = self.chains.get_or_init(|| {
self.classes
.iter()
.map(|(_, ty)| {
let mut chain = crate::netlist::base_chain(*ty);
chain.push(*ty);
let keys = chain.iter().map(|c| c.as_symbol().uid()).collect();
ClassChain { chain, keys }
})
.collect()
});
let mut out: Vec<(Vec<u32>, Symbol<'d>)> = Vec::new();
for ((id, _), cc) in self.classes.iter().zip(chains) {
if !cc.keys.contains(&decl_key) {
continue;
}
let found = cc.chain.iter().rev().find_map(|c| {
let s = c.as_symbol().find(name)?;
let s = if s.kind() == SymbolKind::MethodPrototype {
s.method_prototype_subroutine()?
} else {
s
};
(s.kind() == SymbolKind::Subroutine
&& s.body().is_some()
&& !s.subroutine_flags().contains(sv_lang::MethodFlags::PURE))
.then_some(s)
});
let Some(imp) = found else { continue };
match out.iter_mut().find(|(_, s)| *s == imp) {
Some((ids, _)) => ids.push(*id),
None => out.push((vec![*id], imp)),
}
}
out
}
}
#[allow(clippy::too_many_arguments)]
fn build_cfg_in<'d>(
body: &Statement<'d>,
loop_kind: ProcessLoopKind,
fork_name: Option<String>,
default_clocking: Option<Symbol<'d>>,
peers: Option<&[PeerBlock]>,
checker_clock: Option<InferredClockEvent<'d>>,
inherited: Inherited<'d>,
virtual_tasks: Option<std::rc::Rc<VirtualTasks<'d>>>,
) -> Result<ProcessCfg<'d>, LoweringError> {
let mut disable_names: Vec<String> = Vec::new();
disable_names.extend(fork_name.clone());
disable_names.extend(peer_block(body).map(|p| p.name));
let mut builder = CfgBuilder::new();
builder.this_stack.extend(inherited.this_var);
builder.ref_stack = inherited.ref_aliases;
builder.global_clock = inherited.global_clock;
builder.detached_trigger = inherited.detached_trigger;
builder.forced_extern = inherited.forced_extern;
builder.rec_outer = inherited.rec_outer;
if let Some(memo) = inherited.fork_memo {
builder.fork_memo = memo;
}
if let Some(rec) = inherited.rec {
for _ in 0..rec.groups {
for &(a, f) in &rec.proto {
builder.set_capture_type(a, f);
}
}
builder.rec = Some(rec);
}
builder.rec_up = inherited.rec_up;
builder.virtual_tasks = virtual_tasks;
builder.fork_name = fork_name;
builder.fork_branch_names = inherited.known_fork_labels;
builder.disable_names_extra(&inherited.ancestor_blocks, &mut disable_names);
builder.body_node = Some(body.as_sem_node());
builder.ancestor_blocks = inherited.ancestor_blocks;
builder.default_clocking = default_clocking;
builder.checker_clock = checker_clock;
if let Some(peers) = peers {
builder.peers = peers.to_vec();
builder.assertion_labels = assertion_labels(body)
.into_iter()
.map(|(name, _)| name)
.collect();
builder.deferred_labels = deferred_assertion_labels(body)
.into_iter()
.map(|(name, _)| name)
.collect();
builder.immediate_labels = immediate_assertion_labels(body);
builder.outermost = peer_block(body).map(|p| p.name);
}
let entry = builder.alloc_block();
let exit = builder.walk_stmt(*body, entry)?;
let loop_back = match loop_kind {
ProcessLoopKind::OneShot => None,
ProcessLoopKind::Looping => Some(entry),
};
builder.close(exit, Terminator::End { loop_back });
let ref_bindings = builder.take_ref_bindings();
let inlined_locals = std::mem::take(&mut builder.inlined_locals);
let captures = std::mem::take(&mut builder.captures);
let capture_types = std::mem::take(&mut builder.capture_types);
let fork_branch_names = std::mem::take(&mut builder.fork_branch_names);
let disable_scopes = builder.take_disable_scopes();
let disable_broadcast = std::mem::take(&mut builder.disable_broadcast);
Ok(ProcessCfg {
blocks: builder.finish(),
entry,
body: *body,
inlined_locals,
captures,
captured: Vec::new(),
ref_bindings,
disable_names,
fork_branch_names,
disable_scopes,
disable_broadcast,
inherited: Vec::new(),
capture_types,
recursion: Recursion::None,
share_id: None,
})
}
#[derive(Clone)]
struct RecursiveTask<'d> {
task: crate::symid::SymbolId,
share: u64,
proto: Vec<(Expression<'d>, Symbol<'d>)>,
n: u32,
groups: u32,
outs: Vec<bool>,
refs: Vec<Option<Symbol<'d>>>,
has_this: bool,
body: Statement<'d>,
}
#[derive(PartialEq, Eq, Hash)]
struct ForkBranchKey {
stmt: (usize, u16),
fork_name: Option<String>,
default_clocking: Option<crate::symid::SymbolId>,
this_var: Option<crate::symid::SymbolId>,
ref_aliases: Vec<(crate::symid::SymbolId, crate::symid::SymbolId)>,
rec_outer: Vec<crate::symid::SymbolId>,
labels: Vec<String>,
ancestors: Vec<String>,
}
#[derive(PartialEq, Eq, Hash)]
struct RootKey {
task: crate::symid::SymbolId,
this_var: Option<crate::symid::SymbolId>,
default_clocking: Option<crate::symid::SymbolId>,
rec_outer: Vec<crate::symid::SymbolId>,
chain: Vec<u64>,
ref_aliases: Vec<(crate::symid::SymbolId, crate::symid::SymbolId)>,
labels: Vec<String>,
ancestors: Vec<String>,
}
#[derive(PartialEq, Eq, Hash)]
enum MemoKey {
Fork(ForkBranchKey),
Root(RootKey),
}
type ForkMemo<'d> =
std::rc::Rc<std::cell::RefCell<std::collections::HashMap<MemoKey, ProcessCfg<'d>>>>;
static NEXT_SHARE_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
fn stmt_addr(s: &Statement<'_>) -> usize {
unsafe { *std::ptr::from_ref(s).cast::<usize>() }
}
#[derive(Default)]
struct Inherited<'d> {
this_var: Option<Symbol<'d>>,
ref_aliases: Vec<(Symbol<'d>, Symbol<'d>)>,
global_clock: Option<InferredClockEvent<'d>>,
detached_trigger: bool,
forced_extern: Option<Symbol<'d>>,
rec_outer: Vec<crate::symid::SymbolId>,
fork_memo: Option<ForkMemo<'d>>,
rec: Option<RecursiveTask<'d>>,
rec_up: Vec<RecursiveTask<'d>>,
known_fork_labels: Vec<String>,
ancestor_blocks: Vec<String>,
}
#[derive(Clone, Debug)]
pub enum FrameProbe<'d> {
Enter {
task: Symbol<'d>,
vars: Vec<Symbol<'d>>,
bound: Vec<(Symbol<'d>, Expression<'d>)>,
},
Exit {
task: Symbol<'d>,
vars: Vec<Symbol<'d>>,
},
}
struct PartialBlock<'d> {
bindings: Vec<ArgBinding<'d>>,
this_var: Option<Symbol<'d>>,
ref_aliases: Vec<(Symbol<'d>, Symbol<'d>)>,
stmts: Vec<Statement<'d>>,
probes: Vec<(usize, Statement<'d>)>,
disable_probes: Vec<(usize, Statement<'d>)>,
frame_probes: Vec<(usize, FrameProbe<'d>)>,
block_events: Vec<(usize, Symbol<'d>, bool)>,
stmt_scopes: Vec<(usize, Symbol<'d>)>,
step_exprs: Vec<Expression<'d>>,
repeat_init: Option<(RepeatCounterId, RepeatCount<'d>)>,
repeat_decrement: Option<RepeatCounterId>,
expect_resume: bool,
terminator: Option<Terminator<'d>>,
}
struct CfgBuilder<'d> {
blocks: Vec<PartialBlock<'d>>,
inferred_clock: Option<Option<(Expression<'d>, EdgeKind)>>,
next_repeat_counter: u32,
inlined_locals: Vec<Symbol<'d>>,
ref_candidates: Vec<(Symbol<'d>, Option<Symbol<'d>>)>,
entering_block: bool,
call_anchor: Option<Expression<'d>>,
pending_result_temp: Option<(u32, Expression<'d>)>,
return_targets: Vec<BlockId>,
ret_var_stack: Vec<Option<Symbol<'d>>>,
this_stack: Vec<Symbol<'d>>,
ref_stack: Vec<(Symbol<'d>, Symbol<'d>)>,
virtual_tasks: Option<std::rc::Rc<VirtualTasks<'d>>>,
capture_types: Vec<Option<Symbol<'d>>>,
inline_stack: Vec<crate::symid::SymbolId>,
rec: Option<RecursiveTask<'d>>,
rec_up: Vec<RecursiveTask<'d>>,
rec_outer: Vec<crate::symid::SymbolId>,
loop_targets: Vec<(BlockId, BlockId)>,
owned_scopes: Vec<Vec<Symbol<'d>>>,
target_depth: std::collections::HashMap<BlockId, usize>,
disable_targets: Vec<(String, BlockId, usize)>,
body_node: Option<SemNode<'d>>,
fork_block_names: Vec<String>,
body_forks: Option<bool>,
event_stack: Vec<(Symbol<'d>, usize)>,
frame_stack: Vec<(Symbol<'d>, Vec<Symbol<'d>>)>,
scope_chain: Vec<(String, BlockId, usize)>,
name_path: Vec<String>,
block_scopes: Vec<Vec<(String, BlockId, usize)>>,
block_frames: Vec<Vec<(Symbol<'d>, Vec<Symbol<'d>>)>>,
disable_broadcast: Vec<(String, bool)>,
fork_name: Option<String>,
fork_branch_names: Vec<String>,
ancestor_blocks: Vec<String>,
outermost: Option<String>,
assertion_labels: Vec<String>,
deferred_labels: Vec<String>,
immediate_labels: Vec<String>,
peers: Vec<PeerBlock>,
captures: Vec<Expression<'d>>,
default_clocking: Option<Symbol<'d>>,
drive_clocking: Option<Symbol<'d>>,
drive_via: Option<Expression<'d>>,
allow_dynamic_cycle: bool,
skip_class_event_terms: bool,
dynamic_cycle_count: Option<Expression<'d>>,
checker_clock: Option<InferredClockEvent<'d>>,
global_clock: Option<InferredClockEvent<'d>>,
expect_as_leaf: bool,
detached_trigger: bool,
fork_memo: ForkMemo<'d>,
forced_extern: Option<Symbol<'d>>,
}
impl<'d> CfgBuilder<'d> {
fn new() -> Self {
Self {
expect_as_leaf: false,
detached_trigger: false,
fork_memo: ForkMemo::default(),
forced_extern: None,
blocks: Vec::new(),
inferred_clock: None,
next_repeat_counter: 0,
inlined_locals: Vec::new(),
ref_candidates: Vec::new(),
entering_block: false,
return_targets: Vec::new(),
ret_var_stack: Vec::new(),
this_stack: Vec::new(),
ref_stack: Vec::new(),
virtual_tasks: None,
inline_stack: Vec::new(),
capture_types: Vec::new(),
rec: None,
rec_up: Vec::new(),
rec_outer: Vec::new(),
loop_targets: Vec::new(),
owned_scopes: Vec::new(),
target_depth: std::collections::HashMap::new(),
disable_targets: Vec::new(),
body_node: None,
fork_block_names: Vec::new(),
body_forks: None,
event_stack: Vec::new(),
call_anchor: None,
pending_result_temp: None,
frame_stack: Vec::new(),
scope_chain: Vec::new(),
name_path: Vec::new(),
block_scopes: Vec::new(),
block_frames: Vec::new(),
disable_broadcast: Vec::new(),
ancestor_blocks: Vec::new(),
fork_name: None,
fork_branch_names: Vec::new(),
outermost: None,
assertion_labels: Vec::new(),
deferred_labels: Vec::new(),
immediate_labels: Vec::new(),
peers: Vec::new(),
captures: Vec::new(),
default_clocking: None,
drive_clocking: None,
drive_via: None,
allow_dynamic_cycle: false,
skip_class_event_terms: false,
dynamic_cycle_count: None,
checker_clock: None,
global_clock: None,
}
}
fn alloc_block(&mut self) -> BlockId {
let id = BlockId(self.blocks.len() as u32);
let this_var = self.this_stack.last().copied();
let ref_aliases = self.ref_stack.clone();
self.block_scopes.push(self.scope_chain.clone());
self.block_frames.push(if self.scope_chain.is_empty() {
Vec::new()
} else {
self.frame_stack.clone()
});
self.blocks.push(PartialBlock {
bindings: Vec::new(),
this_var,
ref_aliases,
stmts: Vec::new(),
probes: Vec::new(),
disable_probes: Vec::new(),
frame_probes: Vec::new(),
block_events: Vec::new(),
stmt_scopes: Vec::new(),
step_exprs: Vec::new(),
repeat_init: None,
repeat_decrement: None,
expect_resume: false,
terminator: None,
});
id
}
fn fn_disable_check(&mut self, cur: BlockId) -> BlockId {
let targets: Vec<(usize, String, BlockId, usize)> = self
.disable_targets
.iter()
.enumerate()
.rev()
.filter(|(_, (n, _, _))| is_function_disable_label(n))
.map(|(pos, (n, exit, frames))| (pos, n.clone(), *exit, *frames))
.collect();
if targets.is_empty() {
return cur;
}
let next = self.alloc_block();
let mut arms = Vec::new();
let mut labels = Vec::new();
for (pos, label, exit, frames) in targets {
let arm = self.alloc_block();
let left: Vec<_> = self.frame_stack[frames..].iter().rev().cloned().collect();
for (task, vars) in left {
self.frame_probe(arm, FrameProbe::Exit { task, vars });
}
self.left_block_events(arm, pos);
self.jump(arm, exit);
arms.push(arm);
labels.push(label);
}
self.close(
cur,
Terminator::Switch {
on: SwitchOn::FnDisable { labels },
arms,
default_blk: next,
report: None,
},
);
next
}
fn push(&mut self, blk: BlockId, stmt: Statement<'d>) {
let scope = self.frame_stack.last().map(|f| f.0);
let b = &mut self.blocks[blk.0 as usize];
if let Some(scope) = scope {
b.stmt_scopes.push((b.stmts.len(), scope));
}
b.stmts.push(stmt);
}
fn probe(&mut self, blk: BlockId, stmt: Statement<'d>) {
let b = &mut self.blocks[blk.0 as usize];
let at = b.stmts.len();
b.probes.push((at, stmt));
}
fn disable_probe(&mut self, blk: BlockId, stmt: Statement<'d>) {
let b = &mut self.blocks[blk.0 as usize];
let at = b.stmts.len();
b.disable_probes.push((at, stmt));
}
fn disable_names_extra(&self, blocks: &[String], out: &mut Vec<String>) {
for b in blocks {
if is_disabled_somewhere(b) && !out.contains(b) {
out.push(b.clone());
}
}
}
fn forks_in_body(&mut self) -> bool {
if let Some(v) = self.body_forks {
return v;
}
let v = self.body_node.is_some_and(contains_fork_through_calls);
self.body_forks = Some(v);
v
}
fn walk_hier_disable_of_enclosing(
&mut self,
stmt: Statement<'d>,
cur: BlockId,
path: &[String],
) -> Result<Option<BlockId>, LoweringError> {
let loud = || {
LoweringError::unsupported(format!(
"disable of `{}` (a hierarchical name is supported only as a chain of the \
statement's own lexically enclosing named blocks, not the process's \
outermost block or a task frame; IEEE 1800-2023 9.6.2)",
path.join(".")
))
};
let last = path.last().ok_or_else(loud)?;
let Some(pos) = self.disable_targets.iter().rposition(|(n, _, _)| n == last) else {
return Ok(None);
};
let chain_ok = pos + 1 >= path.len()
&& path
.iter()
.enumerate()
.all(|(i, seg)| self.disable_targets[pos + 1 - path.len() + i].0 == *seg);
if !chain_ok {
return Ok(None);
}
let (_, exit, frames) = self.disable_targets[pos].clone();
let outermost = pos == 0 && self.outermost.as_deref() == Some(last.as_str());
if outermost || frames > 0 {
return Err(loud());
}
self.disable_probe(cur, stmt);
self.left_block_events(cur, pos);
self.jump(cur, exit);
Ok(Some(self.alloc_block()))
}
fn hier_peer_leaf(&self, path: &[String]) -> Result<String, LoweringError> {
if path
.iter()
.all(|seg| self.peers.iter().any(|p| &p.name == seg))
{
return Ok(path[path.len() - 1].clone());
}
let known = |s: &String| {
self.ancestor_blocks.contains(s)
|| self.fork_branch_names.contains(s)
|| self.disable_targets.iter().any(|(n, _, _)| n == s)
|| self.outermost.as_deref() == Some(s.as_str())
};
if let Some((leaf, mid)) = path.split_last()
&& known(leaf)
{
let skip = usize::from(mid.len() >= 2 && !known(&mid[0]));
if mid[skip..].iter().all(known) {
return Ok(leaf.clone());
}
}
let first = &path[0];
if !self.peers.iter().any(|p| &p.name == first)
&& !self.disable_targets.iter().any(|(n, _, _)| n == first)
&& !is_task_name(first)
&& self.fork_name.as_deref() != Some(first.as_str())
&& !self
.fork_branch_names
.iter()
.any(|n| n == first && !self.fork_block_names.contains(n))
{
return Ok(path.join("."));
}
if self.peers.iter().any(|p| &p.name == first) {
return Ok(path.join("."));
}
Err(LoweringError::unsupported(format!(
"disable of `{}` (a hierarchical name is supported only as a chain of the \
statement's own lexically enclosing named blocks or of the named blocks of \
another process of the same instance, not an instance path, the process's \
outermost block or a task frame; IEEE 1800-2023 9.6.2)",
path.join(".")
)))
}
fn frame_probe(&mut self, blk: BlockId, probe: FrameProbe<'d>) {
let b = &mut self.blocks[blk.0 as usize];
let at = b.stmts.len();
b.frame_probes.push((at, probe));
}
fn block_event(&mut self, blk: BlockId, task: Symbol<'d>, is_end: bool) {
let b = &mut self.blocks[blk.0 as usize];
let at = b.stmts.len();
b.block_events.push((at, task, is_end));
}
fn left_block_events(&mut self, cur: BlockId, pos: usize) {
let left: Vec<_> = self
.event_stack
.iter()
.rev()
.take_while(|(_, depth)| *depth > pos)
.map(|(s, _)| *s)
.collect();
for s in left {
self.block_event(cur, s, true);
}
}
fn push_step_expr(&mut self, blk: BlockId, expr: Expression<'d>) {
self.blocks[blk.0 as usize].step_exprs.push(expr);
}
fn alloc_repeat_counter(&mut self) -> RepeatCounterId {
let id = RepeatCounterId(self.next_repeat_counter);
self.next_repeat_counter += 1;
id
}
fn set_repeat_init(&mut self, blk: BlockId, counter: RepeatCounterId, count: RepeatCount<'d>) {
let slot = &mut self.blocks[blk.0 as usize].repeat_init;
debug_assert!(
slot.is_none(),
"cfg builder tried to set block {blk:?}'s repeat_init twice"
);
*slot = Some((counter, count));
}
fn set_repeat_decrement(&mut self, blk: BlockId, counter: RepeatCounterId) {
let slot = &mut self.blocks[blk.0 as usize].repeat_decrement;
debug_assert!(
slot.is_none(),
"cfg builder tried to set block {blk:?}'s repeat_decrement twice"
);
*slot = Some(counter);
}
fn note_edge_signal(&mut self, signal: Expression<'d>, edge: EdgeKind) {
let this_sym = signal.referenced_symbol().map(|s| s.uid());
match &self.inferred_clock {
None => {
self.inferred_clock = Some(this_sym.is_some().then_some((signal, edge)));
}
Some(Some((existing, existing_edge))) => {
let existing_sym = existing.referenced_symbol().map(|s| s.uid());
if existing_sym.is_none() || this_sym != existing_sym || *existing_edge != edge {
self.inferred_clock = Some(None); }
}
Some(None) => {} }
}
fn close(&mut self, blk: BlockId, term: Terminator<'d>) {
let slot = &mut self.blocks[blk.0 as usize].terminator;
debug_assert!(
slot.is_none(),
"cfg builder tried to close block {blk:?} twice"
);
*slot = Some(term);
}
fn jump(&mut self, cur: BlockId, target: BlockId) {
let open = self.owned_scopes.len();
let depth = self.target_depth.get(&target).copied().unwrap_or(open);
let vars: Vec<Symbol<'d>> = self.owned_scopes[depth.min(open)..]
.iter()
.rev()
.flat_map(|s| s.iter().rev().copied())
.collect();
if vars.is_empty() {
self.close(cur, Terminator::Fallthrough(target));
return;
}
let leave = self.alloc_block();
self.close(cur, Terminator::Fallthrough(leave));
for var in vars {
self.blocks[leave.0 as usize]
.bindings
.push(ArgBinding::Release { var });
}
self.close(leave, Terminator::Fallthrough(target));
}
fn ref_storage(&self, actual: Expression<'d>) -> Option<Symbol<'d>> {
if !crate::symid::is_value_name(actual.kind()) {
return None;
}
let sym = actual.referenced_symbol()?;
if sym.kind() == SymbolKind::Net
|| (sym.kind() == SymbolKind::Variable
&& sym.variable_lifetime() == sv_lang::VariableLifetime::Static)
{
return Some(sym);
}
self.ref_stack
.iter()
.rev()
.find(|(f, _)| f.uid() == sym.uid())
.map(|&(_, a)| a)
}
fn take_ref_bindings(&mut self) -> Vec<(Symbol<'d>, Symbol<'d>)> {
use std::collections::HashMap;
let storage = |s: &Symbol<'d>| {
s.kind() == SymbolKind::Net
|| (s.kind() == SymbolKind::Variable
&& s.variable_lifetime() == sv_lang::VariableLifetime::Static)
};
let mut targets: HashMap<crate::symid::SymbolId, (Symbol<'d>, Option<Symbol<'d>>)> =
HashMap::new();
let mut order = Vec::new();
for (formal, named) in &self.ref_candidates {
match targets.get_mut(&formal.uid()) {
None => {
order.push(formal.uid());
targets.insert(formal.uid(), (*formal, *named));
}
Some((_, cur)) => {
if cur.map(|c| c.uid()) != named.map(|n| n.uid()) {
*cur = None;
}
}
}
}
let resolve = |start: Symbol<'d>| -> Option<Symbol<'d>> {
let mut cur = start;
for _ in 0..=targets.len() {
if storage(&cur) {
return Some(cur);
}
cur = targets.get(&cur.uid())?.1?;
}
None
};
let mut out = Vec::new();
for uid in order {
let (formal, named) = targets[&uid];
if let Some(actual) = named.and_then(resolve) {
out.push((formal, actual));
}
}
let bound: std::collections::HashSet<crate::symid::SymbolId> =
out.iter().map(|(f, _)| f.uid()).collect();
for b in &mut self.blocks {
b.bindings.retain(|x| match x {
ArgBinding::In { formal, .. } | ArgBinding::Out { formal, .. } => {
!bound.contains(&formal.uid())
}
_ => true,
});
}
self.inlined_locals.retain(|l| !bound.contains(&l.uid()));
out
}
fn take_disable_scopes(&mut self) -> Vec<(BlockId, String, BlockId)> {
let scopes = std::mem::take(&mut self.block_scopes);
let frames = std::mem::take(&mut self.block_frames);
let mut out = Vec::new();
for (i, (chain, active)) in scopes.into_iter().zip(frames).enumerate() {
for (label, exit, outside) in chain {
let target = if active.len() > outside {
let leave = self.alloc_block();
for (task, vars) in active[outside..].iter().rev().cloned() {
self.frame_probe(leave, FrameProbe::Exit { task, vars });
}
self.close(leave, Terminator::Fallthrough(exit));
leave
} else {
exit
};
out.push((BlockId(i as u32), label, target));
}
}
out
}
fn finish(self) -> Vec<BasicBlock<'d>> {
self.blocks
.into_iter()
.map(|b| BasicBlock {
bindings: b.bindings,
this_var: b.this_var,
ref_aliases: b.ref_aliases,
stmts: b.stmts,
probes: b.probes,
disable_probes: b.disable_probes,
frame_probes: b.frame_probes,
block_events: b.block_events,
stmt_scopes: b.stmt_scopes,
step_exprs: b.step_exprs,
repeat_init: b.repeat_init.clone(),
repeat_decrement: b.repeat_decrement,
expect_resume: b.expect_resume,
terminator: b.terminator.expect(
"internal invariant: build_cfg must close every block it allocates \
before returning Ok",
),
})
.collect()
}
fn walk_stmt(&mut self, stmt: Statement<'d>, cur: BlockId) -> Result<BlockId, LoweringError> {
let stmt = unwrap_invalid(stmt);
let kind = stmt.kind();
if kind == StatementKind::Invalid {
return Err(LoweringError::malformed(
"Invalid statement node -- slang rejected this statement; an error inside a \
statically dead `if`/`case` arm (e.g. a reversed range select `v[7:8]` of a \
descending vector under a false parameter condition) is suppressed by slang \
but still invalidates the whole enclosing procedure",
));
}
if kind == StatementKind::List {
return self.walk_seq(live_statements(&stmt), cur);
}
if is_entry_probe_kind(&stmt) && !self.detached_trigger {
self.probe(cur, stmt);
}
if kind == StatementKind::Block {
return self.walk_block(stmt, cur);
}
if kind == StatementKind::Conditional {
return self.walk_conditional(stmt, cur);
}
if kind == StatementKind::Timed {
return self.walk_timed(stmt, cur);
}
if kind == StatementKind::Wait {
return self.walk_wait(stmt, cur);
}
if kind == StatementKind::WaitFork {
let resume = self.alloc_block();
self.close(
cur,
Terminator::Suspend {
point: SuspendPoint::WaitFork,
resume_blk: resume,
},
);
return Ok(resume);
}
if kind == StatementKind::ExpressionStatement && is_process_by_request_stmt(&stmt) {
self.push(cur, stmt);
let resume = self.alloc_block();
self.close(
cur,
Terminator::Suspend {
point: SuspendPoint::ByRequest,
resume_blk: resume,
},
);
if is_mailbox_two_phase_stmt(&stmt) {
self.push(resume, stmt);
}
return Ok(resume);
}
if kind == StatementKind::WaitOrder {
return self.walk_wait_order(stmt, cur);
}
if is_expect(&stmt) && !self.expect_as_leaf {
return self.walk_expect(stmt, cur);
}
if kind == StatementKind::RandCase
&& (contains_suspend_trigger(stmt.as_sem_node())
|| escapes(
stmt.as_sem_node(),
!self.loop_targets.is_empty(),
!self.return_targets.is_empty(),
))
{
return self.walk_randcase(stmt, cur);
}
if kind == StatementKind::EventTrigger {
return self.walk_event_trigger(stmt, cur);
}
if kind == StatementKind::ExpressionStatement && async_pla_call(&stmt).is_some() {
return self.walk_async_pla(stmt, cur);
}
if kind == StatementKind::DisableFork {
self.push(cur, stmt);
return Ok(cur);
}
if kind == StatementKind::Disable {
let mut label_override = None;
if let Some(path) = disable_path(&stmt) {
if let Some(next) = self.walk_hier_disable_of_enclosing(stmt, cur, &path)? {
return Ok(next);
}
label_override = Some(self.hier_peer_leaf(&path)?);
}
let label = match label_override {
Some(l) => l,
None => disable_label(&stmt)?,
};
if !self.disable_targets.iter().any(|(n, _, _)| *n == label)
&& self.fork_name.as_deref() == Some(label.as_str())
{
self.push(cur, stmt);
self.close(cur, Terminator::End { loop_back: None });
return Ok(self.alloc_block());
}
if let Some(pos) = self
.disable_targets
.iter()
.rposition(|(n, _, _)| *n == label)
{
let (_, exit, frames) = self.disable_targets[pos].clone();
let flush = pos == 0 && self.outermost.as_deref() == Some(label.as_str());
let broadcast = frames > 0;
let ends_branches =
!broadcast && self.forks_in_body() && is_disabled_somewhere(&label);
if flush || broadcast || ends_branches {
self.push(cur, stmt);
}
if broadcast || ends_branches {
self.disable_broadcast.push((label.clone(), false));
}
self.disable_probe(cur, stmt);
let left: Vec<_> = self.frame_stack[frames..].iter().rev().cloned().collect();
for (task, vars) in left {
self.frame_probe(cur, FrameProbe::Exit { task, vars });
}
self.left_block_events(cur, pos);
self.jump(cur, exit);
return Ok(self.alloc_block());
}
if self.assertion_labels.contains(&label) {
self.push(cur, stmt);
return Ok(cur);
}
if self.deferred_labels.contains(&label) {
self.push(cur, stmt);
return Ok(cur);
}
if self.immediate_labels.contains(&label) {
return Ok(cur);
}
if let Some(can_suspend) = self
.peers
.iter()
.find(|p| p.name == label)
.map(|p| p.can_suspend)
{
self.push(cur, stmt);
if can_suspend {
self.disable_broadcast.push((label, true));
}
return Ok(cur);
}
if label.contains('.') {
self.push(cur, stmt);
self.disable_broadcast.push((label, true));
return Ok(cur);
}
if is_task_name(&label) {
self.push(cur, stmt);
self.disable_broadcast.push((label, true));
return Ok(cur);
}
if self.fork_branch_names.iter().any(|n| n == &label) {
self.push(cur, stmt);
return Ok(cur);
}
if self.ancestor_blocks.iter().any(|n| n == &label) {
self.push(cur, stmt);
self.disable_broadcast.push((label, true));
self.close(cur, Terminator::End { loop_back: None });
return Ok(self.alloc_block());
}
return Err(LoweringError::unsupported(format!(
"disable {label}; where `{label}` is not a named block or task enclosing it \
in the same process, a procedural assertion label of it, a uniquely named \
sequential block of another process or a task of the design (not a block \
inside a fork branch of another process, nor a name shared by two blocks), \
or a fork branch's own name reachable from here"
)));
}
if kind == StatementKind::ForLoop {
return self.walk_for_loop(stmt, cur);
}
if kind == StatementKind::WhileLoop {
return self.walk_while_loop(stmt, cur);
}
if kind == StatementKind::DoWhileLoop {
return self.walk_do_while_loop(stmt, cur);
}
if kind == StatementKind::ForeverLoop {
return self.walk_forever_loop(stmt, cur);
}
if kind == StatementKind::ForeachLoop {
return self.walk_foreach_loop(stmt, cur);
}
if kind == StatementKind::RepeatLoop
&& (contains_suspend_trigger(stmt.as_sem_node())
|| escapes(stmt.as_sem_node(), false, !self.return_targets.is_empty()))
{
return self.walk_repeat_loop(stmt, cur);
}
if kind == StatementKind::ExpressionStatement
&& let Some(e) = stmt.expr()
&& crate::netlist::itf::is_vif_extern_call(&e)
{
return Err(LoweringError::unsupported(
crate::netlist::itf::vif_extern_message(&e),
));
}
if kind == StatementKind::ExpressionStatement
&& let Some(e) = stmt.expr()
&& let Some(targets) = crate::netlist::itf::call_extern_targets(&e)
&& let Some(next) = self.walk_extern_call(stmt, e, &targets, cur)?
{
return Ok(next);
}
if kind == StatementKind::ExpressionStatement
&& let Some(e) = stmt.expr()
&& let Some(callee) = suspending_task_callee(&e)
{
return self.inline_task_call(e, callee, cur);
}
if matches!(kind, StatementKind::Break | StatementKind::Continue) {
let &(cont, brk) = self
.loop_targets
.last()
.ok_or_else(|| LoweringError::malformed(format!("{kind:?} outside any loop")))?;
let target = if kind == StatementKind::Break {
brk
} else {
cont
};
self.jump(cur, target);
return Ok(self.alloc_block());
}
if (kind == StatementKind::Case || kind == StatementKind::PatternCase)
&& (contains_suspend_trigger(stmt.as_sem_node())
|| constant_case_live_arm(&stmt).is_some()
|| escapes(
stmt.as_sem_node(),
!self.loop_targets.is_empty(),
!self.return_targets.is_empty(),
))
{
return self.walk_case(stmt, cur);
}
if kind == StatementKind::ImmediateAssertion
&& let Some(actions) = suspending_assertion_actions(&stmt)
{
return self.walk_immediate_assertion(stmt, actions, cur);
}
if kind == StatementKind::ExpressionStatement
&& let Some(e) = stmt.expr()
&& e.kind() == ExpressionKind::Assignment
&& e.is_nonblocking() != Some(true)
&& e.assignment_timing().is_some()
{
return self.walk_intra_assignment_timing(e, cur);
}
if kind == StatementKind::ExpressionStatement
&& let Some(e) = stmt.expr()
&& e.kind() == ExpressionKind::Assignment
&& e.is_nonblocking() == Some(true)
&& e.assignment_timing()
.and_then(|n| n.as_timing_control())
.is_some_and(|tc| tc.kind() != TimingControlKind::Delay)
{
return self.walk_nba_event(stmt, e, cur);
}
if kind == StatementKind::Return
&& let Some(&target) = self.return_targets.last()
{
let mut cur = cur;
if self.ret_var_stack.len() == self.return_targets.len()
&& let Some(Some(rv)) = self.ret_var_stack.last().copied()
&& let Some(value) = stmt.expr()
{
if let Some(next) = self.hoist_suspending_calls_in(value.as_sem_node(), cur)? {
cur = next;
}
let set = self.alloc_block();
self.close(cur, Terminator::Fallthrough(set));
self.blocks[set.0 as usize].bindings.push(ArgBinding::In {
formal: rv,
actual: value,
});
cur = set;
}
self.jump(cur, target);
return Ok(self.alloc_block());
}
if kind == StatementKind::ExpressionStatement && is_finish_or_stop_call(&stmt) {
self.push(cur, stmt);
self.close(cur, Terminator::End { loop_back: None });
return Ok(self.alloc_block());
}
if is_ordinary_statement_kind(kind) {
if kind == StatementKind::ExpressionStatement
&& contains_suspend_trigger(stmt.as_sem_node())
&& let Some(next) = self.hoist_suspending_function_calls(stmt, cur)?
{
return Ok(next);
}
return if contains_suspend_trigger(stmt.as_sem_node())
&& !(is_expect(&stmt) && self.expect_as_leaf)
{
Err(LoweringError::unsupported(format!(
"suspend point nested inside a {kind:?} statement (no CFG terminator \
for this construct is implemented in this task, PR-069)"
)))
} else {
self.push(cur, stmt);
if kind == StatementKind::ExpressionStatement
&& has_function_disables()
&& contains_subroutine_call(stmt.as_sem_node())
{
return Ok(self.fn_disable_check(cur));
}
Ok(cur)
};
}
Err(LoweringError::malformed(format!(
"unrecognized StatementKind {kind:?} (sv-lang-kinds version drift -- see \
determinism::kind_guard's StatementKind pin)"
)))
}
fn walk_expect(&mut self, stmt: Statement<'d>, cur: BlockId) -> Result<BlockId, LoweringError> {
let blocking = [stmt.assertion_if_true(), stmt.assertion_if_false()]
.into_iter()
.flatten()
.any(|a| contains_suspend_trigger(a.as_sem_node()));
if blocking {
return self.walk_expect_blocking(stmt, cur);
}
self.push(cur, stmt);
let resume = self.alloc_block();
self.blocks[resume.0 as usize].expect_resume = true;
self.close(
cur,
Terminator::Suspend {
point: SuspendPoint::EdgeList(Vec::new(), Vec::new()),
resume_blk: resume,
},
);
self.push(resume, stmt);
Ok(resume)
}
fn walk_expect_blocking(
&mut self,
stmt: Statement<'d>,
cur: BlockId,
) -> Result<BlockId, LoweringError> {
self.push(cur, stmt);
let resume = self.alloc_block();
self.close(
cur,
Terminator::Suspend {
point: SuspendPoint::EdgeList(Vec::new(), Vec::new()),
resume_blk: resume,
},
);
let join = self.alloc_block();
let pass = self.alloc_block();
let fail = self.alloc_block();
let pass_exit = match stmt.assertion_if_true() {
Some(s) => self.walk_stmt(s, pass)?,
None => pass,
};
self.close(pass_exit, Terminator::Fallthrough(join));
let fail_exit = match stmt.assertion_if_false() {
Some(s) => self.walk_stmt(s, fail)?,
None => {
self.close(
fail,
Terminator::Switch {
on: SwitchOn::FiredTerm,
arms: Vec::new(),
default_blk: join,
report: Some(stmt.source_range().start),
},
);
self.alloc_block()
}
};
self.close(fail_exit, Terminator::Fallthrough(join));
self.close(
resume,
Terminator::Switch {
on: SwitchOn::FiredTerm,
arms: vec![pass, fail, join],
default_blk: join,
report: None,
},
);
Ok(join)
}
fn walk_randcase(
&mut self,
stmt: Statement<'d>,
cur: BlockId,
) -> Result<BlockId, LoweringError> {
let join = self.alloc_block();
let mut arms = Vec::new();
for item in stmt.randcase_items() {
let arm = self.alloc_block();
arms.push(arm);
let exit = self.walk_stmt(item.stmt(), arm)?;
self.close(exit, Terminator::Fallthrough(join));
}
self.close(
cur,
Terminator::Switch {
on: SwitchOn::RandCase(stmt),
arms,
default_blk: join,
report: None,
},
);
Ok(join)
}
fn walk_wait_order(
&mut self,
stmt: Statement<'d>,
cur: BlockId,
) -> Result<BlockId, LoweringError> {
let events = stmt.wait_order_events();
if events.is_empty() {
return Err(LoweringError::malformed("wait_order with no events"));
}
let pass = self.alloc_block();
let fail = self.alloc_block();
let join = self.alloc_block();
let if_false = stmt.wait_order_if_false();
let report = if_false.is_none().then(|| stmt.source_range().start);
let mut at = cur;
for i in 0..events.len() {
let resume = self.alloc_block();
self.close(
at,
Terminator::Suspend {
point: SuspendPoint::EdgeList(
events[i..]
.iter()
.map(|e| (*e, sv_lang::EdgeKind::None))
.collect(),
vec![Vec::new(); events.len() - i],
),
resume_blk: resume,
},
);
let next = if i + 1 == events.len() {
pass
} else {
self.alloc_block()
};
self.close(
resume,
Terminator::Switch {
on: SwitchOn::FiredTerm,
arms: vec![next],
default_blk: fail,
report,
},
);
at = next;
}
let pass_exit = match stmt.wait_order_if_true() {
Some(s) => self.walk_stmt(s, pass)?,
None => pass,
};
self.close(pass_exit, Terminator::Fallthrough(join));
let fail_exit = match if_false {
Some(s) => self.walk_stmt(s, fail)?,
None => fail,
};
self.close(fail_exit, Terminator::Fallthrough(join));
Ok(join)
}
fn inline_task_call(
&mut self,
call: Expression<'d>,
task: Symbol<'d>,
cur: BlockId,
) -> Result<BlockId, LoweringError> {
let result_temp = self.pending_result_temp.take();
if let Some((vif, targets)) = crate::netlist::vif::call_targets(&call) {
let actuals: Vec<Expression<'d>> = call.operands()[1..].to_vec();
let join = self.alloc_block();
let mut arms = Vec::new();
for target in targets {
let arm = self.alloc_block();
arms.push(arm);
match target {
Some(t) => {
let exit = self.inline_task_body(&actuals, t, arm, None)?;
self.park_call_result(exit, result_temp, t);
self.close(exit, Terminator::Fallthrough(join));
}
None => self.close(arm, Terminator::Fallthrough(join)),
}
}
self.close(
cur,
Terminator::Switch {
on: SwitchOn::VifId(vif),
arms,
default_blk: join,
report: None,
},
);
return Ok(join);
}
self.call_anchor = Some(call);
let receiver = call.call_this_class();
let mut actuals = call.operands();
if receiver.is_some() && !actuals.is_empty() {
actuals.remove(0);
}
let pure = task.subroutine_flags().contains(sv_lang::MethodFlags::PURE);
let this_var = task.subroutine_this_var();
if this_var.is_none() && !pure {
let exit = self.inline_task_body(&actuals, task, cur, None)?;
self.park_call_result(exit, result_temp, task);
return Ok(exit);
}
if !(receiver.is_none() && is_explicit_super(call))
&& (task
.subroutine_flags()
.contains(sv_lang::MethodFlags::VIRTUAL)
|| task.subroutine_override().is_some())
{
let impls = self
.virtual_tasks
.as_ref()
.map(|v| v.implementations(&task))
.unwrap_or_default();
let join = self.alloc_block();
let mut arms = Vec::new();
let mut ids = Vec::new();
for (class_ids, imp) in impls {
let this_var = imp.subroutine_this_var().ok_or_else(|| {
LoweringError::malformed(format!(
"override of virtual task `{}` has no `this`",
task.name()
))
})?;
let arm = self.alloc_block();
arms.push(arm);
ids.push(class_ids);
let exit = self.inline_task_body(&actuals, imp, arm, Some((this_var, receiver)))?;
self.park_call_result(exit, result_temp, imp);
self.close(exit, Terminator::Fallthrough(join));
}
self.close(
cur,
Terminator::Switch {
on: SwitchOn::ObjClass { receiver, ids },
arms,
default_blk: join,
report: Some(call.source_range().start),
},
);
return Ok(join);
}
let this_var = this_var.ok_or_else(|| {
LoweringError::malformed(format!("method task `{}` has no `this`", task.name()))
})?;
let exit = self.inline_task_body(&actuals, task, cur, Some((this_var, receiver)))?;
self.park_call_result(exit, result_temp, task);
Ok(exit)
}
fn park_call_result(
&mut self,
exit: BlockId,
result_temp: Option<(u32, Expression<'d>)>,
imp: Symbol<'d>,
) {
if let Some((temp, _)) = result_temp
&& let Some(rv) = imp.subroutine_return_val_var()
{
self.blocks[exit.0 as usize]
.bindings
.push(ArgBinding::ToCaptured { temp, formal: rv });
}
}
fn inline_recursive_task(
&mut self,
actuals: &[Expression<'d>],
task: Symbol<'d>,
cur: BlockId,
this: Option<(Symbol<'d>, Option<Expression<'d>>)>,
) -> Result<BlockId, LoweringError> {
let refuse = |why: &str| {
LoweringError::unsupported(format!(
"recursive call to task `{}`, which contains timing controls: {why} \
(LRM 1800-2023 13.4.1)",
task.name()
))
};
let body = proc_body(&task).ok_or_else(|| {
LoweringError::malformed(format!("task `{}` has no body", task.name()))
})?;
let mut formals: Vec<Symbol<'d>> = task.subroutine_arguments().collect();
if formals.len() != actuals.len() {
return Err(LoweringError::malformed(format!(
"call to task `{}` has {} arguments for {} formals",
task.name(),
actuals.len(),
formals.len()
)));
}
if task.subroutine_kind() == Some(sv_lang::SubroutineKind::Function)
&& task.subroutine_return_type().is_some_and(|t| !t.is_void())
{
return Err(refuse("a value-returning function is not supported"));
}
for f in &formals {
if crate::netlist::is_static_procedural_var(f) {
return Err(refuse("a recursive task must be `automatic`"));
}
}
let mut actuals: Vec<Expression<'d>> = actuals
.iter()
.zip(&formals)
.map(|(&a, f)| {
if f.formal_argument_direction() == sv_lang::ArgumentDirection::In {
a
} else {
call_actual_lvalue(a)
}
})
.collect();
let has_this = this.is_some();
let mut implicit_this = false;
if let Some((this_var, recv)) = this {
let anchor = self.call_anchor.ok_or_else(|| {
LoweringError::malformed("recursive method call without a call expression")
})?;
implicit_this = recv.is_none();
formals.insert(0, this_var);
actuals.insert(0, recv.unwrap_or(anchor));
}
let n = formals.len() as u32;
let actuals = &actuals[..];
let mut ref_alias: Vec<Option<Symbol<'d>>> = vec![None; formals.len()];
for (i, f) in formals.iter().enumerate() {
if f.formal_argument_direction() != sv_lang::ArgumentDirection::Ref {
continue;
}
let is_event = f
.value_type()
.as_ref()
.is_some_and(|t| crate::state::event_pool::event_count(t).is_some());
match self.ref_storage(actuals[i]) {
Some(t) if !is_event => ref_alias[i] = Some(t),
_ => {
return Err(refuse(
"a `ref` argument must name a static variable or net (or an \
enclosing task's aliased `ref` formal)",
));
}
}
}
let is_in = |i: usize| {
(has_this && i == 0)
|| formals[i].formal_argument_direction() == sv_lang::ArgumentDirection::In
|| ref_alias[i].is_some()
};
let outs: Vec<usize> = (0..formals.len()).filter(|&i| !is_in(i)).collect();
let groups = if outs.is_empty() { 2 } else { 4 };
let key = (self.global_clock.is_none() && self.checker_clock.is_none()).then(|| {
MemoKey::Root(RootKey {
task: task.uid(),
this_var: this.map(|(t, _)| t.uid()),
default_clocking: self.default_clocking.map(|c| c.uid()),
rec_outer: self.rec_outer.clone(),
chain: self
.rec_up
.iter()
.chain(self.rec.iter())
.map(|r| r.share)
.collect(),
ref_aliases: self
.ref_stack
.iter()
.map(|(f, t)| (f.uid(), t.uid()))
.chain(
formals
.iter()
.zip(&ref_alias)
.filter_map(|(f, t)| t.map(|t| (f.uid(), t.uid()))),
)
.collect(),
labels: self.fork_branch_names.clone(),
ancestors: self.ancestor_blocks.clone(),
})
});
let cached = key
.as_ref()
.and_then(|k| self.fork_memo.borrow().get(k).cloned());
let mut branch = if let Some(hit) = cached {
hit
} else {
let share = NEXT_SHARE_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let mut b = CfgBuilder::new();
b.fork_memo = self.fork_memo.clone();
b.global_clock = self.global_clock;
b.virtual_tasks = self.virtual_tasks.clone();
b.fork_branch_names = self.fork_branch_names.clone();
b.ancestor_blocks = self.ancestor_blocks.clone();
b.default_clocking = self.default_clocking;
b.checker_clock = self.checker_clock;
b.rec_outer = self.rec_outer.clone();
b.rec_outer.push(task.uid());
b.rec_up = self.rec_up.clone();
b.rec_up.extend(self.rec.clone());
b.rec = Some(RecursiveTask {
task: task.uid(),
share,
proto: actuals
.iter()
.copied()
.zip(formals.iter().copied())
.collect(),
n,
groups,
outs: (0..formals.len()).map(|i| !is_in(i)).collect(),
refs: ref_alias.clone(),
has_this,
body,
});
b.ref_stack = self.ref_stack.clone();
for (f, t) in formals.iter().zip(&ref_alias) {
if let Some(t) = t {
b.ref_stack.push((*f, *t));
b.ref_candidates.push((*f, Some(*t)));
}
}
if let Some((this_var, _)) = this {
b.this_stack.push(this_var);
}
for _ in 0..groups {
for (f, &a) in formals.iter().zip(actuals) {
b.set_capture_type(a, *f);
}
}
let entry = b.alloc_block();
for (i, f) in formals.iter().enumerate() {
if ref_alias[i].is_some() {
continue;
}
b.blocks[entry.0 as usize].bindings.push(
if !is_in(i) && f.formal_argument_direction() == sv_lang::ArgumentDirection::Out
{
ArgBinding::Init { var: *f }
} else {
ArgBinding::FromCaptured {
temp: i as u32,
formal: *f,
}
},
);
b.inlined_locals.push(*f);
}
for local in task_locals(&task) {
if this.is_some_and(|(t, _)| t.uid() == local.uid()) {
continue;
}
if crate::netlist::is_static_procedural_var(&local) {
continue;
}
if !is_automatic_block_var(&local) {
b.blocks[entry.0 as usize]
.bindings
.push(ArgBinding::Init { var: local });
}
b.inlined_locals.push(local);
}
for v in crate::patterns::pattern_vars(body.as_sem_node()) {
if !b.inlined_locals.iter().any(|l| l.uid() == v.uid()) {
b.inlined_locals.push(v);
}
}
let after = b.alloc_block();
b.target_depth.insert(after, 0);
b.return_targets.push(after);
let task_name = task.name().to_string();
let task_scoped = is_disabled_somewhere(&task_name);
if task_scoped {
b.scope_chain
.push((task_name.clone(), after, b.frame_stack.len()));
b.disable_targets
.push((task_name, after, b.frame_stack.len()));
}
b.block_event(entry, task, false);
b.event_stack.push((task, b.disable_targets.len()));
let exit = b.walk_stmt(body, entry);
b.event_stack.pop();
if task_scoped {
b.scope_chain.pop();
b.disable_targets.pop();
}
let exit = exit?;
b.close(exit, Terminator::Fallthrough(after));
b.block_event(after, task, true);
for &i in &outs {
b.blocks[after.0 as usize]
.bindings
.push(ArgBinding::ToCaptured {
temp: 2 * n + i as u32,
formal: formals[i],
});
}
b.close(after, Terminator::End { loop_back: None });
let inlined_locals = std::mem::take(&mut b.inlined_locals);
let captures = std::mem::take(&mut b.captures);
let capture_types = std::mem::take(&mut b.capture_types);
let b_ref_formals: Vec<Symbol<'d>> = b.ref_stack.iter().map(|&(f, _)| f).collect();
let ref_bindings = b.take_ref_bindings();
let disable_scopes = b.take_disable_scopes();
let disable_broadcast = std::mem::take(&mut b.disable_broadcast);
let fork_branch_names = std::mem::take(&mut b.fork_branch_names);
let mut built = ProcessCfg {
blocks: b.finish(),
entry,
body,
inlined_locals,
captures,
captured: Vec::new(),
ref_bindings,
disable_names: Vec::new(),
fork_branch_names,
disable_scopes,
disable_broadcast,
inherited: Vec::new(),
capture_types,
recursion: Recursion::Root,
share_id: None,
};
built.captured = captured_vars(&built);
built
.captured
.retain(|c| !b_ref_formals.iter().any(|f| f.uid() == c.uid()));
if let Some(k) = key {
built.share_id = Some(share);
self.fork_memo.borrow_mut().insert(k, built.clone());
}
built
};
let call = self.alloc_block();
self.close(cur, Terminator::Fallthrough(call));
for (i, (f, &a)) in formals.iter().zip(actuals).enumerate() {
let temp = self.set_capture_type(a, *f);
self.blocks[call.0 as usize]
.bindings
.push(if i == 0 && implicit_this {
ArgBinding::CaptureThis { temp }
} else {
ArgBinding::Capture { temp, value: a }
});
branch.inherited.push(InheritedSlot::Temp {
parent: temp,
child: i as u32,
});
}
let join_blk = self.alloc_block();
for &i in &outs {
let ret = self.set_capture_type(actuals[i], formals[i]);
branch.inherited.push(InheritedSlot::Return {
parent: ret,
child: 2 * n + i as u32,
});
self.blocks[join_blk.0 as usize]
.bindings
.push(ArgBinding::AssignCaptured {
temp: ret,
target: actuals[i],
nba: false,
});
}
self.close(
call,
Terminator::ForkJoin {
kind: StatementBlockKind::JoinAll,
branches: vec![branch],
join_blk,
},
);
Ok(join_blk)
}
fn recursive_self_call(
&mut self,
actuals: &[Expression<'d>],
cur: BlockId,
recv: Option<Option<Expression<'d>>>,
) -> Result<BlockId, LoweringError> {
let rec = self
.rec
.as_ref()
.ok_or_else(|| LoweringError::malformed("recursive call outside its activation"))?;
let (n, body, rec_outs) = (rec.n, rec.body, rec.outs.clone());
let skip = usize::from(rec.has_this);
for (a, t) in actuals.iter().zip(&rec.refs[skip..]) {
if let Some(t) = t
&& self.ref_storage(call_actual_lvalue(*a)).map(|s| s.uid()) != Some(t.uid())
{
return Err(LoweringError::unsupported(
"recursive call whose `ref` argument is not the storage the first call \
bound (a recursive task's `ref` formal is an alias of one static \
variable or net; LRM 1800-2023 13.5.2)",
));
}
}
let mut actuals: Vec<Expression<'d>> = actuals
.iter()
.zip(&rec_outs[skip..])
.map(|(&a, &out)| if out { call_actual_lvalue(a) } else { a })
.collect();
let mut implicit_this = false;
if let Some(r) = recv {
let anchor = self.call_anchor.ok_or_else(|| {
LoweringError::malformed("recursive method call without a call expression")
})?;
implicit_this = r.is_none();
actuals.insert(0, r.unwrap_or(anchor));
}
let actuals = &actuals[..];
if actuals.len() as u32 != n {
return Err(LoweringError::malformed(
"recursive call with the wrong number of arguments",
));
}
let call = self.alloc_block();
self.close(cur, Terminator::Fallthrough(call));
let mut inherited = Vec::new();
for (i, &a) in actuals.iter().enumerate() {
let temp = n + i as u32;
self.blocks[call.0 as usize]
.bindings
.push(if i == 0 && implicit_this {
ArgBinding::CaptureThis { temp }
} else {
ArgBinding::Capture { temp, value: a }
});
inherited.push(InheritedSlot::Temp {
parent: temp,
child: i as u32,
});
}
let join_blk = self.alloc_block();
for (i, &a) in actuals.iter().enumerate() {
if rec_outs[i] {
let ret = 3 * n + i as u32;
inherited.push(InheritedSlot::Return {
parent: ret,
child: 2 * n + i as u32,
});
self.blocks[join_blk.0 as usize]
.bindings
.push(ArgBinding::AssignCaptured {
temp: ret,
target: a,
nba: false,
});
}
}
let mut stub = CfgBuilder::new();
let entry = stub.alloc_block();
stub.close(entry, Terminator::End { loop_back: None });
let branch = ProcessCfg {
blocks: stub.finish(),
entry,
body,
inlined_locals: Vec::new(),
captures: Vec::new(),
captured: Vec::new(),
ref_bindings: Vec::new(),
disable_names: Vec::new(),
fork_branch_names: Vec::new(),
disable_scopes: Vec::new(),
disable_broadcast: Vec::new(),
inherited,
capture_types: Vec::new(),
recursion: Recursion::SelfSpawn,
share_id: None,
};
self.close(
call,
Terminator::ForkJoin {
kind: StatementBlockKind::JoinAll,
branches: vec![branch],
join_blk,
},
);
Ok(join_blk)
}
fn recursive_ancestor_call(
&mut self,
k: usize,
actuals: &[Expression<'d>],
cur: BlockId,
recv: Option<Option<Expression<'d>>>,
) -> Result<BlockId, LoweringError> {
let anc = self.rec_up[k].clone();
if anc.refs.iter().any(Option::is_some) {
return Err(LoweringError::unsupported(
"mutually recursive tasks with a `ref` formal (LRM 1800-2023 13.4.1, 13.5.2)",
));
}
let skip = usize::from(anc.has_this);
let mut actuals: Vec<Expression<'d>> = actuals
.iter()
.zip(&anc.outs[skip..])
.map(|(&a, &out)| if out { call_actual_lvalue(a) } else { a })
.collect();
let mut implicit_this = false;
if let Some(r) = recv {
let anchor = self.call_anchor.ok_or_else(|| {
LoweringError::malformed("recursive method call without a call expression")
})?;
implicit_this = r.is_none();
actuals.insert(0, r.unwrap_or(anchor));
}
if actuals.len() as u32 != anc.n {
return Err(LoweringError::malformed(
"recursive call with the wrong number of arguments",
));
}
let call = self.alloc_block();
self.close(cur, Terminator::Fallthrough(call));
let mut inherited = Vec::new();
for (i, &a) in actuals.iter().enumerate() {
let temp = self.set_capture_type(a, anc.proto[i].1);
self.blocks[call.0 as usize]
.bindings
.push(if i == 0 && implicit_this {
ArgBinding::CaptureThis { temp }
} else {
ArgBinding::Capture { temp, value: a }
});
inherited.push(InheritedSlot::Temp {
parent: temp,
child: i as u32,
});
}
let join_blk = self.alloc_block();
for (i, &a) in actuals.iter().enumerate() {
if anc.outs[i] {
let ret = self.set_capture_type(a, anc.proto[i].1);
inherited.push(InheritedSlot::Return {
parent: ret,
child: 2 * anc.n + i as u32,
});
self.blocks[join_blk.0 as usize]
.bindings
.push(ArgBinding::AssignCaptured {
temp: ret,
target: a,
nba: false,
});
}
}
let mut stub = CfgBuilder::new();
let entry = stub.alloc_block();
stub.close(entry, Terminator::End { loop_back: None });
let branch = ProcessCfg {
blocks: stub.finish(),
entry,
body: anc.body,
inlined_locals: Vec::new(),
captures: Vec::new(),
captured: Vec::new(),
ref_bindings: Vec::new(),
disable_names: Vec::new(),
fork_branch_names: Vec::new(),
disable_scopes: Vec::new(),
disable_broadcast: Vec::new(),
inherited,
capture_types: Vec::new(),
recursion: Recursion::AncestorSpawn((self.rec_up.len() - k) as u32),
share_id: None,
};
self.close(
call,
Terminator::ForkJoin {
kind: StatementBlockKind::JoinAll,
branches: vec![branch],
join_blk,
},
);
Ok(join_blk)
}
fn set_capture_type(&mut self, value: Expression<'d>, formal: Symbol<'d>) -> u32 {
let temp = self.captures.len() as u32;
self.captures.push(value);
self.capture_types.resize(temp as usize, None);
self.capture_types.push(Some(formal));
temp
}
fn hoist_suspending_function_calls(
&mut self,
stmt: Statement<'d>,
cur: BlockId,
) -> Result<Option<BlockId>, LoweringError> {
let Some(cur) = self.hoist_suspending_calls_in(stmt.as_sem_node(), cur)? else {
return Ok(None);
};
self.push(cur, stmt);
Ok(Some(cur))
}
fn hoist_suspending_calls_in(
&mut self,
node: SemNode<'d>,
cur: BlockId,
) -> Result<Option<BlockId>, LoweringError> {
let mut calls = Vec::new();
collect_suspending_function_calls(node, &mut calls);
if calls.is_empty() {
return Ok(None);
}
let mut count: std::collections::HashMap<_, usize> = std::collections::HashMap::new();
for call in &calls {
if let Some(callee) = user_task_callee(call) {
*count.entry(callee.uid()).or_default() += 1;
}
}
let mut cur = cur;
for call in calls {
let Some(callee) = user_task_callee(&call) else {
continue;
};
let is_method =
callee.subroutine_this_var().is_some() || call.call_this_class().is_some();
let flags = callee.subroutine_flags();
let is_virtual = is_method
&& (flags.contains(sv_lang::MethodFlags::VIRTUAL)
|| flags.contains(sv_lang::MethodFlags::PURE)
|| callee.subroutine_override().is_some());
let temp = (is_virtual || count.get(&callee.uid()).copied().unwrap_or(0) > 1)
.then(|| {
callee
.subroutine_return_val_var()
.map(|rv| self.set_capture_type(call, rv))
})
.flatten();
if let Some(temp) = temp {
self.pending_result_temp = Some((temp, call));
}
cur = if is_method {
self.inline_task_call(call, callee, cur)?
} else {
let actuals = call.operands();
let exit = self.inline_task_body(&actuals, callee, cur, None)?;
self.park_call_result(exit, temp.map(|t| (t, call)), callee);
exit
};
self.pending_result_temp = None;
if let Some(temp) = temp {
self.blocks[cur.0 as usize]
.bindings
.push(ArgBinding::CallResult { temp, call });
}
}
Ok(Some(cur))
}
fn inline_task_body(
&mut self,
actuals: &[Expression<'d>],
task: Symbol<'d>,
cur: BlockId,
this: Option<(Symbol<'d>, Option<Expression<'d>>)>,
) -> Result<BlockId, LoweringError> {
if let Some(rec) = &self.rec
&& rec.task == task.uid()
&& this.is_some() == rec.has_this
{
return self.recursive_self_call(actuals, cur, this.map(|t| t.1));
}
if let Some(k) = self
.rec_up
.iter()
.rposition(|r| r.task == task.uid() && r.has_this == this.is_some())
{
return self.recursive_ancestor_call(k, actuals, cur, this.map(|t| t.1));
}
if self.rec.is_none() && self.rec_outer.contains(&task.uid()) {
return Err(LoweringError::unsupported(format!(
"call to task `{}` from a branch of its own recursive activation that cannot \
spawn it (LRM 1800-2023 13.4.1)",
task.name()
)));
}
if self.inline_stack.contains(&task.uid()) {
return Err(LoweringError::unsupported(format!(
"recursive call to task `{}`, which contains timing controls (it has to be \
inlined into the calling process)",
task.name()
)));
}
if !self.rec_outer.contains(&task.uid())
&& task_reaches_itself(&task, self.virtual_tasks.as_deref())
{
return self.inline_recursive_task(actuals, task, cur, this);
}
let body = proc_body(&task).ok_or_else(|| {
LoweringError::malformed(format!("task `{}` has no body", task.name()))
})?;
let formals: Vec<Symbol<'d>> = task.subroutine_arguments().collect();
if formals.len() != actuals.len() {
return Err(LoweringError::malformed(format!(
"call to task `{}` has {} arguments for {} formals",
task.name(),
actuals.len(),
formals.len()
)));
}
let entry = self.alloc_block();
self.close(cur, Terminator::Fallthrough(entry));
let after = self.alloc_block();
self.target_depth.insert(after, self.owned_scopes.len());
let frame_mark = self.inlined_locals.len();
let mut releases = Vec::new();
if let Some((this_var, receiver)) = this {
let binding = match receiver {
Some(r) => ArgBinding::In {
formal: this_var,
actual: r,
},
None => ArgBinding::ThisOf { var: this_var },
};
self.blocks[entry.0 as usize].bindings.push(binding);
self.inlined_locals.push(this_var);
releases.push(ArgBinding::Release { var: this_var });
}
let mut copy_backs = Vec::new();
let mut aliases = Vec::new();
let mut bound_refs: Vec<(Symbol<'d>, Expression<'d>)> = Vec::new();
for (formal, &actual) in formals.iter().zip(actuals) {
let lvalue = if actual.kind() == ExpressionKind::Assignment {
actual.left().ok_or_else(|| {
LoweringError::malformed("output argument with no target expression")
})?
} else {
actual
};
if formal.formal_argument_direction() == sv_lang::ArgumentDirection::Ref
&& !formal
.value_type()
.as_ref()
.is_some_and(|t| crate::state::event_pool::event_count(t).is_some())
&& let Some(target) = self.ref_storage(lvalue)
{
self.ref_candidates.push((*formal, Some(target)));
aliases.push((*formal, target));
continue;
}
if formal.formal_argument_direction() == sv_lang::ArgumentDirection::Ref
&& lvalue.kind() == ExpressionKind::ElementSelect
&& formal
.value_type()
.is_some_and(|t| t.is_integral() || t.is_floating())
&& lvalue
.select_value()
.and_then(|b| b.expr_type())
.is_some_and(|t| t.is_dynamically_sized_array() || t.is_associative_array())
{
let prior =
self.blocks
.iter()
.flat_map(|b| b.bindings.iter())
.find_map(|b| match b {
ArgBinding::RefOpen {
temp, formal: f, ..
} if f.uid() == formal.uid() => Some(*temp),
_ => None,
});
let temp = prior.unwrap_or_else(|| {
self.captures.push(lvalue);
self.captures.len() as u32 - 1
});
self.blocks[entry.0 as usize]
.bindings
.push(ArgBinding::RefOpen {
temp,
formal: *formal,
actual: lvalue,
});
copy_backs.push(ArgBinding::RefClose {
temp,
formal: *formal,
actual: lvalue,
});
self.ref_candidates.push((*formal, None));
if !crate::netlist::is_static_procedural_var(formal) {
self.inlined_locals.push(*formal);
}
bound_refs.push((*formal, lvalue));
continue;
}
let binding = match formal.formal_argument_direction() {
sv_lang::ArgumentDirection::In => ArgBinding::In {
formal: *formal,
actual,
},
sv_lang::ArgumentDirection::Out => ArgBinding::Init { var: *formal },
sv_lang::ArgumentDirection::InOut | sv_lang::ArgumentDirection::Ref => {
ArgBinding::In {
formal: *formal,
actual: lvalue,
}
}
};
self.blocks[entry.0 as usize].bindings.push(binding);
if formal.formal_argument_direction() == sv_lang::ArgumentDirection::Ref {
bound_refs.push((*formal, lvalue));
let named = (crate::symid::is_value_name(lvalue.kind())
&& !formal
.value_type()
.as_ref()
.is_some_and(|t| crate::state::event_pool::event_count(t).is_some()))
.then(|| lvalue.referenced_symbol())
.flatten();
self.ref_candidates.push((*formal, named));
}
if formal.formal_argument_direction() != sv_lang::ArgumentDirection::In {
copy_backs.push(ArgBinding::Out {
actual: lvalue,
formal: *formal,
});
}
if !crate::netlist::is_static_procedural_var(formal) {
self.inlined_locals.push(*formal);
if formal.formal_argument_direction() == sv_lang::ArgumentDirection::In {
releases.push(ArgBinding::Release { var: *formal });
}
}
}
if let Some((this_var, _)) = this {
self.this_stack.push(this_var);
}
let n_aliases = aliases.len();
self.ref_stack.extend(aliases);
let body_entry = if this.is_some() || n_aliases > 0 {
let b = self.alloc_block();
self.close(entry, Terminator::Fallthrough(b));
b
} else {
entry
};
for local in task_locals(&task) {
if this.is_some_and(|(t, _)| t.uid() == local.uid()) {
continue;
}
if crate::netlist::is_static_procedural_var(&local) {
continue;
}
if !is_automatic_block_var(&local) {
self.blocks[body_entry.0 as usize]
.bindings
.push(ArgBinding::Init { var: local });
releases.push(ArgBinding::Release { var: local });
}
self.inlined_locals.push(local);
}
let ret_var = if task.subroutine_kind() == Some(sv_lang::SubroutineKind::Function)
&& task.subroutine_return_type().is_some_and(|t| !t.is_void())
{
task.subroutine_return_val_var()
} else {
None
};
if let Some(rv) = ret_var {
self.blocks[body_entry.0 as usize]
.bindings
.push(ArgBinding::Init { var: rv });
self.inlined_locals.push(rv);
}
for v in crate::patterns::pattern_vars(body.as_sem_node()) {
if !self.inlined_locals.iter().any(|l| l.uid() == v.uid()) {
self.inlined_locals.push(v);
}
}
let frame_vars: Vec<Symbol<'d>> = self.inlined_locals[frame_mark..]
.iter()
.copied()
.filter(|v| this.is_none_or(|(t, _)| t.uid() != v.uid()))
.filter(|v| !bound_refs.iter().any(|(f, _)| f.uid() == v.uid()))
.collect();
self.frame_probe(
body_entry,
FrameProbe::Enter {
task,
vars: frame_vars.clone(),
bound: bound_refs,
},
);
self.block_event(body_entry, task, false);
self.event_stack.push((task, self.disable_targets.len()));
self.frame_stack.push((task, frame_vars.clone()));
self.inline_stack.push(task.uid());
self.return_targets.push(after);
self.ret_var_stack.push(ret_var);
let task_name = task.name().to_string();
let task_scoped = is_disabled_somewhere(&task_name);
let mut n_tags = 0;
if task_scoped {
self.scope_chain
.push((task_name.clone(), after, self.frame_stack.len()));
self.disable_targets
.push((task_name, after, self.frame_stack.len()));
for tag in crate::netlist::itf::extern_scope_tags(&task) {
self.scope_chain.push((tag, after, self.frame_stack.len()));
n_tags += 1;
}
}
let exit = self.walk_stmt(body, body_entry);
if task_scoped {
self.scope_chain.pop();
self.disable_targets.pop();
for _ in 0..n_tags {
self.scope_chain.pop();
}
}
self.event_stack.pop();
self.return_targets.pop();
self.ret_var_stack.pop();
self.inline_stack.pop();
self.frame_stack.pop();
if this.is_some() {
self.this_stack.pop();
}
self.ref_stack.truncate(self.ref_stack.len() - n_aliases);
let exit = exit?;
self.close(exit, Terminator::Fallthrough(after));
self.blocks[after.0 as usize].bindings.extend(copy_backs);
self.blocks[after.0 as usize].bindings.extend(releases);
self.block_event(after, task, true);
self.frame_probe(
after,
FrameProbe::Exit {
task,
vars: frame_vars,
},
);
Ok(after)
}
fn walk_immediate_assertion(
&mut self,
stmt: Statement<'d>,
(pass_stmt, fail_stmt): (Option<Statement<'d>>, Option<Statement<'d>>),
cur: BlockId,
) -> Result<BlockId, LoweringError> {
if stmt.is_deferred_assertion() == Some(true) {
return Err(LoweringError::unsupported(
"a deferred assertion (LRM 16.4) whose action block waits (the action must be \
a subroutine call that is queued)"
.to_string(),
));
}
let pass_blk = self.alloc_block();
let fail_blk = self.alloc_block();
let done_blk = self.alloc_block();
self.close(
cur,
Terminator::Assertion {
stmt,
pass_blk,
fail_blk,
done_blk,
},
);
for (action, blk) in [(pass_stmt, pass_blk), (fail_stmt, fail_blk)] {
let exit = match action {
Some(a) => self.walk_stmt(a, blk)?,
None => blk,
};
self.close(exit, Terminator::Fallthrough(done_blk));
}
Ok(done_blk)
}
fn walk_case(&mut self, stmt: Statement<'d>, cur: BlockId) -> Result<BlockId, LoweringError> {
let is_pattern = stmt.kind() == StatementKind::PatternCase;
if !is_pattern && let Some(live) = constant_case_live_arm(&stmt) {
return match live {
Some(arm) => self.walk_stmt(arm, cur),
None => Ok(cur),
};
}
let arm_stmts: Vec<Statement<'d>> = if is_pattern {
stmt.pattern_case_items().iter().map(|i| i.stmt()).collect()
} else {
stmt.case_items().iter().map(|i| i.stmt()).collect()
};
let arms: Vec<BlockId> = arm_stmts.iter().map(|_| self.alloc_block()).collect();
let default_blk = self.alloc_block();
let join = self.alloc_block();
self.close(
cur,
Terminator::Case {
stmt,
arms: arms.clone(),
default_blk,
},
);
for (arm_stmt, &arm) in arm_stmts.iter().zip(&arms) {
let exit = self.walk_stmt(*arm_stmt, arm)?;
self.close(exit, Terminator::Fallthrough(join));
}
let default_stmt = if is_pattern {
stmt.pattern_case_default()
} else {
stmt.default_case()
};
let default_exit = match default_stmt {
Some(d) => self.walk_stmt(d, default_blk)?,
None => default_blk,
};
self.close(default_exit, Terminator::Fallthrough(join));
Ok(join)
}
fn walk_loop_body(
&mut self,
body_stmt: Statement<'d>,
body: BlockId,
cont: BlockId,
brk: BlockId,
) -> Result<BlockId, LoweringError> {
self.loop_targets.push((cont, brk));
let open = self.owned_scopes.len();
self.target_depth.insert(cont, open);
self.target_depth.insert(brk, open);
let exit = self.walk_stmt(body_stmt, body);
self.loop_targets.pop();
exit
}
fn walk_block(&mut self, stmt: Statement<'d>, cur: BlockId) -> Result<BlockId, LoweringError> {
if !self.entering_block && self_disabling_assertion_block(&stmt) {
self.push(cur, stmt);
return Ok(cur);
}
let name = (!self.entering_block
&& stmt.block_kind() == Some(StatementBlockKind::Sequential))
.then(|| block_name(&stmt))
.filter(|n| !n.is_empty());
let pushed = name.is_some();
if let Some(n) = name {
self.name_path.push(n);
}
let blk = (!self.entering_block
&& stmt.block_kind() == Some(StatementBlockKind::Sequential))
.then(|| stmt.block_symbol())
.flatten()
.filter(|b| !b.name().is_empty());
if let Some(b) = blk {
self.block_event(cur, b, false);
self.event_stack.push((b, self.disable_targets.len()));
}
let r = self.walk_block_inner(stmt, cur);
if blk.is_some() {
self.event_stack.pop();
}
if pushed {
self.name_path.pop();
}
if let (Some(b), Ok(end)) = (blk, &r) {
self.block_event(*end, b, true);
}
r
}
fn walk_block_inner(
&mut self,
stmt: Statement<'d>,
cur: BlockId,
) -> Result<BlockId, LoweringError> {
match stmt.block_kind() {
Some(_) if !self.entering_block && !block_automatic_vars(&stmt, true).is_empty() => {
let fresh = &self.blocks[cur.0 as usize];
let entry = if fresh.stmts.is_empty() && fresh.step_exprs.is_empty() {
cur
} else {
let entry = self.alloc_block();
self.close(cur, Terminator::Fallthrough(entry));
entry
};
for var in block_automatic_vars(&stmt, true) {
self.blocks[entry.0 as usize]
.bindings
.push(ArgBinding::Init { var });
}
let owned: Vec<Symbol<'d>> =
if stmt.block_kind() == Some(StatementBlockKind::Sequential) {
block_automatic_vars(&stmt, true)
.into_iter()
.filter(|v| v.value_type().is_some_and(|t| may_hold_handles(&t)))
.collect()
} else {
Vec::new()
};
let scoped = !owned.is_empty();
if scoped {
self.owned_scopes.push(owned.clone());
}
self.entering_block = true;
let r = self.walk_block(stmt, entry);
self.entering_block = false;
if !scoped {
return r;
}
self.owned_scopes.pop();
let end = r?;
let leave = self.alloc_block();
self.close(end, Terminator::Fallthrough(leave));
for var in owned.into_iter().rev() {
self.blocks[leave.0 as usize]
.bindings
.push(ArgBinding::Release { var });
}
Ok(leave)
}
Some(StatementBlockKind::Sequential)
if let name = block_name(&stmt)
&& !name.is_empty()
&& (is_disabled_somewhere(&name)
|| is_disabled_somewhere(&self.name_path.join("."))
|| contains_disable_through_calls(stmt.as_sem_node())) =>
{
self.entering_block = false;
let exit = self.alloc_block();
self.target_depth.insert(exit, self.owned_scopes.len());
let scoped = is_disabled_somewhere(&name);
if scoped {
self.scope_chain
.push((name.clone(), exit, self.frame_stack.len()));
}
let dotted = self.name_path.join(".");
let path_scoped = dotted.contains('.') && is_disabled_somewhere(&dotted);
if path_scoped {
self.scope_chain
.push((dotted.clone(), exit, self.frame_stack.len()));
}
let fork_key = self
.fork_name
.as_ref()
.map(|f| format!("{f}.{dotted}"))
.filter(|k| is_disabled_somewhere(k));
let fork_scoped = fork_key.is_some();
if let Some(key) = fork_key {
self.scope_chain.push((key, exit, self.frame_stack.len()));
}
self.disable_targets
.push((name, exit, self.frame_stack.len()));
let end = self.walk_seq(live_statements(&stmt), cur);
self.disable_targets.pop();
if fork_scoped {
self.scope_chain.pop();
}
if path_scoped {
self.scope_chain.pop();
}
if scoped {
self.scope_chain.pop();
}
self.close(end?, Terminator::Fallthrough(exit));
Ok(exit)
}
Some(StatementBlockKind::Sequential) => {
self.entering_block = false;
self.walk_seq(live_statements(&stmt), cur)
}
Some(
kind @ (StatementBlockKind::JoinAll
| StatementBlockKind::JoinAny
| StatementBlockKind::JoinNone),
) => {
let branch_stmts = live_statements(&stmt);
let branch_stmts = match branch_stmts.as_slice() {
[only] if only.kind() == StatementKind::List => live_statements(only),
_ => branch_stmts,
};
self.entering_block = false;
let fork_name = Some(block_name(&stmt)).filter(|n| !n.is_empty());
let new_branch_names: Vec<String> = branch_stmts
.iter()
.filter_map(|s| peer_block(s).map(|p| p.name))
.collect();
let known_fork_labels: Vec<String> = self
.fork_branch_names
.iter()
.filter(|n| !self.fork_block_names.contains(n))
.cloned()
.chain(new_branch_names.iter().cloned())
.collect();
let ancestor_blocks: Vec<String> = self
.ancestor_blocks
.iter()
.cloned()
.chain(self.disable_targets.iter().map(|(n, _, _)| n.clone()))
.collect();
let memoizable = self.rec.is_none()
&& self.rec_up.is_empty()
&& self.global_clock.is_none()
&& self.checker_clock.is_none();
let mut branches: Vec<ProcessCfg<'d>> = branch_stmts
.into_iter()
.map(|branch_stmt| {
let key = memoizable.then(|| {
MemoKey::Fork(ForkBranchKey {
stmt: (stmt_addr(&branch_stmt), branch_stmt.kind() as u16),
fork_name: fork_name.clone(),
default_clocking: self.default_clocking.map(|c| c.uid()),
this_var: self.this_stack.last().map(|t| t.uid()),
ref_aliases: self
.ref_stack
.iter()
.map(|(a, b)| (a.uid(), b.uid()))
.collect(),
rec_outer: self.rec_outer.clone(),
labels: known_fork_labels.clone(),
ancestors: ancestor_blocks.clone(),
})
});
if let Some(k) = &key
&& let Some(hit) = self.fork_memo.borrow().get(k)
{
return Ok(hit.clone());
}
let mut cfg = build_cfg_in(
&branch_stmt,
ProcessLoopKind::OneShot,
fork_name.clone(),
self.default_clocking,
None,
self.checker_clock,
Inherited {
this_var: self.this_stack.last().copied(),
ref_aliases: self.ref_stack.clone(),
global_clock: self.global_clock,
detached_trigger: false,
forced_extern: None,
rec_outer: self.rec_outer.clone(),
fork_memo: Some(self.fork_memo.clone()),
rec: self.rec.clone(),
rec_up: self.rec_up.clone(),
known_fork_labels: known_fork_labels.clone(),
ancestor_blocks: ancestor_blocks.clone(),
},
self.virtual_tasks.clone(),
)?;
if let Some(k) = key {
cfg.share_id = Some(
NEXT_SHARE_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
);
self.fork_memo.borrow_mut().insert(k, cfg.clone());
}
Ok(cfg)
})
.collect::<Result<_, LoweringError>>()?;
for b in &mut branches {
b.captured = captured_vars(b);
b.captured
.retain(|c| !self.ref_stack.iter().any(|(f, _)| f.uid() == c.uid()));
if let Some(t) = self.this_stack.last()
&& !b.captured.iter().any(|c| c.uid() == t.uid())
{
b.captured.push(*t);
}
}
self.fork_branch_names.extend(new_branch_names);
if let Some(f) = &fork_name {
self.fork_branch_names.push(f.clone());
self.fork_block_names.push(f.clone());
}
let join_blk = self.alloc_block();
self.close(
cur,
Terminator::ForkJoin {
kind,
branches,
join_blk,
},
);
Ok(join_blk)
}
None => Err(LoweringError::malformed(
"Block statement with no block_kind (unexpected AST shape)",
)),
}
}
fn walk_seq(
&mut self,
stmts: Vec<Statement<'d>>,
mut cur: BlockId,
) -> Result<BlockId, LoweringError> {
let mut released = false;
for s in stmts {
let s = unwrap_invalid(s);
match self.default_clocking_wait(&s) {
Some(0) if released => {
if let Some(body) = s.body()
&& body.kind() != StatementKind::Empty
{
cur = self.walk_stmt(body, cur)?;
}
continue;
}
Some(_) => released = s.body().is_none_or(|b| b.kind() == StatementKind::Empty),
None => {
released = released && Self::untimed_simple(&s);
}
}
cur = self.walk_stmt(s, cur)?;
}
Ok(cur)
}
fn default_clocking_wait(&self, stmt: &Statement<'d>) -> Option<u32> {
let cb = self.default_clocking?;
if stmt.kind() != StatementKind::Timed {
return None;
}
let tc = stmt.timing_control()?;
if tc.kind() == TimingControlKind::CycleDelay {
first_expression_child(&tc)?
.constant_value()?
.as_i64()
.and_then(|v| u32::try_from(v).ok())
} else if tc.kind() == TimingControlKind::SignalEvent && tc.edge() == EdgeKind::None {
let sym = first_expression_child(&tc)?.referenced_symbol()?;
(sym.kind() == SymbolKind::ClockingBlock && sym.uid() == cb.uid()).then_some(1)
} else {
None
}
}
fn untimed_simple(stmt: &Statement<'d>) -> bool {
stmt.kind() == StatementKind::ExpressionStatement
&& stmt.expr().is_some_and(|e| {
(e.kind() == ExpressionKind::Call && e.is_system_call() == Some(true))
|| (e.kind() == ExpressionKind::Assignment && e.assignment_timing().is_none())
})
}
fn walk_conditional(
&mut self,
stmt: Statement<'d>,
cur: BlockId,
) -> Result<BlockId, LoweringError> {
if violation_check(&stmt).is_some() || is_clause_if(&stmt) {
return self.walk_checked_if(stmt, cur);
}
let cond = stmt.expressions().into_iter().next().ok_or_else(|| {
LoweringError::malformed("Conditional statement with no condition expression")
})?;
let then_stmt = stmt
.then_branch()
.ok_or_else(|| LoweringError::malformed("Conditional statement with no then-branch"))?;
let else_stmt = stmt.else_branch();
if let Some(taken) = constant_if_arm(&stmt) {
return match if taken { Some(then_stmt) } else { else_stmt } {
Some(live) => self.walk_stmt(live, cur),
None => Ok(cur),
};
}
let cur = self
.hoist_suspending_calls_in(cond.as_sem_node(), cur)?
.unwrap_or(cur);
let then_blk = self.alloc_block();
let else_blk = self.alloc_block();
self.close(
cur,
Terminator::Branch {
cond,
then_blk,
else_blk,
},
);
let then_exit = self.walk_stmt(then_stmt, then_blk)?;
let else_exit = match else_stmt {
Some(e) => self.walk_stmt(e, else_blk)?,
None => else_blk,
};
let join = self.alloc_block();
self.close(then_exit, Terminator::Fallthrough(join));
self.close(else_exit, Terminator::Fallthrough(join));
Ok(join)
}
fn walk_checked_if(
&mut self,
stmt: Statement<'d>,
cur: BlockId,
) -> Result<BlockId, LoweringError> {
let chain = if_series(stmt).map_err(|why| {
LoweringError::unsupported(format!("{why} in an if series (malformed statement)"))
})?;
let arms: Vec<BlockId> = chain.thens.iter().map(|_| self.alloc_block()).collect();
let default_blk = self.alloc_block();
let join = self.alloc_block();
self.close(
cur,
Terminator::Case {
stmt,
arms: arms.clone(),
default_blk,
},
);
for (then_stmt, &arm) in chain.thens.iter().zip(&arms) {
let exit = self.walk_stmt(*then_stmt, arm)?;
self.close(exit, Terminator::Fallthrough(join));
}
let default_exit = match chain.otherwise {
Some(e) => self.walk_stmt(e, default_blk)?,
None => default_blk,
};
self.close(default_exit, Terminator::Fallthrough(join));
Ok(join)
}
fn walk_timed(&mut self, stmt: Statement<'d>, cur: BlockId) -> Result<BlockId, LoweringError> {
let tc = stmt
.timing_control()
.ok_or_else(|| LoweringError::malformed("Timed statement with no timing control"))?;
if !class_event_wait_terms(&tc)?.is_empty() && class_event_wait_is_mixed(&tc) {
let guarded = stmt
.body()
.ok_or_else(|| LoweringError::malformed("Timed statement with no guarded body"))?;
self.skip_class_event_terms = true;
let point = self.suspend_point(tc, None);
self.skip_class_event_terms = false;
let point = point?;
let term_iffs = class_event_term_iffs(&tc);
if term_iffs.iter().any(Option::is_some) {
if term_iffs.len() > 64 {
return Err(LoweringError::unsupported(
"an `@(a or b ...)` list of more than 64 class `event` terms with an `iff`",
));
}
let header = self.alloc_block();
let body = self.alloc_block();
let exit = self.alloc_block();
let resume = self.alloc_block();
self.blocks[resume.0 as usize]
.bindings
.push(ArgBinding::ClassEventLeave);
self.close(
cur,
Terminator::Loop {
header,
body,
exit,
entry: header,
},
);
self.close(header, Terminator::Fallthrough(body));
self.push(body, stmt);
self.close(
body,
Terminator::Suspend {
point,
resume_blk: resume,
},
);
let first = self.alloc_block();
self.close(
resume,
Terminator::Switch {
on: SwitchOn::ClassFired(None),
arms: vec![exit, first],
default_blk: exit,
report: None,
},
);
let mut at = first;
for (k, iff) in term_iffs.iter().enumerate() {
let chk = self.alloc_block();
let next = if k + 1 == term_iffs.len() {
header
} else {
self.alloc_block()
};
self.close(
at,
Terminator::Switch {
on: SwitchOn::ClassFired(Some(k as u32)),
arms: vec![next, chk],
default_blk: next,
report: None,
},
);
match iff {
Some(cond) => self.close(
chk,
Terminator::Branch {
cond: *cond,
then_blk: exit,
else_blk: next,
},
),
None => self.close(chk, Terminator::Fallthrough(exit)),
}
at = next;
}
return self.walk_stmt(guarded, exit);
}
self.push(cur, stmt);
let resume = self.alloc_block();
self.blocks[resume.0 as usize]
.bindings
.push(ArgBinding::ClassEventLeave);
self.close(
cur,
Terminator::Suspend {
point,
resume_blk: resume,
},
);
return self.walk_stmt(guarded, resume);
}
if !class_event_wait_terms(&tc)?.is_empty() {
let guarded = stmt
.body()
.ok_or_else(|| LoweringError::malformed("Timed statement with no guarded body"))?;
let term_iffs = class_event_term_iffs(&tc);
if term_iffs.iter().any(Option::is_some) {
if term_iffs.len() > 64 {
return Err(LoweringError::unsupported(
"an `@(a or b ...)` list of more than 64 class `event` terms with an `iff`",
));
}
let header = self.alloc_block();
let body = self.alloc_block();
let exit = self.alloc_block();
let resume = self.alloc_block();
self.close(
cur,
Terminator::Loop {
header,
body,
exit,
entry: header,
},
);
self.close(header, Terminator::Fallthrough(body));
self.push(body, stmt);
self.close(
body,
Terminator::Suspend {
point: SuspendPoint::ByRequest,
resume_blk: resume,
},
);
let mut at = resume;
for (k, iff) in term_iffs.iter().enumerate() {
let chk = self.alloc_block();
let next = if k + 1 == term_iffs.len() {
header
} else {
self.alloc_block()
};
self.close(
at,
Terminator::Switch {
on: SwitchOn::FiredBit(k as u32),
arms: vec![next, chk],
default_blk: next,
report: None,
},
);
match iff {
Some(cond) => self.close(
chk,
Terminator::Branch {
cond: *cond,
then_blk: exit,
else_blk: next,
},
),
None => self.close(chk, Terminator::Fallthrough(exit)),
}
at = next;
}
return self.walk_stmt(guarded, exit);
}
if let Some(iff) = extract_iff(&tc) {
let header = self.alloc_block();
let body = self.alloc_block();
let exit = self.alloc_block();
let resume = self.alloc_block();
self.close(
cur,
Terminator::Loop {
header,
body,
exit,
entry: header,
},
);
self.close(header, Terminator::Fallthrough(body));
self.push(body, stmt);
self.close(
body,
Terminator::Suspend {
point: SuspendPoint::ByRequest,
resume_blk: resume,
},
);
self.close(
resume,
Terminator::Branch {
cond: iff,
then_blk: exit,
else_blk: header,
},
);
return self.walk_stmt(guarded, exit);
}
self.push(cur, stmt);
let resume = self.alloc_block();
self.close(
cur,
Terminator::Suspend {
point: SuspendPoint::ByRequest,
resume_blk: resume,
},
);
return self.walk_stmt(guarded, resume);
}
self.allow_dynamic_cycle = true;
let point = self.suspend_point(tc, stmt.body());
self.allow_dynamic_cycle = false;
let point = point?;
let guarded = stmt
.body()
.ok_or_else(|| LoweringError::malformed("Timed statement with no guarded body"))?;
if let Some(count) = self.dynamic_cycle_count.take() {
let entry = self.alloc_block();
self.close(cur, Terminator::Fallthrough(entry));
let counter = self.alloc_repeat_counter();
self.set_repeat_init(entry, counter, RepeatCount::Expr(count));
let header = self.alloc_block();
let body = self.alloc_block();
let exit = self.alloc_block();
let resume = self.alloc_block();
self.close(
entry,
Terminator::Loop {
header,
body,
exit,
entry: header,
},
);
self.close(
header,
Terminator::RepeatCheck {
counter,
then_blk: body,
else_blk: exit,
},
);
self.close(
body,
Terminator::Suspend {
point,
resume_blk: resume,
},
);
self.set_repeat_decrement(resume, counter);
self.close(resume, Terminator::Fallthrough(header));
return self.walk_stmt(guarded, exit);
}
let resume = self.alloc_block();
self.close(
cur,
Terminator::Suspend {
point,
resume_blk: resume,
},
);
self.walk_stmt(guarded, resume)
}
fn walk_intra_assignment_timing(
&mut self,
assign: Expression<'d>,
cur: BlockId,
) -> Result<BlockId, LoweringError> {
let tc = assign
.assignment_timing()
.and_then(|n| n.as_timing_control())
.ok_or_else(|| LoweringError::malformed("assignment timing is not a timing control"))?;
let target = assign
.left()
.ok_or_else(|| LoweringError::malformed("assignment with no target"))?;
let value = assign
.right()
.ok_or_else(|| LoweringError::malformed("assignment with no value"))?;
let temp = self.captures.len() as u32;
self.captures.push(target);
let capture = self.alloc_block();
self.close(cur, Terminator::Fallthrough(capture));
self.blocks[capture.0 as usize]
.bindings
.push(ArgBinding::Capture { temp, value });
self.drive_clocking = drive_clocking_of(target);
self.drive_via = drive_via_of(target);
let after = if tc.kind() == TimingControlKind::RepeatedEvent {
let count = first_expression_child(&tc).ok_or_else(|| {
LoweringError::malformed("repeated event control with no count expression")
})?;
let event = tc
.children()
.into_iter()
.find_map(|n| n.as_timing_control())
.ok_or_else(|| {
LoweringError::malformed("repeated event control with no event control")
})?;
let point = self.suspend_point(event, None)?;
let counter = self.alloc_repeat_counter();
self.set_repeat_init(capture, counter, RepeatCount::Expr(count));
let header = self.alloc_block();
let body = self.alloc_block();
let exit = self.alloc_block();
let resume = self.alloc_block();
self.close(
capture,
Terminator::Loop {
header,
body,
exit,
entry: header,
},
);
self.close(
header,
Terminator::RepeatCheck {
counter,
then_blk: body,
else_blk: exit,
},
);
self.close(
body,
Terminator::Suspend {
point,
resume_blk: resume,
},
);
self.set_repeat_decrement(resume, counter);
self.close(resume, Terminator::Fallthrough(header));
exit
} else {
let point = self.suspend_point(tc, None)?;
let resume = self.alloc_block();
self.close(
capture,
Terminator::Suspend {
point,
resume_blk: resume,
},
);
resume
};
self.drive_clocking = None;
self.drive_via = None;
self.blocks[after.0 as usize]
.bindings
.push(ArgBinding::AssignCaptured {
temp,
target,
nba: false,
});
Ok(after)
}
fn walk_nba_event(
&mut self,
stmt: Statement<'d>,
assign: Expression<'d>,
cur: BlockId,
) -> Result<BlockId, LoweringError> {
let tc = assign
.assignment_timing()
.and_then(|n| n.as_timing_control())
.ok_or_else(|| LoweringError::malformed("assignment timing is not a timing control"))?;
let target = assign
.left()
.ok_or_else(|| LoweringError::malformed("assignment with no target"))?;
let value = assign
.right()
.ok_or_else(|| LoweringError::malformed("assignment with no value"))?;
let temp = self.captures.len() as u32;
self.captures.push(target);
let capture = self.alloc_block();
self.close(cur, Terminator::Fallthrough(capture));
self.blocks[capture.0 as usize]
.bindings
.push(ArgBinding::Capture { temp, value });
let mut b = CfgBuilder::new();
b.this_stack.extend(self.this_stack.last().copied());
b.ref_stack = self.ref_stack.clone();
b.global_clock = self.global_clock;
b.virtual_tasks = self.virtual_tasks.clone();
b.fork_branch_names = self.fork_branch_names.clone();
b.ancestor_blocks = self.ancestor_blocks.clone();
b.default_clocking = self.default_clocking;
b.drive_clocking = drive_clocking_of(target);
b.drive_via = drive_via_of(target);
b.checker_clock = self.checker_clock;
b.captures.push(target);
let mut inherited = vec![InheritedSlot::Temp {
parent: temp,
child: 0,
}];
let entry = b.alloc_block();
let event_roots = vec![target.as_sem_node(), tc.as_sem_node()];
let mut dyn_cycle: Option<(Expression<'d>, SuspendPoint)> = None;
let mut const_point: Option<SuspendPoint> = None;
if tc.kind() == TimingControlKind::CycleDelay {
b.allow_dynamic_cycle = true;
let point = b.suspend_point(tc, None);
b.allow_dynamic_cycle = false;
let point = point?;
match b.dynamic_cycle_count.take() {
Some(count) => dyn_cycle = Some((count, point)),
None => const_point = Some(point),
}
}
let after = if dyn_cycle.is_some() || tc.kind() == TimingControlKind::RepeatedEvent {
let (count, point) = if let Some(d) = dyn_cycle.take() {
d
} else {
let count = first_expression_child(&tc).ok_or_else(|| {
LoweringError::malformed("repeated event control with no count expression")
})?;
let event = tc
.children()
.into_iter()
.find_map(|n| n.as_timing_control())
.ok_or_else(|| {
LoweringError::malformed("repeated event control with no event control")
})?;
(count, b.suspend_point(event, None)?)
};
let parent_counter = self.alloc_repeat_counter();
self.set_repeat_init(capture, parent_counter, RepeatCount::Expr(count));
let counter = b.alloc_repeat_counter();
inherited.push(InheritedSlot::Counter {
parent: parent_counter,
child: counter,
});
b.set_repeat_init(entry, counter, RepeatCount::Inherited(count));
let header = b.alloc_block();
let body = b.alloc_block();
let exit = b.alloc_block();
let resume = b.alloc_block();
b.close(
entry,
Terminator::Loop {
header,
body,
exit,
entry: header,
},
);
b.close(
header,
Terminator::RepeatCheck {
counter,
then_blk: body,
else_blk: exit,
},
);
b.close(
body,
Terminator::Suspend {
point,
resume_blk: resume,
},
);
b.set_repeat_decrement(resume, counter);
b.close(resume, Terminator::Fallthrough(header));
exit
} else {
let point = match const_point.take() {
Some(p) => p,
None => b.suspend_point(tc, None)?,
};
let resume = b.alloc_block();
b.close(
entry,
Terminator::Suspend {
point,
resume_blk: resume,
},
);
resume
};
b.blocks[after.0 as usize]
.bindings
.push(ArgBinding::AssignCaptured {
temp: 0,
target,
nba: true,
});
b.close(after, Terminator::End { loop_back: None });
let inlined_locals = std::mem::take(&mut b.inlined_locals);
let captures = std::mem::take(&mut b.captures);
let ref_bindings = b.take_ref_bindings();
let disable_scopes = b.take_disable_scopes();
let disable_broadcast = std::mem::take(&mut b.disable_broadcast);
let fork_branch_names = std::mem::take(&mut b.fork_branch_names);
let mut branch = ProcessCfg {
blocks: b.finish(),
entry,
body: stmt,
inlined_locals,
captures,
captured: Vec::new(),
ref_bindings,
disable_names: Vec::new(),
fork_branch_names,
disable_scopes,
disable_broadcast,
inherited,
capture_types: Vec::new(),
recursion: Recursion::None,
share_id: None,
};
branch.captured = captured_in(event_roots);
branch
.captured
.retain(|c| !self.ref_stack.iter().any(|(f, _)| f.uid() == c.uid()));
if let Some(t) = self.this_stack.last()
&& !branch.captured.iter().any(|c| c.uid() == t.uid())
{
branch.captured.push(*t);
}
let join_blk = self.alloc_block();
self.close(
capture,
Terminator::ForkJoin {
kind: StatementBlockKind::JoinNone,
branches: vec![branch],
join_blk,
},
);
Ok(join_blk)
}
fn resolve_inferred_clock(
&self,
signal: Expression<'d>,
edge: EdgeKind,
) -> Result<InferredClockEvent<'d>, LoweringError> {
if is_global_clock_call(&signal) {
return self.global_clock.ok_or_else(|| {
LoweringError::unsupported(
"`@$global_clock` as a procedural event control needs a `global clocking` \
declared in the process's instance or an enclosing one (LRM 14.14); none \
is in scope",
)
});
}
if !is_inferred_clock_call(&signal) {
return Ok((signal, edge, None));
}
self.checker_clock.ok_or_else(|| {
LoweringError::unsupported(
"`$inferred_clock` (LRM 17.4) as the event control of a checker procedure: the \
checker is instantiated where no default clocking applies (14.12), so no \
clock is inferred",
)
})
}
fn suspend_point(
&mut self,
tc: TimingControl<'d>,
guarded: Option<Statement<'d>>,
) -> Result<SuspendPoint<'d>, LoweringError> {
let tc_kind = tc.kind();
let point = if tc_kind == TimingControlKind::Invalid {
return Err(LoweringError::malformed("Invalid timing control kind"));
} else if tc_kind == TimingControlKind::Delay {
let expr = first_expression_child(&tc).ok_or_else(|| {
LoweringError::malformed("Delay timing control with no delay expression")
})?;
SuspendPoint::Delay(expr, self.frame_stack.last().map(|f| f.0))
} else if tc_kind == TimingControlKind::SignalEvent {
let edge = tc.edge();
let signal = first_expression_child(&tc).ok_or_else(|| {
LoweringError::malformed("SignalEvent timing control with no signal expression")
})?;
reject_chandle_event(&signal)?;
let iff = extract_iff(&tc);
let (signal, edge, inferred_iff) = self.resolve_inferred_clock(signal, edge)?;
if let (Some(a), Some(b)) = (iff, inferred_iff) {
if edge == EdgeKind::PosEdge || edge == EdgeKind::NegEdge {
self.note_edge_signal(signal, edge);
}
return Ok(SuspendPoint::EdgeList(
vec![(signal, edge)],
vec![vec![a, b]],
));
}
let iff = iff.or(inferred_iff);
if edge != EdgeKind::PosEdge && edge != EdgeKind::NegEdge {
if iff.is_some() {
SuspendPoint::Edge { signal, edge, iff }
} else {
SuspendPoint::EdgeList(vec![(signal, edge)], vec![Vec::new()])
}
} else {
self.note_edge_signal(signal, edge);
SuspendPoint::Edge { signal, edge, iff }
}
} else if tc_kind == TimingControlKind::EventList {
let mut entries = Vec::new();
let mut iffs = Vec::new();
for child in tc.children() {
let sub_tc = child.as_timing_control().ok_or_else(|| {
LoweringError::malformed(
"EventList timing control with a non-timing-control child",
)
})?;
let edge = sub_tc.edge();
let signal = first_expression_child(&sub_tc).ok_or_else(|| {
LoweringError::malformed(
"EventList member timing control with no signal expression",
)
})?;
if self.skip_class_event_terms && is_class_event_expr(&signal) {
continue;
}
reject_chandle_event(&signal)?;
let iff = extract_iff(&sub_tc);
let (signal, edge, inferred_iff) = self.resolve_inferred_clock(signal, edge)?;
if edge == EdgeKind::PosEdge || edge == EdgeKind::NegEdge {
self.note_edge_signal(signal, edge);
}
entries.push((signal, edge));
iffs.push(iff.into_iter().chain(inferred_iff).collect());
}
if entries.is_empty() {
return Err(LoweringError::malformed(
"EventList timing control with no member event controls",
));
}
SuspendPoint::EdgeList(entries, iffs)
} else if tc_kind == TimingControlKind::CycleDelay {
let count_expr = first_expression_child(&tc).ok_or_else(|| {
LoweringError::malformed("CycleDelay timing control with no count expression")
})?;
let constant = count_expr
.constant_value()
.and_then(|v| v.as_i64())
.and_then(|v| u32::try_from(v).ok());
let count = match constant {
Some(c) => c,
None if self.allow_dynamic_cycle => {
self.dynamic_cycle_count = Some(count_expr);
1
}
None => {
return Err(LoweringError::unsupported(
"a non-constant cycle delay `##(expr)` in an intra-assignment timing \
control (LRM 14.11 allows a run-time count only as a statement \
delay; this simulator needs a compile-time constant here)",
));
}
};
let (clock, edge) = match (
self.drive_clocking.or(self.default_clocking),
self.inferred_clock,
) {
(Some(cb), _) => (cb, EdgeKind::BothEdges),
(None, Some(Some((signal, edge)))) => (
signal.referenced_symbol().ok_or_else(|| {
LoweringError::unsupported("CycleDelay (`##N`) clock with no symbol")
})?,
edge,
),
_ => {
return Err(LoweringError::unsupported(
"CycleDelay (`##N`) with no unambiguous single inferred clock in scope \
(no enclosing posedge/negedge edge, or more than one distinct clock \
signal/edge seen) -- ARCHITECTURE §6.5's own \"ClockDomain the scheduler \
needs\" requirement",
));
}
};
let via = if self.drive_clocking.is_some() {
self.drive_via
} else {
None
};
SuspendPoint::CycleDelay {
count,
clock,
edge,
via,
}
} else if tc_kind == TimingControlKind::ImplicitEvent {
let body_stmt = guarded.ok_or_else(|| {
LoweringError::unsupported(
"an `@*` event control with no guarded statement (an intra-assignment `@*`)",
)
})?;
SuspendPoint::ImplicitComb(implicit_comb_read_set(&body_stmt))
} else if tc_kind == TimingControlKind::RepeatedEvent {
return Err(LoweringError::unsupported(
"RepeatedEvent timing control (`repeat(n) @(...)`) can never be the outer \
TimingControlKind of a Timed statement -- `repeat` at statement-position \
always parses as StatementKind::RepeatLoop instead, and RepeatedEventControl \
is only ever producible as an intra-assignment timing control, which \
walk_intra_assignment_timing lowers",
));
} else if tc_kind == TimingControlKind::OneStepDelay {
SuspendPoint::OneStep
} else if tc_kind == TimingControlKind::BlockEventList {
return Err(LoweringError::unsupported(
"a block event list `@@(...)` as a procedural timing control (LRM 19.3: \
only a covergroup sampling event)",
));
} else if tc_kind == TimingControlKind::Delay3 {
return Err(LoweringError::unsupported(
"Delay3 timing control (gate-primitive rise/fall/turn-off delay syntax -- not \
applicable to a procedural statement's suspend point)",
));
} else {
return Err(LoweringError::malformed(format!(
"unrecognized TimingControlKind {tc_kind:?} (sv-lang-kinds version drift -- \
see determinism::kind_guard's TimingControlKind pin)"
)));
};
Ok(point)
}
fn walk_wait(&mut self, stmt: Statement<'d>, cur: BlockId) -> Result<BlockId, LoweringError> {
let cond = stmt.condition().ok_or_else(|| {
LoweringError::malformed("Wait statement with no condition expression")
})?;
let resume = self.alloc_block();
let suspend_blk = self.alloc_block();
self.close(
cur,
Terminator::Branch {
cond,
then_blk: resume,
else_blk: suspend_blk,
},
);
self.close(
suspend_blk,
Terminator::Suspend {
point: SuspendPoint::Level(cond),
resume_blk: resume,
},
);
let guarded = stmt
.body()
.ok_or_else(|| LoweringError::malformed("Wait statement with no guarded body"))?;
self.walk_stmt(guarded, resume)
}
fn walk_event_trigger(
&mut self,
stmt: Statement<'d>,
cur: BlockId,
) -> Result<BlockId, LoweringError> {
let detached = std::mem::take(&mut self.detached_trigger);
let Some(timing_node) = stmt.timing() else {
self.push(cur, stmt);
return Ok(cur);
};
let tc = timing_node.as_timing_control().ok_or_else(|| {
LoweringError::malformed("EventTrigger timing is not a TimingControl node")
})?;
if tc.kind() == TimingControlKind::Delay {
self.push(cur, stmt);
return Ok(cur);
}
if !detached {
return self.spawn_detached_trigger(stmt, cur, ProcessLoopKind::OneShot);
}
let point = self.suspend_point(tc, None)?;
let resume = self.alloc_block();
self.close(
cur,
Terminator::Suspend {
point,
resume_blk: resume,
},
);
self.push(resume, stmt);
Ok(resume)
}
fn spawn_detached_trigger(
&mut self,
stmt: Statement<'d>,
cur: BlockId,
loop_kind: ProcessLoopKind,
) -> Result<BlockId, LoweringError> {
let mut branch = build_cfg_in(
&stmt,
loop_kind,
None,
self.default_clocking,
None,
self.checker_clock,
Inherited {
this_var: self.this_stack.last().copied(),
ref_aliases: self.ref_stack.clone(),
global_clock: self.global_clock,
detached_trigger: true,
forced_extern: None,
rec_outer: self.rec_outer.clone(),
fork_memo: Some(self.fork_memo.clone()),
rec: None,
rec_up: Vec::new(),
known_fork_labels: Vec::new(),
ancestor_blocks: Vec::new(),
},
self.virtual_tasks.clone(),
)?;
branch.captured = captured_vars(&branch);
branch
.captured
.retain(|c| !self.ref_stack.iter().any(|(f, _)| f.uid() == c.uid()));
if let Some(t) = self.this_stack.last()
&& !branch.captured.iter().any(|c| c.uid() == t.uid())
{
branch.captured.push(*t);
}
let join_blk = self.alloc_block();
self.close(
cur,
Terminator::ForkJoin {
kind: StatementBlockKind::JoinNone,
branches: vec![branch],
join_blk,
},
);
Ok(join_blk)
}
fn walk_async_pla(
&mut self,
stmt: Statement<'d>,
cur: BlockId,
) -> Result<BlockId, LoweringError> {
let detached = std::mem::take(&mut self.detached_trigger);
if !detached {
self.push(cur, stmt);
return self.spawn_detached_trigger(stmt, cur, ProcessLoopKind::Looping);
}
let reads = async_pla_read_set(&stmt);
let resume = self.alloc_block();
self.push(cur, stmt);
self.close(
cur,
Terminator::Suspend {
point: SuspendPoint::ImplicitComb(reads),
resume_blk: resume,
},
);
Ok(resume)
}
fn walk_extern_call(
&mut self,
stmt: Statement<'d>,
call: Expression<'d>,
targets: &crate::netlist::itf::ExternTargets<'d>,
cur: BlockId,
) -> Result<Option<BlockId>, LoweringError> {
if let Some(imp) = self.forced_extern.take() {
return self.inline_task_call(call, imp, cur).map(Some);
}
if targets.impls.is_empty() {
let join = self.alloc_block();
self.close(
cur,
Terminator::Switch {
on: SwitchOn::NoTarget {
what: format!(
"no module connected to the interface implements extern forkjoin \
task `{}`; the call has no effect",
targets.proto.name()
),
},
arms: Vec::new(),
default_blk: join,
report: Some(call.source_range().start),
},
);
return Ok(Some(join));
}
if !targets.impls.iter().any(subroutine_can_suspend) {
return Ok(None);
}
if targets.impls.len() == 1 {
return self.inline_task_call(call, targets.impls[0], cur).map(Some);
}
let mut branches = Vec::with_capacity(targets.impls.len());
for imp in &targets.impls {
let mut branch = build_cfg_in(
&stmt,
ProcessLoopKind::OneShot,
None,
self.default_clocking,
None,
self.checker_clock,
Inherited {
this_var: self.this_stack.last().copied(),
ref_aliases: self.ref_stack.clone(),
global_clock: self.global_clock,
detached_trigger: false,
forced_extern: Some(*imp),
rec_outer: self.rec_outer.clone(),
fork_memo: Some(self.fork_memo.clone()),
rec: None,
rec_up: Vec::new(),
known_fork_labels: self.fork_branch_names.clone(),
ancestor_blocks: self.ancestor_blocks.clone(),
},
self.virtual_tasks.clone(),
)?;
branch.captured = captured_vars(&branch);
branch
.captured
.retain(|c| !self.ref_stack.iter().any(|(f, _)| f.uid() == c.uid()));
if let Some(t) = self.this_stack.last()
&& !branch.captured.iter().any(|c| c.uid() == t.uid())
{
branch.captured.push(*t);
}
branches.push(branch);
}
let join_blk = self.alloc_block();
self.close(
cur,
Terminator::ForkJoin {
kind: StatementBlockKind::JoinAll,
branches,
join_blk,
},
);
Ok(Some(join_blk))
}
fn walk_while_loop(
&mut self,
stmt: Statement<'d>,
cur: BlockId,
) -> Result<BlockId, LoweringError> {
let cond = stmt
.condition()
.ok_or_else(|| LoweringError::malformed("WhileLoop with no condition expression"))?;
let body_stmt = stmt
.body()
.ok_or_else(|| LoweringError::malformed("WhileLoop with no body statement"))?;
let header = self.alloc_block();
let body = self.alloc_block();
let exit = self.alloc_block();
self.probe(header, stmt);
self.close(
cur,
Terminator::Loop {
header,
body,
exit,
entry: header,
},
);
self.close(
header,
Terminator::Branch {
cond,
then_blk: body,
else_blk: exit,
},
);
let body_exit = self.walk_loop_body(body_stmt, body, header, exit)?;
self.close(body_exit, Terminator::Fallthrough(header));
Ok(exit)
}
fn walk_do_while_loop(
&mut self,
stmt: Statement<'d>,
cur: BlockId,
) -> Result<BlockId, LoweringError> {
let cond = stmt
.condition()
.ok_or_else(|| LoweringError::malformed("DoWhileLoop with no condition expression"))?;
let body_stmt = stmt
.body()
.ok_or_else(|| LoweringError::malformed("DoWhileLoop with no body statement"))?;
let header = self.alloc_block();
let body = self.alloc_block();
let exit = self.alloc_block();
self.close(
cur,
Terminator::Loop {
header,
body,
exit,
entry: body,
},
);
self.close(
header,
Terminator::Branch {
cond,
then_blk: body,
else_blk: exit,
},
);
let body_exit = self.walk_loop_body(body_stmt, body, header, exit)?;
self.close(body_exit, Terminator::Fallthrough(header));
Ok(exit)
}
fn walk_forever_loop(
&mut self,
stmt: Statement<'d>,
cur: BlockId,
) -> Result<BlockId, LoweringError> {
let body_stmt = stmt
.body()
.ok_or_else(|| LoweringError::malformed("ForeverLoop with no body statement"))?;
let header = self.alloc_block();
let body = self.alloc_block();
let exit = self.alloc_block();
self.probe(header, stmt);
self.close(
cur,
Terminator::Loop {
header,
body,
exit,
entry: header,
},
);
self.close(header, Terminator::Fallthrough(body));
let body_exit = self.walk_loop_body(body_stmt, body, header, exit)?;
self.close(body_exit, Terminator::Fallthrough(header));
Ok(exit)
}
fn walk_for_loop(
&mut self,
stmt: Statement<'d>,
cur: BlockId,
) -> Result<BlockId, LoweringError> {
self.probe(cur, stmt);
for init in stmt.for_loop_initializers() {
self.push_step_expr(cur, init);
}
let cond = stmt.condition();
let body_stmt = stmt
.body()
.ok_or_else(|| LoweringError::malformed("ForLoop with no body statement"))?;
let steps = stmt.for_loop_steps();
let header = self.alloc_block();
let body = self.alloc_block();
let exit = self.alloc_block();
self.close(
cur,
Terminator::Loop {
header,
body,
exit,
entry: header,
},
);
match cond {
Some(cond) => self.close(
header,
Terminator::Branch {
cond,
then_blk: body,
else_blk: exit,
},
),
None => self.close(header, Terminator::Fallthrough(body)),
}
let step = self.alloc_block();
let body_exit = self.walk_loop_body(body_stmt, body, step, exit)?;
self.close(body_exit, Terminator::Fallthrough(step));
self.probe(step, stmt);
for s in steps {
self.push_step_expr(step, s);
}
self.close(step, Terminator::Fallthrough(header));
Ok(exit)
}
fn walk_foreach_loop(
&mut self,
stmt: Statement<'d>,
cur: BlockId,
) -> Result<BlockId, LoweringError> {
let over_collection = stmt
.expressions()
.first()
.and_then(|a| a.expr_type())
.is_some_and(|t| crate::state::storage::is_collection(&t));
let nested_dyn = stmt
.foreach_loop_dims()
.iter()
.skip(usize::from(over_collection))
.any(|d| d.range().is_none());
let returns = escapes(stmt.as_sem_node(), false, !self.return_targets.is_empty());
if nested_dyn && (contains_suspend_trigger(stmt.as_sem_node()) || returns) {
return self.walk_nested_foreach(stmt, cur);
}
if over_collection {
if contains_suspend_trigger(stmt.as_sem_node()) || returns {
return self.walk_collection_foreach(stmt, cur);
}
self.push(cur, stmt);
return Ok(cur);
}
if stmt.foreach_loop_dims().iter().any(|d| d.range().is_none())
&& !contains_suspend_trigger(stmt.as_sem_node())
&& !returns
{
self.push(cur, stmt);
return Ok(cur);
}
let dims = stmt.foreach_loop_dims();
let body_stmt = stmt
.body()
.ok_or_else(|| LoweringError::malformed("ForeachLoop with no body statement"))?;
let mut loops = Vec::new();
for d in &dims {
let Some(var) = d.loop_var() else {
continue;
};
let range = d.range().ok_or_else(|| {
LoweringError::unsupported(
"foreach over a dynamically-sized dimension (a dynamic array, queue or \
associative array)",
)
})?;
loops.push((var, i64::from(range.left), i64::from(range.right)));
}
if loops.is_empty() {
return Ok(cur);
}
self.walk_foreach_nest(&loops, body_stmt, cur, None)
}
fn walk_nested_foreach(
&mut self,
stmt: Statement<'d>,
cur: BlockId,
) -> Result<BlockId, LoweringError> {
let arr = stmt.expressions()[0];
let body_stmt = stmt
.body()
.ok_or_else(|| LoweringError::malformed("ForeachLoop with no body statement"))?;
let dims = stmt.foreach_loop_dims();
let last_dyn = dims.iter().rposition(|d| d.range().is_none());
let mut ty = arr.expr_type();
let mut assoc_dims = Vec::new();
for _ in &dims {
assoc_dims.push(ty.as_ref().is_some_and(|t| t.is_associative_array()));
ty = ty.and_then(|t| t.element_type());
}
let mut levels = Vec::new();
for (k, d) in dims.iter().enumerate() {
let Some(var) = d.loop_var() else {
if last_dyn.is_some_and(|l| l > k) {
return Err(LoweringError::unsupported(
"foreach whose body waits with an omitted index in front of a nested \
queue, dynamic-array or associative-array dimension",
));
}
continue;
};
let fixed = d.range().map(|r| (r.left, r.right));
levels.push(NestStep {
var,
fixed,
assoc: assoc_dims[k],
});
}
self.walk_nest_levels(&levels, &mut Vec::new(), arr, body_stmt, cur, None)
}
fn walk_nest_levels(
&mut self,
levels: &[NestStep<'d>],
prefix: &mut Vec<NestStep<'d>>,
arr: Expression<'d>,
body_stmt: Statement<'d>,
cur: BlockId,
outer_exit: Option<BlockId>,
) -> Result<BlockId, LoweringError> {
let Some(&step_info) = levels.first() else {
return self.walk_stmt(body_stmt, cur);
};
let var = step_info.var;
let entry = self.alloc_block();
self.close(cur, Terminator::Fallthrough(entry));
let counter = self.alloc_repeat_counter();
let (count, first, by) = match step_info.fixed {
Some((left, right)) => {
let by: i64 = if left <= right { 1 } else { -1 };
let n = (i64::from(left) - i64::from(right)).unsigned_abs() + 1;
(RepeatCount::Const(n), i64::from(left) - by, by)
}
None if prefix.is_empty() => (RepeatCount::Size(arr), -1, 1),
None => (
RepeatCount::NestedSize {
arr,
path: prefix.clone(),
},
-1,
1,
),
};
self.set_repeat_init(entry, counter, count);
let header = self.alloc_block();
let body = self.alloc_block();
let exit = self.alloc_block();
self.inlined_locals.push(var);
if step_info.assoc {
self.blocks[entry.0 as usize]
.bindings
.push(ArgBinding::AssocKey {
var,
arr,
path: prefix.clone(),
next: false,
});
} else {
self.blocks[entry.0 as usize]
.bindings
.push(ArgBinding::Const { var, value: first });
self.blocks[header.0 as usize]
.bindings
.push(ArgBinding::Step { var, by });
}
self.close(
entry,
Terminator::Loop {
header,
body,
exit,
entry: header,
},
);
self.close(
header,
Terminator::RepeatCheck {
counter,
then_blk: body,
else_blk: exit,
},
);
let latch = self.alloc_block();
self.set_repeat_decrement(latch, counter);
if step_info.assoc {
self.blocks[latch.0 as usize]
.bindings
.push(ArgBinding::AssocKey {
var,
arr,
path: prefix.clone(),
next: true,
});
}
self.close(latch, Terminator::Fallthrough(header));
let outer = outer_exit.unwrap_or(exit);
prefix.push(step_info);
let body_exit = if levels.len() == 1 {
self.walk_loop_body(body_stmt, body, latch, outer)?
} else {
self.walk_nest_levels(&levels[1..], prefix, arr, body_stmt, body, Some(outer))?
};
prefix.pop();
self.close(body_exit, Terminator::Fallthrough(latch));
Ok(exit)
}
fn walk_collection_foreach(
&mut self,
stmt: Statement<'d>,
cur: BlockId,
) -> Result<BlockId, LoweringError> {
let arr = stmt.expressions()[0];
let assoc = arr.expr_type().is_some_and(|t| t.is_associative_array());
let dims = stmt.foreach_loop_dims();
let body_stmt = stmt
.body()
.ok_or_else(|| LoweringError::malformed("ForeachLoop with no body statement"))?;
let mut rest = Vec::new();
for d in dims.iter().skip(1) {
let Some(v) = d.loop_var() else {
continue;
};
let range = d.range().ok_or_else(|| {
LoweringError::unsupported(
"foreach over a dimension of a queue or dynamic array's element that is \
itself a queue, dynamic or associative array",
)
})?;
rest.push((v, i64::from(range.left), i64::from(range.right)));
}
let Some(var) = dims.first().and_then(|d| d.loop_var()) else {
return if rest.is_empty() {
Ok(cur)
} else {
self.walk_foreach_nest(&rest, body_stmt, cur, None)
};
};
let entry = self.alloc_block();
self.close(cur, Terminator::Fallthrough(entry));
let counter = self.alloc_repeat_counter();
self.set_repeat_init(entry, counter, RepeatCount::Size(arr));
let header = self.alloc_block();
let body = self.alloc_block();
let exit = self.alloc_block();
self.inlined_locals.push(var);
if assoc {
self.blocks[entry.0 as usize]
.bindings
.push(ArgBinding::AssocKey {
var,
arr,
path: Vec::new(),
next: false,
});
} else {
self.blocks[entry.0 as usize]
.bindings
.push(ArgBinding::Const { var, value: -1 });
self.blocks[header.0 as usize]
.bindings
.push(ArgBinding::Step { var, by: 1 });
}
self.close(
entry,
Terminator::Loop {
header,
body,
exit,
entry: header,
},
);
self.close(
header,
Terminator::RepeatCheck {
counter,
then_blk: body,
else_blk: exit,
},
);
let latch = self.alloc_block();
self.set_repeat_decrement(latch, counter);
if assoc {
self.blocks[latch.0 as usize]
.bindings
.push(ArgBinding::AssocKey {
var,
arr,
path: Vec::new(),
next: true,
});
}
self.close(latch, Terminator::Fallthrough(header));
let body_exit = if rest.is_empty() {
self.walk_loop_body(body_stmt, body, latch, exit)?
} else {
self.walk_foreach_nest(&rest, body_stmt, body, Some(exit))?
};
self.close(body_exit, Terminator::Fallthrough(latch));
Ok(exit)
}
fn walk_foreach_nest(
&mut self,
loops: &[(Symbol<'d>, i64, i64)],
body_stmt: Statement<'d>,
cur: BlockId,
outer_exit: Option<BlockId>,
) -> Result<BlockId, LoweringError> {
let Some(&(var, left, right)) = loops.first() else {
return self.walk_stmt(body_stmt, cur);
};
let step: i64 = if left <= right { 1 } else { -1 };
let count = (left - right).unsigned_abs() + 1;
let entry = self.alloc_block();
self.close(cur, Terminator::Fallthrough(entry));
let counter = self.alloc_repeat_counter();
self.set_repeat_init(entry, counter, RepeatCount::Const(count));
let header = self.alloc_block();
let body = self.alloc_block();
let exit = self.alloc_block();
self.inlined_locals.push(var);
self.blocks[entry.0 as usize]
.bindings
.push(ArgBinding::Const {
var,
value: left - step,
});
self.blocks[header.0 as usize]
.bindings
.push(ArgBinding::Step { var, by: step });
self.close(
entry,
Terminator::Loop {
header,
body,
exit,
entry: header,
},
);
self.close(
header,
Terminator::RepeatCheck {
counter,
then_blk: body,
else_blk: exit,
},
);
let latch = self.alloc_block();
self.set_repeat_decrement(latch, counter);
self.close(latch, Terminator::Fallthrough(header));
let outer = outer_exit.unwrap_or(exit);
let body_exit = if loops.len() == 1 {
self.walk_loop_body(body_stmt, body, latch, outer)?
} else {
self.walk_foreach_nest(&loops[1..], body_stmt, body, Some(outer))?
};
self.close(body_exit, Terminator::Fallthrough(latch));
Ok(exit)
}
fn walk_repeat_loop(
&mut self,
stmt: Statement<'d>,
cur: BlockId,
) -> Result<BlockId, LoweringError> {
let count_expr = stmt
.condition()
.ok_or_else(|| LoweringError::malformed("RepeatLoop with no count expression"))?;
let body_stmt = stmt
.body()
.ok_or_else(|| LoweringError::malformed("RepeatLoop with no body statement"))?;
let counter = self.alloc_repeat_counter();
self.set_repeat_init(cur, counter, RepeatCount::Expr(count_expr));
let header = self.alloc_block();
let body = self.alloc_block();
let exit = self.alloc_block();
self.probe(header, stmt);
self.close(
cur,
Terminator::Loop {
header,
body,
exit,
entry: header,
},
);
self.close(
header,
Terminator::RepeatCheck {
counter,
then_blk: body,
else_blk: exit,
},
);
let latch = self.alloc_block();
self.set_repeat_decrement(latch, counter);
self.close(latch, Terminator::Fallthrough(header));
let body_exit = self.walk_loop_body(body_stmt, body, latch, exit)?;
self.close(body_exit, Terminator::Fallthrough(latch));
Ok(exit)
}
}
fn escapes(node: SemNode<'_>, in_loop: bool, in_task: bool) -> bool {
let mut stack = vec![(node, false)];
while let Some((n, nested_loop)) = stack.pop() {
let mut inner = nested_loop;
if let Some(s) = n.as_statement() {
let kind = s.kind();
if matches!(kind, StatementKind::Break | StatementKind::Continue)
&& in_loop
&& !nested_loop
{
return true;
}
if kind == StatementKind::Return && in_task {
return true;
}
if matches!(
kind,
StatementKind::ForLoop
| StatementKind::WhileLoop
| StatementKind::DoWhileLoop
| StatementKind::ForeverLoop
| StatementKind::ForeachLoop
| StatementKind::RepeatLoop
) {
inner = true;
}
}
stack.extend(n.children().into_iter().map(|c| (c, inner)));
}
false
}
pub fn is_entry_probe_kind(stmt: &Statement<'_>) -> bool {
match stmt.kind() {
StatementKind::Block
| StatementKind::Conditional
| StatementKind::Case
| StatementKind::PatternCase
| StatementKind::Wait
| StatementKind::Disable
| StatementKind::EventTrigger
| StatementKind::ProceduralAssign
| StatementKind::ProceduralDeassign => true,
StatementKind::ExpressionStatement => stmt.expr().is_some_and(|e| {
matches!(
e.kind(),
ExpressionKind::Assignment | ExpressionKind::Call | ExpressionKind::UnaryOp
)
}),
StatementKind::Timed => {
stmt.timing_control().map(|t| t.kind()) != Some(TimingControlKind::RepeatedEvent)
}
StatementKind::Invalid
| StatementKind::Empty
| StatementKind::List
| StatementKind::VariableDeclaration
| StatementKind::Return
| StatementKind::Continue
| StatementKind::Break
| StatementKind::DisableFork
| StatementKind::ForLoop
| StatementKind::RepeatLoop
| StatementKind::ForeachLoop
| StatementKind::WhileLoop
| StatementKind::DoWhileLoop
| StatementKind::ForeverLoop
| StatementKind::ImmediateAssertion
| StatementKind::ConcurrentAssertion
| StatementKind::WaitFork
| StatementKind::WaitOrder
| StatementKind::RandCase
| StatementKind::RandSequence
| StatementKind::ProceduralChecker => false,
other => unreachable!("statement kind {other:?} is newer than this classification"),
}
}
thread_local! {
static DISABLE_SOURCE: std::cell::RefCell<Vec<(Option<String>, std::rc::Rc<str>)>> =
const { std::cell::RefCell::new(Vec::new()) };
}
pub fn set_source_text(text: Option<&str>) {
DISABLE_SOURCE.with(|s| {
*s.borrow_mut() = text
.map(|t| (None, std::rc::Rc::from(t)))
.into_iter()
.collect();
});
scan_disable_names(text.into_iter());
}
thread_local! {
static DISABLE_NAMES: std::cell::RefCell<(
std::collections::HashSet<String>,
std::collections::HashSet<String>,
)> = std::cell::RefCell::new(Default::default());
}
fn scan_disable_names<'a>(texts: impl Iterator<Item = &'a str>) {
let mut labels = std::collections::HashSet::new();
let mut tasks = std::collections::HashSet::new();
let mut fn_labels = std::collections::HashSet::new();
let ident = |c: u8| c == b'_' || c == b'$' || c.is_ascii_alphanumeric();
for text in texts {
let b = text.as_bytes();
let word_at = |mut i: usize| -> Option<(&str, usize)> {
while i < b.len() && b[i].is_ascii_whitespace() {
i += 1;
}
let start = i;
while i < b.len() && ident(b[i]) {
i += 1;
}
(i > start).then(|| (&text[start..i], i))
};
let mut i = 0;
let (mut in_fn, mut proto) = (false, false);
while i < b.len() {
if !ident(b[i]) {
if b[i] == b';' {
proto = false;
}
i += 1;
continue;
}
let start = i;
while i < b.len() && ident(b[i]) {
i += 1;
}
let boundary = start == 0 || (b[start - 1] != b'.' && b[start - 1] != b'`');
match &text[start..i] {
"import" | "export" | "extern" | "pure" if boundary => proto = true,
"function" if boundary => in_fn = !proto,
"endfunction" if boundary => in_fn = false,
"disable" if boundary => {
let skip_idx = |mut e: usize| -> usize {
loop {
let t = text[e..].trim_start();
let lead = text[e..].len() - t.len();
let Some(inner) = t.strip_prefix('[') else {
return e;
};
match inner.find(']') {
Some(c)
if inner[..c].trim().bytes().all(|d| d.is_ascii_digit()) =>
{
e += lead + 1 + c + 1;
}
_ => return e,
}
}
};
if let Some((mut name, e0)) = word_at(i)
&& name != "fork"
{
let mut end = skip_idx(e0);
while text[end..].trim_start().starts_with('.')
&& let Some((next, e)) = word_at(
end + (text[end..].len() - text[end..].trim_start().len()) + 1,
)
{
name = next;
end = skip_idx(e);
}
if text[end..].trim_start().starts_with(';') {
labels.insert(name.to_string());
if in_fn {
fn_labels.insert(name.to_string());
}
let full: String = text[i..end]
.chars()
.filter(|c| !c.is_ascii_whitespace())
.collect();
let mut rest = full.as_str();
while let Some((_, tail)) = rest.split_once('.') {
if tail.contains('.') {
labels.insert(tail.to_string());
}
rest = tail;
}
if full.contains('.') {
labels.insert(full);
}
}
}
}
"task" if boundary => {
if let Some((mut name, end)) = word_at(i) {
if matches!(name, "automatic" | "static")
&& let Some((n, _)) = word_at(end)
{
name = n;
}
tasks.insert(name.to_string());
}
}
_ => {}
}
}
}
DISABLE_NAMES.with(|n| *n.borrow_mut() = (labels, tasks));
FN_DISABLE_LABELS.with(|n| *n.borrow_mut() = fn_labels);
}
thread_local! {
static FN_DISABLE_LABELS: std::cell::RefCell<std::collections::HashSet<String>> =
std::cell::RefCell::new(Default::default());
}
pub fn has_function_disables() -> bool {
FN_DISABLE_LABELS.with(|n| !n.borrow().is_empty())
}
pub fn is_function_disable_label(label: &str) -> bool {
FN_DISABLE_LABELS.with(|n| n.borrow().contains(label))
}
fn is_disabled_somewhere(label: &str) -> bool {
DISABLE_NAMES.with(|n| n.borrow().0.contains(label))
}
pub fn dotted_disable_labels() -> Vec<String> {
let mut v: Vec<String> = DISABLE_NAMES.with(|n| {
n.borrow()
.0
.iter()
.filter(|l| l.contains('.'))
.cloned()
.collect()
});
v.sort();
v
}
pub fn is_task_name(label: &str) -> bool {
DISABLE_NAMES.with(|n| n.borrow().1.contains(label))
}
pub fn set_source_texts(files: Vec<(String, String)>) {
scan_disable_names(files.iter().map(|(_, t)| t.as_str()));
DISABLE_SOURCE.with(|s| {
*s.borrow_mut() = files
.into_iter()
.map(|(name, text)| (Some(name), std::rc::Rc::from(text)))
.collect();
});
}
fn same_file(a: &str, b: &str) -> bool {
a == b
|| std::fs::canonicalize(a)
.ok()
.is_some_and(|x| std::fs::canonicalize(b).ok() == Some(x))
}
fn source_text_at(at: sv_lang::SourceLoc) -> Option<std::rc::Rc<str>> {
let files = DISABLE_SOURCE.with(|s| s.borrow().clone());
if let [(None, text)] = files.as_slice() {
return Some(text.clone());
}
let file = SOURCE_SESSION
.with(|s| s.borrow().as_ref().and_then(|se| se.resolve_location(at)))?
.file;
files
.into_iter()
.find(|(n, _)| n.as_deref().is_some_and(|n| same_file(n, &file)))
.map(|(_, text)| text)
}
thread_local! {
static SOURCE_NAME: std::cell::RefCell<Option<String>> =
const { std::cell::RefCell::new(None) };
}
pub fn set_source_name(name: Option<&str>) {
SOURCE_NAME.with(|s| *s.borrow_mut() = name.map(str::to_owned));
}
pub fn source_text_range(span: sv_lang::SourceSpan) -> Option<String> {
source_text_at(span.start)?
.get(span.start.offset as usize..span.end.offset as usize)
.map(str::to_owned)
}
thread_local! {
static SOURCE_SESSION: std::cell::RefCell<Option<sv_lang::Session>> =
const { std::cell::RefCell::new(None) };
}
pub fn set_source_session(session: Option<sv_lang::Session>) {
SOURCE_SESSION.with(|s| *s.borrow_mut() = session);
}
pub fn source_location(loc: sv_lang::SourceLoc) -> Option<String> {
if let Some(at) = SOURCE_SESSION.with(|s| {
s.borrow()
.as_ref()
.and_then(|session| session.resolve_location(loc))
}) {
let main = SOURCE_NAME.with(|s| s.borrow().clone());
let file = match main {
Some(m) if same_file(&at.file, &m) => m,
_ => at.file,
};
return Some(format!("{file}:{}", at.line));
}
let name = SOURCE_NAME.with(|s| s.borrow().clone())?;
let text = source_text_at(loc)?;
let head = text.get(..(loc.offset as usize).min(text.len()))?;
let line = head.bytes().filter(|&b| b == b'\n').count() + 1;
Some(format!("{name}:{line}"))
}
pub fn disable_path(stmt: &Statement<'_>) -> Option<Vec<String>> {
let text = source_text_range(stmt.source_range())?;
let rest = text.trim().strip_prefix("disable")?;
if !rest.starts_with(char::is_whitespace) {
return None;
}
let label: String = rest
.trim()
.trim_end_matches(';')
.chars()
.filter(|c| !c.is_whitespace())
.collect();
let segs: Vec<String> = label.split('.').map(str::to_string).collect();
let ident = |t: &str| {
let t = match t.find('[') {
Some(b)
if t.ends_with(']')
&& t[b + 1..t.len() - 1].bytes().all(|d| d.is_ascii_digit())
&& b + 2 < t.len() =>
{
&t[..b]
}
Some(_) => return false,
None => t,
};
!t.is_empty()
&& t.chars().enumerate().all(|(i, c)| {
c == '_' || c.is_ascii_alphabetic() || (i > 0 && (c.is_ascii_digit() || c == '$'))
})
};
(segs.len() > 1 && segs.iter().all(|t| ident(t))).then_some(segs)
}
pub fn disable_leaf_label(stmt: &Statement<'_>) -> Result<String, LoweringError> {
match disable_path(stmt).and_then(|p| p.last().cloned()) {
Some(leaf) => Ok(leaf),
None => disable_label(stmt),
}
}
pub fn disable_label(stmt: &Statement<'_>) -> Result<String, LoweringError> {
let span = stmt.source_range();
let text = source_text_range(span);
let unreadable = || {
LoweringError::unsupported(
"disable statement whose target cannot be read (no registered source text, or \
the statement is not in a registered source file)",
)
};
let text = text.ok_or_else(unreadable)?;
let rest = text.trim().strip_prefix("disable").ok_or_else(unreadable)?;
let label = rest.trim().trim_end_matches(';').trim();
let ident = !label.is_empty()
&& label.chars().enumerate().all(|(i, c)| {
c == '_' || c.is_ascii_alphabetic() || (i > 0 && (c.is_ascii_digit() || c == '$'))
});
if !ident || !rest.starts_with(char::is_whitespace) {
let hier = if label.contains('.') {
"; disabling a task or interface method by hierarchical name would have to \
end every activation of it in other processes (IEEE 1800-2023 9.6.2)"
} else {
""
};
return Err(LoweringError::unsupported(format!(
"disable of `{label}` (only a simple block name is supported{hier})"
)));
}
Ok(label.to_string())
}
fn contains_disable_through_calls(node: SemNode<'_>) -> bool {
let mut visiting = std::collections::HashSet::new();
let mut stack = vec![node];
while let Some(n) = stack.pop() {
if n.as_statement()
.is_some_and(|s| s.kind() == StatementKind::Disable)
{
return true;
}
if let Some(e) = n.as_expression()
&& let Some(callee) = user_task_callee(&e)
&& let Some(body) = proc_body(&callee)
&& visiting.insert(callee.uid())
{
stack.push(body.as_sem_node());
}
stack.extend(live_children(&n));
}
false
}
fn contains_fork_through_calls(node: SemNode<'_>) -> bool {
let mut visiting = std::collections::HashSet::new();
let mut stack = vec![node];
while let Some(n) = stack.pop() {
if n.as_statement().is_some_and(|s| {
matches!(
s.block_kind(),
Some(
StatementBlockKind::JoinAll
| StatementBlockKind::JoinAny
| StatementBlockKind::JoinNone
)
)
}) {
return true;
}
if let Some(e) = n.as_expression()
&& let Some(callee) = user_task_callee(&e)
&& let Some(body) = proc_body(&callee)
&& visiting.insert(callee.uid())
{
stack.push(body.as_sem_node());
}
stack.extend(live_children(&n));
}
false
}
fn is_ordinary_statement_kind(kind: StatementKind) -> bool {
matches!(
kind,
StatementKind::Empty
| StatementKind::ExpressionStatement
| StatementKind::VariableDeclaration
| StatementKind::Return
| StatementKind::Continue
| StatementKind::Break
| StatementKind::Case
| StatementKind::PatternCase
| StatementKind::RepeatLoop
| StatementKind::ImmediateAssertion
| StatementKind::ConcurrentAssertion
| StatementKind::ProceduralAssign
| StatementKind::ProceduralDeassign
| StatementKind::RandCase
| StatementKind::RandSequence
| StatementKind::ProceduralChecker
)
}
fn is_finish_or_stop_call(stmt: &Statement<'_>) -> bool {
let Some(e) = stmt.expr() else {
return false;
};
if e.kind() != ExpressionKind::Call || e.is_system_call() != Some(true) {
return false;
}
let Some(sub) = e.system_subroutine() else {
return false;
};
matches!(
sub.known_name_id(),
KnownSystemName::Finish | KnownSystemName::Stop | KnownSystemName::Fatal
)
}
pub fn violation_check(stmt: &Statement<'_>) -> Option<UniquePriorityCheck> {
let check = if stmt.kind() == StatementKind::PatternCase {
stmt.pattern_case_check()
} else {
stmt.check()
};
match check {
Some(UniquePriorityCheck::None) | None => None,
Some(check) => Some(check),
}
}
pub struct IfChain<'d> {
pub links: Vec<Statement<'d>>,
pub thens: Vec<Statement<'d>>,
pub otherwise: Option<Statement<'d>>,
}
pub fn unwrap_invalid<'d>(mut stmt: Statement<'d>) -> Statement<'d> {
while stmt.kind() == StatementKind::Invalid {
match stmt.invalid_child() {
Some(child) => stmt = child,
None => break,
}
}
stmt
}
pub fn live_statements<'d>(stmt: &Statement<'d>) -> Vec<Statement<'d>> {
stmt.as_sem_node()
.children_with_invalid()
.into_iter()
.filter_map(|n| n.as_statement())
.map(unwrap_invalid)
.collect()
}
pub fn live_children<'d>(node: &SemNode<'d>) -> Vec<SemNode<'d>> {
node.children_with_invalid()
.into_iter()
.map(|n| {
if let Some(s) = n.as_statement() {
return unwrap_invalid(s).as_sem_node();
}
if let Some(mut e) = n.as_expression() {
while e.kind() == ExpressionKind::Invalid
&& let Some(c) = e.invalid_child()
{
e = c;
}
return e.as_sem_node();
}
n
})
.collect()
}
pub fn proc_body<'d>(block: &Symbol<'d>) -> Option<Statement<'d>> {
block.body().map(unwrap_invalid)
}
pub fn block_scopes<'d>(stmt: &Statement<'d>) -> Vec<Symbol<'d>> {
if let Some(bs) = stmt.block_symbol() {
return vec![bs];
}
let node = stmt.as_sem_node();
if stmt.kind() != StatementKind::Block || !contains_invalid(node) {
return Vec::new();
}
let mut out: Vec<Symbol<'d>> = Vec::new();
let mut seen = HashSet::new();
let mut stack = vec![node];
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression()
&& crate::symid::is_value_name(e.kind())
&& let Some(sym) = e.referenced_symbol()
&& let Some(parent) = sym.parent()
&& parent.kind() == SymbolKind::StatementBlock
&& seen.insert(parent.uid())
{
out.push(parent);
}
stack.extend(live_children(&n));
}
out
}
pub fn block_name(stmt: &Statement<'_>) -> String {
if let Some(bs) = stmt.block_symbol() {
return bs.name().to_string();
}
if stmt.kind() != StatementKind::Block || !contains_invalid(stmt.as_sem_node()) {
return String::new();
}
let Some(text) = source_text_range(stmt.source_range()) else {
return String::new();
};
let ident = |s: &str| -> Option<(String, String)> {
let s = s.trim_start();
let n = s
.char_indices()
.take_while(|&(i, c)| {
c == '_' || c.is_ascii_alphabetic() || (i > 0 && (c.is_ascii_digit() || c == '$'))
})
.count();
(n > 0).then(|| (s[..n].to_string(), s[n..].to_string()))
};
let Some((first, rest)) = ident(&text) else {
return String::new();
};
if matches!(first.as_str(), "begin" | "fork") {
return match rest.trim_start().strip_prefix(':').and_then(ident) {
Some((name, _)) => name,
None => String::new(),
};
}
match rest.trim_start().strip_prefix(':').and_then(ident) {
Some((kw, _)) if matches!(kw.as_str(), "begin" | "fork") => first,
_ => String::new(),
}
}
fn contains_invalid(node: SemNode<'_>) -> bool {
let is_invalid = node
.as_statement()
.is_some_and(|s| s.kind() == StatementKind::Invalid)
|| node
.as_expression()
.is_some_and(|e| e.kind() == ExpressionKind::Invalid);
is_invalid
|| node
.children_with_invalid()
.into_iter()
.any(contains_invalid)
}
pub fn constant_case_live_arm<'d>(stmt: &Statement<'d>) -> Option<Option<Statement<'d>>> {
fn has_call(e: Expression<'_>) -> bool {
e.kind() == ExpressionKind::Call || e.operands().into_iter().any(has_call)
}
if stmt.kind() != StatementKind::Case
|| stmt.case_condition() != Some(sv_lang::CaseStatementCondition::Normal)
|| violation_check(stmt).is_some()
{
return None;
}
let items = stmt.case_items();
let default = stmt.default_case();
if !items
.iter()
.map(|i| i.stmt())
.chain(default)
.any(|a| contains_invalid(a.as_sem_node()))
{
return None;
}
let sel = stmt.expr()?;
if has_call(sel) {
return None;
}
let sv = sel.constant_value()?.as_i64()?;
for item in &items {
for label in item.expressions() {
if has_call(label) {
return None;
}
if label.constant_value()?.as_i64()? == sv {
return Some(Some(item.stmt()));
}
}
}
Some(default)
}
pub fn constant_if_arm(stmt: &Statement<'_>) -> Option<bool> {
fn has_call(e: Expression<'_>) -> bool {
e.kind() == ExpressionKind::Call || e.operands().into_iter().any(has_call)
}
let conditions = stmt.conditions();
if stmt.kind() != StatementKind::Conditional
|| conditions.len() != 1
|| conditions[0].pattern().is_some()
|| violation_check(stmt).is_some()
{
return None;
}
let cond = conditions[0].expr();
if has_call(cond) {
return None;
}
cond.constant_value()
.and_then(|c| c.as_i64())
.map(|c| c != 0)
}
pub fn is_clause_if(stmt: &Statement<'_>) -> bool {
if stmt.kind() != StatementKind::Conditional {
return false;
}
let conditions = stmt.conditions();
conditions.len() != 1 || conditions[0].pattern().is_some()
}
pub fn if_series<'d>(head: Statement<'d>) -> Result<IfChain<'d>, &'static str> {
if violation_check(&head).is_some() {
return if_chain(head);
}
Ok(IfChain {
links: vec![head],
thens: vec![head.then_branch().ok_or("an `if` with no then-branch")?],
otherwise: head.else_branch(),
})
}
pub fn if_chain<'d>(head: Statement<'d>) -> Result<IfChain<'d>, &'static str> {
let mut chain = IfChain {
links: Vec::new(),
thens: Vec::new(),
otherwise: None,
};
let mut link = head;
loop {
chain.links.push(link);
chain
.thens
.push(link.then_branch().ok_or("an `if` with no then-branch")?);
match link.else_branch() {
Some(next)
if next.kind() == StatementKind::Conditional
&& violation_check(&next).is_none() =>
{
link = next;
}
other => {
chain.otherwise = other;
return Ok(chain);
}
}
}
}
fn first_expression_child<'d>(tc: &TimingControl<'d>) -> Option<Expression<'d>> {
tc.children().into_iter().find_map(|n| n.as_expression())
}
fn implicit_comb_read_set<'d>(body: &Statement<'d>) -> Vec<Expression<'d>> {
let mut order = Vec::new();
let mut seen = HashSet::new();
let mut stack = vec![body.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(st) = n.as_statement()
&& st.kind() == StatementKind::Invalid
{
if let Some(c) = st.invalid_child() {
stack.push(c.as_sem_node());
}
continue;
}
if let Some(e) = n.as_expression()
&& e.kind() == ExpressionKind::Invalid
{
if let Some(c) = e.invalid_child() {
stack.push(c.as_sem_node());
}
continue;
}
if let Some(e) = n.as_expression() {
if crate::symid::is_value_name(e.kind()) {
if let Some(sym) = e.referenced_symbol()
&& seen.insert(sym.uid())
{
order.push(e);
}
continue;
}
if e.kind() == ExpressionKind::Assignment {
let lhs_is_read = e.is_compound_assignment() == Some(true);
if let Some(lhs) = e.left()
&& (lhs_is_read || !crate::symid::is_value_name(lhs.kind()))
{
stack.push(lhs.as_sem_node());
}
if let Some(rhs) = e.right() {
stack.push(rhs.as_sem_node());
}
continue;
}
}
stack.extend(n.children_with_invalid());
}
order
}
fn reject_chandle_event(signal: &Expression<'_>) -> Result<(), LoweringError> {
if signal.expr_type().is_some_and(|t| t.is_chandle()) {
return Err(LoweringError::unsupported(
"a chandle in an event expression (illegal, LRM 1800-2023 6.14)".to_string(),
));
}
Ok(())
}
pub fn async_pla_call<'d>(stmt: &Statement<'d>) -> Option<Vec<Expression<'d>>> {
let e = stmt.expr()?;
if e.kind() != ExpressionKind::Call || e.is_system_call() != Some(true) {
return None;
}
let name = e.system_subroutine()?.name();
let rest = name.strip_prefix("$async$")?;
let (logic, format) = rest.split_once('$')?;
(matches!(logic, "and" | "or" | "nand" | "nor") && matches!(format, "array" | "plane"))
.then(|| e.operands())
}
fn async_pla_read_set<'d>(stmt: &Statement<'d>) -> Vec<Expression<'d>> {
let ops = async_pla_call(stmt).unwrap_or_default();
let root = |x: Expression<'d>| {
let x = if x.kind() == ExpressionKind::Assignment {
x.left().unwrap_or(x)
} else {
x
};
crate::symid::is_value_name(x.kind())
.then(|| x.referenced_symbol())
.flatten()
.map(|s| (x, s.uid()))
};
let out_uid = ops.get(2).and_then(|o| root(*o)).map(|(_, u)| u);
let mut reads: Vec<Expression<'d>> = implicit_comb_read_set(stmt)
.into_iter()
.filter(|r| r.referenced_symbol().map(|s| s.uid()) != out_uid)
.collect();
if let Some((mem, uid)) = ops.first().and_then(|o| root(*o))
&& !reads
.iter()
.any(|r| r.referenced_symbol().map(|s| s.uid()) == Some(uid.clone()))
{
reads.push(mem);
}
reads
}
fn extract_iff<'d>(tc: &TimingControl<'d>) -> Option<Expression<'d>> {
let children = tc.children();
if children.len() == 2 {
children[1].as_expression()
} else {
None
}
}
fn suspending_assertion_actions<'d>(
stmt: &Statement<'d>,
) -> Option<(Option<Statement<'d>>, Option<Statement<'d>>)> {
let pass = stmt.immediate_assertion_if_true();
let fail = stmt.immediate_assertion_if_false();
let waits = |s: &Option<Statement<'d>>| {
s.as_ref()
.is_some_and(|s| contains_suspend_trigger(s.as_sem_node()))
};
(waits(&pass) || waits(&fail)).then_some((pass, fail))
}
pub fn assertion_cond<'d>(stmt: &Statement<'d>) -> Option<Expression<'d>> {
stmt.as_sem_node()
.children()
.into_iter()
.find_map(|n| n.as_expression())
}
fn is_expect(stmt: &Statement<'_>) -> bool {
stmt.kind() == StatementKind::ConcurrentAssertion
&& stmt.assertion_kind() == Some(sv_lang::AssertionKind::Expect)
}
fn contains_suspend_trigger(node: SemNode<'_>) -> bool {
contains_suspend_trigger_in(node, &mut std::collections::HashSet::new(), true, true)
}
fn contains_suspend_trigger_in(
node: SemNode<'_>,
visiting: &mut std::collections::HashSet<crate::symid::SymbolId>,
by_request: bool,
disable_is_trigger: bool,
) -> bool {
let mut stack = vec![node];
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression()
&& let Some(callee) = user_task_callee(&e)
&& is_suspending_virtual(&callee)
{
return true;
}
if let Some(e) = n.as_expression()
&& e.kind() == ExpressionKind::Assignment
&& e.assignment_timing().is_some()
&& (e.is_nonblocking() != Some(true)
|| e.assignment_timing()
.and_then(|t| t.as_timing_control())
.is_some_and(|tc| tc.kind() != TimingControlKind::Delay))
{
return true;
}
if let Some(e) = n.as_expression() {
let callees: Vec<Symbol<'_>> = match crate::netlist::itf::call_extern_targets(&e) {
Some(t) if t.impls.is_empty() => return true,
Some(t) => t.impls,
None => user_task_callee(&e).into_iter().collect(),
};
for callee in callees {
if has_event_out_formal(&callee) {
return true;
}
if let Some(body) = proc_body(&callee)
&& visiting.insert(callee.uid())
&& contains_suspend_trigger_in(
body.as_sem_node(),
visiting,
by_request
&& callee.subroutine_kind() == Some(sv_lang::SubroutineKind::Task),
disable_is_trigger
&& callee.subroutine_kind() == Some(sv_lang::SubroutineKind::Task),
)
{
return true;
}
}
}
if let Some(s) = n.as_statement() {
let kind = s.kind();
if by_request
&& kind == StatementKind::ExpressionStatement
&& is_process_by_request_stmt(&s)
{
return true;
}
let is_join_block = kind == StatementKind::Block
&& matches!(
s.block_kind(),
Some(
StatementBlockKind::JoinAll
| StatementBlockKind::JoinAny
| StatementBlockKind::JoinNone
)
);
if is_join_block
|| is_expect(&s)
|| matches!(
kind,
StatementKind::Timed
| StatementKind::Wait
| StatementKind::WaitFork
| StatementKind::WaitOrder
| StatementKind::EventTrigger
| StatementKind::DisableFork
)
|| (disable_is_trigger && kind == StatementKind::Disable)
{
return true;
}
if kind == StatementKind::ConcurrentAssertion {
stack.extend(
live_children(&n)
.into_iter()
.filter(|c| c.as_statement().is_some()),
);
continue;
}
}
stack.extend(live_children(&n));
}
false
}
fn task_reaches_itself<'a>(task: &Symbol<'a>, vt: Option<&VirtualTasks<'a>>) -> bool {
let uid = task.uid();
if let Some(vt) = vt
&& let Some(r) = vt.self_reach.borrow().get(&uid)
{
return *r;
}
let r = task_reaches(task, &uid, vt, &mut std::collections::HashSet::new());
if let Some(vt) = vt {
vt.self_reach.borrow_mut().insert(uid, r);
}
r
}
fn task_reaches<'a>(
sub: &Symbol<'a>,
target: &crate::symid::SymbolId,
vt: Option<&VirtualTasks<'a>>,
visiting: &mut std::collections::HashSet<crate::symid::SymbolId>,
) -> bool {
let Some(body) = proc_body(sub) else {
return false;
};
let mut stack = vec![body.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression()
&& let Some(callee) = user_task_callee(&e)
{
let mut callees = vec![callee];
if let Some(vt) = vt
&& !is_explicit_super(e)
&& (callee
.subroutine_flags()
.contains(sv_lang::MethodFlags::VIRTUAL)
|| callee.subroutine_override().is_some())
{
callees.extend(vt.implementations(&callee).into_iter().map(|(_, s)| s));
}
for c in callees {
if c.uid() == *target {
return true;
}
if visiting.insert(c.uid()) && task_reaches(&c, target, vt, visiting) {
return true;
}
}
}
stack.extend(live_children(&n));
}
false
}
fn contains_subroutine_call(node: SemNode<'_>) -> bool {
let mut stack = vec![node];
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression()
&& e.kind() == ExpressionKind::Call
&& e.is_system_call() != Some(true)
{
return true;
}
stack.extend(live_children(&n));
}
false
}
fn user_task_callee<'d>(e: &Expression<'d>) -> Option<Symbol<'d>> {
if e.kind() != ExpressionKind::Call || e.is_system_call() == Some(true) {
return None;
}
let callee = e.call_subroutine()?;
if callee
.subroutine_flags()
.contains(sv_lang::MethodFlags::DPI_IMPORT)
{
return None;
}
Some(callee)
}
fn suspending_task_callee<'d>(e: &Expression<'d>) -> Option<Symbol<'d>> {
let callee = user_task_callee(e)?;
subroutine_can_suspend(&callee).then_some(callee)
}
fn collect_suspending_function_calls<'d>(node: SemNode<'d>, out: &mut Vec<Expression<'d>>) {
for c in node.children() {
collect_suspending_function_calls(c, out);
}
if let Some(e) = node.as_expression()
&& let Some(callee) = user_task_callee(&e)
&& callee.subroutine_kind() == Some(sv_lang::SubroutineKind::Function)
&& subroutine_can_suspend(&callee)
{
out.push(e);
}
}
fn has_event_out_formal(sub: &Symbol<'_>) -> bool {
sub.subroutine_arguments().any(|a| {
a.formal_argument_direction() != sv_lang::ArgumentDirection::In
&& a.value_type().is_some_and(|t| t.is_event())
})
}
pub fn call_result_key(call: &Expression<'_>) -> crate::symid::SymbolId {
let at = call.source_range().start;
crate::symid::SymbolId::synthetic_tagged(&format!("callret.{}.{}", at.buffer, at.offset))
}
pub fn subroutine_can_suspend(sub: &Symbol<'_>) -> bool {
if is_suspending_virtual(sub) {
return true;
}
let Some(body) = proc_body(sub) else {
return false;
};
if has_event_out_formal(sub) {
return true;
}
let mut visiting = std::collections::HashSet::new();
visiting.insert(sub.uid());
let by_request = sub.subroutine_kind() == Some(sv_lang::SubroutineKind::Task);
contains_suspend_trigger_in(body.as_sem_node(), &mut visiting, by_request, by_request)
|| (sub.subroutine_kind() == Some(sv_lang::SubroutineKind::Task)
&& task_stages_nba(sub, &mut std::collections::HashSet::new()))
}
fn task_stages_nba(
sub: &Symbol<'_>,
visiting: &mut std::collections::HashSet<crate::symid::SymbolId>,
) -> bool {
let Some(body) = proc_body(sub) else {
return false;
};
let mut stack = vec![body.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression() {
if e.kind() == ExpressionKind::Assignment && e.is_nonblocking() == Some(true) {
return true;
}
if let Some(callee) = user_task_callee(&e)
&& callee.subroutine_kind() == Some(sv_lang::SubroutineKind::Task)
&& visiting.insert(callee.uid())
&& task_stages_nba(&callee, visiting)
{
return true;
}
}
stack.extend(live_children(&n));
}
false
}
pub fn std_process_method(callee: &Symbol<'_>) -> Option<String> {
std_class_method(callee, "process")
}
pub fn std_class_method(callee: &Symbol<'_>, class_name: &str) -> Option<String> {
let class = callee.parent()?;
if !matches!(
class.kind(),
SymbolKind::ClassType | SymbolKind::GenericClassDef
) || class.name() != class_name
{
return None;
}
let mut scope = class.parent();
while let Some(s) = scope {
if s.kind() == SymbolKind::Package && s.name() == "std" {
return Some(callee.name().to_string());
}
scope = s.parent();
}
None
}
pub fn is_class_event_expr(e: &Expression<'_>) -> bool {
let mut e = *e;
while e.kind() == ExpressionKind::Conversion
&& let Some(inner) = e.conversion_operand()
{
e = inner;
}
if !e.expr_type().is_some_and(|t| t.is_event()) {
return false;
}
let sym = if e.kind() == ExpressionKind::MemberAccess {
e.member_symbol()
} else if matches!(
e.kind(),
ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue
) {
e.referenced_symbol()
} else {
None
};
let Some(sym) = sym else {
return false;
};
if sym.kind() == SymbolKind::ClassProperty {
return true;
}
let mut scope = sym.parent();
while let Some(s) = scope {
if s.kind() == SymbolKind::Subroutine {
return s.parent().is_some_and(|p| {
matches!(
p.kind(),
SymbolKind::ClassType | SymbolKind::GenericClassDef
)
});
}
if matches!(
s.kind(),
SymbolKind::ClassType | SymbolKind::GenericClassDef
) {
return false;
}
scope = s.parent();
}
false
}
pub fn class_event_wait_terms<'d>(
tc: &TimingControl<'d>,
) -> Result<Vec<Expression<'d>>, LoweringError> {
let kind = tc.kind();
if kind == TimingControlKind::SignalEvent {
let Some(sig) = first_expression_child(tc) else {
return Ok(Vec::new());
};
if !is_class_event_expr(&sig) {
return Ok(Vec::new());
}
if tc.edge() != EdgeKind::None {
return Err(LoweringError::unsupported(
"an edge event control on a class `event` (LRM 1800-2023 15.5)",
));
}
return Ok(vec![sig]);
}
if kind == TimingControlKind::EventList {
let mut terms = Vec::new();
let mut plain_iff = false;
for child in tc.children() {
let Some(sub) = child.as_timing_control() else {
continue;
};
match first_expression_child(&sub) {
Some(sig)
if sub.kind() == TimingControlKind::SignalEvent
&& is_class_event_expr(&sig) =>
{
if sub.edge() != EdgeKind::None {
return Err(LoweringError::unsupported(
"an edge term on a class `event` in an `@(a or b)` event list (LRM 1800-2023 15.5)",
));
}
terms.push(sig);
}
_ => {
if extract_iff(&sub).is_some() {
plain_iff = true;
}
}
}
}
if plain_iff && !terms.is_empty() {
return Err(LoweringError::unsupported(
"an `iff` on a signal term of an `@(a or b)` event list that also has a class `event` term (the guard would gate the class wake too; LRM 1800-2023 9.4.2.3, 15.5)",
));
}
return Ok(terms);
}
Ok(Vec::new())
}
pub fn class_event_term_iffs<'d>(tc: &TimingControl<'d>) -> Vec<Option<Expression<'d>>> {
let mut out = Vec::new();
if tc.kind() != TimingControlKind::EventList {
return out;
}
for child in tc.children() {
let Some(sub) = child.as_timing_control() else {
continue;
};
if sub.kind() == TimingControlKind::SignalEvent
&& first_expression_child(&sub).is_some_and(|sig| is_class_event_expr(&sig))
{
out.push(extract_iff(&sub));
}
}
out
}
pub fn class_event_wait_is_mixed(tc: &TimingControl<'_>) -> bool {
if tc.kind() != TimingControlKind::EventList {
return false;
}
let (mut class, mut plain) = (false, false);
for child in tc.children() {
let Some(sub) = child.as_timing_control() else {
continue;
};
match first_expression_child(&sub) {
Some(sig)
if sub.kind() == TimingControlKind::SignalEvent && is_class_event_expr(&sig) =>
{
class = true;
}
_ => plain = true,
}
}
class && plain
}
fn is_process_by_request_stmt(stmt: &Statement<'_>) -> bool {
let Some(e) = stmt.expr() else {
return false;
};
if e.kind() != ExpressionKind::Call || e.is_system_call() == Some(true) {
return false;
}
let Some(callee) = e.call_subroutine() else {
return false;
};
std_process_method(&callee).is_some_and(|m| m == "await" || m == "suspend")
|| std_class_method(&callee, "semaphore").is_some_and(|m| m == "get")
|| std_class_method(&callee, "mailbox")
.is_some_and(|m| matches!(m.as_str(), "put" | "get" | "peek"))
}
fn is_mailbox_two_phase_stmt(stmt: &Statement<'_>) -> bool {
let Some(e) = stmt.expr() else {
return false;
};
e.kind() == ExpressionKind::Call
&& e.is_system_call() != Some(true)
&& e.call_subroutine()
.and_then(|callee| std_class_method(&callee, "mailbox"))
.is_some_and(|m| m == "get" || m == "peek")
}
pub fn subroutine_can_abort(sub: &Symbol<'_>) -> bool {
let mut visiting = std::collections::HashSet::new();
can_abort_in(sub, &mut visiting)
}
thread_local! {
static ABORTING_VIRTUALS: std::cell::RefCell<HashSet<crate::symid::SymbolId>> =
std::cell::RefCell::new(HashSet::new());
static RANDOMIZE_CAN_ABORT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
pub fn reset_aborting_virtuals() {
ABORTING_VIRTUALS.with(|s| s.borrow_mut().clear());
RANDOMIZE_CAN_ABORT.with(|c| c.set(false));
}
pub fn set_randomize_can_abort() -> bool {
RANDOMIZE_CAN_ABORT.with(|c| !c.replace(true))
}
pub fn randomize_can_abort() -> bool {
RANDOMIZE_CAN_ABORT.with(|c| c.get())
}
pub fn mark_aborting_virtual(uid: crate::symid::SymbolId) -> bool {
ABORTING_VIRTUALS.with(|s| s.borrow_mut().insert(uid))
}
fn can_abort_in(
sub: &Symbol<'_>,
visiting: &mut std::collections::HashSet<crate::symid::SymbolId>,
) -> bool {
if sub
.subroutine_flags()
.contains(sv_lang::MethodFlags::DPI_IMPORT)
{
return true;
}
if ABORTING_VIRTUALS.with(|s| s.borrow().contains(&sub.uid())) {
return true;
}
if !visiting.insert(sub.uid()) {
return false;
}
let Some(body) = proc_body(sub) else {
return false;
};
if node_can_abort(body.as_sem_node(), Some(sub), visiting) {
return true;
}
sub.subroutine_flags()
.contains(sv_lang::MethodFlags::CONSTRUCTOR)
&& sub
.parent()
.and_then(|class| class.as_type())
.is_some_and(|ty| class_level_can_abort(ty, visiting))
}
pub fn class_construction_can_abort(ty: sv_lang::Type<'_>) -> bool {
let mut visiting = std::collections::HashSet::new();
construction_can_abort(ty, &mut visiting)
}
fn construction_can_abort(
ty: sv_lang::Type<'_>,
visiting: &mut std::collections::HashSet<crate::symid::SymbolId>,
) -> bool {
let mut level = Some(ty);
while let Some(t) = level {
if !t.is_class() {
return false;
}
if class_level_can_abort(t, visiting) {
return true;
}
if t.class_constructor()
.is_some_and(|ctor| can_abort_in(&ctor, visiting))
{
return true;
}
level = t.class_base();
}
false
}
fn class_level_can_abort(
ty: sv_lang::Type<'_>,
visiting: &mut std::collections::HashSet<crate::symid::SymbolId>,
) -> bool {
let class_sym = ty.as_symbol();
if !visiting.insert(class_sym.uid()) {
return false;
}
if let Some(call) = ty.class_base_constructor_call()
&& node_can_abort(call.as_sem_node(), None, visiting)
{
return true;
}
for member in class_sym.members() {
if member.kind() == SymbolKind::ClassProperty
&& let Some(init) = member.initializer()
&& node_can_abort(init.as_sem_node(), None, visiting)
{
return true;
}
}
false
}
fn node_can_abort<'d>(
root: SemNode<'d>,
enclosing: Option<&Symbol<'d>>,
visiting: &mut std::collections::HashSet<crate::symid::SymbolId>,
) -> bool {
let mut stack = vec![root];
while let Some(n) = stack.pop() {
if let Some(s) = n.as_statement()
&& s.kind() == StatementKind::ExpressionStatement
&& let Some(e) = s.expr()
&& is_failable_task_cast(&e)
{
return true;
}
if let Some(e) = n.as_expression() {
if crate::netlist::vif::is_member_access(&e)
|| crate::netlist::vif::call_targets(&e).is_some()
{
return true;
}
if e.kind() == ExpressionKind::Call {
if e.is_system_call() == Some(true) {
if e.system_subroutine().is_some_and(|s| {
match s.known_name_id() {
KnownSystemName::Finish
| KnownSystemName::Stop
| KnownSystemName::Fatal => true,
KnownSystemName::Randomize => randomize_can_abort(),
_ => false,
}
}) {
return true;
}
} else if let Some(callee) = e.call_subroutine() {
if std_process_method(&callee).is_some_and(|m| m == "kill") {
return true;
}
for target in crate::netlist::itf::call_callees(&e) {
if can_abort_in(&target, visiting) {
return true;
}
}
}
} else if e.kind() == ExpressionKind::NewClass {
if let Some(ctor_call) = e.new_class_constructor_call() {
stack.push(ctor_call.as_sem_node());
}
let constructed = if e.new_class_is_super_class() == Some(true) {
enclosing
.and_then(|sub| sub.parent())
.and_then(|class| class.as_type())
.and_then(|ty| ty.class_base())
} else {
e.expr_type().map(|t| t.canonical())
};
if constructed.is_some_and(|ty| construction_can_abort(ty, visiting)) {
return true;
}
}
}
stack.extend(live_children(&n));
}
false
}
fn is_failable_task_cast(e: &Expression<'_>) -> bool {
if e.kind() != ExpressionKind::Call
|| e.is_system_call() != Some(true)
|| !e
.system_subroutine()
.is_some_and(|s| s.known_name_id() == KnownSystemName::Cast)
{
return false;
}
let ops = e.operands();
let Some(&dest) = ops.first() else {
return false;
};
let dest = if dest.kind() == ExpressionKind::Assignment {
dest.left().unwrap_or(dest)
} else {
dest
};
dest.expr_type()
.is_some_and(|t| t.is_class() || t.is_enum())
}
fn call_actual_lvalue<'d>(actual: Expression<'d>) -> Expression<'d> {
if actual.kind() == ExpressionKind::Assignment {
actual.left().unwrap_or(actual)
} else {
actual
}
}
fn captured_vars<'d>(branch: &ProcessCfg<'d>) -> Vec<Symbol<'d>> {
let own: std::collections::HashSet<crate::symid::SymbolId> =
super::liveness::declared_local_ids(branch);
captured_in_excluding(vec![branch.body.as_sem_node()], &own)
}
fn captured_in<'d>(roots: Vec<sv_lang::SemNode<'d>>) -> Vec<Symbol<'d>> {
captured_in_excluding(roots, &std::collections::HashSet::new())
}
fn captured_in_excluding<'d>(
roots: Vec<sv_lang::SemNode<'d>>,
own: &std::collections::HashSet<crate::symid::SymbolId>,
) -> Vec<Symbol<'d>> {
let mut out: Vec<Symbol<'d>> = Vec::new();
let mut seen = std::collections::HashSet::new();
let mut stack = roots;
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression()
&& crate::symid::is_value_name(e.kind())
&& let Some(r) = e.referenced_symbol()
{
let procedural = (r.kind() == SymbolKind::Variable
&& r.variable_lifetime() == sv_lang::VariableLifetime::Automatic
&& r.parent().is_some_and(|p| {
matches!(
p.kind(),
SymbolKind::StatementBlock | SymbolKind::Subroutine
)
}))
|| r.kind() == SymbolKind::FormalArgument;
if procedural && !own.contains(&r.uid()) && seen.insert(r.uid()) {
out.push(r);
}
}
stack.extend(live_children(&n));
}
out
}
pub fn block_automatic_vars<'d>(stmt: &Statement<'d>, with_loop_vars: bool) -> Vec<Symbol<'d>> {
if stmt.kind() != StatementKind::Block {
return Vec::new();
}
let Some(block) = stmt.block_symbol() else {
return Vec::new();
};
let mut loop_vars = std::collections::HashSet::new();
if !with_loop_vars {
let mut stack = stmt.statements();
while let Some(s) = stack.pop() {
if s.kind() == StatementKind::List {
stack.extend(s.statements());
} else if s.kind() == StatementKind::ForLoop {
loop_vars.extend(s.for_loop_vars().iter().map(|v| v.uid()));
}
}
}
block
.members()
.filter(|m| {
m.kind() == SymbolKind::Variable
&& m.variable_lifetime() == sv_lang::VariableLifetime::Automatic
&& !loop_vars.contains(&m.uid())
})
.collect()
}
pub const INTERNAL_NAME_PREFIX: &str = "__vj_";
pub fn shown_path(sym: &Symbol<'_>) -> String {
let path = sym.hierarchical_path();
if !path.contains(INTERNAL_NAME_PREFIX) {
return path;
}
let mut out = String::with_capacity(path.len());
let mut rest = path.as_str();
while let Some(i) = rest.find(INTERNAL_NAME_PREFIX) {
let at_segment = i == 0 || rest.as_bytes()[i - 1] == b'.';
if !at_segment {
out.push_str(&rest[..i + INTERNAL_NAME_PREFIX.len()]);
rest = &rest[i + INTERNAL_NAME_PREFIX.len()..];
continue;
}
let end = rest[i..].find('.').map_or(rest.len(), |e| i + e);
if i > 0 {
out.push_str(&rest[..i - 1]);
rest = &rest[end..];
} else {
rest = rest[end..].strip_prefix('.').unwrap_or(&rest[end..]);
}
}
out.push_str(rest);
out
}
pub fn may_hold_handles(ty: &sv_lang::Type<'_>) -> bool {
if ty.is_class() || crate::state::storage::is_collection(ty) {
return true;
}
ty.is_unpacked_struct()
&& ty
.fields()
.any(|f| f.value_type().is_some_and(|t| may_hold_handles(&t)))
}
pub fn is_automatic_block_var(var: &Symbol<'_>) -> bool {
var.variable_lifetime() == sv_lang::VariableLifetime::Automatic
&& var
.parent()
.is_some_and(|p| p.kind() == SymbolKind::StatementBlock)
}
fn task_locals<'d>(task: &Symbol<'d>) -> Vec<Symbol<'d>> {
let formal_ids: std::collections::HashSet<crate::symid::SymbolId> =
task.subroutine_arguments().map(|a| a.uid()).collect();
let ret_id = task.subroutine_return_val_var().map(|r| r.uid());
let mut out = Vec::new();
let mut seen = std::collections::HashSet::new();
let mut take = |m: Symbol<'d>, out: &mut Vec<Symbol<'d>>| {
let id = m.uid();
if m.kind() == SymbolKind::Variable
&& !formal_ids.contains(&id)
&& Some(&id) != ret_id.as_ref()
&& seen.insert(id)
{
out.push(m);
}
};
for m in task.members() {
take(m, &mut out);
}
if let Some(body) = proc_body(task) {
let mut stack = vec![body.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(s) = n.as_statement() {
for block in block_scopes(&s) {
for m in block.members() {
take(m, &mut out);
}
}
}
let mut kids = live_children(&n);
kids.reverse();
stack.extend(kids);
}
}
out
}
pub use super::liveness::FrameLayout;
use super::liveness::FrameSlotSource;
#[derive(Debug, Clone)]
pub struct Segment<'d> {
pub id: SegId,
pub live_in: Vec<u32>,
pub bindings: Vec<ArgBinding<'d>>,
pub this_var: Option<Symbol<'d>>,
pub ref_aliases: Vec<(Symbol<'d>, Symbol<'d>)>,
pub body: Vec<Statement<'d>>,
pub probes: Vec<(usize, Statement<'d>)>,
pub disable_probes: Vec<(usize, Statement<'d>)>,
pub frame_probes: Vec<(usize, FrameProbe<'d>)>,
pub block_events: Vec<(usize, Symbol<'d>, bool)>,
pub stmt_scopes: Vec<(usize, Symbol<'d>)>,
pub step_exprs: Vec<Expression<'d>>,
pub repeat_init: Option<(RepeatCounterId, RepeatCount<'d>)>,
pub repeat_decrement: Option<RepeatCounterId>,
pub expect_resume: bool,
pub terminator: SegTerminator<'d>,
}
#[derive(Debug, Clone)]
pub enum SegTerminator<'d> {
Goto(SegId),
CondGoto {
cond: Expression<'d>,
t: SegId,
f: SegId,
},
Suspend {
point: SuspendPoint<'d>,
resume: SegId,
live_out: Vec<u32>,
},
End {
loop_back: Option<SegId>,
},
SwitchGoto {
on: SwitchOn<'d>,
arms: Vec<SegId>,
default: SegId,
report: Option<sv_lang::SourceLoc>,
},
CondGotoCounter {
counter: RepeatCounterId,
t: SegId,
f: SegId,
},
ForkJoin {
kind: StatementBlockKind,
branches: Vec<ProcessCfg<'d>>,
join_blk: SegId,
live_out: Vec<u32>,
},
AssertGoto {
stmt: Statement<'d>,
pass: SegId,
fail: SegId,
done: SegId,
},
CaseGoto {
stmt: Statement<'d>,
arms: Vec<SegId>,
default: SegId,
},
}
pub fn lower_to_segments<'d>(cfg: &ProcessCfg<'d>, frame: &FrameLayout) -> Vec<Segment<'d>> {
let permanent: Vec<u32> = frame
.slots
.iter()
.enumerate()
.filter(|(_, s)| {
matches!(
s.source,
FrameSlotSource::DeclaredVar(_)
| FrameSlotSource::SyntheticCounter(_)
| FrameSlotSource::Temporary(_)
)
})
.map(|(i, _)| i as u32)
.collect();
cfg.blocks
.iter()
.enumerate()
.map(|(i, b)| Segment {
id: SegId(i as u32),
live_in: permanent.clone(),
bindings: b.bindings.clone(),
this_var: b.this_var,
ref_aliases: b.ref_aliases.clone(),
body: b.stmts.clone(),
probes: b.probes.clone(),
disable_probes: b.disable_probes.clone(),
frame_probes: b.frame_probes.clone(),
block_events: b.block_events.clone(),
stmt_scopes: b.stmt_scopes.clone(),
step_exprs: b.step_exprs.clone(),
repeat_init: b.repeat_init.clone(),
repeat_decrement: b.repeat_decrement,
expect_resume: b.expect_resume,
terminator: lower_terminator(&b.terminator, &permanent),
})
.collect()
}
pub fn contains_unschedulable_fork_join(cfg: &ProcessCfg<'_>) -> bool {
cfg.blocks
.iter()
.any(|b| matches!(b.terminator, Terminator::ForkJoin { .. }))
}
pub fn contains_unsupported_fork_join_kind(cfg: &ProcessCfg<'_>) -> bool {
cfg.blocks.iter().any(|b| {
matches!(
b.terminator,
Terminator::ForkJoin {
kind: StatementBlockKind::JoinAny | StatementBlockKind::JoinNone,
..
}
)
})
}
fn lower_terminator<'d>(t: &Terminator<'d>, permanent: &[u32]) -> SegTerminator<'d> {
match t {
Terminator::Fallthrough(b) => SegTerminator::Goto(SegId(b.0)),
Terminator::Switch {
on,
arms,
default_blk,
report,
} => SegTerminator::SwitchGoto {
on: on.clone(),
arms: arms.iter().map(|b| SegId(b.0)).collect(),
default: SegId(default_blk.0),
report: *report,
},
Terminator::Branch {
cond,
then_blk,
else_blk,
} => SegTerminator::CondGoto {
cond: *cond,
t: SegId(then_blk.0),
f: SegId(else_blk.0),
},
Terminator::Suspend { point, resume_blk } => SegTerminator::Suspend {
point: point.clone(),
resume: SegId(resume_blk.0),
live_out: permanent.to_vec(),
},
Terminator::End { loop_back } => SegTerminator::End {
loop_back: loop_back.map(|b| SegId(b.0)),
},
Terminator::Loop { entry, .. } => SegTerminator::Goto(SegId(entry.0)),
Terminator::RepeatCheck {
counter,
then_blk,
else_blk,
} => SegTerminator::CondGotoCounter {
counter: *counter,
t: SegId(then_blk.0),
f: SegId(else_blk.0),
},
Terminator::ForkJoin {
kind,
branches,
join_blk,
} => SegTerminator::ForkJoin {
kind: *kind,
branches: branches.clone(),
join_blk: SegId(join_blk.0),
live_out: permanent.to_vec(),
},
Terminator::Assertion {
stmt,
pass_blk,
fail_blk,
done_blk,
} => SegTerminator::AssertGoto {
stmt: *stmt,
pass: SegId(pass_blk.0),
fail: SegId(fail_blk.0),
done: SegId(done_blk.0),
},
Terminator::Case {
stmt,
arms,
default_blk,
} => SegTerminator::CaseGoto {
stmt: *stmt,
arms: arms.iter().map(|b| SegId(b.0)).collect(),
default: SegId(default_blk.0),
},
}
}
pub fn count_timed_statements(body: &Statement<'_>) -> usize {
let mut count = 0usize;
let mut stack = vec![body.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(s) = n.as_statement()
&& s.kind() == StatementKind::Timed
{
count += 1;
}
stack.extend(live_children(&n));
}
count
}
pub fn check_suspend_count_matches(
own_walk_count: usize,
analyzed_count: usize,
) -> Result<(), LoweringError> {
if own_walk_count == analyzed_count {
Ok(())
} else {
Err(LoweringError::malformed(format!(
"PR-074 suspend-point cross-check failed: this subsystem's own tree walk found \
{own_walk_count} Timed statement(s), but AnalyzedProcedure::timing_controls() \
found {analyzed_count} -- this is a hard bug (a missed or double-counted \
suspend point), never a warning"
)))
}
}