rudy_db/
database.rs

1//! Salsa database for debugging information
2//!
3//! This module provides a salsa database for debugging information.
4//!
5//! The core idea is to make debugging information (today: just DWARF)
6//! available via salsa queries.
7//!
8//! The main benefit this provides is to make queries lazy, memoized, and
9//! incremental.
10//!
11//! ## Approach
12//!
13//! The structure of debugging information in DWARF means that it's realtively
14//! cheap to look things up once you know where they are, but finding it
15//! requires walking/parsing multiple files/sections. Furthermore, there is
16//! information that cannot be eagerly loaded, such as the location of variables
17//! in memory since it depends on the current state of the program.
18//!
19//! Given all of this, we take a multi-pass approach:
20//!
21//! 1. Up-front, walk all the files to construct indexes into the debugging information
22//!    that makes it quick to find the relevant files/sections. e.g.
23//!     - Symbol -> compilation unit + offset
24//!     - Source file -> compilation unit
25//!     - Address -> relevant compilation units/sections
26//!
27//! This indexing happens on startup/initial loading of the files and
28//! only changes if the binary is recompiled (although we should be able
29//! to memoize anything looked up from the individual files).
30//!
31//! 2. Lazily parse specific sections and memoize the results. This is only
32//!    called whenever we need the information (e.g. when breaking on a line inside a function).
33//!    But the results should be able to be memoized and ~never recomputed, even when stepping
34//!    through a debug session
35//! 3. Session-specific recomputed values. There are some things that we always need to recompute
36//!    depending on the current session. E.g. when getting locations for variables when inside a
37//!    function, or parsing stack frames. These will typically use a lot of cached/memoized
38//!    intermediate results, but are unlikely to be themselves cached.
39//!
40//!
41//! NOTE: today we don't actually have _any_ inputs. There is no incrementality since we're
42//! assuming the debug information is static. However, in the future we may want incrementality
43//! in via making the Binary file and all object files inputs -- this way if we recompile the
44//! binary we can recompute which parts of the binary are the same and which are unchanged.
45
46use std::path::PathBuf;
47
48use anyhow::Result;
49use rudy_dwarf::{Binary, DwarfDb, file::File};
50
51#[salsa::db]
52pub trait Db: salsa::Database + DwarfDb {}
53
54#[salsa::db]
55#[derive(Clone)]
56pub struct DebugDatabaseImpl {
57    storage: salsa::Storage<Self>,
58    source_map: Vec<(PathBuf, PathBuf)>,
59}
60
61pub struct DebugDbRef {
62    handle: salsa::StorageHandle<DebugDatabaseImpl>,
63    source_map: Vec<(PathBuf, PathBuf)>,
64}
65
66impl DebugDbRef {
67    pub fn get_db(self) -> DebugDatabaseImpl {
68        DebugDatabaseImpl {
69            storage: self.handle.into_storage(),
70            source_map: self.source_map,
71        }
72    }
73}
74
75impl Default for DebugDatabaseImpl {
76    fn default() -> Self {
77        Self::new()
78    }
79}
80
81impl DebugDatabaseImpl {
82    /// Creates a new debug database instance.
83    ///
84    /// The database manages the loading and caching of debug information from binary files.
85    ///
86    /// # Examples
87    ///
88    /// ```no_run
89    /// use rudy_db::DebugDb;
90    ///
91    /// let db = DebugDb::new();
92    /// ```
93    pub fn new() -> Self {
94        Self {
95            storage: salsa::Storage::default(),
96            source_map: Default::default(),
97        }
98    }
99
100    /// Creates a new debug database instance with salsa event logging.
101    ///
102    /// The event callback will be called for all salsa events, allowing you to monitor
103    /// query execution, cache hits/misses, and dependency tracking.
104    ///
105    /// # Examples
106    ///
107    /// ```no_run
108    /// use rudy_db::DebugDb;
109    ///
110    /// let db = DebugDb::new_with_events(Some(Box::new(|event| {
111    ///     println!("Salsa event: {:?}", event);
112    /// })));
113    /// ```
114    pub fn new_with_events(
115        event_callback: Option<Box<dyn Fn(salsa::Event) + Send + Sync + 'static>>,
116    ) -> Self {
117        Self {
118            storage: salsa::Storage::new(event_callback),
119            source_map: Default::default(),
120        }
121    }
122
123    pub fn with_source_map(mut self, source_map: Vec<(PathBuf, PathBuf)>) -> Self {
124        self.source_map = source_map;
125        self
126    }
127
128    pub(crate) fn load_binary(&self, binary_file: PathBuf) -> Result<Binary> {
129        let file = File::build(self, binary_file, None)?;
130        Ok(Binary::new(self, file))
131    }
132
133    pub fn get_sync_ref(&self) -> DebugDbRef {
134        DebugDbRef {
135            handle: self.storage.clone().into_zalsa_handle(),
136            source_map: self.source_map.clone(),
137        }
138    }
139}
140
141#[salsa::db]
142impl salsa::Database for DebugDatabaseImpl {}
143
144#[salsa::db]
145impl DwarfDb for DebugDatabaseImpl {
146    fn get_source_map(&self) -> &[(PathBuf, PathBuf)] {
147        &self.source_map
148    }
149}
150
151#[salsa::db]
152impl Db for DebugDatabaseImpl {}