1use alloc::{borrow::Cow, boxed::Box, collections::BTreeSet, sync::Arc};
2use core::ops::ControlFlow;
3
4use miden_assembly_syntax::{
5 ast::{
6 AliasTarget, InvocationTarget, InvokeKind, Path, SymbolResolution, SymbolResolutionError,
7 },
8 debuginfo::{SourceManager, SourceSpan, Span, Spanned},
9 diagnostics::RelatedLabel,
10};
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 fn display_kind(&self) -> impl core::fmt::Display {
42 struct Kind(Option<InvokeKind>);
43 impl core::fmt::Display for Kind {
44 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
45 match self {
46 Self(None) => f.write_str("item"),
47 Self(Some(kind)) => core::fmt::Display::fmt(kind, f),
48 }
49 }
50 }
51
52 Kind(self.kind)
53 }
54}
55
56pub struct SymbolResolver<'a> {
70 graph: &'a Linker,
72}
73
74impl<'a> SymbolResolver<'a> {
75 pub fn new(graph: &'a Linker) -> Self {
77 Self { graph }
78 }
79
80 #[inline(always)]
81 pub fn source_manager(&self) -> &dyn SourceManager {
82 &self.graph.source_manager
83 }
84
85 #[inline(always)]
86 pub fn source_manager_arc(&self) -> Arc<dyn SourceManager> {
87 self.graph.source_manager.clone()
88 }
89
90 #[inline(always)]
91 pub(crate) fn linker(&self) -> &Linker {
92 self.graph
93 }
94
95 pub fn resolve_invoke_target(
98 &self,
99 context: &SymbolResolutionContext,
100 target: &InvocationTarget,
101 ) -> Result<SymbolResolution, LinkerError> {
102 match target {
103 InvocationTarget::MastRoot(mast_root) => {
104 log::debug!(target: "name-resolver::invoke", "resolving {target}");
105 match self.graph.get_procedure_index_by_digest(mast_root) {
106 None => Ok(SymbolResolution::MastRoot(*mast_root)),
107 Some(gid) => Ok(SymbolResolution::Exact {
108 gid,
109 path: Span::new(mast_root.span(), self.item_path(gid)),
110 }),
111 }
112 },
113 InvocationTarget::Symbol(symbol) => {
114 self.resolve(context, Span::new(symbol.span(), symbol))
115 },
116 InvocationTarget::Path(path) => match self.resolve_path(context, path.as_deref())? {
117 SymbolResolution::Module { id: _, path: module_path } => {
118 Err(LinkerError::InvalidInvokeTarget {
119 span: path.span(),
120 source_file: self.graph.source_manager.get(path.span().source_id()).ok(),
121 path: module_path.into_inner(),
122 })
123 },
124 resolution => Ok(resolution),
125 },
126 }
127 }
128
129 pub fn resolve_alias_target(
132 &self,
133 context: &SymbolResolutionContext,
134 target: &AliasTarget,
135 ) -> Result<SymbolResolution, LinkerError> {
136 match target {
137 AliasTarget::MastRoot(mast_root) => {
138 log::debug!(target: "name-resolver::alias", "resolving alias target {target}");
139 match self.graph.get_procedure_index_by_digest(mast_root) {
140 None => Ok(SymbolResolution::MastRoot(*mast_root)),
141 Some(gid) => Ok(SymbolResolution::Exact {
142 gid,
143 path: Span::new(mast_root.span(), self.item_path(gid)),
144 }),
145 }
146 },
147 AliasTarget::Path(path) => self.resolve_path(context, path.as_deref()),
148 }
149 }
150
151 pub fn resolve_path(
152 &self,
153 context: &SymbolResolutionContext,
154 path: Span<&Path>,
155 ) -> Result<SymbolResolution, LinkerError> {
156 log::debug!(target: "name-resolver::path", "resolving path '{path}' (absolute = {})", path.is_absolute());
157 if let Some(symbol) = path.as_ident() {
158 log::debug!(target: "name-resolver::path", "resolving path '{symbol}' as local symbol");
159 return self.resolve(context, Span::new(path.span(), &symbol));
160 }
161
162 if context.kind.is_none() {
165 log::debug!(target: "name-resolver::path", "attempting to resolve '{path}' as module path");
166 if let Some(id) = self.get_module_index_by_path(path.inner()) {
167 log::debug!(target: "name-resolver::path", "resolved '{path}' to module id '{id}'");
168 return Ok(SymbolResolution::Module {
169 id,
170 path: Span::new(context.span, self.module_path(id).to_path_buf().into()),
171 });
172 }
173 }
174
175 if path.is_absolute() {
177 self.find(context, path)
178 } else {
179 let (ns, subpath) = path.split_first().unwrap();
180 log::debug!(target: "name-resolver::path", "resolving path as '{subpath}' relative to '{ns}'");
181 match self.resolve_import(context, Span::new(path.span(), ns))? {
183 SymbolResolution::Exact { gid, path } => {
184 if subpath.is_empty() {
185 log::debug!(target: "name-resolver::path", "resolved '{ns}' to imported item '{path}'");
186 Ok(SymbolResolution::Exact { gid, path })
187 } else {
188 log::error!(target: "name-resolver::path", "resolved '{ns}' to imported item '{path}'");
189 Err(Box::new(SymbolResolutionError::InvalidSubPath {
190 span: context.span,
191 source_file: self
192 .graph
193 .source_manager
194 .get(context.span.source_id())
195 .ok(),
196 relative_to: None,
197 })
198 .into())
199 }
200 },
201 SymbolResolution::MastRoot(digest) => {
202 if subpath.is_empty() {
203 log::debug!(target: "name-resolver::path", "resolved '{ns}' to imported procedure '{digest}'");
204 Ok(SymbolResolution::MastRoot(digest))
205 } else {
206 log::error!(target: "name-resolver::path", "resolved '{ns}' to imported procedure '{digest}'");
207 Err(Box::new(SymbolResolutionError::InvalidSubPath {
208 span: context.span,
209 source_file: self
210 .graph
211 .source_manager
212 .get(context.span.source_id())
213 .ok(),
214 relative_to: None,
215 })
216 .into())
217 }
218 },
219 SymbolResolution::Module { id, path: module_path } => {
220 log::debug!(target: "name-resolver::path", "resolved '{ns}' to imported module '{module_path}'");
221 if subpath.is_empty() {
222 return Ok(SymbolResolution::Module { id, path: module_path });
223 }
224
225 let span = path.span();
226 let path = module_path.join(subpath);
227 let context = SymbolResolutionContext {
228 span: module_path.span(),
229 module: id,
230 kind: context.kind,
231 };
232 self.resolve_path(&context, Span::new(span, path.as_path()))
233 },
234 SymbolResolution::Local(_) | SymbolResolution::External(_) => unreachable!(),
235 }
236 }
237 }
238
239 fn resolve(
241 &self,
242 context: &SymbolResolutionContext,
243 symbol: Span<&str>,
244 ) -> Result<SymbolResolution, LinkerError> {
245 log::debug!(target: "name-resolver::resolve", "resolving symbol '{symbol}'");
246 match self.resolve_local(context, symbol.inner())? {
247 SymbolResolution::Local(index) if context.in_syscall() => {
248 log::debug!(target: "name-resolver::resolve", "resolved symbol to local item '{index}'");
249 let gid = GlobalItemIndex {
250 module: self.graph.kernel_index.unwrap(),
251 index: index.into_inner(),
252 };
253 Ok(SymbolResolution::Exact {
254 gid,
255 path: Span::new(index.span(), self.item_path(gid)),
256 })
257 },
258 SymbolResolution::Local(index) => {
259 log::debug!(target: "name-resolver::resolve", "resolved symbol to local item '{index}'");
260 let gid = GlobalItemIndex {
261 module: context.module,
262 index: index.into_inner(),
263 };
264 Ok(SymbolResolution::Exact {
265 gid,
266 path: Span::new(index.span(), self.item_path(gid)),
267 })
268 },
269 SymbolResolution::External(fqn) => match self.find(context, fqn.as_deref())? {
270 resolution @ (SymbolResolution::Exact { .. } | SymbolResolution::Module { .. }) => {
271 log::debug!(target: "name-resolver::resolve", "resolved '{symbol}' via '{fqn}': {resolution:?}");
272 Ok(resolution)
273 },
274 SymbolResolution::External(_)
275 | SymbolResolution::Local(_)
276 | SymbolResolution::MastRoot(_) => unreachable!(),
277 },
278 SymbolResolution::MastRoot(digest) => {
279 log::debug!(target: "name-resolver::resolve", "resolved '{symbol}' to digest {digest}");
280 match self.graph.get_procedure_index_by_digest(&digest) {
281 Some(gid) => Ok(SymbolResolution::Exact {
282 gid,
283 path: Span::new(digest.span(), self.item_path(gid)),
284 }),
285 None => Ok(SymbolResolution::MastRoot(digest)),
286 }
287 },
288 res @ (SymbolResolution::Exact { .. } | SymbolResolution::Module { .. }) => {
289 log::debug!(target: "name-resolver::resolve", "resolved '{symbol}': {res:?}");
290 Ok(res)
291 },
292 }
293 }
294
295 fn resolve_import(
297 &self,
298 context: &SymbolResolutionContext,
299 symbol: Span<&str>,
300 ) -> Result<SymbolResolution, LinkerError> {
301 log::debug!(target: "name-resolver::import", "resolving import '{symbol}' from module index {}", context.module);
302 let module = &self.graph[context.module];
303 log::debug!(target: "name-resolver::import", "context source type is '{:?}'", module.source());
304
305 let found = module.resolve(symbol, self);
306 log::debug!(target: "name-resolver::import", "local resolution for '{symbol}': {found:?}");
307 match found {
308 Ok(SymbolResolution::External(path)) => {
309 let context = SymbolResolutionContext {
310 span: symbol.span(),
311 module: context.module,
312 kind: None,
313 };
314 self.resolve_path(&context, path.as_deref())
315 },
316 Ok(SymbolResolution::Local(item)) => {
317 let gid = context.module + item.into_inner();
318 Ok(SymbolResolution::Exact {
319 gid,
320 path: Span::new(item.span(), self.item_path(gid)),
321 })
322 },
323 Ok(SymbolResolution::MastRoot(digest)) => {
324 match self.graph.get_procedure_index_by_digest(&digest) {
325 Some(gid) => Ok(SymbolResolution::Exact {
326 gid,
327 path: Span::new(digest.span(), self.item_path(gid)),
328 }),
329 None => Ok(SymbolResolution::MastRoot(digest)),
330 }
331 },
332 Ok(res @ (SymbolResolution::Exact { .. } | SymbolResolution::Module { .. })) => Ok(res),
333 Err(err) if matches!(&*err, SymbolResolutionError::UndefinedSymbol { .. }) => {
334 let path = Path::new(symbol.into_inner());
338 match self.get_module_index_by_path(path) {
339 Some(found) => Ok(SymbolResolution::Module {
341 id: found,
342 path: Span::new(symbol.span(), self.module_path(found).into()),
343 }),
344 None => Err(err.into()),
346 }
347 },
348 Err(err) => Err(err.into()),
349 }
350 }
351
352 pub fn resolve_local(
353 &self,
354 context: &SymbolResolutionContext,
355 symbol: &str,
356 ) -> Result<SymbolResolution, Box<SymbolResolutionError>> {
357 let module = if context.in_syscall() {
358 match self.graph.kernel_index {
360 Some(kernel) => kernel,
361 None => {
362 return Err(Box::new(SymbolResolutionError::UndefinedSymbol {
363 span: context.span,
364 source_file: self.source_manager().get(context.span.source_id()).ok(),
365 }));
366 },
367 }
368 } else {
369 context.module
370 };
371 self.resolve_local_with_index(module, Span::new(context.span, symbol))
372 }
373
374 fn resolve_local_with_index(
375 &self,
376 module: ModuleIndex,
377 symbol: Span<&str>,
378 ) -> Result<SymbolResolution, Box<SymbolResolutionError>> {
379 let module = &self.graph[module];
380 log::debug!(target: "name-resolver::local", "resolving '{symbol}' in module {}", module.path());
381 log::debug!(target: "name-resolver::local", "module status: {:?}", &module.status());
382 module.resolve(symbol, self)
383 }
384
385 fn find(
390 &self,
391 context: &SymbolResolutionContext,
392 path: Span<&Path>,
393 ) -> Result<SymbolResolution, LinkerError> {
394 let mut current_context = if context.in_syscall() {
398 let mut caller = context.clone();
399 caller.kind = Some(InvokeKind::ProcRef);
400 Cow::Owned(caller)
401 } else {
402 Cow::Borrowed(context)
403 };
404 let mut resolving = path.map(|p| Arc::<Path>::from(p.to_path_buf()));
405 let mut visited = BTreeSet::default();
406 loop {
407 let current_module_path = self.module_path(current_context.module);
408 log::debug!(target: "name-resolver::find", "resolving {} of {resolving} from {} ({current_module_path})", current_context.display_kind(), ¤t_context.module);
409
410 let (resolving_symbol, resolving_parent) = resolving.split_last().unwrap();
411 let resolving_symbol = Span::new(resolving.span(), resolving_symbol);
412
413 if current_module_path == &**resolving {
416 return Ok(SymbolResolution::Module {
417 id: current_context.module,
418 path: resolving,
419 });
420 }
421
422 if resolving_parent == current_module_path {
424 match self.find_local(
425 ¤t_context,
426 resolving_symbol,
427 resolving.inner().clone(),
428 &mut visited,
429 ) {
430 ControlFlow::Break(result) => break result,
431 ControlFlow::Continue(LocalFindResult { context, resolving: next }) => {
432 current_context = Cow::Owned(context);
433 resolving = next;
434 continue;
435 },
436 }
437 }
438
439 if let Some(id) =
448 self.find_module_index(current_context.module, resolving.as_deref())?
449 {
450 log::debug!(target: "name-resolver::find", "resolved '{resolving}' to module {id} ({})", self.module_path(id));
451 return Ok(SymbolResolution::Module {
452 id,
453 path: Span::new(resolving.span(), self.module_path(id).to_path_buf().into()),
454 });
455 }
456
457 log::debug!(target: "name-resolver::find", "resolving '{resolving_parent}' from {} ({current_module_path})", current_context.module);
460 let module_index = self
461 .find_module_index(
462 current_context.module,
463 Span::new(resolving.span(), resolving_parent),
464 )?
465 .ok_or_else(|| {
466 LinkerError::UndefinedModule {
469 span: current_context.span,
470 source_file: self
471 .graph
472 .source_manager
473 .get(current_context.span.source_id())
474 .ok(),
475 path: (*resolving).clone(),
476 }
477 })?;
478 log::debug!(target: "name-resolver::find", "resolved '{resolving_parent}' to module {module_index} ({})", self.module_path(module_index));
479
480 log::debug!(target: "name-resolver::find", "resolving {resolving_symbol} in module {resolving_parent}");
481 let context = SymbolResolutionContext {
482 module: module_index,
483 span: current_context.span,
484 kind: current_context.kind,
485 };
486 match self.find_local(
487 &context,
488 resolving_symbol,
489 resolving.inner().clone(),
490 &mut visited,
491 ) {
492 ControlFlow::Break(result) => break result,
493 ControlFlow::Continue(LocalFindResult { context, resolving: next }) => {
494 current_context = Cow::Owned(context);
495 resolving = next;
496 },
497 }
498 }
499 }
500
501 fn find_local(
502 &self,
503 context: &SymbolResolutionContext,
504 symbol: Span<&str>,
505 resolving: Arc<Path>,
506 visited: &mut BTreeSet<Span<Arc<Path>>>,
507 ) -> ControlFlow<Result<SymbolResolution, LinkerError>, LocalFindResult> {
508 let resolved = self.resolve_local_with_index(context.module, symbol);
509 match resolved {
510 Ok(SymbolResolution::Local(index)) => {
511 log::debug!(target: "name-resolver::find", "resolved {symbol} to local item {index}");
512 let gid = GlobalItemIndex {
513 module: context.module,
514 index: index.into_inner(),
515 };
516 if context.in_syscall() && self.graph.kernel_index != Some(context.module) {
517 return ControlFlow::Break(Err(LinkerError::InvalidSysCallTarget {
518 span: context.span,
519 source_file: self.graph.source_manager.get(context.span.source_id()).ok(),
520 callee: resolving,
521 }));
522 }
523 ControlFlow::Break(Ok(SymbolResolution::Exact {
524 gid,
525 path: Span::new(index.span(), self.item_path(gid)),
526 }))
527 },
528 Ok(SymbolResolution::External(fqn)) => {
529 log::debug!(target: "name-resolver::find", "resolved {symbol} to external path {fqn}");
530 if !visited.insert(fqn.clone()) {
533 ControlFlow::Break(Err(LinkerError::Failed {
534 labels: vec![
535 RelatedLabel::error("recursive alias")
536 .with_source_file(self.graph.source_manager.get(fqn.span().source_id()).ok())
537 .with_labeled_span(fqn.span(), "occurs because this import causes import resolution to loop back on itself"),
538 RelatedLabel::advice("recursive alias")
539 .with_source_file(self.graph.source_manager.get(context.span.source_id()).ok())
540 .with_labeled_span(context.span, "as a result of resolving this procedure reference"),
541 ].into(),
542 }))
543 } else {
544 ControlFlow::Continue(LocalFindResult {
545 context: SymbolResolutionContext {
546 span: fqn.span(),
547 module: context.module,
548 kind: context.kind,
549 },
550 resolving: fqn,
551 })
552 }
553 },
554 Ok(SymbolResolution::MastRoot(ref digest)) => {
555 log::debug!(target: "name-resolver::find", "resolved {symbol} to MAST root {digest}");
556 if let Some(gid) = self.graph.get_procedure_index_by_digest(digest) {
557 ControlFlow::Break(Ok(SymbolResolution::Exact {
558 gid,
559 path: Span::new(digest.span(), self.item_path(gid)),
560 }))
561 } else {
562 ControlFlow::Break(Err(LinkerError::Failed {
565 labels: vec![
566 RelatedLabel::error("undefined procedure")
567 .with_source_file(
568 self.graph.source_manager.get(context.span.source_id()).ok(),
569 )
570 .with_labeled_span(
571 context.span,
572 "unable to resolve this reference to its definition",
573 ),
574 RelatedLabel::error("name resolution cannot proceed")
575 .with_source_file(
576 self.graph.source_manager.get(symbol.span().source_id()).ok(),
577 )
578 .with_labeled_span(symbol.span(), "this name cannot be resolved"),
579 ]
580 .into(),
581 }))
582 }
583 },
584 Ok(res @ (SymbolResolution::Exact { .. } | SymbolResolution::Module { .. })) => {
585 ControlFlow::Break(Ok(res))
586 },
587 Err(err) if context.in_syscall() => {
588 if let SymbolResolutionError::UndefinedSymbol { .. } = &*err {
589 log::debug!(target: "name-resolver::find", "unable to resolve {symbol}");
590 if self.graph.has_nonempty_kernel() {
591 ControlFlow::Break(Err(LinkerError::Failed {
593 labels: vec![
594 RelatedLabel::error("undefined kernel procedure")
595 .with_source_file(self.graph.source_manager.get(context.span.source_id()).ok())
596 .with_labeled_span(context.span, "unable to resolve this reference to a procedure in the current kernel"),
597 RelatedLabel::error("invalid syscall")
598 .with_source_file(self.graph.source_manager.get(symbol.span().source_id()).ok())
599 .with_labeled_span(
600 symbol.span(),
601 "this name cannot be resolved, because the assembler has an empty kernel",
602 ),
603 ].into()
604 }))
605 } else {
606 ControlFlow::Break(Err(LinkerError::Failed {
608 labels: vec![
609 RelatedLabel::error("undefined kernel procedure")
610 .with_source_file(self.graph.source_manager.get(context.span.source_id()).ok())
611 .with_labeled_span(context.span, "unable to resolve this reference to a procedure in the current kernel"),
612 RelatedLabel::error("name resolution cannot proceed")
613 .with_source_file(self.graph.source_manager.get(symbol.span().source_id()).ok())
614 .with_labeled_span(
615 symbol.span(),
616 "this name cannot be resolved",
617 ),
618 ].into()
619 }))
620 }
621 } else {
622 ControlFlow::Break(Err(LinkerError::SymbolResolution(err)))
623 }
624 },
625 Err(err) => {
626 if matches!(&*err, SymbolResolutionError::UndefinedSymbol { .. }) {
627 log::debug!(target: "name-resolver::find", "unable to resolve {symbol}");
628 ControlFlow::Break(Err(LinkerError::Failed {
630 labels: vec![
631 RelatedLabel::error("undefined item")
632 .with_source_file(
633 self.graph.source_manager.get(context.span.source_id()).ok(),
634 )
635 .with_labeled_span(
636 context.span,
637 "unable to resolve this reference to its definition",
638 ),
639 RelatedLabel::error("name resolution cannot proceed")
640 .with_source_file(
641 self.graph.source_manager.get(symbol.span().source_id()).ok(),
642 )
643 .with_labeled_span(symbol.span(), "this name cannot be resolved"),
644 ]
645 .into(),
646 }))
647 } else {
648 ControlFlow::Break(Err(LinkerError::SymbolResolution(err)))
649 }
650 },
651 }
652 }
653
654 fn find_module_index(
656 &self,
657 src: ModuleIndex,
658 path: Span<&Path>,
659 ) -> Result<Option<ModuleIndex>, Box<SymbolResolutionError>> {
660 log::debug!(target: "name-resolver", "looking up module index for {path} in context of {src}");
661 let found = self.get_module_index_by_path(path.inner());
662 if found.is_some() {
663 return Ok(found);
664 }
665
666 if path.is_absolute() {
667 log::debug!(target: "name-resolver", "{path} is not in the global module table, must be an item path");
668 return Ok(None);
669 }
670
671 log::debug!(target: "name-resolver", "{path} is not in the global module table");
672 log::debug!(target: "name-resolver", "checking if {path} is resolvable via imports in {src}");
673 let src_module = &self.graph[src];
676 let resolved_item = src_module.resolve_path(path, self)?;
677 match resolved_item {
678 SymbolResolution::External(path) => {
679 let path = path.to_absolute();
680 Ok(self.get_module_index_by_path(&path))
681 },
682 SymbolResolution::Local(item) => {
683 Err(Box::new(SymbolResolutionError::invalid_symbol_type(
684 path.span(),
685 "module",
686 item.span(),
687 self.source_manager(),
688 )))
689 },
690 SymbolResolution::MastRoot(item) => {
691 Err(Box::new(SymbolResolutionError::invalid_symbol_type(
692 path.span(),
693 "module",
694 item.span(),
695 self.source_manager(),
696 )))
697 },
698 SymbolResolution::Exact { gid, .. } => Ok(Some(gid.module)),
699 SymbolResolution::Module { id, .. } => Ok(Some(id)),
700 }
701 }
702
703 fn get_module_index_by_path(&self, path: &Path) -> Option<ModuleIndex> {
704 let path = path.to_absolute();
705 log::debug!(target: "name-resolver", "looking up module index for global symbol {path}");
706 self.graph.modules.iter().find_map(|m| {
707 log::debug!(target: "name-resolver::get_module_index_by_path", "checking against {}: {}", m.path(), path.as_ref() == m.path());
708 if path.as_ref() == m.path() {
709 Some(m.id())
710 } else {
711 None
712 }
713 })
714 }
715
716 #[inline]
717 pub fn module_path(&self, module: ModuleIndex) -> &Path {
718 self.graph[module].path()
719 }
720
721 pub fn item_path(&self, item: GlobalItemIndex) -> Arc<Path> {
722 let module = &self.graph[item.module];
723 let name = module[item.index].name();
724 module.path().join(name).into()
725 }
726}
727
728struct LocalFindResult {
729 context: SymbolResolutionContext,
730 resolving: Span<Arc<Path>>,
731}