1#![deny(warnings)]
2
3use alloc::{borrow::Cow, format, str::FromStr, sync::Arc, vec::Vec};
4#[cfg(feature = "std")]
5use alloc::{boxed::Box, string::ToString};
6use core::fmt;
7
8pub use miden_assembly::{
9 Library as CompiledLibrary, LibraryNamespace, LibraryPath, LibraryPathComponent,
10};
11#[cfg(feature = "std")]
12use miden_core::utils::Deserializable;
13use miden_stdlib::StdLibrary;
14use midenc_hir_symbol::sync::LazyLock;
15
16use crate::{diagnostics::Report, PathBuf, Session, TargetEnv};
17#[cfg(feature = "std")]
18use crate::{
19 diagnostics::{IntoDiagnostic, WrapErr},
20 Path,
21};
22
23pub static STDLIB: LazyLock<Arc<CompiledLibrary>> =
24 LazyLock::new(|| Arc::new(StdLibrary::default().into()));
25
26#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
28pub enum LibraryKind {
29 #[default]
31 Mast,
32 Masm,
34 Masp,
36}
37impl fmt::Display for LibraryKind {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match self {
40 Self::Mast => f.write_str("mast"),
41 Self::Masm => f.write_str("masm"),
42 Self::Masp => f.write_str("masp"),
43 }
44 }
45}
46impl FromStr for LibraryKind {
47 type Err = ();
48
49 fn from_str(s: &str) -> Result<Self, Self::Err> {
50 match s {
51 "mast" | "masl" => Ok(Self::Mast),
52 "masm" => Ok(Self::Masm),
53 "masp" => Ok(Self::Masp),
54 _ => Err(()),
55 }
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct LinkLibrary {
62 pub name: Cow<'static, str>,
69 pub path: Option<PathBuf>,
71 pub kind: LibraryKind,
76}
77impl LinkLibrary {
78 pub fn std() -> Self {
80 LinkLibrary {
81 name: "std".into(),
82 path: None,
83 kind: LibraryKind::Mast,
84 }
85 }
86
87 pub fn base() -> Self {
89 LinkLibrary {
90 name: "base".into(),
91 path: None,
92 kind: LibraryKind::Mast,
93 }
94 }
95
96 #[cfg(not(feature = "std"))]
97 pub fn load(&self, _session: &Session) -> Result<CompiledLibrary, Report> {
98 match self.name.as_ref() {
100 "std" => Ok((*STDLIB).as_ref().clone()),
101 "base" => Ok(miden_lib::MidenLib::default().as_ref().clone()),
102 name => Err(Report::msg(format!(
103 "link library '{name}' cannot be loaded: compiler was built without standard \
104 library"
105 ))),
106 }
107 }
108
109 #[cfg(feature = "std")]
110 pub fn load(&self, session: &Session) -> Result<CompiledLibrary, Report> {
111 if let Some(path) = self.path.as_deref() {
112 return self.load_from_path(path, session);
113 }
114
115 match self.name.as_ref() {
117 "std" => return Ok((*STDLIB).as_ref().clone()),
118 "base" => return Ok(miden_lib::MidenLib::default().as_ref().clone()),
119 _ => (),
120 }
121
122 let path = self.find(session)?;
124
125 self.load_from_path(&path, session)
126 }
127
128 #[cfg(feature = "std")]
129 fn load_from_path(&self, path: &Path, session: &Session) -> Result<CompiledLibrary, Report> {
130 match self.kind {
131 LibraryKind::Masm => {
132 let ns = LibraryNamespace::new(&self.name)
133 .into_diagnostic()
134 .wrap_err_with(|| format!("invalid library namespace '{}'", &self.name))?;
135 let assembler = miden_assembly::Assembler::new(session.source_manager.clone())
136 .with_debug_mode(true);
137 CompiledLibrary::from_dir(path, ns, assembler)
138 }
139 LibraryKind::Mast => CompiledLibrary::deserialize_from_file(path).map_err(|err| {
140 Report::msg(format!(
141 "failed to deserialize library from '{}': {err}",
142 path.display()
143 ))
144 }),
145 LibraryKind::Masp => {
146 let bytes = std::fs::read(path).into_diagnostic()?;
147 let package =
148 miden_mast_package::Package::read_from_bytes(&bytes).map_err(|e| {
149 Report::msg(format!(
150 "failed to load Miden package from {}: {e}",
151 path.display()
152 ))
153 })?;
154 let lib = match package.mast {
155 miden_mast_package::MastArtifact::Executable(_) => {
156 return Err(Report::msg(format!(
157 "Expected Miden package to contain a Library, got Program: '{}'",
158 path.display()
159 )))
160 }
161 miden_mast_package::MastArtifact::Library(lib) => lib.clone(),
162 };
163 Ok((*lib).clone())
164 }
165 }
166 }
167
168 #[cfg(feature = "std")]
169 fn find(&self, session: &Session) -> Result<PathBuf, Report> {
170 use std::fs;
171
172 for search_path in session.options.search_paths.iter() {
173 let reader = fs::read_dir(search_path).map_err(|err| {
174 Report::msg(format!(
175 "invalid library search path '{}': {err}",
176 search_path.display()
177 ))
178 })?;
179 for entry in reader {
180 let Ok(entry) = entry else {
181 continue;
182 };
183 let path = entry.path();
184 let Some(stem) = path.file_stem().and_then(|stem| stem.to_str()) else {
185 continue;
186 };
187 if stem != self.name.as_ref() {
188 continue;
189 }
190
191 match self.kind {
192 LibraryKind::Mast => {
193 if !path.is_file() {
194 return Err(Report::msg(format!(
195 "unable to load MAST library from '{}': not a file",
196 path.display()
197 )));
198 }
199 }
200 LibraryKind::Masm => {
201 if !path.is_dir() {
202 return Err(Report::msg(format!(
203 "unable to load Miden Assembly library from '{}': not a directory",
204 path.display()
205 )));
206 }
207 }
208 LibraryKind::Masp => {
209 if !path.is_file() {
210 return Err(Report::msg(format!(
211 "unable to load Miden Assembly package from '{}': not a file",
212 path.display()
213 )));
214 }
215 }
216 }
217 return Ok(path);
218 }
219 }
220
221 Err(Report::msg(format!(
222 "unable to locate library '{}' using any of the provided search paths",
223 &self.name
224 )))
225 }
226}
227
228#[cfg(feature = "std")]
229impl clap::builder::ValueParserFactory for LinkLibrary {
230 type Parser = LinkLibraryParser;
231
232 fn value_parser() -> Self::Parser {
233 LinkLibraryParser
234 }
235}
236
237#[cfg(feature = "std")]
238#[doc(hidden)]
239#[derive(Clone)]
240pub struct LinkLibraryParser;
241
242#[cfg(feature = "std")]
243impl clap::builder::TypedValueParser for LinkLibraryParser {
244 type Value = LinkLibrary;
245
246 fn possible_values(
247 &self,
248 ) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
249 use clap::builder::PossibleValue;
250
251 Some(Box::new(
252 [
253 PossibleValue::new("masm").help("A Miden Assembly project directory"),
254 PossibleValue::new("masl").help("A compiled MAST library file"),
255 ]
256 .into_iter(),
257 ))
258 }
259
260 fn parse_ref(
267 &self,
268 _cmd: &clap::Command,
269 _arg: Option<&clap::Arg>,
270 value: &std::ffi::OsStr,
271 ) -> Result<Self::Value, clap::error::Error> {
272 use clap::error::{Error, ErrorKind};
273
274 let value = value.to_str().ok_or_else(|| Error::new(ErrorKind::InvalidUtf8))?;
275 let (kind, name) = value
276 .split_once('=')
277 .map(|(kind, name)| (Some(kind), name))
278 .unwrap_or((None, value));
279
280 if name.is_empty() {
281 return Err(Error::raw(
282 ErrorKind::ValueValidation,
283 "invalid link library: must specify a name or path",
284 ));
285 }
286
287 let maybe_path = Path::new(name);
288 let extension = maybe_path.extension().map(|ext| ext.to_str().unwrap());
289 let kind = match kind {
290 Some(kind) if !kind.is_empty() => kind.parse::<LibraryKind>().map_err(|_| {
291 Error::raw(ErrorKind::InvalidValue, format!("'{kind}' is not a valid library kind"))
292 })?,
293 Some(_) | None => match extension {
294 Some(kind) => kind.parse::<LibraryKind>().map_err(|_| {
295 Error::raw(
296 ErrorKind::InvalidValue,
297 format!("'{kind}' is not a valid library kind"),
298 )
299 })?,
300 None => LibraryKind::default(),
301 },
302 };
303
304 if maybe_path.is_absolute() {
305 let meta = maybe_path.metadata().map_err(|err| {
306 Error::raw(
307 ErrorKind::ValueValidation,
308 format!(
309 "invalid link library: unable to load '{}': {err}",
310 maybe_path.display()
311 ),
312 )
313 })?;
314
315 match kind {
316 LibraryKind::Mast if !meta.is_file() => {
317 return Err(Error::raw(
318 ErrorKind::ValueValidation,
319 format!("invalid link library: '{}' is not a file", maybe_path.display()),
320 ));
321 }
322 LibraryKind::Masm if !meta.is_dir() => {
323 return Err(Error::raw(
324 ErrorKind::ValueValidation,
325 format!(
326 "invalid link library: kind 'masm' was specified, but '{}' is not a \
327 directory",
328 maybe_path.display()
329 ),
330 ));
331 }
332 _ => (),
333 }
334
335 let name = maybe_path.file_stem().unwrap().to_str().unwrap().to_string();
336
337 Ok(LinkLibrary {
338 name: name.into(),
339 path: Some(maybe_path.to_path_buf()),
340 kind,
341 })
342 } else if extension.is_some() {
343 let name = name.strip_suffix(unsafe { extension.unwrap_unchecked() }).unwrap();
344 let mut name = name.to_string();
345 name.pop();
346
347 Ok(LinkLibrary {
348 name: name.into(),
349 path: None,
350 kind,
351 })
352 } else {
353 Ok(LinkLibrary {
354 name: name.to_string().into(),
355 path: None,
356 kind,
357 })
358 }
359 }
360}
361
362pub fn add_target_link_libraries(
365 link_libraries_in: Vec<LinkLibrary>,
366 target: &TargetEnv,
367) -> Vec<LinkLibrary> {
368 let mut link_libraries_out = link_libraries_in;
369 match target {
370 TargetEnv::Base | TargetEnv::Emu => {
371 if !link_libraries_out.iter().any(|ll| ll.name == "std") {
372 link_libraries_out.push(LinkLibrary::std());
373 }
374 }
375 TargetEnv::Rollup { .. } => {
376 if !link_libraries_out.iter().any(|ll| ll.name == "std") {
377 link_libraries_out.push(LinkLibrary::std());
378 }
379
380 if !link_libraries_out.iter().any(|ll| ll.name == "base") {
381 link_libraries_out.push(LinkLibrary::base());
382 }
383 }
384 }
385 link_libraries_out
386}