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 {}