miden_debug/
dap_server.rs1use std::{path::Path, sync::Arc};
2
3use miden_assembly::{Assembler, DefaultSourceManager, SourceManager};
4use miden_assembly_syntax::diagnostics::{IntoDiagnostic, Report};
5use miden_core::{Word, events::EventId};
6use miden_debug_engine::HybridPackageRegistry;
7use miden_debug_types::{Location, SourceFile, SourceManagerExt, SourceSpan};
8use miden_mast_package::{Package, PackageId};
9use miden_package_registry::{PackageProvider, PackageRegistry};
10use miden_processor::{
11 BaseHost, DefaultHost, FutureMaybeSend, Host, HostLibrary, LoadedMastForest, ProcessorState,
12 advice::AdviceMutation, event::EventError,
13};
14
15use crate::{DapConfig, DapExecutor, DebuggerConfig, InputFile};
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 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 inputs = crate::program_loader::execution_inputs(&config)?;
32 let mut registry = HybridPackageRegistry::new(
33 config.sysroot.as_deref(),
34 &config.search_path,
35 &config.link_libraries,
36 )?;
37 let program = load_program(&config, source_manager.clone(), &mut registry)?;
38 let mut host = StandaloneDapHost::new(source_manager);
39
40 for package in registry.all() {
41 let name = package.name.clone();
42 host.load_library(package).map_err(|err| {
43 Report::msg(format!("failed to load package '{name}' into DAP host: {err}"))
44 })?;
45 }
46
47 let executor = DapExecutor::new(inputs.inputs, inputs.advice_inputs, inputs.options);
48 futures::executor::block_on(executor.execute_async(program, &mut host))
49 .map(|_| ())
50 .map_err(|err| Report::msg(format!("program execution failed: {err}")))
51}
52
53struct StandaloneDapHost {
54 inner: DefaultHost<DefaultSourceManager>,
55 source_manager: Arc<DefaultSourceManager>,
56}
57
58impl StandaloneDapHost {
59 fn new(source_manager: Arc<DefaultSourceManager>) -> Self {
60 let inner = DefaultHost::default().with_source_manager(source_manager.clone());
61 Self {
62 inner,
63 source_manager,
64 }
65 }
66
67 fn load_library(
68 &mut self,
69 lib: impl Into<HostLibrary>,
70 ) -> Result<(), miden_processor::ExecutionError> {
71 self.inner.load_library(lib)
72 }
73
74 fn ensure_source_file(&self, location: &Location) -> Option<Arc<SourceFile>> {
75 if let Some(file) = self.source_manager.get_by_uri(location.uri()) {
76 return Some(file);
77 }
78
79 let path = location
80 .uri()
81 .as_str()
82 .strip_prefix("file://")
83 .unwrap_or_else(|| location.uri().as_str());
84 self.source_manager.load_file(Path::new(path)).ok()
85 }
86}
87
88impl BaseHost for StandaloneDapHost {
89 fn get_label_and_source_file(
90 &self,
91 location: &Location,
92 ) -> (SourceSpan, Option<Arc<SourceFile>>) {
93 let maybe_file = self.ensure_source_file(location);
94 let span = self.source_manager.location_to_span(location.clone()).unwrap_or_default();
95 (span, maybe_file)
96 }
97
98 fn resolve_event(&self, event_id: EventId) -> Option<&miden_core::events::EventName> {
99 self.inner.resolve_event(event_id)
100 }
101}
102
103impl Host for StandaloneDapHost {
104 fn get_mast_forest(
105 &self,
106 node_digest: &Word,
107 ) -> impl FutureMaybeSend<Option<LoadedMastForest>> {
108 self.inner.get_mast_forest(node_digest)
109 }
110
111 fn on_event(
112 &mut self,
113 process: &ProcessorState<'_>,
114 ) -> impl FutureMaybeSend<Result<Vec<AdviceMutation>, EventError>> {
115 self.inner.on_event(process)
116 }
117}
118
119fn load_program(
120 config: &DebuggerConfig,
121 source_manager: Arc<dyn SourceManager>,
122 registry: &mut HybridPackageRegistry,
123) -> Result<Arc<Package>, Report> {
124 let input = config.input.as_ref().ok_or_else(|| Report::msg("no input file specified"))?;
125 if let InputFile::Real(path) = input
126 && path.extension().and_then(|ext| ext.to_str()) == Some("masm")
127 {
128 return assemble_masm_program(path, source_manager, registry);
129 }
130
131 let package = load_package(config)?;
132 verify_package_dependencies(&package, registry)?;
133 assert!(package.is_program());
134 Ok(package)
135}
136
137fn assemble_masm_program(
138 path: &Path,
139 source_manager: Arc<dyn SourceManager>,
140 registry: &HybridPackageRegistry,
141) -> Result<Arc<Package>, Report> {
142 let mut assembler = Assembler::new(source_manager.clone());
143
144 let mut parser =
145 miden_assembly_syntax::ModuleParser::new(Some(miden_assembly::ast::ModuleKind::Executable));
146 let module = parser.parse_file(None, path, source_manager)?;
147
148 for extern_package in module.required_packages() {
149 let package_id = PackageId::from(extern_package.clone().into_inner());
150 let package = registry
151 .find_latest(&package_id, &miden_project::VersionReq::STAR.into())
152 .ok_or_else(|| Report::msg(format!("extern package '{package_id}' is not available")))
153 .and_then(|record| registry.load_package(&package_id, record.version()))?;
154 assembler.link_package(package, miden_project::Linkage::Dynamic)?;
155 }
156
157 assembler.assemble_program("program", module).map(Arc::from)
158}
159
160fn load_package(config: &DebuggerConfig) -> Result<Arc<Package>, Report> {
161 let input = config.input.as_ref().ok_or_else(|| Report::msg("no input file specified"))?;
162 let package = match input {
163 InputFile::Real(path) => {
164 let bytes = std::fs::read(path).into_diagnostic()?;
165 Package::read_from_bytes_trusted(&bytes).map(Arc::new).map_err(|err| {
166 Report::msg(format!("failed to load Miden package from {}: {err}", path.display()))
167 })?
168 }
169 InputFile::Stdin(bytes) => {
170 Package::read_from_bytes_trusted(bytes).map(Arc::new).map_err(|err| {
171 Report::msg(format!("failed to load Miden package from stdin: {err}"))
172 })?
173 }
174 };
175
176 if let Some(entry) = config.entrypoint.as_ref() {
177 let id = entry
178 .parse::<miden_assembly::ast::QualifiedProcedureName>()
179 .map_err(|_| Report::msg(format!("invalid function identifier: '{entry}'")))?;
180 if !package.is_library() {
181 return Err(Report::msg("cannot use --entrypoint with executable packages"));
182 }
183
184 package.make_executable(&id).map(Arc::new)
185 } else {
186 Ok(package)
187 }
188}
189
190fn verify_package_dependencies(
191 package: &Package,
192 registry: &HybridPackageRegistry,
193) -> Result<(), Report> {
194 for dependency in package.manifest.dependencies() {
195 let version = miden_project::Version::new(dependency.version().clone(), dependency.digest);
196 if !registry.is_version_available(&dependency.name, &version) {
197 return Err(Report::msg(format!(
198 "dependency {}@{version} not found in loaded libraries",
199 dependency.name
200 )));
201 }
202 }
203
204 Ok(())
205}