miden_debug/
dap_server.rs1use std::{boxed::Box, net::TcpListener, path::Path, sync::Arc, vec::Vec};
2
3use miden_assembly::DefaultSourceManager;
4use miden_assembly_syntax::diagnostics::Report;
5use miden_core::{Word, events::EventId};
6use miden_debug_engine::{HybridPackageRegistry, normalize_source_path};
7use miden_debug_types::{Location, SourceFile, SourceManager, SourceManagerExt, SourceSpan};
8use miden_mast_package::Package;
9use miden_package_registry::PackageRegistry;
10use miden_processor::{
11 BaseHost, DefaultHost, FutureMaybeSend, Host, HostLibrary, LoadedMastForest, ProcessorState,
12 advice::AdviceMutation, event::EventError,
13};
14
15use crate::{DapConfig, DapExecutor, DebuggerConfig};
16
17pub fn run(config: Box<DebuggerConfig>) -> Result<(), Report> {
22 let addr = config
23 .start_debug_adapter
24 .as_ref()
25 .ok_or_else(|| Report::msg("missing --start-debug-adapter address"))?;
26 let listener = DapExecutor::bind_listener(addr).map_err(Report::msg)?;
27 run_on_listener(config, listener)
28}
29
30fn run_on_listener(config: Box<DebuggerConfig>, listener: TcpListener) -> Result<(), Report> {
31 let addr = listener
32 .local_addr()
33 .map_err(|err| Report::msg(format!("failed to read DAP listener address: {err}")))?;
34 DapConfig::set_global(
35 DapConfig::new(format!("{addr}"))
36 .with_source_path_prefixes(config.source_path_prefixes.clone()),
37 );
38
39 let source_manager = Arc::new(DefaultSourceManager::default());
40 let registry = HybridPackageRegistry::new(
41 config.sysroot.as_deref(),
42 &config.search_path,
43 &config.link_libraries,
44 )?;
45 let program = crate::program_loader::load_package(&config)?;
46 verify_package_dependencies(&program, ®istry)?;
47 let typed_procedure = crate::debug::TypedProcedure::for_package_entrypoint(&program);
48 let inputs = crate::program_loader::execution_inputs(&config, typed_procedure.as_ref())?;
49 let mut host = StandaloneDapHost::new(source_manager);
50
51 for package in registry.all() {
52 let name = package.name.clone();
53 host.load_library(package).map_err(|err| {
54 Report::msg(format!("failed to load package '{name}' into DAP host: {err}"))
55 })?;
56 }
57
58 let executor = DapExecutor::new(inputs.inputs, inputs.advice_inputs, inputs.options);
59 executor
60 .execute_on_listener(program, &mut host, listener)
61 .map(|_| ())
62 .map_err(|err| Report::msg(format!("program execution failed: {err}")))
63}
64
65struct StandaloneDapHost {
66 inner: DefaultHost<DefaultSourceManager>,
67 source_manager: Arc<DefaultSourceManager>,
68}
69
70impl StandaloneDapHost {
71 fn new(source_manager: Arc<DefaultSourceManager>) -> Self {
72 let inner = DefaultHost::default().with_source_manager(source_manager.clone());
73 Self {
74 inner,
75 source_manager,
76 }
77 }
78
79 fn load_library(
80 &mut self,
81 lib: impl Into<HostLibrary>,
82 ) -> Result<(), miden_processor::ExecutionError> {
83 self.inner.load_library(lib)
84 }
85
86 fn ensure_source_file(&self, location: &Location) -> Option<Arc<SourceFile>> {
87 if let Some(file) = self.source_manager.get_by_uri(location.uri()) {
88 return Some(file);
89 }
90
91 let path = normalize_source_path(location.uri().as_str());
92 self.source_manager.load_file(Path::new(&path)).ok()
93 }
94}
95
96impl BaseHost for StandaloneDapHost {
97 fn get_label_and_source_file(
98 &self,
99 location: &Location,
100 ) -> (SourceSpan, Option<Arc<SourceFile>>) {
101 let maybe_file = self.ensure_source_file(location);
102 let span = self.source_manager.location_to_span(location.clone()).unwrap_or_default();
103 (span, maybe_file)
104 }
105
106 fn resolve_event(&self, event_id: EventId) -> Option<&miden_core::events::EventName> {
107 self.inner.resolve_event(event_id)
108 }
109}
110
111impl Host for StandaloneDapHost {
112 fn get_mast_forest(
113 &self,
114 node_digest: &Word,
115 ) -> impl FutureMaybeSend<Option<LoadedMastForest>> {
116 self.inner.get_mast_forest(node_digest)
117 }
118
119 fn on_event(
120 &mut self,
121 process: &ProcessorState<'_>,
122 ) -> impl FutureMaybeSend<Result<Vec<AdviceMutation>, EventError>> {
123 self.inner.on_event(process)
124 }
125}
126
127fn verify_package_dependencies(
128 package: &Package,
129 registry: &HybridPackageRegistry,
130) -> Result<(), Report> {
131 for dependency in package.manifest.dependencies() {
132 let version = miden_project::Version::new(dependency.version().clone(), dependency.digest);
133 if !registry.is_version_available(&dependency.name, &version) {
134 return Err(Report::msg(format!(
135 "dependency {}@{version} not found in loaded libraries",
136 dependency.name
137 )));
138 }
139 }
140
141 Ok(())
142}
143
144#[cfg(test)]
145mod tests;