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