Skip to main content

runmat_core/session/
compile.rs

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    /// Compile the input and produce a fusion plan snapshot without executing.
873    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        // Create a new variable array of the correct size
922        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        // Populate with existing values based on the variable mapping
933        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        // Update our variable array and mapping
957        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, &registry);
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, &registry);
1036                bind_optional_end_exprs(range_step_exprs, &registry);
1037                for expr in range_end_exprs {
1038                    bind_semantic_function_end_expr(expr, &registry);
1039                }
1040                for (_, expr) in end_numeric_exprs {
1041                    bind_semantic_function_end_expr(expr, &registry);
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}