Skip to content

Commit f0ed57a

Browse files
authored
Use compiler source map directly in forc-debug (#6872)
## Description Replace the custom HashMap-based source map implementation in `forc-debug` with the compiler's source map format directly. This simplifies our source mapping logic and maintains better consistency with the compiler's source location tracking, while preserving all existing functionality. Key changes: - Remove custom source map types and use compiler's SourceMap - Simplify breakpoint verification using compiler spans - Add test to verify source map instruction-to-line mappings closes #6733 ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [x] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [x] If my change requires substantial documentation changes, I have [requested support from the DevRel team](https://github.com/FuelLabs/devrel-requests/issues/new/choose) - [x] I have added tests that prove my fix is effective or that my feature works. - [x] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers.
1 parent e511dda commit f0ed57a

File tree

6 files changed

+161
-121
lines changed

6 files changed

+161
-121
lines changed

forc-plugins/forc-debug/src/server/handlers/handle_set_breakpoints.rs

+29-13
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,20 @@ impl DapServer {
4545
.cloned()
4646
.unwrap_or_default();
4747

48-
let source_map = self
49-
.state
50-
.source_map
51-
.get(&source_path_buf)
52-
.cloned()
53-
.unwrap_or_default();
54-
5548
let breakpoints = args
5649
.breakpoints
5750
.clone()
5851
.unwrap_or_default()
5952
.iter()
6053
.map(|source_bp| {
61-
let verified = source_map.contains_key(&source_bp.line);
54+
// Check if there are any instructions mapped to this line in the source
55+
let verified = self.state.source_map.map.iter().any(|(pc, _)| {
56+
if let Some((path, range)) = self.state.source_map.addr_to_span(*pc) {
57+
path == source_path_buf && range.start.line as i64 == source_bp.line
58+
} else {
59+
false
60+
}
61+
});
6262
if let Some(existing_bp) = existing_breakpoints
6363
.iter()
6464
.find(|bp| bp.line.map_or(false, |line| line == source_bp.line))
@@ -91,9 +91,9 @@ impl DapServer {
9191

9292
#[cfg(test)]
9393
mod tests {
94-
use std::{collections::HashMap, iter};
95-
9694
use super::*;
95+
use sway_core::source_map::{LocationRange, PathIndex, SourceMap, SourceMapSpan};
96+
use sway_types::LineCol;
9797

9898
const MOCK_SOURCE_PATH: &str = "some/path";
9999
const MOCK_BP_ID: i64 = 1;
@@ -103,10 +103,26 @@ mod tests {
103103
fn get_test_server(source_map: bool, existing_bp: bool) -> DapServer {
104104
let mut server = DapServer::default();
105105
if source_map {
106-
server.state.source_map.insert(
107-
PathBuf::from(MOCK_SOURCE_PATH),
108-
HashMap::from_iter(iter::once((MOCK_LINE, vec![MOCK_INSTRUCTION]))),
106+
// Create a source map with our test line
107+
let mut map = SourceMap::new();
108+
map.paths.push(PathBuf::from(MOCK_SOURCE_PATH));
109+
map.map.insert(
110+
MOCK_INSTRUCTION as usize,
111+
SourceMapSpan {
112+
path: PathIndex(0),
113+
range: LocationRange {
114+
start: LineCol {
115+
line: MOCK_LINE as usize,
116+
col: 0,
117+
},
118+
end: LineCol {
119+
line: MOCK_LINE as usize,
120+
col: 10,
121+
},
122+
},
123+
},
109124
);
125+
server.state.source_map = map;
110126
}
111127
if existing_bp {
112128
server.state.breakpoints.insert(

forc-plugins/forc-debug/src/server/handlers/handle_stack_trace.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ impl DapServer {
4646
executor.interpreter.registers(),
4747
))
4848
.ok()
49-
.map(|(source_path, line)| (Some(util::path_into_source(source_path)), line)),
49+
.map(|(source_path, line)| (Some(util::path_into_source(&source_path)), line)),
5050
};
5151

5252
// For now, we only return 1 stack frame.

forc-plugins/forc-debug/src/server/mod.rs

+15-49
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,11 @@ use forc_test::{
2424
};
2525
use serde::{Deserialize, Serialize};
2626
use std::{
27-
collections::HashMap,
2827
io::{BufReader, BufWriter, Read, Write},
2928
process,
3029
sync::Arc,
3130
};
3231
use sway_core::BuildTarget;
33-
use sway_types::LineCol;
3432

3533
pub const THREAD_ID: i64 = 0;
3634
pub const REGISTERS_VARIABLE_REF: i64 = 1;
@@ -53,7 +51,7 @@ pub struct DapServer {
5351
/// Used to generate unique breakpoint IDs.
5452
breakpoint_id_gen: IdGenerator,
5553
/// The server state.
56-
state: ServerState,
54+
pub state: ServerState,
5755
}
5856

5957
impl Default for DapServer {
@@ -261,7 +259,7 @@ impl DapServer {
261259
}
262260

263261
/// Builds the tests at the given [PathBuf] and stores the source maps.
264-
pub(crate) fn build_tests(&mut self) -> Result<(BuiltPackage, TestSetup), AdapterError> {
262+
pub fn build_tests(&mut self) -> Result<(BuiltPackage, TestSetup), AdapterError> {
265263
if let Some(pkg) = &self.state.built_package {
266264
if let Some(setup) = &self.state.test_setup {
267265
return Ok((pkg.clone(), setup.clone()));
@@ -327,51 +325,19 @@ impl DapServer {
327325
reason: format!("build packages: {err:?}"),
328326
})?;
329327

330-
// 2. Store the source maps
331-
let mut pkg_to_debug: Option<&BuiltPackage> = None;
332-
for (_, built_pkg) in &built_packages {
333-
if built_pkg.descriptor.manifest_file == pkg_manifest {
334-
pkg_to_debug = Some(built_pkg);
335-
}
336-
let source_map = &built_pkg.source_map;
337-
338-
let paths = &source_map.paths;
339-
source_map.map.iter().for_each(|(instruction, sm_span)| {
340-
if let Some(path_buf) = paths.get(sm_span.path.0) {
341-
let LineCol { line, .. } = sm_span.range.start;
342-
let (line, instruction) = (line as i64, *instruction as Instruction);
343-
344-
self.state
345-
.source_map
346-
.entry(path_buf.clone())
347-
.and_modify(|new_map| {
348-
new_map
349-
.entry(line)
350-
.and_modify(|val| {
351-
// Store the instructions in ascending order
352-
match val.binary_search(&instruction) {
353-
Ok(_) => {} // Ignore duplicates
354-
Err(pos) => val.insert(pos, instruction),
355-
}
356-
})
357-
.or_insert(vec![instruction]);
358-
})
359-
.or_insert(HashMap::from([(line, vec![instruction])]));
360-
} else {
361-
self.error(format!(
362-
"Path missing from source map: {:?}",
363-
sm_span.path.0
364-
));
365-
}
366-
});
367-
}
328+
// 2. Store the source maps and find debug package
329+
let pkg_to_debug = built_packages
330+
.iter()
331+
.find(|(_, pkg)| pkg.descriptor.manifest_file == pkg_manifest)
332+
.map(|(_, pkg)| pkg)
333+
.ok_or(AdapterError::BuildFailed {
334+
reason: format!("find package: {project_name}"),
335+
})?;
368336

369-
// 3. Build the tests
370-
let built_package = pkg_to_debug.ok_or(AdapterError::BuildFailed {
371-
reason: format!("find package: {project_name}"),
372-
})?;
337+
self.state.source_map = pkg_to_debug.source_map.clone();
373338

374-
let built = Built::Package(Arc::from(built_package.clone()));
339+
// 3. Build the tests
340+
let built = Built::Package(Arc::from(pkg_to_debug.clone()));
375341

376342
let built_tests = BuiltTests::from_built(built, &build_plan).map_err(|err| {
377343
AdapterError::BuildFailed {
@@ -390,9 +356,9 @@ impl DapServer {
390356
let test_setup = pkg_tests.setup().map_err(|err| AdapterError::BuildFailed {
391357
reason: format!("test setup: {err:?}"),
392358
})?;
393-
self.state.built_package = Some(built_package.clone());
359+
self.state.built_package = Some(pkg_to_debug.clone());
394360
self.state.test_setup = Some(test_setup.clone());
395-
Ok((built_package.clone(), test_setup))
361+
Ok((pkg_to_debug.clone(), test_setup))
396362
}
397363

398364
/// Sends the 'exited' event to the client and kills the server process.

forc-plugins/forc-debug/src/server/state.rs

+51-54
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use crate::{
22
error::AdapterError,
3-
types::{Breakpoints, Instruction, SourceMap},
3+
types::{Breakpoints, Instruction},
44
};
55
use dap::types::StartDebuggingRequestKind;
66
use forc_pkg::BuiltPackage;
77
use forc_test::{execute::TestExecutor, setup::TestSetup, TestResult};
88
use std::path::PathBuf;
9+
use sway_core::source_map::SourceMap;
910

1011
#[derive(Default, Debug, Clone)]
1112
/// The state of the DAP server.
@@ -58,24 +59,55 @@ impl ServerState {
5859
pub fn vm_pc_to_source_location(
5960
&self,
6061
pc: Instruction,
61-
) -> Result<(&PathBuf, i64), AdapterError> {
62-
// Try to find the source location by looking for the program counter in the source map.
63-
self.source_map
62+
) -> Result<(PathBuf, i64), AdapterError> {
63+
// Convert PC to instruction index (divide by 4 for byte offset)
64+
let instruction_idx = (pc / 4) as usize;
65+
if let Some((path, range)) = self.source_map.addr_to_span(instruction_idx) {
66+
Ok((path, range.start.line as i64))
67+
} else {
68+
Err(AdapterError::MissingSourceMap { pc })
69+
}
70+
}
71+
72+
/// Updates the breakpoints in the VM for all remaining [TestExecutor]s.
73+
pub(crate) fn update_vm_breakpoints(&mut self) {
74+
if !self.breakpoints_need_update {
75+
return;
76+
}
77+
78+
// Convert breakpoints to instruction offsets using the source map
79+
let opcode_indexes = self
80+
.breakpoints
6481
.iter()
65-
.find_map(|(source_path, source_map)| {
66-
for (&line, instructions) in source_map {
67-
// Divide by 4 to get the opcode offset rather than the program counter offset.
68-
let instruction_offset = pc / 4;
69-
if instructions
70-
.iter()
71-
.any(|instruction| instruction_offset == *instruction)
72-
{
73-
return Some((source_path, line));
74-
}
75-
}
76-
None
77-
})
78-
.ok_or(AdapterError::MissingSourceMap { pc })
82+
.flat_map(|(source_path, breakpoints)| {
83+
breakpoints
84+
.iter()
85+
.filter_map(|bp| {
86+
bp.line.and_then(|line| {
87+
// Find any instruction that maps to this line in the source map
88+
self.source_map.map.iter().find_map(|(pc, _)| {
89+
self.source_map
90+
.addr_to_span(*pc)
91+
.filter(|(path, range)| {
92+
path == source_path && range.start.line as i64 == line
93+
})
94+
.map(|_| pc)
95+
})
96+
})
97+
})
98+
.collect::<Vec<_>>()
99+
});
100+
101+
// Set breakpoints in the VM
102+
self.executors.iter_mut().for_each(|executor| {
103+
let bps: Vec<_> = opcode_indexes
104+
.clone()
105+
.map(|opcode_index| fuel_vm::state::Breakpoint::script(*opcode_index as u64))
106+
.collect();
107+
executor.interpreter.overwrite_breakpoints(&bps);
108+
});
109+
110+
self.breakpoints_need_update = false;
79111
}
80112

81113
/// Finds the breakpoint matching a VM program counter.
@@ -85,7 +117,7 @@ impl ServerState {
85117
// Find the breakpoint ID matching the source location.
86118
let source_bps = self
87119
.breakpoints
88-
.get(source_path)
120+
.get(&source_path)
89121
.ok_or(AdapterError::UnknownBreakpoint { pc })?;
90122
let breakpoint_id = source_bps
91123
.iter()
@@ -101,41 +133,6 @@ impl ServerState {
101133
Ok(breakpoint_id)
102134
}
103135

104-
/// Updates the breakpoints in the VM for all remaining [TestExecutor]s.
105-
pub(crate) fn update_vm_breakpoints(&mut self) {
106-
if !self.breakpoints_need_update {
107-
return;
108-
}
109-
let opcode_indexes = self
110-
.breakpoints
111-
.iter()
112-
.flat_map(|(source_path, breakpoints)| {
113-
if let Some(source_map) = self.source_map.get(&PathBuf::from(source_path)) {
114-
breakpoints
115-
.iter()
116-
.filter_map(|bp| {
117-
bp.line.and_then(|line| {
118-
source_map
119-
.get(&line)
120-
.and_then(|instructions| instructions.first())
121-
})
122-
})
123-
.collect::<Vec<_>>()
124-
} else {
125-
vec![]
126-
}
127-
});
128-
129-
self.executors.iter_mut().for_each(|executor| {
130-
// TODO: use `overwrite_breakpoints` when released
131-
opcode_indexes.clone().for_each(|opcode_index| {
132-
let bp: fuel_vm::prelude::Breakpoint =
133-
fuel_vm::state::Breakpoint::script(*opcode_index);
134-
executor.interpreter.set_breakpoint(bp);
135-
});
136-
});
137-
}
138-
139136
pub(crate) fn test_complete(&mut self, result: TestResult) {
140137
self.test_results.push(result);
141138
self.executors.remove(0);

forc-plugins/forc-debug/src/types.rs

-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
use dap::types::Breakpoint;
22
use std::{collections::HashMap, path::PathBuf};
33

4-
pub type Line = i64;
54
pub type ExitCode = i64;
65
pub type Instruction = u64;
7-
pub type FileSourceMap = HashMap<Line, Vec<Instruction>>;
8-
pub type SourceMap = HashMap<PathBuf, FileSourceMap>;
96
pub type Breakpoints = HashMap<PathBuf, Vec<Breakpoint>>;

0 commit comments

Comments
 (0)