calyx_frontend/
source_info.rs

1use itertools::Itertools;
2use std::{
3    collections::{HashMap, HashSet},
4    fmt::Display,
5    fs::File,
6    io::Read,
7    num::{NonZero, TryFromIntError},
8    path::PathBuf,
9};
10use thiserror::Error;
11
12type Word = u32;
13
14/// An identifier representing a given file path
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
16pub struct FileId(Word);
17
18impl Display for FileId {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        self.0.fmt(f)
21    }
22}
23
24impl FileId {
25    pub fn new(id: Word) -> Self {
26        Self(id)
27    }
28}
29
30impl From<Word> for FileId {
31    fn from(value: Word) -> Self {
32        Self(value)
33    }
34}
35
36/// An identifier representing a location in the Calyx source code
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
38pub struct PositionId(Word);
39
40impl PositionId {
41    pub fn new(id: Word) -> Self {
42        Self(id)
43    }
44
45    pub fn value(&self) -> Word {
46        self.0
47    }
48}
49
50impl From<Word> for PositionId {
51    fn from(value: Word) -> Self {
52        Self(value)
53    }
54}
55
56impl TryFrom<u64> for PositionId {
57    type Error = TryFromIntError;
58
59    fn try_from(value: u64) -> Result<Self, Self::Error> {
60        let v: u32 = value.try_into()?;
61        Ok(Self(v))
62    }
63}
64
65impl Display for PositionId {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        self.0.fmt(f)
68    }
69}
70
71/// A newtype wrapping a line number
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub struct LineNum(NonZero<Word>);
74
75impl LineNum {
76    pub fn new(line: Word) -> Self {
77        Self(NonZero::new(line).expect("Line number must be non-zero"))
78    }
79    pub fn as_usize(&self) -> usize {
80        self.0.get() as usize
81    }
82    pub fn into_inner(self) -> NonZero<Word> {
83        self.0
84    }
85}
86
87impl Display for LineNum {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        self.0.fmt(f)
90    }
91}
92
93#[derive(Error)]
94#[error("Line number cannot be zero")]
95pub struct LineNumCreationError;
96
97impl std::fmt::Debug for LineNumCreationError {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        std::fmt::Display::fmt(self, f)
100    }
101}
102
103impl TryFrom<Word> for LineNum {
104    type Error = LineNumCreationError;
105
106    fn try_from(value: Word) -> Result<Self, Self::Error> {
107        if value != 0 {
108            Ok(Self(NonZero::new(value).unwrap()))
109        } else {
110            Err(LineNumCreationError)
111        }
112    }
113}
114
115/// An ID in the source map labelling some memory location
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
117pub struct MemoryLocationId(Word);
118
119impl TryFrom<u64> for MemoryLocationId {
120    type Error = TryFromIntError;
121
122    fn try_from(value: u64) -> Result<Self, Self::Error> {
123        let v: u32 = value.try_into()?;
124        Ok(Self(v))
125    }
126}
127
128impl From<Word> for MemoryLocationId {
129    fn from(value: Word) -> Self {
130        Self(value)
131    }
132}
133
134impl Display for MemoryLocationId {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> core::fmt::Result {
136        <Word as Display>::fmt(&self.0, f)
137    }
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct MemoryLocation {
142    pub cell: String,
143    pub address: Vec<usize>,
144}
145
146/// An ID in the source map labelling a set of mappings from variable names to memory locations
147#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
148pub struct VariableAssignmentId(Word);
149
150impl Display for VariableAssignmentId {
151    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> core::fmt::Result {
152        <Word as Display>::fmt(&self.0, f)
153    }
154}
155impl TryFrom<u64> for VariableAssignmentId {
156    type Error = TryFromIntError;
157
158    fn try_from(value: u64) -> Result<Self, Self::Error> {
159        let v: u32 = value.try_into()?;
160        Ok(Self(v))
161    }
162}
163#[derive(Debug, Clone, PartialEq, Eq, Default)]
164pub struct SourceInfoTable {
165    /// map file ids to the file path, note that this does not contain file content
166    file_map: HashMap<FileId, PathBuf>,
167    /// maps position ids to their source locations.
168    position_map: HashMap<PositionId, SourceLocation>,
169    /// assigns ids to locations in memories and registers
170    mem_location_map: HashMap<MemoryLocationId, MemoryLocation>,
171    /// assigns ids to collections of variable -> location mappings
172    variable_assignment_map: VariableMap,
173    /// collects the mapping from positions representing a point in the control
174    /// program to the set of variable assignments for that position
175    position_state_map: HashMap<PositionId, VariableAssignmentId>,
176    /// stores information about the source information used by the program
177    type_table: HashMap<TypeId, SourceType>,
178}
179
180impl SourceInfoTable {
181    const HEADER: &str = "sourceinfo";
182
183    /// Looks up the path of the file with the given id.
184    ///
185    /// # Panics
186    /// Panics if the file id does not exist in the file map
187    pub fn lookup_file_path(&self, file: FileId) -> &PathBuf {
188        &self.file_map[&file]
189    }
190
191    /// Looks up the source location of the position with the given id.
192    ///
193    /// # Panics
194    /// Panics if the position id does not exist in the position map
195    pub fn lookup_position(&self, pos: PositionId) -> &SourceLocation {
196        &self.position_map[&pos]
197    }
198
199    /// Looks up the source location of the position with the given id. If no
200    /// such position exists, returns `None`
201    pub fn get_position(&self, pos: PositionId) -> Option<&SourceLocation> {
202        self.position_map.get(&pos)
203    }
204
205    /// Iterate over the stored file map, returning a tuple of references to the
206    /// file id and the path
207    pub fn iter_file_map(&self) -> impl Iterator<Item = (&FileId, &PathBuf)> {
208        self.file_map.iter()
209    }
210
211    /// Iterate over the paths of all files in the file map
212    pub fn iter_file_paths(&self) -> impl Iterator<Item = &PathBuf> {
213        self.file_map.values()
214    }
215
216    /// Iterate over all file ids in the file map
217    pub fn iter_file_ids(&self) -> impl Iterator<Item = FileId> + '_ {
218        self.file_map.keys().copied()
219    }
220
221    /// Iterate over the stored position map, returning a tuple of references to
222    /// the position id and the source location
223    pub fn iter_position_map(
224        &self,
225    ) -> impl Iterator<Item = (&PositionId, &SourceLocation)> {
226        self.position_map.iter()
227    }
228
229    /// Iterate over all position ids in the position map
230    pub fn iter_positions(&self) -> impl Iterator<Item = PositionId> + '_ {
231        self.position_map.keys().copied()
232    }
233
234    /// Iterate over the source locations in the position map
235    pub fn iter_source_locations(
236        &self,
237    ) -> impl Iterator<Item = &SourceLocation> {
238        self.position_map.values()
239    }
240
241    /// Adds a file to the file map with the given id
242    pub fn add_file(&mut self, file: FileId, path: PathBuf) {
243        self.file_map.insert(file, path);
244    }
245
246    /// Adds a file to the file map and generates a new file id
247    /// for it. If you want to add a file with a specific id, use
248    /// [`SourceInfoTable::add_file`]
249    pub fn push_file(&mut self, path: PathBuf) -> FileId {
250        // find the largest file id in the map
251        let max = self.iter_file_ids().max().unwrap_or(0.into());
252        let new = FileId(max.0 + 1);
253
254        self.add_file(new, path);
255        new
256    }
257    pub fn add_position(
258        &mut self,
259        pos: PositionId,
260        file: FileId,
261        line: LineNum,
262        endline: Option<LineNum>,
263    ) {
264        self.position_map
265            .insert(pos, SourceLocation::new(file, line, endline));
266    }
267
268    /// Adds a position to the position map and generates a new position id
269    /// for it. If you want to add a position with a specific id, use
270    /// [`SourceInfoTable::add_position`]
271    pub fn push_position(
272        &mut self,
273        file: FileId,
274        line: LineNum,
275        endline: Option<LineNum>,
276    ) -> PositionId {
277        // find the largest position id in the map
278        let max = self.iter_positions().max().unwrap_or(0.into());
279        let new = PositionId(max.0 + 1);
280
281        self.add_position(new, file, line, endline);
282        new
283    }
284
285    pub fn add_location(&mut self, id: MemoryLocationId, info: MemoryLocation) {
286        self.mem_location_map.insert(id, info);
287    }
288
289    /// Attempts to look up the variable mapping associated with a given
290    /// position, if such a mapping exists
291    pub fn get_variable_mapping(
292        &self,
293        pos: PositionId,
294    ) -> Option<&HashMap<String, VariableLayout>> {
295        self.position_state_map
296            .get(&pos)
297            .and_then(|x| self.variable_assignment_map.get(x))
298    }
299
300    pub fn get_memory_location(
301        &self,
302        loc: &MemoryLocationId,
303    ) -> &MemoryLocation {
304        &self.mem_location_map[loc]
305    }
306
307    /// Creates a new empty source info table
308    pub fn new_empty() -> Self {
309        Self {
310            file_map: HashMap::new(),
311            position_map: HashMap::new(),
312            mem_location_map: HashMap::new(),
313            variable_assignment_map: VariableMap::default(),
314            position_state_map: HashMap::new(),
315            type_table: HashMap::new(),
316        }
317    }
318
319    /// A wrapper function to construct a source a source map containing only
320    /// files and positions. If an empty map is needed use [SourceInfoTable::new_empty]
321    pub fn new_minimal(
322        files: impl IntoIterator<Item = (FileId, PathBuf)>,
323        positions: impl IntoIterator<
324            Item = (PositionId, FileId, LineNum, Option<LineNum>),
325        >,
326    ) -> SourceInfoResult<Self> {
327        // the compiler needs some concrete types here even though the input is
328        // all empty
329        let loc: Vec<(MemoryLocationId, MemoryLocation)> = vec![];
330        let states: Vec<(PositionId, VariableAssignmentId)> = vec![];
331        #[allow(clippy::type_complexity)]
332        let variable_assigns: Vec<(
333            VariableAssignmentId,
334            Vec<(VariableName<String>, VariableLayout)>,
335        )> = vec![];
336        let types: Vec<(TypeId, SourceType)> = vec![];
337
338        Self::new(files, positions, loc, variable_assigns, states, types)
339    }
340
341    // this is awful
342    pub fn new(
343        files: impl IntoIterator<Item = (FileId, PathBuf)>,
344        positions: impl IntoIterator<
345            Item = (PositionId, FileId, LineNum, Option<LineNum>),
346        >,
347        locations: impl IntoIterator<Item = (MemoryLocationId, MemoryLocation)>,
348        variable_assigns: impl IntoIterator<
349            Item = (
350                VariableAssignmentId,
351                impl IntoIterator<Item = (VariableName<String>, VariableLayout)>,
352            ),
353        >,
354        states: impl IntoIterator<Item = (PositionId, VariableAssignmentId)>,
355        types: impl IntoIterator<Item = (TypeId, SourceType)>,
356    ) -> SourceInfoResult<Self> {
357        let files = files.into_iter();
358        let positions = positions.into_iter();
359        let locations = locations.into_iter();
360        let states = states.into_iter();
361        let types = types.into_iter();
362
363        let mut file_map = HashMap::with_capacity(
364            files.size_hint().1.unwrap_or(files.size_hint().0),
365        );
366        let mut position_map = HashMap::with_capacity(
367            positions.size_hint().1.unwrap_or(positions.size_hint().0),
368        );
369
370        let mut memory_location_map: HashMap<MemoryLocationId, MemoryLocation> =
371            HashMap::new();
372
373        let mut state_map = HashMap::new();
374        let mut type_map = HashMap::new();
375
376        for (file, path) in files {
377            if let Some(first_path) = file_map.insert(file, path) {
378                let inserted_path = &file_map[&file];
379                if &first_path != inserted_path {
380                    return Err(SourceInfoTableError::InvalidTable(format!(
381                        "File id {file} is defined multiple times:\n   1. {}\n   2. {}\n",
382                        first_path.display(),
383                        inserted_path.display()
384                    )));
385                }
386            }
387        }
388
389        for (pos, file, line, end_line) in positions {
390            let source = SourceLocation::new(file, line, end_line);
391            if let Some(first_pos) = position_map.insert(pos, source) {
392                let inserted_position = &position_map[&pos];
393                if inserted_position != &first_pos {
394                    return Err(SourceInfoTableError::InvalidTable(format!(
395                        "Duplicate positions found in the metadata table. Position {pos} is defined multiple times:\n   1. file {}, line {}\n   2. file {}, line {}\n",
396                        first_pos.file,
397                        first_pos.line,
398                        inserted_position.file,
399                        inserted_position.line
400                    )));
401                }
402            }
403        }
404
405        for (id, loc) in locations {
406            if memory_location_map.insert(id, loc).is_some() {
407                return Err(SourceInfoTableError::InvalidTable(format!(
408                    "Multiple definitions for memory location {id}"
409                )));
410            }
411        }
412
413        let mut types_referenced: HashSet<TypeId> = HashSet::new();
414
415        let variable_map = VariableMap::build(
416            variable_assigns,
417            &memory_location_map,
418            &mut types_referenced,
419        )?;
420
421        for (pos_id, var_id) in states {
422            if !variable_map.contains_key(&var_id) {
423                return Err(SourceInfoTableError::InvalidTable(format!(
424                    "Variable mapping {var_id} is referenced but never defined"
425                )));
426            }
427            if state_map.insert(pos_id, var_id).is_some() {
428                return Err(SourceInfoTableError::InvalidTable(format!(
429                    "Multiple variable maps have been assigned to position {pos_id}"
430                )));
431            }
432        }
433
434        for (id, source_type) in types {
435            types_referenced.extend(source_type.types_referenced());
436
437            if type_map.insert(id, source_type).is_some() {
438                return Err(SourceInfoTableError::InvalidTable(format!(
439                    "multiple definitions for type id {id}"
440                )));
441            }
442        }
443
444        for ty in types_referenced {
445            if !type_map.contains_key(&ty) {
446                return Err(SourceInfoTableError::InvalidTable(format!(
447                    "type id {ty} is referenced but never defined"
448                )));
449            }
450        }
451
452        // need to validate the number of arguments to the split layout function
453        for (name, def) in variable_map.iter().flat_map(|(_, v)| v) {
454            if let VariableLayout {
455                type_info,
456                layout_fn: LayoutFunction::Split,
457                layout_args,
458            } = def
459            {
460                let expected_count = type_info.entry_count(&type_map);
461                if expected_count != layout_args.len() {
462                    return Err(SourceInfoTableError::InvalidTable(format!(
463                        "Variable '{name}' of type '{type_info}' was given {} arguments to the split layout function when {expected_count} were required.",
464                        layout_args.len()
465                    )));
466                }
467            }
468        }
469
470        Ok(SourceInfoTable {
471            file_map,
472            position_map,
473            mem_location_map: memory_location_map,
474            variable_assignment_map: variable_map,
475            position_state_map: state_map,
476            type_table: type_map,
477        })
478    }
479
480    pub fn serialize<W: std::io::Write>(
481        &self,
482        mut f: W,
483    ) -> Result<(), std::io::Error> {
484        Self::write_header(&mut f)?;
485
486        // mandatory entries
487        self.write_file_table(&mut f)?;
488        self.write_pos_table(&mut f)?;
489
490        // optional entries
491        if !(self.mem_location_map.is_empty()
492            && self.variable_assignment_map.is_empty()
493            && self.position_state_map.is_empty()
494            && self.type_table.is_empty())
495        {
496            self.write_memory_table(&mut f)?;
497            self.write_var_assigns(&mut f)?;
498            self.write_pos_state_table(&mut f)?;
499            self.write_type_table(&mut f)?;
500        }
501
502        Self::write_footer(&mut f)
503    }
504
505    fn write_pos_state_table<W: std::io::Write>(
506        &self,
507        f: &mut W,
508    ) -> Result<(), std::io::Error> {
509        writeln!(f, "POSITION_STATE_MAP")?;
510
511        for (pos, var) in
512            self.position_state_map.iter().sorted_by_key(|(k, _)| **k)
513        {
514            writeln!(f, "  {pos}: {var}")?;
515        }
516        Ok(())
517    }
518
519    fn write_var_assigns<W: std::io::Write>(
520        &self,
521        f: &mut W,
522    ) -> Result<(), std::io::Error> {
523        writeln!(f, "VARIABLE_ASSIGNMENTS")?;
524
525        for (id, map) in self
526            .variable_assignment_map
527            .iter_serialize()
528            .sorted_by_key(|(k, _)| **k)
529        {
530            writeln!(f, "  {id}: {{")?;
531            for (var, variable_layout) in map.sorted_by_key(|(v, _)| *v) {
532                write!(f, "    {var}:")?;
533                write!(
534                    f,
535                    " ty {}, {}",
536                    variable_layout.type_info, variable_layout.layout_fn
537                )?;
538                for loc in variable_layout.layout_args.iter() {
539                    write!(f, " {loc}")?;
540                }
541                writeln!(f)?;
542            }
543            writeln!(f, "  }}")?;
544        }
545        Ok(())
546    }
547
548    fn write_pos_table<W: std::io::Write>(
549        &self,
550        f: &mut W,
551    ) -> Result<(), std::io::Error> {
552        writeln!(f, "POSITIONS")?;
553        for (position, source_loc) in
554            self.position_map.iter().sorted_by_key(|(k, _)| **k)
555        {
556            write!(f, "  {position}: ")?;
557            source_loc.serialize(f)?;
558            writeln!(f)?;
559        }
560        Ok(())
561    }
562
563    fn write_memory_table<W: std::io::Write>(
564        &self,
565        f: &mut W,
566    ) -> Result<(), std::io::Error> {
567        writeln!(f, "MEMORY_LOCATIONS")?;
568
569        for (loc, MemoryLocation { cell, address }) in
570            self.mem_location_map.iter().sorted_by_key(|(k, _)| **k)
571        {
572            write!(f, "  {loc}: {cell}")?;
573            if !address.is_empty() {
574                write!(f, "[{}]", address.iter().join(","))?;
575            }
576            writeln!(f)?;
577        }
578        Ok(())
579    }
580
581    fn write_file_table<W: std::io::Write>(
582        &self,
583        f: &mut W,
584    ) -> Result<(), std::io::Error> {
585        writeln!(f, "FILES")?;
586        for (file, path) in self.file_map.iter().sorted_by_key(|(k, _)| **k) {
587            writeln!(f, "  {file}: {}", path.display())?;
588        }
589        Ok(())
590    }
591
592    fn write_type_table<W: std::io::Write>(
593        &self,
594        f: &mut W,
595    ) -> Result<(), std::io::Error> {
596        writeln!(f, "TYPES")?;
597        for (id, source_type) in
598            self.type_table.iter().sorted_by_key(|(k, _)| **k)
599        {
600            write!(f, "    {id}: {{ ",)?;
601            match source_type {
602                SourceType::Array { ty, length } => {
603                    write!(f, "{ty}; {length}")?;
604                }
605                SourceType::Struct { fields } => {
606                    if let Some((name, ty)) = fields.first() {
607                        write!(f, "{name}: {ty}")?;
608                    }
609
610                    for (name, ty) in fields.iter().skip(1) {
611                        write!(f, ", {name}: {ty}")?;
612                    }
613                }
614            }
615
616            writeln!(f, " }}")?;
617        }
618        Ok(())
619    }
620
621    /// Attempt to lookup the line that a given position points to. Returns an error in
622    /// cases when the position does not exist, the file is unavailable, or the file
623    /// does not contain the indicated line.
624    pub fn get_position_string(
625        &self,
626        pos: PositionId,
627    ) -> Result<String, SourceInfoTableError> {
628        let Some(src_loc) = self.get_position(pos) else {
629            return Err(SourceInfoTableError::LookupFailure(format!(
630                "position {pos} does not exist"
631            )));
632        };
633        // this will panic if the file doesn't exist but that would imply the table has
634        // incorrect information in it
635        let file_path = self.lookup_file_path(src_loc.file);
636
637        let Ok(mut file) = File::open(file_path) else {
638            return Err(SourceInfoTableError::LookupFailure(format!(
639                "unable to open file '{}'",
640                file_path.display()
641            )));
642        };
643
644        let mut file_contents = String::new();
645
646        match file.read_to_string(&mut file_contents) {
647            Ok(_) => {}
648            Err(e) => {
649                return Err(SourceInfoTableError::LookupFailure(format!(
650                    "read of file '{}' failed with error {e}",
651                    file_path.display()
652                )));
653            }
654        }
655
656        let Some(line) = file_contents.lines().nth(src_loc.line.as_usize() - 1)
657        else {
658            return Err(SourceInfoTableError::LookupFailure(format!(
659                "file '{}' does not contain a line {}",
660                file_path.display(),
661                src_loc.line.as_usize()
662            )));
663        };
664
665        Ok(String::from(line))
666    }
667
668    fn write_header<W: std::io::Write>(
669        f: &mut W,
670    ) -> Result<(), std::io::Error> {
671        writeln!(f, "{} #{{", SourceInfoTable::HEADER)
672    }
673
674    fn write_footer<W: std::io::Write>(
675        f: &mut W,
676    ) -> Result<(), std::io::Error> {
677        writeln!(f, "}}#")
678    }
679}
680
681#[derive(Debug, Clone, PartialEq, Eq)]
682pub struct SourceLocation {
683    pub file: FileId,
684    pub line: LineNum,
685    pub end_line: Option<LineNum>,
686}
687
688impl SourceLocation {
689    pub fn new(file: FileId, line: LineNum, end_line: Option<LineNum>) -> Self {
690        Self {
691            line,
692            file,
693            end_line,
694        }
695    }
696
697    /// Write out the source location string
698    pub fn serialize(
699        &self,
700        out: &mut impl std::io::Write,
701    ) -> Result<(), std::io::Error> {
702        if let Some(endline) = self.end_line {
703            write!(out, "{} {}:{}", self.file, self.line, endline)
704        } else {
705            write!(out, "{} {}", self.file, self.line)
706        }
707    }
708}
709
710#[derive(Debug, Clone, Hash, PartialEq, Eq)]
711pub enum PrimitiveType {
712    Uint(u32),
713    Sint(u32),
714    Bool,
715    Bitfield(u32),
716}
717
718impl Display for PrimitiveType {
719    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
720        match self {
721            PrimitiveType::Uint(x) => write!(f, "u{x}"),
722            PrimitiveType::Sint(x) => write!(f, "i{x}"),
723            PrimitiveType::Bool => write!(f, "bool"),
724            PrimitiveType::Bitfield(x) => write!(f, "b{x}"),
725        }
726    }
727}
728
729impl PrimitiveType {
730    pub fn type_size(&self) -> usize {
731        match self {
732            PrimitiveType::Uint(width) => *width as usize,
733            PrimitiveType::Sint(width) => *width as usize,
734            PrimitiveType::Bool => 1_usize,
735            PrimitiveType::Bitfield(width) => *width as usize,
736        }
737    }
738}
739
740#[derive(Debug, Clone, PartialEq, Eq)]
741pub enum FieldType {
742    Primitive(PrimitiveType),
743    Composite(TypeId),
744}
745
746impl Display for FieldType {
747    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
748        match self {
749            FieldType::Primitive(primitive_type) => primitive_type.fmt(f),
750            FieldType::Composite(type_id) => type_id.fmt(f),
751        }
752    }
753}
754
755impl FieldType {
756    pub fn type_size(&self, type_map: &HashMap<TypeId, SourceType>) -> usize {
757        match self {
758            FieldType::Primitive(primitive_type) => primitive_type.type_size(),
759            FieldType::Composite(type_id) => {
760                type_map[type_id].type_size(type_map)
761            }
762        }
763    }
764
765    /// Return the number of primitive types that must be mapped for this type
766    pub fn entry_count(&self, type_map: &HashMap<TypeId, SourceType>) -> usize {
767        match self {
768            FieldType::Primitive(_) => 1,
769            FieldType::Composite(type_id) => {
770                type_map[type_id].entry_count(type_map)
771            }
772        }
773    }
774}
775
776#[derive(Debug, Clone, PartialEq, Eq)]
777pub enum SourceType {
778    Array {
779        ty: FieldType,
780        length: u32,
781    },
782    Struct {
783        fields: Vec<(VariableName<String>, FieldType)>,
784    },
785}
786
787impl SourceType {
788    pub fn type_size(&self, type_map: &HashMap<TypeId, SourceType>) -> usize {
789        match self {
790            SourceType::Array { ty, length } => {
791                ty.type_size(type_map) * (*length as usize)
792            }
793            SourceType::Struct { fields } => fields
794                .iter()
795                .fold(0, |acc, (_, ty)| acc + ty.type_size(type_map)),
796        }
797    }
798
799    pub fn types_referenced(&self) -> Vec<TypeId> {
800        match self {
801            SourceType::Array { ty, .. } => {
802                if let FieldType::Composite(id) = ty {
803                    vec![*id]
804                } else {
805                    vec![]
806                }
807            }
808            SourceType::Struct { fields } => fields
809                .iter()
810                .filter_map(|(_, ty)| {
811                    if let FieldType::Composite(id) = ty {
812                        Some(*id)
813                    } else {
814                        None
815                    }
816                })
817                .collect(),
818        }
819    }
820
821    /// Return the number of primitive types that must be mapped for this type
822    pub fn entry_count(&self, type_map: &HashMap<TypeId, SourceType>) -> usize {
823        match self {
824            SourceType::Array { ty, length } => {
825                let entries_per_field = ty.entry_count(type_map);
826                entries_per_field * (*length as usize)
827            }
828            SourceType::Struct { fields } => fields
829                .iter()
830                .fold(0, |acc, (_, ty)| acc + ty.entry_count(type_map)),
831        }
832    }
833}
834
835/// ID for types from the source language
836#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
837pub struct TypeId(Word);
838
839impl TypeId {
840    pub fn new(v: Word) -> Self {
841        Self(v)
842    }
843}
844
845impl Display for TypeId {
846    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> core::fmt::Result {
847        <Word as Display>::fmt(&self.0, f)
848    }
849}
850
851#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)]
852/// A thin wrapper over a String for names used by the source info table
853pub struct VariableName<S: AsRef<str>> {
854    name: S,
855    /// a bool tracking whether or not this name came from a string literal for
856    /// purposes of serialization
857    name_is_literal: bool,
858}
859
860impl<S: AsRef<str>> Display for VariableName<S> {
861    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
862        if self.name_is_literal {
863            write!(f, "\"{}\"", self.name.as_ref())
864        } else {
865            self.name.as_ref().fmt(f)
866        }
867    }
868}
869
870impl<S: AsRef<str>> VariableName<S> {
871    pub fn new(name: S, name_is_literal: bool) -> Self {
872        Self {
873            name,
874            name_is_literal,
875        }
876    }
877
878    pub fn new_non_literal(name: S) -> Self {
879        Self {
880            name,
881            name_is_literal: false,
882        }
883    }
884
885    pub fn new_literal(name: S) -> Self {
886        Self {
887            name,
888            name_is_literal: true,
889        }
890    }
891
892    pub fn into_inner(self) -> S {
893        self.name
894    }
895
896    pub fn as_str(&self) -> &str {
897        self.name.as_ref()
898    }
899
900    /// flips the name_is_literal bool. should only be used when searching for
901    /// a value in a hashmap
902    pub fn flip_is_literal(&mut self) {
903        self.name_is_literal = !self.name_is_literal
904    }
905
906    pub fn set_is_literal(&mut self) {
907        self.name_is_literal = true
908    }
909
910    pub fn unset_is_literal(&mut self) {
911        self.name_is_literal = false
912    }
913}
914
915#[derive(Debug, Clone, PartialEq, Eq, Default)]
916pub struct VariableMap {
917    data: HashMap<VariableAssignmentId, HashMap<String, VariableLayout>>,
918    /// This is a somewhat silly structure but preserves the literals on
919    /// printing even if the same string is written as a literal in one
920    /// assignment collection and normally in another. However, within a single
921    /// assignment collection, it is not possible to have both a literal and
922    /// standard declaration of the same name.
923    is_literal: HashMap<VariableAssignmentId, HashMap<String, bool>>,
924}
925
926impl VariableMap {
927    pub fn build(
928        stream: impl IntoIterator<
929            Item = (
930                VariableAssignmentId,
931                impl IntoIterator<Item = (VariableName<String>, VariableLayout)>,
932            ),
933        >,
934        memory_location_map: &HashMap<MemoryLocationId, MemoryLocation>,
935        types_referenced: &mut HashSet<TypeId>,
936    ) -> SourceInfoResult<Self> {
937        let mut variable_map = HashMap::new();
938        let mut literal_map = HashMap::new();
939
940        for (assign_label, assigns) in stream {
941            let mut data_mapping = HashMap::new();
942            let mut literal_mapping = HashMap::new();
943            for (name, location) in assigns {
944                let VariableName {
945                    name,
946                    name_is_literal,
947                } = name;
948
949                for loc in location.layout_args.iter() {
950                    if !memory_location_map.contains_key(loc) {
951                        // unknown memory location
952                        return Err(SourceInfoTableError::InvalidTable(
953                            format!(
954                                "Memory location {loc} is referenced but never defined"
955                            ),
956                        ));
957                    }
958                }
959
960                if let Some(ty) = location.referenced_type() {
961                    types_referenced.insert(ty);
962                }
963
964                // this is to avoid copying the string in all cases since we
965                // would only need it when emitting the error. Clippy doesn't
966                // like this for good reasons and while I suspect it may be
967                // possible using the entry api, I think this is clearer so I'm
968                // just suppressing the warning and writing this very long
969                // comment about it instead.
970                #[allow(clippy::map_entry)]
971                if data_mapping.contains_key(&name) {
972                    return Err(SourceInfoTableError::InvalidTable(format!(
973                        "In variable mapping {assign_label} the variable '{name}' has multiple definitions"
974                    )));
975                } else {
976                    literal_mapping.insert(name.clone(), name_is_literal);
977                    data_mapping.insert(name, location);
978                }
979            }
980            if variable_map.insert(assign_label, data_mapping).is_some() {
981                return Err(SourceInfoTableError::InvalidTable(format!(
982                    "Duplicate definitions for variable mapping associated with position {assign_label}"
983                )));
984            };
985            literal_map.insert(assign_label, literal_mapping);
986        }
987
988        Ok(Self {
989            data: variable_map,
990            is_literal: literal_map,
991        })
992    }
993
994    pub fn is_empty(&self) -> bool {
995        self.data.is_empty()
996    }
997
998    pub fn iter_serialize(
999        &self,
1000    ) -> impl Iterator<
1001        Item = (
1002            &VariableAssignmentId,
1003            impl Iterator<Item = (VariableName<&str>, &VariableLayout)> + '_,
1004        ),
1005    > {
1006        self.data.iter().map(|(id, data)| {
1007            (
1008                id,
1009                data.iter().map(|(name, dec)| {
1010                    (
1011                        VariableName::new(
1012                            name.as_str(),
1013                            self.is_literal[id][name],
1014                        ),
1015                        dec,
1016                    )
1017                }),
1018            )
1019        })
1020    }
1021
1022    pub fn iter(
1023        &self,
1024    ) -> impl Iterator<
1025        Item = (
1026            &VariableAssignmentId,
1027            impl Iterator<Item = (&String, &VariableLayout)>,
1028        ),
1029    > {
1030        self.data.iter().map(|(k, v)| (k, v.iter()))
1031    }
1032
1033    pub fn contains_key(&self, key: &VariableAssignmentId) -> bool {
1034        self.data.contains_key(key)
1035    }
1036
1037    pub fn get(
1038        &self,
1039        k: &VariableAssignmentId,
1040    ) -> Option<&HashMap<String, VariableLayout>> {
1041        self.data.get(k)
1042    }
1043}
1044
1045#[derive(Debug, Clone, PartialEq, Eq)]
1046pub enum LayoutFunction {
1047    /// Standard layout function which maps all fields into a single memory
1048    /// slot / register. This function must be given exactly a single argument
1049    Packed,
1050    /// Standard layout function which maps each entry in the variable structure
1051    /// to a distinct register / memory location. Must take N arguments where N
1052    /// is the number of entries for the type.
1053    Split,
1054}
1055
1056impl Display for LayoutFunction {
1057    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1058        match self {
1059            LayoutFunction::Packed => write!(f, "packed"),
1060            LayoutFunction::Split => write!(f, "split"),
1061        }
1062    }
1063}
1064
1065#[derive(Debug, Clone, PartialEq, Eq)]
1066pub struct VariableLayout {
1067    pub type_info: FieldType,
1068    pub layout_fn: LayoutFunction,
1069    pub layout_args: Box<[MemoryLocationId]>,
1070}
1071
1072impl VariableLayout {
1073    pub fn new(
1074        type_info: FieldType,
1075        layout_fn: LayoutFunction,
1076        layout_args: impl IntoIterator<Item = MemoryLocationId>,
1077    ) -> Self {
1078        Self {
1079            type_info,
1080            layout_fn,
1081            layout_args: layout_args.into_iter().collect(),
1082        }
1083    }
1084
1085    pub fn referenced_type(&self) -> Option<TypeId> {
1086        if let FieldType::Composite(c) = self.type_info {
1087            Some(c)
1088        } else {
1089            None
1090        }
1091    }
1092}
1093
1094#[derive(Error)]
1095pub enum SourceInfoTableError {
1096    /// A fatal error representing a malformed table
1097    #[error("source info is malformed. {0}")]
1098    InvalidTable(String),
1099    /// A non-fatal error representing a failed lookup
1100    #[error("source lookup failed. {0}")]
1101    LookupFailure(String),
1102}
1103
1104impl std::fmt::Debug for SourceInfoTableError {
1105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1106        std::fmt::Display::fmt(&self, f)
1107    }
1108}
1109
1110pub type SourceInfoResult<T> = Result<T, SourceInfoTableError>;
1111
1112#[cfg(test)]
1113mod tests {
1114    use super::SourceInfoTable;
1115    use crate::{parser::CalyxParser, source_info::LineNum};
1116    use std::path::PathBuf;
1117
1118    #[test]
1119    fn test_parse_metadata() {
1120        let input_str = r#"sourceinfo #{
1121    FILES
1122        0: test.calyx
1123        1: test2.calyx
1124        2: test3.calyx
1125    POSITIONS
1126        0: 0 5
1127        1: 0 1:12
1128        2: 0 2
1129    MEMORY_LOCATIONS
1130        0: main.reg1
1131        1: main.reg2
1132        2: main.mem1 [1,4]
1133        3: main.mem1 [0,1]
1134        4: main.mem1 [0,2]
1135    VARIABLE_ASSIGNMENTS
1136        0: {
1137            x: ty b12, packed 0
1138            y: ty 1, packed 1
1139            z: ty 0, split 2 3
1140            "q": ty 2, packed 4
1141        }
1142        1: {
1143            q: ty 0, packed 1
1144        }
1145    POSITION_STATE_MAP
1146        0: 0
1147        2: 1
1148    TYPES
1149        0: { 0: u4, 1: i6 }
1150        1: { bool; 15 }
1151        2: { coordinate: 0, "bvec": 1 }
1152
1153}#"#;
1154
1155        let metadata = CalyxParser::parse_source_info_table(input_str)
1156            .unwrap()
1157            .unwrap();
1158        let file = metadata.lookup_file_path(1.into());
1159        assert_eq!(file, &PathBuf::from("test2.calyx"));
1160
1161        let pos = metadata.lookup_position(1.into());
1162        assert_eq!(pos.file, 0.into());
1163        assert_eq!(pos.line, LineNum::new(1));
1164
1165        let mut serialized_str = vec![];
1166        metadata.serialize(&mut serialized_str).unwrap();
1167        let serialized_str = String::from_utf8(serialized_str).unwrap();
1168        eprintln!("{}", &serialized_str);
1169        let parsed_metadata =
1170            CalyxParser::parse_source_info_table(&serialized_str)
1171                .unwrap()
1172                .unwrap();
1173
1174        assert_eq!(metadata, parsed_metadata)
1175    }
1176
1177    #[test]
1178    fn test_undefined_mem_loc() {
1179        let input_str = r#"sourceinfo #{
1180    FILES
1181        0: test.calyx
1182    POSITIONS
1183        0: 0 5
1184        1: 0 1
1185        2: 0 2
1186    MEMORY_LOCATIONS
1187        0: main.reg1
1188        2: main.mem1 [1,4]
1189    VARIABLE_ASSIGNMENTS
1190        0: {
1191            x: ty b10, packed 0
1192            y: ty b10, packed 1
1193            z: ty b10, packed 2
1194        }
1195        1: {
1196            q: ty b14, packed 0
1197        }
1198    POSITION_STATE_MAP
1199        0: 0
1200        2: 1
1201    TYPES
1202}#"#;
1203        let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
1204        assert!(metadata.is_err());
1205        eprintln!("{}", metadata.unwrap_err());
1206    }
1207
1208    #[test]
1209    fn test_undefined_variable() {
1210        let input_str = r#"sourceinfo #{
1211    FILES
1212        0: test.calyx
1213    POSITIONS
1214        0: 0 5
1215        1: 0 1
1216        2: 0 2
1217    MEMORY_LOCATIONS
1218        0: main.reg1
1219        1: main.reg2
1220        2: main.mem1 [1,4]
1221    VARIABLE_ASSIGNMENTS
1222        0: {
1223            x: ty b10, packed 0
1224            y: ty b10, packed 1
1225            z: ty b10, packed 2
1226        }
1227        1: {
1228            q: ty b14, packed 0
1229        }
1230    POSITION_STATE_MAP
1231        0: 0
1232        2: 2
1233    TYPES
1234}#"#;
1235        let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
1236        assert!(metadata.is_err());
1237        eprintln!("{}", metadata.unwrap_err());
1238    }
1239
1240    #[test]
1241    fn test_duplicate_variable_maps() {
1242        let input_str = r#"sourceinfo #{
1243    FILES
1244        0: test.calyx
1245    POSITIONS
1246        0: 0 5
1247        1: 0 1
1248        2: 0 2
1249    MEMORY_LOCATIONS
1250        0: main.reg1
1251        1: main.reg2
1252        2: main.mem1 [1,4]
1253    VARIABLE_ASSIGNMENTS
1254        0: {
1255            x: ty b10, packed 0
1256            y: ty b10, packed 1
1257            z: ty b10, packed 2
1258        }
1259        1: {
1260            q: ty b14, packed 0
1261        }
1262        1: {
1263            a: ty i15, packed 0
1264        }
1265    POSITION_STATE_MAP
1266        0: 0
1267        2: 1
1268    TYPES
1269}#"#;
1270        let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
1271        assert!(metadata.is_err());
1272        eprintln!("{}", metadata.unwrap_err());
1273    }
1274
1275    #[test]
1276    fn test_duplicate_variable_assignment() {
1277        let input_str = r#"sourceinfo #{
1278    FILES
1279        0: test.calyx
1280    POSITIONS
1281        0: 0 5
1282        1: 0 1
1283        2: 0 2
1284    MEMORY_LOCATIONS
1285        0: main.reg1
1286        1: main.reg2
1287        2: main.mem1 [1,4]
1288    VARIABLE_ASSIGNMENTS
1289        0: {
1290            x: ty b10, packed 0
1291            y: ty b10, packed 1
1292            z: ty b10, packed 2
1293        }
1294        1: {
1295            q: ty b14, packed 0
1296            q: ty b10, packed 1
1297
1298        }
1299    POSITION_STATE_MAP
1300        0: 0
1301        2: 1
1302    TYPES
1303}#"#;
1304        let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
1305        assert!(metadata.is_err());
1306        eprintln!("{}", metadata.unwrap_err());
1307    }
1308
1309    #[test]
1310    fn test_duplicate_mem_def() {
1311        let input_str = r#"sourceinfo #{
1312    FILES
1313        0: test.calyx
1314    POSITIONS
1315        0: 0 5
1316        1: 0 1
1317        2: 0 2
1318    MEMORY_LOCATIONS
1319        0: main.reg1
1320        1: main.reg2
1321        1: main.mem1 [1,4]
1322    VARIABLE_ASSIGNMENTS
1323       0: {
1324            x: ty b10, packed 0
1325            y: ty b10, packed 1
1326            z: ty b10, packed 2
1327        }
1328        1: {
1329            q: ty b14, packed 0
1330        }
1331    POSITION_STATE_MAP
1332        0: 0
1333        2: 1
1334    TYPES
1335}#"#;
1336        let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
1337        assert!(metadata.is_err());
1338        eprintln!("{}", metadata.unwrap_err());
1339    }
1340
1341    #[test]
1342    fn test_duplicate_pos_state() {
1343        let input_str = r#"sourceinfo #{
1344    FILES
1345        0: test.calyx
1346    POSITIONS
1347        0: 0 5
1348        1: 0 1
1349        2: 0 2
1350    MEMORY_LOCATIONS
1351        0: main.reg1
1352        1: main.reg2
1353        2: main.mem1 [1,4]
1354    VARIABLE_ASSIGNMENTS
1355          0: {
1356            x: ty b10, packed 0
1357            y: ty b10, packed 1
1358            z: ty b10, packed 2
1359        }
1360        1: {
1361            q: ty b14, packed 0
1362        }
1363    POSITION_STATE_MAP
1364        0: 0
1365        0: 1
1366    TYPES
1367}#"#;
1368        let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
1369        assert!(metadata.is_err());
1370        eprintln!("{}", metadata.unwrap_err());
1371    }
1372
1373    #[test]
1374    fn test_duplicate_file_parse() {
1375        let input_str = r#"sourceinfo #{
1376            FILES
1377                0: test.calyx
1378                0: test2.calyx
1379                2: test3.calyx
1380            POSITIONS
1381                0: 0 5:6
1382                1: 0 1
1383                2: 0 2
1384        }#"#;
1385        let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
1386        assert!(metadata.is_err());
1387        eprintln!("{}", metadata.unwrap_err());
1388    }
1389
1390    #[test]
1391    fn test_duplicate_position_parse() {
1392        let input_str = r#"sourceinfo #{
1393            FILES
1394                0: test.calyx
1395                1: test2.calyx
1396                2: test3.calyx
1397            POSITIONS
1398                0: 0 5
1399                0: 0 1
1400                2: 0 2
1401        }#"#;
1402        let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
1403
1404        assert!(metadata.is_err());
1405        eprintln!("{}", metadata.unwrap_err());
1406    }
1407
1408    #[test]
1409    fn test_unknown_type() {
1410        let input_str = r#"sourceinfo #{
1411    FILES
1412        0: test.calyx
1413    POSITIONS
1414        0: 0 5
1415        1: 0 1
1416        2: 0 2
1417    MEMORY_LOCATIONS
1418        0: main.reg1
1419        1: main.reg2
1420        2: main.mem1 [1,4]
1421    VARIABLE_ASSIGNMENTS
1422        0: {
1423            x: ty 2, packed 0
1424            y: ty b12, packed 1
1425            z: ty b12, packed 2
1426        }
1427        1: {
1428            q: ty b10, packed 0
1429        }
1430    POSITION_STATE_MAP
1431        0: 0
1432        1: 1
1433    TYPES
1434}#"#;
1435        let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
1436        assert!(metadata.is_err());
1437        eprintln!("{}", metadata.unwrap_err());
1438    }
1439
1440    #[test]
1441    fn test_serialize() {
1442        let mut metadata = SourceInfoTable::new_empty();
1443        metadata.add_file(0.into(), "test.calyx".into());
1444        metadata.add_file(1.into(), "test2.calyx".into());
1445        metadata.add_file(2.into(), "test3.calyx".into());
1446
1447        metadata.add_position(0.into(), 0.into(), LineNum::new(1), None);
1448        metadata.add_position(
1449            1.into(),
1450            1.into(),
1451            LineNum::new(2),
1452            Some(LineNum::new(4)),
1453        );
1454        metadata.add_position(150.into(), 2.into(), LineNum::new(148), None);
1455
1456        let mut serialized_str = vec![];
1457        metadata.serialize(&mut serialized_str).unwrap();
1458        let serialized_str = String::from_utf8(serialized_str).unwrap();
1459
1460        let parsed_metadata =
1461            CalyxParser::parse_source_info_table(&serialized_str)
1462                .unwrap()
1463                .unwrap();
1464
1465        assert_eq!(metadata, parsed_metadata)
1466    }
1467}