1use super::*;
2use crate::fusion::FusionPlannerMetadata;
3use std::collections::{HashMap, HashSet};
4use std::path::{Path, PathBuf};
5
6fn entrypoint_target_function(
7 assembly: &runmat_hir::HirAssembly,
8) -> Option<runmat_hir::FunctionId> {
9 assembly
10 .entrypoints
11 .first()
12 .map(|entrypoint| entrypoint.target)
13}
14
15fn mir_local_fact_count_for_entrypoint(
16 analysis: &runmat_mir::analysis::AnalysisStore,
17 assembly: &runmat_hir::HirAssembly,
18) -> usize {
19 let Some(entrypoint_target) = entrypoint_target_function(assembly) else {
20 return analysis.mir_locals.len();
21 };
22 analysis
23 .mir_locals
24 .keys()
25 .filter(|key| key.function == entrypoint_target)
26 .count()
27}
28
29fn discover_known_project_symbols(source_name: &str) -> HashSet<String> {
30 use runmat_config::project::discover_known_project_symbols_from_source_name;
31
32 let source_path = PathBuf::from(source_name);
33 let cwd = if source_path.is_absolute() {
34 source_path
35 .parent()
36 .map(Path::to_path_buf)
37 .unwrap_or_else(|| PathBuf::from("."))
38 } else {
39 runmat_filesystem::current_dir().unwrap_or_else(|_| PathBuf::from("."))
40 };
41 discover_known_project_symbols_from_source_name(Some(source_name), &cwd)
42}
43
44fn source_lookup_cwd(source_name: &str) -> Option<PathBuf> {
45 let source_path = PathBuf::from(source_name);
46 if source_path.is_absolute() {
47 return source_path
48 .parent()
49 .map(Path::to_path_buf)
50 .or_else(|| Some(PathBuf::from(".")));
51 }
52 Some(runmat_filesystem::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
53}
54
55fn resolved_source_path(source_name: &str, cwd: &Path) -> PathBuf {
56 let source_path = PathBuf::from(source_name);
57 if source_path.is_absolute() {
58 source_path
59 } else {
60 cwd.join(source_path)
61 }
62}
63
64fn is_class_source_body(stmts: &[runmat_parser::Stmt]) -> bool {
65 let has_classdef = stmts
66 .iter()
67 .any(|stmt| matches!(stmt, runmat_parser::Stmt::ClassDef { .. }));
68 if !has_classdef {
69 return false;
70 }
71 stmts.iter().all(|stmt| {
72 matches!(
73 stmt,
74 runmat_parser::Stmt::ClassDef { .. } | runmat_parser::Stmt::Function { .. }
75 )
76 })
77}
78
79fn is_function_source_body(stmts: &[runmat_parser::Stmt]) -> bool {
80 !stmts.is_empty()
81 && stmts
82 .iter()
83 .all(|stmt| matches!(stmt, runmat_parser::Stmt::Function { .. }))
84}
85
86fn package_class_name_from_path(source_path: &Path, root_dir: &Path) -> Option<String> {
87 let relative = source_path.strip_prefix(root_dir).ok()?;
88 let class_name = source_path.file_stem()?.to_str()?;
89 let mut package_segments = Vec::new();
90 if let Some(parent) = relative.parent() {
91 for component in parent.components() {
92 let segment = component.as_os_str().to_str()?;
93 if let Some(pkg) = segment.strip_prefix('+') {
94 if pkg.is_empty() {
95 return None;
96 }
97 package_segments.push(pkg.to_string());
98 } else if let Some(class) = segment.strip_prefix('@') {
99 if class.is_empty() {
100 return None;
101 }
102 package_segments.push(class.to_string());
103 } else {
104 return None;
105 }
106 }
107 }
108 if package_segments.is_empty() {
109 return None;
110 }
111 package_segments.push(class_name.to_string());
112 Some(package_segments.join("."))
113}
114
115fn qualify_companion_classdefs(stmts: &mut [runmat_parser::Stmt], qualified_name: &str) {
116 for stmt in stmts {
117 if let runmat_parser::Stmt::ClassDef { name, .. } = stmt {
118 if !name.contains('.') {
119 *name = qualified_name.to_string();
120 }
121 }
122 }
123}
124
125fn qualify_companion_functions(stmts: &mut [runmat_parser::Stmt], qualified_name: &str) {
126 for stmt in stmts {
127 if let runmat_parser::Stmt::Function { name, .. } = stmt {
128 if !name.contains('.') {
129 *name = qualified_name.to_string();
130 }
131 }
132 }
133}
134
135fn source_index_qualified_function_name(
136 source: &runmat_config::project::ProjectSourceFile,
137) -> Option<&str> {
138 if source.is_private {
139 return None;
140 }
141 (source.package_path.is_some() || source.class_name.is_some())
142 .then_some(source.qualified_name.as_str())
143 .filter(|name| name.contains('.'))
144}
145
146fn source_index_qualified_class_name(
147 source: &runmat_config::project::ProjectSourceFile,
148) -> Option<&str> {
149 source.package_path.as_ref().and_then(|_| {
150 source
151 .qualified_name
152 .contains('.')
153 .then_some(source.qualified_name.as_str())
154 })
155}
156
157fn is_private_dir(path: &Path) -> bool {
158 path.file_name()
159 .and_then(|name| name.to_str())
160 .is_some_and(|name| name.eq_ignore_ascii_case("private"))
161}
162
163fn private_parent_dir_for_source(path: &Path) -> Option<PathBuf> {
164 let private_dir = path.parent()?;
165 if !is_private_dir(private_dir) {
166 return None;
167 }
168 private_dir.parent().map(Path::to_path_buf)
169}
170
171fn source_paths_equivalent(left: &Path, right: &Path) -> bool {
172 if left == right {
173 return true;
174 }
175
176 #[cfg(not(target_arch = "wasm32"))]
177 {
178 if let (Ok(left), Ok(right)) = (std::fs::canonicalize(left), std::fs::canonicalize(right)) {
179 if left == right {
180 return true;
181 }
182 }
183 }
184
185 #[cfg(windows)]
186 {
187 windows_source_path_key(left) == windows_source_path_key(right)
188 }
189 #[cfg(not(windows))]
190 {
191 false
192 }
193}
194
195#[cfg(windows)]
196fn windows_source_path_key(path: &Path) -> String {
197 let mut text = path.to_string_lossy().replace('/', "\\");
198 if let Some(stripped) = text.strip_prefix(r"\\?\UNC\") {
199 text = format!(r"\\{stripped}");
200 } else if let Some(stripped) = text.strip_prefix(r"\\?\") {
201 text = stripped.to_string();
202 }
203 while text.ends_with('\\') && text.len() > 3 {
204 text.pop();
205 }
206 text.to_ascii_lowercase()
207}
208
209fn private_source_visible_to(primary_source_path: &Path, source_path: &Path) -> bool {
210 let Some(private_parent) = private_parent_dir_for_source(source_path) else {
211 return true;
212 };
213 primary_source_path
214 .parent()
215 .is_some_and(|caller_dir| source_paths_equivalent(caller_dir, &private_parent))
216}
217
218fn function_owner_scope_from_qualified_name(qualified_name: &str) -> String {
219 qualified_name
220 .rsplit_once('.')
221 .map(|(owner, _)| owner.to_string())
222 .unwrap_or_default()
223}
224
225fn function_leaf_name(name: &str) -> &str {
226 name.rsplit_once('.').map(|(_, leaf)| leaf).unwrap_or(name)
227}
228
229fn synthetic_private_function_name(owner_scope: &str, leaf_name: &str) -> String {
230 if owner_scope.is_empty() {
231 format!("__private__.{leaf_name}")
232 } else {
233 format!("{owner_scope}.__private__.{leaf_name}")
234 }
235}
236
237fn owner_scope_from_path_skipping_private(source_path: &Path, root_dir: &Path) -> Option<String> {
238 let relative = source_path.strip_prefix(root_dir).ok()?;
239 let parent = relative.parent()?;
240 let mut segments = Vec::new();
241 for component in parent.components() {
242 let segment = component.as_os_str().to_str()?;
243 if segment.eq_ignore_ascii_case("private") {
244 continue;
245 }
246 if let Some(pkg) = segment.strip_prefix('+') {
247 if pkg.is_empty() {
248 return None;
249 }
250 segments.push(pkg.to_string());
251 } else if let Some(class) = segment.strip_prefix('@') {
252 if class.is_empty() {
253 return None;
254 }
255 segments.push(class.to_string());
256 } else {
257 segments.push(segment.to_string());
258 }
259 }
260 Some(segments.join("."))
261}
262
263fn qualify_private_companion_functions(
264 stmts: &mut [runmat_parser::Stmt],
265 owner_scope: &str,
266 primary_visible: bool,
267) -> HashMap<String, String> {
268 let mut aliases = HashMap::new();
269 for stmt in stmts {
270 if let runmat_parser::Stmt::Function { name, .. } = stmt {
271 let leaf = function_leaf_name(name).to_string();
272 let display_name = if primary_visible {
273 leaf.clone()
274 } else {
275 synthetic_private_function_name(owner_scope, &leaf)
276 };
277 *name = display_name.clone();
278 aliases.insert(leaf, display_name);
279 }
280 }
281 aliases
282}
283
284#[derive(Default)]
285pub(super) struct CompanionSourceDiscovery {
286 pub statements: Vec<runmat_parser::Stmt>,
287 pub private_function_names: HashSet<String>,
288 pub private_function_owners: HashMap<String, String>,
289 pub private_function_aliases: HashMap<String, HashMap<String, String>>,
290 pub function_source_contexts: HashMap<String, (String, String)>,
291 private_statement_flags: Vec<bool>,
292}
293
294fn function_names_in_statements(stmts: &[runmat_parser::Stmt]) -> impl Iterator<Item = &str> {
295 stmts.iter().filter_map(|stmt| {
296 if let runmat_parser::Stmt::Function { name, .. } = stmt {
297 Some(name.as_str())
298 } else {
299 None
300 }
301 })
302}
303
304fn source_context_function_names_in_statements(stmts: &[runmat_parser::Stmt]) -> Vec<String> {
305 let mut names = Vec::new();
306 for stmt in stmts {
307 match stmt {
308 runmat_parser::Stmt::Function { name, .. } => names.push(name.clone()),
309 runmat_parser::Stmt::ClassDef {
310 name: class_name,
311 members,
312 ..
313 } => {
314 for member in members {
315 if let runmat_parser::ClassMember::Methods { body, .. } = member {
316 for stmt in body {
317 if let runmat_parser::Stmt::Function { name, .. } = stmt {
318 let display_name = if name.contains('.') {
319 name.clone()
320 } else {
321 format!("{class_name}.{name}")
322 };
323 names.push(display_name);
324 }
325 }
326 }
327 }
328 }
329 _ => {}
330 }
331 }
332 names
333}
334
335impl CompanionSourceDiscovery {
336 fn extend_body(
337 &mut self,
338 body: Vec<runmat_parser::Stmt>,
339 private_owner_scope: Option<&str>,
340 private_aliases: HashMap<String, String>,
341 source_context: Option<(String, String)>,
342 ) {
343 if let Some((source_name, source_text)) = source_context {
344 for function_name in source_context_function_names_in_statements(&body) {
345 self.function_source_contexts
346 .insert(function_name, (source_name.clone(), source_text.clone()));
347 }
348 }
349 let is_private = private_owner_scope.is_some();
350 if is_private {
351 let owner_scope = private_owner_scope.unwrap_or_default();
352 for function_name in function_names_in_statements(&body) {
353 self.private_function_names
354 .insert(function_name.to_string());
355 self.private_function_owners
356 .insert(function_name.to_string(), owner_scope.to_string());
357 }
358 if !private_aliases.is_empty() {
359 self.private_function_aliases
360 .entry(owner_scope.to_string())
361 .or_default()
362 .extend(private_aliases);
363 }
364 }
365 for stmt in body {
366 self.statements.push(stmt);
367 self.private_statement_flags.push(is_private);
368 }
369 }
370
371 fn apply_function_precedence(&mut self, primary_function_names: &HashSet<String>) {
372 let discovered_private_function_names: HashSet<String> = self
373 .statements
374 .iter()
375 .zip(self.private_statement_flags.iter())
376 .filter_map(|(stmt, is_private)| {
377 if !*is_private {
378 return None;
379 }
380 if let runmat_parser::Stmt::Function { name, .. } = stmt {
381 Some(name.clone())
382 } else {
383 None
384 }
385 })
386 .collect();
387
388 let old_statements = std::mem::take(&mut self.statements);
389 let old_private_flags = std::mem::take(&mut self.private_statement_flags);
390 self.private_function_names.clear();
391 self.private_function_owners.clear();
392 self.private_function_aliases.clear();
393
394 for (stmt, is_private) in old_statements.into_iter().zip(old_private_flags) {
395 let keep = match &stmt {
396 runmat_parser::Stmt::Function { name, .. } => {
397 !primary_function_names.contains(name)
398 && (is_private || !discovered_private_function_names.contains(name))
399 }
400 _ => true,
401 };
402 if !keep {
403 if let runmat_parser::Stmt::Function { name, .. } = &stmt {
404 self.function_source_contexts.remove(name);
405 }
406 continue;
407 }
408 if is_private {
409 if let runmat_parser::Stmt::Function { name, .. } = &stmt {
410 self.private_function_names.insert(name.clone());
411 let owner_scope = function_owner_scope_from_qualified_name(name);
412 let owner_scope = if let Some((owner, _)) = name.split_once(".__private__.") {
413 owner.to_string()
414 } else {
415 owner_scope
416 };
417 self.private_function_owners
418 .insert(name.clone(), owner_scope.clone());
419 self.private_function_aliases
420 .entry(owner_scope)
421 .or_default()
422 .insert(function_leaf_name(name).to_string(), name.clone());
423 }
424 }
425 self.statements.push(stmt);
426 self.private_statement_flags.push(is_private);
427 }
428 }
429}
430
431async fn discover_companion_from_composition_graph_async(
432 source_name: &str,
433 cwd: &Path,
434 primary_source_path: &Path,
435 compat_mode: runmat_parser::CompatMode,
436) -> CompanionSourceDiscovery {
437 use runmat_config::project::{
438 build_project_composition_graph_async, discover_project_symbols_from_source_name_async,
439 };
440 let options = ParserOptions::new(compat_mode);
441 let mut out = CompanionSourceDiscovery::default();
442
443 if let Ok(Some(discovered_symbols)) =
444 discover_project_symbols_from_source_name_async(source_name, cwd).await
445 {
446 if let Ok(composition) =
447 build_project_composition_graph_async(&discovered_symbols.manifest_path).await
448 {
449 for package in composition.packages.values() {
450 for source in &package.source_index.files {
451 let file_path = package
452 .project_root
453 .join(&source.source_root)
454 .join(&source.relative_path);
455 if source_paths_equivalent(&file_path, primary_source_path) {
456 continue;
457 }
458 let Ok(contents) = runmat_filesystem::read_to_string_async(&file_path).await
459 else {
460 continue;
461 };
462 if !contents.contains("classdef") && !contents.contains("function") {
463 continue;
464 }
465 let Ok(program) = parse_with_options(&contents, options) else {
466 continue;
467 };
468 let is_class_source = is_class_source_body(&program.body);
469 let is_function_source = is_function_source_body(&program.body);
470 if !is_class_source && !is_function_source {
471 continue;
472 }
473 let mut body = program.body;
474 let private_owner_scope = source
475 .is_private
476 .then(|| function_owner_scope_from_qualified_name(&source.qualified_name));
477 let primary_visible_private = source.is_private
478 && private_source_visible_to(primary_source_path, &file_path);
479 let private_aliases = if let Some(owner_scope) = private_owner_scope.as_deref()
480 {
481 qualify_private_companion_functions(
482 &mut body,
483 owner_scope,
484 primary_visible_private,
485 )
486 } else {
487 HashMap::new()
488 };
489 if is_class_source {
490 if let Some(qualified) = source_index_qualified_class_name(source) {
491 qualify_companion_classdefs(&mut body, qualified);
492 } else if let Some(qualified) =
493 package_class_name_from_path(&file_path, cwd)
494 {
495 qualify_companion_classdefs(&mut body, &qualified);
496 }
497 } else if private_owner_scope.is_none() {
498 if let Some(qualified) = source_index_qualified_function_name(source) {
499 qualify_companion_functions(&mut body, qualified);
500 } else if let Some(qualified) =
501 package_class_name_from_path(&file_path, cwd)
502 {
503 qualify_companion_functions(&mut body, &qualified);
504 }
505 }
506 out.extend_body(
507 body,
508 private_owner_scope.as_deref(),
509 private_aliases,
510 Some((file_path.to_string_lossy().to_string(), contents)),
511 );
512 }
513 }
514 }
515 }
516
517 out
518}
519
520pub(super) async fn discover_companion_source_statements_async(
521 source_name: &str,
522 compat_mode: runmat_parser::CompatMode,
523) -> CompanionSourceDiscovery {
524 let Some(cwd) = source_lookup_cwd(source_name) else {
525 return CompanionSourceDiscovery::default();
526 };
527 let primary_source_path = resolved_source_path(source_name, &cwd);
528 let Some(parent) = primary_source_path.parent() else {
529 return CompanionSourceDiscovery::default();
530 };
531 let mut out = discover_companion_from_composition_graph_async(
532 source_name,
533 &cwd,
534 &primary_source_path,
535 compat_mode,
536 )
537 .await;
538 if !out.statements.is_empty() {
539 return out;
540 }
541 let options = ParserOptions::new(compat_mode);
542 let mut stack = vec![parent.to_path_buf()];
543 while let Some(dir) = stack.pop() {
544 let Ok(entries) = runmat_filesystem::read_dir_async(&dir).await else {
545 continue;
546 };
547 for entry in entries {
548 let path = entry.path().to_path_buf();
549 if source_paths_equivalent(&path, &primary_source_path) {
550 continue;
551 }
552 if entry.is_dir() {
553 let is_package_dir = entry
554 .file_name()
555 .to_str()
556 .is_some_and(|name| name.starts_with('+'));
557 let is_class_dir = entry
558 .file_name()
559 .to_str()
560 .is_some_and(|name| name.starts_with('@'));
561 if is_package_dir || is_class_dir || is_private_dir(&path) {
562 stack.push(path);
563 }
564 continue;
565 }
566 if !path
567 .extension()
568 .and_then(|ext| ext.to_str())
569 .is_some_and(|ext| ext.eq_ignore_ascii_case("m"))
570 {
571 continue;
572 }
573 let Ok(contents) = runmat_filesystem::read_to_string_async(&path).await else {
574 continue;
575 };
576 if !contents.contains("classdef") && !contents.contains("function") {
577 continue;
578 }
579 let Ok(program) = parse_with_options(&contents, options) else {
580 continue;
581 };
582 let is_class_source = is_class_source_body(&program.body);
583 let is_function_source = is_function_source_body(&program.body);
584 if !is_class_source && !is_function_source {
585 continue;
586 }
587 let mut body = program.body;
588 let private_owner_scope = private_parent_dir_for_source(&path)
589 .and_then(|_| owner_scope_from_path_skipping_private(&path, parent));
590 let primary_visible_private = private_owner_scope.is_some()
591 && private_source_visible_to(&primary_source_path, &path);
592 let private_aliases = if let Some(owner_scope) = private_owner_scope.as_deref() {
593 qualify_private_companion_functions(&mut body, owner_scope, primary_visible_private)
594 } else {
595 HashMap::new()
596 };
597 if is_class_source {
598 if let Some(qualified) = package_class_name_from_path(&path, parent) {
599 qualify_companion_classdefs(&mut body, &qualified);
600 }
601 } else if private_owner_scope.is_none() {
602 if let Some(qualified) = package_class_name_from_path(&path, parent) {
603 qualify_companion_functions(&mut body, &qualified);
604 }
605 }
606 out.extend_body(
607 body,
608 private_owner_scope.as_deref(),
609 private_aliases,
610 Some((path.to_string_lossy().to_string(), contents)),
611 );
612 }
613 }
614 out
615}
616
617impl RunMatSession {
618 #[cfg(test)]
619 pub(crate) fn compile_input_for_source_name(
620 &mut self,
621 source_name: &str,
622 input: &str,
623 ) -> std::result::Result<PreparedExecution, RunError> {
624 let previous_source_name = self.active_source_name.clone();
625 self.active_source_name = source_name.to_string();
626 let result = self.compile_input(input);
627 self.active_source_name = previous_source_name;
628 result
629 }
630
631 pub(crate) fn compile_input(
632 &mut self,
633 input: &str,
634 ) -> std::result::Result<PreparedExecution, RunError> {
635 let source_name = self.current_source_name().to_string();
636 let source_id = self.source_pool.intern(&source_name, input);
637 let (
638 ast,
639 private_companion_function_names,
640 private_companion_function_owners,
641 private_companion_function_aliases,
642 companion_function_source_ids,
643 ) = {
644 let _span = info_span!("runtime.parse").entered();
645 let mut ast = parse_with_options(input, ParserOptions::new(self.compat_mode))?;
646 let primary_function_names = function_names_in_statements(&ast.body)
647 .map(ToString::to_string)
648 .collect::<HashSet<_>>();
649 let mut companion = self
650 .pending_companion_source_discovery
651 .take()
652 .unwrap_or_default();
653 companion.apply_function_precedence(&primary_function_names);
654 let private_companion_function_names =
655 std::mem::take(&mut companion.private_function_names);
656 let private_companion_function_owners =
657 std::mem::take(&mut companion.private_function_owners);
658 let private_companion_function_aliases =
659 std::mem::take(&mut companion.private_function_aliases);
660 let companion_function_source_ids =
661 std::mem::take(&mut companion.function_source_contexts)
662 .into_iter()
663 .map(|(function_name, (source_name, source_text))| {
664 (
665 function_name,
666 self.source_pool.intern(&source_name, &source_text),
667 )
668 })
669 .collect::<HashMap<_, _>>();
670 if !companion.statements.is_empty() {
671 ast.body.append(&mut companion.statements);
672 }
673 (
674 ast,
675 private_companion_function_names,
676 private_companion_function_owners,
677 private_companion_function_aliases,
678 companion_function_source_ids,
679 )
680 };
681 let lowering = {
682 let _span = info_span!("runtime.lower").entered();
683 let function_names = self.function_registry.names.clone();
684 let workspace_bindings = self.lowering_workspace_bindings();
685 let known_project_symbols = discover_known_project_symbols(&source_name);
686 runmat_hir::lower(
687 &ast,
688 &LoweringContext::new(&workspace_bindings)
689 .with_bound_functions(&function_names)
690 .with_known_project_symbols(&known_project_symbols)
691 .with_private_functions(
692 &private_companion_function_owners,
693 &private_companion_function_aliases,
694 )
695 .with_runmat_extensions_enabled(self.compat_mode.allows_runmat_extensions())
696 .with_top_level_await_enabled(self.top_level_await_enabled),
697 )?
698 };
699 let mir = {
700 let _span = info_span!("runtime.compile.mir").entered();
701 runmat_mir::lowering::lower_assembly(&lowering.assembly)?
702 };
703 let analysis = {
704 let _span = info_span!("runtime.analyze").entered();
705 runmat_mir::analysis::analyze_assembly(&mir)
706 };
707 let mut bytecode = {
708 let _span = info_span!("runtime.compile.bytecode").entered();
709 self.compile_semantic_bytecode_from_mir(&lowering.assembly, &mir)?
710 };
711 bytecode.source_id = Some(source_id);
712 for function in bytecode.function_registry.functions.values_mut() {
713 function.source_id = companion_function_source_ids
714 .get(&function.display_name)
715 .copied()
716 .or(Some(source_id));
717 }
718 bytecode.bound_functions = bytecode.function_registry.functions.clone();
719 let (function_registry_after_success, next_semantic_function_id_after_success) = self
720 .prepare_session_semantic_function_registry(
721 &mut bytecode,
722 &private_companion_function_names,
723 );
724 Ok(PreparedExecution {
725 ast,
726 lowering,
727 analysis,
728 bytecode,
729 function_registry_after_success,
730 next_semantic_function_id_after_success,
731 })
732 }
733
734 pub(crate) fn populate_callstack(&self, error: &mut RuntimeError) {
735 if !error.context.call_stack.is_empty() || error.context.call_frames.is_empty() {
736 return;
737 }
738 let mut rendered = Vec::new();
739 if error.context.call_frames_elided > 0 {
740 rendered.push(format!(
741 "... {} frames elided ...",
742 error.context.call_frames_elided
743 ));
744 }
745 for frame in error.context.call_frames.iter().rev() {
746 let mut line = frame.function.clone();
747 if let (Some(source_id), Some((start, _end))) = (frame.source_id, frame.span) {
748 if let Some(source) = self.source_pool.get(SourceId(source_id)) {
749 let (line_num, col) = line_col_from_offset(&source.text, start);
750 line = format!("{} @ {}:{}:{}", frame.function, source.name, line_num, col);
751 }
752 }
753 rendered.push(line);
754 }
755 error.context.call_stack = rendered;
756 }
757
758 fn compile_semantic_bytecode_from_mir(
759 &self,
760 assembly: &runmat_hir::HirAssembly,
761 mir: &runmat_mir::MirAssembly,
762 ) -> std::result::Result<runmat_vm::Bytecode, RunError> {
763 let Some(entrypoint) = assembly.entrypoints.first() else {
764 let bound_functions = runmat_vm::compile_semantic_function_registry(assembly, mir)?;
765 let function_registry = runmat_vm::FunctionRegistry::new(bound_functions.clone());
766 let mut bytecode = runmat_vm::Bytecode::empty();
767 bytecode.bound_functions = bound_functions;
768 bytecode.function_registry = function_registry;
769 return Ok(bytecode);
770 };
771 Ok(runmat_vm::compile(assembly, mir, entrypoint.id)?)
772 }
773
774 fn prepare_session_semantic_function_registry(
775 &self,
776 bytecode: &mut runmat_vm::Bytecode,
777 private_companion_function_names: &HashSet<String>,
778 ) -> (runmat_vm::FunctionRegistry, usize) {
779 let mut session_registry = self.function_registry.clone();
780 let mut execution_registry = session_registry.clone();
781 let mut next_semantic_function_id = self.next_semantic_function_id;
782 let current_registry = bytecode.function_registry();
783 if current_registry.functions.is_empty() {
784 bytecode.function_registry = session_registry.clone();
785 bytecode.bound_functions = bytecode.function_registry.functions.clone();
786 bind_semantic_function_references(bytecode);
787 return (session_registry, next_semantic_function_id);
788 }
789
790 let mut remap = HashMap::new();
791 let mut ids: Vec<_> = current_registry.functions.keys().copied().collect();
792 ids.sort_by_key(|id| id.0);
793 for old_id in ids {
794 let new_id = runmat_hir::FunctionId(next_semantic_function_id);
795 next_semantic_function_id += 1;
796 remap.insert(old_id, new_id);
797 }
798
799 for instr in &mut bytecode.instructions {
800 remap_semantic_function_instr(instr, &remap);
801 }
802 let name_remaps: Vec<(String, runmat_hir::FunctionId)> = current_registry
803 .names
804 .iter()
805 .map(|(name, function)| (name.clone(), *function))
806 .collect();
807
808 let mut replaced_sources = Vec::new();
809 for function in current_registry.functions.values() {
810 if private_companion_function_names.contains(&function.display_name) {
811 continue;
812 }
813 if let Some(existing_id) = session_registry.resolve_name(&function.display_name) {
814 if let Some(source_id) = session_registry
815 .get(existing_id)
816 .and_then(|existing| existing.source_id)
817 {
818 if !replaced_sources.contains(&source_id) {
819 replaced_sources.push(source_id);
820 }
821 }
822 }
823 }
824 for source_id in replaced_sources {
825 session_registry.remove_source(source_id);
826 }
827
828 for (old_id, function) in current_registry.functions {
829 let Some(new_id) = remap.get(&old_id).copied() else {
830 continue;
831 };
832 let mut function = function;
833 function.function = new_id;
834 function.source_id = function.source_id.or(bytecode.source_id);
835 for instr in &mut function.instructions {
836 remap_semantic_function_instr(instr, &remap);
837 }
838 let persist_function =
839 !private_companion_function_names.contains(&function.display_name);
840 execution_registry.insert_replacing_name(function.clone());
841 if persist_function {
842 session_registry.insert_replacing_name(function);
843 }
844 }
845 for (name, old_id) in name_remaps {
846 let Some(new_id) = remap.get(&old_id).copied() else {
847 continue;
848 };
849 execution_registry.names.insert(name.clone(), new_id);
850 if !private_companion_function_names.contains(&name) {
851 session_registry.names.insert(name, new_id);
852 }
853 }
854
855 bytecode.function_registry = execution_registry;
856 bytecode.bound_functions = bytecode.function_registry.functions.clone();
857 bind_semantic_function_references(bytecode);
858 (session_registry, next_semantic_function_id)
859 }
860
861 pub(crate) fn normalize_error_namespace(&self, error: &mut RuntimeError) {
862 let Some(identifier) = error.identifier.clone() else {
863 return;
864 };
865 let suffix = identifier
866 .split_once(':')
867 .map(|(_, suffix)| suffix)
868 .unwrap_or(identifier.as_str());
869 error.identifier = Some(format!("{}:{suffix}", self.error_namespace));
870 }
871
872 pub fn compile_fusion_plan(
874 &mut self,
875 input: &str,
876 ) -> std::result::Result<Option<FusionPlanSnapshot>, RunError> {
877 let prepared = self.compile_input(input)?;
878 let runtime_groups = prepared.bytecode.runtime_fusion_groups();
879 let (runtime_graph, runtime_graph_source) = prepared
880 .bytecode
881 .runtime_accel_graph_for_fusion_with_source(&runtime_groups);
882 Ok(build_fusion_snapshot(
883 &runtime_groups,
884 &prepared
885 .bytecode
886 .fusion_metadata
887 .mir_fusion_candidate_groups,
888 &prepared.bytecode.fusion_metadata.instruction_windows,
889 Some(FusionPlannerMetadata {
890 source: "semantic-mir-analysis".to_string(),
891 accel_graph_state: if runtime_graph.is_some() {
892 "present".to_string()
893 } else {
894 "missing".to_string()
895 },
896 accel_graph_source: runtime_graph_source.as_str().to_string(),
897 mir_local_fact_count: mir_local_fact_count_for_entrypoint(
898 &prepared.analysis,
899 &prepared.lowering.assembly,
900 ),
901 mir_diagnostic_count: prepared.analysis.diagnostics.len(),
902 mir_fusion_signal_count: prepared.bytecode.fusion_metadata.mir_fusion_signal_count,
903 mir_fusion_candidate_group_count: prepared
904 .bytecode
905 .fusion_metadata
906 .mir_fusion_candidate_group_count,
907 mir_semantic_instruction_window_count: prepared
908 .bytecode
909 .fusion_metadata
910 .instruction_window_count,
911 }),
912 ))
913 }
914
915 pub(crate) fn prepare_variable_array_for_execution(
916 &mut self,
917 bytecode: &runmat_vm::Bytecode,
918 updated_var_mapping: &HashMap<String, usize>,
919 debug_trace: bool,
920 ) {
921 let max_var_id = updated_var_mapping.values().copied().max().unwrap_or(0);
923 let required_len = std::cmp::max(bytecode.var_count, max_var_id + 1);
924 let mut new_variable_array = vec![Value::Num(0.0); required_len];
925 if debug_trace {
926 debug!(
927 bytecode_var_count = bytecode.var_count,
928 required_len, max_var_id, "[repl] prepare variable array"
929 );
930 }
931
932 for (var_name, &new_var_id) in updated_var_mapping {
934 if new_var_id < new_variable_array.len() {
935 if let Some(value) = self.workspace_values.get(var_name) {
936 if debug_trace {
937 debug!(
938 var_name,
939 var_id = new_var_id,
940 ?value,
941 "[repl] prepare set var"
942 );
943 }
944 new_variable_array[new_var_id] = value.clone();
945 }
946 } else if debug_trace {
947 debug!(
948 var_name,
949 var_id = new_var_id,
950 len = new_variable_array.len(),
951 "[repl] prepare skipping var"
952 );
953 }
954 }
955
956 self.variable_array = new_variable_array;
958 }
959}
960
961fn remap_semantic_function_instr(
962 instr: &mut runmat_vm::Instr,
963 remap: &HashMap<runmat_hir::FunctionId, runmat_hir::FunctionId>,
964) {
965 match instr {
966 runmat_vm::Instr::CreateSemanticClosure(function, _, _)
967 | runmat_vm::Instr::CreateBoundFunctionHandle(function, _)
968 | runmat_vm::Instr::CreateSemanticFuture(function, _, _)
969 | runmat_vm::Instr::CreateSemanticFutureExpandMultiOutput(function, _, _)
970 | runmat_vm::Instr::CallSemanticFunctionMulti(function, _, _)
971 | runmat_vm::Instr::CallSemanticFunctionMultiUsingOutputSlot(function, _, _)
972 | runmat_vm::Instr::CallSemanticFunctionExpandMultiOutput(function, _, _) => {
973 if let Some(new_id) = remap.get(function).copied() {
974 *function = new_id;
975 }
976 }
977 runmat_vm::Instr::CallSemanticNestedFunctionMulti { function, .. }
978 | runmat_vm::Instr::CallSemanticNestedFunctionMultiUsingOutputSlot { function, .. }
979 | runmat_vm::Instr::CallSemanticNestedFunctionExpandMultiOutput { function, .. } => {
980 if let Some(new_id) = remap.get(function).copied() {
981 *function = new_id;
982 }
983 }
984 runmat_vm::Instr::IndexSliceExpr {
985 range_start_exprs,
986 range_step_exprs,
987 range_end_exprs,
988 end_numeric_exprs,
989 ..
990 }
991 | runmat_vm::Instr::StoreSliceExpr {
992 range_start_exprs,
993 range_step_exprs,
994 range_end_exprs,
995 end_numeric_exprs,
996 ..
997 } => {
998 remap_optional_end_exprs(range_start_exprs, remap);
999 remap_optional_end_exprs(range_step_exprs, remap);
1000 for expr in range_end_exprs {
1001 remap_semantic_function_end_expr(expr, remap);
1002 }
1003 for (_, expr) in end_numeric_exprs {
1004 remap_semantic_function_end_expr(expr, remap);
1005 }
1006 }
1007 _ => {}
1008 }
1009}
1010
1011fn bind_semantic_function_references(bytecode: &mut runmat_vm::Bytecode) {
1012 let registry = bytecode.function_registry.clone();
1013 bind_semantic_callback_literals(bytecode, ®istry);
1014 for instr in &mut bytecode.instructions {
1015 match instr {
1016 runmat_vm::Instr::CreateFunctionHandle(name) => {
1017 if let Some(function) = registry.resolve_name(name) {
1018 *instr = runmat_vm::Instr::CreateBoundFunctionHandle(function, name.clone());
1019 }
1020 }
1021 runmat_vm::Instr::IndexSliceExpr {
1022 range_start_exprs,
1023 range_step_exprs,
1024 range_end_exprs,
1025 end_numeric_exprs,
1026 ..
1027 }
1028 | runmat_vm::Instr::StoreSliceExpr {
1029 range_start_exprs,
1030 range_step_exprs,
1031 range_end_exprs,
1032 end_numeric_exprs,
1033 ..
1034 } => {
1035 bind_optional_end_exprs(range_start_exprs, ®istry);
1036 bind_optional_end_exprs(range_step_exprs, ®istry);
1037 for expr in range_end_exprs {
1038 bind_semantic_function_end_expr(expr, ®istry);
1039 }
1040 for (_, expr) in end_numeric_exprs {
1041 bind_semantic_function_end_expr(expr, ®istry);
1042 }
1043 }
1044 _ => {}
1045 }
1046 }
1047}
1048
1049fn bind_semantic_callback_literals(
1050 bytecode: &mut runmat_vm::Bytecode,
1051 registry: &runmat_vm::FunctionRegistry,
1052) {
1053 let mut stack: Vec<usize> = Vec::new();
1054 let mut replacements = Vec::new();
1055
1056 for (pc, instr) in bytecode.instructions.iter().enumerate() {
1057 match instr {
1058 runmat_vm::Instr::CallBuiltinMulti(name, argc, _)
1059 if matches!(name.as_str(), "cellfun" | "arrayfun") && *argc > 0 =>
1060 {
1061 if stack.len() >= *argc {
1062 let producer = stack[stack.len() - *argc];
1063 if let Some((function, display_name)) =
1064 callback_literal(bytecode.instructions.get(producer), registry)
1065 {
1066 replacements.push((producer, function, display_name));
1067 }
1068 }
1069 }
1070 runmat_vm::Instr::CallFevalMulti(argc, _) => {
1071 let pops = *argc + 1;
1072 if stack.len() >= pops {
1073 let producer = stack[stack.len() - pops];
1074 if let Some((function, display_name)) =
1075 callback_literal(bytecode.instructions.get(producer), registry)
1076 {
1077 replacements.push((producer, function, display_name));
1078 }
1079 }
1080 }
1081 runmat_vm::Instr::CallFevalMultiUsingOutputSlot(argc, _) => {
1082 let pops = *argc + 1;
1083 if stack.len() >= pops {
1084 let producer = stack[stack.len() - pops];
1085 if let Some((function, display_name)) =
1086 callback_literal(bytecode.instructions.get(producer), registry)
1087 {
1088 replacements.push((producer, function, display_name));
1089 }
1090 }
1091 }
1092 runmat_vm::Instr::CallFevalExpandMultiOutput(_, _) => {
1093 if let Some(effect) = instr.stack_effect() {
1094 if stack.len() >= effect.pops {
1095 let producer = stack[stack.len() - effect.pops];
1096 if let Some((function, display_name)) =
1097 callback_literal(bytecode.instructions.get(producer), registry)
1098 {
1099 replacements.push((producer, function, display_name));
1100 }
1101 }
1102 }
1103 }
1104 runmat_vm::Instr::CallFevalExpandMultiOutputUsingOutputSlot(_, _) => {
1105 if let Some(effect) = instr.stack_effect() {
1106 if stack.len() >= effect.pops {
1107 let producer = stack[stack.len() - effect.pops];
1108 if let Some((function, display_name)) =
1109 callback_literal(bytecode.instructions.get(producer), registry)
1110 {
1111 replacements.push((producer, function, display_name));
1112 }
1113 }
1114 }
1115 }
1116 _ => {}
1117 }
1118
1119 let Some(effect) = instr.stack_effect() else {
1120 stack.clear();
1121 continue;
1122 };
1123 if effect.pops > stack.len() {
1124 stack.clear();
1125 } else {
1126 for _ in 0..effect.pops {
1127 stack.pop();
1128 }
1129 }
1130 for _ in 0..effect.pushes {
1131 stack.push(pc);
1132 }
1133 }
1134
1135 for (producer, function, display_name) in replacements {
1136 bytecode.instructions[producer] =
1137 runmat_vm::Instr::CreateBoundFunctionHandle(function, display_name);
1138 }
1139}
1140
1141fn callback_literal(
1142 instr: Option<&runmat_vm::Instr>,
1143 registry: &runmat_vm::FunctionRegistry,
1144) -> Option<(runmat_hir::FunctionId, String)> {
1145 let text = match instr? {
1146 runmat_vm::Instr::LoadString(text) | runmat_vm::Instr::LoadCharRow(text) => text,
1147 _ => return None,
1148 };
1149 let name = text.trim().strip_prefix('@').unwrap_or(text.trim()).trim();
1150 if name.is_empty() {
1151 return None;
1152 }
1153 registry
1154 .resolve_name(name)
1155 .map(|function| (function, name.to_string()))
1156}
1157
1158fn bind_optional_end_exprs(
1159 exprs: &mut [Option<runmat_vm::EndExpr>],
1160 registry: &runmat_vm::FunctionRegistry,
1161) {
1162 for expr in exprs.iter_mut().flatten() {
1163 bind_semantic_function_end_expr(expr, registry);
1164 }
1165}
1166
1167fn bind_semantic_function_end_expr(
1168 expr: &mut runmat_vm::EndExpr,
1169 registry: &runmat_vm::FunctionRegistry,
1170) {
1171 match expr {
1172 runmat_vm::EndExpr::ResolvedCall { identity, args, .. } => {
1173 if let runmat_hir::CallableIdentity::DynamicName(name) = identity {
1174 let dynamic_name = name.0.clone();
1175 if let Some(function) = registry.resolve_name(&dynamic_name) {
1176 *identity = runmat_hir::CallableIdentity::BoundFunction(function);
1177 }
1178 }
1179 for arg in args {
1180 bind_semantic_function_end_expr(arg, registry);
1181 }
1182 }
1183 runmat_vm::EndExpr::Add(lhs, rhs)
1184 | runmat_vm::EndExpr::Sub(lhs, rhs)
1185 | runmat_vm::EndExpr::Mul(lhs, rhs)
1186 | runmat_vm::EndExpr::Div(lhs, rhs)
1187 | runmat_vm::EndExpr::LeftDiv(lhs, rhs)
1188 | runmat_vm::EndExpr::Pow(lhs, rhs) => {
1189 bind_semantic_function_end_expr(lhs, registry);
1190 bind_semantic_function_end_expr(rhs, registry);
1191 }
1192 runmat_vm::EndExpr::Neg(inner)
1193 | runmat_vm::EndExpr::Pos(inner)
1194 | runmat_vm::EndExpr::Floor(inner)
1195 | runmat_vm::EndExpr::Ceil(inner)
1196 | runmat_vm::EndExpr::Round(inner)
1197 | runmat_vm::EndExpr::Fix(inner) => bind_semantic_function_end_expr(inner, registry),
1198 runmat_vm::EndExpr::End | runmat_vm::EndExpr::Const(_) | runmat_vm::EndExpr::Var(_) => {}
1199 }
1200}
1201
1202fn remap_optional_end_exprs(
1203 exprs: &mut [Option<runmat_vm::EndExpr>],
1204 remap: &HashMap<runmat_hir::FunctionId, runmat_hir::FunctionId>,
1205) {
1206 for expr in exprs.iter_mut().flatten() {
1207 remap_semantic_function_end_expr(expr, remap);
1208 }
1209}
1210
1211fn remap_semantic_function_end_expr(
1212 expr: &mut runmat_vm::EndExpr,
1213 remap: &HashMap<runmat_hir::FunctionId, runmat_hir::FunctionId>,
1214) {
1215 match expr {
1216 runmat_vm::EndExpr::ResolvedCall { identity, args, .. } => {
1217 match identity {
1218 runmat_hir::CallableIdentity::BoundFunction(function)
1219 | runmat_hir::CallableIdentity::AnonymousFunction(function) => {
1220 if let Some(new_id) = remap.get(function).copied() {
1221 *function = new_id;
1222 }
1223 }
1224 _ => {}
1225 }
1226 for arg in args {
1227 remap_semantic_function_end_expr(arg, remap);
1228 }
1229 }
1230 runmat_vm::EndExpr::Add(lhs, rhs)
1231 | runmat_vm::EndExpr::Sub(lhs, rhs)
1232 | runmat_vm::EndExpr::Mul(lhs, rhs)
1233 | runmat_vm::EndExpr::Div(lhs, rhs)
1234 | runmat_vm::EndExpr::LeftDiv(lhs, rhs)
1235 | runmat_vm::EndExpr::Pow(lhs, rhs) => {
1236 remap_semantic_function_end_expr(lhs, remap);
1237 remap_semantic_function_end_expr(rhs, remap);
1238 }
1239 runmat_vm::EndExpr::Neg(inner)
1240 | runmat_vm::EndExpr::Pos(inner)
1241 | runmat_vm::EndExpr::Floor(inner)
1242 | runmat_vm::EndExpr::Ceil(inner)
1243 | runmat_vm::EndExpr::Round(inner)
1244 | runmat_vm::EndExpr::Fix(inner) => remap_semantic_function_end_expr(inner, remap),
1245 runmat_vm::EndExpr::End | runmat_vm::EndExpr::Const(_) | runmat_vm::EndExpr::Var(_) => {}
1246 }
1247}