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