use std::{collections::VecDeque, rc::Rc, sync::Arc};
use miden_assembly::{DefaultSourceManager, SourceManager};
use miden_assembly_syntax::diagnostics::{IntoDiagnostic, Report};
use miden_core::{program::Program, serde::Deserializable};
use miden_processor::{
Felt, StackInputs,
advice::{AdviceInputs, AdviceMutation},
mast::MastForest,
};
use crate::{
config::DebuggerConfig,
debug::{Breakpoint, BreakpointType, ReadMemoryExpr, ResolvedLocation, resolve_variable_value},
exec::{DebugExecutor, Executor},
input::InputFile,
};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum DebugMode {
Program,
Transaction,
Remote,
}
fn clone_advice_mutation(mutation: &AdviceMutation) -> AdviceMutation {
match mutation {
AdviceMutation::ExtendStack { values } => AdviceMutation::ExtendStack {
values: values.clone(),
},
AdviceMutation::ExtendMap { other } => AdviceMutation::ExtendMap {
other: other.clone(),
},
AdviceMutation::ExtendMerkleStore { infos } => AdviceMutation::ExtendMerkleStore {
infos: infos.clone(),
},
AdviceMutation::ExtendPrecompileRequests { data } => {
AdviceMutation::ExtendPrecompileRequests { data: data.clone() }
}
}
}
fn clone_event_replay_queue(event_replay: &[Vec<AdviceMutation>]) -> VecDeque<Vec<AdviceMutation>> {
event_replay
.iter()
.map(|batch| batch.iter().map(clone_advice_mutation).collect())
.collect()
}
pub struct State {
pub source_manager: Arc<dyn SourceManager>,
pub config: Box<DebuggerConfig>,
pub input_mode: InputMode,
pub breakpoints: Vec<Breakpoint>,
pub breakpoints_hit: Vec<Breakpoint>,
pub next_breakpoint_id: u8,
pub stopped: bool,
pub debug_mode: DebugMode,
session: SessionState,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
pub enum InputMode {
#[default]
Normal,
#[allow(dead_code)]
Insert,
Command,
}
struct LocalState {
executor: DebugExecutor,
execution_failed: Option<miden_processor::ExecutionError>,
}
#[cfg(feature = "dap")]
struct RemoteState {
client: crate::exec::DapClient,
executor: DebugExecutor,
addr: String,
synced_bp_files: std::collections::BTreeSet<String>,
}
enum SessionState {
Local(Box<LocalState>),
#[cfg(feature = "dap")]
Remote(Box<RemoteState>),
}
#[cfg(feature = "dap")]
struct RemoteSnapshot {
callstack: crate::debug::CallStack,
current_stack: Vec<Felt>,
cycle: usize,
}
#[cfg(feature = "dap")]
impl RemoteState {
fn connect(addr: &str, source_manager: &Arc<dyn SourceManager>) -> Result<Self, Report> {
use std::{cell::RefCell, collections::BTreeSet, rc::Rc};
use miden_debug_engine::debug::DebugVarTracker;
use miden_processor::{ContextId, FastProcessor};
use crate::exec::DebuggerHost;
let mut client = crate::exec::DapClient::connect(addr).map_err(Report::msg)?;
let ui_state = client.handshake().map_err(Report::msg)?;
let snapshot = convert_ui_state(&ui_state, source_manager);
let debug_vars = DebugVarTracker::new(Rc::new(RefCell::new(Default::default())));
let executor = DebugExecutor {
processor: FastProcessor::new(StackInputs::default()),
host: DebuggerHost::new(source_manager.clone()),
resume_ctx: None,
current_stack: snapshot.current_stack,
current_op: None,
current_asmop: None,
stack_outputs: Default::default(),
contexts: BTreeSet::new(),
root_context: ContextId::root(),
current_context: ContextId::root(),
callstack: snapshot.callstack,
current_proc: None,
debug_vars,
last_debug_var_count: 0,
recent: VecDeque::new(),
cycle: snapshot.cycle,
stopped: false,
};
Ok(Self {
client,
executor,
addr: addr.to_string(),
synced_bp_files: std::collections::BTreeSet::new(),
})
}
fn read_memory(&mut self, expr: &ReadMemoryExpr) -> Result<String, String> {
self.client.read_memory(expr)
}
fn sync_breakpoints(&mut self, breakpoints: &[Breakpoint]) {
use std::collections::BTreeMap;
let mut by_file: BTreeMap<String, Vec<i64>> = BTreeMap::new();
let mut func_names: Vec<String> = Vec::new();
for bp in breakpoints {
match &bp.ty {
BreakpointType::Line { pattern, line } => {
by_file.entry(pattern.as_str().to_string()).or_default().push(*line as i64);
}
BreakpointType::Called(pattern) | BreakpointType::File(pattern) => {
func_names.push(pattern.as_str().to_string());
}
_ => {}
}
}
let stale_files: Vec<String> = self
.synced_bp_files
.iter()
.filter(|f| !by_file.contains_key(f.as_str()))
.cloned()
.collect();
for file in &stale_files {
let _ = self.client.set_breakpoints(file, &[]);
}
for (file, lines) in &by_file {
let _ = self.client.set_breakpoints(file, lines);
}
let _ = self.client.set_function_breakpoints(&func_names);
self.synced_bp_files = by_file.into_keys().collect();
}
fn resume(&mut self, breakpoints: &[Breakpoint]) -> Result<crate::exec::DapStopReason, String> {
self.sync_breakpoints(breakpoints);
let has_step = breakpoints.iter().any(|bp| matches!(bp.ty, BreakpointType::Step));
let has_next = breakpoints
.iter()
.any(|bp| matches!(bp.ty, BreakpointType::Next | BreakpointType::NextLine));
let has_finish = breakpoints.iter().any(|bp| matches!(bp.ty, BreakpointType::Finish));
if has_step {
self.client.step_in()
} else if has_next {
self.client.step_over()
} else if has_finish {
self.client.step_out()
} else {
self.client.continue_()
}
}
fn refresh_executor(
&mut self,
source_manager: &Arc<dyn SourceManager>,
pushed: &crate::exec::DapUiState,
) {
let snapshot = convert_ui_state(pushed, source_manager);
self.executor.current_stack = snapshot.current_stack;
self.executor.callstack = snapshot.callstack;
self.executor.cycle = snapshot.cycle;
}
fn reconnect(&mut self, source_manager: &Arc<dyn SourceManager>) -> Result<(), Report> {
let timeout = std::time::Duration::from_secs(30);
let mut new_client =
crate::exec::DapClient::connect_with_retry(&self.addr, timeout).map_err(Report::msg)?;
let ui_state = new_client.handshake().map_err(Report::msg)?;
let snapshot = convert_ui_state(&ui_state, source_manager);
self.client = new_client;
self.executor.current_stack = snapshot.current_stack;
self.executor.callstack = snapshot.callstack;
self.executor.cycle = snapshot.cycle;
Ok(())
}
}
impl State {
fn new_local(
source_manager: Arc<dyn SourceManager>,
config: Box<DebuggerConfig>,
debug_mode: DebugMode,
local: LocalState,
) -> Self {
Self {
source_manager,
config,
input_mode: InputMode::Normal,
breakpoints: vec![],
breakpoints_hit: vec![],
next_breakpoint_id: 0,
stopped: true,
debug_mode,
session: SessionState::Local(Box::new(local)),
}
}
pub fn new(config: Box<DebuggerConfig>) -> Result<Self, Report> {
let source_manager = Arc::new(DefaultSourceManager::default());
let mut inputs = config.inputs.clone().unwrap_or_default();
if !config.args.is_empty() {
let args = config.args.iter().rev().map(|felt| felt.0).collect::<Vec<_>>();
inputs.inputs = StackInputs::new(&args).into_diagnostic()?;
}
let args = inputs.inputs.iter().copied().collect::<Vec<_>>();
let package = load_package(&config)?;
let mut libs = Vec::with_capacity(config.link_libraries.len());
for link_library in config.link_libraries.iter() {
log::debug!(target: "state", "loading link library {}", link_library.name());
let lib = link_library.load(&config, source_manager.clone())?;
libs.push(lib.clone());
}
if let Some(toolchain_dir) = config.toolchain_dir() {
libs.extend(load_sysroot_libs(&toolchain_dir)?);
}
let mut executor = Executor::new(args.clone());
for lib in libs.iter() {
executor.register_library_dependency(lib.clone());
executor.with_library(lib.clone());
}
let dependencies = package.manifest.dependencies();
executor.with_dependencies(dependencies)?;
executor.with_advice_inputs(inputs.advice_inputs);
let program = package.unwrap_program();
let executor = executor.into_debug(&program, source_manager.clone());
Ok(Self::new_local(
source_manager,
config,
DebugMode::Program,
LocalState {
executor,
execution_failed: None,
},
))
}
pub fn new_for_transaction(
program: Arc<Program>,
stack_inputs: StackInputs,
advice_inputs: AdviceInputs,
source_manager: Arc<dyn SourceManager>,
mast_forests: Vec<Arc<MastForest>>,
event_replay: Vec<Vec<AdviceMutation>>,
) -> Result<Self, Report> {
let args = stack_inputs.iter().copied().rev().collect::<Vec<_>>();
let mut executor = Executor::new(args);
executor.with_advice_inputs(advice_inputs);
let debug_executor = executor.into_debug_with_replay(
&program,
source_manager.clone(),
mast_forests,
clone_event_replay_queue(&event_replay),
);
Ok(Self::new_local(
source_manager,
Box::default(),
DebugMode::Transaction,
LocalState {
executor: debug_executor,
execution_failed: None,
},
))
}
pub fn reload(&mut self) -> Result<(), Report> {
if self.debug_mode == DebugMode::Transaction {
return Err(Report::msg("reload is not supported in transaction debug mode"));
}
if self.debug_mode == DebugMode::Remote {
#[cfg(feature = "dap")]
{
let source_manager = self.source_manager.clone();
let SessionState::Remote(remote) = &mut self.session else {
return Err(Report::msg("no remote debug session"));
};
let result = remote.client.restart_phase2().map_err(Report::msg)?;
match result {
crate::exec::DapStopReason::Restarting => {
remote.reconnect(&source_manager)?;
}
crate::exec::DapStopReason::Stopped(snapshot) => {
remote.refresh_executor(&source_manager, &snapshot);
}
crate::exec::DapStopReason::Terminated => {
return Err(Report::msg("server terminated without restart signal"));
}
}
self.breakpoints_hit.clear();
self.stopped = true;
return Ok(());
}
#[cfg(not(feature = "dap"))]
return Err(Report::msg("remote debug mode requires the `dap` feature"));
}
log::debug!("reloading program");
let package = load_package(&self.config)?;
let mut inputs = self.config.inputs.clone().unwrap_or_default();
if !self.config.args.is_empty() {
let args = self.config.args.iter().rev().map(|felt| felt.0).collect::<Vec<_>>();
inputs.inputs = StackInputs::new(&args).into_diagnostic()?;
}
let args = inputs.inputs.iter().copied().collect::<Vec<_>>();
let mut libs = Vec::with_capacity(self.config.link_libraries.len());
for link_library in self.config.link_libraries.iter() {
let lib = link_library.load(&self.config, self.source_manager.clone())?;
libs.push(lib.clone());
}
if let Some(toolchain_dir) = self.config.toolchain_dir() {
libs.extend(load_sysroot_libs(&toolchain_dir)?);
}
let mut executor = Executor::new(args.clone());
for lib in libs.iter() {
executor.register_library_dependency(lib.clone());
executor.with_library(lib.clone());
}
let dependencies = package.manifest.dependencies();
executor.with_dependencies(dependencies)?;
executor.with_advice_inputs(inputs.advice_inputs);
let program = package.unwrap_program();
let executor = executor.into_debug(&program, self.source_manager.clone());
self.session = SessionState::Local(Box::new(LocalState {
executor,
execution_failed: None,
}));
self.breakpoints_hit.clear();
let breakpoints = core::mem::take(&mut self.breakpoints);
self.breakpoints.reserve(breakpoints.len());
self.next_breakpoint_id = 0;
self.stopped = true;
for bp in breakpoints {
self.create_breakpoint(bp.ty);
}
Ok(())
}
pub fn create_breakpoint(&mut self, ty: BreakpointType) {
let id = self.next_breakpoint_id();
let creation_cycle = self.executor().cycle;
log::trace!("created breakpoint with id {id} at cycle {creation_cycle}");
if matches!(ty, BreakpointType::Finish)
&& let Some(frame) = self.executor_mut().callstack.current_frame_mut()
{
frame.break_on_exit();
}
self.breakpoints.push(Breakpoint {
id,
creation_cycle,
ty,
});
}
fn next_breakpoint_id(&mut self) -> u8 {
let mut candidate = self.next_breakpoint_id;
let initial = candidate;
let mut next = candidate.wrapping_add(1);
loop {
assert_ne!(initial, next, "unable to allocate a breakpoint id: too many breakpoints");
if self
.breakpoints
.iter()
.chain(self.breakpoints_hit.iter())
.any(|bp| bp.id == candidate)
{
candidate = next;
next = candidate.wrapping_add(1);
continue;
}
self.next_breakpoint_id = next;
break candidate;
}
}
pub fn executor(&self) -> &DebugExecutor {
match &self.session {
SessionState::Local(local) => &local.executor,
#[cfg(feature = "dap")]
SessionState::Remote(remote) => &remote.executor,
}
}
pub fn executor_mut(&mut self) -> &mut DebugExecutor {
match &mut self.session {
SessionState::Local(local) => &mut local.executor,
#[cfg(feature = "dap")]
SessionState::Remote(remote) => &mut remote.executor,
}
}
pub fn current_procedure(&self) -> Option<Rc<str>> {
let live_proc = self
.executor()
.current_asmop
.as_ref()
.map(|op| Rc::from(op.context_name()))
.or_else(|| self.executor().current_proc.clone());
let frame_proc =
self.executor().callstack.current_frame().and_then(|frame| frame.procedure(""));
live_proc.or(frame_proc)
}
pub fn current_location(&self) -> Option<ResolvedLocation> {
self.executor()
.callstack
.current_frame()
.and_then(|frame| frame.recent().back())
.and_then(|detail| detail.resolve(&*self.source_manager))
.cloned()
}
pub fn current_display_location(&self) -> Option<ResolvedLocation> {
self.executor()
.callstack
.current_frame()
.and_then(|frame| frame.last_resolved(&*self.source_manager))
.cloned()
}
pub fn is_next_source_line(
start_proc: Option<&str>,
start_loc: Option<&ResolvedLocation>,
current_proc: Option<&str>,
current_loc: Option<&ResolvedLocation>,
) -> bool {
let same_proc = match (start_proc, current_proc) {
(Some(start), Some(current)) => start == current,
(Some(_), None) => false,
_ => true,
};
if !same_proc {
return false;
}
match (start_loc, current_loc) {
(Some(start), Some(current)) => {
start.source_file.uri().as_str() == current.source_file.uri().as_str()
&& start.line != current.line
}
(None, Some(_)) => true,
_ => false,
}
}
pub fn execution_failed(&self) -> Option<&miden_processor::ExecutionError> {
match &self.session {
SessionState::Local(local) => local.execution_failed.as_ref(),
#[cfg(feature = "dap")]
SessionState::Remote(_) => None,
}
}
pub fn set_execution_failed(&mut self, error: miden_processor::ExecutionError) {
match &mut self.session {
SessionState::Local(local) => local.execution_failed = Some(error),
#[cfg(feature = "dap")]
SessionState::Remote(_) => {
panic!("cannot record local execution failure while in remote mode")
}
}
}
}
macro_rules! write_with_format_type {
($out:ident, $read_expr:ident, $value:expr) => {
match $read_expr.format {
crate::debug::FormatType::Decimal => write!(&mut $out, "{}", $value).unwrap(),
crate::debug::FormatType::Hex => write!(&mut $out, "{:0x}", $value).unwrap(),
crate::debug::FormatType::Binary => write!(&mut $out, "{:0b}", $value).unwrap(),
}
};
}
impl State {
pub fn read_memory(&mut self, expr: &ReadMemoryExpr) -> Result<String, String> {
use core::fmt::Write;
use miden_assembly_syntax::ast::types::Type;
use crate::debug::FormatType;
#[cfg(feature = "dap")]
if self.debug_mode == DebugMode::Remote {
let SessionState::Remote(remote) = &mut self.session else {
return Err("no remote debug session".into());
};
return remote.read_memory(expr);
}
#[cfg(not(feature = "dap"))]
if self.debug_mode == DebugMode::Remote {
return Err("remote debug mode requires the `dap` feature".into());
}
let executor = self.executor();
let cycle = miden_processor::trace::RowIndex::from(executor.cycle);
let context = executor.current_context;
let memory = executor.processor.memory();
let read_element = |addr: u32| -> Option<Felt> {
memory.read_element(context, Felt::new(addr as u64)).ok()
};
let mut output = String::new();
if expr.count > 1 {
return Err("-count with value > 1 is not yet implemented".into());
} else if matches!(expr.ty, Type::Felt) {
if !expr.addr.is_element_aligned() {
return Err(
"read failed: type 'felt' must be aligned to an element boundary".into()
);
}
let felt = read_element(expr.addr.addr).unwrap_or(Felt::ZERO);
write_with_format_type!(output, expr, felt.as_canonical_u64());
} else if matches!(
expr.ty,
Type::Array(ref array_ty) if array_ty.element_type() == &Type::Felt && array_ty.len() == 4
) {
if !expr.addr.is_word_aligned() {
return Err("read failed: type 'word' must be aligned to a word boundary".into());
}
let word = memory
.read_word(context, Felt::new(expr.addr.addr as u64), cycle)
.unwrap_or_default();
output.push('[');
for (i, elem) in word.iter().enumerate() {
if i > 0 {
output.push_str(", ");
}
write_with_format_type!(output, expr, elem.as_canonical_u64());
}
output.push(']');
} else {
if !expr.addr.is_element_aligned() {
return Err("invalid read: unaligned reads are not supported yet".into());
}
const U32_MASK: u64 = u32::MAX as u64;
let size = expr.ty.size_in_bytes();
let size_in_felts = expr.ty.size_in_felts();
let mut bytes = Vec::with_capacity(size);
let mut needed = size;
for i in 0..size_in_felts {
let addr = expr.addr.addr.checked_add(i as u32).ok_or_else(|| {
"invalid read: attempted to read beyond end of linear memory".to_string()
})?;
let elem = read_element(addr).unwrap_or_default();
let elem_bytes = ((elem.as_canonical_u64() & U32_MASK) as u32).to_le_bytes();
let take = core::cmp::min(needed, 4);
bytes.extend(&elem_bytes[..take]);
needed -= take;
}
match &expr.ty {
Type::I1 => match expr.format {
FormatType::Decimal => write!(&mut output, "{}", bytes[0] != 0).unwrap(),
FormatType::Hex => {
write!(&mut output, "{:#0x}", (bytes[0] != 0) as u8).unwrap()
}
FormatType::Binary => {
write!(&mut output, "{:#0b}", (bytes[0] != 0) as u8).unwrap()
}
},
Type::I8 => write_with_format_type!(output, expr, bytes[0] as i8),
Type::U8 => write_with_format_type!(output, expr, bytes[0]),
Type::I16 => {
write_with_format_type!(output, expr, i16::from_le_bytes([bytes[0], bytes[1]]))
}
Type::U16 => {
write_with_format_type!(output, expr, u16::from_le_bytes([bytes[0], bytes[1]]))
}
Type::I32 => write_with_format_type!(
output,
expr,
i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
),
Type::U32 => write_with_format_type!(
output,
expr,
u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
),
ty @ (Type::I64 | Type::U64) => {
let val = u64::from_le_bytes(bytes[..8].try_into().unwrap());
if matches!(ty, Type::I64) {
write_with_format_type!(output, expr, val as i64)
} else {
write_with_format_type!(output, expr, val)
}
}
ty => {
return Err(format!(
"support for reads of type '{ty}' are not implemented yet"
));
}
}
}
Ok(output)
}
pub fn format_variables(&self, show_all: bool) -> String {
use core::fmt::Write;
let executor = self.executor();
let debug_vars = &executor.debug_vars;
if !debug_vars.has_variables() {
return "No debug variables tracked".to_string();
}
let mut output = String::new();
let stack = executor.current_stack.clone();
let context = executor.current_context;
let read_mem = |addr: u32| -> Option<Felt> {
executor.processor.memory().read_element(context, Felt::new(addr as u64)).ok()
};
let current_source = if show_all {
None
} else {
self.current_display_location()
};
for var_snapshot in debug_vars.current_variables() {
let name = var_snapshot.info.name();
if !show_all && is_compiler_generated_name(name) {
continue;
}
if let (Some(current), Some(var_loc)) =
(current_source.as_ref(), var_snapshot.info.location())
&& (var_loc.uri.as_str() != current.source_file.uri().as_str()
|| var_loc.line.to_u32() > current.line)
{
continue;
}
if !output.is_empty() {
output.push_str(", ");
}
let location = var_snapshot.info.value_location();
let value = resolve_variable_value(location, &stack, read_mem, |offset| {
let fmp_addr = miden_core::FMP_ADDR.as_canonical_u64() as u32;
let fmp = read_mem(fmp_addr)?;
let addr = (fmp.as_canonical_u64() as i64 + offset as i64) as u32;
read_mem(addr)
});
match value {
Some(felt) => {
write!(&mut output, "{name}={}", felt.as_canonical_u64()).unwrap();
}
None => {
write!(&mut output, "{name}={location}").unwrap();
}
}
}
if output.is_empty() {
"No source-level variables (use ':vars all' to show compiler locals)".to_string()
} else {
output
}
}
}
fn is_compiler_generated_name(name: &str) -> bool {
name.strip_prefix("local")
.is_some_and(|suffix| !suffix.is_empty() && suffix.chars().all(|c| c.is_ascii_digit()))
}
#[cfg(feature = "dap")]
impl State {
pub fn new_for_dap(addr: &str) -> Result<Self, Report> {
let source_manager: Arc<dyn SourceManager> = Arc::new(DefaultSourceManager::default());
let remote = RemoteState::connect(addr, &source_manager)?;
Ok(Self {
source_manager,
config: Box::default(),
input_mode: InputMode::Normal,
breakpoints: vec![],
breakpoints_hit: vec![],
next_breakpoint_id: 0,
stopped: true,
debug_mode: DebugMode::Remote,
session: SessionState::Remote(Box::new(remote)),
})
}
pub fn step_remote(&mut self) -> Result<crate::exec::DapStopReason, Report> {
let source_manager = self.source_manager.clone();
let SessionState::Remote(remote) = &mut self.session else {
return Err(Report::msg("no remote debug session"));
};
let result = remote.resume(&self.breakpoints).map_err(Report::msg)?;
self.breakpoints.retain(|bp| !bp.is_one_shot());
match &result {
crate::exec::DapStopReason::Stopped(snapshot) => {
remote.refresh_executor(&source_manager, snapshot);
self.stopped = true;
}
crate::exec::DapStopReason::Terminated => {
remote.executor.stopped = true;
self.stopped = true;
}
crate::exec::DapStopReason::Restarting => {
return Err(Report::msg("unexpected Phase 2 restart signal during step"));
}
}
Ok(result)
}
}
#[cfg(feature = "dap")]
fn convert_ui_state(
snapshot: &crate::exec::DapUiState,
source_manager: &Arc<dyn SourceManager>,
) -> RemoteSnapshot {
use crate::debug::{CallFrame, CallStack};
let call_frames: Vec<CallFrame> = snapshot
.callstack
.iter()
.map(|frame| {
let resolved = resolve_remote_frame(frame, source_manager);
CallFrame::from_remote(Some(frame.name.clone()), resolved)
})
.collect();
let current_stack = snapshot.current_stack.iter().copied().map(Felt::new).collect();
RemoteSnapshot {
callstack: CallStack::from_remote_frames(call_frames),
current_stack,
cycle: snapshot.cycle,
}
}
#[cfg(feature = "dap")]
fn resolve_remote_frame(
frame: &crate::exec::DapUiFrame,
source_manager: &Arc<dyn SourceManager>,
) -> Option<crate::debug::ResolvedLocation> {
use std::path::Path;
use miden_debug_types::{SourceManagerExt, SourceSpan};
let path_str = frame.source_path.as_ref()?;
let path = Path::new(path_str);
let source_file = source_manager.load_file(path).ok()?;
let line = frame.line.max(1) as u32;
let col = frame.column.max(1) as u32;
let content = source_file.content();
let line_index = miden_debug_types::LineIndex::from(line.saturating_sub(1));
let range = content.line_range(line_index)?;
let span = SourceSpan::new(source_file.id(), range);
Some(crate::debug::ResolvedLocation {
source_file,
line,
col,
span,
})
}
fn load_sysroot_libs(
toolchain_dir: &std::path::Path,
) -> Result<Vec<Arc<miden_assembly_syntax::Library>>, Report> {
let mut libs = Vec::new();
let entries = match std::fs::read_dir(toolchain_dir) {
Ok(entries) => entries,
Err(_) => {
log::debug!(target: "state", "could not read sysroot directory: {}", toolchain_dir.display());
return Ok(libs);
}
};
for entry in entries {
let entry = entry.into_diagnostic()?;
let path = entry.path();
let Some(ext) = path.extension() else {
continue;
};
if ext == "masp" {
log::debug!(target: "state", "loading library from sysroot: {}", path.display());
let bytes = std::fs::read(&path).into_diagnostic()?;
let package = miden_mast_package::Package::read_from_bytes(&bytes).map_err(|e| {
Report::msg(format!("failed to load package '{}': {e}", path.display()))
})?;
libs.push(package.mast.clone());
} else if ext == "masl" {
log::debug!(target: "state", "loading library from sysroot: {}", path.display());
let bytes = std::fs::read(&path).into_diagnostic()?;
let lib = miden_assembly_syntax::Library::read_from_bytes(&bytes).map_err(|e| {
Report::msg(format!("failed to load library '{}': {e}", path.display()))
})?;
libs.push(Arc::new(lib));
}
}
if libs.is_empty() {
log::debug!(target: "state", "no libraries found in sysroot: {}", toolchain_dir.display());
}
Ok(libs)
}
fn load_package(config: &DebuggerConfig) -> Result<Arc<miden_mast_package::Package>, Report> {
let input = config.input.as_ref().ok_or_else(|| Report::msg("no input file specified"))?;
let package = match input {
InputFile::Real(path) => {
let bytes = std::fs::read(path).into_diagnostic()?;
miden_mast_package::Package::read_from_bytes(&bytes)
.map(Arc::new)
.map_err(|e| {
Report::msg(format!(
"failed to load Miden package from {}: {e}",
path.display()
))
})?
}
InputFile::Stdin(bytes) => miden_mast_package::Package::read_from_bytes(bytes)
.map(Arc::new)
.map_err(|e| Report::msg(format!("failed to load Miden package from stdin: {e}")))?,
};
if let Some(entry) = config.entrypoint.as_ref() {
let id = entry
.parse::<miden_assembly::ast::QualifiedProcedureName>()
.map_err(|_| Report::msg(format!("invalid function identifier: '{entry}'")))?;
if !package.is_library() {
return Err(Report::msg("cannot use --entrypoint with executable packages"));
}
package.make_executable(&id).map(Arc::new)
} else {
Ok(package)
}
}