Skip to main content

miden_debug/
dap_server.rs

1use std::{boxed::Box, 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    DapConfig::set_global(
27        DapConfig::new(addr).with_source_path_prefixes(config.source_path_prefixes.clone()),
28    );
29
30    let source_manager = Arc::new(DefaultSourceManager::default());
31    let registry = HybridPackageRegistry::new(
32        config.sysroot.as_deref(),
33        &config.search_path,
34        &config.link_libraries,
35    )?;
36    let program = crate::program_loader::load_package(&config)?;
37    verify_package_dependencies(&program, &registry)?;
38    let typed_procedure = crate::debug::TypedProcedure::for_package_entrypoint(&program);
39    let inputs = crate::program_loader::execution_inputs(&config, typed_procedure.as_ref())?;
40    let mut host = StandaloneDapHost::new(source_manager);
41
42    for package in registry.all() {
43        let name = package.name.clone();
44        host.load_library(package).map_err(|err| {
45            Report::msg(format!("failed to load package '{name}' into DAP host: {err}"))
46        })?;
47    }
48
49    let executor = DapExecutor::new(inputs.inputs, inputs.advice_inputs, inputs.options);
50    futures_executor::block_on(executor.execute_async(program, &mut host))
51        .map(|_| ())
52        .map_err(|err| Report::msg(format!("program execution failed: {err}")))
53}
54
55struct StandaloneDapHost {
56    inner: DefaultHost<DefaultSourceManager>,
57    source_manager: Arc<DefaultSourceManager>,
58}
59
60impl StandaloneDapHost {
61    fn new(source_manager: Arc<DefaultSourceManager>) -> Self {
62        let inner = DefaultHost::default().with_source_manager(source_manager.clone());
63        Self {
64            inner,
65            source_manager,
66        }
67    }
68
69    fn load_library(
70        &mut self,
71        lib: impl Into<HostLibrary>,
72    ) -> Result<(), miden_processor::ExecutionError> {
73        self.inner.load_library(lib)
74    }
75
76    fn ensure_source_file(&self, location: &Location) -> Option<Arc<SourceFile>> {
77        if let Some(file) = self.source_manager.get_by_uri(location.uri()) {
78            return Some(file);
79        }
80
81        let path = normalize_source_path(location.uri().as_str());
82        self.source_manager.load_file(Path::new(&path)).ok()
83    }
84}
85
86impl BaseHost for StandaloneDapHost {
87    fn get_label_and_source_file(
88        &self,
89        location: &Location,
90    ) -> (SourceSpan, Option<Arc<SourceFile>>) {
91        let maybe_file = self.ensure_source_file(location);
92        let span = self.source_manager.location_to_span(location.clone()).unwrap_or_default();
93        (span, maybe_file)
94    }
95
96    fn resolve_event(&self, event_id: EventId) -> Option<&miden_core::events::EventName> {
97        self.inner.resolve_event(event_id)
98    }
99}
100
101impl Host for StandaloneDapHost {
102    fn get_mast_forest(
103        &self,
104        node_digest: &Word,
105    ) -> impl FutureMaybeSend<Option<LoadedMastForest>> {
106        self.inner.get_mast_forest(node_digest)
107    }
108
109    fn on_event(
110        &mut self,
111        process: &ProcessorState<'_>,
112    ) -> impl FutureMaybeSend<Result<Vec<AdviceMutation>, EventError>> {
113        self.inner.on_event(process)
114    }
115}
116
117fn verify_package_dependencies(
118    package: &Package,
119    registry: &HybridPackageRegistry,
120) -> Result<(), Report> {
121    for dependency in package.manifest.dependencies() {
122        let version = miden_project::Version::new(dependency.version().clone(), dependency.digest);
123        if !registry.is_version_available(&dependency.name, &version) {
124            return Err(Report::msg(format!(
125                "dependency {}@{version} not found in loaded libraries",
126                dependency.name
127            )));
128        }
129    }
130
131    Ok(())
132}