1use std::{collections::BTreeSet, path::Path, sync::Arc};
2
3use miden_assembly::{Assembler, DefaultSourceManager, SourceManager};
4use miden_assembly_syntax::{
5 Library,
6 diagnostics::{IntoDiagnostic, Report},
7};
8use miden_core::{
9 Word, events::EventId, mast::MastForest, operations::DebugOptions, program::Program,
10 serde::Deserializable,
11};
12use miden_debug_types::{Location, SourceFile, SourceManagerExt, SourceSpan};
13use miden_processor::{
14 BaseHost, DefaultDebugHandler, DefaultHost, FutureMaybeSend, Host, ProcessorState, StackInputs,
15 TraceError, advice::AdviceMutation, event::EventError,
16};
17
18use crate::{DapConfig, DapExecutor, DebuggerConfig, InputFile, exec::ExecutionConfig};
19
20pub fn run(config: Box<DebuggerConfig>) -> Result<(), Report> {
25 let addr = config
26 .start_debug_adapter
27 .as_ref()
28 .ok_or_else(|| Report::msg("missing --start-debug-adapter address"))?;
29 DapConfig::set_global(
30 DapConfig::new(addr).with_source_path_prefixes(config.source_path_prefixes.clone()),
31 );
32
33 let source_manager = Arc::new(DefaultSourceManager::default());
34 let inputs = execution_inputs(&config)?;
35 let libs = load_libraries(&config, source_manager.clone())?;
36 let program = load_program(&config, source_manager.clone(), &libs)?;
37 let mut host = StandaloneDapHost::new(source_manager);
38
39 for lib in libs {
40 host.load_library(lib.mast_forest().clone()).map_err(|err| {
41 Report::msg(format!("failed to load linked library into DAP host: {err}"))
42 })?;
43 }
44
45 let executor = DapExecutor::new(inputs.inputs, inputs.advice_inputs, inputs.options);
46 futures::executor::block_on(executor.execute_async(&program, &mut host))
47 .map(|_| ())
48 .map_err(|err| Report::msg(format!("program execution failed: {err}")))
49}
50
51struct StandaloneDapHost {
52 inner: DefaultHost<DefaultDebugHandler, DefaultSourceManager>,
53 source_manager: Arc<DefaultSourceManager>,
54}
55
56impl StandaloneDapHost {
57 fn new(source_manager: Arc<DefaultSourceManager>) -> Self {
58 let inner = DefaultHost::default().with_source_manager(source_manager.clone());
59 Self {
60 inner,
61 source_manager,
62 }
63 }
64
65 fn load_library(
66 &mut self,
67 lib: Arc<MastForest>,
68 ) -> Result<(), miden_processor::ExecutionError> {
69 self.inner.load_library(lib)
70 }
71
72 fn ensure_source_file(&self, location: &Location) -> Option<Arc<SourceFile>> {
73 if let Some(file) = self.source_manager.get_by_uri(location.uri()) {
74 return Some(file);
75 }
76
77 let path = location
78 .uri()
79 .as_str()
80 .strip_prefix("file://")
81 .unwrap_or_else(|| 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 on_debug(
97 &mut self,
98 process: &ProcessorState,
99 options: &DebugOptions,
100 ) -> Result<(), miden_processor::DebugError> {
101 self.inner.on_debug(process, options)
102 }
103
104 fn on_trace(&mut self, process: &ProcessorState, trace_id: u32) -> Result<(), TraceError> {
105 self.inner.on_trace(process, trace_id)
106 }
107
108 fn resolve_event(&self, event_id: EventId) -> Option<&miden_core::events::EventName> {
109 self.inner.resolve_event(event_id)
110 }
111}
112
113impl Host for StandaloneDapHost {
114 fn get_mast_forest(&self, node_digest: &Word) -> impl FutureMaybeSend<Option<Arc<MastForest>>> {
115 self.inner.get_mast_forest(node_digest)
116 }
117
118 fn on_event(
119 &mut self,
120 process: &ProcessorState<'_>,
121 ) -> impl FutureMaybeSend<Result<Vec<AdviceMutation>, EventError>> {
122 self.inner.on_event(process)
123 }
124}
125
126fn execution_inputs(config: &DebuggerConfig) -> Result<ExecutionConfig, Report> {
127 let mut inputs = config.inputs.clone().unwrap_or_default();
128 if !config.args.is_empty() {
129 let args = config.args.iter().rev().map(|felt| felt.0).collect::<Vec<_>>();
131 inputs.inputs = StackInputs::new(&args).into_diagnostic()?;
132 }
133
134 Ok(inputs)
135}
136
137fn load_program(
138 config: &DebuggerConfig,
139 source_manager: Arc<dyn SourceManager>,
140 libs: &[Arc<Library>],
141) -> Result<Program, Report> {
142 let input = config.input.as_ref().ok_or_else(|| Report::msg("no input file specified"))?;
143 if let InputFile::Real(path) = input
144 && path.extension().and_then(|ext| ext.to_str()) == Some("masm")
145 {
146 return assemble_masm_program(path, source_manager, libs);
147 }
148
149 let package = load_package(config)?;
150 verify_package_dependencies(&package, libs)?;
151 Ok(package.unwrap_program())
152}
153
154fn assemble_masm_program(
155 path: &Path,
156 source_manager: Arc<dyn SourceManager>,
157 libs: &[Arc<Library>],
158) -> Result<Program, Report> {
159 let mut assembler = Assembler::new(source_manager);
160 for lib in libs {
161 assembler.link_dynamic_library(lib.as_ref())?;
162 }
163
164 assembler.assemble_program(path)
165}
166
167fn load_libraries(
168 config: &DebuggerConfig,
169 source_manager: Arc<dyn SourceManager>,
170) -> Result<Vec<Arc<Library>>, Report> {
171 let mut libs = Vec::with_capacity(config.link_libraries.len());
172 for link_library in config.link_libraries.iter() {
173 log::debug!(target: "dap", "loading link library {}", link_library.name());
174 libs.push(link_library.load(config, source_manager.clone())?);
175 }
176
177 if let Some(toolchain_dir) = config.toolchain_dir() {
178 libs.extend(load_sysroot_libs(&toolchain_dir)?);
179 }
180
181 Ok(libs)
182}
183
184fn load_sysroot_libs(toolchain_dir: &Path) -> Result<Vec<Arc<Library>>, Report> {
185 let mut libs = Vec::new();
186
187 let entries = match std::fs::read_dir(toolchain_dir) {
188 Ok(entries) => entries,
189 Err(_) => {
190 log::debug!(target: "dap", "could not read sysroot directory: {}", toolchain_dir.display());
191 return Ok(libs);
192 }
193 };
194
195 for entry in entries {
196 let entry = entry.into_diagnostic()?;
197 let path = entry.path();
198 let Some(ext) = path.extension() else {
199 continue;
200 };
201
202 if ext == "masp" {
203 log::debug!(target: "dap", "loading package from sysroot: {}", path.display());
204 let bytes = std::fs::read(&path).into_diagnostic()?;
205 let package = miden_mast_package::Package::read_from_bytes(&bytes).map_err(|err| {
206 Report::msg(format!("failed to load package '{}': {err}", path.display()))
207 })?;
208 libs.push(package.mast.clone());
209 } else if ext == "masl" {
210 log::debug!(target: "dap", "loading library from sysroot: {}", path.display());
211 let bytes = std::fs::read(&path).into_diagnostic()?;
212 let lib = Library::read_from_bytes(&bytes).map_err(|err| {
213 Report::msg(format!("failed to load library '{}': {err}", path.display()))
214 })?;
215 libs.push(Arc::new(lib));
216 }
217 }
218
219 Ok(libs)
220}
221
222fn load_package(config: &DebuggerConfig) -> Result<Arc<miden_mast_package::Package>, Report> {
223 let input = config.input.as_ref().ok_or_else(|| Report::msg("no input file specified"))?;
224 let package = match input {
225 InputFile::Real(path) => {
226 let bytes = std::fs::read(path).into_diagnostic()?;
227 miden_mast_package::Package::read_from_bytes(&bytes)
228 .map(Arc::new)
229 .map_err(|err| {
230 Report::msg(format!(
231 "failed to load Miden package from {}: {err}",
232 path.display()
233 ))
234 })?
235 }
236 InputFile::Stdin(bytes) => miden_mast_package::Package::read_from_bytes(bytes)
237 .map(Arc::new)
238 .map_err(|err| {
239 Report::msg(format!("failed to load Miden package from stdin: {err}"))
240 })?,
241 };
242
243 if let Some(entry) = config.entrypoint.as_ref() {
244 let id = entry
245 .parse::<miden_assembly::ast::QualifiedProcedureName>()
246 .map_err(|_| Report::msg(format!("invalid function identifier: '{entry}'")))?;
247 if !package.is_library() {
248 return Err(Report::msg("cannot use --entrypoint with executable packages"));
249 }
250
251 package.make_executable(&id).map(Arc::new)
252 } else {
253 Ok(package)
254 }
255}
256
257fn verify_package_dependencies(
258 package: &miden_mast_package::Package,
259 libs: &[Arc<Library>],
260) -> Result<(), Report> {
261 let available = libs.iter().map(|lib| *lib.digest()).collect::<BTreeSet<_>>();
262 for dependency in package.manifest.dependencies() {
263 if !available.contains(&dependency.digest) {
264 return Err(Report::msg(format!(
265 "dependency {dependency:?} not found in loaded libraries"
266 )));
267 }
268 }
269
270 Ok(())
271}