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#[derive(Debug, Clone)]
26pub struct SymbolResolutionContext {
27 pub span: SourceSpan,
29 pub module: ModuleIndex,
31 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
46pub struct SymbolResolver<'a> {
60 graph: &'a Linker,
62 namespaces: Option<&'a NamespaceGraph>,
64 imports: Option<&'a ResolvedImports>,
66}
67
68impl<'a> SymbolResolver<'a> {
69 pub fn new(graph: &'a Linker) -> Self {
71 Self { graph, namespaces: None, imports: None }
72 }
73
74 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 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 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 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 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 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 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 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}