Skip to main content

celox_frontend_core/shared/
lookup.rs

1use std::fmt;
2
3use celox_design::{AbsoluteAddrBase, InstanceId, ModuleId, StateAddr, VariableMetadata};
4use serde::{Deserialize, Serialize};
5
6use crate::{HashMap, HashSet};
7
8pub type SourceAddr = AbsoluteAddrBase<SourceVarId>;
9
10/// Frontend-local identity of a source variable within one module.
11///
12/// This is deliberately distinct from every parser or analyzer's variable ID.
13/// A frontend projects its native IDs into this namespace before constructing
14/// source lookup metadata retained by the runtime.
15#[derive(
16    Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
17)]
18pub struct SourceVarId(pub u32);
19
20impl fmt::Display for SourceVarId {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        write!(f, "var{}", self.0)
23    }
24}
25
26/// Source-language-independent role of a frontend variable.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
28pub enum VariableKind {
29    Parameter,
30    Constant,
31    Input,
32    Output,
33    Inout,
34    Variable,
35    Let,
36}
37
38impl VariableKind {
39    pub const fn is_port(self) -> bool {
40        matches!(self, Self::Input | Self::Output | Self::Inout)
41    }
42
43    pub const fn description(self) -> &'static str {
44        match self {
45            Self::Parameter => "parameter",
46            Self::Constant => "constant",
47            Self::Input => "input",
48            Self::Output => "output",
49            Self::Inout => "inout",
50            Self::Variable => "variable",
51            Self::Let => "let-bounded variable",
52        }
53    }
54}
55
56#[derive(Clone, Serialize, Deserialize)]
57pub struct VariableInfo {
58    pub id: SourceVarId,
59    pub path: Vec<String>,
60    pub var_kind: VariableKind,
61    pub signed: bool,
62    pub metadata: VariableMetadata,
63    /// Per-dimension sizes for the packed shape of the variable.
64    ///
65    /// `VariableMetadata::array_dims` deliberately only describes unpacked
66    /// arrays because that is the source-independent storage shape. The
67    /// testbench adapter also needs packed shape for chained selects.
68    pub packed_dims: Vec<usize>,
69}
70
71impl std::ops::Deref for VariableInfo {
72    type Target = VariableMetadata;
73
74    fn deref(&self) -> &Self::Target {
75        &self.metadata
76    }
77}
78
79impl fmt::Debug for VariableInfo {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        f.debug_struct("VariableInfo")
82            .field("width", &self.width)
83            .field("id", &self.id)
84            .field("is_4state", &self.is_4state)
85            .field("signed", &self.signed)
86            .field("kind", &self.kind)
87            .field("type_kind", &self.type_kind)
88            .finish()
89    }
90}
91
92#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
93pub struct InstancePath(pub Vec<(String, usize)>);
94
95/// Source-language-independent lookup retained for diagnostics and public paths.
96/// Parser-native IDs are projected into [`SourceVarId`] before this artifact is
97/// built and do not cross into runtime metadata.
98#[derive(Clone, Default, Serialize, Deserialize)]
99pub struct FrontendLookup {
100    pub instance_ids: HashMap<InstancePath, InstanceId>,
101    pub instance_module: HashMap<InstanceId, ModuleId>,
102    /// Elaborated children whose source-facing name requires an index.
103    pub indexed_instances: HashSet<InstanceId>,
104    pub module_variables: HashMap<ModuleId, HashMap<SourceVarId, VariableInfo>>,
105    /// Reverse index from source path to source variable ID. `None` marks a
106    /// path that is ambiguous within the module.
107    pub module_var_path_index: HashMap<ModuleId, HashMap<Vec<String>, Option<SourceVarId>>>,
108    pub module_names: HashMap<ModuleId, String>,
109    /// Bidirectional boundary map between frontend source identities and the
110    /// dense source-independent state identities consumed by later phases.
111    pub source_to_state: HashMap<SourceAddr, StateAddr>,
112    pub state_to_source: HashMap<StateAddr, SourceAddr>,
113    /// Event aliases projected to the canonical runtime event domain.
114    pub event_aliases: HashMap<StateAddr, StateAddr>,
115}
116
117impl fmt::Debug for FrontendLookup {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        f.debug_struct("FrontendLookup")
120            .field("instances", &self.instance_module.len())
121            .field("modules", &self.module_variables.len())
122            .field("projected_state_objects", &self.source_to_state.len())
123            .finish_non_exhaustive()
124    }
125}
126
127impl FrontendLookup {
128    pub fn instance_path_segments(&self, path: &InstancePath) -> Vec<String> {
129        let mut prefix = Vec::with_capacity(path.0.len());
130        path.0
131            .iter()
132            .map(|(name, index)| {
133                prefix.push((name.clone(), *index));
134                let instance = self.instance_ids.get(&InstancePath(prefix.clone()));
135                if instance.is_some_and(|id| self.indexed_instances.contains(id)) {
136                    format!("{name}[{index}]")
137                } else {
138                    name.clone()
139                }
140            })
141            .collect()
142    }
143
144    pub fn root_instance_and_module(&self) -> Option<(InstanceId, ModuleId)> {
145        let instance_id = *self.instance_ids.get(&InstancePath(Vec::new()))?;
146        let module_id = *self.instance_module.get(&instance_id)?;
147        Some((instance_id, module_id))
148    }
149
150    pub fn root_variable(&self, var_id: SourceVarId) -> Option<(StateAddr, &VariableInfo)> {
151        let (instance_id, _) = self.root_instance_and_module()?;
152        self.instance_variable(instance_id, var_id)
153    }
154
155    pub fn instance_variable(
156        &self,
157        instance_id: InstanceId,
158        var_id: SourceVarId,
159    ) -> Option<(StateAddr, &VariableInfo)> {
160        let module_id = *self.instance_module.get(&instance_id)?;
161        let info = self.module_variables.get(&module_id)?.get(&var_id)?;
162        let address = self.state_address(&SourceAddr {
163            instance_id,
164            var_id,
165        })?;
166        Some((address, info))
167    }
168
169    pub fn root_named_variable(&self, name: &str) -> Option<(StateAddr, &VariableInfo)> {
170        let (_, module_id) = self.root_instance_and_module()?;
171        let var_id = self
172            .module_var_path_index
173            .get(&module_id)?
174            .get(&vec![name.to_string()])
175            .copied()
176            .flatten()?;
177        self.root_variable(var_id)
178    }
179
180    pub fn get_path(&self, address: &SourceAddr) -> String {
181        let instance_path = self
182            .instance_ids
183            .iter()
184            .find(|(_, id)| **id == address.instance_id)
185            .map(|(path, _)| path);
186        let module_id = self.instance_module.get(&address.instance_id).unwrap();
187        let module_vars = self.module_variables.get(module_id).unwrap();
188        let variable_path = module_vars
189            .values()
190            .find(|info| info.id == address.var_id)
191            .map(|info| &info.path);
192
193        let mut result = Vec::new();
194        if let Some(instance_path) = instance_path {
195            result.extend(self.instance_path_segments(instance_path));
196        }
197        if let Some(variable_path) = variable_path {
198            result.extend(variable_path.iter().cloned());
199        }
200        result.join(".")
201    }
202
203    pub fn get_state_path(&self, address: &StateAddr) -> String {
204        self.state_to_source
205            .get(address)
206            .map(|source| self.get_path(source))
207            .unwrap_or_else(|| address.to_string())
208    }
209
210    pub fn source_address(&self, address: &StateAddr) -> Option<SourceAddr> {
211        self.state_to_source.get(address).copied()
212    }
213
214    pub fn state_address(&self, address: &SourceAddr) -> Option<StateAddr> {
215        self.source_to_state.get(address).copied()
216    }
217}