Skip to main content

runmat_vm/bytecode/
compile.rs

1#[cfg(feature = "native-accel")]
2use crate::accel::graph::build_accel_graph;
3#[cfg(feature = "native-accel")]
4use crate::accel::stack_layout::annotate_fusion_groups_with_stack_layout;
5#[cfg(feature = "native-accel")]
6use crate::bytecode::instr::Instr;
7use crate::bytecode::program::FunctionBytecode;
8use crate::bytecode::{Bytecode, FunctionRegistry};
9use crate::compiler::{CompileError, Compiler};
10use crate::layout::derive_layout;
11#[cfg(feature = "native-accel")]
12use runmat_builtins::{builtin_functions, AccelTag, BuiltinSemanticKind};
13use runmat_hir::{EntrypointId, FunctionId, HirAssembly};
14use runmat_mir::MirAssembly;
15use runmat_mir::{MirRvalue, MirStmtKind, MirTerminatorKind};
16use std::collections::{HashMap, HashSet};
17
18pub fn compile(
19    hir: &HirAssembly,
20    mir: &MirAssembly,
21    entrypoint: EntrypointId,
22) -> Result<Bytecode, CompileError> {
23    let layout = derive_layout(hir, mir)
24        .map_err(|err| CompileError::new(format!("failed to derive VM layout: {err:?}")))?;
25    let mut c = Compiler::new(hir, mir, layout, entrypoint)?;
26    c.compile()?;
27    let bound_functions =
28        compile_semantic_functions(hir, mir, c.layout.as_ref().unwrap(), Some(entrypoint))?;
29    let function_registry = FunctionRegistry::new(bound_functions.clone());
30    let (var_names, initially_unassigned_slots) = c
31        .layout
32        .as_ref()
33        .and_then(|layout| {
34            let entrypoint_layout = layout.entrypoints.get(&entrypoint)?;
35            let function_layout = layout.functions.get(&entrypoint_layout.target)?;
36            Some((
37                entrypoint_layout
38                    .exports
39                    .iter()
40                    .map(|export| (export.slot.0, export.name.clone()))
41                    .collect(),
42                function_layout_initially_unassigned_slots(hir, function_layout),
43            ))
44        })
45        .unwrap_or_default();
46    let entrypoint_target = hir
47        .entrypoints
48        .iter()
49        .find(|candidate| candidate.id == entrypoint)
50        .map(|candidate| candidate.target);
51    #[cfg(feature = "native-accel")]
52    let mut fusion_metadata = derive_semantic_fusion_metadata(mir, entrypoint_target);
53    let instruction_windows = derive_semantic_fusion_instruction_windows(
54        &c.instructions,
55        &c.instr_spans,
56        &fusion_metadata.mir_fusion_candidate_groups,
57    );
58    fusion_metadata.instruction_window_count = instruction_windows.len();
59    fusion_metadata.instruction_windows = instruction_windows.clone();
60    #[cfg(feature = "native-accel")]
61    let (accel_graph, fusion_groups) = if fusion_metadata.mir_fusion_candidate_group_count == 0
62        || instruction_windows.is_empty()
63    {
64        (None, Vec::new())
65    } else {
66        let accel_graph = build_accel_graph(&c.instructions, &c.var_types);
67        // Compile-time ownership is semantic-window scaffolding only; runtime fusion plan
68        // preparation performs node reconciliation against the accel graph.
69        let mut fusion_groups =
70            derive_semantic_fusion_groups_from_instruction_windows(&instruction_windows);
71        if !fusion_groups.is_empty() {
72            annotate_fusion_groups_with_stack_layout(
73                &c.instructions,
74                &accel_graph,
75                &mut fusion_groups,
76            );
77            fusion_groups.retain(|group| {
78                fusion_group_within_semantic_candidate_spans(
79                    group,
80                    &c.instr_spans,
81                    &fusion_metadata.mir_fusion_candidate_groups,
82                )
83            });
84        }
85        // Preserve accel graph whenever semantic candidate/window scaffolds exist.
86        // Runtime planning owns final executable-group reconciliation and may still
87        // recover groups from semantic windows when compile groups are empty.
88        (Some(accel_graph), fusion_groups)
89    };
90    let async_metadata = derive_semantic_async_metadata(mir, entrypoint_target);
91
92    let source_id = entrypoint_target
93        .and_then(|function_id| {
94            hir.functions
95                .iter()
96                .find(|function| function.id == function_id)
97        })
98        .and_then(|function| hir.modules.get(function.module.0))
99        .map(|module| module.source_id);
100
101    Ok(Bytecode {
102        instructions: c.instructions,
103        instr_spans: c.instr_spans,
104        call_arg_spans: c.call_arg_spans,
105        source_id,
106        var_count: c.var_count,
107        bound_functions,
108        function_registry,
109        var_types: c.var_types,
110        var_names,
111        initially_unassigned_slots,
112        layout: c.layout,
113        async_metadata,
114        #[cfg(feature = "native-accel")]
115        accel_graph,
116        #[cfg(feature = "native-accel")]
117        fusion_groups,
118        #[cfg(feature = "native-accel")]
119        fusion_metadata,
120    })
121}
122
123fn derive_semantic_async_metadata(
124    mir: &MirAssembly,
125    entrypoint_target: Option<FunctionId>,
126) -> crate::bytecode::AsyncMetadata {
127    let mut spawn_sites = Vec::new();
128    let mut await_sites = Vec::new();
129    let mut function_ids: Vec<_> = if let Some(function) = entrypoint_target {
130        vec![function]
131    } else {
132        mir.bodies.keys().copied().collect()
133    };
134    function_ids.sort_by_key(|id| id.0);
135    for function_id in function_ids {
136        let Some(body) = mir.bodies.get(&function_id) else {
137            continue;
138        };
139        for block in &body.blocks {
140            for (stmt_index, stmt) in block.statements.iter().enumerate() {
141                let value = match &stmt.kind {
142                    MirStmtKind::Assign { value, .. }
143                    | MirStmtKind::MultiAssign { value, .. }
144                    | MirStmtKind::Expr(value) => value,
145                    MirStmtKind::PlaceMutation(_)
146                    | MirStmtKind::WorkspaceEffect { .. }
147                    | MirStmtKind::EnvironmentEffect(_) => continue,
148                };
149                if matches!(value, MirRvalue::Spawn(_)) {
150                    spawn_sites.push(crate::bytecode::SpawnSite {
151                        function: body.function,
152                        block: block.id,
153                        stmt_index,
154                    });
155                }
156            }
157            if let MirTerminatorKind::Await { resume, .. } = &block.terminator.kind {
158                await_sites.push(crate::bytecode::AwaitSite {
159                    function: body.function,
160                    block: block.id,
161                    resume: *resume,
162                });
163            }
164        }
165    }
166    crate::bytecode::AsyncMetadata {
167        mir_spawn_site_count: spawn_sites.len(),
168        mir_spawn_sites: spawn_sites,
169        mir_await_site_count: await_sites.len(),
170        mir_await_sites: await_sites,
171        runtime_model: crate::bytecode::program::AsyncRuntimeModel::LazyFutureDescriptorLane,
172    }
173}
174
175#[cfg(feature = "native-accel")]
176fn derive_semantic_fusion_metadata(
177    mir: &MirAssembly,
178    entrypoint_target: Option<FunctionId>,
179) -> crate::bytecode::FusionMetadata {
180    let (mir_fusion_signal_count, mir_fusion_candidate_groups) =
181        derive_semantic_fusion_candidate_groups(mir, entrypoint_target);
182    crate::bytecode::FusionMetadata {
183        mir_fusion_signal_count,
184        mir_fusion_candidate_group_count: mir_fusion_candidate_groups.len(),
185        mir_fusion_candidate_groups,
186        instruction_window_count: 0,
187        instruction_windows: Vec::new(),
188    }
189}
190
191#[cfg(feature = "native-accel")]
192fn derive_semantic_fusion_candidate_groups(
193    mir: &MirAssembly,
194    entrypoint_target: Option<FunctionId>,
195) -> (usize, Vec<crate::bytecode::FusionCandidateGroup>) {
196    let mut signal_count = 0usize;
197    let mut groups = Vec::new();
198    let mut function_ids: Vec<_> = if let Some(function) = entrypoint_target {
199        vec![function]
200    } else {
201        mir.bodies.keys().copied().collect()
202    };
203    function_ids.sort_by_key(|id| id.0);
204    for function_id in function_ids {
205        let Some(body) = mir.bodies.get(&function_id) else {
206            continue;
207        };
208        for block in &body.blocks {
209            let mut run_len = 0usize;
210            for (stmt_index, stmt) in block.statements.iter().enumerate() {
211                let value = match &stmt.kind {
212                    MirStmtKind::Assign { value, .. }
213                    | MirStmtKind::MultiAssign { value, .. }
214                    | MirStmtKind::Expr(value) => value,
215                    MirStmtKind::PlaceMutation(_)
216                    | MirStmtKind::WorkspaceEffect { .. }
217                    | MirStmtKind::EnvironmentEffect(_) => {
218                        if run_len >= 2 {
219                            let stmt_start = stmt_index - run_len;
220                            let stmt_end = stmt_index;
221                            groups.push(crate::bytecode::FusionCandidateGroup {
222                                id: groups.len(),
223                                signal_count: run_len,
224                                function: body.function,
225                                block: block.id,
226                                stmt_start,
227                                stmt_end,
228                                source_span: merge_stmt_run_span(block, stmt_start, stmt_end),
229                            });
230                        }
231                        run_len = 0;
232                        continue;
233                    }
234                };
235                if rvalue_has_fusion_signal(value) {
236                    signal_count += 1;
237                    run_len += 1;
238                } else {
239                    if run_len >= 2 {
240                        let stmt_start = stmt_index - run_len;
241                        let stmt_end = stmt_index;
242                        groups.push(crate::bytecode::FusionCandidateGroup {
243                            id: groups.len(),
244                            signal_count: run_len,
245                            function: body.function,
246                            block: block.id,
247                            stmt_start,
248                            stmt_end,
249                            source_span: merge_stmt_run_span(block, stmt_start, stmt_end),
250                        });
251                    }
252                    run_len = 0;
253                }
254            }
255            if run_len >= 2 {
256                let stmt_start = block.statements.len() - run_len;
257                let stmt_end = block.statements.len();
258                groups.push(crate::bytecode::FusionCandidateGroup {
259                    id: groups.len(),
260                    signal_count: run_len,
261                    function: body.function,
262                    block: block.id,
263                    stmt_start,
264                    stmt_end,
265                    source_span: merge_stmt_run_span(block, stmt_start, stmt_end),
266                });
267            }
268        }
269    }
270    (signal_count, groups)
271}
272
273#[cfg(feature = "native-accel")]
274fn merge_stmt_run_span(
275    block: &runmat_mir::BasicBlock,
276    stmt_start: usize,
277    stmt_end: usize,
278) -> runmat_hir::Span {
279    let mut iter = block.statements[stmt_start..stmt_end]
280        .iter()
281        .map(|stmt| stmt.span);
282    let Some(first) = iter.next() else {
283        return runmat_hir::Span::default();
284    };
285    iter.fold(first, runmat_hir::merge_span)
286}
287
288#[cfg(feature = "native-accel")]
289fn source_span_contains(outer: runmat_hir::Span, inner: runmat_hir::Span) -> bool {
290    outer.start <= inner.start && inner.end <= outer.end
291}
292
293#[cfg(all(feature = "native-accel", test))]
294fn candidates_touch_accel_capable_instruction(
295    instructions: &[Instr],
296    instr_spans: &[runmat_hir::Span],
297    candidate_groups: &[crate::bytecode::FusionCandidateGroup],
298) -> bool {
299    if instructions.is_empty() || instr_spans.is_empty() || candidate_groups.is_empty() {
300        return false;
301    }
302    instructions
303        .iter()
304        .enumerate()
305        .filter(|(index, _)| *index < instr_spans.len())
306        .any(|(index, instr)| {
307            let span = instr_spans[index];
308            candidate_groups
309                .iter()
310                .any(|candidate| source_span_contains(candidate.source_span, span))
311                && instr_is_accel_capable(instr)
312        })
313}
314
315#[cfg(all(feature = "native-accel", test))]
316fn instr_is_accel_capable(instr: &Instr) -> bool {
317    match instr {
318        Instr::Add
319        | Instr::Sub
320        | Instr::Mul
321        | Instr::RightDiv
322        | Instr::LeftDiv
323        | Instr::Pow
324        | Instr::Neg
325        | Instr::UPlus
326        | Instr::Transpose
327        | Instr::ConjugateTranspose
328        | Instr::ElemMul
329        | Instr::ElemDiv
330        | Instr::ElemPow
331        | Instr::ElemLeftDiv
332        | Instr::LessEqual
333        | Instr::Less
334        | Instr::Greater
335        | Instr::GreaterEqual
336        | Instr::Equal
337        | Instr::NotEqual => true,
338        Instr::CallBuiltinMulti(name, _, _) => builtin_functions()
339            .iter()
340            .find(|func| func.name == name.as_str())
341            .map(|func| {
342                func.accel_tags.iter().any(|tag| {
343                    matches!(
344                        tag,
345                        AccelTag::Unary
346                            | AccelTag::Elementwise
347                            | AccelTag::Reduction
348                            | AccelTag::MatMul
349                            | AccelTag::Transpose
350                    )
351                })
352            })
353            .unwrap_or(false),
354        _ => false,
355    }
356}
357
358#[cfg(feature = "native-accel")]
359fn fusion_group_within_semantic_candidate_spans(
360    group: &runmat_accelerate::fusion::FusionGroup,
361    instr_spans: &[runmat_hir::Span],
362    candidate_groups: &[crate::bytecode::FusionCandidateGroup],
363) -> bool {
364    if instr_spans.is_empty()
365        || group.span.start > group.span.end
366        || group.span.start >= instr_spans.len()
367    {
368        return false;
369    }
370    let end = group.span.end.min(instr_spans.len().saturating_sub(1));
371    let candidate_spans: Vec<_> = candidate_groups
372        .iter()
373        .map(|group| group.source_span)
374        .collect();
375    candidate_spans.iter().any(|candidate| {
376        instr_spans[group.span.start..=end]
377            .iter()
378            .all(|span| source_span_contains(*candidate, *span))
379    })
380}
381
382#[cfg(all(feature = "native-accel", test))]
383fn derive_semantic_fusion_groups_from_candidates(
384    instruction_windows: &[crate::bytecode::FusionInstructionWindow],
385    accel_graph: &runmat_accelerate::graph::AccelGraph,
386) -> Vec<runmat_accelerate::fusion::FusionGroup> {
387    let mut groups = Vec::new();
388    let mut assigned_nodes = HashSet::new();
389
390    for window in instruction_windows {
391        let nodes = accel_nodes_for_instruction_window(accel_graph, window, &assigned_nodes);
392        if nodes.is_empty() {
393            continue;
394        }
395        for node_id in &nodes {
396            assigned_nodes.insert(*node_id);
397        }
398        let kind = infer_semantic_fusion_kind(window.kind);
399        groups.push(runmat_accelerate::fusion::FusionGroup {
400            id: groups.len(),
401            kind,
402            nodes,
403            shape: runmat_accelerate::graph::ShapeInfo::Unknown,
404            span: window.span.clone(),
405            pattern: None,
406            stack_layout: None,
407        });
408    }
409
410    groups
411}
412
413#[cfg(all(feature = "native-accel", test))]
414fn derive_semantic_fusion_groups_preserving_unmapped_windows(
415    instruction_windows: &[crate::bytecode::FusionInstructionWindow],
416    accel_graph: &runmat_accelerate::graph::AccelGraph,
417) -> Vec<runmat_accelerate::fusion::FusionGroup> {
418    let mut groups = Vec::new();
419    let mut assigned_nodes = HashSet::new();
420
421    for window in instruction_windows {
422        let nodes = accel_nodes_for_instruction_window(accel_graph, window, &assigned_nodes);
423        for node_id in &nodes {
424            assigned_nodes.insert(*node_id);
425        }
426        groups.push(runmat_accelerate::fusion::FusionGroup {
427            id: groups.len(),
428            kind: infer_semantic_fusion_kind(window.kind),
429            nodes,
430            shape: runmat_accelerate::graph::ShapeInfo::Unknown,
431            span: window.span.clone(),
432            pattern: None,
433            stack_layout: None,
434        });
435    }
436
437    groups
438}
439
440#[cfg(feature = "native-accel")]
441fn derive_semantic_fusion_groups_from_instruction_windows(
442    instruction_windows: &[crate::bytecode::FusionInstructionWindow],
443) -> Vec<runmat_accelerate::fusion::FusionGroup> {
444    instruction_windows
445        .iter()
446        .enumerate()
447        .map(|(id, window)| runmat_accelerate::fusion::FusionGroup {
448            id,
449            kind: infer_semantic_fusion_kind(window.kind),
450            nodes: Vec::new(),
451            shape: runmat_accelerate::graph::ShapeInfo::Unknown,
452            span: window.span.clone(),
453            pattern: None,
454            stack_layout: None,
455        })
456        .collect()
457}
458
459#[cfg(all(feature = "native-accel", test))]
460fn accel_nodes_for_instruction_window(
461    accel_graph: &runmat_accelerate::graph::AccelGraph,
462    window: &crate::bytecode::FusionInstructionWindow,
463    assigned_nodes: &HashSet<runmat_accelerate::graph::NodeId>,
464) -> Vec<runmat_accelerate::graph::NodeId> {
465    let mut nodes: Vec<_> = accel_graph
466        .nodes
467        .iter()
468        .filter(|node| {
469            !assigned_nodes.contains(&node.id)
470                && accel_node_matches_semantic_window_kind(node, window.kind)
471                && accel_node_span_matches_instruction_window(node, window)
472        })
473        .map(|node| node.id)
474        .collect();
475    nodes.sort_unstable_by_key(|node_id| {
476        accel_graph
477            .node(*node_id)
478            .map(|node| (node.span.start, node.span.end, node.id))
479            .unwrap_or((usize::MAX, usize::MAX, *node_id))
480    });
481    nodes.dedup();
482    nodes
483}
484
485#[cfg(all(feature = "native-accel", test))]
486fn accel_node_span_matches_instruction_window(
487    node: &runmat_accelerate::graph::AccelNode,
488    window: &crate::bytecode::FusionInstructionWindow,
489) -> bool {
490    // Compile-time mapping is strict: only contained spans are assigned.
491    // Broader reconciliation remains runtime-owned in fusion plan sanitization.
492    node.span.start >= window.span.start && node.span.end <= window.span.end
493}
494
495#[cfg(all(feature = "native-accel", test))]
496fn accel_node_has_semantic_signal(node: &runmat_accelerate::graph::AccelNode) -> bool {
497    node.tags.iter().any(|tag| {
498        matches!(
499            tag,
500            runmat_accelerate::graph::AccelGraphTag::Unary
501                | runmat_accelerate::graph::AccelGraphTag::Elementwise
502                | runmat_accelerate::graph::AccelGraphTag::Reduction
503                | runmat_accelerate::graph::AccelGraphTag::MatMul
504                | runmat_accelerate::graph::AccelGraphTag::Transpose
505        )
506    })
507}
508
509#[cfg(all(feature = "native-accel", test))]
510fn accel_node_matches_semantic_window_kind(
511    node: &runmat_accelerate::graph::AccelNode,
512    kind: crate::bytecode::FusionInstructionKind,
513) -> bool {
514    let has_semantic_signal = accel_node_has_semantic_signal(node);
515    if !has_semantic_signal {
516        // If graph tags are absent, fall back to accel category compatibility
517        // instead of admitting any span-matched node.
518        return match kind {
519            crate::bytecode::FusionInstructionKind::Elementwise => matches!(
520                node.category,
521                runmat_accelerate::graph::AccelOpCategory::Elementwise
522                    | runmat_accelerate::graph::AccelOpCategory::Transpose
523            ),
524            crate::bytecode::FusionInstructionKind::Reduction => {
525                matches!(
526                    node.category,
527                    runmat_accelerate::graph::AccelOpCategory::Reduction
528                )
529            }
530            crate::bytecode::FusionInstructionKind::Matmul => {
531                matches!(
532                    node.category,
533                    runmat_accelerate::graph::AccelOpCategory::MatMul
534                )
535            }
536        };
537    }
538    let has_reduction = node
539        .tags
540        .iter()
541        .any(|tag| matches!(tag, runmat_accelerate::graph::AccelGraphTag::Reduction));
542    let has_matmul = node
543        .tags
544        .iter()
545        .any(|tag| matches!(tag, runmat_accelerate::graph::AccelGraphTag::MatMul));
546    match kind {
547        crate::bytecode::FusionInstructionKind::Elementwise => !has_reduction && !has_matmul,
548        crate::bytecode::FusionInstructionKind::Reduction => !has_matmul,
549        crate::bytecode::FusionInstructionKind::Matmul => true,
550    }
551}
552
553#[cfg(feature = "native-accel")]
554fn instruction_within_semantic_candidate_span(
555    instruction_index: usize,
556    instr_spans: &[runmat_hir::Span],
557    candidate_span: runmat_hir::Span,
558) -> bool {
559    if instr_spans.is_empty() || instruction_index >= instr_spans.len() {
560        return false;
561    }
562    source_span_contains(candidate_span, instr_spans[instruction_index])
563}
564
565#[cfg(feature = "native-accel")]
566fn derive_semantic_fusion_instruction_windows(
567    instructions: &[Instr],
568    instr_spans: &[runmat_hir::Span],
569    candidate_groups: &[crate::bytecode::FusionCandidateGroup],
570) -> Vec<crate::bytecode::FusionInstructionWindow> {
571    if instructions.is_empty() || instr_spans.is_empty() || candidate_groups.is_empty() {
572        return Vec::new();
573    }
574
575    let mut windows = Vec::new();
576    let mut assigned_instructions = HashSet::new();
577
578    for candidate in candidate_groups {
579        let mut run_start: Option<usize> = None;
580        let mut run_kind: Option<crate::bytecode::FusionInstructionKind> = None;
581        for (index, instr) in instructions.iter().enumerate() {
582            if index >= instr_spans.len() || assigned_instructions.contains(&index) {
583                if let Some(start) = run_start.take() {
584                    windows.push(crate::bytecode::FusionInstructionWindow {
585                        span: runmat_accelerate::graph::InstrSpan {
586                            start,
587                            end: index.saturating_sub(1),
588                        },
589                        kind: run_kind
590                            .unwrap_or(crate::bytecode::FusionInstructionKind::Elementwise),
591                    });
592                    run_kind = None;
593                }
594                continue;
595            }
596            if !instruction_within_semantic_candidate_span(
597                index,
598                instr_spans,
599                candidate.source_span,
600            ) {
601                if let Some(start) = run_start.take() {
602                    windows.push(crate::bytecode::FusionInstructionWindow {
603                        span: runmat_accelerate::graph::InstrSpan {
604                            start,
605                            end: index.saturating_sub(1),
606                        },
607                        kind: run_kind
608                            .unwrap_or(crate::bytecode::FusionInstructionKind::Elementwise),
609                    });
610                    run_kind = None;
611                }
612                continue;
613            }
614            let Some(signal_kind) = instr_fusion_signal_kind(instr) else {
615                if let Some(start) = run_start.take() {
616                    windows.push(crate::bytecode::FusionInstructionWindow {
617                        span: runmat_accelerate::graph::InstrSpan {
618                            start,
619                            end: index.saturating_sub(1),
620                        },
621                        kind: run_kind
622                            .unwrap_or(crate::bytecode::FusionInstructionKind::Elementwise),
623                    });
624                    run_kind = None;
625                }
626                continue;
627            };
628
629            if run_start.is_none() {
630                run_start = Some(index);
631                run_kind = Some(signal_kind);
632            } else if matches!(signal_kind, crate::bytecode::FusionInstructionKind::Matmul) {
633                run_kind = Some(crate::bytecode::FusionInstructionKind::Matmul);
634            } else if matches!(
635                signal_kind,
636                crate::bytecode::FusionInstructionKind::Reduction
637            ) && !matches!(
638                run_kind,
639                Some(crate::bytecode::FusionInstructionKind::Matmul)
640            ) {
641                run_kind = Some(crate::bytecode::FusionInstructionKind::Reduction);
642            }
643            assigned_instructions.insert(index);
644        }
645        if let Some(start) = run_start.take() {
646            windows.push(crate::bytecode::FusionInstructionWindow {
647                span: runmat_accelerate::graph::InstrSpan {
648                    start,
649                    end: instructions.len().saturating_sub(1),
650                },
651                kind: run_kind.unwrap_or(crate::bytecode::FusionInstructionKind::Elementwise),
652            });
653        }
654    }
655
656    windows
657}
658
659#[cfg(feature = "native-accel")]
660fn instr_fusion_signal_kind(instr: &Instr) -> Option<crate::bytecode::FusionInstructionKind> {
661    match instr {
662        Instr::Add
663        | Instr::Sub
664        | Instr::Mul
665        | Instr::RightDiv
666        | Instr::LeftDiv
667        | Instr::Pow
668        | Instr::Neg
669        | Instr::UPlus
670        | Instr::Transpose
671        | Instr::ConjugateTranspose
672        | Instr::ElemMul
673        | Instr::ElemDiv
674        | Instr::ElemPow
675        | Instr::ElemLeftDiv
676        | Instr::LessEqual
677        | Instr::Less
678        | Instr::Greater
679        | Instr::GreaterEqual
680        | Instr::Equal
681        | Instr::NotEqual => Some(crate::bytecode::FusionInstructionKind::Elementwise),
682        Instr::CallBuiltinMulti(name, _, _) => builtin_functions()
683            .iter()
684            .find(|func| func.name == name.as_str())
685            .and_then(|func| {
686                let has_matmul = func
687                    .accel_tags
688                    .iter()
689                    .any(|tag| matches!(tag, AccelTag::MatMul));
690                if has_matmul {
691                    return Some(crate::bytecode::FusionInstructionKind::Matmul);
692                }
693                let has_reduction = func
694                    .accel_tags
695                    .iter()
696                    .any(|tag| matches!(tag, AccelTag::Reduction));
697                if has_reduction {
698                    return Some(crate::bytecode::FusionInstructionKind::Reduction);
699                }
700                let has_elementwise = func.accel_tags.iter().any(|tag| {
701                    matches!(
702                        tag,
703                        AccelTag::Unary | AccelTag::Elementwise | AccelTag::Transpose
704                    )
705                });
706                has_elementwise.then_some(crate::bytecode::FusionInstructionKind::Elementwise)
707            }),
708        _ => None,
709    }
710}
711
712#[cfg(feature = "native-accel")]
713fn infer_semantic_fusion_kind(
714    kind_hint: crate::bytecode::FusionInstructionKind,
715) -> runmat_accelerate::fusion::FusionKind {
716    match kind_hint {
717        crate::bytecode::FusionInstructionKind::Matmul => {
718            runmat_accelerate::fusion::FusionKind::MatmulEpilogue
719        }
720        crate::bytecode::FusionInstructionKind::Reduction => {
721            runmat_accelerate::fusion::FusionKind::Reduction
722        }
723        crate::bytecode::FusionInstructionKind::Elementwise => {
724            runmat_accelerate::fusion::FusionKind::ElementwiseChain
725        }
726    }
727}
728
729#[cfg(feature = "native-accel")]
730fn rvalue_has_fusion_signal(value: &MirRvalue) -> bool {
731    match value {
732        MirRvalue::Unary(_, _) | MirRvalue::Binary(_, _, _) => true,
733        MirRvalue::Call(call) => matches!(
734            call.semantic_kind,
735            BuiltinSemanticKind::Elementwise
736                | BuiltinSemanticKind::Reduction
737                | BuiltinSemanticKind::LinearAlgebra
738                | BuiltinSemanticKind::ShapeTransform(_)
739        ),
740        MirRvalue::ShortCircuit { .. } => false,
741        MirRvalue::Use(_)
742        | MirRvalue::Range { .. }
743        | MirRvalue::Aggregate { .. }
744        | MirRvalue::StructLiteral { .. }
745        | MirRvalue::ObjectLiteral { .. }
746        | MirRvalue::Index { .. }
747        | MirRvalue::Member { .. }
748        | MirRvalue::DynamicMember { .. }
749        | MirRvalue::WorkspaceFirstStaticProperty { .. }
750        | MirRvalue::MetaClass(_)
751        | MirRvalue::Colon
752        | MirRvalue::End
753        | MirRvalue::Future { .. }
754        | MirRvalue::Spawn(_) => false,
755    }
756}
757
758pub fn compile_semantic_function_registry(
759    hir: &HirAssembly,
760    mir: &MirAssembly,
761) -> Result<HashMap<FunctionId, FunctionBytecode>, CompileError> {
762    let layout = derive_layout(hir, mir)
763        .map_err(|err| CompileError::new(format!("failed to derive VM layout: {err:?}")))?;
764    compile_semantic_functions(hir, mir, &layout, None)
765}
766
767fn compile_semantic_functions(
768    hir: &HirAssembly,
769    mir: &MirAssembly,
770    layout: &crate::layout::VmAssemblyLayout,
771    entrypoint: Option<EntrypointId>,
772) -> Result<HashMap<FunctionId, FunctionBytecode>, CompileError> {
773    let entry_target = entrypoint
774        .and_then(|entrypoint| layout.entrypoints.get(&entrypoint))
775        .map(|entry| entry.target);
776    let mut functions = HashMap::new();
777    for function in &hir.functions {
778        if Some(function.id) == entry_target {
779            continue;
780        }
781        let mut compiler = Compiler::new_for_function(hir, mir, layout.clone(), function.id)?;
782        compiler.compile()?;
783        let function_layout = layout.functions.get(&function.id).ok_or_else(|| {
784            CompileError::new(format!("missing VM layout for function {:?}", function.id))
785        })?;
786        let source_id = hir
787            .modules
788            .get(function.module.0)
789            .map(|module| module.source_id);
790        functions.insert(
791            function.id,
792            FunctionBytecode {
793                function: function.id,
794                display_name: function_layout.display_name.clone(),
795                private_owner_scope: function_layout.private_owner_scope.clone(),
796                source_id,
797                instructions: compiler.instructions,
798                instr_spans: compiler.instr_spans,
799                call_arg_spans: compiler.call_arg_spans,
800                var_count: compiler.var_count,
801                input_slots: function_layout
802                    .frame_abi
803                    .fixed_inputs
804                    .iter()
805                    .filter(|slot| Some(**slot) != function_layout.frame_abi.varargin)
806                    .map(|slot| slot.0)
807                    .collect(),
808                varargin_slot: function_layout.frame_abi.varargin.map(|slot| slot.0),
809                implicit_nargin_slot: function_layout.frame_abi.implicit_nargin.map(|slot| slot.0),
810                output_slots: function_layout
811                    .frame_abi
812                    .fixed_outputs
813                    .iter()
814                    .filter(|slot| Some(**slot) != function_layout.frame_abi.varargout)
815                    .map(|slot| slot.0)
816                    .collect(),
817                varargout_slot: function_layout.frame_abi.varargout.map(|slot| slot.0),
818                implicit_nargout_slot: function_layout
819                    .frame_abi
820                    .implicit_nargout
821                    .map(|slot| slot.0),
822                capture_slots: function_layout
823                    .captures
824                    .iter()
825                    .map(|capture| capture.slot.0)
826                    .collect(),
827                var_names: function_layout_var_names(hir, function_layout)?,
828                initially_unassigned_slots: function_layout_initially_unassigned_slots(
829                    hir,
830                    function_layout,
831                ),
832                argument_validations: function
833                    .argument_validations
834                    .iter()
835                    .filter_map(|validation| {
836                        function_layout
837                            .binding_slots
838                            .get(&validation.binding)
839                            .map(|slot| crate::bytecode::program::FunctionArgumentValidation {
840                                input_slot: slot.0,
841                                size: validation.size.as_ref().map(|size| {
842                                    crate::bytecode::program::FunctionArgSizeSpec {
843                                        rows: match size.rows {
844                                            runmat_hir::FunctionArgDim::Any => {
845                                                crate::bytecode::program::FunctionArgDim::Any
846                                            }
847                                            runmat_hir::FunctionArgDim::Exact(value) => {
848                                                crate::bytecode::program::FunctionArgDim::Exact(value)
849                                            }
850                                        },
851                                        cols: match size.cols {
852                                            runmat_hir::FunctionArgDim::Any => {
853                                                crate::bytecode::program::FunctionArgDim::Any
854                                            }
855                                            runmat_hir::FunctionArgDim::Exact(value) => {
856                                                crate::bytecode::program::FunctionArgDim::Exact(value)
857                                            }
858                                        },
859                                    }
860                                }),
861                                class_name: validation.class_name.clone(),
862                                validators: validation
863                                    .validators
864                                    .iter()
865                                    .map(|validator| match validator {
866                                        runmat_hir::FunctionArgValidator::A(class_names) => {
867                                            crate::bytecode::program::FunctionArgValidator::A(class_names.clone())
868                                        }
869                                        runmat_hir::FunctionArgValidator::Column => {
870                                            crate::bytecode::program::FunctionArgValidator::Column
871                                        }
872                                        runmat_hir::FunctionArgValidator::Finite => {
873                                            crate::bytecode::program::FunctionArgValidator::Finite
874                                        }
875                                        runmat_hir::FunctionArgValidator::Float => {
876                                            crate::bytecode::program::FunctionArgValidator::Float
877                                        }
878                                        runmat_hir::FunctionArgValidator::Folder => {
879                                            crate::bytecode::program::FunctionArgValidator::Folder
880                                        }
881                                        runmat_hir::FunctionArgValidator::File => {
882                                            crate::bytecode::program::FunctionArgValidator::File
883                                        }
884                                        runmat_hir::FunctionArgValidator::NumericOrLogical => {
885                                            crate::bytecode::program::FunctionArgValidator::NumericOrLogical
886                                        }
887                                        runmat_hir::FunctionArgValidator::Numeric => {
888                                            crate::bytecode::program::FunctionArgValidator::Numeric
889                                        }
890                                        runmat_hir::FunctionArgValidator::Text => {
891                                            crate::bytecode::program::FunctionArgValidator::Text
892                                        }
893                                        runmat_hir::FunctionArgValidator::TextScalar => {
894                                            crate::bytecode::program::FunctionArgValidator::TextScalar
895                                        }
896                                        runmat_hir::FunctionArgValidator::NonzeroLengthText => {
897                                            crate::bytecode::program::FunctionArgValidator::NonzeroLengthText
898                                        }
899                                        runmat_hir::FunctionArgValidator::Nonempty => {
900                                            crate::bytecode::program::FunctionArgValidator::Nonempty
901                                        }
902                                        runmat_hir::FunctionArgValidator::ScalarOrEmpty => {
903                                            crate::bytecode::program::FunctionArgValidator::ScalarOrEmpty
904                                        }
905                                        runmat_hir::FunctionArgValidator::Real => {
906                                            crate::bytecode::program::FunctionArgValidator::Real
907                                        }
908                                        runmat_hir::FunctionArgValidator::Integer => {
909                                            crate::bytecode::program::FunctionArgValidator::Integer
910                                        }
911                                        runmat_hir::FunctionArgValidator::Vector => {
912                                            crate::bytecode::program::FunctionArgValidator::Vector
913                                        }
914                                        runmat_hir::FunctionArgValidator::Positive => {
915                                            crate::bytecode::program::FunctionArgValidator::Positive
916                                        }
917                                        runmat_hir::FunctionArgValidator::Negative => {
918                                            crate::bytecode::program::FunctionArgValidator::Negative
919                                        }
920                                        runmat_hir::FunctionArgValidator::Nonnegative => {
921                                            crate::bytecode::program::FunctionArgValidator::Nonnegative
922                                        }
923                                        runmat_hir::FunctionArgValidator::Nonmissing => {
924                                            crate::bytecode::program::FunctionArgValidator::Nonmissing
925                                        }
926                                        runmat_hir::FunctionArgValidator::NonNan => {
927                                            crate::bytecode::program::FunctionArgValidator::NonNan
928                                        }
929                                        runmat_hir::FunctionArgValidator::Nonzero => {
930                                            crate::bytecode::program::FunctionArgValidator::Nonzero
931                                        }
932                                        runmat_hir::FunctionArgValidator::Nonpositive => {
933                                            crate::bytecode::program::FunctionArgValidator::Nonpositive
934                                        }
935                                        runmat_hir::FunctionArgValidator::Nonsparse => {
936                                            crate::bytecode::program::FunctionArgValidator::Nonsparse
937                                        }
938                                        runmat_hir::FunctionArgValidator::Sparse => {
939                                            crate::bytecode::program::FunctionArgValidator::Sparse
940                                        }
941                                        runmat_hir::FunctionArgValidator::ValidVariableName => {
942                                            crate::bytecode::program::FunctionArgValidator::ValidVariableName
943                                        }
944                                        runmat_hir::FunctionArgValidator::UnderlyingType(class_names) => {
945                                            crate::bytecode::program::FunctionArgValidator::UnderlyingType(class_names.clone())
946                                        }
947                                        runmat_hir::FunctionArgValidator::Member(literals) => {
948                                            crate::bytecode::program::FunctionArgValidator::Member(
949                                                literals
950                                                    .iter()
951                                                    .map(|literal| match literal {
952                                                        runmat_hir::FunctionArgValidationLiteral::Number(value) => {
953                                                            crate::bytecode::program::FunctionArgValidationLiteral::Number(*value)
954                                                        }
955                                                        runmat_hir::FunctionArgValidationLiteral::Text(value) => {
956                                                            crate::bytecode::program::FunctionArgValidationLiteral::Text(value.clone())
957                                                        }
958                                                        runmat_hir::FunctionArgValidationLiteral::Bool(value) => {
959                                                            crate::bytecode::program::FunctionArgValidationLiteral::Bool(*value)
960                                                        }
961                                                    })
962                                                    .collect(),
963                                            )
964                                        }
965                                        runmat_hir::FunctionArgValidator::InRange(lower, upper, inclusivity) => {
966                                            crate::bytecode::program::FunctionArgValidator::InRange(
967                                                *lower,
968                                                *upper,
969                                                crate::bytecode::program::FunctionArgRangeInclusivity {
970                                                    lower: inclusivity.lower,
971                                                    upper: inclusivity.upper,
972                                                },
973                                            )
974                                        }
975                                        runmat_hir::FunctionArgValidator::GreaterThanOrEqual(
976                                            threshold,
977                                        ) => crate::bytecode::program::FunctionArgValidator::GreaterThanOrEqual(*threshold),
978                                        runmat_hir::FunctionArgValidator::LessThanOrEqual(
979                                            threshold,
980                                        ) => crate::bytecode::program::FunctionArgValidator::LessThanOrEqual(*threshold),
981                                        runmat_hir::FunctionArgValidator::GreaterThan(
982                                            threshold,
983                                        ) => crate::bytecode::program::FunctionArgValidator::GreaterThan(*threshold),
984                                        runmat_hir::FunctionArgValidator::LessThan(
985                                            threshold,
986                                        ) => crate::bytecode::program::FunctionArgValidator::LessThan(*threshold),
987                                    })
988                                    .collect(),
989                                default_value: validation.default_value.as_ref().map(|default| {
990                                    match default {
991                                        runmat_hir::FunctionArgDefaultValue::Number(value) => {
992                                            crate::bytecode::program::FunctionArgDefaultValue::Number(*value)
993                                        }
994                                        runmat_hir::FunctionArgDefaultValue::Bool(value) => {
995                                            crate::bytecode::program::FunctionArgDefaultValue::Bool(*value)
996                                        }
997                                        runmat_hir::FunctionArgDefaultValue::String(value) => {
998                                            crate::bytecode::program::FunctionArgDefaultValue::String(value.clone())
999                                        }
1000                                        runmat_hir::FunctionArgDefaultValue::EmptyArray => {
1001                                            crate::bytecode::program::FunctionArgDefaultValue::EmptyArray
1002                                        }
1003                                    }
1004                                }),
1005                            })
1006                    })
1007                    .collect(),
1008            },
1009        );
1010    }
1011    Ok(functions)
1012}
1013
1014fn function_layout_var_names(
1015    hir: &HirAssembly,
1016    function_layout: &crate::layout::VmFunctionLayout,
1017) -> Result<HashMap<usize, String>, CompileError> {
1018    let mut names = HashMap::new();
1019    for (binding, slot) in &function_layout.binding_slots {
1020        let hir_binding = hir.bindings.get(binding.0).ok_or_else(|| {
1021            CompileError::new(format!("missing HIR binding for VM slot {:?}", binding))
1022        })?;
1023        names.insert(slot.0, hir_binding.name.0.clone());
1024    }
1025    Ok(names)
1026}
1027
1028fn function_layout_initially_unassigned_slots(
1029    hir: &HirAssembly,
1030    function_layout: &crate::layout::VmFunctionLayout,
1031) -> HashSet<usize> {
1032    function_layout
1033        .binding_slots
1034        .iter()
1035        .filter_map(|(binding, slot)| {
1036            hir.bindings
1037                .get(binding.0)
1038                .is_some_and(|hir_binding| {
1039                    matches!(hir_binding.role, runmat_hir::BindingRole::ExternalWorkspace)
1040                })
1041                .then_some(slot.0)
1042        })
1043        .collect()
1044}
1045
1046#[cfg(test)]
1047mod tests {
1048    use super::compile;
1049    use crate::Instr;
1050    use futures::executor::block_on;
1051    #[cfg(feature = "native-accel")]
1052    use runmat_accelerate::fusion::prepare_fusion_plan;
1053    use runmat_builtins::Value;
1054    use runmat_hir::{
1055        lower, AssignmentCreationPolicy, BuiltinId, CallableFallbackPolicy, CallableIdentity,
1056        DefPath, DefPathSegment, FunctionId, IndexResultContext, LoweringContext, MethodId,
1057        OperatorKind, PackageName, QualifiedName, RequestedOutputCount, SymbolName,
1058    };
1059    use runmat_mir::lowering::lower_assembly;
1060    use runmat_mir::{
1061        MirAggregateKind, MirCallee, MirConstant, MirIndexComponent, MirIndexPlan, MirOperand,
1062        MirOutputTarget, MirPlace, MirRvalue, MirStmtKind, MirTerminatorKind,
1063    };
1064    use std::collections::HashMap;
1065    use std::sync::Arc;
1066    use std::time::{SystemTime, UNIX_EPOCH};
1067
1068    fn unique_csv_temp_path(prefix: &str) -> std::path::PathBuf {
1069        let nanos = SystemTime::now()
1070            .duration_since(UNIX_EPOCH)
1071            .expect("system time before unix epoch")
1072            .as_nanos();
1073        std::env::temp_dir().join(format!(
1074            "runmat_{prefix}_{}_{}.csv",
1075            std::process::id(),
1076            nanos
1077        ))
1078    }
1079
1080    #[test]
1081    fn compile_attaches_derived_layout() {
1082        let ast = runmat_parser::parse("x = 1 + 2;").expect("parse");
1083        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
1084        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
1085        let entrypoint = hir.assembly.entrypoints[0].id;
1086
1087        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
1088
1089        let layout = bytecode.layout.as_ref().expect("layout");
1090        let entrypoint_layout = &layout.entrypoints[&entrypoint];
1091        let function_layout = &layout.functions[&entrypoint_layout.target];
1092        assert_eq!(bytecode.var_count, function_layout.local_count);
1093        assert_eq!(bytecode.var_types.len(), function_layout.local_count);
1094    }
1095
1096    #[test]
1097    fn compile_lowers_simple_assignment_arithmetic() {
1098        let ast = runmat_parser::parse("x = 1 + 2;").expect("parse");
1099        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
1100        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
1101        let entrypoint = hir.assembly.entrypoints[0].id;
1102
1103        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
1104
1105        assert_eq!(bytecode.instructions.len(), 4);
1106        assert!(matches!(bytecode.instructions[0], Instr::LoadConst(1.0)));
1107        assert!(matches!(bytecode.instructions[1], Instr::LoadConst(2.0)));
1108        assert!(matches!(bytecode.instructions[2], Instr::Add));
1109        assert!(matches!(bytecode.instructions[3], Instr::StoreVar(_)));
1110    }
1111
1112    #[cfg(feature = "native-accel")]
1113    #[test]
1114    fn compile_records_semantic_fusion_metadata() {
1115        let ast = runmat_parser::parse("x = 1 + 2; y = x * 3;").expect("parse");
1116        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
1117        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
1118        let entrypoint = hir.assembly.entrypoints[0].id;
1119
1120        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
1121
1122        assert!(
1123            bytecode.fusion_metadata.mir_fusion_signal_count > 0,
1124            "expected non-zero MIR fusion signal count"
1125        );
1126        assert!(
1127            bytecode.fusion_metadata.mir_fusion_candidate_group_count > 0,
1128            "expected non-zero MIR fusion candidate group count"
1129        );
1130        assert!(
1131            !bytecode
1132                .fusion_metadata
1133                .mir_fusion_candidate_groups
1134                .is_empty(),
1135            "expected non-empty MIR fusion candidate groups"
1136        );
1137        assert!(
1138            bytecode
1139                .fusion_metadata
1140                .mir_fusion_candidate_groups
1141                .iter()
1142                .all(|group| group.stmt_end > group.stmt_start),
1143            "expected candidate groups to carry non-empty statement spans"
1144        );
1145        assert!(
1146            bytecode
1147                .fusion_metadata
1148                .mir_fusion_candidate_groups
1149                .iter()
1150                .all(|group| group.source_span.end > group.source_span.start),
1151            "expected candidate groups to carry non-empty source spans"
1152        );
1153        assert!(
1154            bytecode.fusion_metadata.instruction_window_count > 0,
1155            "expected non-zero semantic instruction window count"
1156        );
1157        assert_eq!(
1158            bytecode.fusion_metadata.instruction_window_count,
1159            bytecode.fusion_metadata.instruction_windows.len(),
1160            "window count should match serialized semantic instruction window entries"
1161        );
1162        assert!(
1163            bytecode
1164                .fusion_metadata
1165                .instruction_windows
1166                .iter()
1167                .all(|window| window.span.end >= window.span.start),
1168            "expected semantic instruction windows to carry valid instruction spans"
1169        );
1170    }
1171
1172    #[cfg(feature = "native-accel")]
1173    #[test]
1174    fn compile_emits_semantic_window_scaffolds_and_runtime_plan_reconciles_nodes() {
1175        let ast = runmat_parser::parse("x = 1 + 2; y = x * 3;").expect("parse");
1176        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
1177        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
1178        let entrypoint = hir.assembly.entrypoints[0].id;
1179
1180        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
1181        let runtime_groups = bytecode.runtime_fusion_groups();
1182        let runtime_graph = bytecode.runtime_accel_graph_for_fusion(&runtime_groups);
1183        assert!(
1184            runtime_graph.is_some(),
1185            "expected runtime accel graph when semantic fusion candidates/windows exist"
1186        );
1187        assert!(
1188            !bytecode.fusion_groups.is_empty(),
1189            "expected semantic-window fusion scaffolds"
1190        );
1191        assert!(
1192            bytecode
1193                .fusion_groups
1194                .iter()
1195                .all(|group| group.nodes.is_empty()),
1196            "compile should not assign accel node IDs to semantic-window groups"
1197        );
1198
1199        let runtime_groups = if let Some(graph) = runtime_graph.as_ref() {
1200            bytecode.runtime_fusion_groups_for_graph(graph)
1201        } else {
1202            bytecode.fusion_groups.clone()
1203        };
1204        let runtime_plan = prepare_fusion_plan(
1205            runtime_graph.as_ref(),
1206            &runtime_groups,
1207            bytecode.fusion_metadata.mir_fusion_candidate_group_count,
1208        )
1209        .expect("runtime fusion planning should reconcile executable groups");
1210        assert!(
1211            runtime_plan
1212                .groups
1213                .iter()
1214                .any(|group| !group.group.nodes.is_empty()),
1215            "runtime fusion planning should reconcile node IDs from accel graph"
1216        );
1217    }
1218
1219    #[cfg(feature = "native-accel")]
1220    #[test]
1221    fn compile_keeps_multi_window_groups_node_empty_before_runtime_reconciliation() {
1222        let ast =
1223            runmat_parser::parse("a = 1 + 2; b = a * 3; marker = 'x'; c = b - 4; d = c ./ 2;")
1224                .expect("parse");
1225        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
1226        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
1227        let entrypoint = hir.assembly.entrypoints[0].id;
1228
1229        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
1230        assert!(
1231            bytecode.fusion_groups.len() >= 2,
1232            "expected multiple semantic-window fusion groups for split accel-capable runs"
1233        );
1234        assert!(
1235            bytecode
1236                .fusion_groups
1237                .iter()
1238                .all(|group| group.nodes.is_empty()),
1239            "compile-time semantic-window groups must remain node-empty"
1240        );
1241
1242        let runtime_groups = bytecode.runtime_fusion_groups();
1243        let runtime_graph = bytecode.runtime_accel_graph_for_fusion(&runtime_groups);
1244        let runtime_groups = if let Some(graph) = runtime_graph.as_ref() {
1245            bytecode.runtime_fusion_groups_for_graph(graph)
1246        } else {
1247            bytecode.fusion_groups.clone()
1248        };
1249        let runtime_plan = prepare_fusion_plan(
1250            runtime_graph.as_ref(),
1251            &runtime_groups,
1252            bytecode.fusion_metadata.mir_fusion_candidate_group_count,
1253        )
1254        .expect("runtime fusion planning should reconcile executable groups");
1255        assert!(
1256            runtime_plan
1257                .groups
1258                .iter()
1259                .any(|group| !group.group.nodes.is_empty()),
1260            "runtime reconciliation should assign accel nodes"
1261        );
1262    }
1263
1264    #[cfg(feature = "native-accel")]
1265    #[test]
1266    fn compile_semantically_gates_bytecode_fusion_groups() {
1267        let ast = runmat_parser::parse("x = 1;").expect("parse");
1268        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
1269        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
1270        let entrypoint = hir.assembly.entrypoints[0].id;
1271        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
1272        let runtime_graph =
1273            bytecode.runtime_accel_graph_for_fusion(&bytecode.runtime_fusion_groups());
1274
1275        assert_eq!(
1276            bytecode.fusion_metadata.mir_fusion_candidate_group_count, 0,
1277            "expected no semantic fusion candidate groups"
1278        );
1279        assert!(
1280            runtime_graph.is_none(),
1281            "expected runtime accel graph to be omitted when semantic candidate groups are absent"
1282        );
1283        assert!(
1284            bytecode.fusion_groups.is_empty(),
1285            "expected bytecode fusion groups to be gated off when semantic candidates are absent"
1286        );
1287    }
1288
1289    #[cfg(feature = "native-accel")]
1290    #[test]
1291    fn compile_omits_accel_graph_when_signals_exist_but_no_candidate_group() {
1292        let ast = runmat_parser::parse("x = 1 + 2;").expect("parse");
1293        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
1294        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
1295        let entrypoint = hir.assembly.entrypoints[0].id;
1296        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
1297        let runtime_graph =
1298            bytecode.runtime_accel_graph_for_fusion(&bytecode.runtime_fusion_groups());
1299
1300        assert!(
1301            bytecode.fusion_metadata.mir_fusion_signal_count > 0,
1302            "expected non-zero fusion signal count for arithmetic operation"
1303        );
1304        assert_eq!(
1305            bytecode.fusion_metadata.mir_fusion_candidate_group_count, 0,
1306            "expected no semantic candidate groups for a single-operation run"
1307        );
1308        assert!(
1309            runtime_graph.is_none(),
1310            "expected runtime accel graph omission to follow semantic candidate-group gating"
1311        );
1312        assert!(
1313            bytecode.fusion_groups.is_empty(),
1314            "expected no executable bytecode fusion groups without semantic candidates"
1315        );
1316    }
1317
1318    #[cfg(feature = "native-accel")]
1319    #[test]
1320    fn compile_omits_accel_graph_when_candidates_overlap_only_logical_ops() {
1321        let ast =
1322            runmat_parser::parse("a = true; b = false; c = a & b; d = c | a;").expect("parse");
1323        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
1324        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
1325        let entrypoint = hir.assembly.entrypoints[0].id;
1326        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
1327        let runtime_graph =
1328            bytecode.runtime_accel_graph_for_fusion(&bytecode.runtime_fusion_groups());
1329
1330        assert!(
1331            bytecode.fusion_metadata.mir_fusion_candidate_group_count > 0,
1332            "logical chain should still produce semantic candidate groups"
1333        );
1334        assert!(
1335            runtime_graph.is_none(),
1336            "expected runtime accel graph omission when candidate overlap is non-accelerable logical ops"
1337        );
1338        assert!(
1339            bytecode.fusion_groups.is_empty(),
1340            "expected no executable fusion groups for logical-only candidate overlap"
1341        );
1342    }
1343
1344    #[cfg(feature = "native-accel")]
1345    #[test]
1346    fn candidate_accel_capability_gate_rejects_logical_ops() {
1347        let instructions = vec![Instr::LogicalAnd, Instr::LogicalOr];
1348        let instr_spans = vec![
1349            runmat_hir::Span { start: 10, end: 20 },
1350            runmat_hir::Span { start: 21, end: 30 },
1351        ];
1352        let candidates = vec![crate::bytecode::FusionCandidateGroup {
1353            id: 0,
1354            signal_count: 2,
1355            function: runmat_hir::FunctionId(0),
1356            block: runmat_mir::BasicBlockId(0),
1357            stmt_start: 0,
1358            stmt_end: 2,
1359            source_span: runmat_hir::Span { start: 10, end: 30 },
1360        }];
1361        assert!(
1362            !super::candidates_touch_accel_capable_instruction(
1363                &instructions,
1364                &instr_spans,
1365                &candidates,
1366            ),
1367            "logical ops should not trigger accel-graph construction gate"
1368        );
1369    }
1370
1371    #[cfg(feature = "native-accel")]
1372    #[test]
1373    fn candidate_accel_capability_gate_accepts_binary_ops() {
1374        let instructions = vec![Instr::Add, Instr::ElemMul];
1375        let instr_spans = vec![
1376            runmat_hir::Span { start: 10, end: 20 },
1377            runmat_hir::Span { start: 21, end: 30 },
1378        ];
1379        let candidates = vec![crate::bytecode::FusionCandidateGroup {
1380            id: 0,
1381            signal_count: 2,
1382            function: runmat_hir::FunctionId(0),
1383            block: runmat_mir::BasicBlockId(0),
1384            stmt_start: 0,
1385            stmt_end: 2,
1386            source_span: runmat_hir::Span { start: 10, end: 30 },
1387        }];
1388        assert!(
1389            super::candidates_touch_accel_capable_instruction(
1390                &instructions,
1391                &instr_spans,
1392                &candidates,
1393            ),
1394            "elementwise arithmetic ops should trigger accel-graph construction gate"
1395        );
1396    }
1397
1398    #[cfg(feature = "native-accel")]
1399    #[test]
1400    fn candidate_accel_capability_gate_rejects_partial_span_overlap() {
1401        let instructions = vec![Instr::Add];
1402        let instr_spans = vec![runmat_hir::Span { start: 10, end: 20 }];
1403        let candidates = vec![crate::bytecode::FusionCandidateGroup {
1404            id: 0,
1405            signal_count: 1,
1406            function: runmat_hir::FunctionId(0),
1407            block: runmat_mir::BasicBlockId(0),
1408            stmt_start: 0,
1409            stmt_end: 1,
1410            source_span: runmat_hir::Span { start: 19, end: 25 },
1411        }];
1412        assert!(
1413            !super::candidates_touch_accel_capable_instruction(
1414                &instructions,
1415                &instr_spans,
1416                &candidates,
1417            ),
1418            "partial boundary overlap should not satisfy accel-capability semantic gate"
1419        );
1420    }
1421
1422    #[cfg(feature = "native-accel")]
1423    #[test]
1424    fn candidate_accel_capability_gate_accepts_reduction_builtin() {
1425        let instructions = vec![Instr::CallBuiltinMulti("sum".to_string(), 1, 1)];
1426        let instr_spans = vec![runmat_hir::Span { start: 10, end: 20 }];
1427        let candidates = vec![crate::bytecode::FusionCandidateGroup {
1428            id: 0,
1429            signal_count: 2,
1430            function: runmat_hir::FunctionId(0),
1431            block: runmat_mir::BasicBlockId(0),
1432            stmt_start: 0,
1433            stmt_end: 2,
1434            source_span: runmat_hir::Span { start: 10, end: 20 },
1435        }];
1436        assert!(
1437            super::candidates_touch_accel_capable_instruction(
1438                &instructions,
1439                &instr_spans,
1440                &candidates,
1441            ),
1442            "reduction builtin call should trigger accel-graph construction gate"
1443        );
1444    }
1445
1446    #[cfg(feature = "native-accel")]
1447    #[test]
1448    fn candidate_accel_capability_gate_rejects_control_assert_builtin() {
1449        let instructions = vec![Instr::CallBuiltinMulti("assert".to_string(), 1, 0)];
1450        let instr_spans = vec![runmat_hir::Span { start: 10, end: 20 }];
1451        let candidates = vec![crate::bytecode::FusionCandidateGroup {
1452            id: 0,
1453            signal_count: 2,
1454            function: runmat_hir::FunctionId(0),
1455            block: runmat_mir::BasicBlockId(0),
1456            stmt_start: 0,
1457            stmt_end: 2,
1458            source_span: runmat_hir::Span { start: 10, end: 20 },
1459        }];
1460        assert!(
1461            !super::candidates_touch_accel_capable_instruction(
1462                &instructions,
1463                &instr_spans,
1464                &candidates,
1465            ),
1466            "control/assertion builtin should not trigger accel-graph construction gate"
1467        );
1468    }
1469
1470    #[cfg(feature = "native-accel")]
1471    #[test]
1472    fn candidate_accel_capability_gate_rejects_sink_builtins() {
1473        for builtin in ["disp", "fprintf"] {
1474            let instructions = vec![Instr::CallBuiltinMulti(builtin.to_string(), 1, 0)];
1475            let instr_spans = vec![runmat_hir::Span { start: 10, end: 20 }];
1476            let candidates = vec![crate::bytecode::FusionCandidateGroup {
1477                id: 0,
1478                signal_count: 2,
1479                function: runmat_hir::FunctionId(0),
1480                block: runmat_mir::BasicBlockId(0),
1481                stmt_start: 0,
1482                stmt_end: 2,
1483                source_span: runmat_hir::Span { start: 10, end: 20 },
1484            }];
1485            assert!(
1486                !super::candidates_touch_accel_capable_instruction(
1487                    &instructions,
1488                    &instr_spans,
1489                    &candidates,
1490                ),
1491                "sink builtin `{builtin}` should not trigger accel-graph construction gate"
1492            );
1493        }
1494    }
1495
1496    #[cfg(feature = "native-accel")]
1497    #[test]
1498    fn compile_scopes_semantic_fusion_metadata_to_entrypoint_target() {
1499        let source = "x = 1; function z = helper(a); t = a + 1; z = t * 2; end;";
1500        let ast = runmat_parser::parse(source).expect("parse");
1501        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
1502        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
1503        let entrypoint = hir.assembly.entrypoints[0].id;
1504        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
1505        let runtime_graph =
1506            bytecode.runtime_accel_graph_for_fusion(&bytecode.runtime_fusion_groups());
1507
1508        assert_eq!(
1509            bytecode.fusion_metadata.mir_fusion_signal_count, 0,
1510            "non-entrypoint helper MIR bodies should not drive entrypoint fusion signal metadata"
1511        );
1512        assert_eq!(
1513            bytecode.fusion_metadata.mir_fusion_candidate_group_count, 0,
1514            "non-entrypoint helper MIR bodies should not drive entrypoint fusion candidate metadata"
1515        );
1516        assert!(
1517            runtime_graph.is_none(),
1518            "entrypoint with no semantic candidates should omit runtime accel graph even if helper bodies are fusible"
1519        );
1520        assert!(
1521            bytecode.fusion_groups.is_empty(),
1522            "entrypoint with no semantic candidates should not emit executable fusion groups"
1523        );
1524    }
1525
1526    #[cfg(feature = "native-accel")]
1527    #[test]
1528    fn fusion_group_semantic_span_filter_requires_full_group_coverage() {
1529        let instr_spans = vec![
1530            runmat_hir::Span { start: 0, end: 2 },
1531            runmat_hir::Span { start: 2, end: 4 },
1532            runmat_hir::Span { start: 4, end: 6 },
1533        ];
1534        let group = runmat_accelerate::fusion::FusionGroup {
1535            id: 0,
1536            kind: runmat_accelerate::fusion::FusionKind::ElementwiseChain,
1537            nodes: vec![],
1538            shape: runmat_accelerate::graph::ShapeInfo::Scalar,
1539            span: runmat_accelerate::graph::InstrSpan { start: 1, end: 2 },
1540            pattern: None,
1541            stack_layout: None,
1542        };
1543        let fully_covering_candidates = vec![crate::bytecode::FusionCandidateGroup {
1544            id: 0,
1545            signal_count: 3,
1546            function: runmat_hir::FunctionId(0),
1547            block: runmat_mir::BasicBlockId(0),
1548            stmt_start: 0,
1549            stmt_end: 2,
1550            source_span: runmat_hir::Span { start: 2, end: 6 },
1551        }];
1552        let partially_covering_candidates = vec![crate::bytecode::FusionCandidateGroup {
1553            id: 0,
1554            signal_count: 2,
1555            function: runmat_hir::FunctionId(0),
1556            block: runmat_mir::BasicBlockId(0),
1557            stmt_start: 0,
1558            stmt_end: 1,
1559            source_span: runmat_hir::Span { start: 0, end: 3 },
1560        }];
1561        let non_overlapping_candidates = vec![crate::bytecode::FusionCandidateGroup {
1562            id: 0,
1563            signal_count: 2,
1564            function: runmat_hir::FunctionId(0),
1565            block: runmat_mir::BasicBlockId(0),
1566            stmt_start: 0,
1567            stmt_end: 1,
1568            source_span: runmat_hir::Span { start: 8, end: 10 },
1569        }];
1570
1571        assert!(
1572            super::fusion_group_within_semantic_candidate_spans(
1573                &group,
1574                &instr_spans,
1575                &fully_covering_candidates
1576            ),
1577            "expected full coverage when all instruction source spans intersect semantic candidate spans"
1578        );
1579        assert!(
1580            !super::fusion_group_within_semantic_candidate_spans(
1581                &group,
1582                &instr_spans,
1583                &partially_covering_candidates
1584            ),
1585            "expected group rejection when only part of the instruction span range intersects semantic candidate spans"
1586        );
1587        assert!(
1588            !super::fusion_group_within_semantic_candidate_spans(
1589                &group,
1590                &instr_spans,
1591                &non_overlapping_candidates
1592            ),
1593            "expected no overlap when instruction source spans are disjoint from semantic candidate spans"
1594        );
1595    }
1596
1597    #[cfg(feature = "native-accel")]
1598    #[test]
1599    fn fusion_group_semantic_span_filter_rejects_multi_candidate_union_coverage() {
1600        let instr_spans = vec![
1601            runmat_hir::Span { start: 0, end: 2 },
1602            runmat_hir::Span { start: 2, end: 4 },
1603        ];
1604        let group = runmat_accelerate::fusion::FusionGroup {
1605            id: 0,
1606            kind: runmat_accelerate::fusion::FusionKind::ElementwiseChain,
1607            nodes: vec![],
1608            shape: runmat_accelerate::graph::ShapeInfo::Scalar,
1609            span: runmat_accelerate::graph::InstrSpan { start: 0, end: 1 },
1610            pattern: None,
1611            stack_layout: None,
1612        };
1613        let split_candidates = vec![
1614            crate::bytecode::FusionCandidateGroup {
1615                id: 0,
1616                signal_count: 1,
1617                function: runmat_hir::FunctionId(0),
1618                block: runmat_mir::BasicBlockId(0),
1619                stmt_start: 0,
1620                stmt_end: 1,
1621                source_span: runmat_hir::Span { start: 0, end: 2 },
1622            },
1623            crate::bytecode::FusionCandidateGroup {
1624                id: 1,
1625                signal_count: 1,
1626                function: runmat_hir::FunctionId(0),
1627                block: runmat_mir::BasicBlockId(0),
1628                stmt_start: 1,
1629                stmt_end: 2,
1630                source_span: runmat_hir::Span { start: 2, end: 4 },
1631            },
1632        ];
1633
1634        assert!(
1635            !super::fusion_group_within_semantic_candidate_spans(
1636                &group,
1637                &instr_spans,
1638                &split_candidates
1639            ),
1640            "expected rejection when bytecode group coverage requires unioning multiple semantic candidate spans"
1641        );
1642    }
1643
1644    #[cfg(feature = "native-accel")]
1645    #[test]
1646    fn candidates_build_fusion_groups_from_accel_graph_nodes() {
1647        let accel_graph = runmat_accelerate::graph::AccelGraph {
1648            nodes: vec![
1649                runmat_accelerate::graph::AccelNode {
1650                    id: 0,
1651                    label: runmat_accelerate::graph::AccelNodeLabel::Primitive(
1652                        runmat_accelerate::graph::PrimitiveOp::Add,
1653                    ),
1654                    category: runmat_accelerate::graph::AccelOpCategory::Elementwise,
1655                    inputs: vec![0, 0],
1656                    outputs: vec![1],
1657                    span: runmat_accelerate::graph::InstrSpan { start: 0, end: 0 },
1658                    tags: vec![runmat_accelerate::graph::AccelGraphTag::Elementwise],
1659                },
1660                runmat_accelerate::graph::AccelNode {
1661                    id: 1,
1662                    label: runmat_accelerate::graph::AccelNodeLabel::Primitive(
1663                        runmat_accelerate::graph::PrimitiveOp::ElemMul,
1664                    ),
1665                    category: runmat_accelerate::graph::AccelOpCategory::Elementwise,
1666                    inputs: vec![1, 0],
1667                    outputs: vec![2],
1668                    span: runmat_accelerate::graph::InstrSpan { start: 1, end: 1 },
1669                    tags: vec![runmat_accelerate::graph::AccelGraphTag::Elementwise],
1670                },
1671            ],
1672            values: vec![
1673                runmat_accelerate::graph::ValueInfo {
1674                    id: 0,
1675                    origin: runmat_accelerate::graph::ValueOrigin::Variable {
1676                        kind: runmat_accelerate::graph::VarKind::Global,
1677                        index: 0,
1678                    },
1679                    ty: runmat_builtins::Type::Num,
1680                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
1681                    constant: None,
1682                },
1683                runmat_accelerate::graph::ValueInfo {
1684                    id: 1,
1685                    origin: runmat_accelerate::graph::ValueOrigin::NodeOutput {
1686                        node: 0,
1687                        output: 0,
1688                    },
1689                    ty: runmat_builtins::Type::Num,
1690                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
1691                    constant: None,
1692                },
1693                runmat_accelerate::graph::ValueInfo {
1694                    id: 2,
1695                    origin: runmat_accelerate::graph::ValueOrigin::NodeOutput {
1696                        node: 1,
1697                        output: 0,
1698                    },
1699                    ty: runmat_builtins::Type::Num,
1700                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
1701                    constant: None,
1702                },
1703            ],
1704            var_bindings: std::collections::HashMap::new(),
1705            node_bindings: std::collections::HashMap::new(),
1706        };
1707        let instr_spans = vec![
1708            runmat_hir::Span { start: 10, end: 11 },
1709            runmat_hir::Span { start: 11, end: 12 },
1710        ];
1711        let candidates = vec![crate::bytecode::FusionCandidateGroup {
1712            id: 0,
1713            signal_count: 2,
1714            function: runmat_hir::FunctionId(0),
1715            block: runmat_mir::BasicBlockId(0),
1716            stmt_start: 0,
1717            stmt_end: 2,
1718            source_span: runmat_hir::Span { start: 10, end: 12 },
1719        }];
1720
1721        let windows = super::derive_semantic_fusion_instruction_windows(
1722            &[Instr::Add, Instr::ElemMul],
1723            &instr_spans,
1724            &candidates,
1725        );
1726        let groups = super::derive_semantic_fusion_groups_from_candidates(&windows, &accel_graph);
1727        assert_eq!(groups.len(), 1, "expected one semantic-driven fusion group");
1728        assert_eq!(groups[0].nodes, vec![0, 1]);
1729        assert_eq!(
1730            groups[0].kind,
1731            runmat_accelerate::fusion::FusionKind::ElementwiseChain
1732        );
1733        assert_eq!(
1734            groups[0].shape,
1735            runmat_accelerate::graph::ShapeInfo::Unknown
1736        );
1737    }
1738
1739    #[cfg(feature = "native-accel")]
1740    #[test]
1741    fn windows_fallback_to_empty_node_groups_when_mapping_drops_all_nodes() {
1742        let windows = vec![crate::bytecode::FusionInstructionWindow {
1743            span: runmat_accelerate::graph::InstrSpan { start: 7, end: 9 },
1744            kind: crate::bytecode::FusionInstructionKind::Elementwise,
1745        }];
1746        let groups = super::derive_semantic_fusion_groups_from_instruction_windows(&windows);
1747        assert_eq!(
1748            groups.len(),
1749            1,
1750            "semantic windows fallback should preserve executable-group scaffolding even when graph mapping is unavailable"
1751        );
1752        assert_eq!(
1753            groups[0].nodes,
1754            Vec::<runmat_accelerate::graph::NodeId>::new()
1755        );
1756        assert_eq!(groups[0].span.start, 7);
1757        assert_eq!(groups[0].span.end, 9);
1758        assert_eq!(
1759            groups[0].kind,
1760            runmat_accelerate::fusion::FusionKind::ElementwiseChain
1761        );
1762    }
1763
1764    #[cfg(feature = "native-accel")]
1765    #[test]
1766    fn windows_preserve_unmapped_windows_alongside_mapped_groups() {
1767        let accel_graph = runmat_accelerate::graph::AccelGraph {
1768            nodes: vec![runmat_accelerate::graph::AccelNode {
1769                id: 0,
1770                label: runmat_accelerate::graph::AccelNodeLabel::Primitive(
1771                    runmat_accelerate::graph::PrimitiveOp::Add,
1772                ),
1773                category: runmat_accelerate::graph::AccelOpCategory::Elementwise,
1774                inputs: vec![0, 0],
1775                outputs: vec![1],
1776                span: runmat_accelerate::graph::InstrSpan { start: 0, end: 0 },
1777                tags: vec![],
1778            }],
1779            values: vec![
1780                runmat_accelerate::graph::ValueInfo {
1781                    id: 0,
1782                    origin: runmat_accelerate::graph::ValueOrigin::Variable {
1783                        kind: runmat_accelerate::graph::VarKind::Global,
1784                        index: 0,
1785                    },
1786                    ty: runmat_builtins::Type::Num,
1787                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
1788                    constant: None,
1789                },
1790                runmat_accelerate::graph::ValueInfo {
1791                    id: 1,
1792                    origin: runmat_accelerate::graph::ValueOrigin::NodeOutput {
1793                        node: 0,
1794                        output: 0,
1795                    },
1796                    ty: runmat_builtins::Type::Num,
1797                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
1798                    constant: None,
1799                },
1800            ],
1801            var_bindings: std::collections::HashMap::new(),
1802            node_bindings: std::collections::HashMap::new(),
1803        };
1804        let windows = vec![
1805            crate::bytecode::FusionInstructionWindow {
1806                span: runmat_accelerate::graph::InstrSpan { start: 0, end: 0 },
1807                kind: crate::bytecode::FusionInstructionKind::Elementwise,
1808            },
1809            crate::bytecode::FusionInstructionWindow {
1810                span: runmat_accelerate::graph::InstrSpan { start: 5, end: 5 },
1811                kind: crate::bytecode::FusionInstructionKind::Elementwise,
1812            },
1813        ];
1814        let groups = super::derive_semantic_fusion_groups_preserving_unmapped_windows(
1815            &windows,
1816            &accel_graph,
1817        );
1818        assert_eq!(groups.len(), 2);
1819        assert_eq!(groups[0].nodes, vec![0]);
1820        assert_eq!(
1821            groups[1].nodes,
1822            Vec::<runmat_accelerate::graph::NodeId>::new()
1823        );
1824        assert_eq!(groups[0].span.start, 0);
1825        assert_eq!(groups[1].span.start, 5);
1826    }
1827
1828    #[cfg(feature = "native-accel")]
1829    #[test]
1830    fn windows_map_accel_nodes_without_semantic_tags() {
1831        let accel_graph = runmat_accelerate::graph::AccelGraph {
1832            nodes: vec![runmat_accelerate::graph::AccelNode {
1833                id: 0,
1834                label: runmat_accelerate::graph::AccelNodeLabel::Primitive(
1835                    runmat_accelerate::graph::PrimitiveOp::Add,
1836                ),
1837                category: runmat_accelerate::graph::AccelOpCategory::Elementwise,
1838                inputs: vec![0, 0],
1839                outputs: vec![1],
1840                span: runmat_accelerate::graph::InstrSpan { start: 0, end: 0 },
1841                tags: vec![],
1842            }],
1843            values: vec![
1844                runmat_accelerate::graph::ValueInfo {
1845                    id: 0,
1846                    origin: runmat_accelerate::graph::ValueOrigin::Variable {
1847                        kind: runmat_accelerate::graph::VarKind::Global,
1848                        index: 0,
1849                    },
1850                    ty: runmat_builtins::Type::Num,
1851                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
1852                    constant: None,
1853                },
1854                runmat_accelerate::graph::ValueInfo {
1855                    id: 1,
1856                    origin: runmat_accelerate::graph::ValueOrigin::NodeOutput {
1857                        node: 0,
1858                        output: 0,
1859                    },
1860                    ty: runmat_builtins::Type::Num,
1861                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
1862                    constant: None,
1863                },
1864            ],
1865            var_bindings: std::collections::HashMap::new(),
1866            node_bindings: std::collections::HashMap::new(),
1867        };
1868        let windows = vec![crate::bytecode::FusionInstructionWindow {
1869            span: runmat_accelerate::graph::InstrSpan { start: 0, end: 0 },
1870            kind: crate::bytecode::FusionInstructionKind::Elementwise,
1871        }];
1872        let groups = super::derive_semantic_fusion_groups_from_candidates(&windows, &accel_graph);
1873        assert_eq!(
1874            groups.len(),
1875            1,
1876            "semantic windows should still map accel nodes when graph semantic tags are absent"
1877        );
1878        assert_eq!(groups[0].nodes, vec![0]);
1879        assert_eq!(
1880            groups[0].kind,
1881            runmat_accelerate::fusion::FusionKind::ElementwiseChain
1882        );
1883    }
1884
1885    #[cfg(feature = "native-accel")]
1886    #[test]
1887    fn windows_without_tags_reject_category_mismatch() {
1888        let accel_graph = runmat_accelerate::graph::AccelGraph {
1889            nodes: vec![runmat_accelerate::graph::AccelNode {
1890                id: 0,
1891                label: runmat_accelerate::graph::AccelNodeLabel::Builtin {
1892                    name: "sum".to_string(),
1893                },
1894                category: runmat_accelerate::graph::AccelOpCategory::Reduction,
1895                inputs: vec![0],
1896                outputs: vec![1],
1897                span: runmat_accelerate::graph::InstrSpan { start: 0, end: 0 },
1898                tags: vec![],
1899            }],
1900            values: vec![
1901                runmat_accelerate::graph::ValueInfo {
1902                    id: 0,
1903                    origin: runmat_accelerate::graph::ValueOrigin::Variable {
1904                        kind: runmat_accelerate::graph::VarKind::Global,
1905                        index: 0,
1906                    },
1907                    ty: runmat_builtins::Type::Num,
1908                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
1909                    constant: None,
1910                },
1911                runmat_accelerate::graph::ValueInfo {
1912                    id: 1,
1913                    origin: runmat_accelerate::graph::ValueOrigin::NodeOutput {
1914                        node: 0,
1915                        output: 0,
1916                    },
1917                    ty: runmat_builtins::Type::Num,
1918                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
1919                    constant: None,
1920                },
1921            ],
1922            var_bindings: std::collections::HashMap::new(),
1923            node_bindings: std::collections::HashMap::new(),
1924        };
1925        let windows = vec![crate::bytecode::FusionInstructionWindow {
1926            span: runmat_accelerate::graph::InstrSpan { start: 0, end: 0 },
1927            kind: crate::bytecode::FusionInstructionKind::Elementwise,
1928        }];
1929        let groups = super::derive_semantic_fusion_groups_from_candidates(&windows, &accel_graph);
1930        assert!(
1931            groups.is_empty(),
1932            "missing tags should not bypass category mismatch for semantic window mapping"
1933        );
1934    }
1935
1936    #[cfg(feature = "native-accel")]
1937    #[test]
1938    fn windows_reject_covering_node_span_at_compile_mapping_stage() {
1939        let accel_graph = runmat_accelerate::graph::AccelGraph {
1940            nodes: vec![runmat_accelerate::graph::AccelNode {
1941                id: 0,
1942                label: runmat_accelerate::graph::AccelNodeLabel::Primitive(
1943                    runmat_accelerate::graph::PrimitiveOp::Add,
1944                ),
1945                category: runmat_accelerate::graph::AccelOpCategory::Elementwise,
1946                inputs: vec![0, 0],
1947                outputs: vec![1],
1948                span: runmat_accelerate::graph::InstrSpan { start: 0, end: 2 },
1949                tags: vec![runmat_accelerate::graph::AccelGraphTag::Elementwise],
1950            }],
1951            values: vec![
1952                runmat_accelerate::graph::ValueInfo {
1953                    id: 0,
1954                    origin: runmat_accelerate::graph::ValueOrigin::Variable {
1955                        kind: runmat_accelerate::graph::VarKind::Global,
1956                        index: 0,
1957                    },
1958                    ty: runmat_builtins::Type::Num,
1959                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
1960                    constant: None,
1961                },
1962                runmat_accelerate::graph::ValueInfo {
1963                    id: 1,
1964                    origin: runmat_accelerate::graph::ValueOrigin::NodeOutput {
1965                        node: 0,
1966                        output: 0,
1967                    },
1968                    ty: runmat_builtins::Type::Num,
1969                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
1970                    constant: None,
1971                },
1972            ],
1973            var_bindings: std::collections::HashMap::new(),
1974            node_bindings: std::collections::HashMap::new(),
1975        };
1976        let windows = vec![crate::bytecode::FusionInstructionWindow {
1977            span: runmat_accelerate::graph::InstrSpan { start: 1, end: 1 },
1978            kind: crate::bytecode::FusionInstructionKind::Elementwise,
1979        }];
1980        let groups = super::derive_semantic_fusion_groups_from_candidates(&windows, &accel_graph);
1981        assert!(
1982            groups.is_empty(),
1983            "compile-time mapping should reject covering node spans and defer reconciliation to runtime sanitization"
1984        );
1985    }
1986
1987    #[cfg(feature = "native-accel")]
1988    #[test]
1989    fn windows_reject_overly_wide_covering_node_spans() {
1990        let accel_graph = runmat_accelerate::graph::AccelGraph {
1991            nodes: vec![runmat_accelerate::graph::AccelNode {
1992                id: 0,
1993                label: runmat_accelerate::graph::AccelNodeLabel::Primitive(
1994                    runmat_accelerate::graph::PrimitiveOp::Add,
1995                ),
1996                category: runmat_accelerate::graph::AccelOpCategory::Elementwise,
1997                inputs: vec![0, 0],
1998                outputs: vec![1],
1999                span: runmat_accelerate::graph::InstrSpan { start: 0, end: 4 },
2000                tags: vec![runmat_accelerate::graph::AccelGraphTag::Elementwise],
2001            }],
2002            values: vec![
2003                runmat_accelerate::graph::ValueInfo {
2004                    id: 0,
2005                    origin: runmat_accelerate::graph::ValueOrigin::Variable {
2006                        kind: runmat_accelerate::graph::VarKind::Global,
2007                        index: 0,
2008                    },
2009                    ty: runmat_builtins::Type::Num,
2010                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2011                    constant: None,
2012                },
2013                runmat_accelerate::graph::ValueInfo {
2014                    id: 1,
2015                    origin: runmat_accelerate::graph::ValueOrigin::NodeOutput {
2016                        node: 0,
2017                        output: 0,
2018                    },
2019                    ty: runmat_builtins::Type::Num,
2020                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2021                    constant: None,
2022                },
2023            ],
2024            var_bindings: std::collections::HashMap::new(),
2025            node_bindings: std::collections::HashMap::new(),
2026        };
2027        let windows = vec![crate::bytecode::FusionInstructionWindow {
2028            span: runmat_accelerate::graph::InstrSpan { start: 2, end: 2 },
2029            kind: crate::bytecode::FusionInstructionKind::Elementwise,
2030        }];
2031        let groups = super::derive_semantic_fusion_groups_from_candidates(&windows, &accel_graph);
2032        assert!(
2033            groups.is_empty(),
2034            "semantic windows should reject overly broad covering node spans"
2035        );
2036    }
2037
2038    #[cfg(feature = "native-accel")]
2039    #[test]
2040    fn windows_reject_partial_overlap_at_compile_mapping_stage() {
2041        let accel_graph = runmat_accelerate::graph::AccelGraph {
2042            nodes: vec![runmat_accelerate::graph::AccelNode {
2043                id: 0,
2044                label: runmat_accelerate::graph::AccelNodeLabel::Primitive(
2045                    runmat_accelerate::graph::PrimitiveOp::Add,
2046                ),
2047                category: runmat_accelerate::graph::AccelOpCategory::Elementwise,
2048                inputs: vec![0, 0],
2049                outputs: vec![1],
2050                span: runmat_accelerate::graph::InstrSpan { start: 0, end: 1 },
2051                tags: vec![runmat_accelerate::graph::AccelGraphTag::Elementwise],
2052            }],
2053            values: vec![
2054                runmat_accelerate::graph::ValueInfo {
2055                    id: 0,
2056                    origin: runmat_accelerate::graph::ValueOrigin::Variable {
2057                        kind: runmat_accelerate::graph::VarKind::Global,
2058                        index: 0,
2059                    },
2060                    ty: runmat_builtins::Type::Num,
2061                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2062                    constant: None,
2063                },
2064                runmat_accelerate::graph::ValueInfo {
2065                    id: 1,
2066                    origin: runmat_accelerate::graph::ValueOrigin::NodeOutput {
2067                        node: 0,
2068                        output: 0,
2069                    },
2070                    ty: runmat_builtins::Type::Num,
2071                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2072                    constant: None,
2073                },
2074            ],
2075            var_bindings: std::collections::HashMap::new(),
2076            node_bindings: std::collections::HashMap::new(),
2077        };
2078        let windows = vec![crate::bytecode::FusionInstructionWindow {
2079            span: runmat_accelerate::graph::InstrSpan { start: 1, end: 2 },
2080            kind: crate::bytecode::FusionInstructionKind::Elementwise,
2081        }];
2082        let groups = super::derive_semantic_fusion_groups_from_candidates(&windows, &accel_graph);
2083        assert!(
2084            groups.is_empty(),
2085            "compile-time mapping should reject partial overlap and defer reconciliation to runtime sanitization"
2086        );
2087    }
2088
2089    #[cfg(feature = "native-accel")]
2090    #[test]
2091    fn windows_reject_partial_overlap_with_large_boundary_shift() {
2092        let accel_graph = runmat_accelerate::graph::AccelGraph {
2093            nodes: vec![runmat_accelerate::graph::AccelNode {
2094                id: 0,
2095                label: runmat_accelerate::graph::AccelNodeLabel::Primitive(
2096                    runmat_accelerate::graph::PrimitiveOp::Add,
2097                ),
2098                category: runmat_accelerate::graph::AccelOpCategory::Elementwise,
2099                inputs: vec![0, 0],
2100                outputs: vec![1],
2101                span: runmat_accelerate::graph::InstrSpan { start: 0, end: 2 },
2102                tags: vec![runmat_accelerate::graph::AccelGraphTag::Elementwise],
2103            }],
2104            values: vec![
2105                runmat_accelerate::graph::ValueInfo {
2106                    id: 0,
2107                    origin: runmat_accelerate::graph::ValueOrigin::Variable {
2108                        kind: runmat_accelerate::graph::VarKind::Global,
2109                        index: 0,
2110                    },
2111                    ty: runmat_builtins::Type::Num,
2112                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2113                    constant: None,
2114                },
2115                runmat_accelerate::graph::ValueInfo {
2116                    id: 1,
2117                    origin: runmat_accelerate::graph::ValueOrigin::NodeOutput {
2118                        node: 0,
2119                        output: 0,
2120                    },
2121                    ty: runmat_builtins::Type::Num,
2122                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2123                    constant: None,
2124                },
2125            ],
2126            var_bindings: std::collections::HashMap::new(),
2127            node_bindings: std::collections::HashMap::new(),
2128        };
2129        let windows = vec![crate::bytecode::FusionInstructionWindow {
2130            span: runmat_accelerate::graph::InstrSpan { start: 2, end: 3 },
2131            kind: crate::bytecode::FusionInstructionKind::Elementwise,
2132        }];
2133        let groups = super::derive_semantic_fusion_groups_from_candidates(&windows, &accel_graph);
2134        assert!(
2135            groups.is_empty(),
2136            "semantic windows should reject partial overlap when boundary shift exceeds tolerance"
2137        );
2138    }
2139
2140    #[cfg(feature = "native-accel")]
2141    #[test]
2142    fn windows_reject_disjoint_gap_at_compile_mapping_stage() {
2143        let accel_graph = runmat_accelerate::graph::AccelGraph {
2144            nodes: vec![runmat_accelerate::graph::AccelNode {
2145                id: 0,
2146                label: runmat_accelerate::graph::AccelNodeLabel::Primitive(
2147                    runmat_accelerate::graph::PrimitiveOp::Add,
2148                ),
2149                category: runmat_accelerate::graph::AccelOpCategory::Elementwise,
2150                inputs: vec![0, 0],
2151                outputs: vec![1],
2152                span: runmat_accelerate::graph::InstrSpan { start: 0, end: 0 },
2153                tags: vec![runmat_accelerate::graph::AccelGraphTag::Elementwise],
2154            }],
2155            values: vec![
2156                runmat_accelerate::graph::ValueInfo {
2157                    id: 0,
2158                    origin: runmat_accelerate::graph::ValueOrigin::Variable {
2159                        kind: runmat_accelerate::graph::VarKind::Global,
2160                        index: 0,
2161                    },
2162                    ty: runmat_builtins::Type::Num,
2163                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2164                    constant: None,
2165                },
2166                runmat_accelerate::graph::ValueInfo {
2167                    id: 1,
2168                    origin: runmat_accelerate::graph::ValueOrigin::NodeOutput {
2169                        node: 0,
2170                        output: 0,
2171                    },
2172                    ty: runmat_builtins::Type::Num,
2173                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2174                    constant: None,
2175                },
2176            ],
2177            var_bindings: std::collections::HashMap::new(),
2178            node_bindings: std::collections::HashMap::new(),
2179        };
2180        let windows = vec![crate::bytecode::FusionInstructionWindow {
2181            span: runmat_accelerate::graph::InstrSpan { start: 1, end: 1 },
2182            kind: crate::bytecode::FusionInstructionKind::Elementwise,
2183        }];
2184        let groups = super::derive_semantic_fusion_groups_from_candidates(&windows, &accel_graph);
2185        assert!(
2186            groups.is_empty(),
2187            "compile-time mapping should reject disjoint graph/window spans and leave reconciliation to runtime sanitization"
2188        );
2189    }
2190
2191    #[cfg(feature = "native-accel")]
2192    #[test]
2193    fn windows_reject_accel_nodes_with_large_disjoint_gap() {
2194        let accel_graph = runmat_accelerate::graph::AccelGraph {
2195            nodes: vec![runmat_accelerate::graph::AccelNode {
2196                id: 0,
2197                label: runmat_accelerate::graph::AccelNodeLabel::Primitive(
2198                    runmat_accelerate::graph::PrimitiveOp::Add,
2199                ),
2200                category: runmat_accelerate::graph::AccelOpCategory::Elementwise,
2201                inputs: vec![0, 0],
2202                outputs: vec![1],
2203                span: runmat_accelerate::graph::InstrSpan { start: 0, end: 0 },
2204                tags: vec![runmat_accelerate::graph::AccelGraphTag::Elementwise],
2205            }],
2206            values: vec![
2207                runmat_accelerate::graph::ValueInfo {
2208                    id: 0,
2209                    origin: runmat_accelerate::graph::ValueOrigin::Variable {
2210                        kind: runmat_accelerate::graph::VarKind::Global,
2211                        index: 0,
2212                    },
2213                    ty: runmat_builtins::Type::Num,
2214                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2215                    constant: None,
2216                },
2217                runmat_accelerate::graph::ValueInfo {
2218                    id: 1,
2219                    origin: runmat_accelerate::graph::ValueOrigin::NodeOutput {
2220                        node: 0,
2221                        output: 0,
2222                    },
2223                    ty: runmat_builtins::Type::Num,
2224                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2225                    constant: None,
2226                },
2227            ],
2228            var_bindings: std::collections::HashMap::new(),
2229            node_bindings: std::collections::HashMap::new(),
2230        };
2231        let windows = vec![crate::bytecode::FusionInstructionWindow {
2232            span: runmat_accelerate::graph::InstrSpan { start: 3, end: 3 },
2233            kind: crate::bytecode::FusionInstructionKind::Elementwise,
2234        }];
2235        let groups = super::derive_semantic_fusion_groups_from_candidates(&windows, &accel_graph);
2236        assert!(
2237            groups.is_empty(),
2238            "semantic windows should reject accel-node mapping when disjoint span gap exceeds tolerance"
2239        );
2240    }
2241
2242    #[cfg(feature = "native-accel")]
2243    #[test]
2244    fn window_kind_is_not_overridden_by_graph_category() {
2245        let accel_graph = runmat_accelerate::graph::AccelGraph {
2246            nodes: vec![runmat_accelerate::graph::AccelNode {
2247                id: 0,
2248                label: runmat_accelerate::graph::AccelNodeLabel::Primitive(
2249                    runmat_accelerate::graph::PrimitiveOp::Add,
2250                ),
2251                category: runmat_accelerate::graph::AccelOpCategory::Reduction,
2252                inputs: vec![0, 0],
2253                outputs: vec![1],
2254                span: runmat_accelerate::graph::InstrSpan { start: 0, end: 0 },
2255                tags: vec![runmat_accelerate::graph::AccelGraphTag::Elementwise],
2256            }],
2257            values: vec![
2258                runmat_accelerate::graph::ValueInfo {
2259                    id: 0,
2260                    origin: runmat_accelerate::graph::ValueOrigin::Variable {
2261                        kind: runmat_accelerate::graph::VarKind::Global,
2262                        index: 0,
2263                    },
2264                    ty: runmat_builtins::Type::Num,
2265                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2266                    constant: None,
2267                },
2268                runmat_accelerate::graph::ValueInfo {
2269                    id: 1,
2270                    origin: runmat_accelerate::graph::ValueOrigin::NodeOutput {
2271                        node: 0,
2272                        output: 0,
2273                    },
2274                    ty: runmat_builtins::Type::Num,
2275                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2276                    constant: None,
2277                },
2278            ],
2279            var_bindings: std::collections::HashMap::new(),
2280            node_bindings: std::collections::HashMap::new(),
2281        };
2282        let instr_spans = vec![runmat_hir::Span { start: 10, end: 11 }];
2283        let candidates = vec![crate::bytecode::FusionCandidateGroup {
2284            id: 0,
2285            signal_count: 1,
2286            function: runmat_hir::FunctionId(0),
2287            block: runmat_mir::BasicBlockId(0),
2288            stmt_start: 0,
2289            stmt_end: 1,
2290            source_span: runmat_hir::Span { start: 10, end: 11 },
2291        }];
2292
2293        let windows = super::derive_semantic_fusion_instruction_windows(
2294            &[Instr::Add],
2295            &instr_spans,
2296            &candidates,
2297        );
2298        let groups = super::derive_semantic_fusion_groups_from_candidates(&windows, &accel_graph);
2299        assert_eq!(groups.len(), 1, "expected one semantic fusion group");
2300        assert_eq!(
2301            groups[0].kind,
2302            runmat_accelerate::fusion::FusionKind::ElementwiseChain,
2303            "semantic instruction-window kind should drive fusion kind classification"
2304        );
2305    }
2306
2307    #[cfg(feature = "native-accel")]
2308    #[test]
2309    fn candidates_build_fusion_groups_from_transpose_nodes() {
2310        let accel_graph = runmat_accelerate::graph::AccelGraph {
2311            nodes: vec![runmat_accelerate::graph::AccelNode {
2312                id: 0,
2313                label: runmat_accelerate::graph::AccelNodeLabel::Primitive(
2314                    runmat_accelerate::graph::PrimitiveOp::Transpose,
2315                ),
2316                category: runmat_accelerate::graph::AccelOpCategory::Transpose,
2317                inputs: vec![0],
2318                outputs: vec![1],
2319                span: runmat_accelerate::graph::InstrSpan { start: 0, end: 0 },
2320                tags: vec![runmat_accelerate::graph::AccelGraphTag::Transpose],
2321            }],
2322            values: vec![
2323                runmat_accelerate::graph::ValueInfo {
2324                    id: 0,
2325                    origin: runmat_accelerate::graph::ValueOrigin::Variable {
2326                        kind: runmat_accelerate::graph::VarKind::Global,
2327                        index: 0,
2328                    },
2329                    ty: runmat_builtins::Type::Num,
2330                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2331                    constant: None,
2332                },
2333                runmat_accelerate::graph::ValueInfo {
2334                    id: 1,
2335                    origin: runmat_accelerate::graph::ValueOrigin::NodeOutput {
2336                        node: 0,
2337                        output: 0,
2338                    },
2339                    ty: runmat_builtins::Type::Num,
2340                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2341                    constant: None,
2342                },
2343            ],
2344            var_bindings: std::collections::HashMap::new(),
2345            node_bindings: std::collections::HashMap::new(),
2346        };
2347        let instr_spans = vec![runmat_hir::Span { start: 10, end: 11 }];
2348        let candidates = vec![crate::bytecode::FusionCandidateGroup {
2349            id: 0,
2350            signal_count: 1,
2351            function: runmat_hir::FunctionId(0),
2352            block: runmat_mir::BasicBlockId(0),
2353            stmt_start: 0,
2354            stmt_end: 1,
2355            source_span: runmat_hir::Span { start: 10, end: 11 },
2356        }];
2357
2358        let windows = super::derive_semantic_fusion_instruction_windows(
2359            &[Instr::Transpose],
2360            &instr_spans,
2361            &candidates,
2362        );
2363        let groups = super::derive_semantic_fusion_groups_from_candidates(&windows, &accel_graph);
2364        assert_eq!(
2365            groups.len(),
2366            1,
2367            "expected transpose-tagged accel node to participate in semantic fusion-group mapping"
2368        );
2369        assert_eq!(groups[0].nodes, vec![0]);
2370        assert_eq!(
2371            groups[0].kind,
2372            runmat_accelerate::fusion::FusionKind::ElementwiseChain
2373        );
2374    }
2375
2376    #[cfg(feature = "native-accel")]
2377    #[test]
2378    fn elementwise_window_excludes_reduction_nodes() {
2379        let accel_graph = runmat_accelerate::graph::AccelGraph {
2380            nodes: vec![runmat_accelerate::graph::AccelNode {
2381                id: 0,
2382                label: runmat_accelerate::graph::AccelNodeLabel::Builtin {
2383                    name: "sum".to_string(),
2384                },
2385                category: runmat_accelerate::graph::AccelOpCategory::Reduction,
2386                inputs: vec![0],
2387                outputs: vec![1],
2388                span: runmat_accelerate::graph::InstrSpan { start: 0, end: 0 },
2389                tags: vec![runmat_accelerate::graph::AccelGraphTag::Reduction],
2390            }],
2391            values: vec![
2392                runmat_accelerate::graph::ValueInfo {
2393                    id: 0,
2394                    origin: runmat_accelerate::graph::ValueOrigin::Variable {
2395                        kind: runmat_accelerate::graph::VarKind::Global,
2396                        index: 0,
2397                    },
2398                    ty: runmat_builtins::Type::Num,
2399                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2400                    constant: None,
2401                },
2402                runmat_accelerate::graph::ValueInfo {
2403                    id: 1,
2404                    origin: runmat_accelerate::graph::ValueOrigin::NodeOutput {
2405                        node: 0,
2406                        output: 0,
2407                    },
2408                    ty: runmat_builtins::Type::Num,
2409                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2410                    constant: None,
2411                },
2412            ],
2413            var_bindings: std::collections::HashMap::new(),
2414            node_bindings: std::collections::HashMap::new(),
2415        };
2416        let windows = vec![crate::bytecode::FusionInstructionWindow {
2417            span: runmat_accelerate::graph::InstrSpan { start: 0, end: 0 },
2418            kind: crate::bytecode::FusionInstructionKind::Elementwise,
2419        }];
2420        let groups = super::derive_semantic_fusion_groups_from_candidates(&windows, &accel_graph);
2421        assert!(
2422            groups.is_empty(),
2423            "elementwise semantic windows should not absorb reduction-tagged accel nodes"
2424        );
2425    }
2426
2427    #[cfg(feature = "native-accel")]
2428    #[test]
2429    fn reduction_window_accepts_reduction_nodes() {
2430        let accel_graph = runmat_accelerate::graph::AccelGraph {
2431            nodes: vec![runmat_accelerate::graph::AccelNode {
2432                id: 0,
2433                label: runmat_accelerate::graph::AccelNodeLabel::Builtin {
2434                    name: "sum".to_string(),
2435                },
2436                category: runmat_accelerate::graph::AccelOpCategory::Reduction,
2437                inputs: vec![0],
2438                outputs: vec![1],
2439                span: runmat_accelerate::graph::InstrSpan { start: 0, end: 0 },
2440                tags: vec![runmat_accelerate::graph::AccelGraphTag::Reduction],
2441            }],
2442            values: vec![
2443                runmat_accelerate::graph::ValueInfo {
2444                    id: 0,
2445                    origin: runmat_accelerate::graph::ValueOrigin::Variable {
2446                        kind: runmat_accelerate::graph::VarKind::Global,
2447                        index: 0,
2448                    },
2449                    ty: runmat_builtins::Type::Num,
2450                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2451                    constant: None,
2452                },
2453                runmat_accelerate::graph::ValueInfo {
2454                    id: 1,
2455                    origin: runmat_accelerate::graph::ValueOrigin::NodeOutput {
2456                        node: 0,
2457                        output: 0,
2458                    },
2459                    ty: runmat_builtins::Type::Num,
2460                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2461                    constant: None,
2462                },
2463            ],
2464            var_bindings: std::collections::HashMap::new(),
2465            node_bindings: std::collections::HashMap::new(),
2466        };
2467        let windows = vec![crate::bytecode::FusionInstructionWindow {
2468            span: runmat_accelerate::graph::InstrSpan { start: 0, end: 0 },
2469            kind: crate::bytecode::FusionInstructionKind::Reduction,
2470        }];
2471        let groups = super::derive_semantic_fusion_groups_from_candidates(&windows, &accel_graph);
2472        assert_eq!(
2473            groups.len(),
2474            1,
2475            "reduction semantic windows should include reduction-tagged accel nodes"
2476        );
2477        assert_eq!(groups[0].nodes, vec![0]);
2478        assert_eq!(
2479            groups[0].kind,
2480            runmat_accelerate::fusion::FusionKind::Reduction
2481        );
2482    }
2483
2484    #[cfg(feature = "native-accel")]
2485    #[test]
2486    fn candidate_instruction_windows_split_on_non_accel_ops() {
2487        let instructions = vec![Instr::Add, Instr::LoadConst(1.0), Instr::ElemMul];
2488        let instr_spans = vec![
2489            runmat_hir::Span { start: 10, end: 11 },
2490            runmat_hir::Span { start: 11, end: 12 },
2491            runmat_hir::Span { start: 12, end: 13 },
2492        ];
2493        let candidates = vec![crate::bytecode::FusionCandidateGroup {
2494            id: 0,
2495            signal_count: 3,
2496            function: runmat_hir::FunctionId(0),
2497            block: runmat_mir::BasicBlockId(0),
2498            stmt_start: 0,
2499            stmt_end: 3,
2500            source_span: runmat_hir::Span { start: 10, end: 13 },
2501        }];
2502
2503        let windows = super::derive_semantic_fusion_instruction_windows(
2504            &instructions,
2505            &instr_spans,
2506            &candidates,
2507        );
2508        assert_eq!(
2509            windows.len(),
2510            2,
2511            "expected non-accel instruction boundary to split semantic instruction windows"
2512        );
2513        assert_eq!(windows[0].span.start, 0);
2514        assert_eq!(windows[0].span.end, 0);
2515        assert_eq!(windows[1].span.start, 2);
2516        assert_eq!(windows[1].span.end, 2);
2517    }
2518
2519    #[cfg(feature = "native-accel")]
2520    #[test]
2521    fn candidates_without_overlap_do_not_build_fusion_groups() {
2522        let accel_graph = runmat_accelerate::graph::AccelGraph {
2523            nodes: vec![runmat_accelerate::graph::AccelNode {
2524                id: 0,
2525                label: runmat_accelerate::graph::AccelNodeLabel::Primitive(
2526                    runmat_accelerate::graph::PrimitiveOp::Add,
2527                ),
2528                category: runmat_accelerate::graph::AccelOpCategory::Elementwise,
2529                inputs: vec![0, 0],
2530                outputs: vec![1],
2531                span: runmat_accelerate::graph::InstrSpan { start: 0, end: 0 },
2532                tags: vec![runmat_accelerate::graph::AccelGraphTag::Elementwise],
2533            }],
2534            values: vec![
2535                runmat_accelerate::graph::ValueInfo {
2536                    id: 0,
2537                    origin: runmat_accelerate::graph::ValueOrigin::Variable {
2538                        kind: runmat_accelerate::graph::VarKind::Global,
2539                        index: 0,
2540                    },
2541                    ty: runmat_builtins::Type::Num,
2542                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2543                    constant: None,
2544                },
2545                runmat_accelerate::graph::ValueInfo {
2546                    id: 1,
2547                    origin: runmat_accelerate::graph::ValueOrigin::NodeOutput {
2548                        node: 0,
2549                        output: 0,
2550                    },
2551                    ty: runmat_builtins::Type::Num,
2552                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2553                    constant: None,
2554                },
2555            ],
2556            var_bindings: std::collections::HashMap::new(),
2557            node_bindings: std::collections::HashMap::new(),
2558        };
2559        let instr_spans = vec![runmat_hir::Span { start: 10, end: 11 }];
2560        let candidates = vec![crate::bytecode::FusionCandidateGroup {
2561            id: 0,
2562            signal_count: 1,
2563            function: runmat_hir::FunctionId(0),
2564            block: runmat_mir::BasicBlockId(0),
2565            stmt_start: 0,
2566            stmt_end: 1,
2567            source_span: runmat_hir::Span {
2568                start: 100,
2569                end: 101,
2570            },
2571        }];
2572
2573        let windows = super::derive_semantic_fusion_instruction_windows(
2574            &[Instr::Add],
2575            &instr_spans,
2576            &candidates,
2577        );
2578        let groups = super::derive_semantic_fusion_groups_from_candidates(&windows, &accel_graph);
2579        assert!(
2580            groups.is_empty(),
2581            "expected no semantic-driven fusion groups when candidate spans do not overlap instruction source spans"
2582        );
2583    }
2584
2585    #[cfg(feature = "native-accel")]
2586    #[test]
2587    fn candidates_with_partial_overlap_do_not_build_fusion_groups() {
2588        let accel_graph = runmat_accelerate::graph::AccelGraph {
2589            nodes: vec![runmat_accelerate::graph::AccelNode {
2590                id: 0,
2591                label: runmat_accelerate::graph::AccelNodeLabel::Primitive(
2592                    runmat_accelerate::graph::PrimitiveOp::Add,
2593                ),
2594                category: runmat_accelerate::graph::AccelOpCategory::Elementwise,
2595                inputs: vec![0, 0],
2596                outputs: vec![1],
2597                span: runmat_accelerate::graph::InstrSpan { start: 0, end: 0 },
2598                tags: vec![runmat_accelerate::graph::AccelGraphTag::Elementwise],
2599            }],
2600            values: vec![
2601                runmat_accelerate::graph::ValueInfo {
2602                    id: 0,
2603                    origin: runmat_accelerate::graph::ValueOrigin::Variable {
2604                        kind: runmat_accelerate::graph::VarKind::Global,
2605                        index: 0,
2606                    },
2607                    ty: runmat_builtins::Type::Num,
2608                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2609                    constant: None,
2610                },
2611                runmat_accelerate::graph::ValueInfo {
2612                    id: 1,
2613                    origin: runmat_accelerate::graph::ValueOrigin::NodeOutput {
2614                        node: 0,
2615                        output: 0,
2616                    },
2617                    ty: runmat_builtins::Type::Num,
2618                    shape: runmat_accelerate::graph::ShapeInfo::Scalar,
2619                    constant: None,
2620                },
2621            ],
2622            var_bindings: std::collections::HashMap::new(),
2623            node_bindings: std::collections::HashMap::new(),
2624        };
2625        let instr_spans = vec![runmat_hir::Span { start: 10, end: 20 }];
2626        let candidates = vec![crate::bytecode::FusionCandidateGroup {
2627            id: 0,
2628            signal_count: 1,
2629            function: runmat_hir::FunctionId(0),
2630            block: runmat_mir::BasicBlockId(0),
2631            stmt_start: 0,
2632            stmt_end: 1,
2633            source_span: runmat_hir::Span { start: 19, end: 25 },
2634        }];
2635
2636        let windows = super::derive_semantic_fusion_instruction_windows(
2637            &[Instr::Add],
2638            &instr_spans,
2639            &candidates,
2640        );
2641        let groups = super::derive_semantic_fusion_groups_from_candidates(&windows, &accel_graph);
2642        assert!(
2643            groups.is_empty(),
2644            "expected no semantic-driven fusion groups when candidate spans only partially overlap instruction spans"
2645        );
2646    }
2647
2648    #[test]
2649    fn compile_records_semantic_spawn_site_metadata() {
2650        let ast = runmat_parser::parse("fut = make(); task = spawn(fut);").expect("parse");
2651        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
2652        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
2653        let entrypoint = hir.assembly.entrypoints[0].id;
2654
2655        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
2656
2657        assert!(
2658            bytecode.async_metadata.mir_spawn_site_count > 0,
2659            "expected non-zero spawn site count"
2660        );
2661        assert!(
2662            !bytecode.async_metadata.mir_spawn_sites.is_empty(),
2663            "expected spawn site metadata entries"
2664        );
2665        assert_eq!(
2666            bytecode.async_metadata.mir_spawn_site_count,
2667            bytecode.async_metadata.mir_spawn_sites.len(),
2668            "spawn site count should match listed sites"
2669        );
2670        let unique_sites = bytecode
2671            .async_metadata
2672            .mir_spawn_sites
2673            .iter()
2674            .map(|site| (site.function, site.block, site.stmt_index))
2675            .collect::<std::collections::HashSet<_>>();
2676        assert!(
2677            unique_sites.len() == bytecode.async_metadata.mir_spawn_sites.len(),
2678            "spawn site metadata entries should be distinct"
2679        );
2680        assert_eq!(
2681            bytecode.async_metadata.runtime_model,
2682            crate::bytecode::program::AsyncRuntimeModel::LazyFutureDescriptorLane,
2683            "semantic async metadata should surface the current lazy-future runtime model"
2684        );
2685        assert_eq!(
2686            bytecode.async_metadata.mir_await_site_count, 0,
2687            "spawn-only program should not report await sites"
2688        );
2689        assert!(
2690            bytecode.async_metadata.mir_await_sites.is_empty(),
2691            "spawn-only program should have an empty await-site list"
2692        );
2693    }
2694
2695    #[test]
2696    fn compile_scopes_spawn_site_metadata_to_entrypoint_target() {
2697        let source = "x = 1; function z = helper(a); fut = make(); z = spawn(fut); end;";
2698        let ast = runmat_parser::parse(source).expect("parse");
2699        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
2700        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
2701        let entrypoint = hir.assembly.entrypoints[0].id;
2702        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
2703
2704        assert_eq!(
2705            bytecode.async_metadata.mir_spawn_site_count, 0,
2706            "spawn sites in non-entrypoint helper bodies should not be attributed to the entrypoint bytecode artifact"
2707        );
2708        assert!(
2709            bytecode.async_metadata.mir_spawn_sites.is_empty(),
2710            "spawn site list should be empty when only helper bodies contain spawn expressions"
2711        );
2712        assert_eq!(
2713            bytecode.async_metadata.mir_await_site_count, 0,
2714            "program without entrypoint await should not report await sites"
2715        );
2716    }
2717
2718    #[test]
2719    fn compile_records_semantic_await_site_metadata() {
2720        let source = "async function y = inc(x); y = x + 1; end; t = inc(2); z = await(t);";
2721        let ast = runmat_parser::parse(source).expect("parse");
2722        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
2723        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
2724        let entrypoint = hir.assembly.entrypoints[0].id;
2725
2726        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
2727
2728        assert!(
2729            bytecode.async_metadata.mir_await_site_count > 0,
2730            "expected non-zero await site count"
2731        );
2732        assert!(
2733            !bytecode.async_metadata.mir_await_sites.is_empty(),
2734            "expected await site metadata entries"
2735        );
2736        assert_eq!(
2737            bytecode.async_metadata.mir_await_site_count,
2738            bytecode.async_metadata.mir_await_sites.len(),
2739            "await site count should match listed sites"
2740        );
2741        let unique_sites = bytecode
2742            .async_metadata
2743            .mir_await_sites
2744            .iter()
2745            .map(|site| (site.function, site.block, site.resume))
2746            .collect::<std::collections::HashSet<_>>();
2747        assert!(
2748            unique_sites.len() == bytecode.async_metadata.mir_await_sites.len(),
2749            "await site metadata entries should be distinct"
2750        );
2751        assert_eq!(
2752            bytecode.async_metadata.runtime_model,
2753            crate::bytecode::program::AsyncRuntimeModel::LazyFutureDescriptorLane,
2754            "semantic async metadata should surface the current lazy-future runtime model"
2755        );
2756    }
2757
2758    #[test]
2759    fn compile_scopes_await_site_metadata_to_entrypoint_target() {
2760        let source = "x = 1; async function z = helper(a); z = await(a); end;";
2761        let ast = runmat_parser::parse(source).expect("parse");
2762        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
2763        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
2764        let entrypoint = hir.assembly.entrypoints[0].id;
2765        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
2766
2767        assert_eq!(
2768            bytecode.async_metadata.mir_await_site_count, 0,
2769            "await sites in non-entrypoint helper bodies should not be attributed to the entrypoint bytecode artifact"
2770        );
2771        assert!(
2772            bytecode.async_metadata.mir_await_sites.is_empty(),
2773            "await site list should be empty when only helper bodies contain await expressions"
2774        );
2775    }
2776
2777    #[test]
2778    fn compile_interprets_visible_assignment() {
2779        let ast = runmat_parser::parse("x = 1 + 2;").expect("parse");
2780        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
2781        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
2782        let entrypoint = hir.assembly.entrypoints[0].id;
2783
2784        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
2785        let layout = bytecode.layout.as_ref().expect("layout");
2786        let export = &layout.entrypoints[&entrypoint].exports[0];
2787
2788        assert_eq!(bytecode.var_names[&export.slot.0], "x");
2789
2790        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
2791        assert_eq!(vars[export.slot.0], Value::Num(3.0));
2792    }
2793
2794    #[test]
2795    fn compile_interprets_builtin_assignment() {
2796        let ast = runmat_parser::parse("x = sqrt(9);").expect("parse");
2797        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
2798        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
2799        let entrypoint = hir.assembly.entrypoints[0].id;
2800
2801        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
2802        let layout = bytecode.layout.as_ref().expect("layout");
2803        let export = &layout.entrypoints[&entrypoint].exports[0];
2804
2805        assert!(matches!(
2806            bytecode.instructions.as_slice(),
2807            [
2808                Instr::LoadConst(9.0),
2809                Instr::CallBuiltinMulti(name, 1, 1),
2810                Instr::StoreVar(_),
2811            ] if name == "sqrt"
2812        ));
2813
2814        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
2815        assert_eq!(vars[export.slot.0], Value::Num(3.0));
2816    }
2817
2818    #[test]
2819    fn compile_interprets_uigetfile_cancel_destructuring() {
2820        let ast =
2821            runmat_parser::parse("[file, path] = uigetfile('*.xlsx', 'Select a spreadsheet');")
2822                .expect("parse");
2823        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
2824        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
2825        let entrypoint = hir.assembly.entrypoints[0].id;
2826
2827        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
2828        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
2829        let file_slot = bytecode
2830            .var_names
2831            .iter()
2832            .find_map(|(slot, name)| (name == "file").then_some(*slot))
2833            .expect("file slot");
2834        let path_slot = bytecode
2835            .var_names
2836            .iter()
2837            .find_map(|(slot, name)| (name == "path").then_some(*slot))
2838            .expect("path slot");
2839
2840        assert_eq!(vars[file_slot], Value::Num(0.0));
2841        assert_eq!(vars[path_slot], Value::Num(0.0));
2842    }
2843
2844    #[test]
2845    fn compile_interprets_uiputfile_cancel_destructuring() {
2846        let ast = runmat_parser::parse("[file, path] = uiputfile('*.xlsx', 'Save spreadsheet');")
2847            .expect("parse");
2848        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
2849        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
2850        let entrypoint = hir.assembly.entrypoints[0].id;
2851
2852        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
2853        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
2854        let file_slot = bytecode
2855            .var_names
2856            .iter()
2857            .find_map(|(slot, name)| (name == "file").then_some(*slot))
2858            .expect("file slot");
2859        let path_slot = bytecode
2860            .var_names
2861            .iter()
2862            .find_map(|(slot, name)| (name == "path").then_some(*slot))
2863            .expect("path slot");
2864
2865        assert_eq!(vars[file_slot], Value::Num(0.0));
2866        assert_eq!(vars[path_slot], Value::Num(0.0));
2867    }
2868
2869    #[test]
2870    fn compile_interprets_matrix_literal_assignment() {
2871        let ast = runmat_parser::parse("x = [1 2; 3 4];").expect("parse");
2872        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
2873        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
2874        let entrypoint = hir.assembly.entrypoints[0].id;
2875
2876        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
2877        let layout = bytecode.layout.as_ref().expect("layout");
2878        let export = &layout.entrypoints[&entrypoint].exports[0];
2879
2880        assert!(matches!(
2881            bytecode.instructions.as_slice(),
2882            [
2883                Instr::LoadConst(1.0),
2884                Instr::LoadConst(2.0),
2885                Instr::LoadConst(3.0),
2886                Instr::LoadConst(4.0),
2887                Instr::CreateMatrix(2, 2),
2888                Instr::StoreVar(_),
2889            ]
2890        ));
2891
2892        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
2893        let Value::Tensor(tensor) = &vars[export.slot.0] else {
2894            panic!("expected tensor");
2895        };
2896        assert_eq!(tensor.shape, vec![2, 2]);
2897        assert_eq!(tensor.data, vec![1.0, 3.0, 2.0, 4.0]);
2898    }
2899
2900    #[test]
2901    fn compile_interprets_simple_matrix_indexing() {
2902        let ast = runmat_parser::parse("x = [1 2; 3 4]; y = x(2, 1);").expect("parse");
2903        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
2904        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
2905        let entrypoint = hir.assembly.entrypoints[0].id;
2906
2907        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
2908        let layout = bytecode.layout.as_ref().expect("layout");
2909        let y_export = layout.entrypoints[&entrypoint]
2910            .exports
2911            .iter()
2912            .find(|export| export.name == "y")
2913            .expect("y export");
2914
2915        assert!(bytecode
2916            .instructions
2917            .iter()
2918            .any(|instr| matches!(instr, Instr::Index(2))));
2919        assert!(!bytecode
2920            .instructions
2921            .iter()
2922            .any(|instr| matches!(instr, Instr::IndexSlice(2, 2, 0, 0))));
2923
2924        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
2925        assert_eq!(vars[y_export.slot.0], Value::Num(3.0));
2926    }
2927
2928    #[test]
2929    fn compile_interprets_simple_colon_slice() {
2930        let ast = runmat_parser::parse("x = [1 2; 3 4]; y = x(:, 2);").expect("parse");
2931        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
2932        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
2933        let entrypoint = hir.assembly.entrypoints[0].id;
2934
2935        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
2936        let layout = bytecode.layout.as_ref().expect("layout");
2937        let y_export = layout.entrypoints[&entrypoint]
2938            .exports
2939            .iter()
2940            .find(|export| export.name == "y")
2941            .expect("y export");
2942
2943        assert!(bytecode
2944            .instructions
2945            .iter()
2946            .any(|instr| matches!(instr, Instr::IndexSlice(2, 1, 1, 0))));
2947
2948        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
2949        let Value::Tensor(tensor) = &vars[y_export.slot.0] else {
2950            panic!("expected tensor");
2951        };
2952        assert_eq!(tensor.shape, vec![2, 1]);
2953        assert_eq!(tensor.data, vec![2.0, 4.0]);
2954    }
2955
2956    #[test]
2957    fn compile_lowers_ambiguous_local_index_to_slice() {
2958        let ast =
2959            runmat_parser::parse("x = [10 20 30 40]; a = find([0 1 1]); idx = a + 1; y = x(idx);")
2960                .expect("parse");
2961        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
2962        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
2963        let entrypoint = hir.assembly.entrypoints[0].id;
2964
2965        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
2966        let layout = bytecode.layout.as_ref().expect("layout");
2967        let y_export = layout.entrypoints[&entrypoint]
2968            .exports
2969            .iter()
2970            .find(|export| export.name == "y")
2971            .expect("y export");
2972
2973        assert!(bytecode
2974            .instructions
2975            .iter()
2976            .any(|instr| matches!(instr, Instr::IndexSlice(1, _, _, _))));
2977        assert!(!bytecode
2978            .instructions
2979            .iter()
2980            .any(|instr| matches!(instr, Instr::Index(1))));
2981
2982        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
2983        let Value::Tensor(tensor) = &vars[y_export.slot.0] else {
2984            panic!("expected tensor");
2985        };
2986        assert_eq!(tensor.shape, vec![2, 1]);
2987        assert_eq!(tensor.data, vec![30.0, 40.0]);
2988    }
2989
2990    #[test]
2991    fn compile_lowers_ambiguous_local_store_index_to_slice() {
2992        let ast =
2993            runmat_parser::parse("x = [10 20 30 40]; idx = [2 4]; x(idx) = [9 8];").expect("parse");
2994        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
2995        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
2996        let entrypoint = hir.assembly.entrypoints[0].id;
2997
2998        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
2999        let layout = bytecode.layout.as_ref().expect("layout");
3000        let x_export = layout.entrypoints[&entrypoint]
3001            .exports
3002            .iter()
3003            .find(|export| export.name == "x")
3004            .expect("x export");
3005
3006        assert!(bytecode
3007            .instructions
3008            .iter()
3009            .any(|instr| matches!(instr, Instr::StoreSlice(1, _, _, _))));
3010        assert!(!bytecode
3011            .instructions
3012            .iter()
3013            .any(|instr| matches!(instr, Instr::StoreIndex(1))));
3014
3015        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
3016        let Value::Tensor(tensor) = &vars[x_export.slot.0] else {
3017            panic!("expected tensor");
3018        };
3019        assert_eq!(tensor.shape, vec![1, 4]);
3020        assert_eq!(tensor.data, vec![10.0, 9.0, 30.0, 8.0]);
3021    }
3022
3023    #[test]
3024    fn compile_rejects_invalid_scalar_index_plan_with_identifier() {
3025        let ast = runmat_parser::parse("x = [1 2 3]; y = x(2);").expect("parse");
3026        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3027        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
3028        let entrypoint = hir.assembly.entrypoints[0].id;
3029        let function = hir.assembly.entrypoints[0].target;
3030        let body = mir.bodies.get_mut(&function).expect("entry body");
3031
3032        let mut patched = false;
3033        for block in &mut body.blocks {
3034            for stmt in &mut block.statements {
3035                if let MirStmtKind::Assign {
3036                    value: MirRvalue::Index { indexing, .. },
3037                    ..
3038                } = &mut stmt.kind
3039                {
3040                    indexing.plan = MirIndexPlan::Scalar;
3041                    indexing.components = vec![MirIndexComponent::Colon];
3042                    patched = true;
3043                    break;
3044                }
3045            }
3046            if patched {
3047                break;
3048            }
3049        }
3050        assert!(patched, "expected indexed assignment in lowered MIR");
3051
3052        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
3053        assert_eq!(
3054            err.identifier.as_deref(),
3055            Some("RunMat:MirScalarIndexPlanInvalid")
3056        );
3057    }
3058
3059    #[test]
3060    fn compile_rejects_invalid_slice_index_plan_with_identifier() {
3061        let ast = runmat_parser::parse("x = [1 2 3]; y = x(end);").expect("parse");
3062        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3063        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
3064        let entrypoint = hir.assembly.entrypoints[0].id;
3065        let function = hir.assembly.entrypoints[0].target;
3066        let body = mir.bodies.get_mut(&function).expect("entry body");
3067
3068        let mut patched = false;
3069        for block in &mut body.blocks {
3070            for stmt in &mut block.statements {
3071                if let MirStmtKind::Assign {
3072                    value: MirRvalue::Index { indexing, .. },
3073                    ..
3074                } = &mut stmt.kind
3075                {
3076                    indexing.plan = MirIndexPlan::Slice;
3077                    indexing.components = vec![MirIndexComponent::End {
3078                        dim: Some(0),
3079                        offset: 1,
3080                    }];
3081                    patched = true;
3082                    break;
3083                }
3084            }
3085            if patched {
3086                break;
3087            }
3088        }
3089        assert!(patched, "expected indexed assignment in lowered MIR");
3090
3091        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
3092        assert_eq!(
3093            err.identifier.as_deref(),
3094            Some("RunMat:MirSliceIndexPlanInvalid")
3095        );
3096    }
3097
3098    #[test]
3099    fn compile_rejects_slice_plan_selector_dimension_beyond_mask_width() {
3100        let ast = runmat_parser::parse("x = [1 2 3]; y = x(1);").expect("parse");
3101        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3102        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
3103        let entrypoint = hir.assembly.entrypoints[0].id;
3104        let function = hir.assembly.entrypoints[0].target;
3105        let body = mir.bodies.get_mut(&function).expect("entry body");
3106
3107        let mut patched = false;
3108        for block in &mut body.blocks {
3109            for stmt in &mut block.statements {
3110                if let MirStmtKind::Assign {
3111                    value: MirRvalue::Index { indexing, .. },
3112                    ..
3113                } = &mut stmt.kind
3114                {
3115                    indexing.plan = MirIndexPlan::Slice;
3116                    let seed = indexing.components.first().cloned().expect("seed selector");
3117                    let mut components = vec![seed; 33];
3118                    components[32] = MirIndexComponent::Colon;
3119                    indexing.components = components;
3120                    patched = true;
3121                    break;
3122                }
3123            }
3124            if patched {
3125                break;
3126            }
3127        }
3128        assert!(patched, "expected indexed assignment in lowered MIR");
3129
3130        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
3131        assert_eq!(
3132            err.identifier.as_deref(),
3133            Some("RunMat:MirSliceIndexPlanInvalid")
3134        );
3135    }
3136
3137    #[test]
3138    fn compile_rejects_slice_plan_end_dimension_beyond_mask_width() {
3139        let ast = runmat_parser::parse("x = [1 2 3]; y = x(1);").expect("parse");
3140        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3141        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
3142        let entrypoint = hir.assembly.entrypoints[0].id;
3143        let function = hir.assembly.entrypoints[0].target;
3144        let body = mir.bodies.get_mut(&function).expect("entry body");
3145
3146        let mut patched = false;
3147        for block in &mut body.blocks {
3148            for stmt in &mut block.statements {
3149                if let MirStmtKind::Assign {
3150                    value: MirRvalue::Index { indexing, .. },
3151                    ..
3152                } = &mut stmt.kind
3153                {
3154                    indexing.plan = MirIndexPlan::Slice;
3155                    let seed = indexing.components.first().cloned().expect("seed selector");
3156                    let mut components = vec![seed; 33];
3157                    components[32] = MirIndexComponent::End {
3158                        dim: Some(32),
3159                        offset: 0,
3160                    };
3161                    indexing.components = components;
3162                    patched = true;
3163                    break;
3164                }
3165            }
3166            if patched {
3167                break;
3168            }
3169        }
3170        assert!(patched, "expected indexed assignment in lowered MIR");
3171
3172        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
3173        assert_eq!(
3174            err.identifier.as_deref(),
3175            Some("RunMat:MirSliceIndexPlanInvalid")
3176        );
3177    }
3178
3179    #[test]
3180    fn compile_rejects_scalar_plan_with_range_expr_component_with_identifier() {
3181        let ast = runmat_parser::parse("x = [1 2 3 4]; y = x(1:end);").expect("parse");
3182        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3183        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
3184        let entrypoint = hir.assembly.entrypoints[0].id;
3185        let function = hir.assembly.entrypoints[0].target;
3186        let body = mir.bodies.get_mut(&function).expect("entry body");
3187
3188        let mut patched = false;
3189        for block in &mut body.blocks {
3190            for stmt in &mut block.statements {
3191                if let MirStmtKind::Assign {
3192                    value: MirRvalue::Index { indexing, .. },
3193                    ..
3194                } = &mut stmt.kind
3195                {
3196                    indexing.plan = MirIndexPlan::Scalar;
3197                    patched = true;
3198                    break;
3199                }
3200            }
3201            if patched {
3202                break;
3203            }
3204        }
3205        assert!(patched, "expected indexed assignment in lowered MIR");
3206
3207        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
3208        assert_eq!(
3209            err.identifier.as_deref(),
3210            Some("RunMat:MirScalarIndexPlanInvalid")
3211        );
3212    }
3213
3214    #[test]
3215    fn compile_rejects_slice_plan_with_range_expr_component_with_identifier() {
3216        let ast = runmat_parser::parse("x = [1 2 3 4]; y = x(1:end);").expect("parse");
3217        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3218        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
3219        let entrypoint = hir.assembly.entrypoints[0].id;
3220        let function = hir.assembly.entrypoints[0].target;
3221        let body = mir.bodies.get_mut(&function).expect("entry body");
3222
3223        let mut patched = false;
3224        for block in &mut body.blocks {
3225            for stmt in &mut block.statements {
3226                if let MirStmtKind::Assign {
3227                    value: MirRvalue::Index { indexing, .. },
3228                    ..
3229                } = &mut stmt.kind
3230                {
3231                    indexing.plan = MirIndexPlan::Slice;
3232                    patched = true;
3233                    break;
3234                }
3235            }
3236            if patched {
3237                break;
3238            }
3239        }
3240        assert!(patched, "expected indexed assignment in lowered MIR");
3241
3242        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
3243        assert_eq!(
3244            err.identifier.as_deref(),
3245            Some("RunMat:MirSliceIndexPlanInvalid")
3246        );
3247    }
3248
3249    #[test]
3250    fn compile_rejects_invalid_paren_cell_plan_with_identifier() {
3251        let ast = runmat_parser::parse("x = [1 2 3]; y = x(2);").expect("parse");
3252        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3253        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
3254        let entrypoint = hir.assembly.entrypoints[0].id;
3255        let function = hir.assembly.entrypoints[0].target;
3256        let body = mir.bodies.get_mut(&function).expect("entry body");
3257
3258        let mut patched = false;
3259        for block in &mut body.blocks {
3260            for stmt in &mut block.statements {
3261                if let MirStmtKind::Assign {
3262                    value: MirRvalue::Index { indexing, .. },
3263                    ..
3264                } = &mut stmt.kind
3265                {
3266                    indexing.plan = MirIndexPlan::Cell;
3267                    patched = true;
3268                    break;
3269                }
3270            }
3271            if patched {
3272                break;
3273            }
3274        }
3275        assert!(patched, "expected indexed assignment in lowered MIR");
3276
3277        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
3278        assert_eq!(
3279            err.identifier.as_deref(),
3280            Some("RunMat:MirParenCellPlanInvalid")
3281        );
3282    }
3283
3284    #[test]
3285    fn compile_rejects_index_assignment_with_read_context_identifier() {
3286        let ast = runmat_parser::parse("x=[1,2,3]; x(1)=4;").expect("parse");
3287        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3288        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
3289        let entrypoint = hir.assembly.entrypoints[0].id;
3290        let function = hir.assembly.entrypoints[0].target;
3291        let body = mir.bodies.get_mut(&function).expect("entry body");
3292
3293        let mut patched = false;
3294        for block in &mut body.blocks {
3295            for stmt in &mut block.statements {
3296                if let MirStmtKind::Assign {
3297                    place: MirPlace::Index(_, indexing),
3298                    ..
3299                } = &mut stmt.kind
3300                {
3301                    indexing.result_context = IndexResultContext::ReadSingle;
3302                    patched = true;
3303                    break;
3304                }
3305            }
3306            if patched {
3307                break;
3308            }
3309        }
3310        assert!(patched, "expected indexed assignment place in lowered MIR");
3311
3312        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
3313        assert_eq!(
3314            err.identifier.as_deref(),
3315            Some("RunMat:MirIndexContextInvalid")
3316        );
3317    }
3318
3319    #[test]
3320    fn compile_rejects_index_assignment_with_deletion_context_identifier() {
3321        let ast = runmat_parser::parse("x=[1,2,3]; x(1)=4;").expect("parse");
3322        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3323        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
3324        let entrypoint = hir.assembly.entrypoints[0].id;
3325        let function = hir.assembly.entrypoints[0].target;
3326        let body = mir.bodies.get_mut(&function).expect("entry body");
3327
3328        let mut patched = false;
3329        for block in &mut body.blocks {
3330            for stmt in &mut block.statements {
3331                if let MirStmtKind::Assign {
3332                    place: MirPlace::Index(_, indexing),
3333                    ..
3334                } = &mut stmt.kind
3335                {
3336                    indexing.result_context = IndexResultContext::DeletionTarget;
3337                    patched = true;
3338                    break;
3339                }
3340            }
3341            if patched {
3342                break;
3343            }
3344        }
3345        assert!(patched, "expected indexed assignment place in lowered MIR");
3346
3347        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
3348        assert_eq!(
3349            err.identifier.as_deref(),
3350            Some("RunMat:MirDeletionContextWithoutDeleteInvalid")
3351        );
3352    }
3353
3354    #[test]
3355    fn compile_rejects_invalid_cell_expand_all_shape_with_identifier() {
3356        let ast = runmat_parser::parse("c = {1,2;3,4}; [a,b] = c{:,2};").expect("parse");
3357        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3358        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
3359        let entrypoint = hir.assembly.entrypoints[0].id;
3360        let function = hir.assembly.entrypoints[0].target;
3361        let body = mir.bodies.get_mut(&function).expect("entry body");
3362
3363        let mut patched = false;
3364        for block in &mut body.blocks {
3365            for stmt in &mut block.statements {
3366                if let MirStmtKind::MultiAssign {
3367                    value: MirRvalue::Index { indexing, .. },
3368                    ..
3369                } = &mut stmt.kind
3370                {
3371                    indexing.cell_expand_all = true;
3372                    patched = true;
3373                    break;
3374                }
3375            }
3376            if patched {
3377                break;
3378            }
3379        }
3380        assert!(
3381            patched,
3382            "expected multi-assign cell expansion in lowered MIR"
3383        );
3384
3385        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
3386        assert_eq!(
3387            err.identifier.as_deref(),
3388            Some("RunMat:MirCellExpandPlanInvalid")
3389        );
3390    }
3391
3392    #[test]
3393    fn compile_rejects_non_offset_end_expr_in_call_arg_cell_expansion_with_identifier() {
3394        let ast = runmat_parser::parse("c = {10, 20, 30, 40}; x = feval(@max, c{end/2}, 0);")
3395            .expect("parse");
3396        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3397        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
3398        let entrypoint = hir.assembly.entrypoints[0].id;
3399
3400        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
3401        assert_eq!(
3402            err.identifier.as_deref(),
3403            Some("RunMat:MirCellExpandPlanInvalid")
3404        );
3405    }
3406
3407    #[test]
3408    fn compile_rejects_invalid_mir_aggregate_shape_with_identifier() {
3409        let ast = runmat_parser::parse("x = [1 2 3];").expect("parse");
3410        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3411        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
3412        let entrypoint = hir.assembly.entrypoints[0].id;
3413        let function = hir.assembly.entrypoints[0].target;
3414        let body = mir.bodies.get_mut(&function).expect("entry body");
3415
3416        let mut patched = false;
3417        for block in &mut body.blocks {
3418            for stmt in &mut block.statements {
3419                if let MirStmtKind::Assign {
3420                    value: MirRvalue::Aggregate { rows, cols: _, .. },
3421                    ..
3422                } = &mut stmt.kind
3423                {
3424                    *rows = 2;
3425                    patched = true;
3426                    break;
3427                }
3428            }
3429            if patched {
3430                break;
3431            }
3432        }
3433        assert!(patched, "expected aggregate assignment in lowered MIR");
3434
3435        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
3436        assert_eq!(
3437            err.identifier.as_deref(),
3438            Some("RunMat:MirAggregateShapeInvalid")
3439        );
3440    }
3441
3442    #[test]
3443    fn compile_rejects_invalid_cell_index_component_with_identifier() {
3444        let ast = runmat_parser::parse("c = {1}; c{1} = 2;").expect("parse");
3445        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3446        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
3447        let entrypoint = hir.assembly.entrypoints[0].id;
3448        let function = hir.assembly.entrypoints[0].target;
3449        let body = mir.bodies.get_mut(&function).expect("entry body");
3450
3451        let mut patched = false;
3452        for block in &mut body.blocks {
3453            for stmt in &mut block.statements {
3454                if let MirStmtKind::Assign {
3455                    place: MirPlace::Index(_, indexing),
3456                    ..
3457                } = &mut stmt.kind
3458                {
3459                    if matches!(indexing.kind, runmat_hir::IndexKind::Brace) {
3460                        indexing.components = vec![MirIndexComponent::Colon];
3461                        patched = true;
3462                        break;
3463                    }
3464                }
3465            }
3466            if patched {
3467                break;
3468            }
3469        }
3470        assert!(
3471            patched,
3472            "expected brace index assignment place in lowered MIR"
3473        );
3474
3475        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
3476        assert_eq!(
3477            err.identifier.as_deref(),
3478            Some("RunMat:MirCellIndexPlanInvalid")
3479        );
3480    }
3481
3482    #[test]
3483    fn compile_rejects_cell_assignment_colon_selector_from_source_with_identifier() {
3484        let ast = runmat_parser::parse("c = {1,2;3,4}; c{:,2} = 9;").expect("parse");
3485        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3486        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
3487        let entrypoint = hir.assembly.entrypoints[0].id;
3488
3489        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
3490        assert_eq!(
3491            err.identifier.as_deref(),
3492            Some("RunMat:MirCellIndexPlanInvalid")
3493        );
3494    }
3495
3496    #[test]
3497    fn compile_rejects_mismatched_cell_index_context_with_identifier() {
3498        let ast = runmat_parser::parse("c = {1}; c{1} = 2;").expect("parse");
3499        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3500        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
3501        let entrypoint = hir.assembly.entrypoints[0].id;
3502        let function = hir.assembly.entrypoints[0].target;
3503        let body = mir.bodies.get_mut(&function).expect("entry body");
3504
3505        let mut patched = false;
3506        for block in &mut body.blocks {
3507            for stmt in &mut block.statements {
3508                if let MirStmtKind::Assign {
3509                    place: MirPlace::Index(_, indexing),
3510                    ..
3511                } = &mut stmt.kind
3512                {
3513                    if matches!(indexing.kind, runmat_hir::IndexKind::Brace) {
3514                        indexing.result_context = runmat_hir::IndexResultContext::ReadSingle;
3515                        patched = true;
3516                        break;
3517                    }
3518                }
3519            }
3520            if patched {
3521                break;
3522            }
3523        }
3524        assert!(
3525            patched,
3526            "expected brace index assignment place in lowered MIR"
3527        );
3528
3529        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
3530        assert_eq!(
3531            err.identifier.as_deref(),
3532            Some("RunMat:MirIndexContextInvalid")
3533        );
3534    }
3535
3536    #[test]
3537    fn compile_rejects_member_store_back_brace_index_with_read_context_identifier() {
3538        let ast = runmat_parser::parse("c = {struct('x', 1)}; c{1}.x = 2;").expect("parse");
3539        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3540        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
3541        let entrypoint = hir.assembly.entrypoints[0].id;
3542        let function = hir.assembly.entrypoints[0].target;
3543        let body = mir.bodies.get_mut(&function).expect("entry body");
3544
3545        let mut patched = false;
3546        for block in &mut body.blocks {
3547            for stmt in &mut block.statements {
3548                if let MirStmtKind::Assign {
3549                    place: MirPlace::Member(base, _),
3550                    ..
3551                } = &mut stmt.kind
3552                {
3553                    if let MirPlace::Index(_, indexing) = base.as_mut() {
3554                        if matches!(indexing.kind, runmat_hir::IndexKind::Brace) {
3555                            indexing.result_context = runmat_hir::IndexResultContext::ReadSingle;
3556                            patched = true;
3557                            break;
3558                        }
3559                    }
3560                }
3561            }
3562            if patched {
3563                break;
3564            }
3565        }
3566        assert!(
3567            patched,
3568            "expected member-over-brace assignment in lowered MIR"
3569        );
3570
3571        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
3572        assert_eq!(
3573            err.identifier.as_deref(),
3574            Some("RunMat:MirIndexContextInvalid")
3575        );
3576    }
3577
3578    #[test]
3579    fn compile_rejects_member_store_back_brace_index_with_deletion_context_identifier() {
3580        let ast = runmat_parser::parse("c = {struct('x', 1)}; c{1}.x = 2;").expect("parse");
3581        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3582        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
3583        let entrypoint = hir.assembly.entrypoints[0].id;
3584        let function = hir.assembly.entrypoints[0].target;
3585        let body = mir.bodies.get_mut(&function).expect("entry body");
3586
3587        let mut patched = false;
3588        for block in &mut body.blocks {
3589            for stmt in &mut block.statements {
3590                if let MirStmtKind::Assign {
3591                    place: MirPlace::Member(base, _),
3592                    ..
3593                } = &mut stmt.kind
3594                {
3595                    if let MirPlace::Index(_, indexing) = base.as_mut() {
3596                        if matches!(indexing.kind, runmat_hir::IndexKind::Brace) {
3597                            indexing.result_context =
3598                                runmat_hir::IndexResultContext::DeletionTarget;
3599                            patched = true;
3600                            break;
3601                        }
3602                    }
3603                }
3604            }
3605            if patched {
3606                break;
3607            }
3608        }
3609        assert!(
3610            patched,
3611            "expected member-over-brace assignment in lowered MIR"
3612        );
3613
3614        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
3615        assert_eq!(
3616            err.identifier.as_deref(),
3617            Some("RunMat:MirIndexContextInvalid")
3618        );
3619    }
3620
3621    #[test]
3622    fn compile_rejects_member_store_back_paren_index_with_read_context_identifier() {
3623        let ast = runmat_parser::parse("s = struct('x', {1, 2}); s(1).x = 3;").expect("parse");
3624        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3625        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
3626        let entrypoint = hir.assembly.entrypoints[0].id;
3627        let function = hir.assembly.entrypoints[0].target;
3628        let body = mir.bodies.get_mut(&function).expect("entry body");
3629
3630        let mut patched = false;
3631        for block in &mut body.blocks {
3632            for stmt in &mut block.statements {
3633                if let MirStmtKind::Assign {
3634                    place: MirPlace::Member(base, _),
3635                    ..
3636                } = &mut stmt.kind
3637                {
3638                    if let MirPlace::Index(_, indexing) = base.as_mut() {
3639                        if matches!(indexing.kind, runmat_hir::IndexKind::Paren) {
3640                            indexing.result_context = runmat_hir::IndexResultContext::ReadSingle;
3641                            patched = true;
3642                            break;
3643                        }
3644                    }
3645                }
3646            }
3647            if patched {
3648                break;
3649            }
3650        }
3651        assert!(
3652            patched,
3653            "expected member-over-paren assignment in lowered MIR"
3654        );
3655
3656        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
3657        assert_eq!(
3658            err.identifier.as_deref(),
3659            Some("RunMat:MirIndexContextInvalid")
3660        );
3661    }
3662
3663    #[test]
3664    fn compile_rejects_member_store_back_paren_index_with_deletion_context_identifier() {
3665        let ast = runmat_parser::parse("s = struct('x', {1, 2}); s(1).x = 3;").expect("parse");
3666        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3667        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
3668        let entrypoint = hir.assembly.entrypoints[0].id;
3669        let function = hir.assembly.entrypoints[0].target;
3670        let body = mir.bodies.get_mut(&function).expect("entry body");
3671
3672        let mut patched = false;
3673        for block in &mut body.blocks {
3674            for stmt in &mut block.statements {
3675                if let MirStmtKind::Assign {
3676                    place: MirPlace::Member(base, _),
3677                    ..
3678                } = &mut stmt.kind
3679                {
3680                    if let MirPlace::Index(_, indexing) = base.as_mut() {
3681                        if matches!(indexing.kind, runmat_hir::IndexKind::Paren) {
3682                            indexing.result_context =
3683                                runmat_hir::IndexResultContext::DeletionTarget;
3684                            patched = true;
3685                            break;
3686                        }
3687                    }
3688                }
3689            }
3690            if patched {
3691                break;
3692            }
3693        }
3694        assert!(
3695            patched,
3696            "expected member-over-paren assignment in lowered MIR"
3697        );
3698
3699        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
3700        assert_eq!(
3701            err.identifier.as_deref(),
3702            Some("RunMat:MirIndexContextInvalid")
3703        );
3704    }
3705
3706    #[test]
3707    fn compile_interprets_member_store_back_paren_assignment() {
3708        let ast = runmat_parser::parse(
3709            "s = struct('x', {1, 2}); s(2).x = 9; t = s(2); y = getfield(t, 'x');",
3710        )
3711        .expect("parse");
3712        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3713        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
3714        let entrypoint = hir.assembly.entrypoints[0].id;
3715
3716        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
3717        let layout = bytecode.layout.as_ref().expect("layout");
3718        let y_export = layout.entrypoints[&entrypoint]
3719            .exports
3720            .iter()
3721            .find(|export| export.name == "y")
3722            .expect("y export");
3723
3724        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
3725        assert_eq!(vars[y_export.slot.0], Value::Num(9.0));
3726    }
3727
3728    #[test]
3729    fn compile_interprets_readtable_weekly_groupsummary_workflow() {
3730        let path = unique_csv_temp_path("readtable_weekly");
3731        std::fs::write(
3732            &path,
3733            "Date,Orders,Revenue\n2024-03-11,10,100\n2024-03-12,20,300\n2024-03-18,6,90\n",
3734        )
3735        .expect("write csv");
3736        let source = format!(
3737            "\
3738T = readtable('{}');\n\
3739T.Date = datetime(T.Date, 'InputFormat', 'yyyy-MM-dd');\n\
3740T.Week = dateshift(T.Date, 'start', 'week');\n\
3741weekly = groupsummary(T, 'Week', 'mean', {{'Orders', 'Revenue'}});\n\
3742weekly.Properties.VariableNames(end-1:end) = {{'AvgOrders', 'AvgRevenue'}};\n\
3743weekly = sortrows(weekly, 'Week');\n\
3744out = weekly.AvgRevenue;\n",
3745            path.to_string_lossy()
3746        );
3747        let ast = runmat_parser::parse(&source).expect("parse");
3748        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3749        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
3750        let entrypoint = hir.assembly.entrypoints[0].id;
3751
3752        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
3753        let layout = bytecode.layout.as_ref().expect("layout");
3754        let out_export = layout.entrypoints[&entrypoint]
3755            .exports
3756            .iter()
3757            .find(|export| export.name == "out")
3758            .expect("out export");
3759
3760        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
3761        let Value::Tensor(tensor) = &vars[out_export.slot.0] else {
3762            panic!(
3763                "expected numeric AvgRevenue tensor, got {:?}",
3764                vars[out_export.slot.0]
3765            );
3766        };
3767        assert_eq!(tensor.shape, vec![2, 1]);
3768        assert_eq!(tensor.data, vec![200.0, 90.0]);
3769        let _ = std::fs::remove_file(&path);
3770    }
3771
3772    #[test]
3773    fn compile_interprets_imhist_uint8_workflow() {
3774        let ast = runmat_parser::parse(
3775            "\
3776img = uint8([0 1 1; 2 2 2]);\n\
3777[counts, bins] = imhist(img);\n\
3778[peak, idx] = max(counts);\n\
3779dominant = bins(idx);\n\
3780total = sum(counts);\n\
3781summary = [peak; dominant; total];\n",
3782        )
3783        .expect("parse");
3784        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3785        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
3786        let entrypoint = hir.assembly.entrypoints[0].id;
3787
3788        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
3789        let layout = bytecode.layout.as_ref().expect("layout");
3790        let summary_export = layout.entrypoints[&entrypoint]
3791            .exports
3792            .iter()
3793            .find(|export| export.name == "summary")
3794            .expect("summary export");
3795
3796        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
3797        let Value::Tensor(tensor) = &vars[summary_export.slot.0] else {
3798            panic!(
3799                "expected numeric summary tensor, got {:?}",
3800                vars[summary_export.slot.0]
3801            );
3802        };
3803        assert_eq!(tensor.shape, vec![3, 1]);
3804        assert_eq!(tensor.data, vec![3.0, 2.0, 6.0]);
3805    }
3806
3807    #[test]
3808    fn compile_interprets_butter_filter_workflow() {
3809        let ast = runmat_parser::parse(
3810            "\
3811[b, a] = butter(2, 0.25, 'low');\n\
3812x = [1 zeros(1, 5)];\n\
3813y = filter(b, a, x);\n\
3814expected = [0.0976310729378175 0.2873096041807672 0.3359654745135361];\n\
3815err = max(abs(y(1:3) - expected));\n\
3816summary = [numel(b); numel(a); all(isfinite(y)); err < 1e-12];\n",
3817        )
3818        .expect("parse");
3819        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3820        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
3821        let entrypoint = hir.assembly.entrypoints[0].id;
3822
3823        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
3824        let layout = bytecode.layout.as_ref().expect("layout");
3825        let summary_export = layout.entrypoints[&entrypoint]
3826            .exports
3827            .iter()
3828            .find(|export| export.name == "summary")
3829            .expect("summary export");
3830
3831        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
3832        let Value::Tensor(tensor) = &vars[summary_export.slot.0] else {
3833            panic!(
3834                "expected numeric summary tensor, got {:?}",
3835                vars[summary_export.slot.0]
3836            );
3837        };
3838        assert_eq!(tensor.shape, vec![4, 1]);
3839        assert_eq!(tensor.data, vec![3.0, 3.0, 1.0, 1.0]);
3840    }
3841
3842    #[test]
3843    fn compile_interprets_rref_rank_workflow() {
3844        let ast = runmat_parser::parse(
3845            "\
3846A = [1 2 3; 2 4 6; 1 1 1];\n\
3847rankA = rank(A);\n\
3848[R, p] = rref(A);\n\
3849expected = [1 0 -1; 0 1 2; 0 0 0];\n\
3850expected_p = [1 2];\n\
3851err = max(max(abs(R - expected)));\n\
3852pivot_ok = all(p == expected_p);\n\
3853summary = [rankA; double(err < 1e-12); numel(p); double(pivot_ok)];\n",
3854        )
3855        .expect("parse");
3856        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3857        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
3858        let entrypoint = hir.assembly.entrypoints[0].id;
3859
3860        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
3861        let layout = bytecode.layout.as_ref().expect("layout");
3862        let summary_export = layout.entrypoints[&entrypoint]
3863            .exports
3864            .iter()
3865            .find(|export| export.name == "summary")
3866            .expect("summary export");
3867
3868        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
3869        let Value::Tensor(tensor) = &vars[summary_export.slot.0] else {
3870            panic!(
3871                "expected numeric summary tensor, got {:?}",
3872                vars[summary_export.slot.0]
3873            );
3874        };
3875        assert_eq!(tensor.shape, vec![4, 1]);
3876        assert_eq!(tensor.data, vec![2.0, 1.0, 2.0, 1.0]);
3877    }
3878
3879    #[test]
3880    fn compile_interprets_symbolic_limit_workflow() {
3881        let ast = runmat_parser::parse(
3882            "\
3883syms x h\n\
3884syms('z')\n\
3885f1 = limit(sin(x)/x, x, 0);\n\
3886f2 = limit((cos(x+h) - cos(x))/h, h, 0);\n\
3887f3 = limit(sin(z)/z + 1, z, 0);\n",
3888        )
3889        .expect("parse");
3890        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3891        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
3892        let entrypoint = hir.assembly.entrypoints[0].id;
3893
3894        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
3895        let layout = bytecode.layout.as_ref().expect("layout");
3896        let entry_layout = &layout.entrypoints[&entrypoint];
3897        let f1_export = entry_layout
3898            .exports
3899            .iter()
3900            .find(|export| export.name == "f1")
3901            .expect("f1 export");
3902        let f2_export = entry_layout
3903            .exports
3904            .iter()
3905            .find(|export| export.name == "f2")
3906            .expect("f2 export");
3907        let f3_export = entry_layout
3908            .exports
3909            .iter()
3910            .find(|export| export.name == "f3")
3911            .expect("f3 export");
3912
3913        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
3914        assert!(matches!(&vars[f1_export.slot.0], Value::Symbolic(_)));
3915        assert_eq!(vars[f1_export.slot.0].to_string(), "1");
3916        assert_eq!(vars[f2_export.slot.0].to_string(), "-sin(x)");
3917        assert_eq!(vars[f3_export.slot.0].to_string(), "2");
3918    }
3919
3920    #[test]
3921    fn compile_interprets_symbolic_function_declaration_workflow() {
3922        let ast = runmat_parser::parse(
3923            "\
3924syms Y(X);\n\
3925a = 0;\n\
3926z = 0;\n\
3927applied = Y(a);\n\
3928reapplied = applied(1);\n\
3929cond = applied == z;\n\
3930dydx = diff(Y, X);\n\
3931eqn = diff(Y, X) == 2*Y + X;\n\
3932syms('F(P, Q)');\n\
3933probe = F(1, 2);\n",
3934        )
3935        .expect("parse");
3936        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3937        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
3938        let entrypoint = hir.assembly.entrypoints[0].id;
3939
3940        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
3941        let layout = bytecode.layout.as_ref().expect("layout");
3942        let entry_layout = &layout.entrypoints[&entrypoint];
3943        let export = |name: &str| {
3944            entry_layout
3945                .exports
3946                .iter()
3947                .find(|export| export.name == name)
3948                .unwrap_or_else(|| panic!("{name} export"))
3949                .slot
3950                .0
3951        };
3952
3953        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
3954        assert_eq!(vars[export("Y")].to_string(), "Y(X)");
3955        assert_eq!(vars[export("X")].to_string(), "X");
3956        assert_eq!(vars[export("applied")].to_string(), "Y(0)");
3957        assert_eq!(vars[export("reapplied")].to_string(), "Y(0)");
3958        assert_eq!(vars[export("cond")].to_string(), "Y(0) == 0");
3959        assert_eq!(vars[export("dydx")].to_string(), "diff(Y(X), X)");
3960        assert_eq!(
3961            vars[export("eqn")].to_string(),
3962            "diff(Y(X), X) == 2*Y(X) + X"
3963        );
3964        assert_eq!(vars[export("F")].to_string(), "F(P, Q)");
3965        assert_eq!(vars[export("P")].to_string(), "P");
3966        assert_eq!(vars[export("Q")].to_string(), "Q");
3967        assert_eq!(vars[export("probe")].to_string(), "F(1, 2)");
3968    }
3969
3970    #[test]
3971    fn compile_rejects_symbolic_function_arity_mismatch() {
3972        let ast = runmat_parser::parse(
3973            "\
3974syms Y(X);\n\
3975bad = Y(1, 2);\n",
3976        )
3977        .expect("parse");
3978        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
3979        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
3980        let entrypoint = hir.assembly.entrypoints[0].id;
3981
3982        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
3983        let err = block_on(crate::interpret(&bytecode)).expect_err("arity mismatch should fail");
3984
3985        assert_eq!(
3986            err.identifier.as_deref(),
3987            Some("RunMat:SymbolicFunctionArity")
3988        );
3989    }
3990
3991    #[test]
3992    fn compile_interprets_symbolic_fractional_power_limit() {
3993        let ast = runmat_parser::parse(
3994            "\
3995syms x\n\
3996f = (cos(x)^(1/3) - 1) / x^2;\n\
3997L = limit(f, x, 0);\n",
3998        )
3999        .expect("parse");
4000        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4001        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
4002        let entrypoint = hir.assembly.entrypoints[0].id;
4003
4004        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
4005        let layout = bytecode.layout.as_ref().expect("layout");
4006        let l_export = layout.entrypoints[&entrypoint]
4007            .exports
4008            .iter()
4009            .find(|export| export.name == "L")
4010            .expect("L export");
4011
4012        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
4013        let Value::Symbolic(expr) = &vars[l_export.slot.0] else {
4014            panic!(
4015                "expected symbolic limit result, got {:?}",
4016                vars[l_export.slot.0]
4017            );
4018        };
4019        let result = expr.constant_value().expect("constant limit result");
4020        assert!((result + 1.0 / 6.0).abs() < 1e-12, "{result}");
4021    }
4022
4023    #[test]
4024    fn compile_interprets_scalar_symbolic_power() {
4025        let ast = runmat_parser::parse(
4026            "\
4027syms x\n\
4028a = x^2;\n\
4029b = 2^x;\n",
4030        )
4031        .expect("parse");
4032        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4033        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
4034        let entrypoint = hir.assembly.entrypoints[0].id;
4035
4036        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
4037        let layout = bytecode.layout.as_ref().expect("layout");
4038        let entry_layout = &layout.entrypoints[&entrypoint];
4039        let a_export = entry_layout
4040            .exports
4041            .iter()
4042            .find(|export| export.name == "a")
4043            .expect("a export");
4044        let b_export = entry_layout
4045            .exports
4046            .iter()
4047            .find(|export| export.name == "b")
4048            .expect("b export");
4049
4050        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
4051        assert!(matches!(&vars[a_export.slot.0], Value::Symbolic(_)));
4052        assert!(matches!(&vars[b_export.slot.0], Value::Symbolic(_)));
4053        assert_eq!(vars[a_export.slot.0].to_string(), "x^2");
4054        assert_eq!(vars[b_export.slot.0].to_string(), "2^x");
4055    }
4056
4057    #[test]
4058    fn compile_interprets_nonscalar_symbolic_power_operand() {
4059        let ast = runmat_parser::parse(
4060            "\
4061syms x\n\
4062y = x^[1 2; 3 4];\n",
4063        )
4064        .expect("parse");
4065        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4066        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
4067        let entrypoint = hir.assembly.entrypoints[0].id;
4068
4069        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
4070        let layout = bytecode.layout.as_ref().expect("layout");
4071        let entry_layout = &layout.entrypoints[&entrypoint];
4072        let y_export = entry_layout
4073            .exports
4074            .iter()
4075            .find(|export| export.name == "y")
4076            .expect("y export");
4077
4078        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
4079        match &vars[y_export.slot.0] {
4080            Value::SymbolicArray(array) => {
4081                assert_eq!(array.shape, vec![2, 2]);
4082                assert_eq!(
4083                    array
4084                        .data
4085                        .iter()
4086                        .map(ToString::to_string)
4087                        .collect::<Vec<_>>(),
4088                    vec!["x", "x^3", "x^2", "x^4"]
4089                );
4090            }
4091            other => panic!("expected symbolic array, got {other:?}"),
4092        }
4093    }
4094
4095    #[test]
4096    fn compile_rejects_dynamic_member_store_back_paren_index_with_read_context_identifier() {
4097        let ast =
4098            runmat_parser::parse("s = struct('x', {1, 2}); f = 'x'; s(1).(f) = 3;").expect("parse");
4099        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4100        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4101        let entrypoint = hir.assembly.entrypoints[0].id;
4102        let function = hir.assembly.entrypoints[0].target;
4103        let body = mir.bodies.get_mut(&function).expect("entry body");
4104
4105        let mut patched = false;
4106        for block in &mut body.blocks {
4107            for stmt in &mut block.statements {
4108                if let MirStmtKind::Assign {
4109                    place: MirPlace::DynamicMember(base, _),
4110                    ..
4111                } = &mut stmt.kind
4112                {
4113                    if let MirPlace::Index(_, indexing) = base.as_mut() {
4114                        if matches!(indexing.kind, runmat_hir::IndexKind::Paren) {
4115                            indexing.result_context = runmat_hir::IndexResultContext::ReadSingle;
4116                            patched = true;
4117                            break;
4118                        }
4119                    }
4120                }
4121            }
4122            if patched {
4123                break;
4124            }
4125        }
4126        assert!(
4127            patched,
4128            "expected dynamic-member-over-paren assignment in lowered MIR"
4129        );
4130
4131        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4132        assert_eq!(
4133            err.identifier.as_deref(),
4134            Some("RunMat:MirIndexContextInvalid")
4135        );
4136    }
4137
4138    #[test]
4139    fn compile_rejects_dynamic_member_store_back_paren_index_with_deletion_context_identifier() {
4140        let ast =
4141            runmat_parser::parse("s = struct('x', {1, 2}); f = 'x'; s(1).(f) = 3;").expect("parse");
4142        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4143        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4144        let entrypoint = hir.assembly.entrypoints[0].id;
4145        let function = hir.assembly.entrypoints[0].target;
4146        let body = mir.bodies.get_mut(&function).expect("entry body");
4147
4148        let mut patched = false;
4149        for block in &mut body.blocks {
4150            for stmt in &mut block.statements {
4151                if let MirStmtKind::Assign {
4152                    place: MirPlace::DynamicMember(base, _),
4153                    ..
4154                } = &mut stmt.kind
4155                {
4156                    if let MirPlace::Index(_, indexing) = base.as_mut() {
4157                        if matches!(indexing.kind, runmat_hir::IndexKind::Paren) {
4158                            indexing.result_context =
4159                                runmat_hir::IndexResultContext::DeletionTarget;
4160                            patched = true;
4161                            break;
4162                        }
4163                    }
4164                }
4165            }
4166            if patched {
4167                break;
4168            }
4169        }
4170        assert!(
4171            patched,
4172            "expected dynamic-member-over-paren assignment in lowered MIR"
4173        );
4174
4175        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4176        assert_eq!(
4177            err.identifier.as_deref(),
4178            Some("RunMat:MirIndexContextInvalid")
4179        );
4180    }
4181
4182    #[test]
4183    fn compile_interprets_dynamic_member_store_back_paren_assignment() {
4184        let ast = runmat_parser::parse(
4185            "s = struct('x', {1, 2}); f = 'x'; s(2).(f) = 9; t = s(2); y = getfield(t, f);",
4186        )
4187        .expect("parse");
4188        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4189        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
4190        let entrypoint = hir.assembly.entrypoints[0].id;
4191
4192        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
4193        let layout = bytecode.layout.as_ref().expect("layout");
4194        let y_export = layout.entrypoints[&entrypoint]
4195            .exports
4196            .iter()
4197            .find(|export| export.name == "y")
4198            .expect("y export");
4199
4200        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
4201        assert_eq!(vars[y_export.slot.0], Value::Num(9.0));
4202    }
4203
4204    #[test]
4205    fn compile_rejects_dynamic_member_store_back_brace_index_with_read_context_identifier() {
4206        let ast =
4207            runmat_parser::parse("c = {struct('x', 1)}; f = 'x'; c{1}.(f) = 3;").expect("parse");
4208        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4209        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4210        let entrypoint = hir.assembly.entrypoints[0].id;
4211        let function = hir.assembly.entrypoints[0].target;
4212        let body = mir.bodies.get_mut(&function).expect("entry body");
4213
4214        let mut patched = false;
4215        for block in &mut body.blocks {
4216            for stmt in &mut block.statements {
4217                if let MirStmtKind::Assign {
4218                    place: MirPlace::DynamicMember(base, _),
4219                    ..
4220                } = &mut stmt.kind
4221                {
4222                    if let MirPlace::Index(_, indexing) = base.as_mut() {
4223                        if matches!(indexing.kind, runmat_hir::IndexKind::Brace) {
4224                            indexing.result_context = runmat_hir::IndexResultContext::ReadSingle;
4225                            patched = true;
4226                            break;
4227                        }
4228                    }
4229                }
4230            }
4231            if patched {
4232                break;
4233            }
4234        }
4235        assert!(
4236            patched,
4237            "expected dynamic-member-over-brace assignment in lowered MIR"
4238        );
4239
4240        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4241        assert_eq!(
4242            err.identifier.as_deref(),
4243            Some("RunMat:MirIndexContextInvalid")
4244        );
4245    }
4246
4247    #[test]
4248    fn compile_rejects_dynamic_member_store_back_brace_index_with_deletion_context_identifier() {
4249        let ast =
4250            runmat_parser::parse("c = {struct('x', 1)}; f = 'x'; c{1}.(f) = 3;").expect("parse");
4251        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4252        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4253        let entrypoint = hir.assembly.entrypoints[0].id;
4254        let function = hir.assembly.entrypoints[0].target;
4255        let body = mir.bodies.get_mut(&function).expect("entry body");
4256
4257        let mut patched = false;
4258        for block in &mut body.blocks {
4259            for stmt in &mut block.statements {
4260                if let MirStmtKind::Assign {
4261                    place: MirPlace::DynamicMember(base, _),
4262                    ..
4263                } = &mut stmt.kind
4264                {
4265                    if let MirPlace::Index(_, indexing) = base.as_mut() {
4266                        if matches!(indexing.kind, runmat_hir::IndexKind::Brace) {
4267                            indexing.result_context =
4268                                runmat_hir::IndexResultContext::DeletionTarget;
4269                            patched = true;
4270                            break;
4271                        }
4272                    }
4273                }
4274            }
4275            if patched {
4276                break;
4277            }
4278        }
4279        assert!(
4280            patched,
4281            "expected dynamic-member-over-brace assignment in lowered MIR"
4282        );
4283
4284        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4285        assert_eq!(
4286            err.identifier.as_deref(),
4287            Some("RunMat:MirIndexContextInvalid")
4288        );
4289    }
4290
4291    #[test]
4292    fn compile_interprets_dynamic_member_store_back_brace_assignment() {
4293        let ast = runmat_parser::parse(
4294            "c = {struct('x', 1)}; f = 'x'; c{1}.(f) = 9; t = c{1}; y = getfield(t, f);",
4295        )
4296        .expect("parse");
4297        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4298        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
4299        let entrypoint = hir.assembly.entrypoints[0].id;
4300
4301        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
4302        let layout = bytecode.layout.as_ref().expect("layout");
4303        let y_export = layout.entrypoints[&entrypoint]
4304            .exports
4305            .iter()
4306            .find(|export| export.name == "y")
4307            .expect("y export");
4308
4309        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
4310        assert_eq!(vars[y_export.slot.0], Value::Num(9.0));
4311    }
4312
4313    #[test]
4314    fn compile_interprets_dynamic_member_nested_index_delete_store_back() {
4315        let ast = runmat_parser::parse(
4316            "s = struct(); s.x = {1, 2, 3}; f = 'x'; s(1).(f)(2) = []; z = getfield(s(1), f); y = z{2};",
4317        )
4318        .expect("parse");
4319        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4320        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
4321        let entrypoint = hir.assembly.entrypoints[0].id;
4322
4323        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
4324        let layout = bytecode.layout.as_ref().expect("layout");
4325        let y_export = layout.entrypoints[&entrypoint]
4326            .exports
4327            .iter()
4328            .find(|export| export.name == "y")
4329            .expect("y export");
4330
4331        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
4332        assert_eq!(vars[y_export.slot.0], Value::Num(3.0));
4333    }
4334
4335    #[test]
4336    fn compile_rejects_multi_assign_call_output_count_mismatch_with_identifier() {
4337        let ast = runmat_parser::parse("[a, b] = deal(1, 2);").expect("parse");
4338        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4339        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4340        let entrypoint = hir.assembly.entrypoints[0].id;
4341        let function = hir.assembly.entrypoints[0].target;
4342        let body = mir.bodies.get_mut(&function).expect("entry body");
4343
4344        let mut patched = false;
4345        for block in &mut body.blocks {
4346            for stmt in &mut block.statements {
4347                if let MirStmtKind::MultiAssign {
4348                    value: MirRvalue::Call(call),
4349                    ..
4350                } = &mut stmt.kind
4351                {
4352                    call.requested_outputs = RequestedOutputCount::One;
4353                    patched = true;
4354                    break;
4355                }
4356            }
4357            if patched {
4358                break;
4359            }
4360        }
4361        assert!(patched, "expected multi-assign call in lowered MIR");
4362
4363        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4364        assert_eq!(
4365            err.identifier.as_deref(),
4366            Some("RunMat:MirMultiAssignOutputCountMismatch")
4367        );
4368    }
4369
4370    #[test]
4371    fn compile_rejects_multi_assign_index_target_context_mismatch_with_identifier() {
4372        let ast = runmat_parser::parse("a(1)=0; [x, b] = deal(1, 2);").expect("parse");
4373        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4374        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4375        let entrypoint = hir.assembly.entrypoints[0].id;
4376        let function = hir.assembly.entrypoints[0].target;
4377        let body = mir.bodies.get_mut(&function).expect("entry body");
4378
4379        let mut indexed_place_for_target: Option<MirPlace> = None;
4380        for block in &body.blocks {
4381            for stmt in &block.statements {
4382                if let MirStmtKind::Assign { place, .. } = &stmt.kind {
4383                    let mut cloned = place.clone();
4384                    if let MirPlace::Index(_, ref mut idx) = cloned {
4385                        idx.result_context = IndexResultContext::ReadSingle;
4386                        indexed_place_for_target = Some(cloned);
4387                    }
4388                }
4389                if indexed_place_for_target.is_some() {
4390                    break;
4391                }
4392            }
4393            if indexed_place_for_target.is_some() {
4394                break;
4395            }
4396        }
4397
4398        let mut patched = false;
4399        for block in &mut body.blocks {
4400            for stmt in &mut block.statements {
4401                if let MirStmtKind::MultiAssign { targets, .. } = &mut stmt.kind {
4402                    let place = indexed_place_for_target
4403                        .clone()
4404                        .expect("expected indexed place in lowered MIR");
4405                    if let Some(target) = targets.targets.first_mut() {
4406                        *target = MirOutputTarget::Place(place);
4407                        patched = true;
4408                    }
4409                }
4410                if patched {
4411                    break;
4412                }
4413            }
4414            if patched {
4415                break;
4416            }
4417        }
4418        assert!(
4419            patched,
4420            "expected indexed multi-assign output target in lowered MIR"
4421        );
4422
4423        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4424        assert_eq!(
4425            err.identifier.as_deref(),
4426            Some("RunMat:MirIndexContextInvalid")
4427        );
4428    }
4429
4430    #[test]
4431    fn compile_rejects_multi_assign_index_target_deletion_context_identifier() {
4432        let ast = runmat_parser::parse("a(1)=0; [x, b] = deal(1, 2);").expect("parse");
4433        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4434        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4435        let entrypoint = hir.assembly.entrypoints[0].id;
4436        let function = hir.assembly.entrypoints[0].target;
4437        let body = mir.bodies.get_mut(&function).expect("entry body");
4438
4439        let mut indexed_place_for_target: Option<MirPlace> = None;
4440        for block in &body.blocks {
4441            for stmt in &block.statements {
4442                if let MirStmtKind::Assign { place, .. } = &stmt.kind {
4443                    let mut cloned = place.clone();
4444                    if let MirPlace::Index(_, ref mut idx) = cloned {
4445                        idx.result_context = IndexResultContext::DeletionTarget;
4446                        indexed_place_for_target = Some(cloned);
4447                    }
4448                }
4449                if indexed_place_for_target.is_some() {
4450                    break;
4451                }
4452            }
4453            if indexed_place_for_target.is_some() {
4454                break;
4455            }
4456        }
4457
4458        let mut patched = false;
4459        for block in &mut body.blocks {
4460            for stmt in &mut block.statements {
4461                if let MirStmtKind::MultiAssign { targets, .. } = &mut stmt.kind {
4462                    let place = indexed_place_for_target
4463                        .clone()
4464                        .expect("expected indexed place in lowered MIR");
4465                    if let Some(target) = targets.targets.first_mut() {
4466                        *target = MirOutputTarget::Place(place);
4467                        patched = true;
4468                    }
4469                }
4470                if patched {
4471                    break;
4472                }
4473            }
4474            if patched {
4475                break;
4476            }
4477        }
4478        assert!(
4479            patched,
4480            "expected indexed multi-assign output target in lowered MIR"
4481        );
4482
4483        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4484        assert_eq!(
4485            err.identifier.as_deref(),
4486            Some("RunMat:MirIndexContextInvalid")
4487        );
4488    }
4489
4490    #[test]
4491    fn compile_rejects_nonempty_delete_rhs_with_identifier() {
4492        let ast = runmat_parser::parse("x = [1 2 3]; x(2) = [];").expect("parse");
4493        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4494        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4495        let entrypoint = hir.assembly.entrypoints[0].id;
4496        let function = hir.assembly.entrypoints[0].target;
4497        let body = mir.bodies.get_mut(&function).expect("entry body");
4498
4499        let mut patched = false;
4500        for block in &mut body.blocks {
4501            for stmt in &mut block.statements {
4502                if let MirStmtKind::Assign { place, value } = &mut stmt.kind {
4503                    if matches!(place, MirPlace::Index(_, _)) {
4504                        *value = MirRvalue::Aggregate {
4505                            kind: MirAggregateKind::Tensor,
4506                            rows: 1,
4507                            cols: 1,
4508                            elements: vec![MirOperand::Constant(MirConstant::Number(
4509                                "1".to_string(),
4510                            ))],
4511                        };
4512                        patched = true;
4513                        break;
4514                    }
4515                }
4516            }
4517            if patched {
4518                break;
4519            }
4520        }
4521        assert!(patched, "expected indexed delete assignment in lowered MIR");
4522
4523        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4524        assert_eq!(
4525            err.identifier.as_deref(),
4526            Some("RunMat:MirDeleteAssignmentRhsInvalid")
4527        );
4528    }
4529
4530    #[test]
4531    fn compile_rejects_delete_place_mismatch_with_identifier() {
4532        let ast = runmat_parser::parse("x = [1 2 3]; x(2) = [];").expect("parse");
4533        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4534        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4535        let entrypoint = hir.assembly.entrypoints[0].id;
4536        let function = hir.assembly.entrypoints[0].target;
4537        let body = mir.bodies.get_mut(&function).expect("entry body");
4538
4539        let mut patched = false;
4540        for block in &mut body.blocks {
4541            for stmt in &mut block.statements {
4542                if let MirStmtKind::Assign { place, .. } = &mut stmt.kind {
4543                    if let MirPlace::Index(base, _) = place {
4544                        *place = (**base).clone();
4545                        patched = true;
4546                        break;
4547                    }
4548                }
4549            }
4550            if patched {
4551                break;
4552            }
4553        }
4554        assert!(patched, "expected indexed delete assignment in lowered MIR");
4555
4556        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4557        assert_eq!(
4558            err.identifier.as_deref(),
4559            Some("RunMat:MirDeleteAssignmentPlaceMismatch")
4560        );
4561    }
4562
4563    #[test]
4564    fn compile_rejects_delete_on_nonindexed_target_with_identifier() {
4565        let ast = runmat_parser::parse("x = [1 2 3]; x(2) = [];").expect("parse");
4566        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4567        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4568        let entrypoint = hir.assembly.entrypoints[0].id;
4569        let function = hir.assembly.entrypoints[0].target;
4570        let body = mir.bodies.get_mut(&function).expect("entry body");
4571
4572        let replacement = body
4573            .blocks
4574            .iter()
4575            .flat_map(|block| block.statements.iter())
4576            .find_map(|stmt| match &stmt.kind {
4577                MirStmtKind::Assign {
4578                    place: MirPlace::Index(base, _),
4579                    ..
4580                } => Some((**base).clone()),
4581                _ => None,
4582            })
4583            .expect("expected indexed delete assignment target");
4584
4585        let mut patched_assign = false;
4586        let mut patched_mutation = false;
4587        for block in &mut body.blocks {
4588            for stmt in &mut block.statements {
4589                match &mut stmt.kind {
4590                    MirStmtKind::Assign { place, .. } => {
4591                        if matches!(place, MirPlace::Index(_, _)) {
4592                            *place = replacement.clone();
4593                            patched_assign = true;
4594                        }
4595                    }
4596                    MirStmtKind::PlaceMutation(mutation) => {
4597                        if matches!(mutation.kind, runmat_hir::PlaceMutationKind::Delete) {
4598                            mutation.place = replacement.clone();
4599                            patched_mutation = true;
4600                        }
4601                    }
4602                    _ => {}
4603                }
4604            }
4605        }
4606        assert!(
4607            patched_assign,
4608            "expected indexed delete assign stmt in lowered MIR"
4609        );
4610        assert!(
4611            patched_mutation,
4612            "expected delete place mutation stmt in lowered MIR"
4613        );
4614
4615        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4616        assert_eq!(
4617            err.identifier.as_deref(),
4618            Some("RunMat:MirDeleteAssignmentTargetInvalid")
4619        );
4620    }
4621
4622    #[test]
4623    fn compile_rejects_delete_on_brace_index_target_with_identifier() {
4624        let ast = runmat_parser::parse("x = [1 2 3]; x(2) = [];").expect("parse");
4625        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4626        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4627        let entrypoint = hir.assembly.entrypoints[0].id;
4628        let function = hir.assembly.entrypoints[0].target;
4629        let body = mir.bodies.get_mut(&function).expect("entry body");
4630
4631        let mut patched_assign = false;
4632        let mut patched_mutation = false;
4633        for block in &mut body.blocks {
4634            for stmt in &mut block.statements {
4635                match &mut stmt.kind {
4636                    MirStmtKind::Assign {
4637                        place: MirPlace::Index(_, indexing),
4638                        ..
4639                    } => {
4640                        indexing.kind = runmat_hir::IndexKind::Brace;
4641                        patched_assign = true;
4642                    }
4643                    MirStmtKind::PlaceMutation(mutation) => {
4644                        if let MirPlace::Index(_, indexing) = &mut mutation.place {
4645                            indexing.kind = runmat_hir::IndexKind::Brace;
4646                            patched_mutation = true;
4647                        }
4648                    }
4649                    _ => {}
4650                }
4651            }
4652        }
4653        assert!(
4654            patched_assign,
4655            "expected indexed delete assign stmt in lowered MIR"
4656        );
4657        assert!(
4658            patched_mutation,
4659            "expected delete place mutation stmt in lowered MIR"
4660        );
4661
4662        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4663        assert_eq!(
4664            err.identifier.as_deref(),
4665            Some("RunMat:MirDeleteAssignmentIndexKindInvalid")
4666        );
4667    }
4668
4669    #[test]
4670    fn compile_rejects_delete_with_nondeletion_index_context_with_identifier() {
4671        let ast = runmat_parser::parse("x = [1 2 3]; x(2) = [];").expect("parse");
4672        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4673        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4674        let entrypoint = hir.assembly.entrypoints[0].id;
4675        let function = hir.assembly.entrypoints[0].target;
4676        let body = mir.bodies.get_mut(&function).expect("entry body");
4677
4678        let mut patched_assign = false;
4679        let mut patched_mutation = false;
4680        for block in &mut body.blocks {
4681            for stmt in &mut block.statements {
4682                match &mut stmt.kind {
4683                    MirStmtKind::Assign {
4684                        place: MirPlace::Index(_, indexing),
4685                        ..
4686                    } => {
4687                        indexing.result_context = IndexResultContext::AssignmentTarget;
4688                        patched_assign = true;
4689                    }
4690                    MirStmtKind::PlaceMutation(mutation) => {
4691                        if let MirPlace::Index(_, indexing) = &mut mutation.place {
4692                            indexing.result_context = IndexResultContext::AssignmentTarget;
4693                            patched_mutation = true;
4694                        }
4695                    }
4696                    _ => {}
4697                }
4698            }
4699        }
4700        assert!(
4701            patched_assign,
4702            "expected indexed delete assign stmt in lowered MIR"
4703        );
4704        assert!(
4705            patched_mutation,
4706            "expected delete place mutation stmt in lowered MIR"
4707        );
4708
4709        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4710        assert_eq!(
4711            err.identifier.as_deref(),
4712            Some("RunMat:MirDeleteAssignmentContextInvalid")
4713        );
4714    }
4715
4716    #[test]
4717    fn compile_rejects_deletion_context_without_delete_with_identifier() {
4718        let ast = runmat_parser::parse("x = [1 2 3]; x(2) = 9;").expect("parse");
4719        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4720        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4721        let entrypoint = hir.assembly.entrypoints[0].id;
4722        let function = hir.assembly.entrypoints[0].target;
4723        let body = mir.bodies.get_mut(&function).expect("entry body");
4724
4725        let mut patched_assign = false;
4726        for block in &mut body.blocks {
4727            for stmt in &mut block.statements {
4728                if let MirStmtKind::Assign {
4729                    place: MirPlace::Index(_, indexing),
4730                    ..
4731                } = &mut stmt.kind
4732                {
4733                    indexing.result_context = IndexResultContext::DeletionTarget;
4734                    patched_assign = true;
4735                    break;
4736                }
4737            }
4738            if patched_assign {
4739                break;
4740            }
4741        }
4742        assert!(
4743            patched_assign,
4744            "expected indexed non-delete assign stmt in lowered MIR"
4745        );
4746
4747        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4748        assert_eq!(
4749            err.identifier.as_deref(),
4750            Some("RunMat:MirDeletionContextWithoutDeleteInvalid")
4751        );
4752    }
4753
4754    #[test]
4755    fn compile_rejects_delete_with_nonexisting_creation_policy_with_identifier() {
4756        let ast = runmat_parser::parse("x = [1 2 3]; x(2) = [];").expect("parse");
4757        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4758        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4759        let entrypoint = hir.assembly.entrypoints[0].id;
4760        let function = hir.assembly.entrypoints[0].target;
4761        let body = mir.bodies.get_mut(&function).expect("entry body");
4762
4763        let mut patched = false;
4764        for block in &mut body.blocks {
4765            for stmt in &mut block.statements {
4766                if let MirStmtKind::PlaceMutation(mutation) = &mut stmt.kind {
4767                    if matches!(mutation.kind, runmat_hir::PlaceMutationKind::Delete) {
4768                        mutation.creation_policy = AssignmentCreationPolicy::CreateArrayByIndex;
4769                        patched = true;
4770                        break;
4771                    }
4772                }
4773            }
4774            if patched {
4775                break;
4776            }
4777        }
4778        assert!(patched, "expected delete place mutation in lowered MIR");
4779
4780        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4781        assert_eq!(
4782            err.identifier.as_deref(),
4783            Some("RunMat:MirDeleteAssignmentCreationPolicyInvalid")
4784        );
4785    }
4786
4787    #[test]
4788    fn compile_rejects_invalid_read_index_context_with_identifier() {
4789        let ast = runmat_parser::parse("x = [1 2 3]; y = x(2);").expect("parse");
4790        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4791        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4792        let entrypoint = hir.assembly.entrypoints[0].id;
4793        let function = hir.assembly.entrypoints[0].target;
4794        let body = mir.bodies.get_mut(&function).expect("entry body");
4795
4796        let mut patched = false;
4797        for block in &mut body.blocks {
4798            for stmt in &mut block.statements {
4799                if let MirStmtKind::Assign {
4800                    value: MirRvalue::Index { indexing, .. },
4801                    ..
4802                } = &mut stmt.kind
4803                {
4804                    indexing.result_context = IndexResultContext::AssignmentTarget;
4805                    patched = true;
4806                    break;
4807                }
4808            }
4809            if patched {
4810                break;
4811            }
4812        }
4813        assert!(patched, "expected indexed read assignment in lowered MIR");
4814
4815        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4816        assert_eq!(
4817            err.identifier.as_deref(),
4818            Some("RunMat:MirIndexContextInvalid")
4819        );
4820    }
4821
4822    #[test]
4823    fn compile_rejects_unsupported_mir_unary_operator_with_identifier() {
4824        let ast = runmat_parser::parse("x = -1;").expect("parse");
4825        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4826        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4827        let entrypoint = hir.assembly.entrypoints[0].id;
4828        let function = hir.assembly.entrypoints[0].target;
4829        let body = mir.bodies.get_mut(&function).expect("entry body");
4830
4831        let mut patched = false;
4832        for block in &mut body.blocks {
4833            for stmt in &mut block.statements {
4834                if let MirStmtKind::Assign {
4835                    value: MirRvalue::Unary(op, _),
4836                    ..
4837                } = &mut stmt.kind
4838                {
4839                    *op = OperatorKind::Add;
4840                    patched = true;
4841                    break;
4842                }
4843            }
4844            if patched {
4845                break;
4846            }
4847        }
4848        assert!(patched, "expected unary assignment in lowered MIR");
4849
4850        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4851        assert_eq!(
4852            err.identifier.as_deref(),
4853            Some("RunMat:MirOperatorUnsupported")
4854        );
4855    }
4856
4857    #[test]
4858    fn compile_rejects_unsupported_mir_binary_operator_with_identifier() {
4859        let ast = runmat_parser::parse("x = 1 + 2;").expect("parse");
4860        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4861        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4862        let entrypoint = hir.assembly.entrypoints[0].id;
4863        let function = hir.assembly.entrypoints[0].target;
4864        let body = mir.bodies.get_mut(&function).expect("entry body");
4865
4866        let mut patched = false;
4867        for block in &mut body.blocks {
4868            for stmt in &mut block.statements {
4869                if let MirStmtKind::Assign {
4870                    value: MirRvalue::Binary(_, op, _),
4871                    ..
4872                } = &mut stmt.kind
4873                {
4874                    *op = OperatorKind::Transpose;
4875                    patched = true;
4876                    break;
4877                }
4878            }
4879            if patched {
4880                break;
4881            }
4882        }
4883        assert!(patched, "expected binary assignment in lowered MIR");
4884
4885        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4886        assert_eq!(
4887            err.identifier.as_deref(),
4888            Some("RunMat:MirOperatorUnsupported")
4889        );
4890    }
4891
4892    #[test]
4893    fn compile_rejects_unknown_mir_builtin_id_with_identifier() {
4894        let ast = runmat_parser::parse("x = sin(1);").expect("parse");
4895        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4896        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4897        let entrypoint = hir.assembly.entrypoints[0].id;
4898        let function = hir.assembly.entrypoints[0].target;
4899        let body = mir.bodies.get_mut(&function).expect("entry body");
4900
4901        let mut patched = false;
4902        for block in &mut body.blocks {
4903            for stmt in &mut block.statements {
4904                if let MirStmtKind::Assign {
4905                    value: MirRvalue::Call(call),
4906                    ..
4907                } = &mut stmt.kind
4908                {
4909                    call.callee = MirCallee::Static(CallableIdentity::Builtin(BuiltinId(
4910                        "__not_a_builtin".into(),
4911                    )));
4912                    patched = true;
4913                    break;
4914                }
4915            }
4916            if patched {
4917                break;
4918            }
4919        }
4920        assert!(patched, "expected call assignment in lowered MIR");
4921
4922        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4923        assert_eq!(err.identifier.as_deref(), Some("RunMat:MirBuiltinUnknown"));
4924    }
4925
4926    #[test]
4927    fn compile_rejects_invalid_mir_number_literal_with_identifier() {
4928        let ast = runmat_parser::parse("x = 1;").expect("parse");
4929        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4930        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4931        let entrypoint = hir.assembly.entrypoints[0].id;
4932        let function = hir.assembly.entrypoints[0].target;
4933        let body = mir.bodies.get_mut(&function).expect("entry body");
4934
4935        let mut patched = false;
4936        for block in &mut body.blocks {
4937            for stmt in &mut block.statements {
4938                if let MirStmtKind::Assign { value, .. } = &mut stmt.kind {
4939                    *value = MirRvalue::Use(MirOperand::Constant(MirConstant::Number(
4940                        "not_a_number".into(),
4941                    )));
4942                    patched = true;
4943                    break;
4944                }
4945            }
4946            if patched {
4947                break;
4948            }
4949        }
4950        assert!(patched, "expected assignment in lowered MIR");
4951
4952        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4953        assert_eq!(
4954            err.identifier.as_deref(),
4955            Some("RunMat:MirNumberLiteralInvalid")
4956        );
4957    }
4958
4959    #[test]
4960    fn compile_rejects_unknown_mir_constant_with_identifier() {
4961        let ast = runmat_parser::parse("x = pi;").expect("parse");
4962        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4963        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4964        let entrypoint = hir.assembly.entrypoints[0].id;
4965        let function = hir.assembly.entrypoints[0].target;
4966        let body = mir.bodies.get_mut(&function).expect("entry body");
4967
4968        let mut patched = false;
4969        for block in &mut body.blocks {
4970            for stmt in &mut block.statements {
4971                if let MirStmtKind::Assign { value, .. } = &mut stmt.kind {
4972                    *value = MirRvalue::Use(MirOperand::Constant(MirConstant::Symbol(
4973                        runmat_hir::SymbolName("definitely_missing_constant".to_string()),
4974                    )));
4975                    patched = true;
4976                    break;
4977                }
4978            }
4979            if patched {
4980                break;
4981            }
4982        }
4983        assert!(patched, "expected assignment in lowered MIR");
4984
4985        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
4986        assert_eq!(err.identifier.as_deref(), Some("RunMat:MirConstantUnknown"));
4987    }
4988
4989    #[test]
4990    fn compile_rejects_missing_mir_function_handle_runtime_name_with_identifier() {
4991        let ast = runmat_parser::parse("f = @sin;").expect("parse");
4992        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
4993        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
4994        let entrypoint = hir.assembly.entrypoints[0].id;
4995        let function = hir.assembly.entrypoints[0].target;
4996        let body = mir.bodies.get_mut(&function).expect("entry body");
4997
4998        let mut patched = false;
4999        for block in &mut body.blocks {
5000            for stmt in &mut block.statements {
5001                if let MirStmtKind::Assign { value, .. } = &mut stmt.kind {
5002                    *value = MirRvalue::Use(MirOperand::FunctionHandle(
5003                        CallableIdentity::ExternalName(QualifiedName(vec![
5004                            SymbolName("pkg".to_string()),
5005                            SymbolName(String::new()),
5006                            SymbolName("broken".to_string()),
5007                        ])),
5008                    ));
5009                    patched = true;
5010                    break;
5011                }
5012            }
5013            if patched {
5014                break;
5015            }
5016        }
5017        assert!(patched, "expected assignment in lowered MIR");
5018
5019        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5020        assert_eq!(
5021            err.identifier.as_deref(),
5022            Some("RunMat:MirFunctionHandleNameMissing")
5023        );
5024    }
5025
5026    #[test]
5027    fn compile_rejects_single_segment_external_function_handle_name_with_identifier() {
5028        let ast = runmat_parser::parse("f = @sin;").expect("parse");
5029        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5030        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5031        let entrypoint = hir.assembly.entrypoints[0].id;
5032        let function = hir.assembly.entrypoints[0].target;
5033        let body = mir.bodies.get_mut(&function).expect("entry body");
5034
5035        let mut patched = false;
5036        for block in &mut body.blocks {
5037            for stmt in &mut block.statements {
5038                if let MirStmtKind::Assign { value, .. } = &mut stmt.kind {
5039                    *value =
5040                        MirRvalue::Use(MirOperand::FunctionHandle(CallableIdentity::ExternalName(
5041                            QualifiedName(vec![SymbolName("pkg".to_string())]),
5042                        )));
5043                    patched = true;
5044                    break;
5045                }
5046            }
5047            if patched {
5048                break;
5049            }
5050        }
5051        assert!(patched, "expected assignment in lowered MIR");
5052
5053        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5054        assert_eq!(
5055            err.identifier.as_deref(),
5056            Some("RunMat:MirFunctionHandleNameMissing")
5057        );
5058    }
5059
5060    #[test]
5061    fn compile_rejects_empty_dynamic_function_handle_name_with_identifier() {
5062        let ast = runmat_parser::parse("f = @sin;").expect("parse");
5063        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5064        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5065        let entrypoint = hir.assembly.entrypoints[0].id;
5066        let function = hir.assembly.entrypoints[0].target;
5067        let body = mir.bodies.get_mut(&function).expect("entry body");
5068
5069        let mut patched = false;
5070        for block in &mut body.blocks {
5071            for stmt in &mut block.statements {
5072                if let MirStmtKind::Assign { value, .. } = &mut stmt.kind {
5073                    *value = MirRvalue::Use(MirOperand::FunctionHandle(
5074                        CallableIdentity::DynamicName(SymbolName(String::new())),
5075                    ));
5076                    patched = true;
5077                    break;
5078                }
5079            }
5080            if patched {
5081                break;
5082            }
5083        }
5084        assert!(patched, "expected assignment in lowered MIR");
5085
5086        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5087        assert_eq!(
5088            err.identifier.as_deref(),
5089            Some("RunMat:MirFunctionHandleNameMissing")
5090        );
5091    }
5092
5093    #[test]
5094    fn compile_rejects_whitespace_dynamic_function_handle_name_with_identifier() {
5095        let ast = runmat_parser::parse("f = @sin;").expect("parse");
5096        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5097        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5098        let entrypoint = hir.assembly.entrypoints[0].id;
5099        let function = hir.assembly.entrypoints[0].target;
5100        let body = mir.bodies.get_mut(&function).expect("entry body");
5101
5102        let mut patched = false;
5103        for block in &mut body.blocks {
5104            for stmt in &mut block.statements {
5105                if let MirStmtKind::Assign { value, .. } = &mut stmt.kind {
5106                    *value = MirRvalue::Use(MirOperand::FunctionHandle(
5107                        CallableIdentity::DynamicName(SymbolName("   ".to_string())),
5108                    ));
5109                    patched = true;
5110                    break;
5111                }
5112            }
5113            if patched {
5114                break;
5115            }
5116        }
5117        assert!(patched, "expected assignment in lowered MIR");
5118
5119        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5120        assert_eq!(
5121            err.identifier.as_deref(),
5122            Some("RunMat:MirFunctionHandleNameMissing")
5123        );
5124    }
5125
5126    #[test]
5127    fn compile_rejects_empty_builtin_function_handle_name_with_identifier() {
5128        let ast = runmat_parser::parse("f = @sin;").expect("parse");
5129        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5130        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5131        let entrypoint = hir.assembly.entrypoints[0].id;
5132        let function = hir.assembly.entrypoints[0].target;
5133        let body = mir.bodies.get_mut(&function).expect("entry body");
5134
5135        let mut patched = false;
5136        for block in &mut body.blocks {
5137            for stmt in &mut block.statements {
5138                if let MirStmtKind::Assign { value, .. } = &mut stmt.kind {
5139                    *value = MirRvalue::Use(MirOperand::FunctionHandle(CallableIdentity::Builtin(
5140                        BuiltinId(String::new()),
5141                    )));
5142                    patched = true;
5143                    break;
5144                }
5145            }
5146            if patched {
5147                break;
5148            }
5149        }
5150        assert!(patched, "expected assignment in lowered MIR");
5151
5152        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5153        assert_eq!(
5154            err.identifier.as_deref(),
5155            Some("RunMat:MirFunctionHandleNameMissing")
5156        );
5157    }
5158
5159    #[test]
5160    fn compile_rejects_whitespace_builtin_function_handle_name_with_identifier() {
5161        let ast = runmat_parser::parse("f = @sin;").expect("parse");
5162        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5163        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5164        let entrypoint = hir.assembly.entrypoints[0].id;
5165        let function = hir.assembly.entrypoints[0].target;
5166        let body = mir.bodies.get_mut(&function).expect("entry body");
5167
5168        let mut patched = false;
5169        for block in &mut body.blocks {
5170            for stmt in &mut block.statements {
5171                if let MirStmtKind::Assign { value, .. } = &mut stmt.kind {
5172                    *value = MirRvalue::Use(MirOperand::FunctionHandle(CallableIdentity::Builtin(
5173                        BuiltinId("   ".to_string()),
5174                    )));
5175                    patched = true;
5176                    break;
5177                }
5178            }
5179            if patched {
5180                break;
5181            }
5182        }
5183        assert!(patched, "expected assignment in lowered MIR");
5184
5185        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5186        assert_eq!(
5187            err.identifier.as_deref(),
5188            Some("RunMat:MirFunctionHandleNameMissing")
5189        );
5190    }
5191
5192    #[test]
5193    fn compile_lowers_method_function_handle_target_to_typed_instruction() {
5194        let ast = runmat_parser::parse("f = @sin;").expect("parse");
5195        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5196        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5197        let entrypoint = hir.assembly.entrypoints[0].id;
5198        let function = hir.assembly.entrypoints[0].target;
5199        let body = mir.bodies.get_mut(&function).expect("entry body");
5200
5201        let mut patched = false;
5202        for block in &mut body.blocks {
5203            for stmt in &mut block.statements {
5204                if let MirStmtKind::Assign { value, .. } = &mut stmt.kind {
5205                    *value = MirRvalue::Use(MirOperand::FunctionHandle(CallableIdentity::Method(
5206                        MethodId("m".to_string()),
5207                    )));
5208                    patched = true;
5209                    break;
5210                }
5211            }
5212            if patched {
5213                break;
5214            }
5215        }
5216        assert!(patched, "expected assignment in lowered MIR");
5217
5218        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile should succeed");
5219        assert!(bytecode.instructions.iter().any(|instr| matches!(
5220            instr,
5221            Instr::CreateMethodFunctionHandle(name) if name == "m"
5222        )));
5223    }
5224
5225    #[test]
5226    fn compile_lowers_struct_aggregate_literal_to_typed_instruction() {
5227        let ast = runmat_parser::parse("s = struct{a = 1, a = 2, b = 3};").expect("parse");
5228        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5229        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
5230        let entrypoint = hir.assembly.entrypoints[0].id;
5231        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile should succeed");
5232        assert!(bytecode.instructions.iter().any(|instr| match instr {
5233            Instr::CreateStructLiteral(fields) => {
5234                fields.as_slice() == ["a".to_string(), "a".to_string(), "b".to_string()]
5235            }
5236            _ => false,
5237        }));
5238    }
5239
5240    #[test]
5241    fn compile_lowers_object_aggregate_literal_to_typed_instruction() {
5242        let ast = runmat_parser::parse("p = ?Point{x = 1, y = 2};").expect("parse");
5243        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5244        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
5245        let entrypoint = hir.assembly.entrypoints[0].id;
5246        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile should succeed");
5247        assert!(bytecode.instructions.iter().any(|instr| match instr {
5248            Instr::CreateObjectLiteral { class_name, fields } => {
5249                class_name == "Point" && fields.as_slice() == ["x".to_string(), "y".to_string()]
5250            }
5251            _ => false,
5252        }));
5253    }
5254
5255    #[test]
5256    fn compile_rejects_whitespace_method_function_handle_name_with_identifier() {
5257        let ast = runmat_parser::parse("f = @sin;").expect("parse");
5258        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5259        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5260        let entrypoint = hir.assembly.entrypoints[0].id;
5261        let function = hir.assembly.entrypoints[0].target;
5262        let body = mir.bodies.get_mut(&function).expect("entry body");
5263
5264        let mut patched = false;
5265        for block in &mut body.blocks {
5266            for stmt in &mut block.statements {
5267                if let MirStmtKind::Assign { value, .. } = &mut stmt.kind {
5268                    *value = MirRvalue::Use(MirOperand::FunctionHandle(CallableIdentity::Method(
5269                        MethodId("   ".to_string()),
5270                    )));
5271                    patched = true;
5272                    break;
5273                }
5274            }
5275            if patched {
5276                break;
5277            }
5278        }
5279        assert!(patched, "expected assignment in lowered MIR");
5280
5281        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5282        assert_eq!(
5283            err.identifier.as_deref(),
5284            Some("RunMat:MirFunctionHandleNameMissing")
5285        );
5286    }
5287
5288    #[test]
5289    fn compile_rejects_empty_imported_module_function_handle_name_with_identifier() {
5290        let ast = runmat_parser::parse("f = @sin;").expect("parse");
5291        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5292        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5293        let entrypoint = hir.assembly.entrypoints[0].id;
5294        let function = hir.assembly.entrypoints[0].target;
5295        let body = mir.bodies.get_mut(&function).expect("entry body");
5296
5297        let mut patched = false;
5298        for block in &mut body.blocks {
5299            for stmt in &mut block.statements {
5300                if let MirStmtKind::Assign { value, .. } = &mut stmt.kind {
5301                    *value = MirRvalue::Use(MirOperand::FunctionHandle(
5302                        CallableIdentity::Imported(DefPath {
5303                            package: PackageName("pkg".to_string()),
5304                            module: QualifiedName(vec![]),
5305                            item: vec![DefPathSegment::Function(SymbolName("target".to_string()))],
5306                        }),
5307                    ));
5308                    patched = true;
5309                    break;
5310                }
5311            }
5312            if patched {
5313                break;
5314            }
5315        }
5316        assert!(patched, "expected assignment in lowered MIR");
5317
5318        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5319        assert_eq!(
5320            err.identifier.as_deref(),
5321            Some("RunMat:MirFunctionHandleNameMissing")
5322        );
5323    }
5324
5325    #[test]
5326    fn compile_rejects_imported_function_handle_missing_item_with_identifier() {
5327        let ast = runmat_parser::parse("f = @sin;").expect("parse");
5328        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5329        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5330        let entrypoint = hir.assembly.entrypoints[0].id;
5331        let function = hir.assembly.entrypoints[0].target;
5332        let body = mir.bodies.get_mut(&function).expect("entry body");
5333
5334        let mut patched = false;
5335        for block in &mut body.blocks {
5336            for stmt in &mut block.statements {
5337                if let MirStmtKind::Assign { value, .. } = &mut stmt.kind {
5338                    *value = MirRvalue::Use(MirOperand::FunctionHandle(
5339                        CallableIdentity::Imported(DefPath {
5340                            package: PackageName("pkg".to_string()),
5341                            module: QualifiedName(vec![
5342                                SymbolName("pkg".to_string()),
5343                                SymbolName("mod".to_string()),
5344                            ]),
5345                            item: vec![],
5346                        }),
5347                    ));
5348                    patched = true;
5349                    break;
5350                }
5351            }
5352            if patched {
5353                break;
5354            }
5355        }
5356        assert!(patched, "expected assignment in lowered MIR");
5357
5358        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5359        assert_eq!(
5360            err.identifier.as_deref(),
5361            Some("RunMat:MirFunctionHandleNameMissing")
5362        );
5363    }
5364
5365    #[test]
5366    fn compile_rejects_imported_function_handle_mismatched_item_with_identifier() {
5367        let ast = runmat_parser::parse("f = @sin;").expect("parse");
5368        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5369        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5370        let entrypoint = hir.assembly.entrypoints[0].id;
5371        let function = hir.assembly.entrypoints[0].target;
5372        let body = mir.bodies.get_mut(&function).expect("entry body");
5373
5374        let mut patched = false;
5375        for block in &mut body.blocks {
5376            for stmt in &mut block.statements {
5377                if let MirStmtKind::Assign { value, .. } = &mut stmt.kind {
5378                    *value = MirRvalue::Use(MirOperand::FunctionHandle(
5379                        CallableIdentity::Imported(DefPath {
5380                            package: PackageName("pkg".to_string()),
5381                            module: QualifiedName(vec![
5382                                SymbolName("pkg".to_string()),
5383                                SymbolName("target".to_string()),
5384                            ]),
5385                            item: vec![DefPathSegment::Function(SymbolName(
5386                                "different".to_string(),
5387                            ))],
5388                        }),
5389                    ));
5390                    patched = true;
5391                    break;
5392                }
5393            }
5394            if patched {
5395                break;
5396            }
5397        }
5398        assert!(patched, "expected assignment in lowered MIR");
5399
5400        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5401        assert_eq!(
5402            err.identifier.as_deref(),
5403            Some("RunMat:MirFunctionHandleNameMissing")
5404        );
5405    }
5406
5407    #[test]
5408    fn compile_rejects_unsupported_mir_static_call_fallback_policy_with_identifier() {
5409        let ast = runmat_parser::parse("x = sin(1);").expect("parse");
5410        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5411        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5412        let entrypoint = hir.assembly.entrypoints[0].id;
5413        let function = hir.assembly.entrypoints[0].target;
5414        let body = mir.bodies.get_mut(&function).expect("entry body");
5415
5416        let mut patched = false;
5417        for block in &mut body.blocks {
5418            for stmt in &mut block.statements {
5419                if let MirStmtKind::Assign {
5420                    value: MirRvalue::Call(call),
5421                    ..
5422                } = &mut stmt.kind
5423                {
5424                    call.callee = MirCallee::Static(CallableIdentity::Method(MethodId("m".into())));
5425                    call.fallback_policy = CallableFallbackPolicy::ObjectDispatch;
5426                    patched = true;
5427                    break;
5428                }
5429            }
5430            if patched {
5431                break;
5432            }
5433        }
5434        assert!(patched, "expected call assignment in lowered MIR");
5435
5436        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5437        assert_eq!(
5438            err.identifier.as_deref(),
5439            Some("RunMat:MirCallFallbackPolicyUnsupported")
5440        );
5441    }
5442
5443    #[test]
5444    fn compile_rejects_static_call_with_mismatched_imported_identity_name_shape() {
5445        let ast = runmat_parser::parse("x = sin(1);").expect("parse");
5446        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5447        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5448        let entrypoint = hir.assembly.entrypoints[0].id;
5449        let function = hir.assembly.entrypoints[0].target;
5450        let body = mir.bodies.get_mut(&function).expect("entry body");
5451
5452        let mut patched = false;
5453        for block in &mut body.blocks {
5454            for stmt in &mut block.statements {
5455                if let MirStmtKind::Assign {
5456                    value: MirRvalue::Call(call),
5457                    ..
5458                } = &mut stmt.kind
5459                {
5460                    call.callee = MirCallee::Static(CallableIdentity::Imported(DefPath {
5461                        package: PackageName("pkg".to_string()),
5462                        module: QualifiedName(vec![
5463                            SymbolName("pkg".to_string()),
5464                            SymbolName("target".to_string()),
5465                        ]),
5466                        item: vec![DefPathSegment::Function(SymbolName(
5467                            "different".to_string(),
5468                        ))],
5469                    }));
5470                    call.fallback_policy = CallableFallbackPolicy::RuntimeNameResolution;
5471                    patched = true;
5472                    break;
5473                }
5474            }
5475            if patched {
5476                break;
5477            }
5478        }
5479        assert!(patched, "expected call assignment in lowered MIR");
5480
5481        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5482        assert_eq!(
5483            err.identifier.as_deref(),
5484            Some("RunMat:MirCallTargetNameInvalid")
5485        );
5486    }
5487
5488    #[test]
5489    fn compile_rejects_static_call_with_single_segment_external_identity() {
5490        let ast = runmat_parser::parse("x = sin(1);").expect("parse");
5491        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5492        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5493        let entrypoint = hir.assembly.entrypoints[0].id;
5494        let function = hir.assembly.entrypoints[0].target;
5495        let body = mir.bodies.get_mut(&function).expect("entry body");
5496
5497        let mut patched = false;
5498        for block in &mut body.blocks {
5499            for stmt in &mut block.statements {
5500                if let MirStmtKind::Assign {
5501                    value: MirRvalue::Call(call),
5502                    ..
5503                } = &mut stmt.kind
5504                {
5505                    call.callee =
5506                        MirCallee::Static(CallableIdentity::ExternalName(QualifiedName(vec![
5507                            SymbolName("sqrt".to_string()),
5508                        ])));
5509                    call.fallback_policy = CallableFallbackPolicy::ExternalBoundary;
5510                    patched = true;
5511                    break;
5512                }
5513            }
5514            if patched {
5515                break;
5516            }
5517        }
5518        assert!(patched, "expected call assignment in lowered MIR");
5519
5520        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5521        assert_eq!(
5522            err.identifier.as_deref(),
5523            Some("RunMat:MirCallTargetNameInvalid")
5524        );
5525    }
5526
5527    #[test]
5528    fn compile_rejects_static_call_with_method_identity_name_shape() {
5529        let ast = runmat_parser::parse("x = sin(1);").expect("parse");
5530        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5531        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5532        let entrypoint = hir.assembly.entrypoints[0].id;
5533        let function = hir.assembly.entrypoints[0].target;
5534        let body = mir.bodies.get_mut(&function).expect("entry body");
5535
5536        let mut patched = false;
5537        for block in &mut body.blocks {
5538            for stmt in &mut block.statements {
5539                if let MirStmtKind::Assign {
5540                    value: MirRvalue::Call(call),
5541                    ..
5542                } = &mut stmt.kind
5543                {
5544                    call.callee = MirCallee::Static(CallableIdentity::Method(MethodId(
5545                        "remote_inc".to_string(),
5546                    )));
5547                    call.fallback_policy = CallableFallbackPolicy::RuntimeNameResolution;
5548                    patched = true;
5549                    break;
5550                }
5551            }
5552            if patched {
5553                break;
5554            }
5555        }
5556        assert!(patched, "expected call assignment in lowered MIR");
5557
5558        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5559        assert_eq!(
5560            err.identifier.as_deref(),
5561            Some("RunMat:MirCallTargetNameInvalid")
5562        );
5563    }
5564
5565    #[test]
5566    fn compile_rejects_static_call_with_whitespace_dynamic_identity_name_shape() {
5567        let ast = runmat_parser::parse("x = sin(1);").expect("parse");
5568        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5569        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5570        let entrypoint = hir.assembly.entrypoints[0].id;
5571        let function = hir.assembly.entrypoints[0].target;
5572        let body = mir.bodies.get_mut(&function).expect("entry body");
5573
5574        let mut patched = false;
5575        for block in &mut body.blocks {
5576            for stmt in &mut block.statements {
5577                if let MirStmtKind::Assign {
5578                    value: MirRvalue::Call(call),
5579                    ..
5580                } = &mut stmt.kind
5581                {
5582                    call.callee = MirCallee::Static(CallableIdentity::DynamicName(SymbolName(
5583                        "   ".to_string(),
5584                    )));
5585                    call.fallback_policy = CallableFallbackPolicy::RuntimeNameResolution;
5586                    patched = true;
5587                    break;
5588                }
5589            }
5590            if patched {
5591                break;
5592            }
5593        }
5594        assert!(patched, "expected call assignment in lowered MIR");
5595
5596        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5597        assert_eq!(
5598            err.identifier.as_deref(),
5599            Some("RunMat:MirCallTargetNameInvalid")
5600        );
5601    }
5602
5603    #[test]
5604    fn compile_rejects_multi_assign_static_call_with_invalid_name_shape() {
5605        let ast = runmat_parser::parse("[a, b] = max([1,2]);").expect("parse");
5606        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5607        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5608        let entrypoint = hir.assembly.entrypoints[0].id;
5609        let function = hir.assembly.entrypoints[0].target;
5610        let body = mir.bodies.get_mut(&function).expect("entry body");
5611
5612        let mut patched = false;
5613        for block in &mut body.blocks {
5614            for stmt in &mut block.statements {
5615                if let MirStmtKind::MultiAssign {
5616                    value: MirRvalue::Call(call),
5617                    ..
5618                } = &mut stmt.kind
5619                {
5620                    call.callee = MirCallee::Static(CallableIdentity::Imported(DefPath {
5621                        package: PackageName("pkg".to_string()),
5622                        module: QualifiedName(vec![
5623                            SymbolName("pkg".to_string()),
5624                            SymbolName("target".to_string()),
5625                        ]),
5626                        item: vec![DefPathSegment::Function(SymbolName(
5627                            "different".to_string(),
5628                        ))],
5629                    }));
5630                    call.fallback_policy = CallableFallbackPolicy::RuntimeNameResolution;
5631                    patched = true;
5632                    break;
5633                }
5634            }
5635            if patched {
5636                break;
5637            }
5638        }
5639        assert!(patched, "expected multi-assign call in lowered MIR");
5640
5641        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5642        assert_eq!(
5643            err.identifier.as_deref(),
5644            Some("RunMat:MirCallTargetNameInvalid")
5645        );
5646    }
5647
5648    #[test]
5649    fn compile_rejects_multi_assign_static_call_with_method_identity_name_shape() {
5650        let ast = runmat_parser::parse("[a, b] = max([1,2]);").expect("parse");
5651        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5652        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5653        let entrypoint = hir.assembly.entrypoints[0].id;
5654        let function = hir.assembly.entrypoints[0].target;
5655        let body = mir.bodies.get_mut(&function).expect("entry body");
5656
5657        let mut patched = false;
5658        for block in &mut body.blocks {
5659            for stmt in &mut block.statements {
5660                if let MirStmtKind::MultiAssign {
5661                    value: MirRvalue::Call(call),
5662                    ..
5663                } = &mut stmt.kind
5664                {
5665                    call.callee = MirCallee::Static(CallableIdentity::Method(MethodId(
5666                        "remote_pair".to_string(),
5667                    )));
5668                    call.fallback_policy = CallableFallbackPolicy::RuntimeNameResolution;
5669                    patched = true;
5670                    break;
5671                }
5672            }
5673            if patched {
5674                break;
5675            }
5676        }
5677        assert!(patched, "expected multi-assign call in lowered MIR");
5678
5679        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5680        assert_eq!(
5681            err.identifier.as_deref(),
5682            Some("RunMat:MirCallTargetNameInvalid")
5683        );
5684    }
5685
5686    #[test]
5687    fn compile_rejects_unsupported_mir_method_call_fallback_policy_with_identifier() {
5688        let ast = runmat_parser::parse("obj = 1; obj.method(1);").expect("parse");
5689        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5690        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5691        let entrypoint = hir.assembly.entrypoints[0].id;
5692        let function = hir.assembly.entrypoints[0].target;
5693        let body = mir.bodies.get_mut(&function).expect("entry body");
5694
5695        let mut patched = false;
5696        for block in &mut body.blocks {
5697            for stmt in &mut block.statements {
5698                let maybe_call = match &mut stmt.kind {
5699                    MirStmtKind::Assign {
5700                        value: MirRvalue::Call(call),
5701                        ..
5702                    }
5703                    | MirStmtKind::Expr(MirRvalue::Call(call)) => Some(call),
5704                    _ => None,
5705                };
5706                if let Some(call) = maybe_call {
5707                    if matches!(
5708                        call.syntax,
5709                        runmat_hir::CallSyntax::Method | runmat_hir::CallSyntax::DottedInvoke
5710                    ) {
5711                        call.fallback_policy = CallableFallbackPolicy::ExternalBoundary;
5712                        patched = true;
5713                        break;
5714                    }
5715                }
5716            }
5717            if patched {
5718                break;
5719            }
5720        }
5721        assert!(patched, "expected method call assignment in lowered MIR");
5722
5723        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5724        assert_eq!(
5725            err.identifier.as_deref(),
5726            Some("RunMat:MirMethodFallbackPolicyUnsupported")
5727        );
5728    }
5729
5730    #[test]
5731    fn compile_rejects_missing_mir_method_call_receiver_with_identifier() {
5732        let ast = runmat_parser::parse("obj = 1; obj.method(1);").expect("parse");
5733        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5734        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5735        let entrypoint = hir.assembly.entrypoints[0].id;
5736        let function = hir.assembly.entrypoints[0].target;
5737        let body = mir.bodies.get_mut(&function).expect("entry body");
5738
5739        let mut patched = false;
5740        for block in &mut body.blocks {
5741            for stmt in &mut block.statements {
5742                let maybe_call = match &mut stmt.kind {
5743                    MirStmtKind::Assign {
5744                        value: MirRvalue::Call(call),
5745                        ..
5746                    }
5747                    | MirStmtKind::Expr(MirRvalue::Call(call)) => Some(call),
5748                    _ => None,
5749                };
5750                if let Some(call) = maybe_call {
5751                    if matches!(
5752                        call.syntax,
5753                        runmat_hir::CallSyntax::Method | runmat_hir::CallSyntax::DottedInvoke
5754                    ) {
5755                        call.args.clear();
5756                        patched = true;
5757                        break;
5758                    }
5759                }
5760            }
5761            if patched {
5762                break;
5763            }
5764        }
5765        assert!(patched, "expected method call in lowered MIR");
5766
5767        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5768        assert_eq!(
5769            err.identifier.as_deref(),
5770            Some("RunMat:MirMethodCallReceiverMissing")
5771        );
5772    }
5773
5774    #[test]
5775    fn compile_rejects_invalid_mir_method_call_callee_with_identifier() {
5776        let ast = runmat_parser::parse("obj = 1; obj.method(1);").expect("parse");
5777        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5778        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5779        let entrypoint = hir.assembly.entrypoints[0].id;
5780        let function = hir.assembly.entrypoints[0].target;
5781        let body = mir.bodies.get_mut(&function).expect("entry body");
5782
5783        let mut patched = false;
5784        for block in &mut body.blocks {
5785            for stmt in &mut block.statements {
5786                let maybe_call = match &mut stmt.kind {
5787                    MirStmtKind::Assign {
5788                        value: MirRvalue::Call(call),
5789                        ..
5790                    }
5791                    | MirStmtKind::Expr(MirRvalue::Call(call)) => Some(call),
5792                    _ => None,
5793                };
5794                if let Some(call) = maybe_call {
5795                    if matches!(
5796                        call.syntax,
5797                        runmat_hir::CallSyntax::Method | runmat_hir::CallSyntax::DottedInvoke
5798                    ) {
5799                        call.callee = MirCallee::Dynamic(MirOperand::Constant(
5800                            MirConstant::Number("1".into()),
5801                        ));
5802                        patched = true;
5803                        break;
5804                    }
5805                }
5806            }
5807            if patched {
5808                break;
5809            }
5810        }
5811        assert!(patched, "expected method call in lowered MIR");
5812
5813        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5814        assert_eq!(
5815            err.identifier.as_deref(),
5816            Some("RunMat:MirMethodCallCalleeInvalid")
5817        );
5818    }
5819
5820    #[test]
5821    fn compile_rejects_imported_mir_method_call_callee_with_identifier() {
5822        let ast = runmat_parser::parse("obj = 1; obj.method(1);").expect("parse");
5823        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5824        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5825        let entrypoint = hir.assembly.entrypoints[0].id;
5826        let function = hir.assembly.entrypoints[0].target;
5827        let body = mir.bodies.get_mut(&function).expect("entry body");
5828
5829        let mut patched = false;
5830        for block in &mut body.blocks {
5831            for stmt in &mut block.statements {
5832                let maybe_call = match &mut stmt.kind {
5833                    MirStmtKind::Assign {
5834                        value: MirRvalue::Call(call),
5835                        ..
5836                    }
5837                    | MirStmtKind::Expr(MirRvalue::Call(call)) => Some(call),
5838                    _ => None,
5839                };
5840                if let Some(call) = maybe_call {
5841                    if matches!(
5842                        call.syntax,
5843                        runmat_hir::CallSyntax::Method | runmat_hir::CallSyntax::DottedInvoke
5844                    ) {
5845                        call.callee = MirCallee::Static(CallableIdentity::Imported(DefPath {
5846                            package: PackageName("pkg".into()),
5847                            module: QualifiedName(vec![
5848                                SymbolName("pkg".into()),
5849                                SymbolName("method".into()),
5850                            ]),
5851                            item: vec![DefPathSegment::Function(SymbolName("method".into()))],
5852                        }));
5853                        patched = true;
5854                        break;
5855                    }
5856                }
5857            }
5858            if patched {
5859                break;
5860            }
5861        }
5862        assert!(patched, "expected method call in lowered MIR");
5863
5864        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5865        assert_eq!(
5866            err.identifier.as_deref(),
5867            Some("RunMat:MirMethodCallCalleeInvalid")
5868        );
5869    }
5870
5871    #[test]
5872    fn compile_rejects_invalid_mir_multi_assign_method_call_callee_with_identifier() {
5873        let ast = runmat_parser::parse("obj = 1; [a, b] = obj.method(1);").expect("parse");
5874        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5875        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5876        let entrypoint = hir.assembly.entrypoints[0].id;
5877        let function = hir.assembly.entrypoints[0].target;
5878        let body = mir.bodies.get_mut(&function).expect("entry body");
5879
5880        let mut patched = false;
5881        for block in &mut body.blocks {
5882            for stmt in &mut block.statements {
5883                if let MirStmtKind::MultiAssign {
5884                    value: MirRvalue::Call(call),
5885                    ..
5886                } = &mut stmt.kind
5887                {
5888                    if matches!(
5889                        call.syntax,
5890                        runmat_hir::CallSyntax::Method | runmat_hir::CallSyntax::DottedInvoke
5891                    ) {
5892                        call.callee = MirCallee::Dynamic(MirOperand::Constant(
5893                            MirConstant::Number("1".into()),
5894                        ));
5895                        patched = true;
5896                        break;
5897                    }
5898                }
5899            }
5900            if patched {
5901                break;
5902            }
5903        }
5904        assert!(patched, "expected multi-assign method call in lowered MIR");
5905
5906        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5907        assert_eq!(
5908            err.identifier.as_deref(),
5909            Some("RunMat:MirMethodCallCalleeInvalid")
5910        );
5911    }
5912
5913    #[test]
5914    fn compile_rejects_imported_mir_multi_assign_method_call_callee_with_identifier() {
5915        let ast = runmat_parser::parse("obj = 1; [a, b] = obj.method(1);").expect("parse");
5916        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5917        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5918        let entrypoint = hir.assembly.entrypoints[0].id;
5919        let function = hir.assembly.entrypoints[0].target;
5920        let body = mir.bodies.get_mut(&function).expect("entry body");
5921
5922        let mut patched = false;
5923        for block in &mut body.blocks {
5924            for stmt in &mut block.statements {
5925                if let MirStmtKind::MultiAssign {
5926                    value: MirRvalue::Call(call),
5927                    ..
5928                } = &mut stmt.kind
5929                {
5930                    if matches!(
5931                        call.syntax,
5932                        runmat_hir::CallSyntax::Method | runmat_hir::CallSyntax::DottedInvoke
5933                    ) {
5934                        call.callee = MirCallee::Static(CallableIdentity::Imported(DefPath {
5935                            package: PackageName("pkg".into()),
5936                            module: QualifiedName(vec![
5937                                SymbolName("pkg".into()),
5938                                SymbolName("method".into()),
5939                            ]),
5940                            item: vec![DefPathSegment::Function(SymbolName("method".into()))],
5941                        }));
5942                        patched = true;
5943                        break;
5944                    }
5945                }
5946            }
5947            if patched {
5948                break;
5949            }
5950        }
5951        assert!(patched, "expected multi-assign method call in lowered MIR");
5952
5953        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
5954        assert_eq!(
5955            err.identifier.as_deref(),
5956            Some("RunMat:MirMethodCallCalleeInvalid")
5957        );
5958    }
5959
5960    #[test]
5961    fn compile_rejects_multisegment_external_mir_method_call_callee_with_identifier() {
5962        let ast = runmat_parser::parse("obj = 1; obj.method(1);").expect("parse");
5963        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
5964        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
5965        let entrypoint = hir.assembly.entrypoints[0].id;
5966        let function = hir.assembly.entrypoints[0].target;
5967        let body = mir.bodies.get_mut(&function).expect("entry body");
5968
5969        let mut patched = false;
5970        for block in &mut body.blocks {
5971            for stmt in &mut block.statements {
5972                let maybe_call = match &mut stmt.kind {
5973                    MirStmtKind::Assign {
5974                        value: MirRvalue::Call(call),
5975                        ..
5976                    }
5977                    | MirStmtKind::Expr(MirRvalue::Call(call)) => Some(call),
5978                    _ => None,
5979                };
5980                if let Some(call) = maybe_call {
5981                    if matches!(
5982                        call.syntax,
5983                        runmat_hir::CallSyntax::Method | runmat_hir::CallSyntax::DottedInvoke
5984                    ) {
5985                        call.callee =
5986                            MirCallee::Static(CallableIdentity::ExternalName(QualifiedName(vec![
5987                                SymbolName("pkg".into()),
5988                                SymbolName("method".into()),
5989                            ])));
5990                        patched = true;
5991                        break;
5992                    }
5993                }
5994            }
5995            if patched {
5996                break;
5997            }
5998        }
5999        assert!(patched, "expected method call in lowered MIR");
6000
6001        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
6002        assert_eq!(
6003            err.identifier.as_deref(),
6004            Some("RunMat:MirMethodCallCalleeInvalid")
6005        );
6006    }
6007
6008    #[test]
6009    fn compile_rejects_empty_method_name_mir_method_call_callee_with_identifier() {
6010        let ast = runmat_parser::parse("obj = 1; obj.method(1);").expect("parse");
6011        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6012        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
6013        let entrypoint = hir.assembly.entrypoints[0].id;
6014        let function = hir.assembly.entrypoints[0].target;
6015        let body = mir.bodies.get_mut(&function).expect("entry body");
6016
6017        let mut patched = false;
6018        for block in &mut body.blocks {
6019            for stmt in &mut block.statements {
6020                let maybe_call = match &mut stmt.kind {
6021                    MirStmtKind::Assign {
6022                        value: MirRvalue::Call(call),
6023                        ..
6024                    }
6025                    | MirStmtKind::Expr(MirRvalue::Call(call)) => Some(call),
6026                    _ => None,
6027                };
6028                if let Some(call) = maybe_call {
6029                    if matches!(
6030                        call.syntax,
6031                        runmat_hir::CallSyntax::Method | runmat_hir::CallSyntax::DottedInvoke
6032                    ) {
6033                        call.callee =
6034                            MirCallee::Static(CallableIdentity::Method(MethodId(String::new())));
6035                        patched = true;
6036                        break;
6037                    }
6038                }
6039            }
6040            if patched {
6041                break;
6042            }
6043        }
6044        assert!(patched, "expected method call in lowered MIR");
6045
6046        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
6047        assert_eq!(
6048            err.identifier.as_deref(),
6049            Some("RunMat:MirMethodCallCalleeInvalid")
6050        );
6051    }
6052
6053    #[test]
6054    fn compile_rejects_whitespace_method_name_mir_method_call_callee_with_identifier() {
6055        let ast = runmat_parser::parse("obj = 1; obj.method(1);").expect("parse");
6056        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6057        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
6058        let entrypoint = hir.assembly.entrypoints[0].id;
6059        let function = hir.assembly.entrypoints[0].target;
6060        let body = mir.bodies.get_mut(&function).expect("entry body");
6061
6062        let mut patched = false;
6063        for block in &mut body.blocks {
6064            for stmt in &mut block.statements {
6065                let maybe_call = match &mut stmt.kind {
6066                    MirStmtKind::Assign {
6067                        value: MirRvalue::Call(call),
6068                        ..
6069                    }
6070                    | MirStmtKind::Expr(MirRvalue::Call(call)) => Some(call),
6071                    _ => None,
6072                };
6073                if let Some(call) = maybe_call {
6074                    if matches!(
6075                        call.syntax,
6076                        runmat_hir::CallSyntax::Method | runmat_hir::CallSyntax::DottedInvoke
6077                    ) {
6078                        call.callee = MirCallee::Static(CallableIdentity::Method(MethodId(
6079                            "   ".to_string(),
6080                        )));
6081                        patched = true;
6082                        break;
6083                    }
6084                }
6085            }
6086            if patched {
6087                break;
6088            }
6089        }
6090        assert!(patched, "expected method call in lowered MIR");
6091
6092        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
6093        assert_eq!(
6094            err.identifier.as_deref(),
6095            Some("RunMat:MirMethodCallCalleeInvalid")
6096        );
6097    }
6098
6099    #[test]
6100    fn compile_rejects_whitespace_single_segment_external_mir_method_call_callee_with_identifier() {
6101        let ast = runmat_parser::parse("obj = 1; obj.method(1);").expect("parse");
6102        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6103        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
6104        let entrypoint = hir.assembly.entrypoints[0].id;
6105        let function = hir.assembly.entrypoints[0].target;
6106        let body = mir.bodies.get_mut(&function).expect("entry body");
6107
6108        let mut patched = false;
6109        for block in &mut body.blocks {
6110            for stmt in &mut block.statements {
6111                let maybe_call = match &mut stmt.kind {
6112                    MirStmtKind::Assign {
6113                        value: MirRvalue::Call(call),
6114                        ..
6115                    }
6116                    | MirStmtKind::Expr(MirRvalue::Call(call)) => Some(call),
6117                    _ => None,
6118                };
6119                if let Some(call) = maybe_call {
6120                    if matches!(
6121                        call.syntax,
6122                        runmat_hir::CallSyntax::Method | runmat_hir::CallSyntax::DottedInvoke
6123                    ) {
6124                        call.callee =
6125                            MirCallee::Static(CallableIdentity::ExternalName(QualifiedName(vec![
6126                                SymbolName("   ".into()),
6127                            ])));
6128                        patched = true;
6129                        break;
6130                    }
6131                }
6132            }
6133            if patched {
6134                break;
6135            }
6136        }
6137        assert!(patched, "expected method call in lowered MIR");
6138
6139        let err = compile(&hir.assembly, &mir, entrypoint).expect_err("compile should fail");
6140        assert_eq!(
6141            err.identifier.as_deref(),
6142            Some("RunMat:MirMethodCallCalleeInvalid")
6143        );
6144    }
6145
6146    #[test]
6147    fn compile_lowers_statement_semantic_call_to_zero_outputs() {
6148        let ast =
6149            runmat_parser::parse("function y = f(x); y = nargout(); end; f(10);").expect("parse");
6150        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6151        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6152        let entrypoint = hir.assembly.entrypoints[0].id;
6153
6154        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6155
6156        assert!(bytecode
6157            .instructions
6158            .iter()
6159            .any(|instr| matches!(instr, Instr::CallSemanticFunctionMulti(_, _, 0))));
6160    }
6161
6162    #[test]
6163    fn compile_lowers_method_calls_with_explicit_object_dispatch_policy() {
6164        let ast = runmat_parser::parse("obj = 1; obj.method(1);").expect("parse");
6165        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6166        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6167        let entrypoint = hir.assembly.entrypoints[0].id;
6168
6169        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6170        assert!(bytecode.instructions.iter().any(|instr| matches!(
6171            instr,
6172            Instr::CallMethodOrMemberIndexMulti {
6173                fallback_policy: CallableFallbackPolicy::ObjectDispatch,
6174                ..
6175            }
6176        )));
6177    }
6178
6179    #[test]
6180    fn compile_interprets_simple_indexed_assignment() {
6181        let ast = runmat_parser::parse("x = [1 2; 3 4]; x(1, 2) = 9;").expect("parse");
6182        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6183        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6184        let entrypoint = hir.assembly.entrypoints[0].id;
6185
6186        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6187        let layout = bytecode.layout.as_ref().expect("layout");
6188        let x_export = layout.entrypoints[&entrypoint]
6189            .exports
6190            .iter()
6191            .find(|export| export.name == "x")
6192            .expect("x export");
6193
6194        assert!(bytecode
6195            .instructions
6196            .iter()
6197            .any(|instr| matches!(instr, Instr::StoreIndex(2))));
6198        assert!(!bytecode
6199            .instructions
6200            .iter()
6201            .any(|instr| matches!(instr, Instr::StoreSlice(2, 2, 0, 0))));
6202
6203        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6204        let Value::Tensor(tensor) = &vars[x_export.slot.0] else {
6205            panic!("expected tensor");
6206        };
6207        assert_eq!(tensor.shape, vec![2, 2]);
6208        assert_eq!(tensor.data, vec![1.0, 3.0, 9.0, 4.0]);
6209    }
6210
6211    #[test]
6212    fn compile_interprets_simple_slice_assignment() {
6213        let ast = runmat_parser::parse("x = [1 2; 3 4]; x(:, 2) = [9; 8];").expect("parse");
6214        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6215        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6216        let entrypoint = hir.assembly.entrypoints[0].id;
6217
6218        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6219        let layout = bytecode.layout.as_ref().expect("layout");
6220        let x_export = layout.entrypoints[&entrypoint]
6221            .exports
6222            .iter()
6223            .find(|export| export.name == "x")
6224            .expect("x export");
6225
6226        assert!(bytecode
6227            .instructions
6228            .iter()
6229            .any(|instr| matches!(instr, Instr::StoreSlice(2, 1, 1, 0))));
6230
6231        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6232        let Value::Tensor(tensor) = &vars[x_export.slot.0] else {
6233            panic!("expected tensor");
6234        };
6235        assert_eq!(tensor.shape, vec![2, 2]);
6236        assert_eq!(tensor.data, vec![1.0, 3.0, 9.0, 8.0]);
6237    }
6238
6239    #[test]
6240    fn compile_interprets_simple_cell_indexing() {
6241        let ast = runmat_parser::parse("c = {1, 2}; x = c{2};").expect("parse");
6242        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6243        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6244        let entrypoint = hir.assembly.entrypoints[0].id;
6245
6246        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6247        let layout = bytecode.layout.as_ref().expect("layout");
6248        let x_export = layout.entrypoints[&entrypoint]
6249            .exports
6250            .iter()
6251            .find(|export| export.name == "x")
6252            .expect("x export");
6253
6254        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6255        assert_eq!(vars[x_export.slot.0], Value::Num(2.0));
6256    }
6257
6258    #[test]
6259    fn compile_interprets_simple_cell_indexed_assignment() {
6260        let ast = runmat_parser::parse("c = {1, 2}; c{2} = 9; x = c{2};").expect("parse");
6261        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6262        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6263        let entrypoint = hir.assembly.entrypoints[0].id;
6264
6265        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6266        let layout = bytecode.layout.as_ref().expect("layout");
6267        let x_export = layout.entrypoints[&entrypoint]
6268            .exports
6269            .iter()
6270            .find(|export| export.name == "x")
6271            .expect("x export");
6272
6273        assert!(bytecode
6274            .instructions
6275            .iter()
6276            .any(|instr| matches!(instr, Instr::StoreIndexCell { num_indices: 1, .. })));
6277
6278        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6279        assert_eq!(vars[x_export.slot.0], Value::Num(9.0));
6280    }
6281
6282    #[test]
6283    fn compile_carries_cell_end_selector_metadata_for_reads() {
6284        let ast = runmat_parser::parse("c = {1, 2, 3}; x = c{end};").expect("parse");
6285        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6286        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6287        let entrypoint = hir.assembly.entrypoints[0].id;
6288
6289        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6290        let layout = bytecode.layout.as_ref().expect("layout");
6291        let x_export = layout.entrypoints[&entrypoint]
6292            .exports
6293            .iter()
6294            .find(|export| export.name == "x")
6295            .expect("x export");
6296
6297        assert!(bytecode.instructions.iter().any(|instr| {
6298            matches!(
6299                instr,
6300                Instr::IndexCell {
6301                    num_indices: 1,
6302                    end_offsets,
6303                    ..
6304                } if end_offsets == &vec![(0, 0)]
6305            ) || matches!(
6306                instr,
6307                Instr::IndexCellList {
6308                    num_indices: 1,
6309                    end_offsets,
6310                    ..
6311                } if end_offsets == &vec![(0, 0)]
6312            )
6313        }));
6314
6315        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6316        assert_eq!(vars[x_export.slot.0], Value::Num(3.0));
6317    }
6318
6319    #[test]
6320    fn compile_carries_cell_end_selector_metadata_for_stores() {
6321        let ast = runmat_parser::parse("c = {1, 2, 3}; c{end} = 9; x = c{3};").expect("parse");
6322        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6323        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6324        let entrypoint = hir.assembly.entrypoints[0].id;
6325
6326        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6327        let layout = bytecode.layout.as_ref().expect("layout");
6328        let x_export = layout.entrypoints[&entrypoint]
6329            .exports
6330            .iter()
6331            .find(|export| export.name == "x")
6332            .expect("x export");
6333
6334        assert!(bytecode.instructions.iter().any(|instr| matches!(
6335            instr,
6336            Instr::StoreIndexCell {
6337                num_indices: 1,
6338                end_offsets,
6339                ..
6340            } if end_offsets == &vec![(0, 0)]
6341        )));
6342
6343        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6344        assert_eq!(vars[x_export.slot.0], Value::Num(9.0));
6345    }
6346
6347    #[test]
6348    fn compile_carries_cell_end_offset_selector_metadata_in_semantic_function_reads() {
6349        let ast = runmat_parser::parse(
6350            "function y = tail_cell(c); y = c{end-1}; end; c = {1, 2, 3}; x = tail_cell(c);",
6351        )
6352        .expect("parse");
6353        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6354        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6355        let entrypoint = hir.assembly.entrypoints[0].id;
6356
6357        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6358        let function_id = bytecode
6359            .function_registry
6360            .resolve_name("tail_cell")
6361            .expect("tail_cell semantic function id");
6362        let function = bytecode
6363            .function_registry
6364            .get(function_id)
6365            .expect("tail_cell semantic bytecode");
6366        let read_offsets: Vec<Vec<(usize, isize)>> = function
6367            .instructions
6368            .iter()
6369            .filter_map(|instr| match instr {
6370                Instr::IndexCell {
6371                    num_indices: 1,
6372                    end_offsets,
6373                    ..
6374                }
6375                | Instr::IndexCellList {
6376                    num_indices: 1,
6377                    end_offsets,
6378                    ..
6379                } => Some(end_offsets.clone()),
6380                _ => None,
6381            })
6382            .collect();
6383        assert!(
6384            read_offsets.iter().any(|offsets| offsets == &vec![(0, -1)]),
6385            "expected semantic function end-1 metadata offset; actual offsets: {read_offsets:?}; instructions: {:?}",
6386            function.instructions
6387        );
6388
6389        let layout = bytecode.layout.as_ref().expect("layout");
6390        let x_export = layout.entrypoints[&entrypoint]
6391            .exports
6392            .iter()
6393            .find(|export| export.name == "x")
6394            .expect("x export");
6395        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6396        assert_eq!(vars[x_export.slot.0], Value::Num(2.0));
6397    }
6398
6399    #[test]
6400    fn compile_carries_cell_end_offset_selector_metadata_in_semantic_function_stores() {
6401        let ast = runmat_parser::parse(
6402            "function y = patch_cell(c, v); c{end-1} = v; y = c{2}; end; c = {1, 2, 3}; x = patch_cell(c, 9);",
6403        )
6404        .expect("parse");
6405        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6406        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6407        let entrypoint = hir.assembly.entrypoints[0].id;
6408
6409        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6410        let function_id = bytecode
6411            .function_registry
6412            .resolve_name("patch_cell")
6413            .expect("patch_cell semantic function id");
6414        let function = bytecode
6415            .function_registry
6416            .get(function_id)
6417            .expect("patch_cell semantic bytecode");
6418        let store_offsets: Vec<Vec<(usize, isize)>> = function
6419            .instructions
6420            .iter()
6421            .filter_map(|instr| match instr {
6422                Instr::StoreIndexCell {
6423                    num_indices: 1,
6424                    end_offsets,
6425                    ..
6426                } => Some(end_offsets.clone()),
6427                _ => None,
6428            })
6429            .collect();
6430        assert!(
6431            store_offsets
6432                .iter()
6433                .any(|offsets| offsets == &vec![(0, -1)]),
6434            "expected semantic function end-1 metadata offset; actual offsets: {store_offsets:?}"
6435        );
6436
6437        let layout = bytecode.layout.as_ref().expect("layout");
6438        let x_export = layout.entrypoints[&entrypoint]
6439            .exports
6440            .iter()
6441            .find(|export| export.name == "x")
6442            .expect("x export");
6443        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6444        assert_eq!(vars[x_export.slot.0], Value::Num(9.0));
6445    }
6446
6447    #[test]
6448    fn compile_supports_general_cell_end_expression_reads() {
6449        let ast = runmat_parser::parse("c = {10, 20, 30, 40}; x = c{end/2};").expect("parse");
6450        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6451        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6452        let entrypoint = hir.assembly.entrypoints[0].id;
6453
6454        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6455        assert!(bytecode.instructions.iter().any(|instr| {
6456            matches!(
6457                instr,
6458                Instr::IndexCell {
6459                    num_indices: 1,
6460                    end_exprs,
6461                    ..
6462                } if end_exprs.iter().any(|(pos, expr)| {
6463                    *pos == 0
6464                        && matches!(
6465                            expr,
6466                            crate::bytecode::EndExpr::Div(left, right)
6467                                if matches!(left.as_ref(), crate::bytecode::EndExpr::End)
6468                                    && matches!(right.as_ref(), crate::bytecode::EndExpr::Const(v) if (*v - 2.0).abs() < f64::EPSILON)
6469                        )
6470                })
6471            ) || matches!(
6472                instr,
6473                Instr::IndexCellList {
6474                    num_indices: 1,
6475                    end_exprs,
6476                    ..
6477                } if end_exprs.iter().any(|(pos, expr)| {
6478                    *pos == 0
6479                        && matches!(
6480                            expr,
6481                            crate::bytecode::EndExpr::Div(left, right)
6482                                if matches!(left.as_ref(), crate::bytecode::EndExpr::End)
6483                                    && matches!(right.as_ref(), crate::bytecode::EndExpr::Const(v) if (*v - 2.0).abs() < f64::EPSILON)
6484                        )
6485                })
6486            )
6487        }));
6488        let layout = bytecode.layout.as_ref().expect("layout");
6489        let x_export = layout.entrypoints[&entrypoint]
6490            .exports
6491            .iter()
6492            .find(|export| export.name == "x")
6493            .expect("x export");
6494        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6495        assert_eq!(vars[x_export.slot.0], Value::Num(20.0));
6496    }
6497
6498    #[test]
6499    fn compile_rejects_fractional_cell_end_expression_read_index() {
6500        let ast = runmat_parser::parse("c = {10, 20, 30, 40, 50}; x = c{end/2};").expect("parse");
6501        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6502        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6503        let entrypoint = hir.assembly.entrypoints[0].id;
6504
6505        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6506        let err = block_on(crate::interpret(&bytecode))
6507            .expect_err("fractional cell end expression selector should fail");
6508        assert_eq!(err.identifier(), Some("RunMat:UnsupportedIndexType"));
6509    }
6510
6511    #[test]
6512    fn compile_supports_general_cell_end_expression_stores() {
6513        let ast = runmat_parser::parse("c = {1, 2, 3, 4}; c{floor(end/2)} = 9; x = c{2};")
6514            .expect("parse");
6515        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6516        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6517        let entrypoint = hir.assembly.entrypoints[0].id;
6518
6519        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6520        assert!(bytecode.instructions.iter().any(|instr| {
6521            matches!(
6522                instr,
6523                Instr::StoreIndexCell {
6524                    num_indices: 1,
6525                    end_exprs,
6526                    ..
6527                } if end_exprs.iter().any(|(pos, expr)| {
6528                    *pos == 0
6529                        && matches!(
6530                            expr,
6531                            crate::bytecode::EndExpr::ResolvedCall { args, .. } if args.len() == 1
6532                        )
6533                })
6534            )
6535        }));
6536        let layout = bytecode.layout.as_ref().expect("layout");
6537        let x_export = layout.entrypoints[&entrypoint]
6538            .exports
6539            .iter()
6540            .find(|export| export.name == "x")
6541            .expect("x export");
6542        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6543        assert_eq!(vars[x_export.slot.0], Value::Num(9.0));
6544    }
6545
6546    #[test]
6547    fn compile_supports_cell_brace_end_plus_one_growth_for_vectors() {
6548        let ast = runmat_parser::parse("c = {1, 2}; c{end+1} = 9; x = c{3};").expect("parse");
6549        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6550        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6551        let entrypoint = hir.assembly.entrypoints[0].id;
6552
6553        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6554        assert!(bytecode.instructions.iter().any(|instr| {
6555            matches!(
6556                instr,
6557                Instr::StoreIndexCell {
6558                    num_indices: 1,
6559                    end_offsets,
6560                    ..
6561                } if end_offsets == &vec![(0, 1)]
6562            )
6563        }));
6564        let layout = bytecode.layout.as_ref().expect("layout");
6565        let x_export = layout.entrypoints[&entrypoint]
6566            .exports
6567            .iter()
6568            .find(|export| export.name == "x")
6569            .expect("x export");
6570        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6571        assert_eq!(vars[x_export.slot.0], Value::Num(9.0));
6572    }
6573
6574    #[test]
6575    fn compile_supports_cell_brace_linear_gap_growth_for_vectors() {
6576        let ast = runmat_parser::parse(
6577            "c = {1, 2}; c{5} = 9; a = isempty(c{3}); b = isempty(c{4}); x = c{5};",
6578        )
6579        .expect("parse");
6580        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6581        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6582        let entrypoint = hir.assembly.entrypoints[0].id;
6583
6584        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6585        let layout = bytecode.layout.as_ref().expect("layout");
6586        let c_export = layout.entrypoints[&entrypoint]
6587            .exports
6588            .iter()
6589            .find(|export| export.name == "c")
6590            .expect("c export");
6591        let a_export = layout.entrypoints[&entrypoint]
6592            .exports
6593            .iter()
6594            .find(|export| export.name == "a")
6595            .expect("a export");
6596        let b_export = layout.entrypoints[&entrypoint]
6597            .exports
6598            .iter()
6599            .find(|export| export.name == "b")
6600            .expect("b export");
6601        let x_export = layout.entrypoints[&entrypoint]
6602            .exports
6603            .iter()
6604            .find(|export| export.name == "x")
6605            .expect("x export");
6606        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6607        assert_eq!(vars[a_export.slot.0], Value::Bool(true));
6608        assert_eq!(vars[b_export.slot.0], Value::Bool(true));
6609        assert_eq!(vars[x_export.slot.0], Value::Num(9.0));
6610        match &vars[c_export.slot.0] {
6611            Value::Cell(ca) => {
6612                assert_eq!(ca.rows, 1);
6613                assert_eq!(ca.cols, 5);
6614            }
6615            other => panic!("expected cell export, got {other:?}"),
6616        }
6617    }
6618
6619    #[test]
6620    fn compile_supports_cell_brace_linear_end_plus_k_growth_for_vectors() {
6621        let ast = runmat_parser::parse("c = {1, 2}; c{end+3} = 9; a = isempty(c{3}); x = c{5};")
6622            .expect("parse");
6623        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6624        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6625        let entrypoint = hir.assembly.entrypoints[0].id;
6626
6627        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6628        assert!(bytecode.instructions.iter().any(|instr| {
6629            matches!(
6630                instr,
6631                Instr::StoreIndexCell {
6632                    num_indices: 1,
6633                    end_offsets,
6634                    ..
6635                } if end_offsets == &vec![(0, 3)]
6636            )
6637        }));
6638        let layout = bytecode.layout.as_ref().expect("layout");
6639        let a_export = layout.entrypoints[&entrypoint]
6640            .exports
6641            .iter()
6642            .find(|export| export.name == "a")
6643            .expect("a export");
6644        let x_export = layout.entrypoints[&entrypoint]
6645            .exports
6646            .iter()
6647            .find(|export| export.name == "x")
6648            .expect("x export");
6649        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6650        assert_eq!(vars[a_export.slot.0], Value::Bool(true));
6651        assert_eq!(vars[x_export.slot.0], Value::Num(9.0));
6652    }
6653
6654    #[test]
6655    fn compile_linear_cell_growth_from_5_by_0_normalizes_to_row_vector() {
6656        let ast = runmat_parser::parse("c = cell(5,0); c{3} = 2; v = c{3};").expect("parse");
6657        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6658        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6659        let entrypoint = hir.assembly.entrypoints[0].id;
6660        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6661        let layout = bytecode.layout.as_ref().expect("layout");
6662        let c_export = layout.entrypoints[&entrypoint]
6663            .exports
6664            .iter()
6665            .find(|export| export.name == "c")
6666            .expect("c export");
6667        let v_export = layout.entrypoints[&entrypoint]
6668            .exports
6669            .iter()
6670            .find(|export| export.name == "v")
6671            .expect("v export");
6672        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6673        assert_eq!(vars[v_export.slot.0], Value::Num(2.0));
6674        match &vars[c_export.slot.0] {
6675            Value::Cell(ca) => {
6676                assert_eq!(ca.rows, 1);
6677                assert_eq!(ca.cols, 3);
6678            }
6679            other => panic!("expected cell export, got {other:?}"),
6680        }
6681    }
6682
6683    #[test]
6684    fn compile_linear_cell_growth_from_0_by_5_normalizes_to_row_vector() {
6685        let ast = runmat_parser::parse("c = cell(0,5); c{3} = 2; v = c{3};").expect("parse");
6686        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6687        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6688        let entrypoint = hir.assembly.entrypoints[0].id;
6689        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6690        let layout = bytecode.layout.as_ref().expect("layout");
6691        let c_export = layout.entrypoints[&entrypoint]
6692            .exports
6693            .iter()
6694            .find(|export| export.name == "c")
6695            .expect("c export");
6696        let v_export = layout.entrypoints[&entrypoint]
6697            .exports
6698            .iter()
6699            .find(|export| export.name == "v")
6700            .expect("v export");
6701        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6702        assert_eq!(vars[v_export.slot.0], Value::Num(2.0));
6703        match &vars[c_export.slot.0] {
6704            Value::Cell(ca) => {
6705                assert_eq!(ca.rows, 1);
6706                assert_eq!(ca.cols, 3);
6707            }
6708            other => panic!("expected cell export, got {other:?}"),
6709        }
6710    }
6711
6712    #[test]
6713    fn compile_rejects_cell_brace_end_plus_one_growth_for_matrix_linear_assignment() {
6714        let ast = runmat_parser::parse("c = {1, 2; 3, 4}; c{end+1} = 9;").expect("parse");
6715        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6716        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6717        let entrypoint = hir.assembly.entrypoints[0].id;
6718        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6719        let err = block_on(crate::interpret(&bytecode))
6720            .expect_err("matrix linear brace end+1 growth should be rejected");
6721        assert_eq!(err.identifier(), Some("RunMat:UnsupportedCellGrowth"));
6722    }
6723
6724    #[test]
6725    fn compile_supports_cell_brace_subscript_growth_with_empty_fillers() {
6726        let ast =
6727            runmat_parser::parse("c = {1, 2; 3, 4}; c{3,3} = 9; a = c{3,3}; b = isempty(c{2,3});")
6728                .expect("parse");
6729        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6730        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6731        let entrypoint = hir.assembly.entrypoints[0].id;
6732
6733        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6734        let layout = bytecode.layout.as_ref().expect("layout");
6735        let c_export = layout.entrypoints[&entrypoint]
6736            .exports
6737            .iter()
6738            .find(|export| export.name == "c")
6739            .expect("c export");
6740        let a_export = layout.entrypoints[&entrypoint]
6741            .exports
6742            .iter()
6743            .find(|export| export.name == "a")
6744            .expect("a export");
6745        let b_export = layout.entrypoints[&entrypoint]
6746            .exports
6747            .iter()
6748            .find(|export| export.name == "b")
6749            .expect("b export");
6750        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6751
6752        assert_eq!(vars[a_export.slot.0], Value::Num(9.0));
6753        assert_eq!(vars[b_export.slot.0], Value::Bool(true));
6754        match &vars[c_export.slot.0] {
6755            Value::Cell(ca) => {
6756                assert_eq!(ca.rows, 3);
6757                assert_eq!(ca.cols, 3);
6758            }
6759            other => panic!("expected cell export, got {other:?}"),
6760        }
6761    }
6762
6763    #[test]
6764    fn compile_supports_cell_brace_end_plus_one_subscript_growth() {
6765        let ast =
6766            runmat_parser::parse("c = {1, 2; 3, 4}; c{end,end+1} = 8; x = c{2,3};").expect("parse");
6767        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6768        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6769        let entrypoint = hir.assembly.entrypoints[0].id;
6770
6771        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6772        assert!(bytecode.instructions.iter().any(|instr| {
6773            matches!(
6774                instr,
6775                Instr::StoreIndexCell {
6776                    num_indices: 2,
6777                    end_offsets,
6778                    ..
6779                } if end_offsets.contains(&(1, 1))
6780            )
6781        }));
6782        let layout = bytecode.layout.as_ref().expect("layout");
6783        let x_export = layout.entrypoints[&entrypoint]
6784            .exports
6785            .iter()
6786            .find(|export| export.name == "x")
6787            .expect("x export");
6788        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6789        assert_eq!(vars[x_export.slot.0], Value::Num(8.0));
6790    }
6791
6792    #[test]
6793    fn compile_supports_mixed_cell_colon_expansion() {
6794        let ast = runmat_parser::parse("c = {1,2;3,4}; [a,b] = c{:,2}; z = a + b;").expect("parse");
6795        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6796        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6797        let entrypoint = hir.assembly.entrypoints[0].id;
6798
6799        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6800        let layout = bytecode.layout.as_ref().expect("layout");
6801        let z_export = layout.entrypoints[&entrypoint]
6802            .exports
6803            .iter()
6804            .find(|export| export.name == "z")
6805            .expect("z export");
6806
6807        assert!(bytecode.instructions.iter().any(|instr| matches!(
6808            instr,
6809            Instr::IndexCellExpand {
6810                num_indices,
6811                out_count,
6812                ..
6813            } if *num_indices == 2 && *out_count == 2
6814        )));
6815
6816        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6817        assert_eq!(vars[z_export.slot.0], Value::Num(6.0));
6818    }
6819
6820    #[test]
6821    fn compile_3d_slice_roundtrip_uses_slice_expr_paths() {
6822        let ast = runmat_parser::parse(
6823            r#"
6824            A = reshape([1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24], 3, 4, 2);
6825            S = A(1:2, 2:3, end);
6826            A(1:2, 2:3, end) = S;
6827        "#,
6828        )
6829        .expect("parse");
6830        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6831        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6832        let entrypoint = hir.assembly.entrypoints[0].id;
6833
6834        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6835        let mut saw_index_expr = false;
6836        let mut saw_store_expr = false;
6837        for instr in &bytecode.instructions {
6838            if let Instr::IndexSliceExpr {
6839                dims,
6840                end_mask,
6841                end_numeric_exprs,
6842                range_dims,
6843                ..
6844            } = instr
6845            {
6846                if *dims == 3 {
6847                    saw_index_expr = true;
6848                    assert_eq!(*end_mask, 0);
6849                    assert_eq!(end_numeric_exprs.len(), 1);
6850                    assert_eq!(range_dims, &vec![0, 1]);
6851                }
6852            }
6853            if let Instr::StoreSliceExpr {
6854                dims,
6855                numeric_count,
6856                colon_mask,
6857                end_mask,
6858                range_dims,
6859                range_has_step,
6860                end_numeric_exprs,
6861                ..
6862            } = instr
6863            {
6864                if *dims == 3 {
6865                    saw_store_expr = true;
6866                    assert_eq!(*numeric_count, 1);
6867                    assert_eq!(*colon_mask, 0);
6868                    assert_eq!(*end_mask, 0);
6869                    assert_eq!(range_dims, &vec![0, 1]);
6870                    assert_eq!(range_has_step, &vec![false, false]);
6871                    assert_eq!(end_numeric_exprs.len(), 1);
6872                }
6873            }
6874        }
6875        assert!(saw_index_expr);
6876        assert!(saw_store_expr);
6877
6878        let run = block_on(crate::interpret(&bytecode));
6879        assert!(
6880            run.is_ok(),
6881            "roundtrip script should interpret successfully: {run:?}"
6882        );
6883    }
6884
6885    #[test]
6886    fn compile_interprets_basic_if_statement() {
6887        let ast = runmat_parser::parse("if 1; x = 2; else; x = 3; end").expect("parse");
6888        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6889        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6890        let entrypoint = hir.assembly.entrypoints[0].id;
6891
6892        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6893        let layout = bytecode.layout.as_ref().expect("layout");
6894        let x_export = layout.entrypoints[&entrypoint]
6895            .exports
6896            .iter()
6897            .find(|export| export.name == "x")
6898            .expect("x export");
6899
6900        assert!(bytecode
6901            .instructions
6902            .iter()
6903            .any(|instr| matches!(instr, Instr::JumpIfFalse(_))));
6904
6905        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6906        assert_eq!(vars[x_export.slot.0], Value::Num(2.0));
6907    }
6908
6909    #[test]
6910    fn compile_interprets_basic_switch_statement() {
6911        let ast =
6912            runmat_parser::parse("switch 2; case 1; x = 1; case 2; x = 2; otherwise; x = 3; end")
6913                .expect("parse");
6914        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6915        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6916        let entrypoint = hir.assembly.entrypoints[0].id;
6917
6918        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6919        let layout = bytecode.layout.as_ref().expect("layout");
6920        let x_export = layout.entrypoints[&entrypoint]
6921            .exports
6922            .iter()
6923            .find(|export| export.name == "x")
6924            .expect("x export");
6925
6926        assert!(bytecode
6927            .instructions
6928            .iter()
6929            .any(|instr| matches!(instr, Instr::Equal)));
6930
6931        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6932        assert_eq!(vars[x_export.slot.0], Value::Num(2.0));
6933    }
6934
6935    #[test]
6936    fn compile_lowers_unreachable_terminator() {
6937        let ast = runmat_parser::parse("x = 1;").expect("parse");
6938        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
6939        let mut mir = lower_assembly(&hir.assembly).expect("lower MIR");
6940        let entrypoint = hir.assembly.entrypoints[0].id;
6941        let function = hir.assembly.entrypoints[0].target;
6942        let body = mir.bodies.get_mut(&function).expect("entry body");
6943        body.blocks.last_mut().expect("entry block").terminator.kind =
6944            MirTerminatorKind::Unreachable;
6945
6946        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6947
6948        assert!(bytecode
6949            .instructions
6950            .iter()
6951            .any(|instr| matches!(instr, Instr::Return)));
6952    }
6953
6954    #[test]
6955    fn compile_external_semantic_function_handle_keeps_identity() {
6956        let ast = runmat_parser::parse("h = @remote_inc; y = feval(h, 2);").expect("parse");
6957        let mut bound_functions = HashMap::new();
6958        bound_functions.insert("remote_inc".to_string(), FunctionId(9001));
6959        let context = LoweringContext::empty().with_bound_functions(&bound_functions);
6960        let hir = lower(&ast, &context).expect("lower HIR");
6961        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
6962        let entrypoint = hir.assembly.entrypoints[0].id;
6963
6964        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
6965        assert!(bytecode.instructions.iter().any(|instr| matches!(
6966            instr,
6967            Instr::CreateExternalBoundFunctionHandle(FunctionId(9001), name)
6968                if name == "remote_inc"
6969        )));
6970
6971        let _resolver_guard = runmat_runtime::user_functions::install_semantic_function_resolver(
6972            Some(Arc::new(|name| {
6973                if name == "remote_inc" {
6974                    Some(9001)
6975                } else {
6976                    None
6977                }
6978            })),
6979        );
6980        let _invoker_guard = runmat_runtime::user_functions::install_semantic_function_invoker(
6981            Some(Arc::new(|function, args, requested_outputs| {
6982                assert_eq!(function, 9001);
6983                assert_eq!(args, &[Value::Num(2.0)]);
6984                assert_eq!(requested_outputs, 1);
6985                Box::pin(async move { Ok(Value::Num(3.0)) })
6986            })),
6987        );
6988
6989        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
6990        assert!(vars
6991            .iter()
6992            .any(|value| matches!(value, Value::Num(n) if (*n - 3.0).abs() < 1e-12)));
6993    }
6994
6995    #[test]
6996    fn compile_external_semantic_direct_call_uses_host_invoker() {
6997        let ast = runmat_parser::parse("y = remote_inc(2);").expect("parse");
6998        let mut bound_functions = HashMap::new();
6999        bound_functions.insert("remote_inc".to_string(), FunctionId(9001));
7000        let context = LoweringContext::empty().with_bound_functions(&bound_functions);
7001        let hir = lower(&ast, &context).expect("lower HIR");
7002        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
7003        let entrypoint = hir.assembly.entrypoints[0].id;
7004
7005        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
7006        assert!(bytecode.instructions.iter().any(|instr| matches!(
7007            instr,
7008            Instr::CallFunctionMulti {
7009                identity: CallableIdentity::ExternalFunction {
7010                    function: FunctionId(9001),
7011                    ..
7012                },
7013                arg_count: 1,
7014                out_count: 1,
7015                ..
7016            }
7017        )));
7018
7019        let _resolver_guard = runmat_runtime::user_functions::install_semantic_function_resolver(
7020            Some(Arc::new(|name| {
7021                if name == "remote_inc" {
7022                    Some(9001)
7023                } else {
7024                    None
7025                }
7026            })),
7027        );
7028        let _invoker_guard = runmat_runtime::user_functions::install_semantic_function_invoker(
7029            Some(Arc::new(|function, args, requested_outputs| {
7030                assert_eq!(function, 9001);
7031                assert_eq!(args, &[Value::Num(2.0)]);
7032                assert_eq!(requested_outputs, 1);
7033                Box::pin(async move { Ok(Value::Num(3.0)) })
7034            })),
7035        );
7036
7037        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
7038        assert!(vars
7039            .iter()
7040            .any(|value| matches!(value, Value::Num(n) if (*n - 3.0).abs() < 1e-12)));
7041    }
7042
7043    #[test]
7044    fn compile_rejects_workspace_first_bound_function_fallback() {
7045        let ast = runmat_parser::parse("run(\"setup.m\"); y = remote_inc(2);").expect("parse");
7046        let mut bound_functions = HashMap::new();
7047        bound_functions.insert("remote_inc".to_string(), FunctionId(9001));
7048        let context = LoweringContext::empty().with_bound_functions(&bound_functions);
7049        let hir = lower(&ast, &context).expect("lower HIR");
7050        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
7051        let entrypoint = hir.assembly.entrypoints[0].id;
7052
7053        let err = compile(&hir.assembly, &mir, entrypoint)
7054            .expect_err("workspace-first bound function fallback should be rejected");
7055        assert_eq!(
7056            err.identifier.as_deref(),
7057            Some("RunMat:MirCallFallbackPolicyUnsupported")
7058        );
7059    }
7060
7061    #[test]
7062    fn compile_interprets_async_call_and_await_via_semantic_future_lane() {
7063        let source = "async function y = inc(x); y = x + 1; end; t = inc(2); z = await(t);";
7064        let ast = runmat_parser::parse(source).expect("parse");
7065        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
7066        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
7067        let entrypoint = hir.assembly.entrypoints[0].id;
7068
7069        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
7070        assert!(
7071            bytecode
7072                .instructions
7073                .iter()
7074                .any(|instr| matches!(instr, Instr::CreateSemanticFuture(FunctionId(_), 1, 1))),
7075            "expected async direct call lowering to create a semantic future descriptor"
7076        );
7077        let layout = bytecode.layout.as_ref().expect("layout");
7078        let z_export = layout.entrypoints[&entrypoint]
7079            .exports
7080            .iter()
7081            .find(|export| export.name == "z")
7082            .expect("z export");
7083
7084        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
7085        assert_eq!(vars[z_export.slot.0], Value::Num(3.0));
7086    }
7087
7088    #[test]
7089    fn compile_emits_explicit_spawn_instruction() {
7090        let source = "async function y = inc(x); y = x + 1; end; t = spawn(inc(2)); z = await(t);";
7091        let ast = runmat_parser::parse(source).expect("parse");
7092        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
7093        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
7094        let entrypoint = hir.assembly.entrypoints[0].id;
7095
7096        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
7097        assert!(
7098            bytecode
7099                .instructions
7100                .iter()
7101                .any(|instr| matches!(instr, Instr::Spawn)),
7102            "expected MIR spawn lowering to emit an explicit spawn instruction"
7103        );
7104        assert!(
7105            bytecode
7106                .instructions
7107                .iter()
7108                .any(|instr| matches!(instr, Instr::Await)),
7109            "expected MIR await lowering to emit an explicit await instruction"
7110        );
7111
7112        let layout = bytecode.layout.as_ref().expect("layout");
7113        let z_export = layout.entrypoints[&entrypoint]
7114            .exports
7115            .iter()
7116            .find(|export| export.name == "z")
7117            .expect("z export");
7118        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
7119        assert_eq!(vars[z_export.slot.0], Value::Num(3.0));
7120    }
7121
7122    #[test]
7123    fn compile_lowers_async_expansion_call_to_future_expand_instruction() {
7124        let source = "async function y = inc(x); y = x + 1; end; args = {2}; t = inc(args{:}); z = await(t);";
7125        let ast = runmat_parser::parse(source).expect("parse");
7126        let hir = lower(&ast, &LoweringContext::empty()).expect("lower HIR");
7127        let mir = lower_assembly(&hir.assembly).expect("lower MIR");
7128        let entrypoint = hir.assembly.entrypoints[0].id;
7129
7130        let bytecode = compile(&hir.assembly, &mir, entrypoint).expect("compile");
7131        assert!(
7132            bytecode.instructions.iter().any(|instr| matches!(
7133                instr,
7134                Instr::CreateSemanticFutureExpandMultiOutput(FunctionId(_), _, 1)
7135            )),
7136            "expected async expansion call lowering to create a semantic future expansion descriptor"
7137        );
7138        let layout = bytecode.layout.as_ref().expect("layout");
7139        let z_export = layout.entrypoints[&entrypoint]
7140            .exports
7141            .iter()
7142            .find(|export| export.name == "z")
7143            .expect("z export");
7144        let vars = block_on(crate::interpret(&bytecode)).expect("interpret");
7145        assert_eq!(vars[z_export.slot.0], Value::Num(3.0));
7146    }
7147}