Skip to main content

miden_debug/
dap_server.rs

1use 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
17/// Start a DAP server for a local Miden program.
18///
19/// This is the non-transaction counterpart to `miden-client exec --start-debug-adapter`.
20/// It accepts compiled package artifacts only.
21pub 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, &registry)?;
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;