Skip to main content

miden_assembly/linker/resolver/
symbol_resolver.rs

1use alloc::sync::Arc;
2
3use miden_assembly_syntax::{
4    ast::{InvocationTarget, InvokeKind, Path, SymbolResolution},
5    debuginfo::{SourceManager, SourceSpan, Span, Spanned},
6    module::ItemInfo,
7};
8use miden_core::Word;
9
10use crate::{
11    GlobalItemIndex, LinkerError, ModuleIndex,
12    linker::{
13        Linker, SymbolItem,
14        namespaces::{NamespaceGraph, ResolvedImports, ResolvedUse},
15    },
16};
17
18// HELPER STRUCTS
19// ================================================================================================
20
21/// Represents the context in which symbols should be resolved.
22///
23/// A symbol may be resolved in different ways depending on where it is being referenced from, and
24/// how it is being referenced.
25#[derive(Debug, Clone)]
26pub struct SymbolResolutionContext {
27    /// The source span of the caller/referent
28    pub span: SourceSpan,
29    /// The "where", i.e. index of the caller/referent's module node in the [Linker] module graph.
30    pub module: ModuleIndex,
31    /// The "how", i.e. how the symbol is being referenced/invoked.
32    ///
33    /// This is primarily relevant for procedure invocations, particularly syscalls, as "local"
34    /// names resolve in the kernel module, _not_ in the caller's module. Non-procedure symbols are
35    /// always pure references.
36    pub kind: Option<InvokeKind>,
37}
38
39impl SymbolResolutionContext {
40    #[inline]
41    pub fn in_syscall(&self) -> bool {
42        matches!(self.kind, Some(InvokeKind::SysCall))
43    }
44}
45
46// SYMBOL RESOLVER
47// ================================================================================================
48
49/// A [SymbolResolver] is used to resolve a procedure invocation target to its concrete definition.
50///
51/// Because modules can re-export/alias the procedures of modules they import, resolving the name of
52/// a procedure can require multiple steps to reach the original concrete definition of the
53/// procedure.
54///
55/// The [SymbolResolver] encapsulates the tricky details of doing this, so that users of the
56/// resolver need only provide a reference to the [Linker], a name they wish to resolve, and some
57/// information about the caller necessary to determine the context in which the name should be
58/// resolved.
59pub struct SymbolResolver<'a> {
60    /// The graph containing already-compiled and partially-resolved modules.
61    graph: &'a Linker,
62    /// Namespace graph for direct link-time path resolution.
63    namespaces: Option<&'a NamespaceGraph>,
64    /// Precomputed import resolutions for the current link pass.
65    imports: Option<&'a ResolvedImports>,
66}
67
68impl<'a> SymbolResolver<'a> {
69    /// Create a new [SymbolResolver] for the provided [Linker].
70    pub fn new(graph: &'a Linker) -> Self {
71        Self { graph, namespaces: None, imports: None }
72    }
73
74    /// Create a new [SymbolResolver] with precomputed namespace and import resolutions.
75    pub(crate) fn with_namespaces(
76        graph: &'a Linker,
77        namespaces: &'a NamespaceGraph,
78        imports: &'a ResolvedImports,
79    ) -> Self {
80        Self {
81            graph,
82            namespaces: Some(namespaces),
83            imports: Some(imports),
84        }
85    }
86
87    pub(crate) fn resolved_import(&self, owner: ModuleIndex, alias: &str) -> Option<ResolvedUse> {
88        self.imports.and_then(|imports| imports.get(owner, alias))
89    }
90
91    fn to_symbol_resolution(&self, span: SourceSpan, resolved: ResolvedUse) -> SymbolResolution {
92        match resolved {
93            ResolvedUse::Module(id) => SymbolResolution::Module {
94                id,
95                path: Span::new(span, Arc::from(self.module_path(id))),
96            },
97            ResolvedUse::Item(gid) => SymbolResolution::Exact {
98                gid,
99                path: Span::new(span, self.item_path(gid)),
100            },
101        }
102    }
103
104    fn source_file(
105        &self,
106        span: SourceSpan,
107    ) -> Option<Arc<miden_assembly_syntax::debuginfo::SourceFile>> {
108        self.source_manager().get(span.source_id()).ok()
109    }
110
111    fn is_procedure(&self, gid: GlobalItemIndex) -> bool {
112        matches!(
113            self.graph[gid].item(),
114            SymbolItem::Procedure(_) | SymbolItem::Compiled(ItemInfo::Procedure(_))
115        )
116    }
117
118    fn is_constant(&self, gid: GlobalItemIndex) -> bool {
119        matches!(
120            self.graph[gid].item(),
121            SymbolItem::Constant(_) | SymbolItem::Compiled(ItemInfo::Constant(_))
122        )
123    }
124
125    fn is_type(&self, gid: GlobalItemIndex) -> bool {
126        matches!(
127            self.graph[gid].item(),
128            SymbolItem::Type(_) | SymbolItem::Compiled(ItemInfo::Type(_))
129        )
130    }
131
132    fn invalid_constant_ref(&self, span: SourceSpan) -> LinkerError {
133        LinkerError::InvalidConstantRef {
134            span,
135            source_file: self.source_file(span),
136        }
137    }
138
139    fn invalid_type_ref(&self, span: SourceSpan) -> LinkerError {
140        LinkerError::InvalidTypeRef {
141            span,
142            source_file: self.source_file(span),
143        }
144    }
145
146    fn ensure_procedure_target(
147        &self,
148        context: &SymbolResolutionContext,
149        resolution: SymbolResolution,
150    ) -> Result<SymbolResolution, LinkerError> {
151        match resolution {
152            resolution @ SymbolResolution::MastRoot(_) => Ok(resolution),
153            resolution @ SymbolResolution::Exact { gid, .. } if self.is_procedure(gid) => {
154                Ok(resolution)
155            },
156            SymbolResolution::Exact { path, .. } | SymbolResolution::Module { path, .. } => {
157                Err(LinkerError::InvalidInvokeTarget {
158                    span: context.span,
159                    source_file: self.source_file(context.span),
160                    path: path.into_inner(),
161                })
162            },
163            SymbolResolution::Local(_) | SymbolResolution::External(_) => {
164                unreachable!("link-time namespace resolution should produce exact ids")
165            },
166        }
167    }
168
169    pub(crate) fn resolve_constant_path(
170        &self,
171        context: &SymbolResolutionContext,
172        path: Span<&Path>,
173    ) -> Result<GlobalItemIndex, LinkerError> {
174        match self.resolve_path(context, path)? {
175            SymbolResolution::Exact { gid, .. } if self.is_constant(gid) => Ok(gid),
176            SymbolResolution::Exact { .. }
177            | SymbolResolution::Module { .. }
178            | SymbolResolution::MastRoot(_) => Err(self.invalid_constant_ref(path.span())),
179            SymbolResolution::Local(_) | SymbolResolution::External(_) => {
180                unreachable!("link-time namespace resolution should produce exact ids")
181            },
182        }
183    }
184
185    pub(crate) fn resolve_type_path(
186        &self,
187        context: &SymbolResolutionContext,
188        path: Span<&Path>,
189    ) -> Result<GlobalItemIndex, LinkerError> {
190        match self.resolve_path(context, path)? {
191            SymbolResolution::Exact { gid, .. } if self.is_type(gid) => Ok(gid),
192            SymbolResolution::Exact { .. }
193            | SymbolResolution::Module { .. }
194            | SymbolResolution::MastRoot(_) => Err(self.invalid_type_ref(path.span())),
195            SymbolResolution::Local(_) | SymbolResolution::External(_) => {
196                unreachable!("link-time namespace resolution should produce exact ids")
197            },
198        }
199    }
200
201    #[inline(always)]
202    pub fn source_manager(&self) -> &dyn SourceManager {
203        &self.graph.source_manager
204    }
205
206    #[inline(always)]
207    pub fn source_manager_arc(&self) -> Arc<dyn SourceManager> {
208        self.graph.source_manager.clone()
209    }
210
211    #[inline(always)]
212    pub(crate) fn linker(&self) -> &Linker {
213        self.graph
214    }
215
216    /// Resolve `target`, a possibly-resolved symbol reference, to a [SymbolResolution], using
217    /// `context` as the context.
218    pub fn resolve_invoke_target(
219        &self,
220        context: &SymbolResolutionContext,
221        target: &InvocationTarget,
222    ) -> Result<SymbolResolution, LinkerError> {
223        let resolution = match target {
224            InvocationTarget::MastRoot(mast_root) => {
225                log::debug!(target: "name-resolver::invoke", "resolving {target}");
226                self.validate_syscall_digest(context, *mast_root)?;
227                match self.graph.get_procedure_index_by_digest(mast_root) {
228                    None => Ok(SymbolResolution::MastRoot(*mast_root)),
229                    Some(gid) if context.in_syscall() => {
230                        if self.graph.kernel_index.is_some_and(|k| k == gid.module) {
231                            Ok(SymbolResolution::Exact {
232                                gid,
233                                path: Span::new(mast_root.span(), self.item_path(gid)),
234                            })
235                        } else {
236                            Err(LinkerError::InvalidSysCallTarget {
237                                span: context.span,
238                                source_file: self
239                                    .source_manager()
240                                    .get(context.span.source_id())
241                                    .ok(),
242                                callee: self.item_path(gid),
243                            })
244                        }
245                    },
246                    Some(gid) => Ok(SymbolResolution::Exact {
247                        gid,
248                        path: Span::new(mast_root.span(), self.item_path(gid)),
249                    }),
250                }
251            },
252            InvocationTarget::Symbol(symbol) => {
253                let path = Path::from_ident(symbol);
254                let mut context = context.clone();
255                // Force the resolution context for a syscall target to be the kernel module
256                if context.in_syscall() {
257                    if let Some(kernel) = self.graph.kernel_index {
258                        context.module = kernel;
259                    } else {
260                        return Err(LinkerError::InvalidSysCallTarget {
261                            span: context.span,
262                            source_file: self.source_manager().get(context.span.source_id()).ok(),
263                            callee: Path::from_ident(symbol).into_owned().into(),
264                        });
265                    }
266                }
267                match self.resolve_path(&context, Span::new(symbol.span(), path.as_ref()))? {
268                    SymbolResolution::Module { id: _, path: module_path } => {
269                        Err(LinkerError::InvalidInvokeTarget {
270                            span: symbol.span(),
271                            source_file: self
272                                .graph
273                                .source_manager
274                                .get(symbol.span().source_id())
275                                .ok(),
276                            path: module_path.into_inner(),
277                        })
278                    },
279                    resolution => Ok(resolution),
280                }
281            },
282            InvocationTarget::Path(path) => match self.resolve_path(context, path.as_deref())? {
283                SymbolResolution::Module { id: _, path: module_path } => {
284                    Err(LinkerError::InvalidInvokeTarget {
285                        span: path.span(),
286                        source_file: self.graph.source_manager.get(path.span().source_id()).ok(),
287                        path: module_path.into_inner(),
288                    })
289                },
290                SymbolResolution::Exact { gid, path } if context.in_syscall() => {
291                    if self.graph.kernel_index.is_some_and(|k| k == gid.module) {
292                        Ok(SymbolResolution::Exact { gid, path })
293                    } else {
294                        Err(LinkerError::InvalidSysCallTarget {
295                            span: context.span,
296                            source_file: self.source_manager().get(context.span.source_id()).ok(),
297                            callee: path.into_inner(),
298                        })
299                    }
300                },
301                SymbolResolution::MastRoot(mast_root) => {
302                    self.validate_syscall_digest(context, mast_root)?;
303                    match self.graph.get_procedure_index_by_digest(&mast_root) {
304                        None => Ok(SymbolResolution::MastRoot(mast_root)),
305                        Some(gid) if context.in_syscall() => {
306                            if self.graph.kernel_index.is_some_and(|k| k == gid.module) {
307                                Ok(SymbolResolution::Exact {
308                                    gid,
309                                    path: Span::new(mast_root.span(), self.item_path(gid)),
310                                })
311                            } else {
312                                Err(LinkerError::InvalidSysCallTarget {
313                                    span: context.span,
314                                    source_file: self
315                                        .source_manager()
316                                        .get(context.span.source_id())
317                                        .ok(),
318                                    callee: self.item_path(gid),
319                                })
320                            }
321                        },
322                        Some(gid) => Ok(SymbolResolution::Exact {
323                            gid,
324                            path: Span::new(mast_root.span(), self.item_path(gid)),
325                        }),
326                    }
327                },
328                // NOTE: If we're in a syscall here, we can't validate syscall targets that are not
329                // fully resolved - but such targets will be revisited later at which point they
330                // will be checked
331                resolution => Ok(resolution),
332            },
333        }?;
334        let resolution = self.ensure_procedure_target(context, resolution)?;
335        self.enforce_kernel_export_syscall_only(context, target, resolution)
336    }
337
338    fn enforce_kernel_export_syscall_only(
339        &self,
340        context: &SymbolResolutionContext,
341        target: &InvocationTarget,
342        resolution: SymbolResolution,
343    ) -> Result<SymbolResolution, LinkerError> {
344        if matches!(target, InvocationTarget::MastRoot(_)) {
345            return Ok(resolution);
346        }
347        if let SymbolResolution::Exact { gid, ref path } = resolution
348            && context.kind.is_some()
349            && !context.in_syscall()
350        {
351            // Root kernel attached via `with_kernel` is stored as ModuleKind::Library (MAST);
352            // `kernel_index` identifies it. AST kernel modules use ModuleKind::Kernel.
353            let target_is_kernel = self.graph.kernel_index.is_some_and(|ki| ki == gid.module);
354            let caller_is_kernel = self.graph.kernel_index.is_some_and(|ki| ki == context.module);
355            if target_is_kernel && !caller_is_kernel {
356                return Err(LinkerError::KernelProcNotSyscall {
357                    span: context.span,
358                    source_file: self.graph.source_manager.get(context.span.source_id()).ok(),
359                    callee: path.clone().into_inner(),
360                });
361            }
362        }
363        Ok(resolution)
364    }
365
366    fn validate_syscall_digest(
367        &self,
368        context: &SymbolResolutionContext,
369        mast_root: Span<Word>,
370    ) -> Result<(), LinkerError> {
371        if !context.in_syscall() {
372            return Ok(());
373        }
374        // Syscalls must be validated against an attached kernel at assembly time.
375        if !self.graph.has_nonempty_kernel() {
376            return Err(LinkerError::InvalidSysCallTarget {
377                span: context.span,
378                source_file: self.source_manager().get(context.span.source_id()).ok(),
379                callee: Arc::<Path>::from(Path::new("syscall")),
380            });
381        }
382        // Kernel digests only contain exported kernel procedures.
383        if !self.graph.kernel().contains_proc(*mast_root.inner()) {
384            let digest_path = format!("{mast_root}");
385            return Err(LinkerError::InvalidSysCallTarget {
386                span: context.span,
387                source_file: self.source_manager().get(context.span.source_id()).ok(),
388                callee: Arc::<Path>::from(Path::new(&digest_path)),
389            });
390        }
391        Ok(())
392    }
393
394    pub fn resolve_path(
395        &self,
396        context: &SymbolResolutionContext,
397        path: Span<&Path>,
398    ) -> Result<SymbolResolution, LinkerError> {
399        match (self.namespaces, self.imports) {
400            (Some(namespaces), Some(imports)) => {
401                self.resolve_path_with_namespaces(namespaces, imports, context, path)
402            },
403            _ => {
404                let namespaces = NamespaceGraph::build(self.graph)?;
405                let imports = namespaces.resolve_imports(self.graph)?;
406                self.resolve_path_with_namespaces(&namespaces, &imports, context, path)
407            },
408        }
409    }
410
411    fn resolve_path_with_namespaces(
412        &self,
413        namespaces: &NamespaceGraph,
414        imports: &ResolvedImports,
415        context: &SymbolResolutionContext,
416        path: Span<&Path>,
417    ) -> Result<SymbolResolution, LinkerError> {
418        let resolved = namespaces.resolve_code_path(context.module, path, imports, self.graph)?;
419        Ok(self.to_symbol_resolution(path.span(), resolved))
420    }
421
422    pub fn resolve_local(
423        &self,
424        context: &SymbolResolutionContext,
425        symbol: &str,
426    ) -> Result<SymbolResolution, LinkerError> {
427        let mut context = context.clone();
428        if context.in_syscall() {
429            // Resolve local names relative to the kernel
430            match self.graph.kernel_index {
431                Some(kernel) => context.module = kernel,
432                None => {
433                    return Err(LinkerError::InvalidSysCallTarget {
434                        span: context.span,
435                        source_file: self.source_manager().get(context.span.source_id()).ok(),
436                        callee: Arc::from(Path::new(symbol)),
437                    });
438                },
439            }
440        }
441        let path = Path::new(symbol);
442        self.resolve_path(&context, Span::new(context.span, path))
443    }
444
445    #[inline]
446    pub fn module_path(&self, module: ModuleIndex) -> &Path {
447        self.graph[module].path()
448    }
449
450    pub fn item_path(&self, item: GlobalItemIndex) -> Arc<Path> {
451        let module = &self.graph[item.module];
452        let name = module[item.index].name();
453        module.path().join(name).into()
454    }
455}