use crate::analyzer_error::{AnalyzerError, ExceedLimitKind};
use crate::conv::conv_profiler::{ConvProfile, ConvProfileGuard};
use crate::conv::instance::{InstanceHistory, InstanceHistoryError};
use crate::ir::{
Component, Comptime, Declaration, Expression, FfClock, FfReset, FuncPath, Function, Interface,
IrResult, ShapeRef, Signature, Statement, Type, VarId, VarIndex, VarKind, VarPath, VarSelect,
Variable, VariableInfo,
};
use crate::namespace::Namespace;
use crate::scope;
use crate::symbol::{Affiliation, ClockDomain, Direction, GenericMap, SymbolId};
use crate::symbol_path::GenericSymbolPath;
use crate::value::MaskCache;
use crate::{HashMap, HashSet};
use miette::Result;
use std::sync::{Arc, Mutex};
use veryl_parser::resource_table::StrId;
use veryl_parser::token_range::TokenRange;
use veryl_parser::veryl_grammar_trait::{ComponentInstantiation, Identifier};
use veryl_parser::veryl_token::Token;
#[derive(Clone)]
pub struct Config {
pub retain_component_body: bool,
pub instance_depth_limit: usize,
pub instance_total_limit: usize,
pub function_instance_depth_limit: usize,
pub evaluate_size_limit: usize,
pub evaluate_array_limit: usize,
pub defines: HashSet<StrId>,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct RuntimeFunctionEffect {
pub external_write: bool,
pub written_outputs: HashSet<VarPath>,
}
impl RuntimeFunctionEffect {
pub fn written_paths_for<'a>(
&'a self,
formal: &'a VarPath,
) -> impl Iterator<Item = &'a VarPath> {
self.written_outputs
.iter()
.filter(move |path| path.0.starts_with(&formal.0))
}
}
#[derive(Clone, Debug)]
pub(crate) struct FunctionOutputBinding {
pub scheduler_external: bool,
}
#[derive(Clone, Debug)]
struct FunctionEffectFrame {
path: FuncPath,
outputs: HashMap<VarId, FunctionOutputBinding>,
effect: RuntimeFunctionEffect,
}
impl Default for Config {
fn default() -> Self {
Self {
retain_component_body: false,
instance_depth_limit: 1024,
instance_total_limit: 1024 * 1024,
function_instance_depth_limit: 24,
evaluate_size_limit: 1024 * 1024,
evaluate_array_limit: 128,
defines: HashSet::default(),
}
}
}
#[derive(Default)]
pub struct Context {
pub config: Config,
pub var_id: VarId,
pub var_paths: HashMap<VarPath, (VarId, Comptime)>,
pub func_paths: HashMap<FuncPath, VarId>,
pub converting_funcs: Vec<SymbolId>,
pub variables: HashMap<VarId, Variable>,
pub functions: HashMap<VarId, Function>,
pub port_types: HashMap<VarPath, (Type, ClockDomain)>,
pub modports: HashMap<StrId, Vec<(StrId, Direction)>>,
pub declarations: Vec<Declaration>,
pub default_clock: Option<(VarPath, SymbolId)>,
pub default_reset: Option<(VarPath, SymbolId)>,
pub inst_signatures: HashMap<StrId, Signature>,
pub modport_signatures: Vec<HashMap<StrId, Signature>>,
pub instance_history: InstanceHistory,
pub function_eval_depth: usize,
pub function_eval_overflow: Option<(TokenRange, usize)>,
pub comptime_for_overflow: Option<TokenRange>,
pub function_call_stack: Vec<Signature>,
function_effects: HashMap<FuncPath, RuntimeFunctionEffect>,
function_effect_stack: Vec<FunctionEffectFrame>,
pub function_ret_var: Option<VarId>,
pub function_returned: bool,
pub select_paths: Vec<(VarPath, GenericSymbolPath)>,
pub select_dims: Vec<usize>,
pub ignore_var_func: bool,
pub disalbe_const_opt: bool,
pub namespaces: Vec<Namespace>,
pub in_generic: bool,
pub allow_component_as_factor: bool,
pub in_test_module: bool,
pub in_dependency: bool,
pub in_global_func: Option<Token>,
pub in_if_reset: bool,
pub in_tb_block: bool,
pub in_initial: Option<VarId>,
pub tb_hoist: Option<Vec<crate::ir::Statement>>,
pub tb_hoist_count: usize,
pub tb_component_params:
crate::HashMap<veryl_parser::resource_table::StrId, Vec<(String, u64)>>,
pub inst_components: HashMap<StrId, Arc<Component>>,
pub in_inst_port: bool,
pub current_clock: Option<Comptime>,
pub condition_domains: Vec<Comptime>,
pub mask_cache: MaskCache,
pub tb_reset_cycles: HashMap<StrId, Expression>,
pub tb_clock_period: HashMap<StrId, Expression>,
pub tb_reset_clock: HashMap<StrId, StrId>,
analysis_transactions: Vec<AnalysisTransaction>,
hierarchy: Vec<StrId>,
hierarchical_variables: Vec<Vec<VarPath>>,
hierarchical_functions: Vec<Vec<FuncPath>>,
shadowed_variables: HashMap<VarPath, Vec<(VarId, Comptime)>>,
affiliation: Vec<Affiliation>,
overrides: Vec<HashMap<VarPath, (Comptime, Expression)>>,
generic_maps: Vec<Vec<GenericMap>>,
typedef_visiting: Vec<SymbolId>,
errors: Vec<AnalyzerError>,
profiler: Option<Arc<Mutex<ConvProfile>>>,
project_name: Option<String>,
}
struct AnalysisTransaction {
variables: HashMap<VarId, Variable>,
comptime_for_overflow: Option<TokenRange>,
disable_const_opt: bool,
function_eval_depth: usize,
function_eval_overflow: Option<(TokenRange, usize)>,
function_call_stack: Vec<Signature>,
errors: Vec<AnalyzerError>,
}
impl Context {
pub(crate) fn begin_analysis_transaction(&mut self) {
self.analysis_transactions.push(AnalysisTransaction {
variables: HashMap::default(),
comptime_for_overflow: self.comptime_for_overflow,
disable_const_opt: self.disalbe_const_opt,
function_eval_depth: self.function_eval_depth,
function_eval_overflow: self.function_eval_overflow.take(),
function_call_stack: std::mem::take(&mut self.function_call_stack),
errors: std::mem::take(&mut self.errors),
});
self.function_eval_depth = 0;
self.comptime_for_overflow = None;
}
pub(crate) fn rollback_analysis_transaction(&mut self) {
let transaction = self
.analysis_transactions
.pop()
.expect("analysis transaction is active");
for (id, variable) in transaction.variables {
self.variables.insert(id, variable);
}
self.comptime_for_overflow = transaction.comptime_for_overflow;
self.disalbe_const_opt = transaction.disable_const_opt;
self.function_eval_depth = transaction.function_eval_depth;
self.function_eval_overflow = transaction.function_eval_overflow;
self.function_call_stack = transaction.function_call_stack;
self.errors = transaction.errors;
}
pub(crate) fn variable_mut(&mut self, id: &VarId) -> Option<&mut Variable> {
if self
.analysis_transactions
.last()
.is_some_and(|transaction| !transaction.variables.contains_key(id))
{
let previous = self.variables.get(id)?.clone();
self.analysis_transactions
.last_mut()
.expect("checked above")
.variables
.insert(*id, previous);
}
self.variables.get_mut(id)
}
pub fn with_condition_domain<T>(
&mut self,
cond: Comptime,
f: impl FnOnce(&mut Self) -> T,
) -> T {
self.condition_domains.push(cond);
let ret = f(self);
self.condition_domains.pop();
ret
}
pub fn inherit(&mut self, tgt: &mut Context) {
std::mem::swap(&mut self.overrides, &mut tgt.overrides);
std::mem::swap(&mut self.generic_maps, &mut tgt.generic_maps);
std::mem::swap(&mut self.modport_signatures, &mut tgt.modport_signatures);
std::mem::swap(&mut self.instance_history, &mut tgt.instance_history);
std::mem::swap(&mut self.function_eval_depth, &mut tgt.function_eval_depth);
std::mem::swap(
&mut self.function_eval_overflow,
&mut tgt.function_eval_overflow,
);
std::mem::swap(
&mut self.comptime_for_overflow,
&mut tgt.comptime_for_overflow,
);
std::mem::swap(&mut self.function_call_stack, &mut tgt.function_call_stack);
std::mem::swap(&mut self.function_effects, &mut tgt.function_effects);
std::mem::swap(
&mut self.function_effect_stack,
&mut tgt.function_effect_stack,
);
std::mem::swap(&mut self.converting_funcs, &mut tgt.converting_funcs);
std::mem::swap(&mut self.errors, &mut tgt.errors);
std::mem::swap(&mut self.namespaces, &mut tgt.namespaces);
self.disalbe_const_opt = tgt.disalbe_const_opt;
self.in_generic = tgt.in_generic;
self.allow_component_as_factor = tgt.allow_component_as_factor;
self.config = tgt.config.clone();
self.profiler = tgt.profiler.clone();
self.project_name = tgt.project_name.clone();
}
pub fn set_project_name(&mut self, project_name: &str) {
self.project_name = Some(project_name.to_string());
}
pub fn project_name(&self) -> Option<&str> {
self.project_name.as_deref()
}
pub fn enable_conv_profiler(&mut self) {
if std::env::var("VERYL_CONV_PROFILE").is_ok() {
self.profiler = Some(Arc::new(Mutex::new(ConvProfile::default())));
}
}
pub fn conv_profile_guard(&self, component_name: StrId) -> Option<ConvProfileGuard> {
self.profiler.as_ref().map(|p| {
let key = format!(
"{}::{}",
self.project_name.as_deref().unwrap_or("unknown"),
component_name
);
ConvProfileGuard::new(Arc::clone(p), key)
})
}
pub fn finalize_conv_profiler(&self) -> Result<()> {
if let Some(profiler) = self.profiler.as_ref().map(|p| p.lock().unwrap()) {
profiler.to_toml_file(&std::env::var("VERYL_CONV_PROFILE").unwrap())
} else {
Ok(())
}
}
pub fn get_override(&self, x: &VarPath) -> Option<&(Comptime, Expression)> {
let overrides = self.overrides.last()?;
overrides.get(x)
}
pub fn get_variable_info(&self, id: VarId) -> Option<VariableInfo> {
self.variables.get(&id).map(VariableInfo::new)
}
pub(crate) fn begin_function_effect(
&mut self,
path: FuncPath,
outputs: HashMap<VarId, FunctionOutputBinding>,
effect: RuntimeFunctionEffect,
) {
self.function_effect_stack.push(FunctionEffectFrame {
path,
outputs,
effect,
});
}
pub(crate) fn finish_function_effect(&mut self) {
if let Some(frame) = self.function_effect_stack.pop() {
self.function_effects.insert(frame.path, frame.effect);
}
}
pub(crate) fn function_effect(&self, path: &FuncPath) -> Option<RuntimeFunctionEffect> {
self.function_effects.get(path).cloned()
}
pub(crate) fn record_function_call_effect(
&mut self,
effect: &RuntimeFunctionEffect,
outputs: &[(VarPath, Vec<crate::ir::AssignDestination>)],
) {
let Some(frame) = self.function_effect_stack.last() else {
return;
};
let caller_outputs = frame.outputs.clone();
let mut external_write = effect.external_write;
let mut written_outputs = HashSet::default();
for (formal, destinations) in outputs {
for written in effect.written_paths_for(formal) {
let suffix = &written.0[formal.0.len()..];
for destination in destinations {
if let Some(output) = caller_outputs.get(&destination.id) {
let mut mapped = destination.path.clone();
mapped.0.extend_from_slice(suffix);
written_outputs.insert(mapped);
external_write |= output.scheduler_external;
} else if self
.get_variable_info(destination.id)
.is_none_or(|variable| variable.affiliation != Affiliation::Function)
{
external_write = true;
}
}
}
}
if let Some(frame) = self.function_effect_stack.last_mut() {
frame.effect.external_write |= external_write;
frame.effect.written_outputs.extend(written_outputs);
}
}
pub(crate) fn record_function_statement_writes(&mut self, statements: &[Statement]) {
let Some(caller_outputs) = self
.function_effect_stack
.last()
.map(|frame| frame.outputs.clone())
else {
return;
};
let mut effect = RuntimeFunctionEffect::default();
fn visit(
context: &Context,
statements: &[Statement],
caller_outputs: &HashMap<VarId, FunctionOutputBinding>,
effect: &mut RuntimeFunctionEffect,
) {
for statement in statements {
match statement {
Statement::Assign(assign) => {
for destination in &assign.dst {
if let Some(output) = caller_outputs.get(&destination.id) {
effect.written_outputs.insert(destination.path.clone());
effect.external_write |= output.scheduler_external;
} else if context.get_variable_info(destination.id).is_none_or(
|variable| {
variable.affiliation != Affiliation::Function
|| matches!(variable.kind, VarKind::Output | VarKind::Inout)
},
) {
effect.external_write = true;
}
}
}
Statement::If(statement) => {
visit(context, &statement.true_side, caller_outputs, effect);
visit(context, &statement.false_side, caller_outputs, effect);
}
Statement::IfReset(statement) => {
visit(context, &statement.true_side, caller_outputs, effect);
visit(context, &statement.false_side, caller_outputs, effect);
}
Statement::Case(statement) => {
for arm in &statement.arms {
visit(context, &arm.body, caller_outputs, effect);
}
visit(context, &statement.default, caller_outputs, effect);
}
Statement::For(statement) => {
visit(context, &statement.body, caller_outputs, effect);
}
Statement::SystemFunctionCall(_)
| Statement::FunctionCall(_)
| Statement::TbMethodCall(_)
| Statement::Break
| Statement::Unsupported(_)
| Statement::Null => {}
}
}
}
visit(self, statements, &caller_outputs, &mut effect);
if let Some(frame) = self.function_effect_stack.last_mut() {
frame.effect.external_write |= effect.external_write;
frame.effect.written_outputs.extend(effect.written_outputs);
}
}
pub fn resolve_path(&self, mut path: GenericSymbolPath) -> GenericSymbolPath {
let Some((scope, define_context)) = scope::token_scope(path.paths[0].base.id) else {
return path;
};
path.resolve_imported(scope, &define_context, None);
for map in self.generic_maps.iter().rev() {
path.apply_map(map);
}
path
}
pub fn insert_var_path(&mut self, path: VarPath, value: Comptime) -> VarId {
let id = self.var_id;
self.insert_var_path_with_id(path, id, value);
self.var_id.inc();
id
}
pub fn insert_var_path_with_id(&mut self, path: VarPath, id: VarId, value: Comptime) {
if let Some(x) = self.hierarchical_variables.last_mut() {
x.push(path.clone());
}
let shadowed = self.var_paths.insert(path.clone(), (id, value));
if let Some(x) = shadowed {
self.shadowed_variables
.entry(path)
.and_modify(|v| v.push(x.clone()))
.or_insert(vec![x]);
}
}
pub fn insert_func_path(&mut self, path: FuncPath) -> VarId {
let id = self.var_id;
self.func_paths.insert(path, id);
self.var_id.inc();
id
}
pub fn insert_variable(&mut self, id: VarId, mut variable: Variable) {
if self.ignore_var_func {
return;
}
let hier = &self.hierarchy;
if !hier.is_empty() {
variable.path.add_prelude(hier);
}
self.variables.insert(id, variable);
}
pub fn insert_port_type(&mut self, path: VarPath, r#type: Type, clock_domain: ClockDomain) {
self.port_types.insert(path, (r#type, clock_domain));
}
pub fn insert_function(&mut self, id: VarId, mut function: Function) {
if self.ignore_var_func {
return;
}
let hier = &self.hierarchy;
if !hier.is_empty() {
function.path.add_prelude(hier);
}
self.functions.insert(id, function);
}
pub fn insert_declaration(&mut self, decl: Declaration) {
if !decl.is_null() {
self.declarations.push(decl);
}
}
pub fn insert_error(&mut self, mut error: AnalyzerError) {
let mut replaced = false;
if let AnalyzerError::MultipleAssignment {
identifier: ref new_ident,
error_locations: ref mut new_locations,
..
} = error
{
for e in &mut self.errors {
if let AnalyzerError::MultipleAssignment {
identifier: org_ident,
error_locations: org_locations,
..
} = e
&& new_ident == org_ident
{
org_locations.append(new_locations);
org_locations.sort();
org_locations.dedup();
replaced = true;
break;
}
}
}
if !replaced && !self.errors.contains(&error) {
self.errors.push(error);
}
}
pub fn insert_modport(&mut self, name: StrId, members: Vec<(StrId, Direction)>) {
self.modports.insert(name, members);
}
pub fn extract_function(&mut self, context: &mut Context, base: &VarPath, array: &ShapeRef) {
for (id, mut variable) in context.variables.drain() {
let path_offset = variable.path.0.len();
variable.path.add_prelude(&base.0);
variable.prepend_array_at_path(array, path_offset);
self.variables.insert(id, variable);
}
for (mut path, id) in context.func_paths.drain() {
if !path.path.starts_with(&base.0) {
path.path.add_prelude(&base.0);
}
self.func_paths.insert(path, id);
}
for (id, mut function) in context.functions.drain() {
if !array.is_empty() {
let total_array = array.total();
let func_body = function.functions.remove(0);
if let Some(total_array) = total_array {
for i in 0..total_array {
let var_index = VarIndex::from_index(i, array);
let mut func_body = func_body.clone();
func_body.set_index(&var_index);
function.functions.push(func_body);
}
}
}
if !function.path.path.starts_with(&base.0) {
function.path.path.add_prelude(&base.0);
}
self.functions.insert(id, function);
}
}
pub fn extract_var_paths(&mut self, context: &Context, base: &VarPath, array: &ShapeRef) {
for (path, (id, comptime)) in &context.var_paths {
if path.starts_with(&base.0) {
let mut path = path.clone();
path.remove_prelude(&base.0);
if !path.0.is_empty() {
let mut comptime = comptime.clone();
for _ in 0..array.dims() {
comptime.r#type.array.remove(0);
}
self.var_paths.insert(path, (*id, comptime));
}
}
}
}
pub fn extract_interface_member(
&mut self,
base: StrId,
array: &ShapeRef,
component: Interface,
modport: Option<StrId>,
clock_domain: ClockDomain,
token: TokenRange,
) {
let mut inserted = HashSet::default();
let modport_members = if let Some(x) = &modport {
component.get_modport(x)
} else {
HashMap::default()
};
let mut id_map = HashMap::default();
for mut variable in component.variables.into_values() {
if modport.is_some() {
if let Some(x) = modport_members.get(&variable.path.first()) {
variable.kind = match x {
Direction::Input => VarKind::Input,
Direction::Output => VarKind::Output,
Direction::Inout => VarKind::Inout,
_ => variable.kind,
};
} else {
continue;
}
}
let path_offset = variable.path.0.len();
variable.prepend_array_at_path(array, path_offset);
variable.token = token;
variable.affiliation = self.get_affiliation();
inserted.insert(variable.path.clone());
let comptime = Comptime::from_type(variable.r#type.clone(), clock_domain, token);
variable.path.add_prelude(&[base]);
let id = self.insert_var_path(variable.path.clone(), comptime);
id_map.insert(variable.id, id);
variable.id = id;
self.insert_variable(id, variable);
}
for (mut path, (id, mut comptime)) in component.var_paths {
if !inserted.contains(&path) {
path.add_prelude(&[base]);
comptime.r#type.prepend_array(array);
if let Some(id) = id_map.get(&id) {
self.insert_var_path_with_id(path, *id, comptime);
} else {
self.insert_var_path(path, comptime);
}
}
}
}
pub fn inc_select_dim(&mut self) {
if let Some(x) = self.select_dims.last_mut() {
*x += 1;
}
}
pub fn get_select_dim(&self) -> Option<usize> {
self.select_dims.last().copied()
}
pub fn set_default_clock(&mut self, path: VarPath, id: SymbolId) {
self.default_clock.replace((path, id));
}
pub fn set_default_reset(&mut self, path: VarPath, id: SymbolId) {
self.default_reset.replace((path, id));
}
pub fn get_default_clock(&self) -> Option<(FfClock, SymbolId)> {
if let Some(x) = &self.default_clock {
if let Some((id, comptime)) = &self.var_paths.get(&x.0) {
let ret = FfClock {
id: *id,
index: VarIndex::default(),
select: VarSelect::default(),
comptime: comptime.clone(),
};
Some((ret, x.1))
} else {
None
}
} else {
None
}
}
pub fn get_default_reset(&self) -> Option<(FfReset, SymbolId)> {
if let Some(x) = &self.default_reset {
if let Some((id, comptime)) = &self.var_paths.get(&x.0) {
let ret = FfReset {
id: *id,
index: VarIndex::default(),
select: VarSelect::default(),
comptime: comptime.clone(),
};
Some((ret, x.1))
} else {
None
}
} else {
None
}
}
pub fn insert_inst_signature(&mut self, identifier: &Identifier, sig: &Signature) {
self.inst_signatures.insert(identifier.text(), sig.clone());
}
pub fn collect_modport_signatures(
&mut self,
inst: &ComponentInstantiation,
) -> HashMap<StrId, Signature> {
let mut signatures = HashMap::default();
if !self.inst_signatures.is_empty()
&& let Some(ref opt2) = inst.component_instantiation_opt2
&& let Some(ref port_opt) = opt2.inst_port.inst_port_opt
{
let port_items: Vec<_> = port_opt.inst_port_list.as_ref().into();
for port_item in &port_items {
let inst_name = if let Some(ref x) = port_item.inst_port_item_opt {
let Some(identifier) = x.expression.unwrap_identifier().map(|x| x.identifier())
else {
continue;
};
identifier.token.text
} else {
port_item.identifier.text()
};
if let Some(sig) = self.inst_signatures.get(&inst_name) {
let port_name = port_item.identifier.text();
signatures.insert(port_name, sig.clone());
}
}
}
self.modport_signatures.push(signatures.clone());
signatures
}
pub fn pop_modport_signatures(&mut self) {
self.modport_signatures.pop();
}
pub fn get_modport_signature(&self, port_identifier: &Identifier) -> Option<Signature> {
if let Some(signatures) = self.modport_signatures.last() {
signatures.get(&port_identifier.text()).cloned()
} else {
None
}
}
pub fn check_size(&mut self, x: usize, token: TokenRange) -> Option<usize> {
if x > self.config.evaluate_size_limit {
self.insert_error(AnalyzerError::exceed_limit(
ExceedLimitKind::EvaluateSize,
x,
&token,
));
None
} else {
Some(x)
}
}
pub fn block<F, T>(&mut self, f: F) -> IrResult<T>
where
F: FnOnce(&mut Context) -> IrResult<T>,
{
f(self)
}
pub fn push_instance_history(&mut self, x: Signature) -> Result<bool, InstanceHistoryError> {
self.instance_history.push(x, &self.config)
}
pub fn pop_instance_history(&mut self) {
self.instance_history.pop();
}
pub fn get_current_signature(&self) -> Option<&Signature> {
self.instance_history.get_current_signature()
}
pub fn get_instance_history(&self, sig: &Signature) -> Option<(Arc<Component>, bool)> {
self.instance_history.get(sig)
}
pub fn set_instance_history(&mut self, sig: &Signature, component: Arc<Component>) {
self.instance_history.set(sig, component, self.in_generic);
}
pub fn remove_instance_history(&mut self, sig: &Signature) {
self.instance_history.remove(sig);
}
pub fn push_hierarchy(&mut self, x: StrId) {
self.hierarchy.push(x);
self.hierarchical_variables.push(vec![]);
self.hierarchical_functions.push(vec![]);
}
pub fn current_hierarchy(&self) -> &[StrId] {
&self.hierarchy
}
pub fn pop_hierarchy(&mut self) {
self.hierarchy.pop();
let drops = self.hierarchical_variables.pop();
if let Some(drops) = drops {
for x in drops {
if let Some(y) = self.shadowed_variables.get_mut(&x)
&& let Some(y) = y.pop()
{
self.var_paths.insert(x, y);
} else {
self.var_paths.remove(&x);
}
}
}
let drops = self.hierarchical_functions.pop();
if let Some(drops) = drops {
for x in drops {
self.func_paths.remove(&x);
}
}
}
pub fn push_affiliation(&mut self, x: Affiliation) {
self.affiliation.push(x);
}
pub fn pop_affiliation(&mut self) {
self.affiliation.pop();
}
pub fn push_override(&mut self, x: HashMap<VarPath, (Comptime, Expression)>) {
self.overrides.push(x);
}
pub fn pop_override(&mut self) {
self.overrides.pop();
}
pub fn push_generic_map(&mut self, x: Vec<GenericMap>) {
self.generic_maps.push(x);
}
pub fn pop_generic_map(&mut self) {
self.generic_maps.pop();
}
pub fn push_typedef_visiting(&mut self, id: SymbolId) -> bool {
if self.typedef_visiting.contains(&id) {
false
} else {
self.typedef_visiting.push(id);
true
}
}
pub fn pop_typedef_visiting(&mut self) {
self.typedef_visiting.pop();
}
pub fn is_affiliated(&self, value: Affiliation) -> bool {
if let Some(x) = self.affiliation.last() {
*x == value
} else {
false
}
}
pub fn get_affiliation(&self) -> Affiliation {
self.affiliation.last().copied().unwrap()
}
pub fn push_namespace(&mut self, namespace: Namespace) {
self.namespaces.push(namespace);
}
pub fn pop_namespace(&mut self) {
self.namespaces.pop();
}
pub fn current_namespace(&self) -> Option<Namespace> {
self.namespaces.last().cloned()
}
pub fn find_path(&self, path: &VarPath) -> Option<(VarId, Comptime)> {
self.var_paths.get(path).cloned()
}
pub fn get_variable(&self, id: &VarId) -> Option<Variable> {
self.variables.get(id).cloned()
}
pub fn remove_path(&mut self, path: &VarPath) {
self.var_paths.remove(path);
}
pub fn drain_var_paths(&mut self) -> HashMap<VarPath, (VarId, Comptime)> {
std::mem::take(&mut self.var_paths)
}
pub fn drain_func_paths(&mut self) -> HashMap<FuncPath, VarId> {
std::mem::take(&mut self.func_paths)
}
pub fn drain_variables(&mut self) -> HashMap<VarId, Variable> {
std::mem::take(&mut self.variables)
}
pub fn drain_port_types(&mut self) -> HashMap<VarPath, (Type, ClockDomain)> {
std::mem::take(&mut self.port_types)
}
pub fn drain_functions(&mut self) -> HashMap<VarId, Function> {
std::mem::take(&mut self.functions)
}
pub fn drain_modports(&mut self) -> HashMap<StrId, Vec<(StrId, Direction)>> {
std::mem::take(&mut self.modports)
}
pub fn drain_declarations(&mut self) -> Vec<Declaration> {
std::mem::take(&mut self.declarations)
}
pub fn drain_errors(&mut self) -> Vec<AnalyzerError> {
std::mem::take(&mut self.errors)
}
}
#[cfg(test)]
mod analysis_transaction_tests {
use super::*;
use crate::value::Value;
fn test_variable(id: VarId, value: u64) -> Variable {
Variable::new(
id,
VarPath::default(),
VarKind::Variable,
Type::default(),
vec![Value::new(value, 1, false)],
Affiliation::Module,
&TokenRange::default(),
1,
)
}
fn value(context: &Context, id: VarId) -> &Value {
&context.variables[&id].value[0]
}
#[test]
fn nested_analysis_transactions_restore_each_snapshot() {
let id = VarId::from_raw(1);
let mut context = Context::default();
context.variables.insert(id, test_variable(id, 0));
context.begin_analysis_transaction();
context.variable_mut(&id).unwrap().value[0] = Value::new(1, 1, false);
context.begin_analysis_transaction();
context.variable_mut(&id).unwrap().value[0] = Value::new(0, 1, false);
context.rollback_analysis_transaction();
assert_eq!(value(&context, id), &Value::new(1, 1, false));
context.rollback_analysis_transaction();
assert_eq!(value(&context, id), &Value::new(0, 1, false));
}
#[test]
fn reused_analysis_transactions_do_not_leak_variable_state() {
let id = VarId::from_raw(1);
let mut context = Context::default();
context.variables.insert(id, test_variable(id, 0));
for _ in 0..2 {
context.begin_analysis_transaction();
assert_eq!(value(&context, id), &Value::new(0, 1, false));
context.variable_mut(&id).unwrap().value[0] = Value::new(1, 1, false);
context.rollback_analysis_transaction();
}
assert_eq!(value(&context, id), &Value::new(0, 1, false));
}
}