Skip to main content

midenc_session/
libs.rs

1use alloc::{borrow::Cow, format, sync::Arc, vec::Vec};
2#[cfg(feature = "std")]
3use alloc::{boxed::Box, string::ToString};
4
5pub use miden_assembly_syntax::{PathBuf as LibraryPath, PathComponent as LibraryPathComponent};
6use miden_core_lib::CoreLibrary;
7#[cfg(feature = "std")]
8use miden_mast_package::Package;
9use miden_project::Linkage;
10#[cfg(not(feature = "std"))]
11use smallvec::SmallVec;
12
13#[cfg(feature = "std")]
14use crate::{Options, Path, diagnostics::IntoDiagnostic};
15use crate::{PathBuf, diagnostics::Report};
16
17/// A library requested by the user to be linked against during compilation
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct LinkLibrary {
20    /// The name of the library.
21    ///
22    /// If requested by name, e.g. `-l std`, the name is used as given.
23    ///
24    /// If requested by path, e.g. `-l ./target/libs/miden-base.masl`, then the name of the library
25    /// will be the basename of the file specified in the path.
26    pub name: Cow<'static, str>,
27    /// If specified, the path from which this library should be loaded
28    pub path: Option<PathBuf>,
29    /// How to link against this library
30    pub linkage: Linkage,
31}
32
33impl LinkLibrary {
34    pub fn is_core(&self) -> bool {
35        matches!(self.name.as_ref(), "miden-core" | "core" | "std")
36    }
37
38    pub fn is_protocol(&self) -> bool {
39        matches!(self.name.as_ref(), "miden-protocol" | "protocol" | "base")
40    }
41
42    /// Construct a LinkLibrary for Miden stdlib
43    pub fn core() -> Self {
44        LinkLibrary {
45            name: "miden-core".into(),
46            path: None,
47            linkage: Linkage::Dynamic,
48        }
49    }
50
51    /// Construct a LinkLibrary for the Miden precompiles library, a dependency of the core
52    /// library
53    pub fn precompiles() -> Self {
54        LinkLibrary {
55            name: "miden-precompiles".into(),
56            path: None,
57            linkage: Linkage::Dynamic,
58        }
59    }
60
61    /// Construct a LinkLibrary for the Miden transaction kernel library
62    pub fn tx_kernel() -> Self {
63        LinkLibrary {
64            name: "miden-tx-kernel".into(),
65            path: None,
66            linkage: Linkage::Dynamic,
67        }
68    }
69
70    /// Construct a LinkLibrary for Miden protocol library (userspace)
71    pub fn protocol() -> Self {
72        LinkLibrary {
73            name: "miden-protocol".into(),
74            path: None,
75            linkage: Linkage::Dynamic,
76        }
77    }
78
79    #[cfg(not(feature = "std"))]
80    pub fn load(&self, _options: &Options) -> Result<Arc<Package>, Report> {
81        // Handle libraries shipped with the compiler, or via Miden crates
82        match self.name.as_ref() {
83            "std" | "core" | "miden-core" => {
84                return Ok(CoreLibrary::default().package());
85            }
86            "precompiles" | "miden-precompiles" => {
87                return Ok(CoreLibrary::default().precompiles_package());
88            }
89            "base" | "protocol" | "miden-protocol" => {
90                return Ok(miden_protocol::ProtocolLib::default().package());
91            }
92            "tx-kernel" | "miden-tx-kernel" => {
93                return Ok(miden_protocol::transaction::TransactionKernel::package());
94            }
95            name => Err(Report::msg(format!(
96                "link library '{name}' cannot be loaded: compiler was built without standard \
97                 library"
98            ))),
99        }
100    }
101
102    #[cfg(feature = "std")]
103    pub fn load(&self, options: &Options) -> Result<Arc<Package>, Report> {
104        if let Some(path) = self.path.as_deref() {
105            return self.load_from_path(path, options);
106        }
107
108        // Handle libraries shipped with the compiler, or via Miden crates
109        match self.name.as_ref() {
110            "std" | "core" | "miden-core" => {
111                return Ok(CoreLibrary::default().package());
112            }
113            "precompiles" | "miden-precompiles" => {
114                return Ok(CoreLibrary::default().precompiles_package());
115            }
116            "base" | "protocol" | "miden-protocol" => {
117                return Ok(miden_protocol::ProtocolLib::default().package());
118            }
119            "tx-kernel" | "miden-tx-kernel" => {
120                return Ok(miden_protocol::transaction::TransactionKernel::package());
121            }
122            _ => (),
123        }
124
125        // Search for library among specified search paths
126        let path = self.find(options)?;
127
128        self.load_from_path(&path, options)
129    }
130
131    #[cfg(feature = "std")]
132    fn load_from_path(&self, path: &Path, _options: &Options) -> Result<Arc<Package>, Report> {
133        let package = load_package_from_path(path)?;
134        if package.is_program() {
135            return Err(Report::msg(format!(
136                "Expected Miden package to contain a Library, got Program: '{}'",
137                path.display()
138            )));
139        }
140
141        Ok(package)
142    }
143
144    #[cfg(feature = "std")]
145    fn find(&self, options: &Options) -> Result<PathBuf, Report> {
146        use std::fs;
147
148        for search_path in options.search_paths.iter() {
149            let reader = fs::read_dir(search_path).map_err(|err| {
150                Report::msg(format!(
151                    "invalid library search path '{}': {err}",
152                    search_path.display()
153                ))
154            })?;
155            for entry in reader {
156                let Ok(entry) = entry else {
157                    continue;
158                };
159                let path = entry.path();
160                let Some(stem) = path.file_stem().and_then(|stem| stem.to_str()) else {
161                    continue;
162                };
163                if stem != self.name.as_ref() {
164                    continue;
165                }
166
167                if !path.is_file() {
168                    return Err(Report::msg(format!(
169                        "unable to load Miden Assembly package from '{}': not a file",
170                        path.display()
171                    )));
172                }
173                return Ok(path);
174            }
175        }
176
177        Err(Report::msg(format!(
178            "unable to locate library '{}' using any of the provided search paths",
179            self.name
180        )))
181    }
182}
183
184#[cfg(feature = "std")]
185pub(crate) fn load_package_from_path(path: &Path) -> Result<Arc<Package>, Report> {
186    let bytes = std::fs::read(path).into_diagnostic()?;
187    miden_mast_package::Package::read_from_bytes_unchecked(&bytes)
188        .map_err(|e| {
189            Report::msg(format!("failed to load Miden package from {}: {e}", path.display()))
190        })
191        .map(Arc::new)
192}
193
194#[cfg(feature = "std")]
195impl clap::builder::ValueParserFactory for LinkLibrary {
196    type Parser = LinkLibraryParser;
197
198    fn value_parser() -> Self::Parser {
199        LinkLibraryParser
200    }
201}
202
203#[cfg(feature = "std")]
204#[doc(hidden)]
205#[derive(Clone)]
206pub struct LinkLibraryParser;
207
208#[cfg(feature = "std")]
209impl clap::builder::TypedValueParser for LinkLibraryParser {
210    type Value = LinkLibrary;
211
212    fn possible_values(
213        &self,
214    ) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
215        use clap::builder::PossibleValue;
216
217        Some(Box::new(
218            [PossibleValue::new("masp").help("A compiled Miden package")].into_iter(),
219        ))
220    }
221
222    /// Parses the `-l` flag using the following format:
223    ///
224    /// `-l[KIND[:<LINKAGE>]=]NAME`
225    ///
226    /// * `KIND` is one of: `masp`; defaults to `masp`
227    /// * `LINKAGE` is one of: `static`, `dynamic`; defaults to `dynamic`
228    /// * `NAME` is either an absolute path, or a name (without extension)
229    fn parse_ref(
230        &self,
231        _cmd: &clap::Command,
232        _arg: Option<&clap::Arg>,
233        value: &std::ffi::OsStr,
234    ) -> Result<Self::Value, clap::error::Error> {
235        use clap::error::{Error, ErrorKind};
236
237        let value = value.to_str().ok_or_else(|| Error::new(ErrorKind::InvalidUtf8))?;
238        let (kind, name) = value
239            .split_once('=')
240            .map(|(kind, name)| (Some(kind), name))
241            .unwrap_or((None, value));
242
243        let linkage = match kind {
244            Some(kind) => match kind.split_once(':') {
245                Some(("masp", "static")) => Linkage::Static,
246                Some(("masp", "dynamic")) => Linkage::Dynamic,
247                Some(("masp", other)) => {
248                    return Err(Error::raw(
249                        ErrorKind::ValueValidation,
250                        format!("unrecognized linkage modifier '{other}'"),
251                    ));
252                }
253                None if kind == "masp" => Linkage::Dynamic,
254                Some(_) | None => {
255                    return Err(Error::raw(
256                        ErrorKind::ValueValidation,
257                        "invalid link library kind: supported values are 'masp'",
258                    ));
259                }
260            },
261            None => Linkage::Dynamic,
262        };
263
264        if name.is_empty() {
265            return Err(Error::raw(
266                ErrorKind::ValueValidation,
267                "invalid link library: must specify a name or path",
268            ));
269        }
270
271        let maybe_path = Path::new(name);
272        let extension = maybe_path.extension().map(|ext| ext.to_str().unwrap());
273
274        if maybe_path.is_absolute() {
275            let meta = maybe_path.metadata().map_err(|err| {
276                Error::raw(
277                    ErrorKind::ValueValidation,
278                    format!(
279                        "invalid link library: unable to load '{}': {err}",
280                        maybe_path.display()
281                    ),
282                )
283            })?;
284
285            if !meta.is_file() {
286                return Err(Error::raw(
287                    ErrorKind::ValueValidation,
288                    format!("invalid link library: '{}' is not a file", maybe_path.display()),
289                ));
290            }
291
292            let name = maybe_path.file_stem().unwrap().to_str().unwrap().to_string();
293
294            Ok(LinkLibrary {
295                name: name.into(),
296                path: Some(maybe_path.to_path_buf()),
297                linkage,
298            })
299        } else if extension.is_some() {
300            let name = name.strip_suffix(unsafe { extension.unwrap_unchecked() }).unwrap();
301            let mut name = name.to_string();
302            name.pop();
303
304            Ok(LinkLibrary {
305                name: name.into(),
306                path: None,
307                linkage,
308            })
309        } else {
310            Ok(LinkLibrary {
311                name: name.to_string().into(),
312                path: None,
313                linkage,
314            })
315        }
316    }
317}
318
319/// Add libraries required by the target environment to the list of libraries to link against only
320/// if they are not already present.
321pub fn add_target_link_libraries(link_libraries: &mut Vec<LinkLibrary>, requires_protocol: bool) {
322    if !link_libraries.iter().any(LinkLibrary::is_core) {
323        link_libraries.push(LinkLibrary::core());
324    }
325    if requires_protocol && !link_libraries.iter().any(LinkLibrary::is_protocol) {
326        link_libraries.push(LinkLibrary::protocol());
327    }
328}