use std::string::ToString;
use std::sync::Arc;
use miden_processor::advice::AdviceInputs;
use miden_processor::{
ExecutionError,
ExecutionOptions,
ExecutionOutput,
FutureMaybeSend,
Host,
Program,
StackInputs,
};
use miden_protocol::assembly::{Path, ProcedureName};
use miden_protocol::transaction::TransactionKernel;
use miden_protocol::utils::serde::Serializable;
use miden_protocol::vm::{
DebugSourceNodeId,
Package,
PackageDebugInfo,
PackageDebugInfoError,
PackageExport,
ProcedureExport,
Section,
SectionId,
TargetType,
};
use miden_tx::ProgramExecutor;
pub struct DapProgramExecutor(miden_debug::DapExecutor);
impl DapProgramExecutor {
fn execute_package<H: Host + Send>(
self,
package: Result<Arc<Package>, ExecutionError>,
host: &mut H,
) -> impl FutureMaybeSend<Result<ExecutionOutput, ExecutionError>> {
async move {
let package = package?;
self.0.execute_async(package, host).await
}
}
}
impl ProgramExecutor for DapProgramExecutor {
fn new(
stack_inputs: StackInputs,
advice_inputs: AdviceInputs,
options: ExecutionOptions,
) -> Self {
Self(miden_debug::DapExecutor::new(stack_inputs, advice_inputs, options))
}
fn execute<H: Host + Send>(
self,
program: &Program,
host: &mut H,
) -> impl FutureMaybeSend<Result<ExecutionOutput, ExecutionError>> {
let package = build_dap_package(program, &PackageDebugInfo::default(), None);
self.execute_package(package, host)
}
fn execute_with_package_debug_info<H: Host + Send>(
self,
program: &Program,
package_debug_info: &PackageDebugInfo,
entrypoint_source_node: Option<DebugSourceNodeId>,
host: &mut H,
) -> impl FutureMaybeSend<Result<ExecutionOutput, ExecutionError>> {
let package = build_dap_package(program, package_debug_info, entrypoint_source_node);
self.execute_package(package, host)
}
}
fn build_dap_package(
program: &Program,
package_debug_info: &PackageDebugInfo,
entrypoint_source_node: Option<DebugSourceNodeId>,
) -> Result<Arc<Package>, ExecutionError> {
let entrypoint: Arc<Path> = Path::exec_path().join(ProcedureName::MAIN_PROC_NAME).into();
let export =
ProcedureExport::new(entrypoint.clone(), Some(program.entrypoint()), program.hash(), None)
.with_source_node(entrypoint_source_node);
let kernel = TransactionKernel::package();
let mut package = Package::create(
"miden-client-debug".into(),
kernel.version.clone(),
TargetType::Executable,
program.mast_forest().clone(),
[PackageExport::Procedure(export)],
[kernel.to_dependency()],
)
.map_err(|error| dap_package_construction_error(&error))?;
package.sections.push(Section::new(SectionId::KERNEL, kernel.to_bytes()));
package
.sections
.push(Section::new(SectionId::DEBUG_INFO, package_debug_info.to_bytes()));
package.debug_info()?;
Ok(Arc::new(package))
}
fn dap_package_construction_error(error: &impl ToString) -> ExecutionError {
PackageDebugInfoError::InvalidReference {
message: format!("failed to construct DAP executable package: {}", error.to_string()),
}
.into()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn debug_packages_preserve_transaction_programs() {
let programs = [
(
TransactionKernel::main(),
TransactionKernel::main_debug_info().unwrap_or_default(),
TransactionKernel::main_entrypoint_source_node(),
),
(
TransactionKernel::tx_script_main(),
TransactionKernel::tx_script_main_debug_info().unwrap_or_default(),
TransactionKernel::tx_script_main_entrypoint_source_node(),
),
];
for (program, debug_info, entrypoint_source_node) in programs {
let package = build_dap_package(&program, &debug_info, entrypoint_source_node)
.expect("failed to construct debug package");
assert_eq!(package.try_into_program().unwrap(), program);
assert_eq!(package.debug_info().unwrap().unwrap_or_default(), *debug_info);
assert_eq!(
package
.sections
.iter()
.filter(|section| section.id == SectionId::DEBUG_INFO)
.count(),
1
);
}
}
}