Skip to main content

miden_debug_engine/
registry.rs

1use alloc::{collections::BTreeMap, sync::Arc};
2#[cfg(feature = "std")]
3use std::path::{Path, PathBuf};
4
5use miden_assembly_syntax::{
6    Report,
7    diagnostics::{Diagnostic, miette},
8};
9use miden_mast_package::Package;
10use miden_package_registry::{
11    PackageCache, PackageId, PackageIndex, PackageProvider, PackageRecord, PackageRegistry,
12    PackageStore, PackageVersions, VersionRequirement,
13};
14
15type FxHashMap<K, V> = hashbrown::HashMap<K, V, rustc_hash::FxBuildHasher>;
16
17#[cfg(feature = "std")]
18use crate::LinkLibrary;
19
20#[derive(Debug, thiserror::Error, Diagnostic)]
21enum InstallPackageError {
22    #[error("package {package}@{version} is already registered under a different digest")]
23    AlreadyInstalledWithDifferentDigest {
24        package: PackageId,
25        version: miden_package_registry::Version,
26    },
27}
28
29/// The in-memory package registry used during debugger execution.
30///
31/// This is initialized per-session, or on an as-needed basis.
32///
33/// It can be constructed in various ways, but the recommended way to use it is
34/// [HybridPackageRegistry::new], which loads packages from the local filesystem registry (if
35/// available), and adds in any libraries requested explicitly via `-l`.
36pub struct HybridPackageRegistry {
37    packages: FxHashMap<PackageId, PackageVersions>,
38    artifacts: FxHashMap<PackageId, BTreeMap<miden_package_registry::Version, Arc<Package>>>,
39}
40
41impl HybridPackageRegistry {
42    /// Get an empty, uninitialized registry
43    pub fn empty() -> Self {
44        Self {
45            packages: Default::default(),
46            artifacts: Default::default(),
47        }
48    }
49
50    /// Get a new instance of the registry from compiled package inputs.
51    #[cfg(feature = "std")]
52    pub fn new(
53        sysroot: Option<&Path>,
54        search_paths: &[PathBuf],
55        link_libraries: &[LinkLibrary],
56    ) -> Result<Self, Report> {
57        // Load system libraries
58        let mut registry = if let Some(sysroot) = sysroot {
59            Self::from_local_registry(sysroot)?
60        } else {
61            Self::empty()
62        };
63
64        // Load link libraries
65        for lib in link_libraries {
66            let package = lib.load(search_paths)?;
67            match registry.install_if_missing(package) {
68                Ok(_) => (),
69                // Ignore duplicates when initializing the registry
70                Err(InstallPackageError::AlreadyInstalledWithDifferentDigest { .. }) => (),
71            }
72        }
73
74        Ok(registry)
75    }
76
77    /// Get a new instance of the registry seeded with packages available in the local filesystem-
78    /// based package store.
79    ///
80    /// This returns an error if `--sysroot` was not provided/set.
81    #[cfg(feature = "std")]
82    pub fn from_local_registry(sysroot: &Path) -> Result<Self, Report> {
83        let lib_dir = sysroot.join("lib");
84        let entries = lib_dir.read_dir().map_err(|err| {
85            Report::msg(format!("cannot read from sysroot ({}): {err}", lib_dir.display()))
86        })?;
87
88        let mut registry = Self::empty();
89        for entry in entries {
90            let Ok(entry) = entry else {
91                continue;
92            };
93            let path = entry.path();
94            if path.extension().is_none_or(|ext| !ext.eq_ignore_ascii_case("masp")) {
95                continue;
96            }
97
98            let package = crate::package::load_package_from_path(&path)?;
99            match registry.install_if_missing(package) {
100                Ok(_) => (),
101                // Ignore duplicates when initializing the registry
102                Err(InstallPackageError::AlreadyInstalledWithDifferentDigest { .. }) => (),
103            }
104        }
105
106        Ok(registry)
107    }
108
109    pub fn all(&self) -> impl IntoIterator<Item = Arc<Package>> {
110        self.artifacts.values().flat_map(|versions| versions.values().cloned())
111    }
112
113    fn install_if_missing(
114        &mut self,
115        package: Arc<Package>,
116    ) -> Result<miden_package_registry::Version, InstallPackageError> {
117        use alloc::collections::btree_map::Entry as BTreeMapEntry;
118
119        use hashbrown::hash_map::Entry;
120
121        let version = miden_package_registry::Version::new(
122            package.version.clone(),
123            package.dependency_commitment(),
124        );
125        log::trace!(target: "package-registry", "preparing to install package {}@{version}", package.name);
126        let record = PackageRecord::new(
127            version.clone(),
128            package.manifest.dependencies().map(|dep| {
129                (
130                    dep.name.clone(),
131                    VersionRequirement::Exact(miden_package_registry::Version::new(
132                        dep.version.clone(),
133                        dep.digest,
134                    )),
135                )
136            }),
137        );
138        match self.packages.entry(package.name.clone()) {
139            Entry::Occupied(mut entry) => {
140                let versions = entry.get_mut();
141                match versions.entry(package.version.clone()) {
142                    BTreeMapEntry::Occupied(mut prev) => {
143                        let prev_digest = prev.get().digest().copied();
144                        if prev_digest.is_none_or(|prev_digest| {
145                            prev_digest == package.dependency_commitment()
146                        }) {
147                            prev.insert(record);
148                        } else {
149                            log::trace!(target: "package-registry", "package already installed: {}@{version}", package.name);
150                            return Err(InstallPackageError::AlreadyInstalledWithDifferentDigest {
151                                package: package.name.clone(),
152                                version,
153                            });
154                        }
155                    }
156                    BTreeMapEntry::Vacant(entry) => {
157                        entry.insert(record);
158                    }
159                }
160            }
161            Entry::Vacant(entry) => {
162                entry.insert([(package.version.clone(), record)].into_iter().collect());
163            }
164        }
165
166        log::trace!(target: "package-registry", "installed {}@{version}", package.name);
167
168        self.artifacts
169            .entry(package.name.clone())
170            .or_default()
171            .insert(version.clone(), package);
172
173        Ok(version)
174    }
175}
176
177impl HybridPackageRegistry {
178    fn insert_record(&mut self, id: PackageId, record: PackageRecord) {
179        self.packages
180            .entry(id)
181            .or_default()
182            .insert(record.semantic_version().clone(), record);
183    }
184}
185
186impl PackageRegistry for HybridPackageRegistry {
187    fn available_versions(&self, package: &PackageId) -> Option<&PackageVersions> {
188        self.packages.get(package)
189    }
190}
191
192impl PackageIndex for HybridPackageRegistry {
193    type Error = Report;
194
195    fn register(&mut self, name: PackageId, record: PackageRecord) -> Result<(), Self::Error> {
196        if self.is_semver_available(&name, record.semantic_version()) {
197            return Err(Report::msg(format!(
198                "cannot register {name}: version {} is already registered",
199                record.semantic_version()
200            )));
201        }
202        self.insert_record(name, record);
203        Ok(())
204    }
205}
206
207impl PackageProvider for HybridPackageRegistry {
208    fn load_package(
209        &self,
210        package: &PackageId,
211        version: &miden_package_registry::Version,
212    ) -> Result<Arc<Package>, Report> {
213        let found = self.artifacts.get(package).and_then(|versions| versions.get(&version.version));
214        match found {
215            Some(artifact) if version.digest != Some(artifact.dependency_commitment()) => {
216                Err(Report::msg(format!(
217                    "cannot load {package}@{version}: a specific digest was requested, but \
218                     differs from the available version"
219                )))
220            }
221            Some(artifact) => Ok(Arc::clone(artifact)),
222            None => Err(Report::msg(format!(
223                "cannot load {package}@{version}: no such package available",
224            ))),
225        }
226    }
227}
228
229impl PackageCache for HybridPackageRegistry {
230    type Error = Report;
231
232    fn cache_package(
233        &mut self,
234        package: Arc<Package>,
235    ) -> Result<miden_package_registry::Version, Self::Error> {
236        self.install_if_missing(package).map_err(Report::from)
237    }
238}
239
240impl PackageStore for HybridPackageRegistry {
241    fn publish_package(
242        &mut self,
243        package: Arc<Package>,
244    ) -> Result<miden_package_registry::Version, Self::Error> {
245        self.install_if_missing(package).map_err(Report::from)
246    }
247}