1use alloc::{boxed::Box, collections::BTreeSet, string::ToString, sync::Arc};
2
3use miden_assembly_syntax::{
4 ast::{
5 Alias, AliasTarget, InvocationTarget, InvokeKind, Path, SymbolResolution,
6 SymbolResolutionError,
7 },
8 debuginfo::{SourceManager, SourceSpan, Span, Spanned},
9};
10use miden_core::Word;
11
12use crate::{GlobalItemIndex, LinkerError, ModuleIndex, linker::Linker};
13
14#[derive(Debug, Clone)]
22pub struct SymbolResolutionContext {
23 pub span: SourceSpan,
25 pub module: ModuleIndex,
27 pub kind: Option<InvokeKind>,
33}
34
35impl SymbolResolutionContext {
36 #[inline]
37 pub fn in_syscall(&self) -> bool {
38 matches!(self.kind, Some(InvokeKind::SysCall))
39 }
40}
41
42pub struct SymbolResolver<'a> {
56 graph: &'a Linker,
58}
59
60impl<'a> SymbolResolver<'a> {
61 pub fn new(graph: &'a Linker) -> Self {
63 Self { graph }
64 }
65
66 #[inline(always)]
67 pub fn source_manager(&self) -> &dyn SourceManager {
68 &self.graph.source_manager
69 }
70
71 #[inline(always)]
72 pub fn source_manager_arc(&self) -> Arc<dyn SourceManager> {
73 self.graph.source_manager.clone()
74 }
75
76 #[inline(always)]
77 pub(crate) fn linker(&self) -> &Linker {
78 self.graph
79 }
80
81 pub fn resolve_invoke_target(
84 &self,
85 context: &SymbolResolutionContext,
86 target: &InvocationTarget,
87 ) -> Result<SymbolResolution, LinkerError> {
88 let resolution = match target {
89 InvocationTarget::MastRoot(mast_root) => {
90 log::debug!(target: "name-resolver::invoke", "resolving {target}");
91 self.validate_syscall_digest(context, *mast_root)?;
92 match self.graph.get_procedure_index_by_digest(mast_root) {
93 None => Ok(SymbolResolution::MastRoot(*mast_root)),
94 Some(gid) if context.in_syscall() => {
95 if self.graph.kernel_index.is_some_and(|k| k == gid.module) {
96 Ok(SymbolResolution::Exact {
97 gid,
98 path: Span::new(mast_root.span(), self.item_path(gid)),
99 })
100 } else {
101 Err(LinkerError::InvalidSysCallTarget {
102 span: context.span,
103 source_file: self
104 .source_manager()
105 .get(context.span.source_id())
106 .ok(),
107 callee: self.item_path(gid),
108 })
109 }
110 },
111 Some(gid) => Ok(SymbolResolution::Exact {
112 gid,
113 path: Span::new(mast_root.span(), self.item_path(gid)),
114 }),
115 }
116 },
117 InvocationTarget::Symbol(symbol) => {
118 let path = Path::from_ident(symbol);
119 let mut context = context.clone();
120 if context.in_syscall() {
122 if let Some(kernel) = self.graph.kernel_index {
123 context.module = kernel;
124 } else {
125 return Err(LinkerError::InvalidSysCallTarget {
126 span: context.span,
127 source_file: self.source_manager().get(context.span.source_id()).ok(),
128 callee: Path::from_ident(symbol).into_owned().into(),
129 });
130 }
131 }
132 match self.resolve_path(&context, Span::new(symbol.span(), path.as_ref()))? {
133 SymbolResolution::Module { id: _, path: module_path } => {
134 Err(LinkerError::InvalidInvokeTarget {
135 span: symbol.span(),
136 source_file: self
137 .graph
138 .source_manager
139 .get(symbol.span().source_id())
140 .ok(),
141 path: module_path.into_inner(),
142 })
143 },
144 resolution => Ok(resolution),
145 }
146 },
147 InvocationTarget::Path(path) => match self.resolve_path(context, path.as_deref())? {
148 SymbolResolution::Module { id: _, path: module_path } => {
149 Err(LinkerError::InvalidInvokeTarget {
150 span: path.span(),
151 source_file: self.graph.source_manager.get(path.span().source_id()).ok(),
152 path: module_path.into_inner(),
153 })
154 },
155 SymbolResolution::Exact { gid, path } if context.in_syscall() => {
156 if self.graph.kernel_index.is_some_and(|k| k == gid.module) {
157 Ok(SymbolResolution::Exact { gid, path })
158 } else {
159 Err(LinkerError::InvalidSysCallTarget {
160 span: context.span,
161 source_file: self.source_manager().get(context.span.source_id()).ok(),
162 callee: path.into_inner(),
163 })
164 }
165 },
166 SymbolResolution::MastRoot(mast_root) => {
167 self.validate_syscall_digest(context, mast_root)?;
168 match self.graph.get_procedure_index_by_digest(&mast_root) {
169 None => Ok(SymbolResolution::MastRoot(mast_root)),
170 Some(gid) if context.in_syscall() => {
171 if self.graph.kernel_index.is_some_and(|k| k == gid.module) {
172 Ok(SymbolResolution::Exact {
173 gid,
174 path: Span::new(mast_root.span(), self.item_path(gid)),
175 })
176 } else {
177 Err(LinkerError::InvalidSysCallTarget {
178 span: context.span,
179 source_file: self
180 .source_manager()
181 .get(context.span.source_id())
182 .ok(),
183 callee: self.item_path(gid),
184 })
185 }
186 },
187 Some(gid) => Ok(SymbolResolution::Exact {
188 gid,
189 path: Span::new(mast_root.span(), self.item_path(gid)),
190 }),
191 }
192 },
193 resolution => Ok(resolution),
197 },
198 }?;
199 self.enforce_kernel_export_syscall_only(context, target, resolution)
200 }
201
202 fn enforce_kernel_export_syscall_only(
203 &self,
204 context: &SymbolResolutionContext,
205 target: &InvocationTarget,
206 resolution: SymbolResolution,
207 ) -> Result<SymbolResolution, LinkerError> {
208 if matches!(target, InvocationTarget::MastRoot(_)) {
209 return Ok(resolution);
210 }
211 if let SymbolResolution::Exact { gid, ref path } = resolution
212 && context.kind.is_some()
213 && !context.in_syscall()
214 {
215 let target_is_kernel = self.graph.kernel_index.is_some_and(|ki| ki == gid.module)
218 || self.graph[gid.module].kind().is_kernel();
219 let caller_is_kernel = self.graph.kernel_index.is_some_and(|ki| ki == context.module)
220 || self.graph[context.module].kind().is_kernel();
221 if target_is_kernel && !caller_is_kernel {
222 return Err(LinkerError::KernelProcNotSyscall {
223 span: context.span,
224 source_file: self.graph.source_manager.get(context.span.source_id()).ok(),
225 callee: path.clone().into_inner(),
226 });
227 }
228 }
229 Ok(resolution)
230 }
231
232 fn validate_syscall_digest(
233 &self,
234 context: &SymbolResolutionContext,
235 mast_root: Span<Word>,
236 ) -> Result<(), LinkerError> {
237 if !context.in_syscall() {
238 return Ok(());
239 }
240 if !self.graph.has_nonempty_kernel() {
242 return Err(LinkerError::InvalidSysCallTarget {
243 span: context.span,
244 source_file: self.source_manager().get(context.span.source_id()).ok(),
245 callee: Arc::<Path>::from(Path::new("syscall")),
246 });
247 }
248 if !self.graph.kernel().contains_proc(*mast_root.inner()) {
250 let digest_path = format!("{mast_root}");
251 return Err(LinkerError::InvalidSysCallTarget {
252 span: context.span,
253 source_file: self.source_manager().get(context.span.source_id()).ok(),
254 callee: Arc::<Path>::from(Path::new(&digest_path)),
255 });
256 }
257 Ok(())
258 }
259
260 pub fn resolve_alias_target(
263 &self,
264 context: &SymbolResolutionContext,
265 alias: &Alias,
266 ) -> Result<SymbolResolution, LinkerError> {
267 match alias.target() {
268 target @ AliasTarget::MastRoot(mast_root) => {
269 log::debug!(target: "name-resolver::alias", "resolving alias target {target}");
270 match self.graph.get_procedure_index_by_digest(mast_root) {
271 None => Ok(SymbolResolution::MastRoot(*mast_root)),
272 Some(gid) => Ok(SymbolResolution::Exact {
273 gid,
274 path: Span::new(mast_root.span(), self.item_path(gid)),
275 }),
276 }
277 },
278 AliasTarget::Path(path) => {
279 log::debug!(target: "name-resolver::alias", "resolving alias target '{path}'");
280 let mut ignored_imports = BTreeSet::from_iter([alias.name().clone().into_inner()]);
284 self.expand_path(context, path.as_deref(), &mut ignored_imports)
285 },
286 }
287 }
288
289 pub fn resolve_path(
290 &self,
291 context: &SymbolResolutionContext,
292 path: Span<&Path>,
293 ) -> Result<SymbolResolution, LinkerError> {
294 let mut ignored_imports = BTreeSet::default();
295 self.expand_path(context, path, &mut ignored_imports)
296 }
297
298 fn expand_path(
299 &self,
300 context: &SymbolResolutionContext,
301 path: Span<&Path>,
302 ignored_imports: &mut BTreeSet<Arc<str>>,
303 ) -> Result<SymbolResolution, LinkerError> {
304 self.expand_path_from(context.module, context, path, ignored_imports)
305 }
306
307 fn expand_path_from(
308 &self,
309 origin_module: ModuleIndex,
310 context: &SymbolResolutionContext,
311 path: Span<&Path>,
312 ignored_imports: &mut BTreeSet<Arc<str>>,
313 ) -> Result<SymbolResolution, LinkerError> {
314 let span = path.span();
315 let mut path = path.into_inner();
316 let mut context = context.clone();
317 loop {
318 log::debug!(target: "name-resolver::expand", "expanding path '{path}' (absolute = {})", path.is_absolute());
319 if path.is_absolute() {
320 let mut longest_prefix: Option<(ModuleIndex, Arc<Path>)> = None;
333 for module in self.graph.modules.iter() {
334 let module_path = module.path().clone();
335 if path == &*module_path {
336 log::debug!(target: "name-resolver::expand", "found exact match for '{path}': id={}", module.id());
337 return Ok(SymbolResolution::Module {
338 id: module.id(),
339 path: Span::new(span, module_path),
340 });
341 }
342
343 if path.starts_with_exactly(module_path.as_ref()) {
344 if let Some((_, prev)) = longest_prefix.as_ref() {
345 let prev_len = prev.components().count();
346 let module_len = module_path.components().count();
347 if prev_len < module_len {
348 longest_prefix = Some((module.id(), module_path));
349 } else if prev_len == module_len && prev != &module_path {
350 return Err(LinkerError::AmbiguousModulePath {
351 span,
352 source_file: self.source_manager().get(span.source_id()).ok(),
353 path: path.to_path_buf().into_boxed_path().into(),
354 matches:
355 alloc::vec![prev.to_string(), module_path.to_string(),]
356 .into_boxed_slice(),
357 });
358 }
359 } else {
360 longest_prefix = Some((module.id(), module_path));
361 }
362 }
363 }
364
365 match longest_prefix {
366 Some((module_id, module_path)) => {
370 log::trace!(target: "name-resolver::expand", "found prefix match for '{path}': id={module_id}, prefix={module_path}");
371 let subpath = path.strip_prefix(&module_path).unwrap();
372 context.module = module_id;
373 ignored_imports.clear();
374 path = subpath;
375 },
376 None => {
378 log::trace!(target: "name-resolver::expand", "no prefix match found for '{path}' - path is undefined symbol reference");
379 break Err(
380 SymbolResolutionError::undefined(span, self.source_manager()).into()
381 );
382 },
383 }
384 } else if let Some(symbol) = path.as_ident() {
385 break match self
395 .resolve_local_with_index(context.module, Span::new(span, symbol.as_str()))?
396 {
397 SymbolResolution::Local(item) => {
398 log::trace!(target: "name-resolver::expand", "resolved '{symbol}' to local symbol: {}", context.module + item.into_inner());
399 let gid = context.module + item.into_inner();
400 self.ensure_item_visible(origin_module, gid, span)?;
401 let path = self.module_path(context.module).join(&symbol);
402 Ok(SymbolResolution::Exact { gid, path: Span::new(span, path.into()) })
403 },
404 SymbolResolution::External(path) => {
405 log::trace!(target: "name-resolver::expand", "expanded '{symbol}' to unresolved external path '{path}'");
406 self.expand_path_from(
407 origin_module,
408 &context,
409 path.as_deref(),
410 ignored_imports,
411 )
412 },
413 resolved @ SymbolResolution::MastRoot(_) => Ok(resolved),
414 SymbolResolution::Exact { gid, path } => {
415 log::trace!(target: "name-resolver::expand", "resolved '{symbol}' to exact definition");
416 self.ensure_item_visible(origin_module, gid, span)?;
417 Ok(SymbolResolution::Exact { gid, path })
418 },
419 SymbolResolution::Module { id, path: module_path } => {
420 log::trace!(target: "name-resolver::expand", "resolved '{symbol}' to module: id={id} path={module_path}");
421 Ok(SymbolResolution::Module { id, path: module_path })
422 },
423 };
424 } else {
425 let (imported_symbol, subpath) = path.split_first().expect("multi-component path");
453 if ignored_imports.contains(imported_symbol) {
454 log::trace!(target: "name-resolver::expand", "skipping import expansion of '{imported_symbol}': already expanded, resolving as absolute path instead");
455 let path = path.to_absolute();
456 break self.expand_path_from(
457 origin_module,
458 &context,
459 Span::new(span, path.as_ref()),
460 ignored_imports,
461 );
462 }
463 match self
464 .resolve_local_with_index(context.module, Span::new(span, imported_symbol))
465 {
466 Ok(SymbolResolution::Local(item)) => {
467 log::trace!(target: "name-resolver::expand", "cannot expand '{path}': path is relative to local definition");
468 break Err(SymbolResolutionError::invalid_sub_path(
472 span,
473 item.span(),
474 self.source_manager(),
475 )
476 .into());
477 },
478 Ok(SymbolResolution::Exact { path: item, .. }) => {
479 log::trace!(target: "name-resolver::expand", "cannot expand '{path}': path is relative to item at '{item}'");
480 break Err(SymbolResolutionError::invalid_sub_path(
484 span,
485 item.span(),
486 self.source_manager(),
487 )
488 .into());
489 },
490 Ok(SymbolResolution::MastRoot(item)) => {
491 log::trace!(target: "name-resolver::expand", "cannot expand '{path}': path is relative to imported procedure root");
492 break Err(SymbolResolutionError::invalid_sub_path(
496 span,
497 item.span(),
498 self.source_manager(),
499 )
500 .into());
501 },
502 Ok(SymbolResolution::Module { id, path: module_path }) => {
503 log::trace!(target: "name-resolver::expand", "expanded import '{imported_symbol}' to module: id={id} path={module_path}");
504 context.module = id;
507 ignored_imports.clear();
508 path = subpath;
509 },
510 Ok(SymbolResolution::External(external_path)) => {
511 log::trace!(target: "name-resolver::expand", "expanded import '{imported_symbol}' to unresolved external path '{external_path}'");
517 let partially_expanded = external_path.join(subpath);
518 log::trace!(target: "name-resolver::expand", "partially expanded '{path}' to '{partially_expanded}'");
519 ignored_imports.insert(imported_symbol.to_string().into_boxed_str().into());
520 break self.expand_path_from(
521 origin_module,
522 &context,
523 Span::new(span, partially_expanded.as_path()),
524 ignored_imports,
525 );
526 },
527 Err(err)
528 if matches!(
529 err.as_ref(),
530 SymbolResolutionError::UndefinedSymbol { .. }
531 ) =>
532 {
533 let absolute = path.to_absolute();
535 log::trace!(target: "name-resolver::expand", "no import found for '{imported_symbol}' in '{path}': attempting to resolve as absolute path instead");
536 break self.expand_path_from(
537 origin_module,
538 &context,
539 Span::new(span, absolute.as_ref()),
540 ignored_imports,
541 );
542 },
543 Err(err) => {
544 log::trace!(target: "name-resolver::expand", "expansion failed due to symbol resolution error");
545 break Err(err.into());
546 },
547 }
548 }
549 }
550 }
551
552 fn ensure_item_visible(
553 &self,
554 origin_module: ModuleIndex,
555 item: GlobalItemIndex,
556 span: SourceSpan,
557 ) -> Result<(), LinkerError> {
558 if origin_module == item.module {
559 return Ok(());
560 }
561
562 let symbol = &self.graph[item.module][item.index];
563 if symbol.visibility().is_public() {
564 return Ok(());
565 }
566
567 Err(SymbolResolutionError::private_symbol(
568 span,
569 symbol.name().span(),
570 self.source_manager(),
571 )
572 .into())
573 }
574
575 pub fn resolve_local(
576 &self,
577 context: &SymbolResolutionContext,
578 symbol: &str,
579 ) -> Result<SymbolResolution, Box<SymbolResolutionError>> {
580 let module = if context.in_syscall() {
581 match self.graph.kernel_index {
583 Some(kernel) => kernel,
584 None => {
585 return Err(Box::new(SymbolResolutionError::UndefinedSymbol {
586 span: context.span,
587 source_file: self.source_manager().get(context.span.source_id()).ok(),
588 }));
589 },
590 }
591 } else {
592 context.module
593 };
594 self.resolve_local_with_index(module, Span::new(context.span, symbol))
595 }
596
597 fn resolve_local_with_index(
598 &self,
599 module: ModuleIndex,
600 symbol: Span<&str>,
601 ) -> Result<SymbolResolution, Box<SymbolResolutionError>> {
602 let module = &self.graph[module];
603 log::debug!(target: "name-resolver::local", "resolving '{symbol}' in module {}", module.path());
604 log::debug!(target: "name-resolver::local", "module status: {:?}", module.status());
605 module.resolve(symbol, self)
606 }
607
608 #[inline]
609 pub fn module_path(&self, module: ModuleIndex) -> &Path {
610 self.graph[module].path()
611 }
612
613 pub fn item_path(&self, item: GlobalItemIndex) -> Arc<Path> {
614 let module = &self.graph[item.module];
615 let name = module[item.index].name();
616 module.path().join(name).into()
617 }
618}