Skip to main content

stryke/
vm.rs

1use std::collections::{HashMap, VecDeque};
2use std::io::{self, Write as IoWrite};
3use std::sync::Arc;
4
5use indexmap::IndexMap;
6use parking_lot::RwLock;
7use rayon::prelude::*;
8
9use caseless::default_case_fold_str;
10
11use crate::ast::{BinOp, Block, Expr, MatchArm, PerlTypeName, Sigil, SubSigParam};
12use crate::bytecode::{BuiltinId, Chunk, Op, RuntimeSubDecl, SpliceExprEntry};
13use crate::compiler::scalar_compound_op_from_byte;
14use crate::error::{ErrorKind, PerlError, PerlResult};
15use crate::interpreter::{
16    fold_preduce_init_step, merge_preduce_init_partials, preduce_init_fold_identity, Flow,
17    FlowOrError, Interpreter, WantarrayCtx,
18};
19use crate::perl_fs::read_file_text_perl_compat;
20use crate::pmap_progress::{FanProgress, PmapProgress};
21use crate::sort_fast::{sort_magic_cmp, SortBlockFast};
22use crate::value::{
23    perl_list_range_expand, PerlAsyncTask, PerlBarrier, PerlHeap, PerlSub, PerlValue,
24    PipelineInner, PipelineOp,
25};
26use parking_lot::Mutex;
27use std::sync::Barrier;
28
29/// Stable reference for empty-stack [`VM::peek`] (not a temporary `&PerlValue::UNDEF`).
30static PEEK_UNDEF: PerlValue = PerlValue::UNDEF;
31
32/// Immutable snapshot of [`VM`] pools for rayon workers (cheap `Arc` clones; no `&mut VM` in closures).
33struct ParallelBlockVmShared {
34    ops: Arc<Vec<Op>>,
35    names: Arc<Vec<String>>,
36    constants: Arc<Vec<PerlValue>>,
37    lines: Arc<Vec<usize>>,
38    sub_entries: Vec<(u16, usize, bool)>,
39    static_sub_calls: Vec<(usize, bool, u16)>,
40    blocks: Vec<Block>,
41    code_ref_sigs: Vec<Vec<SubSigParam>>,
42    block_bytecode_ranges: Vec<Option<(usize, usize)>>,
43    map_expr_bytecode_ranges: Vec<Option<(usize, usize)>>,
44    grep_expr_bytecode_ranges: Vec<Option<(usize, usize)>>,
45    regex_flip_flop_rhs_expr_bytecode_ranges: Vec<Option<(usize, usize)>>,
46    given_entries: Vec<(Expr, Block)>,
47    given_topic_bytecode_ranges: Vec<Option<(usize, usize)>>,
48    eval_timeout_entries: Vec<(Expr, Block)>,
49    eval_timeout_expr_bytecode_ranges: Vec<Option<(usize, usize)>>,
50    algebraic_match_entries: Vec<(Expr, Vec<MatchArm>)>,
51    algebraic_match_subject_bytecode_ranges: Vec<Option<(usize, usize)>>,
52    par_lines_entries: Vec<(Expr, Expr, Option<Expr>)>,
53    par_walk_entries: Vec<(Expr, Expr, Option<Expr>)>,
54    pwatch_entries: Vec<(Expr, Expr)>,
55    substr_four_arg_entries: Vec<(Expr, Expr, Option<Expr>, Expr)>,
56    keys_expr_entries: Vec<Expr>,
57    keys_expr_bytecode_ranges: Vec<Option<(usize, usize)>>,
58    map_expr_entries: Vec<Expr>,
59    grep_expr_entries: Vec<Expr>,
60    regex_flip_flop_rhs_expr_entries: Vec<Expr>,
61    values_expr_entries: Vec<Expr>,
62    values_expr_bytecode_ranges: Vec<Option<(usize, usize)>>,
63    delete_expr_entries: Vec<Expr>,
64    exists_expr_entries: Vec<Expr>,
65    push_expr_entries: Vec<(Expr, Vec<Expr>)>,
66    pop_expr_entries: Vec<Expr>,
67    shift_expr_entries: Vec<Expr>,
68    unshift_expr_entries: Vec<(Expr, Vec<Expr>)>,
69    splice_expr_entries: Vec<SpliceExprEntry>,
70    lvalues: Vec<Expr>,
71    ast_eval_exprs: Vec<Expr>,
72    format_decls: Vec<(String, Vec<String>)>,
73    use_overload_entries: Vec<Vec<(String, String)>>,
74    runtime_sub_decls: Arc<Vec<RuntimeSubDecl>>,
75    jit_sub_invoke_threshold: u32,
76    op_len_plus_one: usize,
77    static_sub_closure_subs: Vec<Option<Arc<PerlSub>>>,
78    sub_entry_by_name: HashMap<u16, (usize, bool)>,
79}
80
81impl ParallelBlockVmShared {
82    fn from_vm(vm: &VM<'_>) -> Self {
83        let n = vm.ops.len().saturating_add(1);
84        Self {
85            ops: Arc::clone(&vm.ops),
86            names: Arc::clone(&vm.names),
87            constants: Arc::clone(&vm.constants),
88            lines: Arc::clone(&vm.lines),
89            sub_entries: vm.sub_entries.clone(),
90            static_sub_calls: vm.static_sub_calls.clone(),
91            blocks: vm.blocks.clone(),
92            code_ref_sigs: vm.code_ref_sigs.clone(),
93            block_bytecode_ranges: vm.block_bytecode_ranges.clone(),
94            map_expr_bytecode_ranges: vm.map_expr_bytecode_ranges.clone(),
95            grep_expr_bytecode_ranges: vm.grep_expr_bytecode_ranges.clone(),
96            regex_flip_flop_rhs_expr_bytecode_ranges: vm
97                .regex_flip_flop_rhs_expr_bytecode_ranges
98                .clone(),
99            given_entries: vm.given_entries.clone(),
100            given_topic_bytecode_ranges: vm.given_topic_bytecode_ranges.clone(),
101            eval_timeout_entries: vm.eval_timeout_entries.clone(),
102            eval_timeout_expr_bytecode_ranges: vm.eval_timeout_expr_bytecode_ranges.clone(),
103            algebraic_match_entries: vm.algebraic_match_entries.clone(),
104            algebraic_match_subject_bytecode_ranges: vm
105                .algebraic_match_subject_bytecode_ranges
106                .clone(),
107            par_lines_entries: vm.par_lines_entries.clone(),
108            par_walk_entries: vm.par_walk_entries.clone(),
109            pwatch_entries: vm.pwatch_entries.clone(),
110            substr_four_arg_entries: vm.substr_four_arg_entries.clone(),
111            keys_expr_entries: vm.keys_expr_entries.clone(),
112            keys_expr_bytecode_ranges: vm.keys_expr_bytecode_ranges.clone(),
113            map_expr_entries: vm.map_expr_entries.clone(),
114            grep_expr_entries: vm.grep_expr_entries.clone(),
115            regex_flip_flop_rhs_expr_entries: vm.regex_flip_flop_rhs_expr_entries.clone(),
116            values_expr_entries: vm.values_expr_entries.clone(),
117            values_expr_bytecode_ranges: vm.values_expr_bytecode_ranges.clone(),
118            delete_expr_entries: vm.delete_expr_entries.clone(),
119            exists_expr_entries: vm.exists_expr_entries.clone(),
120            push_expr_entries: vm.push_expr_entries.clone(),
121            pop_expr_entries: vm.pop_expr_entries.clone(),
122            shift_expr_entries: vm.shift_expr_entries.clone(),
123            unshift_expr_entries: vm.unshift_expr_entries.clone(),
124            splice_expr_entries: vm.splice_expr_entries.clone(),
125            lvalues: vm.lvalues.clone(),
126            ast_eval_exprs: vm.ast_eval_exprs.clone(),
127            format_decls: vm.format_decls.clone(),
128            use_overload_entries: vm.use_overload_entries.clone(),
129            runtime_sub_decls: Arc::clone(&vm.runtime_sub_decls),
130            jit_sub_invoke_threshold: vm.jit_sub_invoke_threshold,
131            op_len_plus_one: n,
132            static_sub_closure_subs: vm.static_sub_closure_subs.clone(),
133            sub_entry_by_name: vm.sub_entry_by_name.clone(),
134        }
135    }
136
137    fn worker_vm<'a>(&self, interp: &'a mut Interpreter) -> VM<'a> {
138        let n = self.op_len_plus_one;
139        VM {
140            names: Arc::clone(&self.names),
141            constants: Arc::clone(&self.constants),
142            ops: Arc::clone(&self.ops),
143            lines: Arc::clone(&self.lines),
144            sub_entries: self.sub_entries.clone(),
145            static_sub_calls: self.static_sub_calls.clone(),
146            blocks: self.blocks.clone(),
147            code_ref_sigs: self.code_ref_sigs.clone(),
148            block_bytecode_ranges: self.block_bytecode_ranges.clone(),
149            map_expr_bytecode_ranges: self.map_expr_bytecode_ranges.clone(),
150            grep_expr_bytecode_ranges: self.grep_expr_bytecode_ranges.clone(),
151            regex_flip_flop_rhs_expr_bytecode_ranges: self
152                .regex_flip_flop_rhs_expr_bytecode_ranges
153                .clone(),
154            given_entries: self.given_entries.clone(),
155            given_topic_bytecode_ranges: self.given_topic_bytecode_ranges.clone(),
156            eval_timeout_entries: self.eval_timeout_entries.clone(),
157            eval_timeout_expr_bytecode_ranges: self.eval_timeout_expr_bytecode_ranges.clone(),
158            algebraic_match_entries: self.algebraic_match_entries.clone(),
159            algebraic_match_subject_bytecode_ranges: self
160                .algebraic_match_subject_bytecode_ranges
161                .clone(),
162            par_lines_entries: self.par_lines_entries.clone(),
163            par_walk_entries: self.par_walk_entries.clone(),
164            pwatch_entries: self.pwatch_entries.clone(),
165            substr_four_arg_entries: self.substr_four_arg_entries.clone(),
166            keys_expr_entries: self.keys_expr_entries.clone(),
167            keys_expr_bytecode_ranges: self.keys_expr_bytecode_ranges.clone(),
168            map_expr_entries: self.map_expr_entries.clone(),
169            grep_expr_entries: self.grep_expr_entries.clone(),
170            regex_flip_flop_rhs_expr_entries: self.regex_flip_flop_rhs_expr_entries.clone(),
171            values_expr_entries: self.values_expr_entries.clone(),
172            values_expr_bytecode_ranges: self.values_expr_bytecode_ranges.clone(),
173            delete_expr_entries: self.delete_expr_entries.clone(),
174            exists_expr_entries: self.exists_expr_entries.clone(),
175            push_expr_entries: self.push_expr_entries.clone(),
176            pop_expr_entries: self.pop_expr_entries.clone(),
177            shift_expr_entries: self.shift_expr_entries.clone(),
178            unshift_expr_entries: self.unshift_expr_entries.clone(),
179            splice_expr_entries: self.splice_expr_entries.clone(),
180            lvalues: self.lvalues.clone(),
181            ast_eval_exprs: self.ast_eval_exprs.clone(),
182            format_decls: self.format_decls.clone(),
183            use_overload_entries: self.use_overload_entries.clone(),
184            runtime_sub_decls: Arc::clone(&self.runtime_sub_decls),
185            ip: 0,
186            stack: Vec::with_capacity(256),
187            call_stack: Vec::with_capacity(32),
188            wantarray_stack: Vec::with_capacity(8),
189            interp,
190            jit_enabled: false,
191            sub_jit_skip_linear: vec![false; n],
192            sub_jit_skip_block: vec![false; n],
193            sub_entry_at_ip: {
194                let mut v = vec![false; n];
195                for (_, e, _) in &self.sub_entries {
196                    if *e < v.len() {
197                        v[*e] = true;
198                    }
199                }
200                v
201            },
202            sub_entry_invoke_count: vec![0; n],
203            jit_sub_invoke_threshold: self.jit_sub_invoke_threshold,
204            jit_buf_slot: Vec::new(),
205            jit_buf_plain: Vec::new(),
206            jit_buf_arg: Vec::new(),
207            jit_trampoline_out: None,
208            jit_trampoline_depth: 0,
209            halt: false,
210            try_stack: Vec::new(),
211            pending_catch_error: None,
212            exit_main_dispatch: false,
213            exit_main_dispatch_value: None,
214            static_sub_closure_subs: self.static_sub_closure_subs.clone(),
215            sub_entry_by_name: self.sub_entry_by_name.clone(),
216            block_region_mode: false,
217            block_region_end: 0,
218            block_region_return: None,
219        }
220    }
221}
222
223#[inline]
224fn vm_interp_result(r: Result<PerlValue, FlowOrError>, line: usize) -> PerlResult<PerlValue> {
225    match r {
226        Ok(v) => Ok(v),
227        Err(FlowOrError::Error(e)) => Err(e),
228        Err(FlowOrError::Flow(_)) => Err(PerlError::runtime(
229            "unexpected control flow in tree-assisted opcode",
230            line,
231        )),
232    }
233}
234
235/// Saved state for `try { } catch (…) { } finally { }`.
236/// Jump targets live in [`Op::TryPush`] and are patched after emission; we only store the op index.
237#[derive(Debug, Clone)]
238pub(crate) struct TryFrame {
239    pub(crate) try_push_op_idx: usize,
240}
241
242/// Saved state when entering a function call.
243#[derive(Debug)]
244struct CallFrame {
245    return_ip: usize,
246    stack_base: usize,
247    scope_depth: usize,
248    saved_wantarray: WantarrayCtx,
249    /// [`stryke_jit_call_sub`] — no bytecode resume; result stored in [`VM::jit_trampoline_out`].
250    jit_trampoline_return: bool,
251    /// Synthetic frame for [`Op::BlockReturnValue`] (`map`/`grep`/`sort` block bytecode), paired with
252    /// `scope_push_hook` at [`VM::run_block_region`] entry (not a sub call; no closure capture).
253    block_region: bool,
254    /// Wall-clock start for [`crate::profiler::Profiler::exit_sub`] (paired with `enter_sub` on `Call`).
255    sub_profiler_start: Option<std::time::Instant>,
256}
257
258/// Stack-based bytecode virtual machine.
259pub struct VM<'a> {
260    /// Shared with parallel workers via [`Self::new_parallel_worker`] (cheap `Arc` clones).
261    names: Arc<Vec<String>>,
262    constants: Arc<Vec<PerlValue>>,
263    ops: Arc<Vec<Op>>,
264    lines: Arc<Vec<usize>>,
265    sub_entries: Vec<(u16, usize, bool)>,
266    /// See [`Chunk::static_sub_calls`] (`Op::CallStaticSubId`).
267    static_sub_calls: Vec<(usize, bool, u16)>,
268    blocks: Vec<Block>,
269    code_ref_sigs: Vec<Vec<SubSigParam>>,
270    /// Optional `ops[start..end]` lowering for [`Self::blocks`] (see [`Chunk::block_bytecode_ranges`]).
271    block_bytecode_ranges: Vec<Option<(usize, usize)>>,
272    /// Optional lowering for [`Chunk::map_expr_entries`] (see [`Chunk::map_expr_bytecode_ranges`]).
273    map_expr_bytecode_ranges: Vec<Option<(usize, usize)>>,
274    /// Optional lowering for [`Chunk::grep_expr_entries`] (see [`Chunk::grep_expr_bytecode_ranges`]).
275    grep_expr_bytecode_ranges: Vec<Option<(usize, usize)>>,
276    given_entries: Vec<(Expr, Block)>,
277    given_topic_bytecode_ranges: Vec<Option<(usize, usize)>>,
278    eval_timeout_entries: Vec<(Expr, Block)>,
279    eval_timeout_expr_bytecode_ranges: Vec<Option<(usize, usize)>>,
280    algebraic_match_entries: Vec<(Expr, Vec<MatchArm>)>,
281    algebraic_match_subject_bytecode_ranges: Vec<Option<(usize, usize)>>,
282    par_lines_entries: Vec<(Expr, Expr, Option<Expr>)>,
283    par_walk_entries: Vec<(Expr, Expr, Option<Expr>)>,
284    pwatch_entries: Vec<(Expr, Expr)>,
285    substr_four_arg_entries: Vec<(Expr, Expr, Option<Expr>, Expr)>,
286    keys_expr_entries: Vec<Expr>,
287    keys_expr_bytecode_ranges: Vec<Option<(usize, usize)>>,
288    map_expr_entries: Vec<Expr>,
289    grep_expr_entries: Vec<Expr>,
290    regex_flip_flop_rhs_expr_entries: Vec<Expr>,
291    regex_flip_flop_rhs_expr_bytecode_ranges: Vec<Option<(usize, usize)>>,
292    values_expr_entries: Vec<Expr>,
293    values_expr_bytecode_ranges: Vec<Option<(usize, usize)>>,
294    delete_expr_entries: Vec<Expr>,
295    exists_expr_entries: Vec<Expr>,
296    push_expr_entries: Vec<(Expr, Vec<Expr>)>,
297    pop_expr_entries: Vec<Expr>,
298    shift_expr_entries: Vec<Expr>,
299    unshift_expr_entries: Vec<(Expr, Vec<Expr>)>,
300    splice_expr_entries: Vec<SpliceExprEntry>,
301    lvalues: Vec<Expr>,
302    ast_eval_exprs: Vec<Expr>,
303    format_decls: Vec<(String, Vec<String>)>,
304    use_overload_entries: Vec<Vec<(String, String)>>,
305    runtime_sub_decls: Arc<Vec<RuntimeSubDecl>>,
306    pub(crate) ip: usize,
307    stack: Vec<PerlValue>,
308    call_stack: Vec<CallFrame>,
309    /// Paired with [`Op::WantarrayPush`] / [`Op::WantarrayPop`] (e.g. `splice` list vs scalar return).
310    wantarray_stack: Vec<WantarrayCtx>,
311    interp: &'a mut Interpreter,
312    /// When `false`, [`VM::execute`] skips Cranelift JIT (linear, block, and subroutine linear) and
313    /// uses only the opcode interpreter. Default `true`.
314    jit_enabled: bool,
315    /// `sub_jit_skip_linear[ip]` — true when linear sub-JIT cannot apply (control flow / calls).
316    /// Indexed by IP for O(1) lookup instead of hashing (recursive subs like fib hit this millions of times).
317    sub_jit_skip_linear: Vec<bool>,
318    /// `sub_jit_skip_block[ip]` — true when block sub-JIT cannot apply.
319    sub_jit_skip_block: Vec<bool>,
320    /// `sub_entry_at_ip[ip]` — faster than hashing on every opcode (recursive subs dispatch millions of ops).
321    sub_entry_at_ip: Vec<bool>,
322    /// Invocations per sub-entry IP (tiered JIT: interpreter until count exceeds threshold).
323    sub_entry_invoke_count: Vec<u32>,
324    /// Minimum invocations before attempting subroutine JIT. Override with `STRYKE_JIT_SUB_INVOKES` (default 50).
325    jit_sub_invoke_threshold: u32,
326    /// Reused `i64` tables for sub-JIT / top-level JIT attempts (avoids `vec![0; n]` on every try).
327    jit_buf_slot: Vec<i64>,
328    jit_buf_plain: Vec<i64>,
329    jit_buf_arg: Vec<i64>,
330    /// Set when running [`VM::jit_trampoline_run_sub`]; [`Op::ReturnValue`] stores here and exits dispatch.
331    jit_trampoline_out: Option<PerlValue>,
332    /// Nesting depth for [`Self::jit_trampoline_run_sub`]; dispatch breaks on [`Self::jit_trampoline_out`] only when `> 0`.
333    jit_trampoline_depth: u32,
334    /// Set by [`Op::Halt`]; outer loop exits after handling [`Self::try_recover_from_exception`].
335    halt: bool,
336    /// Stack of active `try` regions (LIFO).
337    try_stack: Vec<TryFrame>,
338    /// Error message for the next [`Op::CatchReceive`] (set before jumping to `catch_ip`).
339    pub(crate) pending_catch_error: Option<String>,
340    /// [`Op::Return`] / [`Op::ReturnValue`] with no caller frame: exit the main dispatch loop (was `break`).
341    exit_main_dispatch: bool,
342    /// Top-level [`Op::ReturnValue`] with no frame: value for implicit return (was `last = val; break`).
343    exit_main_dispatch_value: Option<PerlValue>,
344    /// [`Chunk::static_sub_calls`] index → pre-resolved [`PerlSub`] for closure restore (stash key lookup once at VM build).
345    static_sub_closure_subs: Vec<Option<Arc<PerlSub>>>,
346    /// O(1) [`Chunk::sub_entries`] lookup (same first-wins semantics as the old linear scan).
347    sub_entry_by_name: HashMap<u16, (usize, bool)>,
348    /// When executing [`Chunk::block_bytecode_ranges`] via [`Self::run_block_region`].
349    block_region_mode: bool,
350    block_region_end: usize,
351    block_region_return: Option<PerlValue>,
352}
353
354impl<'a> VM<'a> {
355    pub fn new(chunk: &Chunk, interp: &'a mut Interpreter) -> Self {
356        let static_sub_closure_subs: Vec<Option<Arc<PerlSub>>> = chunk
357            .static_sub_calls
358            .iter()
359            .map(|(_, _, name_idx)| {
360                let nm = chunk.names[*name_idx as usize].as_str();
361                interp.subs.get(nm).cloned()
362            })
363            .collect();
364        let mut sub_entry_by_name = HashMap::with_capacity(chunk.sub_entries.len());
365        for &(n, ip, sa) in &chunk.sub_entries {
366            sub_entry_by_name.entry(n).or_insert((ip, sa));
367        }
368        Self {
369            names: Arc::new(chunk.names.clone()),
370            constants: Arc::new(chunk.constants.clone()),
371            ops: Arc::new(chunk.ops.clone()),
372            lines: Arc::new(chunk.lines.clone()),
373            sub_entries: chunk.sub_entries.clone(),
374            static_sub_calls: chunk.static_sub_calls.clone(),
375            blocks: chunk.blocks.clone(),
376            code_ref_sigs: chunk.code_ref_sigs.clone(),
377            block_bytecode_ranges: chunk.block_bytecode_ranges.clone(),
378            map_expr_bytecode_ranges: chunk.map_expr_bytecode_ranges.clone(),
379            grep_expr_bytecode_ranges: chunk.grep_expr_bytecode_ranges.clone(),
380            regex_flip_flop_rhs_expr_bytecode_ranges: chunk
381                .regex_flip_flop_rhs_expr_bytecode_ranges
382                .clone(),
383            given_entries: chunk.given_entries.clone(),
384            given_topic_bytecode_ranges: chunk.given_topic_bytecode_ranges.clone(),
385            eval_timeout_entries: chunk.eval_timeout_entries.clone(),
386            eval_timeout_expr_bytecode_ranges: chunk.eval_timeout_expr_bytecode_ranges.clone(),
387            algebraic_match_entries: chunk.algebraic_match_entries.clone(),
388            algebraic_match_subject_bytecode_ranges: chunk
389                .algebraic_match_subject_bytecode_ranges
390                .clone(),
391            par_lines_entries: chunk.par_lines_entries.clone(),
392            par_walk_entries: chunk.par_walk_entries.clone(),
393            pwatch_entries: chunk.pwatch_entries.clone(),
394            substr_four_arg_entries: chunk.substr_four_arg_entries.clone(),
395            keys_expr_entries: chunk.keys_expr_entries.clone(),
396            keys_expr_bytecode_ranges: chunk.keys_expr_bytecode_ranges.clone(),
397            map_expr_entries: chunk.map_expr_entries.clone(),
398            grep_expr_entries: chunk.grep_expr_entries.clone(),
399            regex_flip_flop_rhs_expr_entries: chunk.regex_flip_flop_rhs_expr_entries.clone(),
400            values_expr_entries: chunk.values_expr_entries.clone(),
401            values_expr_bytecode_ranges: chunk.values_expr_bytecode_ranges.clone(),
402            delete_expr_entries: chunk.delete_expr_entries.clone(),
403            exists_expr_entries: chunk.exists_expr_entries.clone(),
404            push_expr_entries: chunk.push_expr_entries.clone(),
405            pop_expr_entries: chunk.pop_expr_entries.clone(),
406            shift_expr_entries: chunk.shift_expr_entries.clone(),
407            unshift_expr_entries: chunk.unshift_expr_entries.clone(),
408            splice_expr_entries: chunk.splice_expr_entries.clone(),
409            lvalues: chunk.lvalues.clone(),
410            ast_eval_exprs: chunk.ast_eval_exprs.clone(),
411            format_decls: chunk.format_decls.clone(),
412            use_overload_entries: chunk.use_overload_entries.clone(),
413            runtime_sub_decls: Arc::new(chunk.runtime_sub_decls.clone()),
414            ip: 0,
415            stack: Vec::with_capacity(256),
416            call_stack: Vec::with_capacity(32),
417            wantarray_stack: Vec::with_capacity(8),
418            interp,
419            jit_enabled: true,
420            sub_jit_skip_linear: vec![false; chunk.ops.len().saturating_add(1)],
421            sub_jit_skip_block: vec![false; chunk.ops.len().saturating_add(1)],
422            sub_entry_at_ip: {
423                let mut v = vec![false; chunk.ops.len().saturating_add(1)];
424                for (_, e, _) in &chunk.sub_entries {
425                    if *e < v.len() {
426                        v[*e] = true;
427                    }
428                }
429                v
430            },
431            sub_entry_invoke_count: vec![0; chunk.ops.len().saturating_add(1)],
432            jit_sub_invoke_threshold: std::env::var("STRYKE_JIT_SUB_INVOKES")
433                .ok()
434                .and_then(|s| s.parse().ok())
435                .unwrap_or(50),
436            jit_buf_slot: Vec::new(),
437            jit_buf_plain: Vec::new(),
438            jit_buf_arg: Vec::new(),
439            jit_trampoline_out: None,
440            jit_trampoline_depth: 0,
441            halt: false,
442            try_stack: Vec::new(),
443            pending_catch_error: None,
444            exit_main_dispatch: false,
445            exit_main_dispatch_value: None,
446            static_sub_closure_subs,
447            sub_entry_by_name,
448            block_region_mode: false,
449            block_region_end: 0,
450            block_region_return: None,
451        }
452    }
453
454    /// Pop a synthetic [`CallFrame::block_region`] frame if dispatch exited before
455    /// [`Op::BlockReturnValue`] (error or fallthrough), restoring stack and scope.
456    fn unwind_stale_block_region_frame(&mut self) {
457        if let Some(frame) = self.call_stack.pop() {
458            if frame.block_region {
459                self.interp.wantarray_kind = frame.saved_wantarray;
460                self.stack.truncate(frame.stack_base);
461                self.interp.pop_scope_to_depth(frame.scope_depth);
462            } else {
463                self.call_stack.push(frame);
464            }
465        }
466    }
467
468    /// Run `ops[start..end]` (exclusive) for a compiled `map`/`grep`/`sort` block body.
469    ///
470    /// Matches [`Interpreter::exec_block`]: `$_` / `$a` / `$b` are set in the caller before each
471    /// iteration; then one block-local scope frame is pushed (no closure capture) and the body runs
472    /// inline. [`Op::BlockReturnValue`] unwinds that frame via [`Self::unwind_stale_block_region_frame`]
473    /// on error paths here.
474    fn run_block_region(
475        &mut self,
476        start: usize,
477        end: usize,
478        op_count: &mut u64,
479    ) -> PerlResult<PerlValue> {
480        let resume_ip = self.ip;
481        let saved_mode = self.block_region_mode;
482        let saved_end = self.block_region_end;
483        let saved_ret = self.block_region_return.take();
484
485        let scope_depth_before = self.interp.scope.depth();
486        let saved_wa = self.interp.wantarray_kind;
487
488        self.call_stack.push(CallFrame {
489            return_ip: 0,
490            stack_base: self.stack.len(),
491            scope_depth: scope_depth_before,
492            saved_wantarray: saved_wa,
493            jit_trampoline_return: false,
494            block_region: true,
495            sub_profiler_start: None,
496        });
497        self.interp.scope_push_hook();
498        self.interp.wantarray_kind = WantarrayCtx::Scalar;
499        self.ip = start;
500        self.block_region_mode = true;
501        self.block_region_end = end;
502        self.block_region_return = None;
503
504        let r = self.run_main_dispatch_loop(PerlValue::UNDEF, op_count, false);
505        let out = self.block_region_return.take();
506
507        self.block_region_return = saved_ret;
508        self.block_region_mode = saved_mode;
509        self.block_region_end = saved_end;
510        self.ip = resume_ip;
511
512        match r {
513            Ok(_) => {
514                if let Some(val) = out {
515                    Ok(val)
516                } else {
517                    self.unwind_stale_block_region_frame();
518                    Err(PerlError::runtime(
519                        "block bytecode region did not finish with BlockReturnValue",
520                        self.line(),
521                    ))
522                }
523            }
524            Err(e) => {
525                self.unwind_stale_block_region_frame();
526                Err(e)
527            }
528        }
529    }
530
531    #[inline]
532    fn extend_map_outputs(dst: &mut Vec<PerlValue>, val: PerlValue, peel_array_ref: bool) {
533        dst.extend(val.map_flatten_outputs(peel_array_ref));
534    }
535
536    fn map_with_block_common(
537        &mut self,
538        list: Vec<PerlValue>,
539        block_idx: u16,
540        peel_array_ref: bool,
541        op_count: &mut u64,
542    ) -> PerlResult<()> {
543        if list.len() == 1 {
544            if let Some(p) = list[0].as_pipeline() {
545                if peel_array_ref {
546                    return Err(PerlError::runtime(
547                        "flat_map onto a pipeline value is not supported in this form — use a pipeline ->map stage",
548                        self.line(),
549                    ));
550                }
551                let idx = block_idx as usize;
552                let sub = self.interp.anon_coderef_from_block(&self.blocks[idx]);
553                let line = self.line();
554                self.interp.pipeline_push(&p, PipelineOp::Map(sub), line)?;
555                self.push(PerlValue::pipeline(Arc::clone(&p)));
556                return Ok(());
557            }
558        }
559        let idx = block_idx as usize;
560        // map's BLOCK is list context. The shared block bytecode region is compiled with a
561        // scalar-context tail (grep/sort consumers need that), so when the block's tail is
562        // list-sensitive (`($_, $_*10)`, `1..$_`, `reverse …`, an array variable, …) fall
563        // back to the interpreter's list-tail [`Interpreter::exec_block_with_tail`]. For
564        // plain scalar tails (`$_ * 2`, `f($_)`, string ops) the bytecode region produces
565        // the same value in either context, so keep using it for speed.
566        let block_tail_is_list_sensitive = self
567            .blocks
568            .get(idx)
569            .and_then(|b| b.last())
570            .map(|stmt| match &stmt.kind {
571                crate::ast::StmtKind::Expression(expr) => {
572                    crate::compiler::expr_tail_is_list_sensitive(expr)
573                }
574                _ => true,
575            })
576            .unwrap_or(true);
577        if !block_tail_is_list_sensitive {
578            if let Some(&(start, end)) =
579                self.block_bytecode_ranges.get(idx).and_then(|r| r.as_ref())
580            {
581                let mut result = Vec::new();
582                for item in list {
583                    self.interp.scope.set_topic(item);
584                    let val = self.run_block_region(start, end, op_count)?;
585                    Self::extend_map_outputs(&mut result, val, peel_array_ref);
586                }
587                self.push(PerlValue::array(result));
588                return Ok(());
589            }
590        }
591        let block = self.blocks[idx].clone();
592        let mut result = Vec::new();
593        for item in list {
594            self.interp.scope.set_topic(item);
595            match self.interp.exec_block_with_tail(&block, WantarrayCtx::List) {
596                Ok(val) => Self::extend_map_outputs(&mut result, val, peel_array_ref),
597                Err(FlowOrError::Error(e)) => return Err(e),
598                Err(_) => {}
599            }
600        }
601        self.push(PerlValue::array(result));
602        Ok(())
603    }
604
605    fn map_with_expr_common(
606        &mut self,
607        list: Vec<PerlValue>,
608        expr_idx: u16,
609        peel_array_ref: bool,
610        op_count: &mut u64,
611    ) -> PerlResult<()> {
612        let idx = expr_idx as usize;
613        if let Some(&(start, end)) = self
614            .map_expr_bytecode_ranges
615            .get(idx)
616            .and_then(|r| r.as_ref())
617        {
618            let mut result = Vec::new();
619            for item in list {
620                self.interp.scope.set_topic(item);
621                let val = self.run_block_region(start, end, op_count)?;
622                Self::extend_map_outputs(&mut result, val, peel_array_ref);
623            }
624            self.push(PerlValue::array(result));
625        } else {
626            let e = &self.map_expr_entries[idx];
627            let mut result = Vec::new();
628            for item in list {
629                self.interp.scope.set_topic(item);
630                let val = vm_interp_result(
631                    self.interp.eval_expr_ctx(e, WantarrayCtx::List),
632                    self.line(),
633                )?;
634                Self::extend_map_outputs(&mut result, val, peel_array_ref);
635            }
636            self.push(PerlValue::array(result));
637        }
638        Ok(())
639    }
640
641    /// Consecutive groups: key from block with `$_`; keys compared with [`PerlValue::str_eq`].
642    fn chunk_by_with_block_common(
643        &mut self,
644        list: Vec<PerlValue>,
645        block_idx: u16,
646        op_count: &mut u64,
647    ) -> PerlResult<()> {
648        if list.is_empty() {
649            self.push(PerlValue::array(vec![]));
650            return Ok(());
651        }
652        let idx = block_idx as usize;
653        let mut chunks: Vec<PerlValue> = Vec::new();
654        let mut run: Vec<PerlValue> = Vec::new();
655        let mut prev_key: Option<PerlValue> = None;
656
657        let eval_key =
658            |vm: &mut VM, item: PerlValue, op_count: &mut u64| -> PerlResult<PerlValue> {
659                vm.interp.scope.set_topic(item);
660                if let Some(&(start, end)) =
661                    vm.block_bytecode_ranges.get(idx).and_then(|r| r.as_ref())
662                {
663                    vm.run_block_region(start, end, op_count)
664                } else {
665                    let block = vm.blocks[idx].clone();
666                    match vm.interp.exec_block(&block) {
667                        Ok(val) => Ok(val),
668                        Err(FlowOrError::Error(e)) => Err(e),
669                        Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
670                        Err(_) => Ok(PerlValue::UNDEF),
671                    }
672                }
673            };
674
675        for item in list {
676            let key = eval_key(self, item.clone(), op_count)?;
677            match &prev_key {
678                None => {
679                    run.push(item);
680                    prev_key = Some(key);
681                }
682                Some(pk) => {
683                    if key.str_eq(pk) {
684                        run.push(item);
685                    } else {
686                        chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(std::mem::take(
687                            &mut run,
688                        )))));
689                        run.push(item);
690                        prev_key = Some(key);
691                    }
692                }
693            }
694        }
695        if !run.is_empty() {
696            chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(run))));
697        }
698        self.push(PerlValue::array(chunks));
699        Ok(())
700    }
701
702    fn chunk_by_with_expr_common(
703        &mut self,
704        list: Vec<PerlValue>,
705        expr_idx: u16,
706        op_count: &mut u64,
707    ) -> PerlResult<()> {
708        if list.is_empty() {
709            self.push(PerlValue::array(vec![]));
710            return Ok(());
711        }
712        let idx = expr_idx as usize;
713        let mut chunks: Vec<PerlValue> = Vec::new();
714        let mut run: Vec<PerlValue> = Vec::new();
715        let mut prev_key: Option<PerlValue> = None;
716        for item in list {
717            self.interp.scope.set_topic(item.clone());
718            let key = if let Some(&(start, end)) = self
719                .map_expr_bytecode_ranges
720                .get(idx)
721                .and_then(|r| r.as_ref())
722            {
723                self.run_block_region(start, end, op_count)?
724            } else {
725                let e = &self.map_expr_entries[idx];
726                vm_interp_result(
727                    self.interp.eval_expr_ctx(e, WantarrayCtx::Scalar),
728                    self.line(),
729                )?
730            };
731            match &prev_key {
732                None => {
733                    run.push(item);
734                    prev_key = Some(key);
735                }
736                Some(pk) => {
737                    if key.str_eq(pk) {
738                        run.push(item);
739                    } else {
740                        chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(std::mem::take(
741                            &mut run,
742                        )))));
743                        run.push(item);
744                        prev_key = Some(key);
745                    }
746                }
747            }
748        }
749        if !run.is_empty() {
750            chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(run))));
751        }
752        self.push(PerlValue::array(chunks));
753        Ok(())
754    }
755
756    #[inline]
757    fn sub_jit_skip_linear_test(&self, ip: usize) -> bool {
758        self.sub_jit_skip_linear.get(ip).copied().unwrap_or(false)
759    }
760
761    #[inline]
762    fn sub_jit_skip_linear_mark(&mut self, ip: usize) {
763        if ip >= self.sub_jit_skip_linear.len() {
764            self.sub_jit_skip_linear.resize(ip + 1, false);
765        }
766        self.sub_jit_skip_linear[ip] = true;
767    }
768
769    #[inline]
770    fn sub_jit_skip_block_test(&self, ip: usize) -> bool {
771        self.sub_jit_skip_block.get(ip).copied().unwrap_or(false)
772    }
773
774    #[inline]
775    fn sub_jit_skip_block_mark(&mut self, ip: usize) {
776        if ip >= self.sub_jit_skip_block.len() {
777            self.sub_jit_skip_block.resize(ip + 1, false);
778        }
779        self.sub_jit_skip_block[ip] = true;
780    }
781
782    /// Enable or disable Cranelift JIT for this execution. Disabling skips compilation and buffer
783    /// prefetch for JIT paths (pure interpreter).
784    pub fn set_jit_enabled(&mut self, enabled: bool) {
785        self.jit_enabled = enabled;
786    }
787
788    #[inline]
789    fn push(&mut self, val: PerlValue) {
790        self.stack.push(val);
791    }
792
793    #[inline]
794    fn pop(&mut self) -> PerlValue {
795        self.stack.pop().unwrap_or(PerlValue::UNDEF)
796    }
797
798    /// Convert a name-based binding ref (`\@array`, `\%hash`, `\$scalar`) into a
799    /// real `Arc`-based ref by snapshotting the current scope data.  This must be
800    /// called before the declaring scope is destroyed (e.g. on function return)
801    /// so the ref survives scope exit — matching Perl 5's refcount semantics.
802    fn resolve_binding_ref(&self, val: PerlValue) -> PerlValue {
803        if let Some(name) = val.as_array_binding_name() {
804            let data = self.interp.scope.get_array(&name);
805            return PerlValue::array_ref(Arc::new(RwLock::new(data)));
806        }
807        if let Some(name) = val.as_hash_binding_name() {
808            let data = self.interp.scope.get_hash(&name);
809            return PerlValue::hash_ref(Arc::new(RwLock::new(data)));
810        }
811        if let Some(name) = val.as_scalar_binding_name() {
812            let data = self.interp.scope.get_scalar(&name);
813            return PerlValue::scalar_ref(Arc::new(RwLock::new(data)));
814        }
815        val
816    }
817
818    /// Pop `n` array-slice index specs (TOS = last spec). Each spec is a scalar index or an array
819    /// of indices (list-context `..`, `qw/.../`, parenthesized list), matching
820    /// [`crate::compiler::Compiler::compile_array_slice_index_expr`]. Returns flattened indices in
821    /// source order (first spec’s indices first).
822    fn pop_flattened_array_slice_specs(&mut self, n: usize) -> Vec<i64> {
823        let mut chunks: Vec<Vec<i64>> = Vec::with_capacity(n);
824        for _ in 0..n {
825            let spec = self.pop();
826            let mut flat = Vec::new();
827            if let Some(av) = spec.as_array_vec() {
828                for pv in av.iter() {
829                    flat.push(pv.to_int());
830                }
831            } else {
832                flat.push(spec.to_int());
833            }
834            chunks.push(flat);
835        }
836        chunks.reverse();
837        chunks.into_iter().flatten().collect()
838    }
839
840    /// Call operands are pushed so the rightmost syntactic argument is on top. Restore
841    /// left-to-right order, then flatten list-valued operands (`qw/.../`, list literals, hashes)
842    /// into successive scalars — matching Perl's argument list for simple calls. Reversing after
843    /// flattening would incorrectly reverse elements inside expanded lists.
844    fn pop_call_operands_flattened(&mut self, argc: usize) -> Vec<PerlValue> {
845        let mut slots = Vec::with_capacity(argc);
846        for _ in 0..argc {
847            slots.push(self.pop());
848        }
849        slots.reverse();
850        let mut out = Vec::new();
851        for v in slots {
852            if let Some(items) = v.as_array_vec() {
853                out.extend(items);
854            } else if let Some(h) = v.as_hash_map() {
855                for (k, val) in h {
856                    out.push(PerlValue::string(k));
857                    out.push(val);
858                }
859            } else {
860                out.push(v);
861            }
862        }
863        out
864    }
865
866    /// Like [`Self::pop_call_operands_flattened`], but each syntactic argument stays one
867    /// [`PerlValue`] (`zip` / `mesh` need full lists per operand, not Perl's flattened `@_`).
868    fn pop_call_operands_preserved(&mut self, argc: usize) -> Vec<PerlValue> {
869        let mut slots = Vec::with_capacity(argc);
870        for _ in 0..argc {
871            slots.push(self.pop());
872        }
873        slots.reverse();
874        slots
875    }
876
877    #[inline]
878    fn call_preserve_operand_arrays(name: &str) -> bool {
879        matches!(
880            name,
881            "zip"
882                | "List::Util::zip"
883                | "List::Util::zip_longest"
884                | "List::Util::zip_shortest"
885                | "List::Util::mesh"
886                | "List::Util::mesh_longest"
887                | "List::Util::mesh_shortest"
888                | "take"
889                | "head"
890                | "tail"
891                | "drop"
892                | "List::Util::head"
893                | "List::Util::tail"
894        )
895    }
896
897    fn flatten_array_slice_specs_ordered_values(
898        &self,
899        specs: &[PerlValue],
900    ) -> Result<Vec<i64>, PerlError> {
901        let mut out = Vec::new();
902        for spec in specs {
903            if let Some(av) = spec.as_array_vec() {
904                for pv in av.iter() {
905                    out.push(pv.to_int());
906                }
907            } else {
908                out.push(spec.to_int());
909            }
910        }
911        Ok(out)
912    }
913
914    /// Hash `{…}` slice key slots in source order (each slot may expand to many string keys).
915    fn flatten_hash_slice_key_slots(key_vals: &[PerlValue]) -> Vec<String> {
916        let mut ks = Vec::new();
917        for kv in key_vals {
918            if let Some(vv) = kv.as_array_vec() {
919                ks.extend(vv.iter().map(|x| x.to_string()));
920            } else {
921                ks.push(kv.to_string());
922            }
923        }
924        ks
925    }
926
927    #[inline]
928    fn peek(&self) -> &PerlValue {
929        self.stack.last().unwrap_or(&PEEK_UNDEF)
930    }
931
932    #[inline]
933    fn constant(&self, idx: u16) -> &PerlValue {
934        &self.constants[idx as usize]
935    }
936
937    fn line(&self) -> usize {
938        self.lines
939            .get(self.ip.saturating_sub(1))
940            .copied()
941            .unwrap_or(0)
942    }
943
944    /// Cranelift linear JIT for a subroutine body when `ip` is a compiled sub entry (see `Chunk::sub_entries`).
945    /// Returns `Ok(true)` when the sub was executed natively and the VM should continue at `return_ip`.
946    fn try_jit_subroutine_linear(&mut self) -> Result<bool, PerlError> {
947        let ip = self.ip;
948        debug_assert!(self.sub_entry_at_ip.get(ip).copied().unwrap_or(false));
949        if self.sub_jit_skip_linear_test(ip) {
950            return Ok(false);
951        }
952        let ops: &Vec<Op> = &self.ops;
953        let ops = ops as *const Vec<Op>;
954        let ops = unsafe { &*ops };
955        let constants: &Vec<PerlValue> = &self.constants;
956        let constants = constants as *const Vec<PerlValue>;
957        let constants = unsafe { &*constants };
958        let names: &Vec<String> = &self.names;
959        let names = names as *const Vec<String>;
960        let names = unsafe { &*names };
961        let Some((seg, _)) = crate::jit::sub_entry_segment(ops, ip) else {
962            return Ok(false);
963        };
964        // `try_run_linear_sub` rejects these segments without compiling — skip expensive work before
965        // resize/fill of reusable scratch buffers (`jit_buf_*`).
966        if crate::jit::segment_blocks_subroutine_linear_jit(seg, &self.sub_entries) {
967            self.sub_jit_skip_linear_mark(ip);
968            return Ok(false);
969        }
970        let mut slot_len: Option<usize> = None;
971        if let Some(max) = crate::jit::linear_slot_ops_max_index_seq(seg) {
972            let n = max as usize + 1;
973            self.jit_buf_slot.resize(n, 0);
974            let mut ok = true;
975            for i in 0..=max {
976                let pv = self.interp.scope.get_scalar_slot(i);
977                self.jit_buf_slot[i as usize] = match pv.as_integer() {
978                    Some(v) => v,
979                    None if pv.is_undef() && crate::jit::slot_undef_prefill_ok_seq(seg, i) => 0,
980                    None => {
981                        ok = false;
982                        break;
983                    }
984                };
985            }
986            if ok {
987                slot_len = Some(n);
988            }
989        }
990        let mut plain_len: Option<usize> = None;
991        if let Some(max) = crate::jit::linear_plain_ops_max_index_seq(seg) {
992            if (max as usize) < names.len() {
993                let n = max as usize + 1;
994                self.jit_buf_plain.resize(n, 0);
995                let mut ok = true;
996                for i in 0..=max {
997                    let nm = names[i as usize].as_str();
998                    match self.interp.scope.get_scalar(nm).as_integer() {
999                        Some(v) => self.jit_buf_plain[i as usize] = v,
1000                        None => {
1001                            ok = false;
1002                            break;
1003                        }
1004                    }
1005                }
1006                if ok {
1007                    plain_len = Some(n);
1008                }
1009            }
1010        }
1011        let mut arg_len: Option<usize> = None;
1012        if let Some(max) = crate::jit::linear_arg_ops_max_index_seq(seg) {
1013            if let Some(frame) = self.call_stack.last() {
1014                let base = frame.stack_base;
1015                let n = max as usize + 1;
1016                self.jit_buf_arg.resize(n, 0);
1017                let mut ok = true;
1018                for i in 0..=max {
1019                    let pos = base + i as usize;
1020                    let pv = self.stack.get(pos).cloned().unwrap_or(PerlValue::UNDEF);
1021                    match pv.as_integer() {
1022                        Some(v) => self.jit_buf_arg[i as usize] = v,
1023                        None => {
1024                            ok = false;
1025                            break;
1026                        }
1027                    }
1028                }
1029                if ok {
1030                    arg_len = Some(n);
1031                }
1032            }
1033        }
1034        let vm_ptr = self as *mut VM<'_> as *mut std::ffi::c_void;
1035        let slot_buf = slot_len.map(|n| &mut self.jit_buf_slot[..n]);
1036        let plain_buf = plain_len.map(|n| &mut self.jit_buf_plain[..n]);
1037        let arg_buf = arg_len.map(|n| &self.jit_buf_arg[..n]);
1038        let Some(v) = crate::jit::try_run_linear_sub(
1039            ops,
1040            ip,
1041            slot_buf,
1042            plain_buf,
1043            arg_buf,
1044            constants,
1045            &self.sub_entries,
1046            vm_ptr,
1047        ) else {
1048            return Ok(false);
1049        };
1050        if let Some(n) = slot_len {
1051            let buf = &self.jit_buf_slot[..n];
1052            for idx in crate::jit::linear_slot_ops_written_indices_seq(seg) {
1053                self.interp
1054                    .scope
1055                    .set_scalar_slot(idx, PerlValue::integer(buf[idx as usize]));
1056            }
1057        }
1058        if let Some(n) = plain_len {
1059            let buf = &self.jit_buf_plain[..n];
1060            for idx in crate::jit::linear_plain_ops_written_indices_seq(seg) {
1061                let name = names[idx as usize].as_str();
1062                self.interp
1063                    .scope
1064                    .set_scalar(name, PerlValue::integer(buf[idx as usize]))
1065                    .map_err(|e| e.at_line(self.line()))?;
1066            }
1067        }
1068        if let Some(frame) = self.call_stack.pop() {
1069            self.interp.wantarray_kind = frame.saved_wantarray;
1070            self.stack.truncate(frame.stack_base);
1071            self.interp.pop_scope_to_depth(frame.scope_depth);
1072            if frame.jit_trampoline_return {
1073                self.jit_trampoline_out = Some(v);
1074            } else {
1075                self.push(v);
1076                self.ip = frame.return_ip;
1077            }
1078        }
1079        Ok(true)
1080    }
1081
1082    /// Cranelift block JIT for a subroutine with control flow (see [`crate::jit::block_jit_validate_sub`]).
1083    fn try_jit_subroutine_block(&mut self) -> Result<bool, PerlError> {
1084        let ip = self.ip;
1085        debug_assert!(self.sub_entry_at_ip.get(ip).copied().unwrap_or(false));
1086        if self.sub_jit_skip_block_test(ip) {
1087            return Ok(false);
1088        }
1089        let vm_ptr = self as *mut VM<'_> as *mut std::ffi::c_void;
1090        let ops: &Vec<Op> = &self.ops;
1091        let constants: &Vec<PerlValue> = &self.constants;
1092        let names: &Vec<String> = &self.names;
1093        let Some((full_body, term)) = crate::jit::sub_full_body(ops, ip) else {
1094            return Ok(false);
1095        };
1096        if crate::jit::sub_body_blocks_subroutine_block_jit(full_body) {
1097            self.sub_jit_skip_block_mark(ip);
1098            return Ok(false);
1099        }
1100        let Some(validated) =
1101            crate::jit::block_jit_validate_sub(full_body, constants, term, &self.sub_entries)
1102        else {
1103            self.sub_jit_skip_block_mark(ip);
1104            return Ok(false);
1105        };
1106        let block_buf_mode = validated.buffer_mode();
1107
1108        let mut b_slot_len: Option<usize> = None;
1109        if let Some(max) = crate::jit::block_slot_ops_max_index(full_body) {
1110            let n = max as usize + 1;
1111            self.jit_buf_slot.resize(n, 0);
1112            let mut ok = true;
1113            for i in 0..=max {
1114                let pv = self.interp.scope.get_scalar_slot(i);
1115                self.jit_buf_slot[i as usize] = match block_buf_mode {
1116                    crate::jit::BlockJitBufferMode::I64AsPerlValueBits => pv.raw_bits() as i64,
1117                    crate::jit::BlockJitBufferMode::I64AsInteger => match pv.as_integer() {
1118                        Some(v) => v,
1119                        None if pv.is_undef()
1120                            && crate::jit::block_slot_undef_prefill_ok(full_body, i) =>
1121                        {
1122                            0
1123                        }
1124                        None => {
1125                            ok = false;
1126                            break;
1127                        }
1128                    },
1129                };
1130            }
1131            if ok {
1132                b_slot_len = Some(n);
1133            }
1134        }
1135
1136        let mut b_plain_len: Option<usize> = None;
1137        if let Some(max) = crate::jit::block_plain_ops_max_index(full_body) {
1138            if (max as usize) < names.len() {
1139                let n = max as usize + 1;
1140                self.jit_buf_plain.resize(n, 0);
1141                let mut ok = true;
1142                for i in 0..=max {
1143                    let nm = names[i as usize].as_str();
1144                    let pv = self.interp.scope.get_scalar(nm);
1145                    self.jit_buf_plain[i as usize] = match block_buf_mode {
1146                        crate::jit::BlockJitBufferMode::I64AsPerlValueBits => pv.raw_bits() as i64,
1147                        crate::jit::BlockJitBufferMode::I64AsInteger => match pv.as_integer() {
1148                            Some(v) => v,
1149                            None => {
1150                                ok = false;
1151                                break;
1152                            }
1153                        },
1154                    };
1155                }
1156                if ok {
1157                    b_plain_len = Some(n);
1158                }
1159            }
1160        }
1161
1162        let mut b_arg_len: Option<usize> = None;
1163        if let Some(max) = crate::jit::block_arg_ops_max_index(full_body) {
1164            if let Some(frame) = self.call_stack.last() {
1165                let base = frame.stack_base;
1166                let n = max as usize + 1;
1167                self.jit_buf_arg.resize(n, 0);
1168                let mut ok = true;
1169                for i in 0..=max {
1170                    let pos = base + i as usize;
1171                    let pv = self.stack.get(pos).cloned().unwrap_or(PerlValue::UNDEF);
1172                    self.jit_buf_arg[i as usize] = match block_buf_mode {
1173                        crate::jit::BlockJitBufferMode::I64AsPerlValueBits => pv.raw_bits() as i64,
1174                        crate::jit::BlockJitBufferMode::I64AsInteger => match pv.as_integer() {
1175                            Some(v) => v,
1176                            None => {
1177                                ok = false;
1178                                break;
1179                            }
1180                        },
1181                    };
1182                }
1183                if ok {
1184                    b_arg_len = Some(n);
1185                }
1186            }
1187        }
1188
1189        let block_slot_buf = b_slot_len.map(|n| &mut self.jit_buf_slot[..n]);
1190        let block_plain_buf = b_plain_len.map(|n| &mut self.jit_buf_plain[..n]);
1191        let block_arg_buf = b_arg_len.map(|n| &self.jit_buf_arg[..n]);
1192
1193        let Some((v, buf_mode)) = crate::jit::try_run_block_ops(
1194            full_body,
1195            block_slot_buf,
1196            block_plain_buf,
1197            block_arg_buf,
1198            constants,
1199            Some(validated),
1200            vm_ptr,
1201            &self.sub_entries,
1202        ) else {
1203            self.sub_jit_skip_block_mark(ip);
1204            return Ok(false);
1205        };
1206
1207        if let Some(n) = b_slot_len {
1208            let buf = &self.jit_buf_slot[..n];
1209            for idx in crate::jit::block_slot_ops_written_indices(full_body) {
1210                let bits = buf[idx as usize] as u64;
1211                let pv = match buf_mode {
1212                    crate::jit::BlockJitBufferMode::I64AsPerlValueBits => {
1213                        PerlValue::from_raw_bits(bits)
1214                    }
1215                    crate::jit::BlockJitBufferMode::I64AsInteger => {
1216                        PerlValue::integer(buf[idx as usize])
1217                    }
1218                };
1219                self.interp.scope.set_scalar_slot(idx, pv);
1220            }
1221        }
1222        if let Some(n) = b_plain_len {
1223            let buf = &self.jit_buf_plain[..n];
1224            for idx in crate::jit::block_plain_ops_written_indices(full_body) {
1225                let name = names[idx as usize].as_str();
1226                let bits = buf[idx as usize] as u64;
1227                let pv = match buf_mode {
1228                    crate::jit::BlockJitBufferMode::I64AsPerlValueBits => {
1229                        PerlValue::from_raw_bits(bits)
1230                    }
1231                    crate::jit::BlockJitBufferMode::I64AsInteger => {
1232                        PerlValue::integer(buf[idx as usize])
1233                    }
1234                };
1235                self.interp
1236                    .scope
1237                    .set_scalar(name, pv)
1238                    .map_err(|e| e.at_line(self.line()))?;
1239            }
1240        }
1241        if let Some(frame) = self.call_stack.pop() {
1242            self.interp.wantarray_kind = frame.saved_wantarray;
1243            self.stack.truncate(frame.stack_base);
1244            self.interp.pop_scope_to_depth(frame.scope_depth);
1245            if frame.jit_trampoline_return {
1246                self.jit_trampoline_out = Some(v);
1247            } else {
1248                self.push(v);
1249                self.ip = frame.return_ip;
1250            }
1251        }
1252        Ok(true)
1253    }
1254
1255    fn run_method_op(
1256        &mut self,
1257        name_idx: u16,
1258        argc: u8,
1259        wa: u8,
1260        super_call: bool,
1261    ) -> PerlResult<()> {
1262        let method_owned = self.names[name_idx as usize].clone();
1263        let argc = argc as usize;
1264        let want = WantarrayCtx::from_byte(wa);
1265        let mut args = Vec::with_capacity(argc);
1266        for _ in 0..argc {
1267            args.push(self.pop());
1268        }
1269        args.reverse();
1270        let obj = self.pop();
1271        let method = method_owned.as_str();
1272        if let Some(r) = crate::pchannel::dispatch_method(&obj, method, &args, self.line()) {
1273            self.push(r?);
1274            return Ok(());
1275        }
1276        if let Some(r) = self
1277            .interp
1278            .try_native_method(&obj, method, &args, self.line())
1279        {
1280            self.push(r?);
1281            return Ok(());
1282        }
1283        let class = if let Some(b) = obj.as_blessed_ref() {
1284            b.class.clone()
1285        } else if let Some(s) = obj.as_str() {
1286            s
1287        } else {
1288            return Err(PerlError::runtime(
1289                "Can't call method on non-object",
1290                self.line(),
1291            ));
1292        };
1293        if method == "VERSION" && !super_call {
1294            if let Some(ver) = self.interp.package_version_scalar(class.as_str())? {
1295                self.push(ver);
1296                return Ok(());
1297            }
1298        }
1299        // UNIVERSAL methods: isa, can, DOES
1300        if !super_call {
1301            match method {
1302                "isa" => {
1303                    let target = args.first().map(|v| v.to_string()).unwrap_or_default();
1304                    let mro = self.interp.mro_linearize(&class);
1305                    let result = mro.iter().any(|c| c == &target);
1306                    self.push(PerlValue::integer(if result { 1 } else { 0 }));
1307                    return Ok(());
1308                }
1309                "can" => {
1310                    let target_method = args.first().map(|v| v.to_string()).unwrap_or_default();
1311                    let found = self
1312                        .interp
1313                        .resolve_method_full_name(&class, &target_method, false)
1314                        .and_then(|fq| self.interp.subs.get(&fq))
1315                        .is_some();
1316                    if found {
1317                        self.push(PerlValue::code_ref(std::sync::Arc::new(
1318                            crate::value::PerlSub {
1319                                name: target_method,
1320                                params: vec![],
1321                                body: vec![],
1322                                closure_env: None,
1323                                prototype: None,
1324                                fib_like: None,
1325                            },
1326                        )));
1327                    } else {
1328                        self.push(PerlValue::UNDEF);
1329                    }
1330                    return Ok(());
1331                }
1332                "DOES" => {
1333                    let target = args.first().map(|v| v.to_string()).unwrap_or_default();
1334                    let mro = self.interp.mro_linearize(&class);
1335                    let result = mro.iter().any(|c| c == &target);
1336                    self.push(PerlValue::integer(if result { 1 } else { 0 }));
1337                    return Ok(());
1338                }
1339                _ => {}
1340            }
1341        }
1342        let mut all_args = vec![obj];
1343        all_args.extend(args);
1344        let full_name = match self
1345            .interp
1346            .resolve_method_full_name(&class, method, super_call)
1347        {
1348            Some(f) => f,
1349            None => {
1350                return Err(PerlError::runtime(
1351                    format!(
1352                        "Can't locate method \"{}\" via inheritance (invocant \"{}\")",
1353                        method, class
1354                    ),
1355                    self.line(),
1356                ));
1357            }
1358        };
1359        if let Some(sub) = self.interp.subs.get(&full_name).cloned() {
1360            let saved_wa = self.interp.wantarray_kind;
1361            self.interp.wantarray_kind = want;
1362            self.interp.scope_push_hook();
1363            self.interp.scope.declare_array("_", all_args);
1364            if let Some(ref env) = sub.closure_env {
1365                self.interp.scope.restore_capture(env);
1366            }
1367            let line = self.line();
1368            let argv = self.interp.scope.take_sub_underscore().unwrap_or_default();
1369            self.interp
1370                .apply_sub_signature(sub.as_ref(), &argv, line)
1371                .map_err(|e| e.at_line(line))?;
1372            self.interp.scope.declare_array("_", argv);
1373            let result = self.interp.exec_block_no_scope(&sub.body);
1374            self.interp.wantarray_kind = saved_wa;
1375            self.interp.scope_pop_hook();
1376            match result {
1377                Ok(v) => self.push(v),
1378                Err(crate::interpreter::FlowOrError::Flow(crate::interpreter::Flow::Return(v))) => {
1379                    self.push(v)
1380                }
1381                Err(crate::interpreter::FlowOrError::Error(e)) => return Err(e),
1382                Err(_) => self.push(PerlValue::UNDEF),
1383            }
1384        } else if method == "new" && !super_call {
1385            if class == "Set" {
1386                self.push(crate::value::set_from_elements(
1387                    all_args.into_iter().skip(1),
1388                ));
1389            } else if let Some(def) = self.interp.struct_defs.get(&class).cloned() {
1390                let line = self.line();
1391                let mut provided = Vec::new();
1392                let mut i = 1;
1393                while i + 1 < all_args.len() {
1394                    let k = all_args[i].to_string();
1395                    let v = all_args[i + 1].clone();
1396                    provided.push((k, v));
1397                    i += 2;
1398                }
1399                let mut defaults = Vec::with_capacity(def.fields.len());
1400                for field in &def.fields {
1401                    if let Some(ref expr) = field.default {
1402                        let val = self.interp.eval_expr(expr).map_err(|e| match e {
1403                            crate::interpreter::FlowOrError::Error(stryke) => stryke,
1404                            _ => PerlError::runtime("default evaluation flow", line),
1405                        })?;
1406                        defaults.push(Some(val));
1407                    } else {
1408                        defaults.push(None);
1409                    }
1410                }
1411                let v =
1412                    crate::native_data::struct_new_with_defaults(&def, &provided, &defaults, line)?;
1413                self.push(v);
1414            } else {
1415                let mut map = IndexMap::new();
1416                let mut i = 1;
1417                while i + 1 < all_args.len() {
1418                    map.insert(all_args[i].to_string(), all_args[i + 1].clone());
1419                    i += 2;
1420                }
1421                self.push(PerlValue::blessed(Arc::new(
1422                    crate::value::BlessedRef::new_blessed(class, PerlValue::hash(map)),
1423                )));
1424            }
1425        } else if let Some(result) =
1426            self.interp
1427                .try_autoload_call(&full_name, all_args, self.line(), want, Some(&class))
1428        {
1429            match result {
1430                Ok(v) => self.push(v),
1431                Err(crate::interpreter::FlowOrError::Flow(crate::interpreter::Flow::Return(v))) => {
1432                    self.push(v)
1433                }
1434                Err(crate::interpreter::FlowOrError::Error(e)) => return Err(e),
1435                Err(_) => self.push(PerlValue::UNDEF),
1436            }
1437        } else {
1438            return Err(PerlError::runtime(
1439                format!(
1440                    "Can't locate method \"{}\" in package \"{}\"",
1441                    method, class
1442                ),
1443                self.line(),
1444            ));
1445        }
1446        Ok(())
1447    }
1448
1449    fn run_fan_block(
1450        &mut self,
1451        block_idx: u16,
1452        n: usize,
1453        line: usize,
1454        progress: bool,
1455    ) -> PerlResult<()> {
1456        let block = self.blocks[block_idx as usize].clone();
1457        let subs = self.interp.subs.clone();
1458        let (scope_capture, atomic_arrays, atomic_hashes) =
1459            self.interp.scope.capture_with_atomics();
1460        let fan_progress = FanProgress::new(progress, n);
1461        let first_err: Arc<Mutex<Option<PerlError>>> = Arc::new(Mutex::new(None));
1462        (0..n).into_par_iter().for_each(|i| {
1463            if first_err.lock().is_some() {
1464                return;
1465            }
1466            fan_progress.start_worker(i);
1467            let mut local_interp = Interpreter::new();
1468            local_interp.subs = subs.clone();
1469            local_interp.suppress_stdout = progress;
1470            local_interp.scope.restore_capture(&scope_capture);
1471            local_interp
1472                .scope
1473                .restore_atomics(&atomic_arrays, &atomic_hashes);
1474            local_interp.enable_parallel_guard();
1475            local_interp.scope.set_topic(PerlValue::integer(i as i64));
1476            crate::parallel_trace::fan_worker_set_index(Some(i as i64));
1477            local_interp.scope_push_hook();
1478            match local_interp.exec_block_no_scope(&block) {
1479                Ok(_) => {}
1480                Err(e) => {
1481                    let stryke = match e {
1482                        FlowOrError::Error(stryke) => stryke,
1483                        FlowOrError::Flow(_) => PerlError::runtime(
1484                            "return/last/next/redo not supported inside fan block",
1485                            line,
1486                        ),
1487                    };
1488                    let mut g = first_err.lock();
1489                    if g.is_none() {
1490                        *g = Some(stryke);
1491                    }
1492                }
1493            }
1494            local_interp.scope_pop_hook();
1495            crate::parallel_trace::fan_worker_set_index(None);
1496            fan_progress.finish_worker(i);
1497        });
1498        fan_progress.finish();
1499        if let Some(e) = first_err.lock().take() {
1500            return Err(e);
1501        }
1502        self.push(PerlValue::UNDEF);
1503        Ok(())
1504    }
1505
1506    fn run_fan_cap_block(
1507        &mut self,
1508        block_idx: u16,
1509        n: usize,
1510        line: usize,
1511        progress: bool,
1512    ) -> PerlResult<()> {
1513        let block = self.blocks[block_idx as usize].clone();
1514        let subs = self.interp.subs.clone();
1515        let (scope_capture, atomic_arrays, atomic_hashes) =
1516            self.interp.scope.capture_with_atomics();
1517        let fan_progress = FanProgress::new(progress, n);
1518        let pairs: Vec<(usize, Result<PerlValue, FlowOrError>)> = (0..n)
1519            .into_par_iter()
1520            .map(|i| {
1521                fan_progress.start_worker(i);
1522                let mut local_interp = Interpreter::new();
1523                local_interp.subs = subs.clone();
1524                local_interp.suppress_stdout = progress;
1525                local_interp.scope.restore_capture(&scope_capture);
1526                local_interp
1527                    .scope
1528                    .restore_atomics(&atomic_arrays, &atomic_hashes);
1529                local_interp.enable_parallel_guard();
1530                local_interp.scope.set_topic(PerlValue::integer(i as i64));
1531                crate::parallel_trace::fan_worker_set_index(Some(i as i64));
1532                local_interp.scope_push_hook();
1533                let res = local_interp.exec_block_no_scope(&block);
1534                local_interp.scope_pop_hook();
1535                crate::parallel_trace::fan_worker_set_index(None);
1536                fan_progress.finish_worker(i);
1537                (i, res)
1538            })
1539            .collect();
1540        fan_progress.finish();
1541        let mut pairs = pairs;
1542        pairs.sort_by_key(|(i, _)| *i);
1543        let mut out = Vec::with_capacity(n);
1544        for (_, r) in pairs {
1545            match r {
1546                Ok(v) => out.push(v),
1547                Err(e) => {
1548                    let stryke = match e {
1549                        FlowOrError::Error(stryke) => stryke,
1550                        FlowOrError::Flow(_) => PerlError::runtime(
1551                            "return/last/next/redo not supported inside fan_cap block",
1552                            line,
1553                        ),
1554                    };
1555                    return Err(stryke);
1556                }
1557            }
1558        }
1559        self.push(PerlValue::array(out));
1560        Ok(())
1561    }
1562
1563    fn require_scalar_mutable(&self, name: &str) -> PerlResult<()> {
1564        if self.interp.scope.is_scalar_frozen(name) {
1565            return Err(PerlError::syntax(
1566                format!("cannot assign to frozen variable `${}`", name),
1567                self.line(),
1568            ));
1569        }
1570        Ok(())
1571    }
1572
1573    fn require_array_mutable(&self, name: &str) -> PerlResult<()> {
1574        if self.interp.scope.is_array_frozen(name) {
1575            return Err(PerlError::syntax(
1576                format!("cannot modify frozen array `@{}`", name),
1577                self.line(),
1578            ));
1579        }
1580        Ok(())
1581    }
1582
1583    fn require_hash_mutable(&self, name: &str) -> PerlResult<()> {
1584        if self.interp.scope.is_hash_frozen(name) || Self::is_reflection_hash(name) {
1585            return Err(PerlError::syntax(
1586                format!("cannot modify frozen hash `%{}`", name),
1587                self.line(),
1588            ));
1589        }
1590        Ok(())
1591    }
1592
1593    /// Reflection hashes are frozen builtins even before lazy init.
1594    fn is_reflection_hash(name: &str) -> bool {
1595        matches!(name, "b" | "pc" | "e" | "a" | "d" | "c" | "p" | "all")
1596            || name.starts_with("stryke::")
1597    }
1598
1599    /// Run bytecode: first attempts Cranelift method JIT for eligible numeric fragments (unless
1600    /// [`VM::set_jit_enabled`] disabled it). For block JIT, `block_jit_validate` runs once per attempt;
1601    /// buffers may use `PerlValue::raw_bits` for `defined`-style control flow. Then the main opcode
1602    /// interpreter loop.
1603    pub fn execute(&mut self) -> PerlResult<PerlValue> {
1604        let ops_ref: &Vec<Op> = &self.ops;
1605        let ops = ops_ref as *const Vec<Op>;
1606        // SAFETY: ops doesn't change during execution; pointer avoids borrow on self
1607        let ops = unsafe { &*ops };
1608        let names_ref: &Vec<String> = &self.names;
1609        let names = names_ref as *const Vec<String>;
1610        // SAFETY: names doesn't change during execution; pointer avoids borrow on self
1611        let names = unsafe { &*names };
1612        let constants_ref: &Vec<PerlValue> = &self.constants;
1613        let constants = constants_ref as *const Vec<PerlValue>;
1614        // SAFETY: constants doesn't change during execution; pointer avoids borrow on self
1615        let constants = unsafe { &*constants };
1616        let mut last = PerlValue::UNDEF;
1617        // Safety limit: [`run_main_dispatch_loop`] counts ops (1B cap).
1618        let mut op_count: u64 = 0;
1619
1620        // Match Perl signal delivery: deliver `%SIG` and set `$^C` latch (Unix).
1621        crate::perl_signal::poll(self.interp)?;
1622        if self.jit_enabled {
1623            let mut top_slot_len: Option<usize> = None;
1624            if let Some(max) = crate::jit::linear_slot_ops_max_index(ops) {
1625                let n = max as usize + 1;
1626                self.jit_buf_slot.resize(n, 0);
1627                let mut ok = true;
1628                for i in 0..=max {
1629                    let pv = self.interp.scope.get_scalar_slot(i);
1630                    self.jit_buf_slot[i as usize] = match pv.as_integer() {
1631                        Some(v) => v,
1632                        None if pv.is_undef() && crate::jit::slot_undef_prefill_ok(ops, i) => 0,
1633                        None => {
1634                            ok = false;
1635                            break;
1636                        }
1637                    };
1638                }
1639                if ok {
1640                    top_slot_len = Some(n);
1641                }
1642            }
1643
1644            let mut top_plain_len: Option<usize> = None;
1645            if let Some(max) = crate::jit::linear_plain_ops_max_index(ops) {
1646                if (max as usize) < names.len() {
1647                    let n = max as usize + 1;
1648                    self.jit_buf_plain.resize(n, 0);
1649                    let mut ok = true;
1650                    for i in 0..=max {
1651                        let nm = names[i as usize].as_str();
1652                        match self.interp.scope.get_scalar(nm).as_integer() {
1653                            Some(v) => self.jit_buf_plain[i as usize] = v,
1654                            None => {
1655                                ok = false;
1656                                break;
1657                            }
1658                        }
1659                    }
1660                    if ok {
1661                        top_plain_len = Some(n);
1662                    }
1663                }
1664            }
1665
1666            let mut top_arg_len: Option<usize> = None;
1667            if let Some(max) = crate::jit::linear_arg_ops_max_index(ops) {
1668                if let Some(frame) = self.call_stack.last() {
1669                    let base = frame.stack_base;
1670                    let n = max as usize + 1;
1671                    self.jit_buf_arg.resize(n, 0);
1672                    let mut ok = true;
1673                    for i in 0..=max {
1674                        let pos = base + i as usize;
1675                        let pv = self.stack.get(pos).cloned().unwrap_or(PerlValue::UNDEF);
1676                        match pv.as_integer() {
1677                            Some(v) => self.jit_buf_arg[i as usize] = v,
1678                            None => {
1679                                ok = false;
1680                                break;
1681                            }
1682                        }
1683                    }
1684                    if ok {
1685                        top_arg_len = Some(n);
1686                    }
1687                }
1688            }
1689
1690            let slot_buf = top_slot_len.map(|n| &mut self.jit_buf_slot[..n]);
1691            let plain_buf = top_plain_len.map(|n| &mut self.jit_buf_plain[..n]);
1692            let arg_buf = top_arg_len.map(|n| &self.jit_buf_arg[..n]);
1693
1694            if let Some(v) =
1695                crate::jit::try_run_linear_ops(ops, slot_buf, plain_buf, arg_buf, constants)
1696            {
1697                if let Some(n) = top_slot_len {
1698                    let buf = &self.jit_buf_slot[..n];
1699                    for idx in crate::jit::linear_slot_ops_written_indices(ops) {
1700                        self.interp
1701                            .scope
1702                            .set_scalar_slot(idx, PerlValue::integer(buf[idx as usize]));
1703                    }
1704                }
1705                if let Some(n) = top_plain_len {
1706                    let buf = &self.jit_buf_plain[..n];
1707                    for idx in crate::jit::linear_plain_ops_written_indices(ops) {
1708                        let name = names[idx as usize].as_str();
1709                        self.interp
1710                            .scope
1711                            .set_scalar(name, PerlValue::integer(buf[idx as usize]))?;
1712                    }
1713                }
1714                return Ok(v);
1715            }
1716
1717            // ── Block JIT: try to compile sequences with control flow (loops, conditionals). ──
1718            if let Some(validated) =
1719                crate::jit::block_jit_validate(ops, constants, &self.sub_entries)
1720            {
1721                let block_buf_mode = validated.buffer_mode();
1722
1723                let mut top_b_slot_len: Option<usize> = None;
1724                if let Some(max) = crate::jit::block_slot_ops_max_index(ops) {
1725                    let n = max as usize + 1;
1726                    self.jit_buf_slot.resize(n, 0);
1727                    let mut ok = true;
1728                    for i in 0..=max {
1729                        let pv = self.interp.scope.get_scalar_slot(i);
1730                        self.jit_buf_slot[i as usize] = match block_buf_mode {
1731                            crate::jit::BlockJitBufferMode::I64AsPerlValueBits => {
1732                                pv.raw_bits() as i64
1733                            }
1734                            crate::jit::BlockJitBufferMode::I64AsInteger => match pv.as_integer() {
1735                                Some(v) => v,
1736                                None if pv.is_undef()
1737                                    && crate::jit::block_slot_undef_prefill_ok(ops, i) =>
1738                                {
1739                                    0
1740                                }
1741                                None => {
1742                                    ok = false;
1743                                    break;
1744                                }
1745                            },
1746                        };
1747                    }
1748                    if ok {
1749                        top_b_slot_len = Some(n);
1750                    }
1751                }
1752
1753                let mut top_b_plain_len: Option<usize> = None;
1754                if let Some(max) = crate::jit::block_plain_ops_max_index(ops) {
1755                    if (max as usize) < names.len() {
1756                        let n = max as usize + 1;
1757                        self.jit_buf_plain.resize(n, 0);
1758                        let mut ok = true;
1759                        for i in 0..=max {
1760                            let nm = names[i as usize].as_str();
1761                            let pv = self.interp.scope.get_scalar(nm);
1762                            self.jit_buf_plain[i as usize] = match block_buf_mode {
1763                                crate::jit::BlockJitBufferMode::I64AsPerlValueBits => {
1764                                    pv.raw_bits() as i64
1765                                }
1766                                crate::jit::BlockJitBufferMode::I64AsInteger => {
1767                                    match pv.as_integer() {
1768                                        Some(v) => v,
1769                                        None => {
1770                                            ok = false;
1771                                            break;
1772                                        }
1773                                    }
1774                                }
1775                            };
1776                        }
1777                        if ok {
1778                            top_b_plain_len = Some(n);
1779                        }
1780                    }
1781                }
1782
1783                let mut top_b_arg_len: Option<usize> = None;
1784                if let Some(max) = crate::jit::block_arg_ops_max_index(ops) {
1785                    if let Some(frame) = self.call_stack.last() {
1786                        let base = frame.stack_base;
1787                        let n = max as usize + 1;
1788                        self.jit_buf_arg.resize(n, 0);
1789                        let mut ok = true;
1790                        for i in 0..=max {
1791                            let pos = base + i as usize;
1792                            let pv = self.stack.get(pos).cloned().unwrap_or(PerlValue::UNDEF);
1793                            self.jit_buf_arg[i as usize] = match block_buf_mode {
1794                                crate::jit::BlockJitBufferMode::I64AsPerlValueBits => {
1795                                    pv.raw_bits() as i64
1796                                }
1797                                crate::jit::BlockJitBufferMode::I64AsInteger => {
1798                                    match pv.as_integer() {
1799                                        Some(v) => v,
1800                                        None => {
1801                                            ok = false;
1802                                            break;
1803                                        }
1804                                    }
1805                                }
1806                            };
1807                        }
1808                        if ok {
1809                            top_b_arg_len = Some(n);
1810                        }
1811                    }
1812                }
1813
1814                let vm_ptr = self as *mut VM<'_> as *mut std::ffi::c_void;
1815                let block_slot_buf = top_b_slot_len.map(|n| &mut self.jit_buf_slot[..n]);
1816                let block_plain_buf = top_b_plain_len.map(|n| &mut self.jit_buf_plain[..n]);
1817                let block_arg_buf = top_b_arg_len.map(|n| &self.jit_buf_arg[..n]);
1818
1819                if let Some((v, buf_mode)) = crate::jit::try_run_block_ops(
1820                    ops,
1821                    block_slot_buf,
1822                    block_plain_buf,
1823                    block_arg_buf,
1824                    constants,
1825                    Some(validated),
1826                    vm_ptr,
1827                    &self.sub_entries,
1828                ) {
1829                    if let Some(n) = top_b_slot_len {
1830                        let buf = &self.jit_buf_slot[..n];
1831                        for idx in crate::jit::block_slot_ops_written_indices(ops) {
1832                            let bits = buf[idx as usize] as u64;
1833                            let pv = match buf_mode {
1834                                crate::jit::BlockJitBufferMode::I64AsPerlValueBits => {
1835                                    PerlValue::from_raw_bits(bits)
1836                                }
1837                                crate::jit::BlockJitBufferMode::I64AsInteger => {
1838                                    PerlValue::integer(buf[idx as usize])
1839                                }
1840                            };
1841                            self.interp.scope.set_scalar_slot(idx, pv);
1842                        }
1843                    }
1844                    if let Some(n) = top_b_plain_len {
1845                        let buf = &self.jit_buf_plain[..n];
1846                        for idx in crate::jit::block_plain_ops_written_indices(ops) {
1847                            let name = names[idx as usize].as_str();
1848                            let bits = buf[idx as usize] as u64;
1849                            let pv = match buf_mode {
1850                                crate::jit::BlockJitBufferMode::I64AsPerlValueBits => {
1851                                    PerlValue::from_raw_bits(bits)
1852                                }
1853                                crate::jit::BlockJitBufferMode::I64AsInteger => {
1854                                    PerlValue::integer(buf[idx as usize])
1855                                }
1856                            };
1857                            self.interp.scope.set_scalar(name, pv)?;
1858                        }
1859                    }
1860                    return Ok(v);
1861                }
1862            }
1863        }
1864
1865        last = self.run_main_dispatch_loop(last, &mut op_count, true)?;
1866
1867        Ok(last)
1868    }
1869
1870    /// `die` / runtime errors inside `try` jump to `catch_ip` unless the error is [`ErrorKind::Exit`].
1871    fn try_recover_from_exception(&mut self, e: &PerlError) -> PerlResult<bool> {
1872        if matches!(e.kind, ErrorKind::Exit(_)) {
1873            return Ok(false);
1874        }
1875        let Some(frame) = self.try_stack.last() else {
1876            return Ok(false);
1877        };
1878        let Op::TryPush { catch_ip, .. } = &self.ops[frame.try_push_op_idx] else {
1879            return Ok(false);
1880        };
1881        self.pending_catch_error = Some(e.to_string());
1882        self.ip = *catch_ip;
1883        Ok(true)
1884    }
1885
1886    /// Stash lookup only (qualified key from compiler); avoids `resolve_sub_by_name`'s package fallback on hot calls.
1887    #[inline]
1888    fn sub_for_closure_restore(&self, name: &str) -> Option<Arc<PerlSub>> {
1889        self.interp.subs.get(name).cloned()
1890    }
1891
1892    fn vm_dispatch_user_call(
1893        &mut self,
1894        name_idx: u16,
1895        entry_opt: Option<(usize, bool)>,
1896        argc_u8: u8,
1897        wa_byte: u8,
1898        // Pre-resolved sub for `Op::CallStaticSubId` (stash lookup once in `VM::new`).
1899        closure_sub_hint: Option<Arc<PerlSub>>,
1900    ) -> PerlResult<()> {
1901        let name_owned = self.names[name_idx as usize].clone();
1902        let name = name_owned.as_str();
1903        let argc = argc_u8 as usize;
1904        let want = WantarrayCtx::from_byte(wa_byte);
1905
1906        if let Some((entry_ip, stack_args)) = entry_opt {
1907            let saved_wa = self.interp.wantarray_kind;
1908            let sub_prof_t0 = self.interp.profiler.is_some().then(std::time::Instant::now);
1909            if let Some(p) = &mut self.interp.profiler {
1910                p.enter_sub(name);
1911            }
1912
1913            // Fib-shaped recursive-add fast path: if the target sub is tagged with a
1914            // `fib_like` pattern (detected at sub-registration time in the compiler and
1915            // cached in `static_sub_closure_subs`), skip frame setup entirely and
1916            // evaluate the closed-form-ish iterative version. `bench_fib` collapses from
1917            // ~2.7M recursive VM calls to a single `while` loop.
1918            let fib_sub: Option<Arc<PerlSub>> = closure_sub_hint
1919                .clone()
1920                .or_else(|| self.sub_for_closure_restore(name));
1921            if let Some(ref sub_arc) = fib_sub {
1922                if let Some(pat) = sub_arc.fib_like.as_ref() {
1923                    // stack_args path pushes exactly `argc` ints; non-stack_args pops them
1924                    // off the stack into @_. Only the argc==1 / integer case qualifies.
1925                    if argc == 1 {
1926                        let top_idx = self.stack.len().saturating_sub(1);
1927                        if let Some(n0) = self.stack.get(top_idx).and_then(|v| v.as_integer()) {
1928                            let result = crate::fib_like_tail::eval_fib_like_recursive_add(n0, pat);
1929                            // Drop the arg, push the result, keep wantarray as the caller had it.
1930                            self.stack.truncate(top_idx);
1931                            self.push(PerlValue::integer(result));
1932                            if let (Some(p), Some(t0)) = (&mut self.interp.profiler, sub_prof_t0) {
1933                                p.exit_sub(t0.elapsed());
1934                            }
1935                            self.interp.wantarray_kind = saved_wa;
1936                            return Ok(());
1937                        }
1938                    }
1939                }
1940            }
1941
1942            if stack_args {
1943                let eff_argc = if argc == 0 {
1944                    self.push(self.interp.scope.get_scalar("_").clone());
1945                    1
1946                } else {
1947                    argc
1948                };
1949                let stack_base = self.stack.len() - eff_argc;
1950                self.call_stack.push(CallFrame {
1951                    return_ip: self.ip,
1952                    stack_base,
1953                    scope_depth: self.interp.scope.depth(),
1954                    saved_wantarray: saved_wa,
1955                    jit_trampoline_return: false,
1956                    block_region: false,
1957                    sub_profiler_start: sub_prof_t0,
1958                });
1959                self.interp.wantarray_kind = want;
1960                self.interp.scope_push_hook();
1961                let closure_sub = closure_sub_hint.or_else(|| self.sub_for_closure_restore(name));
1962                if let Some(ref sub) = closure_sub {
1963                    if let Some(ref env) = sub.closure_env {
1964                        self.interp.scope.restore_capture(env);
1965                    }
1966                    self.interp.current_sub_stack.push(sub.clone());
1967                }
1968                self.ip = entry_ip;
1969            } else {
1970                let args = if Self::call_preserve_operand_arrays(name) {
1971                    self.pop_call_operands_preserved(argc)
1972                } else {
1973                    self.pop_call_operands_flattened(argc)
1974                };
1975                let args = self.interp.with_topic_default_args(args);
1976                self.call_stack.push(CallFrame {
1977                    return_ip: self.ip,
1978                    stack_base: self.stack.len(),
1979                    scope_depth: self.interp.scope.depth(),
1980                    saved_wantarray: saved_wa,
1981                    jit_trampoline_return: false,
1982                    block_region: false,
1983                    sub_profiler_start: sub_prof_t0,
1984                });
1985                self.interp.wantarray_kind = want;
1986                self.interp.scope_push_hook();
1987                self.interp.scope.declare_array("_", args);
1988                let closure_sub = closure_sub_hint.or_else(|| self.sub_for_closure_restore(name));
1989                if let Some(ref sub) = closure_sub {
1990                    if let Some(ref env) = sub.closure_env {
1991                        self.interp.scope.restore_capture(env);
1992                    }
1993                    let line = self.line();
1994                    let argv = self.interp.scope.take_sub_underscore().unwrap_or_default();
1995                    self.interp
1996                        .apply_sub_signature(sub.as_ref(), &argv, line)
1997                        .map_err(|e| e.at_line(line))?;
1998                    self.interp.scope.declare_array("_", argv.clone());
1999                    self.interp.scope.set_closure_args(&argv);
2000                    self.interp.current_sub_stack.push(sub.clone());
2001                }
2002                self.ip = entry_ip;
2003            }
2004        } else {
2005            let args = if Self::call_preserve_operand_arrays(name) {
2006                self.pop_call_operands_preserved(argc)
2007            } else {
2008                self.pop_call_operands_flattened(argc)
2009            };
2010
2011            let saved_wa_call = self.interp.wantarray_kind;
2012            self.interp.wantarray_kind = want;
2013            if let Some(r) = crate::builtins::try_builtin(self.interp, name, &args, self.line()) {
2014                self.interp.wantarray_kind = saved_wa_call;
2015                self.push(r?);
2016            } else {
2017                self.interp.wantarray_kind = saved_wa_call;
2018                if let Some(sub) = self.interp.resolve_sub_by_name(name) {
2019                    let t0 = self.interp.profiler.is_some().then(std::time::Instant::now);
2020                    if let Some(p) = &mut self.interp.profiler {
2021                        p.enter_sub(name);
2022                    }
2023                    let args = self.interp.with_topic_default_args(args);
2024                    let saved_wa = self.interp.wantarray_kind;
2025                    self.interp.wantarray_kind = want;
2026                    self.interp.scope_push_hook();
2027                    self.interp.scope.declare_array("_", args);
2028                    if let Some(ref env) = sub.closure_env {
2029                        self.interp.scope.restore_capture(env);
2030                    }
2031                    let argv = self.interp.scope.take_sub_underscore().unwrap_or_default();
2032                    let line = self.line();
2033                    self.interp
2034                        .apply_sub_signature(&sub, &argv, line)
2035                        .map_err(|e| e.at_line(line))?;
2036                    let result = if let Some(r) =
2037                        crate::list_util::native_dispatch(self.interp, &sub, &argv, want)
2038                    {
2039                        r
2040                    } else {
2041                        self.interp.scope.declare_array("_", argv.clone());
2042                        self.interp.scope.set_closure_args(&argv);
2043                        self.interp
2044                            .exec_block_no_scope_with_tail(&sub.body, WantarrayCtx::List)
2045                    };
2046                    self.interp.wantarray_kind = saved_wa;
2047                    self.interp.scope_pop_hook();
2048                    match result {
2049                        Ok(v) => self.push(v),
2050                        Err(crate::interpreter::FlowOrError::Flow(
2051                            crate::interpreter::Flow::Return(v),
2052                        )) => self.push(v),
2053                        Err(crate::interpreter::FlowOrError::Error(e)) => {
2054                            if let (Some(p), Some(t0)) = (&mut self.interp.profiler, t0) {
2055                                p.exit_sub(t0.elapsed());
2056                            }
2057                            return Err(e);
2058                        }
2059                        Err(_) => self.push(PerlValue::UNDEF),
2060                    }
2061                    if let (Some(p), Some(t0)) = (&mut self.interp.profiler, t0) {
2062                        p.exit_sub(t0.elapsed());
2063                    }
2064                } else if !name.contains("::")
2065                    && matches!(
2066                        name,
2067                        "uniq"
2068                            | "distinct"
2069                            | "uniqstr"
2070                            | "uniqint"
2071                            | "uniqnum"
2072                            | "shuffle"
2073                            | "sample"
2074                            | "chunked"
2075                            | "windowed"
2076                            | "zip"
2077                            | "zip_shortest"
2078                            | "zip_longest"
2079                            | "mesh"
2080                            | "mesh_shortest"
2081                            | "mesh_longest"
2082                            | "any"
2083                            | "all"
2084                            | "none"
2085                            | "notall"
2086                            | "first"
2087                            | "reduce"
2088                            | "reductions"
2089                            | "sum"
2090                            | "sum0"
2091                            | "product"
2092                            | "min"
2093                            | "max"
2094                            | "minstr"
2095                            | "maxstr"
2096                            | "mean"
2097                            | "median"
2098                            | "mode"
2099                            | "stddev"
2100                            | "variance"
2101                            | "pairs"
2102                            | "unpairs"
2103                            | "pairkeys"
2104                            | "pairvalues"
2105                            | "pairgrep"
2106                            | "pairmap"
2107                            | "pairfirst"
2108                    )
2109                {
2110                    let t0 = self.interp.profiler.is_some().then(std::time::Instant::now);
2111                    if let Some(p) = &mut self.interp.profiler {
2112                        p.enter_sub(name);
2113                    }
2114                    let saved_wa = self.interp.wantarray_kind;
2115                    self.interp.wantarray_kind = want;
2116                    let out = self
2117                        .interp
2118                        .call_bare_list_util(name, args, self.line(), want);
2119                    self.interp.wantarray_kind = saved_wa;
2120                    match out {
2121                        Ok(v) => self.push(v),
2122                        Err(crate::interpreter::FlowOrError::Flow(
2123                            crate::interpreter::Flow::Return(v),
2124                        )) => self.push(v),
2125                        Err(crate::interpreter::FlowOrError::Error(e)) => {
2126                            if let (Some(p), Some(t0)) = (&mut self.interp.profiler, t0) {
2127                                p.exit_sub(t0.elapsed());
2128                            }
2129                            return Err(e);
2130                        }
2131                        Err(_) => self.push(PerlValue::UNDEF),
2132                    }
2133                    if let (Some(p), Some(t0)) = (&mut self.interp.profiler, t0) {
2134                        p.exit_sub(t0.elapsed());
2135                    }
2136                } else if let Some(result) = self.interp.try_autoload_call(
2137                    name,
2138                    self.interp.with_topic_default_args(args.clone()),
2139                    self.line(),
2140                    want,
2141                    None,
2142                ) {
2143                    let t0 = self.interp.profiler.is_some().then(std::time::Instant::now);
2144                    if let Some(p) = &mut self.interp.profiler {
2145                        p.enter_sub(name);
2146                    }
2147                    match result {
2148                        Ok(v) => self.push(v),
2149                        Err(crate::interpreter::FlowOrError::Flow(
2150                            crate::interpreter::Flow::Return(v),
2151                        )) => self.push(v),
2152                        Err(crate::interpreter::FlowOrError::Error(e)) => {
2153                            if let (Some(p), Some(t0)) = (&mut self.interp.profiler, t0) {
2154                                p.exit_sub(t0.elapsed());
2155                            }
2156                            return Err(e);
2157                        }
2158                        Err(_) => self.push(PerlValue::UNDEF),
2159                    }
2160                    if let (Some(p), Some(t0)) = (&mut self.interp.profiler, t0) {
2161                        p.exit_sub(t0.elapsed());
2162                    }
2163                } else if let Some(def) = self.interp.struct_defs.get(name).cloned() {
2164                    // Struct constructor: Point(x => 1, y => 2) or Point(1, 2)
2165                    let result = self.interp.struct_construct(&def, args, self.line());
2166                    match result {
2167                        Ok(v) => self.push(v),
2168                        Err(crate::interpreter::FlowOrError::Error(e)) => return Err(e),
2169                        _ => self.push(PerlValue::UNDEF),
2170                    }
2171                } else if let Some(def) = self.interp.class_defs.get(name).cloned() {
2172                    // Class constructor: Dog(name => "Rex") or Dog("Rex", 5)
2173                    let result = self.interp.class_construct(&def, args, self.line());
2174                    match result {
2175                        Ok(v) => self.push(v),
2176                        Err(crate::interpreter::FlowOrError::Error(e)) => return Err(e),
2177                        _ => self.push(PerlValue::UNDEF),
2178                    }
2179                } else if let Some((prefix, suffix)) = name.rsplit_once("::") {
2180                    // Enum variant constructor: Color::Red or Maybe::Some(value)
2181                    if let Some(def) = self.interp.enum_defs.get(prefix).cloned() {
2182                        let result = self.interp.enum_construct(&def, suffix, args, self.line());
2183                        match result {
2184                            Ok(v) => self.push(v),
2185                            Err(crate::interpreter::FlowOrError::Error(e)) => return Err(e),
2186                            _ => self.push(PerlValue::UNDEF),
2187                        }
2188                    // Static class method: Math::add(...)
2189                    } else if let Some(def) = self.interp.class_defs.get(prefix).cloned() {
2190                        if let Some(m) = def.method(suffix) {
2191                            if m.is_static {
2192                                if let Some(ref body) = m.body {
2193                                    let params = m.params.clone();
2194                                    match self.interp.call_static_class_method(
2195                                        body,
2196                                        &params,
2197                                        args.clone(),
2198                                        self.line(),
2199                                    ) {
2200                                        Ok(v) => self.push(v),
2201                                        Err(crate::interpreter::FlowOrError::Error(e)) => {
2202                                            return Err(e)
2203                                        }
2204                                        Err(crate::interpreter::FlowOrError::Flow(
2205                                            crate::interpreter::Flow::Return(v),
2206                                        )) => self.push(v),
2207                                        _ => self.push(PerlValue::UNDEF),
2208                                    }
2209                                } else {
2210                                    self.push(PerlValue::UNDEF);
2211                                }
2212                            } else {
2213                                return Err(PerlError::runtime(
2214                                    format!("method `{}` is not static", suffix),
2215                                    self.line(),
2216                                ));
2217                            }
2218                        } else if def.static_fields.iter().any(|sf| sf.name == suffix) {
2219                            // Static field access: getter (0 args) or setter (1 arg)
2220                            let key = format!("{}::{}", prefix, suffix);
2221                            match args.len() {
2222                                0 => {
2223                                    let val = self.interp.scope.get_scalar(&key);
2224                                    self.push(val);
2225                                }
2226                                1 => {
2227                                    let _ = self.interp.scope.set_scalar(&key, args[0].clone());
2228                                    self.push(args[0].clone());
2229                                }
2230                                _ => {
2231                                    return Err(PerlError::runtime(
2232                                        format!(
2233                                            "static field `{}::{}` takes 0 or 1 arguments",
2234                                            prefix, suffix
2235                                        ),
2236                                        self.line(),
2237                                    ));
2238                                }
2239                            }
2240                        } else {
2241                            return Err(PerlError::runtime(
2242                                self.interp.undefined_subroutine_call_message(name),
2243                                self.line(),
2244                            ));
2245                        }
2246                    } else {
2247                        return Err(PerlError::runtime(
2248                            self.interp.undefined_subroutine_call_message(name),
2249                            self.line(),
2250                        ));
2251                    }
2252                } else {
2253                    return Err(PerlError::runtime(
2254                        self.interp.undefined_subroutine_call_message(name),
2255                        self.line(),
2256                    ));
2257                }
2258            }
2259        }
2260        Ok(())
2261    }
2262
2263    #[inline]
2264    fn push_binop_with_overload<F>(
2265        &mut self,
2266        op: BinOp,
2267        a: PerlValue,
2268        b: PerlValue,
2269        default: F,
2270    ) -> PerlResult<()>
2271    where
2272        F: FnOnce(&PerlValue, &PerlValue) -> PerlResult<PerlValue>,
2273    {
2274        let line = self.line();
2275        if let Some(exec_res) = self.interp.try_overload_binop(op, &a, &b, line) {
2276            self.push(vm_interp_result(exec_res, line)?);
2277        } else {
2278            self.push(default(&a, &b)?);
2279        }
2280        Ok(())
2281    }
2282
2283    pub(crate) fn concat_stack_values(
2284        &mut self,
2285        a: PerlValue,
2286        b: PerlValue,
2287    ) -> PerlResult<PerlValue> {
2288        let line = self.line();
2289        if let Some(exec_res) = self.interp.try_overload_binop(BinOp::Concat, &a, &b, line) {
2290            vm_interp_result(exec_res, line)
2291        } else {
2292            let sa = match self.interp.stringify_value(a, line) {
2293                Ok(s) => s,
2294                Err(FlowOrError::Error(e)) => return Err(e),
2295                Err(FlowOrError::Flow(_)) => {
2296                    return Err(PerlError::runtime("concat: unexpected control flow", line));
2297                }
2298            };
2299            let sb = match self.interp.stringify_value(b, line) {
2300                Ok(s) => s,
2301                Err(FlowOrError::Error(e)) => return Err(e),
2302                Err(FlowOrError::Flow(_)) => {
2303                    return Err(PerlError::runtime("concat: unexpected control flow", line));
2304                }
2305            };
2306            let mut s = sa;
2307            s.push_str(&sb);
2308            Ok(PerlValue::string(s))
2309        }
2310    }
2311
2312    fn run_main_dispatch_loop(
2313        &mut self,
2314        mut last: PerlValue,
2315        op_count: &mut u64,
2316        init_dispatch: bool,
2317    ) -> PerlResult<PerlValue> {
2318        if init_dispatch {
2319            self.halt = false;
2320            self.exit_main_dispatch = false;
2321            self.exit_main_dispatch_value = None;
2322        }
2323        let ops_ref: &Vec<Op> = &self.ops;
2324        let ops = ops_ref as *const Vec<Op>;
2325        let ops = unsafe { &*ops };
2326        let names_ref: &Vec<String> = &self.names;
2327        let names = names_ref as *const Vec<String>;
2328        let names = unsafe { &*names };
2329        let constants_ref: &Vec<PerlValue> = &self.constants;
2330        let constants = constants_ref as *const Vec<PerlValue>;
2331        let constants = unsafe { &*constants };
2332        let len = ops.len();
2333        const MAX_OPS: u64 = 1_000_000_000;
2334        loop {
2335            if self.jit_trampoline_depth > 0 && self.jit_trampoline_out.is_some() {
2336                break;
2337            }
2338            if self.block_region_return.is_some() {
2339                break;
2340            }
2341            if self.block_region_mode && self.ip >= self.block_region_end {
2342                return Err(PerlError::runtime(
2343                    "block bytecode region fell through without BlockReturnValue",
2344                    self.line(),
2345                ));
2346            }
2347            if self.ip >= len {
2348                break;
2349            }
2350
2351            if !self.block_region_mode
2352                && self.jit_enabled
2353                && self.sub_entry_at_ip.get(self.ip).copied().unwrap_or(false)
2354            {
2355                let sub_ip = self.ip;
2356                if sub_ip >= self.sub_entry_invoke_count.len() {
2357                    self.sub_entry_invoke_count.resize(sub_ip + 1, 0);
2358                }
2359                let c = &mut self.sub_entry_invoke_count[sub_ip];
2360                if *c <= self.jit_sub_invoke_threshold {
2361                    *c = c.saturating_add(1);
2362                }
2363                let should_try_jit = *c > self.jit_sub_invoke_threshold
2364                    && (!self.sub_jit_skip_linear_test(sub_ip)
2365                        || !self.sub_jit_skip_block_test(sub_ip));
2366                if should_try_jit {
2367                    if !self.sub_jit_skip_linear_test(sub_ip) && self.try_jit_subroutine_linear()? {
2368                        continue;
2369                    }
2370                    if !self.sub_jit_skip_block_test(sub_ip) && self.try_jit_subroutine_block()? {
2371                        continue;
2372                    }
2373                }
2374            }
2375
2376            *op_count += 1;
2377            // `%SIG` delivery and the execution cap: same cadence as the old per-op poll (signals
2378            // remain responsive; hot loops avoid a syscall/atomic path every opcode).
2379            if (*op_count & 0x3FF) == 0 {
2380                crate::perl_signal::poll(self.interp)?;
2381                if *op_count > MAX_OPS {
2382                    return Err(PerlError::runtime(
2383                        "VM execution limit exceeded (possible infinite loop)",
2384                        self.line(),
2385                    ));
2386                }
2387            }
2388
2389            let ip_before = self.ip;
2390            let line = self.lines.get(ip_before).copied().unwrap_or(0);
2391            let op = &ops[self.ip];
2392            self.ip += 1;
2393
2394            // Debugger hook: check if we should stop at this line
2395            if let Some(ref mut dbg) = self.interp.debugger {
2396                if dbg.should_stop(line) {
2397                    let call_stack = self.interp.debug_call_stack.clone();
2398                    match dbg.prompt(line, &self.interp.scope, &call_stack) {
2399                        crate::debugger::DebugAction::Quit => {
2400                            return Err(PerlError::runtime("debugger: quit", line));
2401                        }
2402                        crate::debugger::DebugAction::Continue => {}
2403                        crate::debugger::DebugAction::Prompt => {}
2404                    }
2405                }
2406            }
2407
2408            let op_prof_t0 = self.interp.profiler.is_some().then(std::time::Instant::now);
2409            // Closure: `?` / `return Err` inside `match op` must not return from
2410            // `run_main_dispatch_loop` — they must become `__op_res` so `try_recover_from_exception`
2411            // can run before propagating.
2412            let __op_res: PerlResult<()> = (|| -> PerlResult<()> {
2413                match op {
2414                    Op::Nop => Ok(()),
2415                    // ── Constants ──
2416                    Op::LoadInt(n) => {
2417                        self.push(PerlValue::integer(*n));
2418                        Ok(())
2419                    }
2420                    Op::LoadFloat(f) => {
2421                        self.push(PerlValue::float(*f));
2422                        Ok(())
2423                    }
2424                    Op::LoadConst(idx) => {
2425                        self.push(self.constant(*idx).clone());
2426                        Ok(())
2427                    }
2428                    Op::LoadUndef => {
2429                        self.push(PerlValue::UNDEF);
2430                        Ok(())
2431                    }
2432                    Op::RuntimeErrorConst(idx) => {
2433                        let msg = self.constant(*idx).to_string();
2434                        let line = self.line();
2435                        Err(crate::error::PerlError::runtime(msg, line))
2436                    }
2437                    Op::BarewordRvalue(name_idx) => {
2438                        let name = names[*name_idx as usize].clone();
2439                        let line = self.line();
2440                        let out = vm_interp_result(
2441                            self.interp.resolve_bareword_rvalue(
2442                                &name,
2443                                crate::interpreter::WantarrayCtx::Scalar,
2444                                line,
2445                            ),
2446                            line,
2447                        )?;
2448                        self.push(out);
2449                        Ok(())
2450                    }
2451
2452                    // ── Stack ──
2453                    Op::Pop => {
2454                        let v = self.pop();
2455                        // Drain iterators used as void statements so side effects fire.
2456                        if v.is_iterator() {
2457                            let iter = v.into_iterator();
2458                            while iter.next_item().is_some() {}
2459                        }
2460                        Ok(())
2461                    }
2462                    Op::Dup => {
2463                        let v = self.peek().dup_stack();
2464                        self.push(v);
2465                        Ok(())
2466                    }
2467                    Op::Dup2 => {
2468                        let b = self.pop();
2469                        let a = self.pop();
2470                        self.push(a.dup_stack());
2471                        self.push(b.dup_stack());
2472                        self.push(a);
2473                        self.push(b);
2474                        Ok(())
2475                    }
2476                    Op::Swap => {
2477                        let top = self.pop();
2478                        let below = self.pop();
2479                        self.push(top);
2480                        self.push(below);
2481                        Ok(())
2482                    }
2483                    Op::Rot => {
2484                        let c = self.pop();
2485                        let b = self.pop();
2486                        let a = self.pop();
2487                        self.push(b);
2488                        self.push(c);
2489                        self.push(a);
2490                        Ok(())
2491                    }
2492                    Op::ValueScalarContext => {
2493                        let v = self.pop();
2494                        self.push(v.scalar_context());
2495                        Ok(())
2496                    }
2497
2498                    // ── Scalars ──
2499                    Op::GetScalar(idx) => {
2500                        let n = names[*idx as usize].as_str();
2501                        let val = self.interp.get_special_var(n);
2502                        self.push(val);
2503                        Ok(())
2504                    }
2505                    Op::GetScalarPlain(idx) => {
2506                        let n = names[*idx as usize].as_str();
2507                        let val = self.interp.scope.get_scalar(n);
2508                        self.push(val);
2509                        Ok(())
2510                    }
2511                    Op::SetScalar(idx) => {
2512                        let val = self.pop();
2513                        let n = names[*idx as usize].as_str();
2514                        self.require_scalar_mutable(n)?;
2515                        self.interp.maybe_invalidate_regex_capture_memo(n);
2516                        self.interp
2517                            .set_special_var(n, &val)
2518                            .map_err(|e| e.at_line(self.line()))?;
2519                        Ok(())
2520                    }
2521                    Op::SetScalarPlain(idx) => {
2522                        let val = self.pop();
2523                        let n = names[*idx as usize].as_str();
2524                        self.require_scalar_mutable(n)?;
2525                        self.interp.maybe_invalidate_regex_capture_memo(n);
2526                        self.interp
2527                            .scope
2528                            .set_scalar(n, val)
2529                            .map_err(|e| e.at_line(self.line()))?;
2530                        Ok(())
2531                    }
2532                    Op::SetScalarKeep(idx) => {
2533                        let val = self.peek().dup_stack();
2534                        let n = names[*idx as usize].as_str();
2535                        self.require_scalar_mutable(n)?;
2536                        self.interp.maybe_invalidate_regex_capture_memo(n);
2537                        self.interp
2538                            .set_special_var(n, &val)
2539                            .map_err(|e| e.at_line(self.line()))?;
2540                        Ok(())
2541                    }
2542                    Op::SetScalarKeepPlain(idx) => {
2543                        let val = self.peek().dup_stack();
2544                        let n = names[*idx as usize].as_str();
2545                        self.require_scalar_mutable(n)?;
2546                        self.interp.maybe_invalidate_regex_capture_memo(n);
2547                        self.interp
2548                            .scope
2549                            .set_scalar(n, val)
2550                            .map_err(|e| e.at_line(self.line()))?;
2551                        Ok(())
2552                    }
2553                    Op::DeclareScalar(idx) => {
2554                        let val = self.pop();
2555                        let n = names[*idx as usize].as_str();
2556                        self.interp
2557                            .scope
2558                            .declare_scalar_frozen(n, val, false, None)
2559                            .map_err(|e| e.at_line(self.line()))?;
2560                        Ok(())
2561                    }
2562                    Op::DeclareScalarFrozen(idx) => {
2563                        let val = self.pop();
2564                        let n = names[*idx as usize].as_str();
2565                        self.interp
2566                            .scope
2567                            .declare_scalar_frozen(n, val, true, None)
2568                            .map_err(|e| e.at_line(self.line()))?;
2569                        Ok(())
2570                    }
2571                    Op::DeclareScalarTyped(idx, tyb) => {
2572                        let val = self.pop();
2573                        let n = names[*idx as usize].as_str();
2574                        let ty = PerlTypeName::from_byte(*tyb).ok_or_else(|| {
2575                            PerlError::runtime(
2576                                format!("invalid typed scalar type byte {}", tyb),
2577                                self.line(),
2578                            )
2579                        })?;
2580                        self.interp
2581                            .scope
2582                            .declare_scalar_frozen(n, val, false, Some(ty))
2583                            .map_err(|e| e.at_line(self.line()))?;
2584                        Ok(())
2585                    }
2586                    Op::DeclareScalarTypedFrozen(idx, tyb) => {
2587                        let val = self.pop();
2588                        let n = names[*idx as usize].as_str();
2589                        let ty = PerlTypeName::from_byte(*tyb).ok_or_else(|| {
2590                            PerlError::runtime(
2591                                format!("invalid typed scalar type byte {}", tyb),
2592                                self.line(),
2593                            )
2594                        })?;
2595                        self.interp
2596                            .scope
2597                            .declare_scalar_frozen(n, val, true, Some(ty))
2598                            .map_err(|e| e.at_line(self.line()))?;
2599                        Ok(())
2600                    }
2601
2602                    // ── State variables (persist across calls) ──
2603                    Op::DeclareStateScalar(idx) => {
2604                        let init_val = self.pop();
2605                        let n = names[*idx as usize].as_str();
2606                        // Key by source line + name (matches interpreter's state_key format)
2607                        let state_key = format!("{}:{}", self.line(), n);
2608                        let val = if let Some(prev) = self.interp.state_vars.get(&state_key) {
2609                            prev.clone()
2610                        } else {
2611                            self.interp
2612                                .state_vars
2613                                .insert(state_key.clone(), init_val.clone());
2614                            init_val
2615                        };
2616                        self.interp
2617                            .scope
2618                            .declare_scalar_frozen(n, val, false, None)
2619                            .map_err(|e| e.at_line(self.line()))?;
2620                        // Register for save-back when scope pops
2621                        if let Some(frame) = self.interp.state_bindings_stack.last_mut() {
2622                            frame.push((n.to_string(), state_key));
2623                        }
2624                        Ok(())
2625                    }
2626                    Op::DeclareStateArray(idx) => {
2627                        let init_val = self.pop();
2628                        let n = names[*idx as usize].as_str();
2629                        let state_key = format!("{}:{}", self.line(), n);
2630                        let val = if let Some(prev) = self.interp.state_vars.get(&state_key) {
2631                            prev.clone()
2632                        } else {
2633                            self.interp
2634                                .state_vars
2635                                .insert(state_key.clone(), init_val.clone());
2636                            init_val
2637                        };
2638                        self.interp.scope.declare_array(n, val.to_list());
2639                        Ok(())
2640                    }
2641                    Op::DeclareStateHash(idx) => {
2642                        let init_val = self.pop();
2643                        let n = names[*idx as usize].as_str();
2644                        let state_key = format!("{}:{}", self.line(), n);
2645                        let val = if let Some(prev) = self.interp.state_vars.get(&state_key) {
2646                            prev.clone()
2647                        } else {
2648                            self.interp
2649                                .state_vars
2650                                .insert(state_key.clone(), init_val.clone());
2651                            init_val
2652                        };
2653                        let items = val.to_list();
2654                        let mut map = IndexMap::new();
2655                        let mut i = 0;
2656                        while i + 1 < items.len() {
2657                            map.insert(items[i].to_string(), items[i + 1].clone());
2658                            i += 2;
2659                        }
2660                        self.interp.scope.declare_hash(n, map);
2661                        Ok(())
2662                    }
2663
2664                    // ── Arrays ──
2665                    Op::GetArray(idx) => {
2666                        let n = names[*idx as usize].as_str();
2667                        let arr = self.interp.scope.get_array(n);
2668                        self.push(PerlValue::array(arr));
2669                        Ok(())
2670                    }
2671                    Op::SetArray(idx) => {
2672                        let val = self.pop();
2673                        let n = names[*idx as usize].as_str();
2674                        self.require_array_mutable(n)?;
2675                        self.interp
2676                            .scope
2677                            .set_array(n, val.to_list())
2678                            .map_err(|e| e.at_line(self.line()))?;
2679                        Ok(())
2680                    }
2681                    Op::DeclareArray(idx) => {
2682                        let val = self.pop();
2683                        let n = names[*idx as usize].as_str();
2684                        self.interp.scope.declare_array(n, val.to_list());
2685                        Ok(())
2686                    }
2687                    Op::DeclareArrayFrozen(idx) => {
2688                        let val = self.pop();
2689                        let n = names[*idx as usize].as_str();
2690                        self.interp
2691                            .scope
2692                            .declare_array_frozen(n, val.to_list(), true);
2693                        Ok(())
2694                    }
2695                    Op::GetArrayElem(idx) => {
2696                        let index = self.pop().to_int();
2697                        let n = names[*idx as usize].as_str();
2698                        let val = self.interp.scope.get_array_element(n, index);
2699                        self.push(val);
2700                        Ok(())
2701                    }
2702                    Op::ExistsArrayElem(idx) => {
2703                        let index = self.pop().to_int();
2704                        let n = names[*idx as usize].as_str();
2705                        let yes = self.interp.scope.exists_array_element(n, index);
2706                        self.push(PerlValue::integer(if yes { 1 } else { 0 }));
2707                        Ok(())
2708                    }
2709                    Op::DeleteArrayElem(idx) => {
2710                        let index = self.pop().to_int();
2711                        let n = names[*idx as usize].as_str();
2712                        self.require_array_mutable(n)?;
2713                        let v = self
2714                            .interp
2715                            .scope
2716                            .delete_array_element(n, index)
2717                            .map_err(|e| e.at_line(self.line()))?;
2718                        self.push(v);
2719                        Ok(())
2720                    }
2721                    Op::SetArrayElem(idx) => {
2722                        let index = self.pop().to_int();
2723                        let val = self.pop();
2724                        let n = names[*idx as usize].as_str();
2725                        self.require_array_mutable(n)?;
2726                        self.interp
2727                            .scope
2728                            .set_array_element(n, index, val)
2729                            .map_err(|e| e.at_line(self.line()))?;
2730                        Ok(())
2731                    }
2732                    Op::SetArrayElemKeep(idx) => {
2733                        let index = self.pop().to_int();
2734                        let val = self.pop();
2735                        let val_keep = val.clone();
2736                        let n = names[*idx as usize].as_str();
2737                        self.require_array_mutable(n)?;
2738                        let line = self.line();
2739                        self.interp
2740                            .scope
2741                            .set_array_element(n, index, val)
2742                            .map_err(|e| e.at_line(line))?;
2743                        self.push(val_keep);
2744                        Ok(())
2745                    }
2746                    Op::PushArray(idx) => {
2747                        let val = self.pop();
2748                        let n = names[*idx as usize].as_str();
2749                        self.require_array_mutable(n)?;
2750                        let line = self.line();
2751                        if let Some(items) = val.as_array_vec() {
2752                            for item in items {
2753                                self.interp
2754                                    .scope
2755                                    .push_to_array(n, item)
2756                                    .map_err(|e| e.at_line(line))?;
2757                            }
2758                        } else {
2759                            self.interp
2760                                .scope
2761                                .push_to_array(n, val)
2762                                .map_err(|e| e.at_line(line))?;
2763                        }
2764                        Ok(())
2765                    }
2766                    Op::PopArray(idx) => {
2767                        let n = names[*idx as usize].as_str();
2768                        self.require_array_mutable(n)?;
2769                        let line = self.line();
2770                        let val = self
2771                            .interp
2772                            .scope
2773                            .pop_from_array(n)
2774                            .map_err(|e| e.at_line(line))?;
2775                        self.push(val);
2776                        Ok(())
2777                    }
2778                    Op::ShiftArray(idx) => {
2779                        let n = names[*idx as usize].as_str();
2780                        self.require_array_mutable(n)?;
2781                        let line = self.line();
2782                        let val = self
2783                            .interp
2784                            .scope
2785                            .shift_from_array(n)
2786                            .map_err(|e| e.at_line(line))?;
2787                        self.push(val);
2788                        Ok(())
2789                    }
2790                    Op::PushArrayDeref => {
2791                        let val = self.pop();
2792                        let r = self.pop();
2793                        let line = self.line();
2794                        vm_interp_result(
2795                            self.interp
2796                                .push_array_deref_value(r.clone(), val, line)
2797                                .map(|_| PerlValue::UNDEF),
2798                            line,
2799                        )?;
2800                        self.push(r);
2801                        Ok(())
2802                    }
2803                    Op::ArrayDerefLen => {
2804                        let r = self.pop();
2805                        let line = self.line();
2806                        let n = match self.interp.array_deref_len(r, line) {
2807                            Ok(n) => n,
2808                            Err(FlowOrError::Error(e)) => return Err(e),
2809                            Err(FlowOrError::Flow(_)) => {
2810                                return Err(PerlError::runtime(
2811                                    "unexpected flow in tree-assisted opcode",
2812                                    line,
2813                                ));
2814                            }
2815                        };
2816                        self.push(PerlValue::integer(n));
2817                        Ok(())
2818                    }
2819                    Op::PopArrayDeref => {
2820                        let r = self.pop();
2821                        let line = self.line();
2822                        let v = vm_interp_result(self.interp.pop_array_deref(r, line), line)?;
2823                        self.push(v);
2824                        Ok(())
2825                    }
2826                    Op::ShiftArrayDeref => {
2827                        let r = self.pop();
2828                        let line = self.line();
2829                        let v = vm_interp_result(self.interp.shift_array_deref(r, line), line)?;
2830                        self.push(v);
2831                        Ok(())
2832                    }
2833                    Op::UnshiftArrayDeref(n_extra) => {
2834                        let n = *n_extra as usize;
2835                        let mut vals: Vec<PerlValue> = Vec::with_capacity(n);
2836                        for _ in 0..n {
2837                            vals.push(self.pop());
2838                        }
2839                        vals.reverse();
2840                        let r = self.pop();
2841                        let line = self.line();
2842                        let len = match self.interp.unshift_array_deref_multi(r, vals, line) {
2843                            Ok(n) => n,
2844                            Err(FlowOrError::Error(e)) => return Err(e),
2845                            Err(FlowOrError::Flow(_)) => {
2846                                return Err(PerlError::runtime(
2847                                    "unexpected flow in tree-assisted opcode",
2848                                    line,
2849                                ));
2850                            }
2851                        };
2852                        self.push(PerlValue::integer(len));
2853                        Ok(())
2854                    }
2855                    Op::SpliceArrayDeref(n_rep) => {
2856                        let n = *n_rep as usize;
2857                        let mut rep_vals: Vec<PerlValue> = Vec::with_capacity(n);
2858                        for _ in 0..n {
2859                            rep_vals.push(self.pop());
2860                        }
2861                        rep_vals.reverse();
2862                        let length_val = self.pop();
2863                        let offset_val = self.pop();
2864                        let aref = self.pop();
2865                        let line = self.line();
2866                        let v = vm_interp_result(
2867                            self.interp
2868                                .splice_array_deref(aref, offset_val, length_val, rep_vals, line),
2869                            line,
2870                        )?;
2871                        self.push(v);
2872                        Ok(())
2873                    }
2874                    Op::ArrayLen(idx) => {
2875                        let len = self.interp.scope.array_len(&self.names[*idx as usize]);
2876                        self.push(PerlValue::integer(len as i64));
2877                        Ok(())
2878                    }
2879                    Op::ArraySlicePart(idx) => {
2880                        let spec = self.pop();
2881                        let n = names[*idx as usize].as_str();
2882                        let mut out = Vec::new();
2883                        if let Some(indices) = spec.as_array_vec() {
2884                            for pv in indices {
2885                                out.push(self.interp.scope.get_array_element(n, pv.to_int()));
2886                            }
2887                        } else {
2888                            out.push(self.interp.scope.get_array_element(n, spec.to_int()));
2889                        }
2890                        self.push(PerlValue::array(out));
2891                        Ok(())
2892                    }
2893                    Op::ArrayConcatTwo => {
2894                        let b = self.pop();
2895                        let a = self.pop();
2896                        let mut av = a.as_array_vec().unwrap_or_else(|| vec![a]);
2897                        let bv = b.as_array_vec().unwrap_or_else(|| vec![b]);
2898                        av.extend(bv);
2899                        self.push(PerlValue::array(av));
2900                        Ok(())
2901                    }
2902
2903                    // ── Hashes ──
2904                    Op::GetHash(idx) => {
2905                        let n = names[*idx as usize].as_str();
2906                        self.interp.touch_env_hash(n);
2907                        let h = self.interp.scope.get_hash(n);
2908                        self.push(PerlValue::hash(h));
2909                        Ok(())
2910                    }
2911                    Op::SetHash(idx) => {
2912                        let val = self.pop();
2913                        let items = val.to_list();
2914                        let mut map = IndexMap::new();
2915                        let mut i = 0;
2916                        while i + 1 < items.len() {
2917                            map.insert(items[i].to_string(), items[i + 1].clone());
2918                            i += 2;
2919                        }
2920                        let n = names[*idx as usize].as_str();
2921                        self.require_hash_mutable(n)?;
2922                        self.interp
2923                            .scope
2924                            .set_hash(n, map)
2925                            .map_err(|e| e.at_line(self.line()))?;
2926                        Ok(())
2927                    }
2928                    Op::DeclareHash(idx) => {
2929                        let val = self.pop();
2930                        let items = val.to_list();
2931                        let mut map = IndexMap::new();
2932                        let mut i = 0;
2933                        while i + 1 < items.len() {
2934                            map.insert(items[i].to_string(), items[i + 1].clone());
2935                            i += 2;
2936                        }
2937                        let n = names[*idx as usize].as_str();
2938                        self.interp.scope.declare_hash(n, map);
2939                        Ok(())
2940                    }
2941                    Op::DeclareHashFrozen(idx) => {
2942                        let val = self.pop();
2943                        let items = val.to_list();
2944                        let mut map = IndexMap::new();
2945                        let mut i = 0;
2946                        while i + 1 < items.len() {
2947                            map.insert(items[i].to_string(), items[i + 1].clone());
2948                            i += 2;
2949                        }
2950                        let n = names[*idx as usize].as_str();
2951                        self.interp.scope.declare_hash_frozen(n, map, true);
2952                        Ok(())
2953                    }
2954                    Op::LocalDeclareScalar(idx) => {
2955                        let val = self.pop();
2956                        let n = names[*idx as usize].as_str();
2957                        // `local $X` on a special var (`$/`, `$\`, `$,`, `$"`, …) — see
2958                        // Perl's `local` handler. Save prior value to
2959                        // the interpreter's `special_var_restore_frames` so `scope_pop_hook`
2960                        // restores the backing field on block exit.
2961                        if Interpreter::is_special_scalar_name_for_set(n) {
2962                            let old = self.interp.get_special_var(n);
2963                            if let Some(frame) = self.interp.special_var_restore_frames.last_mut() {
2964                                frame.push((n.to_string(), old));
2965                            }
2966                            let line = self.line();
2967                            self.interp
2968                                .set_special_var(n, &val)
2969                                .map_err(|e| e.at_line(line))?;
2970                        }
2971                        self.interp
2972                            .scope
2973                            .local_set_scalar(n, val.clone())
2974                            .map_err(|e| e.at_line(self.line()))?;
2975                        self.push(val);
2976                        Ok(())
2977                    }
2978                    Op::LocalDeclareArray(idx) => {
2979                        let val = self.pop();
2980                        let n = names[*idx as usize].as_str();
2981                        self.interp
2982                            .scope
2983                            .local_set_array(n, val.to_list())
2984                            .map_err(|e| e.at_line(self.line()))?;
2985                        self.push(val);
2986                        Ok(())
2987                    }
2988                    Op::LocalDeclareHash(idx) => {
2989                        let val = self.pop();
2990                        let items = val.to_list();
2991                        let mut map = IndexMap::new();
2992                        let mut i = 0;
2993                        while i + 1 < items.len() {
2994                            map.insert(items[i].to_string(), items[i + 1].clone());
2995                            i += 2;
2996                        }
2997                        let n = names[*idx as usize].as_str();
2998                        self.interp.touch_env_hash(n);
2999                        self.interp
3000                            .scope
3001                            .local_set_hash(n, map)
3002                            .map_err(|e| e.at_line(self.line()))?;
3003                        self.push(val);
3004                        Ok(())
3005                    }
3006                    Op::LocalDeclareHashElement(idx) => {
3007                        let key = self.pop().to_string();
3008                        let val = self.pop();
3009                        let n = names[*idx as usize].as_str();
3010                        self.interp.touch_env_hash(n);
3011                        self.interp
3012                            .scope
3013                            .local_set_hash_element(n, key.as_str(), val.clone())
3014                            .map_err(|e| e.at_line(self.line()))?;
3015                        self.push(val);
3016                        Ok(())
3017                    }
3018                    Op::LocalDeclareArrayElement(idx) => {
3019                        let index = self.pop().to_int();
3020                        let val = self.pop();
3021                        let n = names[*idx as usize].as_str();
3022                        self.require_array_mutable(n)?;
3023                        self.interp
3024                            .scope
3025                            .local_set_array_element(n, index, val.clone())
3026                            .map_err(|e| e.at_line(self.line()))?;
3027                        self.push(val);
3028                        Ok(())
3029                    }
3030                    Op::LocalDeclareTypeglob(lhs_i, rhs_opt) => {
3031                        let lhs = names[*lhs_i as usize].as_str();
3032                        let rhs = rhs_opt.map(|i| names[i as usize].as_str());
3033                        let line = self.line();
3034                        self.interp
3035                            .local_declare_typeglob(lhs, rhs, line)
3036                            .map_err(|e| e.at_line(line))?;
3037                        Ok(())
3038                    }
3039                    Op::LocalDeclareTypeglobDynamic(rhs_opt) => {
3040                        let lhs = self.pop().to_string();
3041                        let rhs = rhs_opt.map(|i| names[i as usize].as_str());
3042                        let line = self.line();
3043                        self.interp
3044                            .local_declare_typeglob(lhs.as_str(), rhs, line)
3045                            .map_err(|e| e.at_line(line))?;
3046                        Ok(())
3047                    }
3048                    Op::GetHashElem(idx) => {
3049                        let key = self.pop().to_string();
3050                        let n = names[*idx as usize].as_str();
3051                        self.interp.touch_env_hash(n);
3052                        let val = self.interp.scope.get_hash_element(n, &key);
3053                        self.push(val);
3054                        Ok(())
3055                    }
3056                    Op::SetHashElem(idx) => {
3057                        let key = self.pop().to_string();
3058                        let val = self.pop();
3059                        let n = names[*idx as usize].as_str();
3060                        self.require_hash_mutable(n)?;
3061                        self.interp.touch_env_hash(n);
3062                        self.interp
3063                            .scope
3064                            .set_hash_element(n, &key, val)
3065                            .map_err(|e| e.at_line(self.line()))?;
3066                        Ok(())
3067                    }
3068                    Op::SetHashElemKeep(idx) => {
3069                        let key = self.pop().to_string();
3070                        let val = self.pop();
3071                        let val_keep = val.clone();
3072                        let n = names[*idx as usize].as_str();
3073                        self.require_hash_mutable(n)?;
3074                        self.interp.touch_env_hash(n);
3075                        let line = self.line();
3076                        self.interp
3077                            .scope
3078                            .set_hash_element(n, &key, val)
3079                            .map_err(|e| e.at_line(line))?;
3080                        self.push(val_keep);
3081                        Ok(())
3082                    }
3083                    Op::DeleteHashElem(idx) => {
3084                        let key = self.pop().to_string();
3085                        let n = names[*idx as usize].as_str();
3086                        self.require_hash_mutable(n)?;
3087                        self.interp.touch_env_hash(n);
3088                        if let Some(obj) = self.interp.tied_hashes.get(n).cloned() {
3089                            let class = obj
3090                                .as_blessed_ref()
3091                                .map(|b| b.class.clone())
3092                                .unwrap_or_default();
3093                            let full = format!("{}::DELETE", class);
3094                            if let Some(sub) = self.interp.subs.get(&full).cloned() {
3095                                let line = self.line();
3096                                let v = vm_interp_result(
3097                                    self.interp.call_sub(
3098                                        &sub,
3099                                        vec![obj, PerlValue::string(key)],
3100                                        WantarrayCtx::Scalar,
3101                                        line,
3102                                    ),
3103                                    line,
3104                                )?;
3105                                self.push(v);
3106                                return Ok(());
3107                            }
3108                        }
3109                        let val = self
3110                            .interp
3111                            .scope
3112                            .delete_hash_element(n, &key)
3113                            .map_err(|e| e.at_line(self.line()))?;
3114                        self.push(val);
3115                        Ok(())
3116                    }
3117                    Op::ExistsHashElem(idx) => {
3118                        let key = self.pop().to_string();
3119                        let n = names[*idx as usize].as_str();
3120                        self.interp.touch_env_hash(n);
3121                        if let Some(obj) = self.interp.tied_hashes.get(n).cloned() {
3122                            let class = obj
3123                                .as_blessed_ref()
3124                                .map(|b| b.class.clone())
3125                                .unwrap_or_default();
3126                            let full = format!("{}::EXISTS", class);
3127                            if let Some(sub) = self.interp.subs.get(&full).cloned() {
3128                                let line = self.line();
3129                                let v = vm_interp_result(
3130                                    self.interp.call_sub(
3131                                        &sub,
3132                                        vec![obj, PerlValue::string(key)],
3133                                        WantarrayCtx::Scalar,
3134                                        line,
3135                                    ),
3136                                    line,
3137                                )?;
3138                                self.push(v);
3139                                return Ok(());
3140                            }
3141                        }
3142                        let exists = self.interp.scope.exists_hash_element(n, &key);
3143                        self.push(PerlValue::integer(if exists { 1 } else { 0 }));
3144                        Ok(())
3145                    }
3146                    Op::ExistsArrowHashElem => {
3147                        let key = self.pop().to_string();
3148                        let container = self.pop();
3149                        let line = self.line();
3150                        let yes = vm_interp_result(
3151                            self.interp
3152                                .exists_arrow_hash_element(container, &key, line)
3153                                .map(|b| PerlValue::integer(if b { 1 } else { 0 }))
3154                                .map_err(FlowOrError::Error),
3155                            line,
3156                        )?;
3157                        self.push(yes);
3158                        Ok(())
3159                    }
3160                    Op::DeleteArrowHashElem => {
3161                        let key = self.pop().to_string();
3162                        let container = self.pop();
3163                        let line = self.line();
3164                        let v = vm_interp_result(
3165                            self.interp
3166                                .delete_arrow_hash_element(container, &key, line)
3167                                .map_err(FlowOrError::Error),
3168                            line,
3169                        )?;
3170                        self.push(v);
3171                        Ok(())
3172                    }
3173                    Op::ExistsArrowArrayElem => {
3174                        let idx = self.pop().to_int();
3175                        let container = self.pop();
3176                        let line = self.line();
3177                        let yes = vm_interp_result(
3178                            self.interp
3179                                .exists_arrow_array_element(container, idx, line)
3180                                .map(|b| PerlValue::integer(if b { 1 } else { 0 }))
3181                                .map_err(FlowOrError::Error),
3182                            line,
3183                        )?;
3184                        self.push(yes);
3185                        Ok(())
3186                    }
3187                    Op::DeleteArrowArrayElem => {
3188                        let idx = self.pop().to_int();
3189                        let container = self.pop();
3190                        let line = self.line();
3191                        let v = vm_interp_result(
3192                            self.interp
3193                                .delete_arrow_array_element(container, idx, line)
3194                                .map_err(FlowOrError::Error),
3195                            line,
3196                        )?;
3197                        self.push(v);
3198                        Ok(())
3199                    }
3200                    Op::HashKeys(idx) => {
3201                        let n = names[*idx as usize].as_str();
3202                        self.interp.touch_env_hash(n);
3203                        let h = self.interp.scope.get_hash(n);
3204                        let keys: Vec<PerlValue> =
3205                            h.keys().map(|k| PerlValue::string(k.clone())).collect();
3206                        self.push(PerlValue::array(keys));
3207                        Ok(())
3208                    }
3209                    Op::HashKeysScalar(idx) => {
3210                        let n = names[*idx as usize].as_str();
3211                        self.interp.touch_env_hash(n);
3212                        let h = self.interp.scope.get_hash(n);
3213                        self.push(PerlValue::integer(h.len() as i64));
3214                        Ok(())
3215                    }
3216                    Op::HashValues(idx) => {
3217                        let n = names[*idx as usize].as_str();
3218                        self.interp.touch_env_hash(n);
3219                        let h = self.interp.scope.get_hash(n);
3220                        let vals: Vec<PerlValue> = h.values().cloned().collect();
3221                        self.push(PerlValue::array(vals));
3222                        Ok(())
3223                    }
3224                    Op::HashValuesScalar(idx) => {
3225                        let n = names[*idx as usize].as_str();
3226                        self.interp.touch_env_hash(n);
3227                        let h = self.interp.scope.get_hash(n);
3228                        self.push(PerlValue::integer(h.len() as i64));
3229                        Ok(())
3230                    }
3231                    Op::KeysFromValue => {
3232                        let val = self.pop();
3233                        let line = self.line();
3234                        let v = vm_interp_result(Interpreter::keys_from_value(val, line), line)?;
3235                        self.push(v);
3236                        Ok(())
3237                    }
3238                    Op::KeysFromValueScalar => {
3239                        let val = self.pop();
3240                        let line = self.line();
3241                        let v = vm_interp_result(Interpreter::keys_from_value(val, line), line)?;
3242                        let n = v.as_array_vec().map(|a| a.len()).unwrap_or(0) as i64;
3243                        self.push(PerlValue::integer(n));
3244                        Ok(())
3245                    }
3246                    Op::ValuesFromValue => {
3247                        let val = self.pop();
3248                        let line = self.line();
3249                        let v = vm_interp_result(Interpreter::values_from_value(val, line), line)?;
3250                        self.push(v);
3251                        Ok(())
3252                    }
3253                    Op::ValuesFromValueScalar => {
3254                        let val = self.pop();
3255                        let line = self.line();
3256                        let v = vm_interp_result(Interpreter::values_from_value(val, line), line)?;
3257                        let n = v.as_array_vec().map(|a| a.len()).unwrap_or(0) as i64;
3258                        self.push(PerlValue::integer(n));
3259                        Ok(())
3260                    }
3261
3262                    // ── Arithmetic (integer fast paths) ──
3263                    Op::Add => {
3264                        let b = self.pop();
3265                        let a = self.pop();
3266                        self.push_binop_with_overload(BinOp::Add, a, b, |a, b| {
3267                            Ok(
3268                                if let (Some(x), Some(y)) = (a.as_integer(), b.as_integer()) {
3269                                    PerlValue::integer(x.wrapping_add(y))
3270                                } else {
3271                                    PerlValue::float(a.to_number() + b.to_number())
3272                                },
3273                            )
3274                        })
3275                    }
3276                    Op::Sub => {
3277                        let b = self.pop();
3278                        let a = self.pop();
3279                        self.push_binop_with_overload(BinOp::Sub, a, b, |a, b| {
3280                            Ok(
3281                                if let (Some(x), Some(y)) = (a.as_integer(), b.as_integer()) {
3282                                    PerlValue::integer(x.wrapping_sub(y))
3283                                } else {
3284                                    PerlValue::float(a.to_number() - b.to_number())
3285                                },
3286                            )
3287                        })
3288                    }
3289                    Op::Mul => {
3290                        let b = self.pop();
3291                        let a = self.pop();
3292                        self.push_binop_with_overload(BinOp::Mul, a, b, |a, b| {
3293                            Ok(
3294                                if let (Some(x), Some(y)) = (a.as_integer(), b.as_integer()) {
3295                                    PerlValue::integer(x.wrapping_mul(y))
3296                                } else {
3297                                    PerlValue::float(a.to_number() * b.to_number())
3298                                },
3299                            )
3300                        })
3301                    }
3302                    Op::Div => {
3303                        let b = self.pop();
3304                        let a = self.pop();
3305                        let line = self.line();
3306                        self.push_binop_with_overload(BinOp::Div, a, b, |a, b| {
3307                            if let (Some(x), Some(y)) = (a.as_integer(), b.as_integer()) {
3308                                if y == 0 {
3309                                    return Err(PerlError::runtime(
3310                                        "Illegal division by zero",
3311                                        line,
3312                                    ));
3313                                }
3314                                Ok(if x % y == 0 {
3315                                    PerlValue::integer(x / y)
3316                                } else {
3317                                    PerlValue::float(x as f64 / y as f64)
3318                                })
3319                            } else {
3320                                let d = b.to_number();
3321                                if d == 0.0 {
3322                                    return Err(PerlError::runtime(
3323                                        "Illegal division by zero",
3324                                        line,
3325                                    ));
3326                                }
3327                                Ok(PerlValue::float(a.to_number() / d))
3328                            }
3329                        })
3330                    }
3331                    Op::Mod => {
3332                        let b = self.pop();
3333                        let a = self.pop();
3334                        let line = self.line();
3335                        self.push_binop_with_overload(BinOp::Mod, a, b, |a, b| {
3336                            let b = b.to_int();
3337                            let a = a.to_int();
3338                            if b == 0 {
3339                                return Err(PerlError::runtime("Illegal modulus zero", line));
3340                            }
3341                            Ok(PerlValue::integer(a % b))
3342                        })
3343                    }
3344                    Op::Pow => {
3345                        let b = self.pop();
3346                        let a = self.pop();
3347                        self.push_binop_with_overload(BinOp::Pow, a, b, |a, b| {
3348                            Ok(
3349                                if let (Some(x), Some(y)) = (a.as_integer(), b.as_integer()) {
3350                                    if let Some(r) = (y >= 0)
3351                                        .then(|| u32::try_from(y).ok())
3352                                        .flatten()
3353                                        .and_then(|yu| x.checked_pow(yu))
3354                                    {
3355                                        PerlValue::integer(r)
3356                                    } else {
3357                                        PerlValue::float(a.to_number().powf(b.to_number()))
3358                                    }
3359                                } else {
3360                                    PerlValue::float(a.to_number().powf(b.to_number()))
3361                                },
3362                            )
3363                        })
3364                    }
3365                    Op::Negate => {
3366                        let a = self.pop();
3367                        let line = self.line();
3368                        if let Some(exec_res) =
3369                            self.interp.try_overload_unary_dispatch("neg", &a, line)
3370                        {
3371                            self.push(vm_interp_result(exec_res, line)?);
3372                        } else {
3373                            self.push(if let Some(n) = a.as_integer() {
3374                                PerlValue::integer(-n)
3375                            } else {
3376                                PerlValue::float(-a.to_number())
3377                            });
3378                        }
3379                        Ok(())
3380                    }
3381                    Op::Inc => {
3382                        let a = self.pop();
3383                        self.push(if let Some(n) = a.as_integer() {
3384                            PerlValue::integer(n.wrapping_add(1))
3385                        } else {
3386                            PerlValue::float(a.to_number() + 1.0)
3387                        });
3388                        Ok(())
3389                    }
3390                    Op::Dec => {
3391                        let a = self.pop();
3392                        self.push(if let Some(n) = a.as_integer() {
3393                            PerlValue::integer(n.wrapping_sub(1))
3394                        } else {
3395                            PerlValue::float(a.to_number() - 1.0)
3396                        });
3397                        Ok(())
3398                    }
3399
3400                    // ── String ──
3401                    Op::Concat => {
3402                        let b = self.pop();
3403                        let a = self.pop();
3404                        let out = self.concat_stack_values(a, b)?;
3405                        self.push(out);
3406                        Ok(())
3407                    }
3408                    Op::ArrayStringifyListSep => {
3409                        let raw = self.pop();
3410                        let v = self.interp.peel_array_ref_for_list_join(raw);
3411                        let sep = self.interp.list_separator.clone();
3412                        let list = v.to_list();
3413                        let joined = list
3414                            .iter()
3415                            .map(|x| x.to_string())
3416                            .collect::<Vec<_>>()
3417                            .join(&sep);
3418                        self.push(PerlValue::string(joined));
3419                        Ok(())
3420                    }
3421                    Op::StringRepeat => {
3422                        let n = self.pop().to_int().max(0) as usize;
3423                        let val = self.pop();
3424                        self.push(PerlValue::string(val.to_string().repeat(n)));
3425                        Ok(())
3426                    }
3427                    Op::ProcessCaseEscapes => {
3428                        let val = self.pop();
3429                        let s = val.to_string();
3430                        let processed = Interpreter::process_case_escapes(&s);
3431                        self.push(PerlValue::string(processed));
3432                        Ok(())
3433                    }
3434
3435                    // ── Numeric comparison ──
3436                    Op::NumEq => {
3437                        let b = self.pop();
3438                        let a = self.pop();
3439                        self.push_binop_with_overload(BinOp::NumEq, a.clone(), b.clone(), |a, b| {
3440                            // Struct equality: compare all fields
3441                            if let (Some(sa), Some(sb)) = (a.as_struct_inst(), b.as_struct_inst()) {
3442                                if sa.def.name != sb.def.name {
3443                                    return Ok(PerlValue::integer(0));
3444                                }
3445                                let av = sa.get_values();
3446                                let bv = sb.get_values();
3447                                let eq = av.len() == bv.len()
3448                                    && av.iter().zip(bv.iter()).all(|(x, y)| x.struct_field_eq(y));
3449                                Ok(PerlValue::integer(if eq { 1 } else { 0 }))
3450                            } else {
3451                                Ok(int_cmp(a, b, |x, y| x == y, |x, y| x == y))
3452                            }
3453                        })
3454                    }
3455                    Op::NumNe => {
3456                        let b = self.pop();
3457                        let a = self.pop();
3458                        self.push_binop_with_overload(BinOp::NumNe, a, b, |a, b| {
3459                            Ok(int_cmp(a, b, |x, y| x != y, |x, y| x != y))
3460                        })
3461                    }
3462                    Op::NumLt => {
3463                        let b = self.pop();
3464                        let a = self.pop();
3465                        self.push_binop_with_overload(BinOp::NumLt, a, b, |a, b| {
3466                            Ok(int_cmp(a, b, |x, y| x < y, |x, y| x < y))
3467                        })
3468                    }
3469                    Op::NumGt => {
3470                        let b = self.pop();
3471                        let a = self.pop();
3472                        self.push_binop_with_overload(BinOp::NumGt, a, b, |a, b| {
3473                            Ok(int_cmp(a, b, |x, y| x > y, |x, y| x > y))
3474                        })
3475                    }
3476                    Op::NumLe => {
3477                        let b = self.pop();
3478                        let a = self.pop();
3479                        self.push_binop_with_overload(BinOp::NumLe, a, b, |a, b| {
3480                            Ok(int_cmp(a, b, |x, y| x <= y, |x, y| x <= y))
3481                        })
3482                    }
3483                    Op::NumGe => {
3484                        let b = self.pop();
3485                        let a = self.pop();
3486                        self.push_binop_with_overload(BinOp::NumGe, a, b, |a, b| {
3487                            Ok(int_cmp(a, b, |x, y| x >= y, |x, y| x >= y))
3488                        })
3489                    }
3490                    Op::Spaceship => {
3491                        let b = self.pop();
3492                        let a = self.pop();
3493                        self.push_binop_with_overload(BinOp::Spaceship, a, b, |a, b| {
3494                            Ok(
3495                                if let (Some(x), Some(y)) = (a.as_integer(), b.as_integer()) {
3496                                    PerlValue::integer(if x < y {
3497                                        -1
3498                                    } else if x > y {
3499                                        1
3500                                    } else {
3501                                        0
3502                                    })
3503                                } else {
3504                                    let x = a.to_number();
3505                                    let y = b.to_number();
3506                                    PerlValue::integer(if x < y {
3507                                        -1
3508                                    } else if x > y {
3509                                        1
3510                                    } else {
3511                                        0
3512                                    })
3513                                },
3514                            )
3515                        })
3516                    }
3517
3518                    // ── String comparison ──
3519                    Op::StrEq => {
3520                        let b = self.pop();
3521                        let a = self.pop();
3522                        self.push_binop_with_overload(BinOp::StrEq, a, b, |a, b| {
3523                            Ok(PerlValue::integer(if a.str_eq(b) { 1 } else { 0 }))
3524                        })
3525                    }
3526                    Op::StrNe => {
3527                        let b = self.pop();
3528                        let a = self.pop();
3529                        self.push_binop_with_overload(BinOp::StrNe, a, b, |a, b| {
3530                            Ok(PerlValue::integer(if !a.str_eq(b) { 1 } else { 0 }))
3531                        })
3532                    }
3533                    Op::StrLt => {
3534                        let b = self.pop();
3535                        let a = self.pop();
3536                        self.push_binop_with_overload(BinOp::StrLt, a, b, |a, b| {
3537                            Ok(PerlValue::integer(
3538                                if a.str_cmp(b) == std::cmp::Ordering::Less {
3539                                    1
3540                                } else {
3541                                    0
3542                                },
3543                            ))
3544                        })
3545                    }
3546                    Op::StrGt => {
3547                        let b = self.pop();
3548                        let a = self.pop();
3549                        self.push_binop_with_overload(BinOp::StrGt, a, b, |a, b| {
3550                            Ok(PerlValue::integer(
3551                                if a.str_cmp(b) == std::cmp::Ordering::Greater {
3552                                    1
3553                                } else {
3554                                    0
3555                                },
3556                            ))
3557                        })
3558                    }
3559                    Op::StrLe => {
3560                        let b = self.pop();
3561                        let a = self.pop();
3562                        self.push_binop_with_overload(BinOp::StrLe, a, b, |a, b| {
3563                            let o = a.str_cmp(b);
3564                            Ok(PerlValue::integer(
3565                                if matches!(o, std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
3566                                {
3567                                    1
3568                                } else {
3569                                    0
3570                                },
3571                            ))
3572                        })
3573                    }
3574                    Op::StrGe => {
3575                        let b = self.pop();
3576                        let a = self.pop();
3577                        self.push_binop_with_overload(BinOp::StrGe, a, b, |a, b| {
3578                            let o = a.str_cmp(b);
3579                            Ok(PerlValue::integer(
3580                                if matches!(
3581                                    o,
3582                                    std::cmp::Ordering::Greater | std::cmp::Ordering::Equal
3583                                ) {
3584                                    1
3585                                } else {
3586                                    0
3587                                },
3588                            ))
3589                        })
3590                    }
3591                    Op::StrCmp => {
3592                        let b = self.pop();
3593                        let a = self.pop();
3594                        self.push_binop_with_overload(BinOp::StrCmp, a, b, |a, b| {
3595                            let cmp = a.str_cmp(b);
3596                            Ok(PerlValue::integer(match cmp {
3597                                std::cmp::Ordering::Less => -1,
3598                                std::cmp::Ordering::Greater => 1,
3599                                std::cmp::Ordering::Equal => 0,
3600                            }))
3601                        })
3602                    }
3603
3604                    // ── Logical / Bitwise ──
3605                    Op::LogNot => {
3606                        let a = self.pop();
3607                        let line = self.line();
3608                        if let Some(exec_res) =
3609                            self.interp.try_overload_unary_dispatch("bool", &a, line)
3610                        {
3611                            let pv = vm_interp_result(exec_res, line)?;
3612                            self.push(PerlValue::integer(if pv.is_true() { 0 } else { 1 }));
3613                        } else {
3614                            self.push(PerlValue::integer(if a.is_true() { 0 } else { 1 }));
3615                        }
3616                        Ok(())
3617                    }
3618                    Op::BitAnd => {
3619                        let rv = self.pop();
3620                        let lv = self.pop();
3621                        if let Some(s) = crate::value::set_intersection(&lv, &rv) {
3622                            self.push(s);
3623                        } else {
3624                            self.push(PerlValue::integer(lv.to_int() & rv.to_int()));
3625                        }
3626                        Ok(())
3627                    }
3628                    Op::BitOr => {
3629                        let rv = self.pop();
3630                        let lv = self.pop();
3631                        if let Some(s) = crate::value::set_union(&lv, &rv) {
3632                            self.push(s);
3633                        } else {
3634                            self.push(PerlValue::integer(lv.to_int() | rv.to_int()));
3635                        }
3636                        Ok(())
3637                    }
3638                    Op::BitXor => {
3639                        let b = self.pop().to_int();
3640                        let a = self.pop().to_int();
3641                        self.push(PerlValue::integer(a ^ b));
3642                        Ok(())
3643                    }
3644                    Op::BitNot => {
3645                        let a = self.pop().to_int();
3646                        self.push(PerlValue::integer(!a));
3647                        Ok(())
3648                    }
3649                    Op::Shl => {
3650                        let b = self.pop().to_int();
3651                        let a = self.pop().to_int();
3652                        self.push(PerlValue::integer(a << b));
3653                        Ok(())
3654                    }
3655                    Op::Shr => {
3656                        let b = self.pop().to_int();
3657                        let a = self.pop().to_int();
3658                        self.push(PerlValue::integer(a >> b));
3659                        Ok(())
3660                    }
3661
3662                    // ── Control flow ──
3663                    Op::Jump(target) => {
3664                        self.ip = *target;
3665                        Ok(())
3666                    }
3667                    Op::JumpIfTrue(target) => {
3668                        let val = self.pop();
3669                        if val.is_true() {
3670                            self.ip = *target;
3671                        }
3672                        Ok(())
3673                    }
3674                    Op::JumpIfFalse(target) => {
3675                        let val = self.pop();
3676                        if !val.is_true() {
3677                            self.ip = *target;
3678                        }
3679                        Ok(())
3680                    }
3681                    Op::JumpIfFalseKeep(target) => {
3682                        if !self.peek().is_true() {
3683                            self.ip = *target;
3684                        } else {
3685                            self.pop();
3686                        }
3687                        Ok(())
3688                    }
3689                    Op::JumpIfTrueKeep(target) => {
3690                        if self.peek().is_true() {
3691                            self.ip = *target;
3692                        } else {
3693                            self.pop();
3694                        }
3695                        Ok(())
3696                    }
3697                    Op::JumpIfDefinedKeep(target) => {
3698                        if !self.peek().is_undef() {
3699                            self.ip = *target;
3700                        } else {
3701                            self.pop();
3702                        }
3703                        Ok(())
3704                    }
3705
3706                    // ── Increment / Decrement ──
3707                    Op::PreInc(idx) => {
3708                        let n = names[*idx as usize].as_str();
3709                        self.require_scalar_mutable(n)?;
3710                        let en = self.interp.english_scalar_name(n);
3711                        let new_val = self
3712                            .interp
3713                            .scope
3714                            .atomic_mutate(en, |v| PerlValue::integer(v.to_int() + 1));
3715                        self.push(new_val);
3716                        Ok(())
3717                    }
3718                    Op::PreDec(idx) => {
3719                        let n = names[*idx as usize].as_str();
3720                        self.require_scalar_mutable(n)?;
3721                        let en = self.interp.english_scalar_name(n);
3722                        let new_val = self
3723                            .interp
3724                            .scope
3725                            .atomic_mutate(en, |v| PerlValue::integer(v.to_int() - 1));
3726                        self.push(new_val);
3727                        Ok(())
3728                    }
3729                    Op::PostInc(idx) => {
3730                        let n = names[*idx as usize].as_str();
3731                        self.require_scalar_mutable(n)?;
3732                        let en = self.interp.english_scalar_name(n);
3733                        if self.ip < len && matches!(ops[self.ip], Op::Pop) {
3734                            let _ = self
3735                                .interp
3736                                .scope
3737                                .atomic_mutate_post(en, |v| PerlValue::integer(v.to_int() + 1));
3738                            self.ip += 1;
3739                        } else {
3740                            let old = self
3741                                .interp
3742                                .scope
3743                                .atomic_mutate_post(en, |v| PerlValue::integer(v.to_int() + 1));
3744                            self.push(old);
3745                        }
3746                        Ok(())
3747                    }
3748                    Op::PostDec(idx) => {
3749                        let n = names[*idx as usize].as_str();
3750                        self.require_scalar_mutable(n)?;
3751                        let en = self.interp.english_scalar_name(n);
3752                        if self.ip < len && matches!(ops[self.ip], Op::Pop) {
3753                            let _ = self
3754                                .interp
3755                                .scope
3756                                .atomic_mutate_post(en, |v| PerlValue::integer(v.to_int() - 1));
3757                            self.ip += 1;
3758                        } else {
3759                            let old = self
3760                                .interp
3761                                .scope
3762                                .atomic_mutate_post(en, |v| PerlValue::integer(v.to_int() - 1));
3763                            self.push(old);
3764                        }
3765                        Ok(())
3766                    }
3767                    Op::PreIncSlot(slot) => {
3768                        let val = self.interp.scope.get_scalar_slot(*slot).to_int() + 1;
3769                        let new_val = PerlValue::integer(val);
3770                        self.interp.scope.set_scalar_slot(*slot, new_val.clone());
3771                        self.push(new_val);
3772                        Ok(())
3773                    }
3774                    Op::PreIncSlotVoid(slot) => {
3775                        let val = self.interp.scope.get_scalar_slot(*slot).to_int() + 1;
3776                        self.interp
3777                            .scope
3778                            .set_scalar_slot(*slot, PerlValue::integer(val));
3779                        Ok(())
3780                    }
3781                    Op::PreDecSlot(slot) => {
3782                        let val = self.interp.scope.get_scalar_slot(*slot).to_int() - 1;
3783                        let new_val = PerlValue::integer(val);
3784                        self.interp.scope.set_scalar_slot(*slot, new_val.clone());
3785                        self.push(new_val);
3786                        Ok(())
3787                    }
3788                    Op::PostIncSlot(slot) => {
3789                        // Fuse PostIncSlot+Pop: if next op discards the old value, skip stack work.
3790                        if self.ip < len && matches!(ops[self.ip], Op::Pop) {
3791                            let val = self.interp.scope.get_scalar_slot(*slot).to_int() + 1;
3792                            self.interp
3793                                .scope
3794                                .set_scalar_slot(*slot, PerlValue::integer(val));
3795                            self.ip += 1; // skip Pop
3796                        } else {
3797                            let old = self.interp.scope.get_scalar_slot(*slot);
3798                            let new_val = PerlValue::integer(old.to_int() + 1);
3799                            self.interp.scope.set_scalar_slot(*slot, new_val);
3800                            self.push(old);
3801                        }
3802                        Ok(())
3803                    }
3804                    Op::PostDecSlot(slot) => {
3805                        if self.ip < len && matches!(ops[self.ip], Op::Pop) {
3806                            let val = self.interp.scope.get_scalar_slot(*slot).to_int() - 1;
3807                            self.interp
3808                                .scope
3809                                .set_scalar_slot(*slot, PerlValue::integer(val));
3810                            self.ip += 1;
3811                        } else {
3812                            let old = self.interp.scope.get_scalar_slot(*slot);
3813                            let new_val = PerlValue::integer(old.to_int() - 1);
3814                            self.interp.scope.set_scalar_slot(*slot, new_val);
3815                            self.push(old);
3816                        }
3817                        Ok(())
3818                    }
3819
3820                    // ── Functions ──
3821                    Op::Call(name_idx, argc, wa) => {
3822                        let entry_opt = self.find_sub_entry(*name_idx);
3823                        self.vm_dispatch_user_call(*name_idx, entry_opt, *argc, *wa, None)?;
3824                        Ok(())
3825                    }
3826                    Op::CallStaticSubId(sid, name_idx, argc, wa) => {
3827                        let t = self.static_sub_calls.get(*sid as usize).ok_or_else(|| {
3828                            PerlError::runtime("VM: invalid CallStaticSubId", self.line())
3829                        })?;
3830                        debug_assert_eq!(t.2, *name_idx);
3831                        let closure_sub = self
3832                            .static_sub_closure_subs
3833                            .get(*sid as usize)
3834                            .and_then(|x| x.clone());
3835                        self.vm_dispatch_user_call(
3836                            *name_idx,
3837                            Some((t.0, t.1)),
3838                            *argc,
3839                            *wa,
3840                            closure_sub,
3841                        )?;
3842                        Ok(())
3843                    }
3844                    Op::Return => {
3845                        if let Some(frame) = self.call_stack.pop() {
3846                            if frame.block_region {
3847                                return Err(PerlError::runtime(
3848                                    "Return in map/grep/sort block bytecode",
3849                                    self.line(),
3850                                ));
3851                            }
3852                            if let Some(t0) = frame.sub_profiler_start {
3853                                if let Some(p) = &mut self.interp.profiler {
3854                                    p.exit_sub(t0.elapsed());
3855                                }
3856                            }
3857                            self.interp.wantarray_kind = frame.saved_wantarray;
3858                            self.stack.truncate(frame.stack_base);
3859                            self.interp.pop_scope_to_depth(frame.scope_depth);
3860                            self.interp.current_sub_stack.pop();
3861                            if frame.jit_trampoline_return {
3862                                self.jit_trampoline_out = Some(PerlValue::UNDEF);
3863                            } else {
3864                                self.push(PerlValue::UNDEF);
3865                                self.ip = frame.return_ip;
3866                            }
3867                        } else {
3868                            self.exit_main_dispatch = true;
3869                        }
3870                        Ok(())
3871                    }
3872                    Op::ReturnValue => {
3873                        let val = self.pop();
3874                        // Resolve binding refs to real refs before scope cleanup.
3875                        // `\@array` creates a name-based ArrayBindingRef that looks
3876                        // up by name at dereference time.  If the array is a `my`
3877                        // variable, its frame will be destroyed below — so we must
3878                        // snapshot the data into an Arc-based ref now.
3879                        let val = self.resolve_binding_ref(val);
3880                        if let Some(frame) = self.call_stack.pop() {
3881                            if frame.block_region {
3882                                return Err(PerlError::runtime(
3883                                    "Return in map/grep/sort block bytecode",
3884                                    self.line(),
3885                                ));
3886                            }
3887                            if let Some(t0) = frame.sub_profiler_start {
3888                                if let Some(p) = &mut self.interp.profiler {
3889                                    p.exit_sub(t0.elapsed());
3890                                }
3891                            }
3892                            self.interp.wantarray_kind = frame.saved_wantarray;
3893                            self.stack.truncate(frame.stack_base);
3894                            self.interp.pop_scope_to_depth(frame.scope_depth);
3895                            self.interp.current_sub_stack.pop();
3896                            if frame.jit_trampoline_return {
3897                                self.jit_trampoline_out = Some(val);
3898                            } else {
3899                                self.push(val);
3900                                self.ip = frame.return_ip;
3901                            }
3902                        } else {
3903                            self.exit_main_dispatch_value = Some(val);
3904                            self.exit_main_dispatch = true;
3905                        }
3906                        Ok(())
3907                    }
3908                    Op::BlockReturnValue => {
3909                        let val = self.pop();
3910                        let val = self.resolve_binding_ref(val);
3911                        if let Some(frame) = self.call_stack.pop() {
3912                            if !frame.block_region {
3913                                return Err(PerlError::runtime(
3914                                    "BlockReturnValue without map/grep/sort block frame",
3915                                    self.line(),
3916                                ));
3917                            }
3918                            self.interp.wantarray_kind = frame.saved_wantarray;
3919                            self.stack.truncate(frame.stack_base);
3920                            self.interp.pop_scope_to_depth(frame.scope_depth);
3921                            self.block_region_return = Some(val);
3922                            Ok(())
3923                        } else {
3924                            Err(PerlError::runtime(
3925                                "BlockReturnValue with empty call stack",
3926                                self.line(),
3927                            ))
3928                        }
3929                    }
3930                    Op::BindSubClosure(name_idx) => {
3931                        let n = names[*name_idx as usize].as_str();
3932                        self.interp.rebind_sub_closure(n);
3933                        Ok(())
3934                    }
3935
3936                    // ── Scope ──
3937                    Op::PushFrame => {
3938                        self.interp.scope_push_hook();
3939                        Ok(())
3940                    }
3941                    Op::PopFrame => {
3942                        self.interp.scope_pop_hook();
3943                        Ok(())
3944                    }
3945                    // ── I/O ──
3946                    Op::Print(handle_idx, argc) => {
3947                        let argc = *argc as usize;
3948                        let mut args = Vec::with_capacity(argc);
3949                        for _ in 0..argc {
3950                            args.push(self.pop());
3951                        }
3952                        args.reverse();
3953                        let mut output = String::new();
3954                        if args.is_empty() {
3955                            let topic = self.interp.scope.get_scalar("_").clone();
3956                            let s = match self.interp.stringify_value(topic, self.line()) {
3957                                Ok(s) => s,
3958                                Err(FlowOrError::Error(e)) => return Err(e),
3959                                Err(FlowOrError::Flow(_)) => {
3960                                    return Err(PerlError::runtime(
3961                                        "print: unexpected control flow",
3962                                        self.line(),
3963                                    ));
3964                                }
3965                            };
3966                            output.push_str(&s);
3967                        } else {
3968                            for (i, arg) in args.iter().enumerate() {
3969                                if i > 0 && !self.interp.ofs.is_empty() {
3970                                    output.push_str(&self.interp.ofs);
3971                                }
3972                                for item in arg.to_list() {
3973                                    let s = match self.interp.stringify_value(item, self.line()) {
3974                                        Ok(s) => s,
3975                                        Err(FlowOrError::Error(e)) => return Err(e),
3976                                        Err(FlowOrError::Flow(_)) => {
3977                                            return Err(PerlError::runtime(
3978                                                "print: unexpected control flow",
3979                                                self.line(),
3980                                            ));
3981                                        }
3982                                    };
3983                                    output.push_str(&s);
3984                                }
3985                            }
3986                        }
3987                        output.push_str(&self.interp.ors);
3988                        let handle_name = match handle_idx {
3989                            Some(idx) => self.interp.resolve_io_handle_name(
3990                                self.names
3991                                    .get(*idx as usize)
3992                                    .map_or("STDOUT", |s| s.as_str()),
3993                            ),
3994                            None => self
3995                                .interp
3996                                .resolve_io_handle_name(self.interp.default_print_handle.as_str()),
3997                        };
3998                        self.interp.write_formatted_print(
3999                            handle_name.as_str(),
4000                            &output,
4001                            self.line(),
4002                        )?;
4003                        self.push(PerlValue::integer(1));
4004                        Ok(())
4005                    }
4006                    Op::Say(handle_idx, argc) => {
4007                        if (self.interp.feature_bits & crate::interpreter::FEAT_SAY) == 0 {
4008                            return Err(PerlError::runtime(
4009                            "say() is disabled (enable with use feature 'say' or use feature ':5.10')",
4010                            self.line(),
4011                        ));
4012                        }
4013                        let argc = *argc as usize;
4014                        let mut args = Vec::with_capacity(argc);
4015                        for _ in 0..argc {
4016                            args.push(self.pop());
4017                        }
4018                        args.reverse();
4019                        let mut output = String::new();
4020                        if args.is_empty() {
4021                            let topic = self.interp.scope.get_scalar("_").clone();
4022                            let s = match self.interp.stringify_value(topic, self.line()) {
4023                                Ok(s) => s,
4024                                Err(FlowOrError::Error(e)) => return Err(e),
4025                                Err(FlowOrError::Flow(_)) => {
4026                                    return Err(PerlError::runtime(
4027                                        "say: unexpected control flow",
4028                                        self.line(),
4029                                    ));
4030                                }
4031                            };
4032                            output.push_str(&s);
4033                        } else {
4034                            for (i, arg) in args.iter().enumerate() {
4035                                if i > 0 && !self.interp.ofs.is_empty() {
4036                                    output.push_str(&self.interp.ofs);
4037                                }
4038                                for item in arg.to_list() {
4039                                    let s = match self.interp.stringify_value(item, self.line()) {
4040                                        Ok(s) => s,
4041                                        Err(FlowOrError::Error(e)) => return Err(e),
4042                                        Err(FlowOrError::Flow(_)) => {
4043                                            return Err(PerlError::runtime(
4044                                                "say: unexpected control flow",
4045                                                self.line(),
4046                                            ));
4047                                        }
4048                                    };
4049                                    output.push_str(&s);
4050                                }
4051                            }
4052                        }
4053                        output.push('\n');
4054                        output.push_str(&self.interp.ors);
4055                        let handle_name = match handle_idx {
4056                            Some(idx) => self.interp.resolve_io_handle_name(
4057                                self.names
4058                                    .get(*idx as usize)
4059                                    .map_or("STDOUT", |s| s.as_str()),
4060                            ),
4061                            None => self
4062                                .interp
4063                                .resolve_io_handle_name(self.interp.default_print_handle.as_str()),
4064                        };
4065                        self.interp.write_formatted_print(
4066                            handle_name.as_str(),
4067                            &output,
4068                            self.line(),
4069                        )?;
4070                        self.push(PerlValue::integer(1));
4071                        Ok(())
4072                    }
4073
4074                    // ── Built-in dispatch ──
4075                    Op::CallBuiltin(id, argc) => {
4076                        let argc = *argc as usize;
4077                        let mut args = Vec::with_capacity(argc);
4078                        for _ in 0..argc {
4079                            args.push(self.pop());
4080                        }
4081                        args.reverse();
4082                        let result = self.exec_builtin(*id, args)?;
4083                        self.push(result);
4084                        Ok(())
4085                    }
4086                    Op::WantarrayPush(wa) => {
4087                        self.wantarray_stack.push(self.interp.wantarray_kind);
4088                        self.interp.wantarray_kind = WantarrayCtx::from_byte(*wa);
4089                        Ok(())
4090                    }
4091                    Op::WantarrayPop => {
4092                        self.interp.wantarray_kind =
4093                            self.wantarray_stack.pop().unwrap_or(WantarrayCtx::Scalar);
4094                        Ok(())
4095                    }
4096
4097                    // ── List / Range ──
4098                    Op::MakeArray(n) => {
4099                        let n = *n as usize;
4100                        // Pops are last-to-first on the stack; reverse to source (left-to-right) order,
4101                        // then flatten nested arrays in place (Perl list literal semantics).
4102                        let mut stack_vals = Vec::with_capacity(n);
4103                        for _ in 0..n {
4104                            stack_vals.push(self.pop());
4105                        }
4106                        stack_vals.reverse();
4107                        let mut arr = Vec::new();
4108                        for v in stack_vals {
4109                            if let Some(items) = v.as_array_vec() {
4110                                arr.extend(items);
4111                            } else {
4112                                arr.push(v);
4113                            }
4114                        }
4115                        self.push(PerlValue::array(arr));
4116                        Ok(())
4117                    }
4118                    Op::HashSliceDeref(n) => {
4119                        let n = *n as usize;
4120                        let mut key_vals = Vec::with_capacity(n);
4121                        for _ in 0..n {
4122                            key_vals.push(self.pop());
4123                        }
4124                        key_vals.reverse();
4125                        let container = self.pop();
4126                        let line = self.line();
4127                        let out = vm_interp_result(
4128                            self.interp
4129                                .hash_slice_deref_values(&container, &key_vals, line),
4130                            line,
4131                        )?;
4132                        self.push(out);
4133                        Ok(())
4134                    }
4135                    Op::ArrowArraySlice(n) => {
4136                        let n = *n as usize;
4137                        let idxs = self.pop_flattened_array_slice_specs(n);
4138                        let r = self.pop();
4139                        let line = self.line();
4140                        let out = vm_interp_result(
4141                            self.interp.arrow_array_slice_values(r, &idxs, line),
4142                            line,
4143                        )?;
4144                        self.push(out);
4145                        Ok(())
4146                    }
4147                    Op::SetHashSliceDeref(n) => {
4148                        let n = *n as usize;
4149                        let mut key_vals = Vec::with_capacity(n);
4150                        for _ in 0..n {
4151                            key_vals.push(self.pop());
4152                        }
4153                        key_vals.reverse();
4154                        let container = self.pop();
4155                        let val = self.pop();
4156                        let line = self.line();
4157                        vm_interp_result(
4158                            self.interp
4159                                .assign_hash_slice_deref(container, key_vals, val, line),
4160                            line,
4161                        )?;
4162                        Ok(())
4163                    }
4164                    Op::SetHashSlice(hash_idx, n) => {
4165                        let n = *n as usize;
4166                        let mut key_vals = Vec::with_capacity(n);
4167                        for _ in 0..n {
4168                            key_vals.push(self.pop());
4169                        }
4170                        key_vals.reverse();
4171                        let name = names[*hash_idx as usize].as_str();
4172                        self.require_hash_mutable(name)?;
4173                        let val = self.pop();
4174                        let line = self.line();
4175                        vm_interp_result(
4176                            self.interp
4177                                .assign_named_hash_slice(name, key_vals, val, line),
4178                            line,
4179                        )?;
4180                        Ok(())
4181                    }
4182                    Op::GetHashSlice(hash_idx, n) => {
4183                        let n = *n as usize;
4184                        let mut key_vals = Vec::with_capacity(n);
4185                        for _ in 0..n {
4186                            key_vals.push(self.pop());
4187                        }
4188                        key_vals.reverse();
4189                        let name = names[*hash_idx as usize].as_str();
4190                        let h = self.interp.scope.get_hash(name);
4191                        let mut result = Vec::new();
4192                        for kv in &key_vals {
4193                            if let Some(vv) = kv.as_array_vec() {
4194                                for v in vv {
4195                                    let k = v.to_string();
4196                                    result.push(h.get(&k).cloned().unwrap_or(PerlValue::UNDEF));
4197                                }
4198                            } else {
4199                                let k = kv.to_string();
4200                                result.push(h.get(&k).cloned().unwrap_or(PerlValue::UNDEF));
4201                            }
4202                        }
4203                        self.push(PerlValue::array(result));
4204                        Ok(())
4205                    }
4206                    Op::HashSliceDerefCompound(op_byte, n) => {
4207                        let n = *n as usize;
4208                        let mut key_vals = Vec::with_capacity(n);
4209                        for _ in 0..n {
4210                            key_vals.push(self.pop());
4211                        }
4212                        key_vals.reverse();
4213                        let container = self.pop();
4214                        let rhs = self.pop();
4215                        let line = self.line();
4216                        let op = crate::compiler::scalar_compound_op_from_byte(*op_byte)
4217                            .ok_or_else(|| {
4218                                crate::error::PerlError::runtime(
4219                                    "VM: HashSliceDerefCompound: bad op byte",
4220                                    line,
4221                                )
4222                            })?;
4223                        let new_val = vm_interp_result(
4224                            self.interp.compound_assign_hash_slice_deref(
4225                                container, key_vals, op, rhs, line,
4226                            ),
4227                            line,
4228                        )?;
4229                        self.push(new_val);
4230                        Ok(())
4231                    }
4232                    Op::HashSliceDerefIncDec(kind, n) => {
4233                        let n = *n as usize;
4234                        let mut key_vals = Vec::with_capacity(n);
4235                        for _ in 0..n {
4236                            key_vals.push(self.pop());
4237                        }
4238                        key_vals.reverse();
4239                        let container = self.pop();
4240                        let line = self.line();
4241                        let out = vm_interp_result(
4242                            self.interp
4243                                .hash_slice_deref_inc_dec(container, key_vals, *kind, line),
4244                            line,
4245                        )?;
4246                        self.push(out);
4247                        Ok(())
4248                    }
4249                    Op::NamedHashSliceCompound(op_byte, hash_idx, n) => {
4250                        let n = *n as usize;
4251                        let mut key_vals = Vec::with_capacity(n);
4252                        for _ in 0..n {
4253                            key_vals.push(self.pop());
4254                        }
4255                        key_vals.reverse();
4256                        let name = names[*hash_idx as usize].as_str();
4257                        self.require_hash_mutable(name)?;
4258                        let rhs = self.pop();
4259                        let line = self.line();
4260                        let op = crate::compiler::scalar_compound_op_from_byte(*op_byte)
4261                            .ok_or_else(|| {
4262                                crate::error::PerlError::runtime(
4263                                    "VM: NamedHashSliceCompound: bad op byte",
4264                                    line,
4265                                )
4266                            })?;
4267                        let new_val = vm_interp_result(
4268                            self.interp
4269                                .compound_assign_named_hash_slice(name, key_vals, op, rhs, line),
4270                            line,
4271                        )?;
4272                        self.push(new_val);
4273                        Ok(())
4274                    }
4275                    Op::NamedHashSliceIncDec(kind, hash_idx, n) => {
4276                        let n = *n as usize;
4277                        let mut key_vals = Vec::with_capacity(n);
4278                        for _ in 0..n {
4279                            key_vals.push(self.pop());
4280                        }
4281                        key_vals.reverse();
4282                        let name = names[*hash_idx as usize].as_str();
4283                        self.require_hash_mutable(name)?;
4284                        let line = self.line();
4285                        let out = vm_interp_result(
4286                            self.interp
4287                                .named_hash_slice_inc_dec(name, key_vals, *kind, line),
4288                            line,
4289                        )?;
4290                        self.push(out);
4291                        Ok(())
4292                    }
4293                    Op::NamedHashSlicePeekLast(hash_idx, n) => {
4294                        let n = *n as usize;
4295                        let line = self.line();
4296                        let name = names[*hash_idx as usize].as_str();
4297                        self.require_hash_mutable(name)?;
4298                        let len = self.stack.len();
4299                        if len < n {
4300                            return Err(PerlError::runtime(
4301                                "VM: NamedHashSlicePeekLast: stack underflow",
4302                                line,
4303                            ));
4304                        }
4305                        let base = len - n;
4306                        let key_vals: Vec<PerlValue> = self.stack[base..base + n].to_vec();
4307                        let ks = Self::flatten_hash_slice_key_slots(&key_vals);
4308                        let last_k = ks.last().ok_or_else(|| {
4309                            PerlError::runtime("VM: NamedHashSlicePeekLast: empty key list", line)
4310                        })?;
4311                        self.interp.touch_env_hash(name);
4312                        let cur = self.interp.scope.get_hash_element(name, last_k.as_str());
4313                        self.push(cur);
4314                        Ok(())
4315                    }
4316                    Op::NamedHashSliceDropKeysKeepCur(n) => {
4317                        let n = *n as usize;
4318                        let cur = self.pop();
4319                        for _ in 0..n {
4320                            self.pop();
4321                        }
4322                        self.push(cur);
4323                        Ok(())
4324                    }
4325                    Op::SetNamedHashSliceLastKeep(hash_idx, n) => {
4326                        let n = *n as usize;
4327                        let line = self.line();
4328                        let name = names[*hash_idx as usize].as_str();
4329                        self.require_hash_mutable(name)?;
4330                        let mut key_vals_rev = Vec::with_capacity(n);
4331                        for _ in 0..n {
4332                            key_vals_rev.push(self.pop());
4333                        }
4334                        key_vals_rev.reverse();
4335                        let mut val = self.pop();
4336                        if let Some(av) = val.as_array_vec() {
4337                            val = av.last().cloned().unwrap_or(PerlValue::UNDEF);
4338                        }
4339                        let ks = Self::flatten_hash_slice_key_slots(&key_vals_rev);
4340                        let last_k = ks.last().ok_or_else(|| {
4341                            PerlError::runtime(
4342                                "VM: SetNamedHashSliceLastKeep: empty key list",
4343                                line,
4344                            )
4345                        })?;
4346                        let val_keep = val.clone();
4347                        self.interp.touch_env_hash(name);
4348                        vm_interp_result(
4349                            self.interp
4350                                .scope
4351                                .set_hash_element(name, last_k.as_str(), val)
4352                                .map(|()| PerlValue::UNDEF)
4353                                .map_err(|e| FlowOrError::Error(e.at_line(line))),
4354                            line,
4355                        )?;
4356                        self.push(val_keep);
4357                        Ok(())
4358                    }
4359                    Op::HashSliceDerefPeekLast(n) => {
4360                        let n = *n as usize;
4361                        let line = self.line();
4362                        let len = self.stack.len();
4363                        if len < n + 1 {
4364                            return Err(PerlError::runtime(
4365                                "VM: HashSliceDerefPeekLast: stack underflow",
4366                                line,
4367                            ));
4368                        }
4369                        let base = len - n - 1;
4370                        let container = self.stack[base].clone();
4371                        let key_vals: Vec<PerlValue> = self.stack[base + 1..base + 1 + n].to_vec();
4372                        let list = vm_interp_result(
4373                            self.interp
4374                                .hash_slice_deref_values(&container, &key_vals, line),
4375                            line,
4376                        )?;
4377                        let cur = list.to_list().last().cloned().unwrap_or(PerlValue::UNDEF);
4378                        self.push(cur);
4379                        Ok(())
4380                    }
4381                    Op::HashSliceDerefRollValUnderKeys(n) => {
4382                        let n = *n as usize;
4383                        let val = self.pop();
4384                        let mut keys_rev = Vec::with_capacity(n);
4385                        for _ in 0..n {
4386                            keys_rev.push(self.pop());
4387                        }
4388                        let container = self.pop();
4389                        keys_rev.reverse();
4390                        self.push(val);
4391                        self.push(container);
4392                        for k in keys_rev {
4393                            self.push(k);
4394                        }
4395                        Ok(())
4396                    }
4397                    Op::HashSliceDerefSetLastKeep(n) => {
4398                        let n = *n as usize;
4399                        let line = self.line();
4400                        let mut key_vals_rev = Vec::with_capacity(n);
4401                        for _ in 0..n {
4402                            key_vals_rev.push(self.pop());
4403                        }
4404                        key_vals_rev.reverse();
4405                        let container = self.pop();
4406                        let mut val = self.pop();
4407                        if let Some(av) = val.as_array_vec() {
4408                            val = av.last().cloned().unwrap_or(PerlValue::UNDEF);
4409                        }
4410                        let ks = Self::flatten_hash_slice_key_slots(&key_vals_rev);
4411                        let last_k = ks.last().ok_or_else(|| {
4412                            PerlError::runtime(
4413                                "VM: HashSliceDerefSetLastKeep: empty key list",
4414                                line,
4415                            )
4416                        })?;
4417                        let val_keep = val.clone();
4418                        vm_interp_result(
4419                            self.interp.assign_hash_slice_one_key(
4420                                container,
4421                                last_k.as_str(),
4422                                val,
4423                                line,
4424                            ),
4425                            line,
4426                        )?;
4427                        self.push(val_keep);
4428                        Ok(())
4429                    }
4430                    Op::HashSliceDerefDropKeysKeepCur(n) => {
4431                        let n = *n as usize;
4432                        let cur = self.pop();
4433                        for _ in 0..n {
4434                            self.pop();
4435                        }
4436                        let _container = self.pop();
4437                        self.push(cur);
4438                        Ok(())
4439                    }
4440                    Op::SetArrowArraySlice(n) => {
4441                        let n = *n as usize;
4442                        let idxs = self.pop_flattened_array_slice_specs(n);
4443                        let aref = self.pop();
4444                        let val = self.pop();
4445                        let line = self.line();
4446                        vm_interp_result(
4447                            self.interp.assign_arrow_array_slice(aref, idxs, val, line),
4448                            line,
4449                        )?;
4450                        Ok(())
4451                    }
4452                    Op::ArrowArraySliceCompound(op_byte, n) => {
4453                        let n = *n as usize;
4454                        let idxs = self.pop_flattened_array_slice_specs(n);
4455                        let aref = self.pop();
4456                        let rhs = self.pop();
4457                        let line = self.line();
4458                        let op = crate::compiler::scalar_compound_op_from_byte(*op_byte)
4459                            .ok_or_else(|| {
4460                                crate::error::PerlError::runtime(
4461                                    "VM: ArrowArraySliceCompound: bad op byte",
4462                                    line,
4463                                )
4464                            })?;
4465                        let new_val = vm_interp_result(
4466                            self.interp
4467                                .compound_assign_arrow_array_slice(aref, idxs, op, rhs, line),
4468                            line,
4469                        )?;
4470                        self.push(new_val);
4471                        Ok(())
4472                    }
4473                    Op::ArrowArraySliceIncDec(kind, n) => {
4474                        let n = *n as usize;
4475                        let idxs = self.pop_flattened_array_slice_specs(n);
4476                        let aref = self.pop();
4477                        let line = self.line();
4478                        let out = vm_interp_result(
4479                            self.interp
4480                                .arrow_array_slice_inc_dec(aref, idxs, *kind, line),
4481                            line,
4482                        )?;
4483                        self.push(out);
4484                        Ok(())
4485                    }
4486                    Op::ArrowArraySlicePeekLast(n) => {
4487                        let n = *n as usize;
4488                        let line = self.line();
4489                        let len = self.stack.len();
4490                        if len < n + 1 {
4491                            return Err(PerlError::runtime(
4492                                "VM: ArrowArraySlicePeekLast: stack underflow",
4493                                line,
4494                            ));
4495                        }
4496                        let base = len - n - 1;
4497                        let aref = self.stack[base].clone();
4498                        let idxs =
4499                            self.flatten_array_slice_specs_ordered_values(&self.stack[base + 1..])?;
4500                        let last = *idxs.last().ok_or_else(|| {
4501                            PerlError::runtime(
4502                                "VM: ArrowArraySlicePeekLast: empty index list",
4503                                line,
4504                            )
4505                        })?;
4506                        let cur = vm_interp_result(
4507                            self.interp.read_arrow_array_element(aref, last, line),
4508                            line,
4509                        )?;
4510                        self.push(cur);
4511                        Ok(())
4512                    }
4513                    Op::ArrowArraySliceDropKeysKeepCur(n) => {
4514                        let n = *n as usize;
4515                        let cur = self.pop();
4516                        let _idxs = self.pop_flattened_array_slice_specs(n);
4517                        let _aref = self.pop();
4518                        self.push(cur);
4519                        Ok(())
4520                    }
4521                    Op::ArrowArraySliceRollValUnderSpecs(n) => {
4522                        let n = *n as usize;
4523                        let val = self.pop();
4524                        let mut specs_rev = Vec::with_capacity(n);
4525                        for _ in 0..n {
4526                            specs_rev.push(self.pop());
4527                        }
4528                        let aref = self.pop();
4529                        self.push(val);
4530                        self.push(aref);
4531                        for s in specs_rev.into_iter().rev() {
4532                            self.push(s);
4533                        }
4534                        Ok(())
4535                    }
4536                    Op::SetArrowArraySliceLastKeep(n) => {
4537                        let n = *n as usize;
4538                        let line = self.line();
4539                        let idxs = self.pop_flattened_array_slice_specs(n);
4540                        let aref = self.pop();
4541                        let mut val = self.pop();
4542                        // RHS is compiled in list context (`(3,4)` → one array value); Perl assigns
4543                        // only the **last** list element to the last slice index (`||=` / `&&=` / `//=`).
4544                        if let Some(av) = val.as_array_vec() {
4545                            val = av.last().cloned().unwrap_or(PerlValue::UNDEF);
4546                        }
4547                        let last = *idxs.last().ok_or_else(|| {
4548                            PerlError::runtime(
4549                                "VM: SetArrowArraySliceLastKeep: empty index list",
4550                                line,
4551                            )
4552                        })?;
4553                        let val_keep = val.clone();
4554                        vm_interp_result(
4555                            self.interp.assign_arrow_array_deref(aref, last, val, line),
4556                            line,
4557                        )?;
4558                        self.push(val_keep);
4559                        Ok(())
4560                    }
4561                    Op::NamedArraySliceIncDec(kind, arr_idx, n) => {
4562                        let n = *n as usize;
4563                        let idxs = self.pop_flattened_array_slice_specs(n);
4564                        let name = names[*arr_idx as usize].as_str();
4565                        self.require_array_mutable(name)?;
4566                        let line = self.line();
4567                        let out = vm_interp_result(
4568                            self.interp
4569                                .named_array_slice_inc_dec(name, idxs, *kind, line),
4570                            line,
4571                        )?;
4572                        self.push(out);
4573                        Ok(())
4574                    }
4575                    Op::NamedArraySliceCompound(op_byte, arr_idx, n) => {
4576                        let n = *n as usize;
4577                        let idxs = self.pop_flattened_array_slice_specs(n);
4578                        let name = names[*arr_idx as usize].as_str();
4579                        self.require_array_mutable(name)?;
4580                        let rhs = self.pop();
4581                        let line = self.line();
4582                        let op = crate::compiler::scalar_compound_op_from_byte(*op_byte)
4583                            .ok_or_else(|| {
4584                                crate::error::PerlError::runtime(
4585                                    "VM: NamedArraySliceCompound: bad op byte",
4586                                    line,
4587                                )
4588                            })?;
4589                        let new_val = vm_interp_result(
4590                            self.interp
4591                                .compound_assign_named_array_slice(name, idxs, op, rhs, line),
4592                            line,
4593                        )?;
4594                        self.push(new_val);
4595                        Ok(())
4596                    }
4597                    Op::NamedArraySlicePeekLast(arr_idx, n) => {
4598                        let n = *n as usize;
4599                        let line = self.line();
4600                        let name = names[*arr_idx as usize].as_str();
4601                        self.require_array_mutable(name)?;
4602                        let len = self.stack.len();
4603                        if len < n {
4604                            return Err(PerlError::runtime(
4605                                "VM: NamedArraySlicePeekLast: stack underflow",
4606                                line,
4607                            ));
4608                        }
4609                        let base = len - n;
4610                        let idxs =
4611                            self.flatten_array_slice_specs_ordered_values(&self.stack[base..])?;
4612                        let last = *idxs.last().ok_or_else(|| {
4613                            PerlError::runtime(
4614                                "VM: NamedArraySlicePeekLast: empty index list",
4615                                line,
4616                            )
4617                        })?;
4618                        let cur = self.interp.scope.get_array_element(name, last);
4619                        self.push(cur);
4620                        Ok(())
4621                    }
4622                    Op::NamedArraySliceDropKeysKeepCur(n) => {
4623                        let n = *n as usize;
4624                        let cur = self.pop();
4625                        let _idxs = self.pop_flattened_array_slice_specs(n);
4626                        self.push(cur);
4627                        Ok(())
4628                    }
4629                    Op::NamedArraySliceRollValUnderSpecs(n) => {
4630                        let n = *n as usize;
4631                        let val = self.pop();
4632                        let mut specs_rev = Vec::with_capacity(n);
4633                        for _ in 0..n {
4634                            specs_rev.push(self.pop());
4635                        }
4636                        self.push(val);
4637                        for s in specs_rev.into_iter().rev() {
4638                            self.push(s);
4639                        }
4640                        Ok(())
4641                    }
4642                    Op::SetNamedArraySliceLastKeep(arr_idx, n) => {
4643                        let n = *n as usize;
4644                        let line = self.line();
4645                        let idxs = self.pop_flattened_array_slice_specs(n);
4646                        let name = names[*arr_idx as usize].as_str();
4647                        self.require_array_mutable(name)?;
4648                        let mut val = self.pop();
4649                        if let Some(av) = val.as_array_vec() {
4650                            val = av.last().cloned().unwrap_or(PerlValue::UNDEF);
4651                        }
4652                        let last = *idxs.last().ok_or_else(|| {
4653                            PerlError::runtime(
4654                                "VM: SetNamedArraySliceLastKeep: empty index list",
4655                                line,
4656                            )
4657                        })?;
4658                        let val_keep = val.clone();
4659                        vm_interp_result(
4660                            self.interp
4661                                .scope
4662                                .set_array_element(name, last, val)
4663                                .map(|()| PerlValue::UNDEF)
4664                                .map_err(|e| FlowOrError::Error(e.at_line(line))),
4665                            line,
4666                        )?;
4667                        self.push(val_keep);
4668                        Ok(())
4669                    }
4670                    Op::SetNamedArraySlice(arr_idx, n) => {
4671                        let n = *n as usize;
4672                        let idxs = self.pop_flattened_array_slice_specs(n);
4673                        let name = names[*arr_idx as usize].as_str();
4674                        self.require_array_mutable(name)?;
4675                        let val = self.pop();
4676                        let line = self.line();
4677                        vm_interp_result(
4678                            self.interp.assign_named_array_slice(name, idxs, val, line),
4679                            line,
4680                        )?;
4681                        Ok(())
4682                    }
4683                    Op::MakeHash(n) => {
4684                        let n = *n as usize;
4685                        let mut items = Vec::with_capacity(n);
4686                        for _ in 0..n {
4687                            items.push(self.pop());
4688                        }
4689                        items.reverse();
4690                        let mut map = IndexMap::new();
4691                        let mut i = 0;
4692                        while i + 1 < items.len() {
4693                            map.insert(items[i].to_string(), items[i + 1].clone());
4694                            i += 2;
4695                        }
4696                        self.push(PerlValue::hash(map));
4697                        Ok(())
4698                    }
4699                    Op::Range => {
4700                        let to = self.pop();
4701                        let from = self.pop();
4702                        let arr = perl_list_range_expand(from, to);
4703                        self.push(PerlValue::array(arr));
4704                        Ok(())
4705                    }
4706                    Op::RangeStep => {
4707                        let step = self.pop().to_int();
4708                        let to = self.pop().to_int();
4709                        let from = self.pop().to_int();
4710                        let arr = if step == 0 {
4711                            vec![]
4712                        } else if step > 0 {
4713                            (from..=to)
4714                                .step_by(step as usize)
4715                                .map(PerlValue::integer)
4716                                .collect()
4717                        } else {
4718                            std::iter::successors(Some(from), |&x| {
4719                                let next = x + step; // step is negative
4720                                if next >= to {
4721                                    Some(next)
4722                                } else {
4723                                    None
4724                                }
4725                            })
4726                            .map(PerlValue::integer)
4727                            .collect()
4728                        };
4729                        self.push(PerlValue::array(arr));
4730                        Ok(())
4731                    }
4732                    Op::ScalarFlipFlop(slot, exclusive) => {
4733                        let to = self.pop().to_int();
4734                        let from = self.pop().to_int();
4735                        let line = self.line();
4736                        let v = vm_interp_result(
4737                            self.interp
4738                                .scalar_flip_flop_eval(from, to, *slot as usize, *exclusive != 0)
4739                                .map_err(Into::into),
4740                            line,
4741                        )?;
4742                        self.push(v);
4743                        Ok(())
4744                    }
4745                    Op::RegexFlipFlop(slot, exclusive, lp, lf, rp, rf) => {
4746                        let line = self.line();
4747                        let left_pat = constants[*lp as usize].as_str_or_empty();
4748                        let left_flags = constants[*lf as usize].as_str_or_empty();
4749                        let right_pat = constants[*rp as usize].as_str_or_empty();
4750                        let right_flags = constants[*rf as usize].as_str_or_empty();
4751                        let v = vm_interp_result(
4752                            self.interp
4753                                .regex_flip_flop_eval(
4754                                    left_pat.as_str(),
4755                                    left_flags.as_str(),
4756                                    right_pat.as_str(),
4757                                    right_flags.as_str(),
4758                                    *slot as usize,
4759                                    *exclusive != 0,
4760                                    line,
4761                                )
4762                                .map_err(Into::into),
4763                            line,
4764                        )?;
4765                        self.push(v);
4766                        Ok(())
4767                    }
4768                    Op::RegexEofFlipFlop(slot, exclusive, lp, lf) => {
4769                        let line = self.line();
4770                        let left_pat = constants[*lp as usize].as_str_or_empty();
4771                        let left_flags = constants[*lf as usize].as_str_or_empty();
4772                        let v = vm_interp_result(
4773                            self.interp
4774                                .regex_eof_flip_flop_eval(
4775                                    left_pat.as_str(),
4776                                    left_flags.as_str(),
4777                                    *slot as usize,
4778                                    *exclusive != 0,
4779                                    line,
4780                                )
4781                                .map_err(Into::into),
4782                            line,
4783                        )?;
4784                        self.push(v);
4785                        Ok(())
4786                    }
4787                    Op::RegexFlipFlopExprRhs(slot, exclusive, lp, lf, rhs_idx) => {
4788                        let idx = *rhs_idx as usize;
4789                        let line = self.line();
4790                        let right_m = if let Some(&(start, end)) = self
4791                            .regex_flip_flop_rhs_expr_bytecode_ranges
4792                            .get(idx)
4793                            .and_then(|r| r.as_ref())
4794                        {
4795                            let val = self.run_block_region(start, end, op_count)?;
4796                            val.is_true()
4797                        } else {
4798                            let e = &self.regex_flip_flop_rhs_expr_entries[idx];
4799                            match self.interp.eval_boolean_rvalue_condition(e) {
4800                                Ok(b) => b,
4801                                Err(FlowOrError::Error(err)) => return Err(err),
4802                                Err(FlowOrError::Flow(_)) => {
4803                                    return Err(PerlError::runtime(
4804                                        "unexpected flow in regex flip-flop RHS",
4805                                        line,
4806                                    ))
4807                                }
4808                            }
4809                        };
4810                        let left_pat = constants[*lp as usize].as_str_or_empty();
4811                        let left_flags = constants[*lf as usize].as_str_or_empty();
4812                        let v = vm_interp_result(
4813                            self.interp
4814                                .regex_flip_flop_eval_dynamic_right(
4815                                    left_pat.as_str(),
4816                                    left_flags.as_str(),
4817                                    *slot as usize,
4818                                    *exclusive != 0,
4819                                    line,
4820                                    right_m,
4821                                )
4822                                .map_err(Into::into),
4823                            line,
4824                        )?;
4825                        self.push(v);
4826                        Ok(())
4827                    }
4828                    Op::RegexFlipFlopDotLineRhs(slot, exclusive, lp, lf, line_cidx) => {
4829                        let line = self.line();
4830                        let rhs_line = constants[*line_cidx as usize].to_int();
4831                        let left_pat = constants[*lp as usize].as_str_or_empty();
4832                        let left_flags = constants[*lf as usize].as_str_or_empty();
4833                        let v = vm_interp_result(
4834                            self.interp
4835                                .regex_flip_flop_eval_dot_line_rhs(
4836                                    left_pat.as_str(),
4837                                    left_flags.as_str(),
4838                                    *slot as usize,
4839                                    *exclusive != 0,
4840                                    line,
4841                                    rhs_line,
4842                                )
4843                                .map_err(Into::into),
4844                            line,
4845                        )?;
4846                        self.push(v);
4847                        Ok(())
4848                    }
4849
4850                    // ── Regex ──
4851                    Op::RegexMatch(pat_idx, flags_idx, scalar_g, pos_key_idx) => {
4852                        let val = self.pop();
4853                        let pattern = constants[*pat_idx as usize].as_str_or_empty();
4854                        let flags = constants[*flags_idx as usize].as_str_or_empty();
4855                        let line = self.line();
4856                        if val.is_iterator() {
4857                            let source = crate::map_stream::into_pull_iter(val);
4858                            let re = match self.interp.compile_regex(&pattern, &flags, line) {
4859                                Ok(r) => r,
4860                                Err(FlowOrError::Error(e)) => return Err(e),
4861                                Err(FlowOrError::Flow(_)) => {
4862                                    return Err(PerlError::runtime(
4863                                        "unexpected flow in regex compile",
4864                                        line,
4865                                    ));
4866                                }
4867                            };
4868                            let global = flags.contains('g');
4869                            if global {
4870                                self.push(PerlValue::iterator(std::sync::Arc::new(
4871                                    crate::map_stream::MatchGlobalStreamIterator::new(source, re),
4872                                )));
4873                            } else {
4874                                self.push(PerlValue::iterator(std::sync::Arc::new(
4875                                    crate::map_stream::MatchStreamIterator::new(source, re),
4876                                )));
4877                            }
4878                            return Ok(());
4879                        }
4880                        let string = val.into_string();
4881                        let pos_key_owned = if *pos_key_idx == u16::MAX {
4882                            None
4883                        } else {
4884                            Some(constants[*pos_key_idx as usize].as_str_or_empty())
4885                        };
4886                        let pos_key: &str = pos_key_owned.as_deref().unwrap_or("_");
4887                        match self
4888                            .interp
4889                            .regex_match_execute(string, &pattern, &flags, *scalar_g, pos_key, line)
4890                        {
4891                            Ok(v) => {
4892                                self.push(v);
4893                                Ok(())
4894                            }
4895                            Err(FlowOrError::Error(e)) => Err(e),
4896                            Err(FlowOrError::Flow(_)) => {
4897                                Err(PerlError::runtime("unexpected flow in regex match", line))
4898                            }
4899                        }
4900                    }
4901                    Op::RegexSubst(pat_idx, repl_idx, flags_idx, lvalue_idx) => {
4902                        let val = self.pop();
4903                        let pattern = constants[*pat_idx as usize].as_str_or_empty();
4904                        let replacement = constants[*repl_idx as usize].as_str_or_empty();
4905                        let flags = constants[*flags_idx as usize].as_str_or_empty();
4906                        let line = self.line();
4907                        if val.is_iterator() {
4908                            let source = crate::map_stream::into_pull_iter(val);
4909                            let re = match self.interp.compile_regex(&pattern, &flags, line) {
4910                                Ok(r) => r,
4911                                Err(FlowOrError::Error(e)) => return Err(e),
4912                                Err(FlowOrError::Flow(_)) => {
4913                                    return Err(PerlError::runtime(
4914                                        "unexpected flow in regex compile",
4915                                        line,
4916                                    ));
4917                                }
4918                            };
4919                            let global = flags.contains('g');
4920                            self.push(PerlValue::iterator(std::sync::Arc::new(
4921                                crate::map_stream::SubstStreamIterator::new(
4922                                    source,
4923                                    re,
4924                                    crate::interpreter::normalize_replacement_backrefs(
4925                                        &replacement,
4926                                    ),
4927                                    global,
4928                                ),
4929                            )));
4930                            return Ok(());
4931                        }
4932                        let string = val.into_string();
4933                        let target = &self.lvalues[*lvalue_idx as usize];
4934                        match self.interp.regex_subst_execute(
4935                            string,
4936                            &pattern,
4937                            &replacement,
4938                            &flags,
4939                            target,
4940                            line,
4941                        ) {
4942                            Ok(v) => {
4943                                self.push(v);
4944                                Ok(())
4945                            }
4946                            Err(FlowOrError::Error(e)) => Err(e),
4947                            Err(FlowOrError::Flow(_)) => {
4948                                Err(PerlError::runtime("unexpected flow in s///", line))
4949                            }
4950                        }
4951                    }
4952                    Op::RegexTransliterate(from_idx, to_idx, flags_idx, lvalue_idx) => {
4953                        let val = self.pop();
4954                        let from = constants[*from_idx as usize].as_str_or_empty();
4955                        let to = constants[*to_idx as usize].as_str_or_empty();
4956                        let flags = constants[*flags_idx as usize].as_str_or_empty();
4957                        let line = self.line();
4958                        if val.is_iterator() {
4959                            let source = crate::map_stream::into_pull_iter(val);
4960                            self.push(PerlValue::iterator(std::sync::Arc::new(
4961                                crate::map_stream::TransliterateStreamIterator::new(
4962                                    source, &from, &to, &flags,
4963                                ),
4964                            )));
4965                            return Ok(());
4966                        }
4967                        let string = val.into_string();
4968                        let target = &self.lvalues[*lvalue_idx as usize];
4969                        match self
4970                            .interp
4971                            .regex_transliterate_execute(string, &from, &to, &flags, target, line)
4972                        {
4973                            Ok(v) => {
4974                                self.push(v);
4975                                Ok(())
4976                            }
4977                            Err(FlowOrError::Error(e)) => Err(e),
4978                            Err(FlowOrError::Flow(_)) => {
4979                                Err(PerlError::runtime("unexpected flow in tr///", line))
4980                            }
4981                        }
4982                    }
4983                    Op::RegexMatchDyn(negate) => {
4984                        let rhs = self.pop();
4985                        let s = self.pop().into_string();
4986                        let line = self.line();
4987                        let exec = if let Some((pat, fl)) = rhs.regex_src_and_flags() {
4988                            self.interp
4989                                .regex_match_execute(s, &pat, &fl, false, "_", line)
4990                        } else {
4991                            let pattern = rhs.into_string();
4992                            self.interp
4993                                .regex_match_execute(s, &pattern, "", false, "_", line)
4994                        };
4995                        match exec {
4996                            Ok(v) => {
4997                                let matched = v.is_true();
4998                                let out = if *negate { !matched } else { matched };
4999                                self.push(PerlValue::integer(if out { 1 } else { 0 }));
5000                            }
5001                            Err(FlowOrError::Error(e)) => return Err(e),
5002                            Err(FlowOrError::Flow(_)) => {
5003                                return Err(PerlError::runtime("unexpected flow in =~", line));
5004                            }
5005                        }
5006                        Ok(())
5007                    }
5008                    Op::RegexBoolToScalar => {
5009                        let v = self.pop();
5010                        self.push(if v.is_true() {
5011                            PerlValue::integer(1)
5012                        } else {
5013                            PerlValue::string(String::new())
5014                        });
5015                        Ok(())
5016                    }
5017                    Op::SetRegexPos => {
5018                        let key = self.pop().to_string();
5019                        let val = self.pop();
5020                        if val.is_undef() {
5021                            self.interp.regex_pos.insert(key, None);
5022                        } else {
5023                            let u = val.to_int().max(0) as usize;
5024                            self.interp.regex_pos.insert(key, Some(u));
5025                        }
5026                        Ok(())
5027                    }
5028                    Op::LoadRegex(pat_idx, flags_idx) => {
5029                        let pattern = constants[*pat_idx as usize].as_str_or_empty();
5030                        let flags = constants[*flags_idx as usize].as_str_or_empty();
5031                        let line = self.line();
5032                        let pattern_owned = pattern.clone();
5033                        let re = match self.interp.compile_regex(&pattern, &flags, line) {
5034                            Ok(r) => r,
5035                            Err(FlowOrError::Error(e)) => return Err(e),
5036                            Err(FlowOrError::Flow(_)) => {
5037                                return Err(PerlError::runtime(
5038                                    "unexpected flow in qr// compile",
5039                                    line,
5040                                ));
5041                            }
5042                        };
5043                        self.push(PerlValue::regex(re, pattern_owned, flags.to_string()));
5044                        Ok(())
5045                    }
5046                    Op::ConcatAppend(idx) => {
5047                        let rhs = self.pop();
5048                        let n = names[*idx as usize].as_str();
5049                        let line = self.line();
5050                        let result = self
5051                            .interp
5052                            .scope
5053                            .scalar_concat_inplace(n, &rhs)
5054                            .map_err(|e| e.at_line(line))?;
5055                        self.push(result);
5056                        Ok(())
5057                    }
5058                    Op::ConcatAppendSlot(slot) => {
5059                        let rhs = self.pop();
5060                        let result = self.interp.scope.scalar_slot_concat_inplace(*slot, &rhs);
5061                        self.push(result);
5062                        Ok(())
5063                    }
5064                    Op::ConcatAppendSlotVoid(slot) => {
5065                        let rhs = self.pop();
5066                        self.interp.scope.scalar_slot_concat_inplace(*slot, &rhs);
5067                        Ok(())
5068                    }
5069                    Op::SlotLtIntJumpIfFalse(slot, limit, target) => {
5070                        let val = self.interp.scope.get_scalar_slot(*slot);
5071                        let lt = if let Some(i) = val.as_integer() {
5072                            i < *limit as i64
5073                        } else {
5074                            val.to_number() < *limit as f64
5075                        };
5076                        if !lt {
5077                            self.ip = *target;
5078                        }
5079                        Ok(())
5080                    }
5081                    Op::SlotIncLtIntJumpBack(slot, limit, body_target) => {
5082                        // Fused trailing `++$slot; goto top_test` for the bench_loop shape:
5083                        // matches `PreIncSlotVoid` + `Jump` + top `SlotLtIntJumpIfFalse` exactly so
5084                        // coercion, wrap-around, and integer-only write semantics line up byte-for-byte
5085                        // with the un-fused form. Every iteration past the first skips the top check
5086                        // and the unconditional jump entirely.
5087                        let next_i = self
5088                            .interp
5089                            .scope
5090                            .get_scalar_slot(*slot)
5091                            .to_int()
5092                            .wrapping_add(1);
5093                        self.interp
5094                            .scope
5095                            .set_scalar_slot(*slot, PerlValue::integer(next_i));
5096                        if next_i < *limit as i64 {
5097                            self.ip = *body_target;
5098                        }
5099                        Ok(())
5100                    }
5101                    Op::AccumSumLoop(sum_slot, i_slot, limit) => {
5102                        // Runs the entire counted `while $i < limit { $sum += $i; $i += 1 }` loop in
5103                        // native Rust. The peephole only fires when the body is exactly this one
5104                        // accumulate statement, so every side effect is captured by the final
5105                        // `$sum` and `$i` writes; there is nothing else to do per iteration.
5106                        let mut sum = self.interp.scope.get_scalar_slot(*sum_slot).to_int();
5107                        let mut i = self.interp.scope.get_scalar_slot(*i_slot).to_int();
5108                        let limit = *limit as i64;
5109                        while i < limit {
5110                            sum = sum.wrapping_add(i);
5111                            i = i.wrapping_add(1);
5112                        }
5113                        self.interp
5114                            .scope
5115                            .set_scalar_slot(*sum_slot, PerlValue::integer(sum));
5116                        self.interp
5117                            .scope
5118                            .set_scalar_slot(*i_slot, PerlValue::integer(i));
5119                        Ok(())
5120                    }
5121                    Op::AddHashElemPlainKeyToSlot(sum_slot, k_name_idx, h_name_idx) => {
5122                        // `$sum += $h{$k}` — single-dispatch slot += hash[name-scalar] with no
5123                        // VM stack traffic. The key scalar is read via plain (name-based) access
5124                        // because the compiler's `for my $k (keys %h)` lowering currently backs
5125                        // `$k` with a frame scalar, not a slot.
5126                        let k_name = names[*k_name_idx as usize].as_str();
5127                        let h_name = names[*h_name_idx as usize].as_str();
5128                        self.interp.touch_env_hash(h_name);
5129                        let key = self.interp.scope.get_scalar(k_name).to_string();
5130                        let elem = self.interp.scope.get_hash_element(h_name, &key);
5131                        let cur = self.interp.scope.get_scalar_slot(*sum_slot);
5132                        let new_v =
5133                            if let (Some(a), Some(b)) = (cur.as_integer(), elem.as_integer()) {
5134                                PerlValue::integer(a.wrapping_add(b))
5135                            } else {
5136                                PerlValue::float(cur.to_number() + elem.to_number())
5137                            };
5138                        self.interp.scope.set_scalar_slot(*sum_slot, new_v);
5139                        Ok(())
5140                    }
5141                    Op::AddHashElemSlotKeyToSlot(sum_slot, k_slot, h_name_idx) => {
5142                        // `$sum += $h{$k}` — slot counter, slot key, slot sum. Zero name lookups
5143                        // for `$sum` and `$k`; one frame-walk for `%h` (same as the non-slot form).
5144                        let h_name = names[*h_name_idx as usize].as_str();
5145                        self.interp.touch_env_hash(h_name);
5146                        let key_val = self.interp.scope.get_scalar_slot(*k_slot);
5147                        let key = key_val.to_string();
5148                        let elem = self.interp.scope.get_hash_element(h_name, &key);
5149                        let cur = self.interp.scope.get_scalar_slot(*sum_slot);
5150                        let new_v =
5151                            if let (Some(a), Some(b)) = (cur.as_integer(), elem.as_integer()) {
5152                                PerlValue::integer(a.wrapping_add(b))
5153                            } else {
5154                                PerlValue::float(cur.to_number() + elem.to_number())
5155                            };
5156                        self.interp.scope.set_scalar_slot(*sum_slot, new_v);
5157                        Ok(())
5158                    }
5159                    Op::SumHashValuesToSlot(sum_slot, h_name_idx) => {
5160                        // `for my $k (keys %h) { $sum += $h{$k} }` fused to a single op that walks
5161                        // `hash.values()` in a tight native loop. No key stringification, no stack
5162                        // traffic, no per-iter dispatch. The foreach body reduced to
5163                        // `AddHashElemSlotKeyToSlot`, so this fusion is correct regardless of `$k`
5164                        // slot assignment — we never read `$k`.
5165                        let h_name = names[*h_name_idx as usize].as_str();
5166                        self.interp.touch_env_hash(h_name);
5167                        let cur = self.interp.scope.get_scalar_slot(*sum_slot);
5168                        let mut int_acc: i64 = cur.as_integer().unwrap_or(0);
5169                        let mut float_acc: f64 = 0.0;
5170                        let mut is_int = cur.as_integer().is_some();
5171                        if !is_int {
5172                            float_acc = cur.to_number();
5173                        }
5174                        // Walk the hash via the scope's borrow path without cloning the whole
5175                        // IndexMap. `for_each_hash_value` takes a visitor so the lock (if any) is
5176                        // held once rather than per-element.
5177                        self.interp.scope.for_each_hash_value(h_name, |v| {
5178                            if is_int {
5179                                if let Some(x) = v.as_integer() {
5180                                    int_acc = int_acc.wrapping_add(x);
5181                                    return;
5182                                }
5183                                float_acc = int_acc as f64;
5184                                is_int = false;
5185                            }
5186                            float_acc += v.to_number();
5187                        });
5188                        let new_v = if is_int {
5189                            PerlValue::integer(int_acc)
5190                        } else {
5191                            PerlValue::float(float_acc)
5192                        };
5193                        self.interp.scope.set_scalar_slot(*sum_slot, new_v);
5194                        Ok(())
5195                    }
5196                    Op::SetHashIntTimesLoop(h_name_idx, i_slot, k, limit) => {
5197                        // Runs the counted `while $i < limit { $h{$i} = $i * k; $i += 1 }` loop
5198                        // natively: the hash is `reserve()`d once, keys are stringified via
5199                        // `itoa` (no `format!` allocation), and values are inserted in a tight
5200                        // Rust loop. `$i` is left at `limit` on exit, matching the un-fused shape.
5201                        let i_cur = self.interp.scope.get_scalar_slot(*i_slot).to_int();
5202                        let lim = *limit as i64;
5203                        if i_cur < lim {
5204                            let n = names[*h_name_idx as usize].as_str();
5205                            self.require_hash_mutable(n)?;
5206                            self.interp.touch_env_hash(n);
5207                            let line = self.line();
5208                            self.interp
5209                                .scope
5210                                .set_hash_int_times_range(n, i_cur, lim, *k as i64)
5211                                .map_err(|e| e.at_line(line))?;
5212                        }
5213                        self.interp
5214                            .scope
5215                            .set_scalar_slot(*i_slot, PerlValue::integer(lim));
5216                        Ok(())
5217                    }
5218                    Op::PushIntRangeToArrayLoop(arr_name_idx, i_slot, limit) => {
5219                        // Runs the entire counted `while $i < limit { push @arr, $i; $i += 1 }`
5220                        // loop in native Rust. The array's `Vec<PerlValue>` is reserved once and
5221                        // `push(PerlValue::integer(i))` runs in a tight Rust loop — no per-iter
5222                        // op dispatch, no `require_array_mutable` check per iter.
5223                        let i_cur = self.interp.scope.get_scalar_slot(*i_slot).to_int();
5224                        let lim = *limit as i64;
5225                        if i_cur < lim {
5226                            let n = names[*arr_name_idx as usize].as_str();
5227                            self.require_array_mutable(n)?;
5228                            let line = self.line();
5229                            self.interp
5230                                .scope
5231                                .push_int_range_to_array(n, i_cur, lim)
5232                                .map_err(|e| e.at_line(line))?;
5233                        }
5234                        self.interp
5235                            .scope
5236                            .set_scalar_slot(*i_slot, PerlValue::integer(lim));
5237                        Ok(())
5238                    }
5239                    Op::ConcatConstSlotLoop(const_idx, s_slot, i_slot, limit) => {
5240                        // Runs the entire counted `while $i < limit { $s .= CONST; $i += 1 }` loop
5241                        // in native Rust. We stringify the constant once, reserve `(limit-i_cur) *
5242                        // const.len()` up front so the owning `String` reallocs at most twice, then
5243                        // `push_str` in a tight loop (see `try_concat_repeat_inplace`). Falls back
5244                        // to the per-iteration slow path when the slot is not the sole owner of a
5245                        // heap `String` — `.=` semantics match the un-fused shape byte-for-byte.
5246                        let i_cur = self.interp.scope.get_scalar_slot(*i_slot).to_int();
5247                        let lim = *limit as i64;
5248                        if i_cur < lim {
5249                            let n_iters = (lim - i_cur) as usize;
5250                            let rhs = constants[*const_idx as usize].as_str_or_empty();
5251                            if !self
5252                                .interp
5253                                .scope
5254                                .scalar_slot_concat_repeat_inplace(*s_slot, &rhs, n_iters)
5255                            {
5256                                self.interp
5257                                    .scope
5258                                    .scalar_slot_concat_repeat_slow(*s_slot, &rhs, n_iters);
5259                            }
5260                        }
5261                        self.interp
5262                            .scope
5263                            .set_scalar_slot(*i_slot, PerlValue::integer(lim));
5264                        Ok(())
5265                    }
5266                    Op::AddAssignSlotSlot(dst, src) => {
5267                        let a = self.interp.scope.get_scalar_slot(*dst);
5268                        let b = self.interp.scope.get_scalar_slot(*src);
5269                        let result = if let (Some(x), Some(y)) = (a.as_integer(), b.as_integer()) {
5270                            PerlValue::integer(x.wrapping_add(y))
5271                        } else {
5272                            PerlValue::float(a.to_number() + b.to_number())
5273                        };
5274                        self.interp.scope.set_scalar_slot(*dst, result.clone());
5275                        self.push(result);
5276                        Ok(())
5277                    }
5278                    Op::AddAssignSlotSlotVoid(dst, src) => {
5279                        let a = self.interp.scope.get_scalar_slot(*dst);
5280                        let b = self.interp.scope.get_scalar_slot(*src);
5281                        let result = if let (Some(x), Some(y)) = (a.as_integer(), b.as_integer()) {
5282                            PerlValue::integer(x.wrapping_add(y))
5283                        } else {
5284                            PerlValue::float(a.to_number() + b.to_number())
5285                        };
5286                        self.interp.scope.set_scalar_slot(*dst, result);
5287                        Ok(())
5288                    }
5289                    Op::SubAssignSlotSlot(dst, src) => {
5290                        let a = self.interp.scope.get_scalar_slot(*dst);
5291                        let b = self.interp.scope.get_scalar_slot(*src);
5292                        let result = if let (Some(x), Some(y)) = (a.as_integer(), b.as_integer()) {
5293                            PerlValue::integer(x.wrapping_sub(y))
5294                        } else {
5295                            PerlValue::float(a.to_number() - b.to_number())
5296                        };
5297                        self.interp.scope.set_scalar_slot(*dst, result.clone());
5298                        self.push(result);
5299                        Ok(())
5300                    }
5301                    Op::MulAssignSlotSlot(dst, src) => {
5302                        let a = self.interp.scope.get_scalar_slot(*dst);
5303                        let b = self.interp.scope.get_scalar_slot(*src);
5304                        let result = if let (Some(x), Some(y)) = (a.as_integer(), b.as_integer()) {
5305                            PerlValue::integer(x.wrapping_mul(y))
5306                        } else {
5307                            PerlValue::float(a.to_number() * b.to_number())
5308                        };
5309                        self.interp.scope.set_scalar_slot(*dst, result.clone());
5310                        self.push(result);
5311                        Ok(())
5312                    }
5313
5314                    // ── Frame-local scalar slots (O(1), no string lookup) ──
5315                    Op::GetScalarSlot(slot) => {
5316                        let val = self.interp.scope.get_scalar_slot(*slot);
5317                        self.push(val);
5318                        Ok(())
5319                    }
5320                    Op::SetScalarSlot(slot) => {
5321                        let val = self.pop();
5322                        self.interp
5323                            .scope
5324                            .set_scalar_slot_checked(*slot, val, None)
5325                            .map_err(|e| e.at_line(self.line()))?;
5326                        Ok(())
5327                    }
5328                    Op::SetScalarSlotKeep(slot) => {
5329                        let val = self.peek().dup_stack();
5330                        self.interp
5331                            .scope
5332                            .set_scalar_slot_checked(*slot, val, None)
5333                            .map_err(|e| e.at_line(self.line()))?;
5334                        Ok(())
5335                    }
5336                    Op::DeclareScalarSlot(slot, name_idx) => {
5337                        let val = self.pop();
5338                        let name_opt = if *name_idx == u16::MAX {
5339                            None
5340                        } else {
5341                            Some(names[*name_idx as usize].as_str())
5342                        };
5343                        self.interp.scope.declare_scalar_slot(*slot, val, name_opt);
5344                        Ok(())
5345                    }
5346                    Op::GetArg(idx) => {
5347                        // Read argument from caller's stack region without @_ allocation.
5348                        let val = if let Some(frame) = self.call_stack.last() {
5349                            let arg_pos = frame.stack_base + *idx as usize;
5350                            self.stack.get(arg_pos).cloned().unwrap_or(PerlValue::UNDEF)
5351                        } else {
5352                            PerlValue::UNDEF
5353                        };
5354                        self.push(val);
5355                        Ok(())
5356                    }
5357
5358                    Op::ReadIntoVar(name_idx) => {
5359                        let length = self.pop().to_int() as usize;
5360                        let fh_val = self.pop();
5361                        let name = &names[*name_idx as usize];
5362                        let line = self.line();
5363                        let result = vm_interp_result(
5364                            self.interp.builtin_read_into(fh_val, name, length, line),
5365                            line,
5366                        )?;
5367                        self.push(result);
5368                        Ok(())
5369                    }
5370                    Op::ChompInPlace(lvalue_idx) => {
5371                        let val = self.pop();
5372                        let target = &self.lvalues[*lvalue_idx as usize];
5373                        let line = self.line();
5374                        match self.interp.chomp_inplace_execute(val, target) {
5375                            Ok(v) => self.push(v),
5376                            Err(FlowOrError::Error(e)) => return Err(e),
5377                            Err(FlowOrError::Flow(_)) => {
5378                                return Err(PerlError::runtime("unexpected flow in chomp", line));
5379                            }
5380                        }
5381                        Ok(())
5382                    }
5383                    Op::ChopInPlace(lvalue_idx) => {
5384                        let val = self.pop();
5385                        let target = &self.lvalues[*lvalue_idx as usize];
5386                        let line = self.line();
5387                        match self.interp.chop_inplace_execute(val, target) {
5388                            Ok(v) => self.push(v),
5389                            Err(FlowOrError::Error(e)) => return Err(e),
5390                            Err(FlowOrError::Flow(_)) => {
5391                                return Err(PerlError::runtime("unexpected flow in chop", line));
5392                            }
5393                        }
5394                        Ok(())
5395                    }
5396                    Op::SubstrFourArg(idx) => {
5397                        let (string_e, offset_e, length_e, rep_e) =
5398                            &self.substr_four_arg_entries[*idx as usize];
5399                        let v = vm_interp_result(
5400                            self.interp.eval_substr_expr(
5401                                string_e,
5402                                offset_e,
5403                                length_e.as_ref(),
5404                                Some(rep_e),
5405                                self.line(),
5406                            ),
5407                            self.line(),
5408                        )?;
5409                        self.push(v);
5410                        Ok(())
5411                    }
5412                    Op::KeysExpr(idx) => {
5413                        let i = *idx as usize;
5414                        let line = self.line();
5415                        let v = if let Some(&(start, end)) = self
5416                            .keys_expr_bytecode_ranges
5417                            .get(i)
5418                            .and_then(|r| r.as_ref())
5419                        {
5420                            let val = self.run_block_region(start, end, op_count)?;
5421                            vm_interp_result(Interpreter::keys_from_value(val, line), line)?
5422                        } else {
5423                            let e = &self.keys_expr_entries[i];
5424                            vm_interp_result(self.interp.eval_keys_expr(e, line), line)?
5425                        };
5426                        self.push(v);
5427                        Ok(())
5428                    }
5429                    Op::KeysExprScalar(idx) => {
5430                        let i = *idx as usize;
5431                        let line = self.line();
5432                        let v = if let Some(&(start, end)) = self
5433                            .keys_expr_bytecode_ranges
5434                            .get(i)
5435                            .and_then(|r| r.as_ref())
5436                        {
5437                            let val = self.run_block_region(start, end, op_count)?;
5438                            vm_interp_result(Interpreter::keys_from_value(val, line), line)?
5439                        } else {
5440                            let e = &self.keys_expr_entries[i];
5441                            vm_interp_result(self.interp.eval_keys_expr(e, line), line)?
5442                        };
5443                        let n = v.as_array_vec().map(|a| a.len()).unwrap_or(0) as i64;
5444                        self.push(PerlValue::integer(n));
5445                        Ok(())
5446                    }
5447                    Op::ValuesExpr(idx) => {
5448                        let i = *idx as usize;
5449                        let line = self.line();
5450                        let v = if let Some(&(start, end)) = self
5451                            .values_expr_bytecode_ranges
5452                            .get(i)
5453                            .and_then(|r| r.as_ref())
5454                        {
5455                            let val = self.run_block_region(start, end, op_count)?;
5456                            vm_interp_result(Interpreter::values_from_value(val, line), line)?
5457                        } else {
5458                            let e = &self.values_expr_entries[i];
5459                            vm_interp_result(self.interp.eval_values_expr(e, line), line)?
5460                        };
5461                        self.push(v);
5462                        Ok(())
5463                    }
5464                    Op::ValuesExprScalar(idx) => {
5465                        let i = *idx as usize;
5466                        let line = self.line();
5467                        let v = if let Some(&(start, end)) = self
5468                            .values_expr_bytecode_ranges
5469                            .get(i)
5470                            .and_then(|r| r.as_ref())
5471                        {
5472                            let val = self.run_block_region(start, end, op_count)?;
5473                            vm_interp_result(Interpreter::values_from_value(val, line), line)?
5474                        } else {
5475                            let e = &self.values_expr_entries[i];
5476                            vm_interp_result(self.interp.eval_values_expr(e, line), line)?
5477                        };
5478                        let n = v.as_array_vec().map(|a| a.len()).unwrap_or(0) as i64;
5479                        self.push(PerlValue::integer(n));
5480                        Ok(())
5481                    }
5482                    Op::DeleteExpr(idx) => {
5483                        let e = &self.delete_expr_entries[*idx as usize];
5484                        let v = vm_interp_result(
5485                            self.interp.eval_delete_operand(e, self.line()),
5486                            self.line(),
5487                        )?;
5488                        self.push(v);
5489                        Ok(())
5490                    }
5491                    Op::ExistsExpr(idx) => {
5492                        let e = &self.exists_expr_entries[*idx as usize];
5493                        let v = vm_interp_result(
5494                            self.interp.eval_exists_operand(e, self.line()),
5495                            self.line(),
5496                        )?;
5497                        self.push(v);
5498                        Ok(())
5499                    }
5500                    Op::PushExpr(idx) => {
5501                        let (array, values) = &self.push_expr_entries[*idx as usize];
5502                        let v = vm_interp_result(
5503                            self.interp
5504                                .eval_push_expr(array, values.as_slice(), self.line()),
5505                            self.line(),
5506                        )?;
5507                        self.push(v);
5508                        Ok(())
5509                    }
5510                    Op::PopExpr(idx) => {
5511                        let e = &self.pop_expr_entries[*idx as usize];
5512                        let v = vm_interp_result(
5513                            self.interp.eval_pop_expr(e, self.line()),
5514                            self.line(),
5515                        )?;
5516                        self.push(v);
5517                        Ok(())
5518                    }
5519                    Op::ShiftExpr(idx) => {
5520                        let e = &self.shift_expr_entries[*idx as usize];
5521                        let v = vm_interp_result(
5522                            self.interp.eval_shift_expr(e, self.line()),
5523                            self.line(),
5524                        )?;
5525                        self.push(v);
5526                        Ok(())
5527                    }
5528                    Op::UnshiftExpr(idx) => {
5529                        let (array, values) = &self.unshift_expr_entries[*idx as usize];
5530                        let v = vm_interp_result(
5531                            self.interp
5532                                .eval_unshift_expr(array, values.as_slice(), self.line()),
5533                            self.line(),
5534                        )?;
5535                        self.push(v);
5536                        Ok(())
5537                    }
5538                    Op::SpliceExpr(idx) => {
5539                        let (array, offset, length, replacement) =
5540                            &self.splice_expr_entries[*idx as usize];
5541                        let v = vm_interp_result(
5542                            self.interp.eval_splice_expr(
5543                                array,
5544                                offset.as_ref(),
5545                                length.as_ref(),
5546                                replacement.as_slice(),
5547                                self.interp.wantarray_kind,
5548                                self.line(),
5549                            ),
5550                            self.line(),
5551                        )?;
5552                        self.push(v);
5553                        Ok(())
5554                    }
5555
5556                    // ── References ──
5557                    Op::MakeScalarRef => {
5558                        let val = self.pop();
5559                        self.push(PerlValue::scalar_ref(Arc::new(RwLock::new(val))));
5560                        Ok(())
5561                    }
5562                    Op::MakeScalarBindingRef(name_idx) => {
5563                        let name = names[*name_idx as usize].clone();
5564                        self.push(PerlValue::scalar_binding_ref(name));
5565                        Ok(())
5566                    }
5567                    Op::MakeArrayBindingRef(name_idx) => {
5568                        let name = &names[*name_idx as usize];
5569                        // Promote the scope's array to shared Arc-backed storage.
5570                        // Both the scope and the returned ref share the same Arc,
5571                        // so mutations through either path are visible.
5572                        let arc = self.interp.scope.promote_array_to_shared(name);
5573                        self.push(PerlValue::array_ref(arc));
5574                        Ok(())
5575                    }
5576                    Op::MakeHashBindingRef(name_idx) => {
5577                        let name = &names[*name_idx as usize];
5578                        let arc = self.interp.scope.promote_hash_to_shared(name);
5579                        self.push(PerlValue::hash_ref(arc));
5580                        Ok(())
5581                    }
5582                    Op::MakeArrayRefAlias => {
5583                        let v = self.pop();
5584                        let line = self.line();
5585                        let out =
5586                            vm_interp_result(self.interp.make_array_ref_alias(v, line), line)?;
5587                        self.push(out);
5588                        Ok(())
5589                    }
5590                    Op::MakeHashRefAlias => {
5591                        let v = self.pop();
5592                        let line = self.line();
5593                        let out = vm_interp_result(self.interp.make_hash_ref_alias(v, line), line)?;
5594                        self.push(out);
5595                        Ok(())
5596                    }
5597                    Op::MakeArrayRef => {
5598                        let val = self.pop();
5599                        let val = self.interp.scope.resolve_container_binding_ref(val);
5600                        let arr = if let Some(a) = val.as_array_vec() {
5601                            a
5602                        } else {
5603                            vec![val]
5604                        };
5605                        self.push(PerlValue::array_ref(Arc::new(RwLock::new(arr))));
5606                        Ok(())
5607                    }
5608                    Op::MakeHashRef => {
5609                        let val = self.pop();
5610                        let map = if let Some(h) = val.as_hash_map() {
5611                            h
5612                        } else {
5613                            let items = val.to_list();
5614                            let mut m = IndexMap::new();
5615                            let mut i = 0;
5616                            while i + 1 < items.len() {
5617                                m.insert(items[i].to_string(), items[i + 1].clone());
5618                                i += 2;
5619                            }
5620                            m
5621                        };
5622                        self.push(PerlValue::hash_ref(Arc::new(RwLock::new(map))));
5623                        Ok(())
5624                    }
5625                    Op::MakeCodeRef(block_idx, sig_idx) => {
5626                        let block = self.blocks[*block_idx as usize].clone();
5627                        let params = self.code_ref_sigs[*sig_idx as usize].clone();
5628                        let captured = self.interp.scope.capture();
5629                        self.push(PerlValue::code_ref(Arc::new(crate::value::PerlSub {
5630                            name: "__ANON__".to_string(),
5631                            params,
5632                            body: block,
5633                            closure_env: Some(captured),
5634                            prototype: None,
5635                            fib_like: None,
5636                        })));
5637                        Ok(())
5638                    }
5639                    Op::LoadNamedSubRef(name_idx) => {
5640                        let name = names[*name_idx as usize].as_str();
5641                        let line = self.line();
5642                        let sub = self.interp.resolve_sub_by_name(name).ok_or_else(|| {
5643                            PerlError::runtime(
5644                                self.interp.undefined_subroutine_resolve_message(name),
5645                                line,
5646                            )
5647                        })?;
5648                        self.push(PerlValue::code_ref(sub));
5649                        Ok(())
5650                    }
5651                    Op::LoadDynamicSubRef => {
5652                        let name = self.pop().to_string();
5653                        let line = self.line();
5654                        let sub = self.interp.resolve_sub_by_name(&name).ok_or_else(|| {
5655                            PerlError::runtime(
5656                                self.interp.undefined_subroutine_resolve_message(&name),
5657                                line,
5658                            )
5659                        })?;
5660                        self.push(PerlValue::code_ref(sub));
5661                        Ok(())
5662                    }
5663                    Op::LoadDynamicTypeglob => {
5664                        let name = self.pop().to_string();
5665                        let n = self.interp.resolve_io_handle_name(&name);
5666                        self.push(PerlValue::string(n));
5667                        Ok(())
5668                    }
5669                    Op::CopyTypeglobSlots(lhs_i, rhs_i) => {
5670                        let lhs = self.names[*lhs_i as usize].as_str();
5671                        let rhs = self.names[*rhs_i as usize].as_str();
5672                        let line = self.line();
5673                        self.interp
5674                            .copy_typeglob_slots(lhs, rhs, line)
5675                            .map_err(|e| e.at_line(line))?;
5676                        Ok(())
5677                    }
5678                    Op::TypeglobAssignFromValue(name_idx) => {
5679                        let val = self.pop();
5680                        let name = self.names[*name_idx as usize].as_str();
5681                        let line = self.line();
5682                        vm_interp_result(
5683                            self.interp.assign_typeglob_value(name, val.clone(), line),
5684                            line,
5685                        )?;
5686                        self.push(val);
5687                        Ok(())
5688                    }
5689                    Op::TypeglobAssignFromValueDynamic => {
5690                        let val = self.pop();
5691                        let name = self.pop().to_string();
5692                        let line = self.line();
5693                        vm_interp_result(
5694                            self.interp.assign_typeglob_value(&name, val.clone(), line),
5695                            line,
5696                        )?;
5697                        self.push(val);
5698                        Ok(())
5699                    }
5700                    Op::CopyTypeglobSlotsDynamicLhs(rhs_i) => {
5701                        let lhs = self.pop().to_string();
5702                        let rhs = self.names[*rhs_i as usize].as_str();
5703                        let line = self.line();
5704                        self.interp
5705                            .copy_typeglob_slots(&lhs, rhs, line)
5706                            .map_err(|e| e.at_line(line))?;
5707                        Ok(())
5708                    }
5709                    Op::SymbolicDeref(kind_byte) => {
5710                        let v = self.pop();
5711                        let kind = match *kind_byte {
5712                            0 => Sigil::Scalar,
5713                            1 => Sigil::Array,
5714                            2 => Sigil::Hash,
5715                            3 => Sigil::Typeglob,
5716                            _ => {
5717                                return Err(PerlError::runtime(
5718                                    "VM: bad SymbolicDeref kind byte",
5719                                    self.line(),
5720                                ));
5721                            }
5722                        };
5723                        let line = self.line();
5724                        let out =
5725                            vm_interp_result(self.interp.symbolic_deref(v, kind, line), line)?;
5726                        self.push(out);
5727                        Ok(())
5728                    }
5729
5730                    // ── Arrow dereference ──
5731                    Op::ArrowArray => {
5732                        let idx = self.pop().to_int();
5733                        let r = self.pop();
5734                        let line = self.line();
5735                        let v = vm_interp_result(
5736                            self.interp.read_arrow_array_element(r, idx, line),
5737                            line,
5738                        )?;
5739                        self.push(v);
5740                        Ok(())
5741                    }
5742                    Op::ArrowHash => {
5743                        let key = self.pop().to_string();
5744                        let r = self.pop();
5745                        let line = self.line();
5746                        let v = vm_interp_result(
5747                            self.interp.read_arrow_hash_element(r, key.as_str(), line),
5748                            line,
5749                        )?;
5750                        self.push(v);
5751                        Ok(())
5752                    }
5753                    Op::SetArrowHash => {
5754                        let key = self.pop().to_string();
5755                        let r = self.pop();
5756                        let val = self.pop();
5757                        let line = self.line();
5758                        vm_interp_result(
5759                            self.interp.assign_arrow_hash_deref(r, key, val, line),
5760                            line,
5761                        )?;
5762                        Ok(())
5763                    }
5764                    Op::SetArrowArray => {
5765                        let idx = self.pop().to_int();
5766                        let r = self.pop();
5767                        let val = self.pop();
5768                        let line = self.line();
5769                        vm_interp_result(
5770                            self.interp.assign_arrow_array_deref(r, idx, val, line),
5771                            line,
5772                        )?;
5773                        Ok(())
5774                    }
5775                    Op::SetArrowArrayKeep => {
5776                        let idx = self.pop().to_int();
5777                        let r = self.pop();
5778                        let val = self.pop();
5779                        let val_keep = val.clone();
5780                        let line = self.line();
5781                        vm_interp_result(
5782                            self.interp.assign_arrow_array_deref(r, idx, val, line),
5783                            line,
5784                        )?;
5785                        self.push(val_keep);
5786                        Ok(())
5787                    }
5788                    Op::SetArrowHashKeep => {
5789                        let key = self.pop().to_string();
5790                        let r = self.pop();
5791                        let val = self.pop();
5792                        let val_keep = val.clone();
5793                        let line = self.line();
5794                        vm_interp_result(
5795                            self.interp.assign_arrow_hash_deref(r, key, val, line),
5796                            line,
5797                        )?;
5798                        self.push(val_keep);
5799                        Ok(())
5800                    }
5801                    Op::ArrowArrayPostfix(b) => {
5802                        let idx = self.pop().to_int();
5803                        let r = self.pop();
5804                        let line = self.line();
5805                        let old = vm_interp_result(
5806                            self.interp.arrow_array_postfix(r, idx, *b == 1, line),
5807                            line,
5808                        )?;
5809                        self.push(old);
5810                        Ok(())
5811                    }
5812                    Op::ArrowHashPostfix(b) => {
5813                        let key = self.pop().to_string();
5814                        let r = self.pop();
5815                        let line = self.line();
5816                        let old = vm_interp_result(
5817                            self.interp.arrow_hash_postfix(r, key, *b == 1, line),
5818                            line,
5819                        )?;
5820                        self.push(old);
5821                        Ok(())
5822                    }
5823                    Op::SetSymbolicScalarRef => {
5824                        let r = self.pop();
5825                        let val = self.pop();
5826                        let line = self.line();
5827                        vm_interp_result(self.interp.assign_scalar_ref_deref(r, val, line), line)?;
5828                        Ok(())
5829                    }
5830                    Op::SetSymbolicScalarRefKeep => {
5831                        let r = self.pop();
5832                        let val = self.pop();
5833                        let val_keep = val.clone();
5834                        let line = self.line();
5835                        vm_interp_result(self.interp.assign_scalar_ref_deref(r, val, line), line)?;
5836                        self.push(val_keep);
5837                        Ok(())
5838                    }
5839                    Op::SetSymbolicArrayRef => {
5840                        let r = self.pop();
5841                        let val = self.pop();
5842                        let line = self.line();
5843                        vm_interp_result(
5844                            self.interp.assign_symbolic_array_ref_deref(r, val, line),
5845                            line,
5846                        )?;
5847                        Ok(())
5848                    }
5849                    Op::SetSymbolicHashRef => {
5850                        let r = self.pop();
5851                        let val = self.pop();
5852                        let line = self.line();
5853                        vm_interp_result(
5854                            self.interp.assign_symbolic_hash_ref_deref(r, val, line),
5855                            line,
5856                        )?;
5857                        Ok(())
5858                    }
5859                    Op::SetSymbolicTypeglobRef => {
5860                        let r = self.pop();
5861                        let val = self.pop();
5862                        let line = self.line();
5863                        vm_interp_result(
5864                            self.interp.assign_symbolic_typeglob_ref_deref(r, val, line),
5865                            line,
5866                        )?;
5867                        Ok(())
5868                    }
5869                    Op::SymbolicScalarRefPostfix(b) => {
5870                        let r = self.pop();
5871                        let line = self.line();
5872                        let old = vm_interp_result(
5873                            self.interp.symbolic_scalar_ref_postfix(r, *b == 1, line),
5874                            line,
5875                        )?;
5876                        self.push(old);
5877                        Ok(())
5878                    }
5879                    Op::ArrowCall(wa) => {
5880                        let want = WantarrayCtx::from_byte(*wa);
5881                        let args_val = self.pop();
5882                        let r = self.pop();
5883                        // Auto-deref ScalarRef so closures that captured $f can call $f->()
5884                        let r = if let Some(inner) = r.as_scalar_ref() {
5885                            inner.read().clone()
5886                        } else {
5887                            r
5888                        };
5889                        let args = args_val.to_list();
5890                        if let Some(sub) = r.as_code_ref() {
5891                            // Higher-order function wrappers (comp, partial, memoize, etc.)
5892                            // have empty bodies + magic closure_env keys. Dispatch them via
5893                            // the interpreter's try_hof_dispatch before falling through to
5894                            // the normal body execution path.
5895                            if let Some(hof_result) =
5896                                self.interp.try_hof_dispatch(&sub, &args, want, self.line())
5897                            {
5898                                let v = vm_interp_result(hof_result, self.line())?;
5899                                self.push(v);
5900                                return Ok(());
5901                            }
5902                            self.interp.current_sub_stack.push(sub.clone());
5903                            let saved_wa = self.interp.wantarray_kind;
5904                            self.interp.wantarray_kind = want;
5905                            self.interp.scope_push_hook();
5906                            self.interp.scope.declare_array("_", args.clone());
5907                            if let Some(ref env) = sub.closure_env {
5908                                self.interp.scope.restore_capture(env);
5909                            }
5910                            let line = self.line();
5911                            let argv = self.interp.scope.take_sub_underscore().unwrap_or_default();
5912                            self.interp
5913                                .apply_sub_signature(sub.as_ref(), &argv, line)
5914                                .map_err(|e| e.at_line(line))?;
5915                            self.interp.scope.declare_array("_", argv.clone());
5916                            // Set $_0, $_1, $_2, ... for all args, and $_ to first arg
5917                            self.interp.scope.set_closure_args(&argv);
5918                            let result = self.interp.exec_block_no_scope(&sub.body);
5919                            self.interp.wantarray_kind = saved_wa;
5920                            self.interp.scope_pop_hook();
5921                            self.interp.current_sub_stack.pop();
5922                            match result {
5923                                Ok(v) => self.push(v),
5924                                Err(crate::interpreter::FlowOrError::Flow(
5925                                    crate::interpreter::Flow::Return(v),
5926                                )) => self.push(v),
5927                                Err(crate::interpreter::FlowOrError::Error(e)) => return Err(e),
5928                                Err(_) => self.push(PerlValue::UNDEF),
5929                            }
5930                        } else {
5931                            return Err(PerlError::runtime("Not a code reference", self.line()));
5932                        }
5933                        Ok(())
5934                    }
5935                    Op::IndirectCall(argc, wa, pass_flag) => {
5936                        let want = WantarrayCtx::from_byte(*wa);
5937                        let line = self.line();
5938                        let arg_vals = if *pass_flag != 0 {
5939                            self.interp.scope.get_array("_")
5940                        } else {
5941                            let n = *argc as usize;
5942                            let mut args = Vec::with_capacity(n);
5943                            for _ in 0..n {
5944                                args.push(self.pop());
5945                            }
5946                            args.reverse();
5947                            args
5948                        };
5949                        let target = self.pop();
5950                        // HOF wrapper fast path (comp, partial, memoize, etc.)
5951                        if let Some(sub) = target.as_code_ref() {
5952                            if let Some(hof_result) =
5953                                self.interp.try_hof_dispatch(&sub, &arg_vals, want, line)
5954                            {
5955                                let v = vm_interp_result(hof_result, line)?;
5956                                self.push(v);
5957                                return Ok(());
5958                            }
5959                        }
5960                        let r = self
5961                            .interp
5962                            .dispatch_indirect_call(target, arg_vals, want, line);
5963                        let v = vm_interp_result(r, line)?;
5964                        self.push(v);
5965                        Ok(())
5966                    }
5967
5968                    // ── Method call ──
5969                    Op::MethodCall(name_idx, argc, wa) => {
5970                        self.run_method_op(*name_idx, *argc, *wa, false)?;
5971                        Ok(())
5972                    }
5973                    Op::MethodCallSuper(name_idx, argc, wa) => {
5974                        self.run_method_op(*name_idx, *argc, *wa, true)?;
5975                        Ok(())
5976                    }
5977
5978                    // ── File test ──
5979                    Op::FileTestOp(test) => {
5980                        let path = self.pop().to_string();
5981                        let op = *test as char;
5982                        // -M, -A, -C return fractional days (float)
5983                        if matches!(op, 'M' | 'A' | 'C') {
5984                            #[cfg(unix)]
5985                            {
5986                                let v = match crate::perl_fs::filetest_age_days(&path, op) {
5987                                    Some(days) => PerlValue::float(days),
5988                                    None => PerlValue::UNDEF,
5989                                };
5990                                self.push(v);
5991                                return Ok(());
5992                            }
5993                            #[cfg(not(unix))]
5994                            {
5995                                self.push(PerlValue::UNDEF);
5996                                return Ok(());
5997                            }
5998                        }
5999                        // -s returns file size (integer)
6000                        if op == 's' {
6001                            let v = match std::fs::metadata(&path) {
6002                                Ok(m) => PerlValue::integer(m.len() as i64),
6003                                Err(_) => PerlValue::UNDEF,
6004                            };
6005                            self.push(v);
6006                            return Ok(());
6007                        }
6008                        let result = match op {
6009                            'e' => std::path::Path::new(&path).exists(),
6010                            'f' => std::path::Path::new(&path).is_file(),
6011                            'd' => std::path::Path::new(&path).is_dir(),
6012                            'l' => std::path::Path::new(&path).is_symlink(),
6013                            #[cfg(unix)]
6014                            'r' => crate::perl_fs::filetest_effective_access(&path, 4),
6015                            #[cfg(not(unix))]
6016                            'r' => std::fs::metadata(&path).is_ok(),
6017                            #[cfg(unix)]
6018                            'w' => crate::perl_fs::filetest_effective_access(&path, 2),
6019                            #[cfg(not(unix))]
6020                            'w' => std::fs::metadata(&path).is_ok(),
6021                            #[cfg(unix)]
6022                            'x' => crate::perl_fs::filetest_effective_access(&path, 1),
6023                            #[cfg(not(unix))]
6024                            'x' => false,
6025                            #[cfg(unix)]
6026                            'o' => crate::perl_fs::filetest_owned_effective(&path),
6027                            #[cfg(not(unix))]
6028                            'o' => false,
6029                            #[cfg(unix)]
6030                            'R' => crate::perl_fs::filetest_real_access(&path, libc::R_OK),
6031                            #[cfg(not(unix))]
6032                            'R' => false,
6033                            #[cfg(unix)]
6034                            'W' => crate::perl_fs::filetest_real_access(&path, libc::W_OK),
6035                            #[cfg(not(unix))]
6036                            'W' => false,
6037                            #[cfg(unix)]
6038                            'X' => crate::perl_fs::filetest_real_access(&path, libc::X_OK),
6039                            #[cfg(not(unix))]
6040                            'X' => false,
6041                            #[cfg(unix)]
6042                            'O' => crate::perl_fs::filetest_owned_real(&path),
6043                            #[cfg(not(unix))]
6044                            'O' => false,
6045                            'z' => std::fs::metadata(&path)
6046                                .map(|m| m.len() == 0)
6047                                .unwrap_or(true),
6048                            't' => crate::perl_fs::filetest_is_tty(&path),
6049                            #[cfg(unix)]
6050                            'p' => crate::perl_fs::filetest_is_pipe(&path),
6051                            #[cfg(not(unix))]
6052                            'p' => false,
6053                            #[cfg(unix)]
6054                            'S' => crate::perl_fs::filetest_is_socket(&path),
6055                            #[cfg(not(unix))]
6056                            'S' => false,
6057                            #[cfg(unix)]
6058                            'b' => crate::perl_fs::filetest_is_block_device(&path),
6059                            #[cfg(not(unix))]
6060                            'b' => false,
6061                            #[cfg(unix)]
6062                            'c' => crate::perl_fs::filetest_is_char_device(&path),
6063                            #[cfg(not(unix))]
6064                            'c' => false,
6065                            #[cfg(unix)]
6066                            'u' => crate::perl_fs::filetest_is_setuid(&path),
6067                            #[cfg(not(unix))]
6068                            'u' => false,
6069                            #[cfg(unix)]
6070                            'g' => crate::perl_fs::filetest_is_setgid(&path),
6071                            #[cfg(not(unix))]
6072                            'g' => false,
6073                            #[cfg(unix)]
6074                            'k' => crate::perl_fs::filetest_is_sticky(&path),
6075                            #[cfg(not(unix))]
6076                            'k' => false,
6077                            'T' => crate::perl_fs::filetest_is_text(&path),
6078                            'B' => crate::perl_fs::filetest_is_binary(&path),
6079                            _ => false,
6080                        };
6081                        self.push(PerlValue::integer(if result { 1 } else { 0 }));
6082                        Ok(())
6083                    }
6084
6085                    // ── Map/Grep/Sort with blocks (opcodes when lowered; else AST block fallback) ──
6086                    Op::MapIntMul(k) => {
6087                        let list = self.pop().to_list();
6088                        if list.len() == 1 {
6089                            if let Some(p) = list[0].as_pipeline() {
6090                                let line = self.line();
6091                                let sub = Interpreter::pipeline_int_mul_sub(*k);
6092                                self.interp.pipeline_push(&p, PipelineOp::Map(sub), line)?;
6093                                self.push(PerlValue::pipeline(Arc::clone(&p)));
6094                                return Ok(());
6095                            }
6096                        }
6097                        let mut result = Vec::with_capacity(list.len());
6098                        for item in list {
6099                            let n = item.to_int();
6100                            result.push(PerlValue::integer(n.wrapping_mul(*k)));
6101                        }
6102                        self.push(PerlValue::array(result));
6103                        Ok(())
6104                    }
6105                    Op::GrepIntModEq(m, r) => {
6106                        let list = self.pop().to_list();
6107                        let mut result = Vec::new();
6108                        for item in list {
6109                            let n = item.to_int();
6110                            if n % m == *r {
6111                                result.push(item);
6112                            }
6113                        }
6114                        self.push(PerlValue::array(result));
6115                        Ok(())
6116                    }
6117                    Op::MapWithBlock(block_idx) => {
6118                        let list = self.pop().to_list();
6119                        self.map_with_block_common(list, *block_idx, false, op_count)
6120                    }
6121                    Op::FlatMapWithBlock(block_idx) => {
6122                        let list = self.pop().to_list();
6123                        self.map_with_block_common(list, *block_idx, true, op_count)
6124                    }
6125                    Op::MapWithExpr(expr_idx) => {
6126                        let list = self.pop().to_list();
6127                        self.map_with_expr_common(list, *expr_idx, false, op_count)
6128                    }
6129                    Op::FlatMapWithExpr(expr_idx) => {
6130                        let list = self.pop().to_list();
6131                        self.map_with_expr_common(list, *expr_idx, true, op_count)
6132                    }
6133                    Op::MapsWithBlock(block_idx) => {
6134                        let val = self.pop();
6135                        let block = self.blocks[*block_idx as usize].clone();
6136                        let out =
6137                            self.interp
6138                                .map_stream_block_output(val, &block, false, self.line())?;
6139                        self.push(out);
6140                        Ok(())
6141                    }
6142                    Op::MapsFlatMapWithBlock(block_idx) => {
6143                        let val = self.pop();
6144                        let block = self.blocks[*block_idx as usize].clone();
6145                        let out =
6146                            self.interp
6147                                .map_stream_block_output(val, &block, true, self.line())?;
6148                        self.push(out);
6149                        Ok(())
6150                    }
6151                    Op::MapsWithExpr(expr_idx) => {
6152                        let val = self.pop();
6153                        let idx = *expr_idx as usize;
6154                        let expr = self.map_expr_entries[idx].clone();
6155                        let out =
6156                            self.interp
6157                                .map_stream_expr_output(val, &expr, false, self.line())?;
6158                        self.push(out);
6159                        Ok(())
6160                    }
6161                    Op::MapsFlatMapWithExpr(expr_idx) => {
6162                        let val = self.pop();
6163                        let idx = *expr_idx as usize;
6164                        let expr = self.map_expr_entries[idx].clone();
6165                        let out =
6166                            self.interp
6167                                .map_stream_expr_output(val, &expr, true, self.line())?;
6168                        self.push(out);
6169                        Ok(())
6170                    }
6171                    Op::FilterWithBlock(block_idx) => {
6172                        let val = self.pop();
6173                        let block = self.blocks[*block_idx as usize].clone();
6174                        let out =
6175                            self.interp
6176                                .filter_stream_block_output(val, &block, self.line())?;
6177                        self.push(out);
6178                        Ok(())
6179                    }
6180                    Op::FilterWithExpr(expr_idx) => {
6181                        let val = self.pop();
6182                        let idx = *expr_idx as usize;
6183                        let expr = self.grep_expr_entries[idx].clone();
6184                        let out = self
6185                            .interp
6186                            .filter_stream_expr_output(val, &expr, self.line())?;
6187                        self.push(out);
6188                        Ok(())
6189                    }
6190                    Op::ChunkByWithBlock(block_idx) => {
6191                        let list = self.pop().to_list();
6192                        self.chunk_by_with_block_common(list, *block_idx, op_count)
6193                    }
6194                    Op::ChunkByWithExpr(expr_idx) => {
6195                        let list = self.pop().to_list();
6196                        self.chunk_by_with_expr_common(list, *expr_idx, op_count)
6197                    }
6198                    Op::GrepWithBlock(block_idx) => {
6199                        let list = self.pop().to_list();
6200                        if list.len() == 1 {
6201                            if let Some(p) = list[0].as_pipeline() {
6202                                let idx = *block_idx as usize;
6203                                let sub = self.interp.anon_coderef_from_block(&self.blocks[idx]);
6204                                let line = self.line();
6205                                self.interp
6206                                    .pipeline_push(&p, PipelineOp::Filter(sub), line)?;
6207                                self.push(PerlValue::pipeline(Arc::clone(&p)));
6208                                return Ok(());
6209                            }
6210                        }
6211                        let idx = *block_idx as usize;
6212                        if let Some(&(start, end)) =
6213                            self.block_bytecode_ranges.get(idx).and_then(|r| r.as_ref())
6214                        {
6215                            let mut result = Vec::new();
6216                            for item in list {
6217                                self.interp.scope.set_topic(item.clone());
6218                                let val = self.run_block_region(start, end, op_count)?;
6219                                // Bare regex → match against $_ (Perl: /pat/ in grep is $_ =~ /pat/)
6220                                let keep = if let Some(re) = val.as_regex() {
6221                                    re.is_match(&item.to_string())
6222                                } else {
6223                                    val.is_true()
6224                                };
6225                                if keep {
6226                                    result.push(item);
6227                                }
6228                            }
6229                            self.push(PerlValue::array(result));
6230                            Ok(())
6231                        } else {
6232                            let block = self.blocks[idx].clone();
6233                            let mut result = Vec::new();
6234                            for item in list {
6235                                self.interp.scope.set_topic(item.clone());
6236                                match self.interp.exec_block(&block) {
6237                                    Ok(val) => {
6238                                        let keep = if let Some(re) = val.as_regex() {
6239                                            re.is_match(&item.to_string())
6240                                        } else {
6241                                            val.is_true()
6242                                        };
6243                                        if keep {
6244                                            result.push(item);
6245                                        }
6246                                    }
6247                                    Err(crate::interpreter::FlowOrError::Error(e)) => {
6248                                        return Err(e)
6249                                    }
6250                                    Err(_) => {}
6251                                }
6252                            }
6253                            self.push(PerlValue::array(result));
6254                            Ok(())
6255                        }
6256                    }
6257                    Op::ForEachWithBlock(block_idx) => {
6258                        let val = self.pop();
6259                        let idx = *block_idx as usize;
6260                        // Lazy iterator: consume one-at-a-time without materializing.
6261                        if val.is_iterator() {
6262                            let iter = val.into_iterator();
6263                            let mut count = 0i64;
6264                            if let Some(&(start, end)) =
6265                                self.block_bytecode_ranges.get(idx).and_then(|r| r.as_ref())
6266                            {
6267                                while let Some(item) = iter.next_item() {
6268                                    count += 1;
6269                                    self.interp.scope.set_topic(item);
6270                                    self.run_block_region(start, end, op_count)?;
6271                                }
6272                            } else {
6273                                let block = self.blocks[idx].clone();
6274                                while let Some(item) = iter.next_item() {
6275                                    count += 1;
6276                                    self.interp.scope.set_topic(item);
6277                                    match self.interp.exec_block(&block) {
6278                                        Ok(_) => {}
6279                                        Err(crate::interpreter::FlowOrError::Error(e)) => {
6280                                            return Err(e)
6281                                        }
6282                                        Err(_) => {}
6283                                    }
6284                                }
6285                            }
6286                            self.push(PerlValue::integer(count));
6287                            return Ok(());
6288                        }
6289                        let list = val.to_list();
6290                        let count = list.len() as i64;
6291                        if let Some(&(start, end)) =
6292                            self.block_bytecode_ranges.get(idx).and_then(|r| r.as_ref())
6293                        {
6294                            for item in list {
6295                                self.interp.scope.set_topic(item);
6296                                self.run_block_region(start, end, op_count)?;
6297                            }
6298                        } else {
6299                            let block = self.blocks[idx].clone();
6300                            for item in list {
6301                                self.interp.scope.set_topic(item);
6302                                match self.interp.exec_block(&block) {
6303                                    Ok(_) => {}
6304                                    Err(crate::interpreter::FlowOrError::Error(e)) => {
6305                                        return Err(e)
6306                                    }
6307                                    Err(_) => {}
6308                                }
6309                            }
6310                        }
6311                        self.push(PerlValue::integer(count));
6312                        Ok(())
6313                    }
6314                    Op::GrepWithExpr(expr_idx) => {
6315                        let list = self.pop().to_list();
6316                        let idx = *expr_idx as usize;
6317                        if let Some(&(start, end)) = self
6318                            .grep_expr_bytecode_ranges
6319                            .get(idx)
6320                            .and_then(|r| r.as_ref())
6321                        {
6322                            let mut result = Vec::new();
6323                            for item in list {
6324                                self.interp.scope.set_topic(item.clone());
6325                                let val = self.run_block_region(start, end, op_count)?;
6326                                let keep = if let Some(re) = val.as_regex() {
6327                                    re.is_match(&item.to_string())
6328                                } else {
6329                                    val.is_true()
6330                                };
6331                                if keep {
6332                                    result.push(item);
6333                                }
6334                            }
6335                            self.push(PerlValue::array(result));
6336                            Ok(())
6337                        } else {
6338                            let e = &self.grep_expr_entries[idx];
6339                            let mut result = Vec::new();
6340                            for item in list {
6341                                self.interp.scope.set_topic(item.clone());
6342                                let val = vm_interp_result(self.interp.eval_expr(e), self.line())?;
6343                                let keep = if let Some(re) = val.as_regex() {
6344                                    re.is_match(&item.to_string())
6345                                } else {
6346                                    val.is_true()
6347                                };
6348                                if keep {
6349                                    result.push(item);
6350                                }
6351                            }
6352                            self.push(PerlValue::array(result));
6353                            Ok(())
6354                        }
6355                    }
6356                    Op::SortWithBlock(block_idx) => {
6357                        let mut items = self.pop().to_list();
6358                        let idx = *block_idx as usize;
6359                        if let Some(&(start, end)) =
6360                            self.block_bytecode_ranges.get(idx).and_then(|r| r.as_ref())
6361                        {
6362                            let mut sort_err: Option<PerlError> = None;
6363                            items.sort_by(|a, b| {
6364                                if sort_err.is_some() {
6365                                    return std::cmp::Ordering::Equal;
6366                                }
6367                                let _ = self.interp.scope.set_scalar("a", a.clone());
6368                                let _ = self.interp.scope.set_scalar("b", b.clone());
6369                                let _ = self.interp.scope.set_scalar("_0", a.clone());
6370                                let _ = self.interp.scope.set_scalar("_1", b.clone());
6371                                match self.run_block_region(start, end, op_count) {
6372                                    Ok(v) => {
6373                                        let n = v.to_int();
6374                                        if n < 0 {
6375                                            std::cmp::Ordering::Less
6376                                        } else if n > 0 {
6377                                            std::cmp::Ordering::Greater
6378                                        } else {
6379                                            std::cmp::Ordering::Equal
6380                                        }
6381                                    }
6382                                    Err(e) => {
6383                                        sort_err = Some(e);
6384                                        std::cmp::Ordering::Equal
6385                                    }
6386                                }
6387                            });
6388                            if let Some(e) = sort_err {
6389                                return Err(e);
6390                            }
6391                            self.push(PerlValue::array(items));
6392                            Ok(())
6393                        } else {
6394                            let block = self.blocks[idx].clone();
6395                            items.sort_by(|a, b| {
6396                                let _ = self.interp.scope.set_scalar("a", a.clone());
6397                                let _ = self.interp.scope.set_scalar("b", b.clone());
6398                                let _ = self.interp.scope.set_scalar("_0", a.clone());
6399                                let _ = self.interp.scope.set_scalar("_1", b.clone());
6400                                match self.interp.exec_block(&block) {
6401                                    Ok(v) => {
6402                                        let n = v.to_int();
6403                                        if n < 0 {
6404                                            std::cmp::Ordering::Less
6405                                        } else if n > 0 {
6406                                            std::cmp::Ordering::Greater
6407                                        } else {
6408                                            std::cmp::Ordering::Equal
6409                                        }
6410                                    }
6411                                    Err(_) => std::cmp::Ordering::Equal,
6412                                }
6413                            });
6414                            self.push(PerlValue::array(items));
6415                            Ok(())
6416                        }
6417                    }
6418                    Op::SortWithBlockFast(tag) => {
6419                        let mut items = self.pop().to_list();
6420                        let mode = match *tag {
6421                            0 => SortBlockFast::Numeric,
6422                            1 => SortBlockFast::String,
6423                            2 => SortBlockFast::NumericRev,
6424                            3 => SortBlockFast::StringRev,
6425                            _ => SortBlockFast::Numeric,
6426                        };
6427                        items.sort_by(|a, b| sort_magic_cmp(a, b, mode));
6428                        self.push(PerlValue::array(items));
6429                        Ok(())
6430                    }
6431                    Op::SortNoBlock => {
6432                        let mut items = self.pop().to_list();
6433                        items.sort_by_key(|a| a.to_string());
6434                        self.push(PerlValue::array(items));
6435                        Ok(())
6436                    }
6437                    Op::SortWithCodeComparator(wa) => {
6438                        let want = WantarrayCtx::from_byte(*wa);
6439                        let cmp_val = self.pop();
6440                        let mut items = self.pop().to_list();
6441                        let line = self.line();
6442                        let Some(sub) = cmp_val.as_code_ref() else {
6443                            return Err(PerlError::runtime(
6444                                "sort: comparator must be a code reference",
6445                                line,
6446                            ));
6447                        };
6448                        let interp = &mut self.interp;
6449                        items.sort_by(|a, b| {
6450                            let _ = interp.scope.set_scalar("a", a.clone());
6451                            let _ = interp.scope.set_scalar("b", b.clone());
6452                            let _ = interp.scope.set_scalar("_0", a.clone());
6453                            let _ = interp.scope.set_scalar("_1", b.clone());
6454                            match interp.call_sub(sub.as_ref(), vec![], want, line) {
6455                                Ok(v) => {
6456                                    let n = v.to_int();
6457                                    if n < 0 {
6458                                        std::cmp::Ordering::Less
6459                                    } else if n > 0 {
6460                                        std::cmp::Ordering::Greater
6461                                    } else {
6462                                        std::cmp::Ordering::Equal
6463                                    }
6464                                }
6465                                Err(_) => std::cmp::Ordering::Equal,
6466                            }
6467                        });
6468                        self.push(PerlValue::array(items));
6469                        Ok(())
6470                    }
6471                    Op::ReverseListOp => {
6472                        let val = self.pop();
6473                        if val.is_iterator() {
6474                            self.push(PerlValue::iterator(std::sync::Arc::new(
6475                                crate::value::RevIterator::new(val.into_iterator()),
6476                            )));
6477                        } else {
6478                            let mut items = val.to_list();
6479                            items.reverse();
6480                            self.push(PerlValue::array(items));
6481                        }
6482                        Ok(())
6483                    }
6484                    Op::ReverseScalarOp => {
6485                        let val = self.pop();
6486                        let items = val.to_list();
6487                        let s: String = items.iter().map(|v| v.to_string()).collect();
6488                        self.push(PerlValue::string(s.chars().rev().collect()));
6489                        Ok(())
6490                    }
6491                    Op::RevListOp => {
6492                        let val = self.pop();
6493                        if val.is_iterator() {
6494                            // Collect the iterator fully and reverse the list order.
6495                            // RevIterator does per-element char reversal, not list reversal.
6496                            let mut items = val.to_list();
6497                            items.reverse();
6498                            self.push(PerlValue::array(items));
6499                        } else if let Some(s) = crate::value::set_payload(&val) {
6500                            let mut out = crate::value::PerlSet::new();
6501                            for (k, v) in s.iter().rev() {
6502                                out.insert(k.clone(), v.clone());
6503                            }
6504                            self.push(PerlValue::set(std::sync::Arc::new(out)));
6505                        } else if let Some(ar) = val.as_array_ref() {
6506                            let items: Vec<_> = ar.read().iter().rev().cloned().collect();
6507                            self.push(PerlValue::array_ref(std::sync::Arc::new(
6508                                parking_lot::RwLock::new(items),
6509                            )));
6510                        } else if let Some(hr) = val.as_hash_ref() {
6511                            let mut out: indexmap::IndexMap<String, PerlValue> =
6512                                indexmap::IndexMap::new();
6513                            for (k, v) in hr.read().iter() {
6514                                out.insert(v.to_string(), PerlValue::string(k.clone()));
6515                            }
6516                            self.push(PerlValue::hash_ref(std::sync::Arc::new(
6517                                parking_lot::RwLock::new(out),
6518                            )));
6519                        } else if let Some(hm) = val.as_hash_map() {
6520                            let mut out: indexmap::IndexMap<String, PerlValue> =
6521                                indexmap::IndexMap::new();
6522                            for (k, v) in hm.iter() {
6523                                out.insert(v.to_string(), PerlValue::string(k.clone()));
6524                            }
6525                            self.push(PerlValue::hash(out));
6526                        } else if val.as_array_vec().is_some() {
6527                            let mut items = val.to_list();
6528                            items.reverse();
6529                            self.push(PerlValue::array(items));
6530                        } else {
6531                            let s = val.to_string();
6532                            self.push(PerlValue::string(s.chars().rev().collect()));
6533                        }
6534                        Ok(())
6535                    }
6536                    Op::RevScalarOp => {
6537                        let val = self.pop();
6538                        if let Some(s) = crate::value::set_payload(&val) {
6539                            let mut out = crate::value::PerlSet::new();
6540                            for (k, v) in s.iter().rev() {
6541                                out.insert(k.clone(), v.clone());
6542                            }
6543                            self.push(PerlValue::set(std::sync::Arc::new(out)));
6544                        } else if let Some(ar) = val.as_array_ref() {
6545                            let items: Vec<_> = ar.read().iter().rev().cloned().collect();
6546                            self.push(PerlValue::array_ref(std::sync::Arc::new(
6547                                parking_lot::RwLock::new(items),
6548                            )));
6549                        } else if let Some(hr) = val.as_hash_ref() {
6550                            let mut out: indexmap::IndexMap<String, PerlValue> =
6551                                indexmap::IndexMap::new();
6552                            for (k, v) in hr.read().iter() {
6553                                out.insert(v.to_string(), PerlValue::string(k.clone()));
6554                            }
6555                            self.push(PerlValue::hash_ref(std::sync::Arc::new(
6556                                parking_lot::RwLock::new(out),
6557                            )));
6558                        } else {
6559                            let items = val.to_list();
6560                            let s: String = items.iter().map(|v| v.to_string()).collect();
6561                            self.push(PerlValue::string(s.chars().rev().collect()));
6562                        }
6563                        Ok(())
6564                    }
6565                    Op::StackArrayLen => {
6566                        let v = self.pop();
6567                        self.push(PerlValue::integer(v.to_list().len() as i64));
6568                        Ok(())
6569                    }
6570                    Op::ListSliceToScalar => {
6571                        let v = self.pop();
6572                        let items = v.to_list();
6573                        self.push(items.last().cloned().unwrap_or(PerlValue::UNDEF));
6574                        Ok(())
6575                    }
6576
6577                    // ── Eval block ──
6578                    Op::EvalBlock(block_idx, want) => {
6579                        let block = self.blocks[*block_idx as usize].clone();
6580                        let tail = crate::interpreter::WantarrayCtx::from_byte(*want);
6581                        self.interp.eval_nesting += 1;
6582                        // Use exec_block (with scope frame) so local/my declarations
6583                        // inside the block are properly scoped.
6584                        match self.interp.exec_block_with_tail(&block, tail) {
6585                            Ok(v) => {
6586                                self.interp.clear_eval_error();
6587                                self.push(v);
6588                            }
6589                            Err(crate::interpreter::FlowOrError::Error(e)) => {
6590                                self.interp.set_eval_error_from_perl_error(&e);
6591                                self.push(PerlValue::UNDEF);
6592                            }
6593                            Err(_) => self.push(PerlValue::UNDEF),
6594                        }
6595                        self.interp.eval_nesting -= 1;
6596                        Ok(())
6597                    }
6598                    Op::TraceBlock(block_idx) => {
6599                        let block = self.blocks[*block_idx as usize].clone();
6600                        crate::parallel_trace::trace_enter();
6601                        self.interp.eval_nesting += 1;
6602                        match self.interp.exec_block(&block) {
6603                            Ok(v) => {
6604                                self.interp.clear_eval_error();
6605                                self.push(v);
6606                            }
6607                            Err(FlowOrError::Error(e)) => {
6608                                self.interp.set_eval_error_from_perl_error(&e);
6609                                self.push(PerlValue::UNDEF);
6610                            }
6611                            Err(_) => self.push(PerlValue::UNDEF),
6612                        }
6613                        self.interp.eval_nesting -= 1;
6614                        crate::parallel_trace::trace_leave();
6615                        Ok(())
6616                    }
6617                    Op::TimerBlock(block_idx) => {
6618                        let block = self.blocks[*block_idx as usize].clone();
6619                        let start = std::time::Instant::now();
6620                        self.interp.eval_nesting += 1;
6621                        let _ = match self.interp.exec_block(&block) {
6622                            Ok(v) => {
6623                                self.interp.clear_eval_error();
6624                                v
6625                            }
6626                            Err(FlowOrError::Error(e)) => {
6627                                self.interp.set_eval_error_from_perl_error(&e);
6628                                PerlValue::UNDEF
6629                            }
6630                            Err(_) => PerlValue::UNDEF,
6631                        };
6632                        self.interp.eval_nesting -= 1;
6633                        let ms = start.elapsed().as_secs_f64() * 1000.0;
6634                        self.push(PerlValue::float(ms));
6635                        Ok(())
6636                    }
6637                    Op::BenchBlock(block_idx) => {
6638                        let n_i = self.pop().to_int();
6639                        if n_i < 0 {
6640                            return Err(PerlError::runtime(
6641                                "bench: iteration count must be non-negative",
6642                                self.line(),
6643                            ));
6644                        }
6645                        let n = n_i as usize;
6646                        let block = self.blocks[*block_idx as usize].clone();
6647                        let v = vm_interp_result(
6648                            self.interp.run_bench_block(&block, n, self.line()),
6649                            self.line(),
6650                        )?;
6651                        self.push(v);
6652                        Ok(())
6653                    }
6654                    Op::Given(idx) => {
6655                        let i = *idx as usize;
6656                        let line = self.line();
6657                        let v = if let Some(&(start, end)) = self
6658                            .given_topic_bytecode_ranges
6659                            .get(i)
6660                            .and_then(|r| r.as_ref())
6661                        {
6662                            let topic_val = self.run_block_region(start, end, op_count)?;
6663                            let body = &self.given_entries[i].1;
6664                            vm_interp_result(
6665                                self.interp.exec_given_with_topic_value(topic_val, body),
6666                                line,
6667                            )?
6668                        } else {
6669                            let (topic, body) = &self.given_entries[i];
6670                            vm_interp_result(self.interp.exec_given(topic, body), line)?
6671                        };
6672                        self.push(v);
6673                        Ok(())
6674                    }
6675                    Op::EvalTimeout(idx) => {
6676                        let i = *idx as usize;
6677                        let body = self.eval_timeout_entries[i].1.clone();
6678                        let secs = if let Some(&(start, end)) = self
6679                            .eval_timeout_expr_bytecode_ranges
6680                            .get(i)
6681                            .and_then(|r| r.as_ref())
6682                        {
6683                            self.run_block_region(start, end, op_count)?.to_number()
6684                        } else {
6685                            let timeout_expr = &self.eval_timeout_entries[i].0;
6686                            vm_interp_result(self.interp.eval_expr(timeout_expr), self.line())?
6687                                .to_number()
6688                        };
6689                        let v = vm_interp_result(
6690                            self.interp.eval_timeout_block(&body, secs, self.line()),
6691                            self.line(),
6692                        )?;
6693                        self.push(v);
6694                        Ok(())
6695                    }
6696                    Op::AlgebraicMatch(idx) => {
6697                        let i = *idx as usize;
6698                        let line = self.line();
6699                        let v = if let Some(&(start, end)) = self
6700                            .algebraic_match_subject_bytecode_ranges
6701                            .get(i)
6702                            .and_then(|r| r.as_ref())
6703                        {
6704                            let subject_val = self.run_block_region(start, end, op_count)?;
6705                            let arms = &self.algebraic_match_entries[i].1;
6706                            vm_interp_result(
6707                                self.interp.eval_algebraic_match_with_subject_value(
6708                                    subject_val,
6709                                    arms,
6710                                    line,
6711                                ),
6712                                self.line(),
6713                            )?
6714                        } else {
6715                            let (subject, arms) = &self.algebraic_match_entries[i];
6716                            vm_interp_result(
6717                                self.interp.eval_algebraic_match(subject, arms, line),
6718                                self.line(),
6719                            )?
6720                        };
6721                        self.push(v);
6722                        Ok(())
6723                    }
6724                    Op::ParLines(idx) => {
6725                        let (path, callback, progress) = &self.par_lines_entries[*idx as usize];
6726                        let v = vm_interp_result(
6727                            self.interp.eval_par_lines_expr(
6728                                path,
6729                                callback,
6730                                progress.as_ref(),
6731                                self.line(),
6732                            ),
6733                            self.line(),
6734                        )?;
6735                        self.push(v);
6736                        Ok(())
6737                    }
6738                    Op::ParWalk(idx) => {
6739                        let (path, callback, progress) = &self.par_walk_entries[*idx as usize];
6740                        let v = vm_interp_result(
6741                            self.interp.eval_par_walk_expr(
6742                                path,
6743                                callback,
6744                                progress.as_ref(),
6745                                self.line(),
6746                            ),
6747                            self.line(),
6748                        )?;
6749                        self.push(v);
6750                        Ok(())
6751                    }
6752                    Op::Pwatch(idx) => {
6753                        let (path, callback) = &self.pwatch_entries[*idx as usize];
6754                        let v = vm_interp_result(
6755                            self.interp.eval_pwatch_expr(path, callback, self.line()),
6756                            self.line(),
6757                        )?;
6758                        self.push(v);
6759                        Ok(())
6760                    }
6761
6762                    // ── Parallel operations (rayon) ──
6763                    Op::PMapWithBlock(block_idx) => {
6764                        let list = self.pop().to_list();
6765                        let progress_flag = self.pop().is_true();
6766                        let idx = *block_idx as usize;
6767                        let subs = self.interp.subs.clone();
6768                        let (scope_capture, atomic_arrays, atomic_hashes) =
6769                            self.interp.scope.capture_with_atomics();
6770                        let pmap_progress = PmapProgress::new(progress_flag, list.len());
6771                        let n_workers = rayon::current_num_threads();
6772                        let pool: Vec<Mutex<Interpreter>> = (0..n_workers)
6773                            .map(|_| {
6774                                let mut interp = Interpreter::new();
6775                                interp.subs = subs.clone();
6776                                interp.scope.restore_capture(&scope_capture);
6777                                interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
6778                                interp.enable_parallel_guard();
6779                                Mutex::new(interp)
6780                            })
6781                            .collect();
6782                        if let Some(&(start, end)) =
6783                            self.block_bytecode_ranges.get(idx).and_then(|r| r.as_ref())
6784                        {
6785                            let shared = Arc::new(ParallelBlockVmShared::from_vm(self));
6786                            let results: Vec<PerlValue> = list
6787                                .into_par_iter()
6788                                .map(|item| {
6789                                    let tid =
6790                                        rayon::current_thread_index().unwrap_or(0) % pool.len();
6791                                    let mut local_interp = pool[tid].lock();
6792                                    local_interp.scope.set_topic(item);
6793                                    let mut vm = shared.worker_vm(&mut local_interp);
6794                                    let mut op_count = 0u64;
6795                                    let val = match vm.run_block_region(start, end, &mut op_count) {
6796                                        Ok(v) => v,
6797                                        Err(_) => PerlValue::UNDEF,
6798                                    };
6799                                    pmap_progress.tick();
6800                                    val
6801                                })
6802                                .collect();
6803                            pmap_progress.finish();
6804                            self.push(PerlValue::array(results));
6805                            Ok(())
6806                        } else {
6807                            let block = self.blocks[idx].clone();
6808                            let results: Vec<PerlValue> = list
6809                                .into_par_iter()
6810                                .map(|item| {
6811                                    let tid =
6812                                        rayon::current_thread_index().unwrap_or(0) % pool.len();
6813                                    let mut local_interp = pool[tid].lock();
6814                                    local_interp.scope.set_topic(item);
6815                                    local_interp.scope_push_hook();
6816                                    let val = match local_interp.exec_block_no_scope(&block) {
6817                                        Ok(val) => val,
6818                                        Err(_) => PerlValue::UNDEF,
6819                                    };
6820                                    local_interp.scope_pop_hook();
6821                                    pmap_progress.tick();
6822                                    val
6823                                })
6824                                .collect();
6825                            pmap_progress.finish();
6826                            self.push(PerlValue::array(results));
6827                            Ok(())
6828                        }
6829                    }
6830                    Op::PFlatMapWithBlock(block_idx) => {
6831                        let list = self.pop().to_list();
6832                        let progress_flag = self.pop().is_true();
6833                        let idx = *block_idx as usize;
6834                        let subs = self.interp.subs.clone();
6835                        let (scope_capture, atomic_arrays, atomic_hashes) =
6836                            self.interp.scope.capture_with_atomics();
6837                        let pmap_progress = PmapProgress::new(progress_flag, list.len());
6838                        let n_workers = rayon::current_num_threads();
6839                        let pool: Vec<Mutex<Interpreter>> = (0..n_workers)
6840                            .map(|_| {
6841                                let mut interp = Interpreter::new();
6842                                interp.subs = subs.clone();
6843                                interp.scope.restore_capture(&scope_capture);
6844                                interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
6845                                interp.enable_parallel_guard();
6846                                Mutex::new(interp)
6847                            })
6848                            .collect();
6849                        if let Some(&(start, end)) =
6850                            self.block_bytecode_ranges.get(idx).and_then(|r| r.as_ref())
6851                        {
6852                            let shared = Arc::new(ParallelBlockVmShared::from_vm(self));
6853                            let mut indexed: Vec<(usize, Vec<PerlValue>)> = list
6854                                .into_par_iter()
6855                                .enumerate()
6856                                .map(|(i, item)| {
6857                                    let tid =
6858                                        rayon::current_thread_index().unwrap_or(0) % pool.len();
6859                                    let mut local_interp = pool[tid].lock();
6860                                    local_interp.scope.set_topic(item);
6861                                    let mut vm = shared.worker_vm(&mut local_interp);
6862                                    let mut op_count = 0u64;
6863                                    let val = match vm.run_block_region(start, end, &mut op_count) {
6864                                        Ok(v) => v,
6865                                        Err(_) => PerlValue::UNDEF,
6866                                    };
6867                                    let out = val.map_flatten_outputs(true);
6868                                    pmap_progress.tick();
6869                                    (i, out)
6870                                })
6871                                .collect();
6872                            pmap_progress.finish();
6873                            indexed.sort_by_key(|(i, _)| *i);
6874                            let results: Vec<PerlValue> =
6875                                indexed.into_iter().flat_map(|(_, v)| v).collect();
6876                            self.push(PerlValue::array(results));
6877                            Ok(())
6878                        } else {
6879                            let block = self.blocks[idx].clone();
6880                            let mut indexed: Vec<(usize, Vec<PerlValue>)> = list
6881                                .into_par_iter()
6882                                .enumerate()
6883                                .map(|(i, item)| {
6884                                    let tid =
6885                                        rayon::current_thread_index().unwrap_or(0) % pool.len();
6886                                    let mut local_interp = pool[tid].lock();
6887                                    local_interp.scope.set_topic(item);
6888                                    local_interp.scope_push_hook();
6889                                    let val = match local_interp.exec_block_no_scope(&block) {
6890                                        Ok(val) => val,
6891                                        Err(_) => PerlValue::UNDEF,
6892                                    };
6893                                    local_interp.scope_pop_hook();
6894                                    let out = val.map_flatten_outputs(true);
6895                                    pmap_progress.tick();
6896                                    (i, out)
6897                                })
6898                                .collect();
6899                            pmap_progress.finish();
6900                            indexed.sort_by_key(|(i, _)| *i);
6901                            let results: Vec<PerlValue> =
6902                                indexed.into_iter().flat_map(|(_, v)| v).collect();
6903                            self.push(PerlValue::array(results));
6904                            Ok(())
6905                        }
6906                    }
6907                    Op::PMapRemote { block_idx, flat } => {
6908                        let cluster = self.pop();
6909                        let list_pv = self.pop();
6910                        let progress_flag = self.pop().is_true();
6911                        let idx = *block_idx as usize;
6912                        let block = self.blocks[idx].clone();
6913                        let flat_outputs = *flat != 0;
6914                        let v = vm_interp_result(
6915                            self.interp.eval_pmap_remote(
6916                                cluster,
6917                                list_pv,
6918                                progress_flag,
6919                                &block,
6920                                flat_outputs,
6921                                self.line(),
6922                            ),
6923                            self.line(),
6924                        )?;
6925                        self.push(v);
6926                        Ok(())
6927                    }
6928                    Op::Puniq => {
6929                        let list = self.pop().to_list();
6930                        let progress_flag = self.pop().is_true();
6931                        let n_threads = self.interp.parallel_thread_count();
6932                        let pmap_progress = PmapProgress::new(progress_flag, list.len());
6933                        let out = crate::par_list::puniq_run(list, n_threads, &pmap_progress);
6934                        pmap_progress.finish();
6935                        self.push(PerlValue::array(out));
6936                        Ok(())
6937                    }
6938                    Op::PFirstWithBlock(block_idx) => {
6939                        let list = self.pop().to_list();
6940                        let progress_flag = self.pop().is_true();
6941                        let idx = *block_idx as usize;
6942                        let pmap_progress = PmapProgress::new(progress_flag, list.len());
6943                        let subs = self.interp.subs.clone();
6944                        let (scope_capture, atomic_arrays, atomic_hashes) =
6945                            self.interp.scope.capture_with_atomics();
6946                        let out = if let Some(&(start, end)) =
6947                            self.block_bytecode_ranges.get(idx).and_then(|r| r.as_ref())
6948                        {
6949                            let shared = Arc::new(ParallelBlockVmShared::from_vm(self));
6950                            crate::par_list::pfirst_run(list, &pmap_progress, |item| {
6951                                let mut local_interp = Interpreter::new();
6952                                local_interp.subs = subs.clone();
6953                                local_interp.scope.restore_capture(&scope_capture);
6954                                local_interp
6955                                    .scope
6956                                    .restore_atomics(&atomic_arrays, &atomic_hashes);
6957                                local_interp.enable_parallel_guard();
6958                                local_interp.scope.set_topic(item);
6959                                let mut vm = shared.worker_vm(&mut local_interp);
6960                                let mut op_count = 0u64;
6961                                match vm.run_block_region(start, end, &mut op_count) {
6962                                    Ok(v) => v.is_true(),
6963                                    Err(_) => false,
6964                                }
6965                            })
6966                        } else {
6967                            let block = self.blocks[idx].clone();
6968                            crate::par_list::pfirst_run(list, &pmap_progress, |item| {
6969                                let mut local_interp = Interpreter::new();
6970                                local_interp.subs = subs.clone();
6971                                local_interp.scope.restore_capture(&scope_capture);
6972                                local_interp
6973                                    .scope
6974                                    .restore_atomics(&atomic_arrays, &atomic_hashes);
6975                                local_interp.enable_parallel_guard();
6976                                local_interp.scope.set_topic(item);
6977                                local_interp.scope_push_hook();
6978                                let ok = match local_interp.exec_block_no_scope(&block) {
6979                                    Ok(v) => v.is_true(),
6980                                    Err(_) => false,
6981                                };
6982                                local_interp.scope_pop_hook();
6983                                ok
6984                            })
6985                        };
6986                        pmap_progress.finish();
6987                        self.push(out.unwrap_or(PerlValue::UNDEF));
6988                        Ok(())
6989                    }
6990                    Op::PAnyWithBlock(block_idx) => {
6991                        let list = self.pop().to_list();
6992                        let progress_flag = self.pop().is_true();
6993                        let idx = *block_idx as usize;
6994                        let pmap_progress = PmapProgress::new(progress_flag, list.len());
6995                        let subs = self.interp.subs.clone();
6996                        let (scope_capture, atomic_arrays, atomic_hashes) =
6997                            self.interp.scope.capture_with_atomics();
6998                        let b = if let Some(&(start, end)) =
6999                            self.block_bytecode_ranges.get(idx).and_then(|r| r.as_ref())
7000                        {
7001                            let shared = Arc::new(ParallelBlockVmShared::from_vm(self));
7002                            crate::par_list::pany_run(list, &pmap_progress, |item| {
7003                                let mut local_interp = Interpreter::new();
7004                                local_interp.subs = subs.clone();
7005                                local_interp.scope.restore_capture(&scope_capture);
7006                                local_interp
7007                                    .scope
7008                                    .restore_atomics(&atomic_arrays, &atomic_hashes);
7009                                local_interp.enable_parallel_guard();
7010                                local_interp.scope.set_topic(item);
7011                                let mut vm = shared.worker_vm(&mut local_interp);
7012                                let mut op_count = 0u64;
7013                                match vm.run_block_region(start, end, &mut op_count) {
7014                                    Ok(v) => v.is_true(),
7015                                    Err(_) => false,
7016                                }
7017                            })
7018                        } else {
7019                            let block = self.blocks[idx].clone();
7020                            crate::par_list::pany_run(list, &pmap_progress, |item| {
7021                                let mut local_interp = Interpreter::new();
7022                                local_interp.subs = subs.clone();
7023                                local_interp.scope.restore_capture(&scope_capture);
7024                                local_interp
7025                                    .scope
7026                                    .restore_atomics(&atomic_arrays, &atomic_hashes);
7027                                local_interp.enable_parallel_guard();
7028                                local_interp.scope.set_topic(item);
7029                                local_interp.scope_push_hook();
7030                                let ok = match local_interp.exec_block_no_scope(&block) {
7031                                    Ok(v) => v.is_true(),
7032                                    Err(_) => false,
7033                                };
7034                                local_interp.scope_pop_hook();
7035                                ok
7036                            })
7037                        };
7038                        pmap_progress.finish();
7039                        self.push(PerlValue::integer(if b { 1 } else { 0 }));
7040                        Ok(())
7041                    }
7042                    Op::PMapChunkedWithBlock(block_idx) => {
7043                        let list = self.pop().to_list();
7044                        let chunk_n = self.pop().to_int().max(1) as usize;
7045                        let progress_flag = self.pop().is_true();
7046                        let idx = *block_idx as usize;
7047                        let subs = self.interp.subs.clone();
7048                        let (scope_capture, atomic_arrays, atomic_hashes) =
7049                            self.interp.scope.capture_with_atomics();
7050                        let indexed_chunks: Vec<(usize, Vec<PerlValue>)> = list
7051                            .chunks(chunk_n)
7052                            .enumerate()
7053                            .map(|(i, c)| (i, c.to_vec()))
7054                            .collect();
7055                        let n_chunks = indexed_chunks.len();
7056                        let pmap_progress = PmapProgress::new(progress_flag, n_chunks);
7057                        if let Some(&(start, end)) =
7058                            self.block_bytecode_ranges.get(idx).and_then(|r| r.as_ref())
7059                        {
7060                            let shared = Arc::new(ParallelBlockVmShared::from_vm(self));
7061                            let mut chunk_results: Vec<(usize, Vec<PerlValue>)> = indexed_chunks
7062                                .into_par_iter()
7063                                .map(|(chunk_idx, chunk)| {
7064                                    let mut local_interp = Interpreter::new();
7065                                    local_interp.subs = subs.clone();
7066                                    local_interp.scope.restore_capture(&scope_capture);
7067                                    local_interp
7068                                        .scope
7069                                        .restore_atomics(&atomic_arrays, &atomic_hashes);
7070                                    local_interp.enable_parallel_guard();
7071                                    let mut out = Vec::with_capacity(chunk.len());
7072                                    for item in chunk {
7073                                        local_interp.scope.set_topic(item);
7074                                        let mut vm = shared.worker_vm(&mut local_interp);
7075                                        let mut op_count = 0u64;
7076                                        let val =
7077                                            match vm.run_block_region(start, end, &mut op_count) {
7078                                                Ok(v) => v,
7079                                                Err(_) => PerlValue::UNDEF,
7080                                            };
7081                                        out.push(val);
7082                                    }
7083                                    pmap_progress.tick();
7084                                    (chunk_idx, out)
7085                                })
7086                                .collect();
7087                            pmap_progress.finish();
7088                            chunk_results.sort_by_key(|(i, _)| *i);
7089                            let results: Vec<PerlValue> =
7090                                chunk_results.into_iter().flat_map(|(_, v)| v).collect();
7091                            self.push(PerlValue::array(results));
7092                            Ok(())
7093                        } else {
7094                            let block = self.blocks[idx].clone();
7095                            let mut chunk_results: Vec<(usize, Vec<PerlValue>)> = indexed_chunks
7096                                .into_par_iter()
7097                                .map(|(chunk_idx, chunk)| {
7098                                    let mut local_interp = Interpreter::new();
7099                                    local_interp.subs = subs.clone();
7100                                    local_interp.scope.restore_capture(&scope_capture);
7101                                    local_interp
7102                                        .scope
7103                                        .restore_atomics(&atomic_arrays, &atomic_hashes);
7104                                    local_interp.enable_parallel_guard();
7105                                    let mut out = Vec::with_capacity(chunk.len());
7106                                    for item in chunk {
7107                                        local_interp.scope.set_topic(item);
7108                                        local_interp.scope_push_hook();
7109                                        let val = match local_interp.exec_block_no_scope(&block) {
7110                                            Ok(val) => val,
7111                                            Err(_) => PerlValue::UNDEF,
7112                                        };
7113                                        local_interp.scope_pop_hook();
7114                                        out.push(val);
7115                                    }
7116                                    pmap_progress.tick();
7117                                    (chunk_idx, out)
7118                                })
7119                                .collect();
7120                            pmap_progress.finish();
7121                            chunk_results.sort_by_key(|(i, _)| *i);
7122                            let results: Vec<PerlValue> =
7123                                chunk_results.into_iter().flat_map(|(_, v)| v).collect();
7124                            self.push(PerlValue::array(results));
7125                            Ok(())
7126                        }
7127                    }
7128                    Op::ReduceWithBlock(block_idx) => {
7129                        let list = self.pop().to_list();
7130                        let idx = *block_idx as usize;
7131                        let subs = self.interp.subs.clone();
7132                        let scope_capture = self.interp.scope.capture();
7133                        if list.is_empty() {
7134                            self.push(PerlValue::UNDEF);
7135                            return Ok(());
7136                        }
7137                        if list.len() == 1 {
7138                            self.push(list.into_iter().next().unwrap());
7139                            return Ok(());
7140                        }
7141                        let mut items = list;
7142                        let mut acc = items.remove(0);
7143                        let rest = items;
7144                        if let Some(&(start, end)) =
7145                            self.block_bytecode_ranges.get(idx).and_then(|r| r.as_ref())
7146                        {
7147                            let shared = Arc::new(ParallelBlockVmShared::from_vm(self));
7148                            for b in rest {
7149                                let mut local_interp = Interpreter::new();
7150                                local_interp.subs = subs.clone();
7151                                local_interp.scope.restore_capture(&scope_capture);
7152                                let _ = local_interp.scope.set_scalar("a", acc.clone());
7153                                let _ = local_interp.scope.set_scalar("b", b.clone());
7154                                let _ = local_interp.scope.set_scalar("_0", acc);
7155                                let _ = local_interp.scope.set_scalar("_1", b);
7156                                let mut vm = shared.worker_vm(&mut local_interp);
7157                                let mut op_count = 0u64;
7158                                acc = match vm.run_block_region(start, end, &mut op_count) {
7159                                    Ok(v) => v,
7160                                    Err(_) => PerlValue::UNDEF,
7161                                };
7162                            }
7163                        } else {
7164                            let block = self.blocks[idx].clone();
7165                            for b in rest {
7166                                let mut local_interp = Interpreter::new();
7167                                local_interp.subs = subs.clone();
7168                                local_interp.scope.restore_capture(&scope_capture);
7169                                let _ = local_interp.scope.set_scalar("a", acc.clone());
7170                                let _ = local_interp.scope.set_scalar("b", b.clone());
7171                                let _ = local_interp.scope.set_scalar("_0", acc);
7172                                let _ = local_interp.scope.set_scalar("_1", b);
7173                                acc = match local_interp.exec_block(&block) {
7174                                    Ok(val) => val,
7175                                    Err(_) => PerlValue::UNDEF,
7176                                };
7177                            }
7178                        }
7179                        self.push(acc);
7180                        Ok(())
7181                    }
7182                    Op::PReduceWithBlock(block_idx) => {
7183                        let list = self.pop().to_list();
7184                        let progress_flag = self.pop().is_true();
7185                        let idx = *block_idx as usize;
7186                        let subs = self.interp.subs.clone();
7187                        let scope_capture = self.interp.scope.capture();
7188                        if list.is_empty() {
7189                            self.push(PerlValue::UNDEF);
7190                            return Ok(());
7191                        }
7192                        if list.len() == 1 {
7193                            self.push(list.into_iter().next().unwrap());
7194                            return Ok(());
7195                        }
7196                        let pmap_progress = PmapProgress::new(progress_flag, list.len());
7197                        if let Some(&(start, end)) =
7198                            self.block_bytecode_ranges.get(idx).and_then(|r| r.as_ref())
7199                        {
7200                            let shared = Arc::new(ParallelBlockVmShared::from_vm(self));
7201                            let result = list
7202                                .into_par_iter()
7203                                .map(|x| {
7204                                    pmap_progress.tick();
7205                                    x
7206                                })
7207                                .reduce_with(|a, b| {
7208                                    let mut local_interp = Interpreter::new();
7209                                    local_interp.subs = subs.clone();
7210                                    local_interp.scope.restore_capture(&scope_capture);
7211                                    let _ = local_interp.scope.set_scalar("a", a.clone());
7212                                    let _ = local_interp.scope.set_scalar("b", b.clone());
7213                                    let _ = local_interp.scope.set_scalar("_0", a);
7214                                    let _ = local_interp.scope.set_scalar("_1", b);
7215                                    let mut vm = shared.worker_vm(&mut local_interp);
7216                                    let mut op_count = 0u64;
7217                                    match vm.run_block_region(start, end, &mut op_count) {
7218                                        Ok(val) => val,
7219                                        Err(_) => PerlValue::UNDEF,
7220                                    }
7221                                });
7222                            pmap_progress.finish();
7223                            self.push(result.unwrap_or(PerlValue::UNDEF));
7224                            Ok(())
7225                        } else {
7226                            let block = self.blocks[idx].clone();
7227                            let result = list
7228                                .into_par_iter()
7229                                .map(|x| {
7230                                    pmap_progress.tick();
7231                                    x
7232                                })
7233                                .reduce_with(|a, b| {
7234                                    let mut local_interp = Interpreter::new();
7235                                    local_interp.subs = subs.clone();
7236                                    local_interp.scope.restore_capture(&scope_capture);
7237                                    let _ = local_interp.scope.set_scalar("a", a.clone());
7238                                    let _ = local_interp.scope.set_scalar("b", b.clone());
7239                                    let _ = local_interp.scope.set_scalar("_0", a);
7240                                    let _ = local_interp.scope.set_scalar("_1", b);
7241                                    match local_interp.exec_block(&block) {
7242                                        Ok(val) => val,
7243                                        Err(_) => PerlValue::UNDEF,
7244                                    }
7245                                });
7246                            pmap_progress.finish();
7247                            self.push(result.unwrap_or(PerlValue::UNDEF));
7248                            Ok(())
7249                        }
7250                    }
7251                    Op::PReduceInitWithBlock(block_idx) => {
7252                        let init_val = self.pop();
7253                        let list = self.pop().to_list();
7254                        let progress_flag = self.pop().is_true();
7255                        let idx = *block_idx as usize;
7256                        let subs = self.interp.subs.clone();
7257                        let scope_capture = self.interp.scope.capture();
7258                        let cap: &[(String, PerlValue)] = scope_capture.as_slice();
7259                        let block = self.blocks[idx].clone();
7260                        if list.is_empty() {
7261                            self.push(init_val);
7262                            return Ok(());
7263                        }
7264                        if list.len() == 1 {
7265                            let v = fold_preduce_init_step(
7266                                &subs,
7267                                cap,
7268                                &block,
7269                                preduce_init_fold_identity(&init_val),
7270                                list.into_iter().next().unwrap(),
7271                            );
7272                            self.push(v);
7273                            return Ok(());
7274                        }
7275                        let pmap_progress = PmapProgress::new(progress_flag, list.len());
7276                        let result = list
7277                            .into_par_iter()
7278                            .fold(
7279                                || preduce_init_fold_identity(&init_val),
7280                                |acc, item| {
7281                                    pmap_progress.tick();
7282                                    fold_preduce_init_step(&subs, cap, &block, acc, item)
7283                                },
7284                            )
7285                            .reduce(
7286                                || preduce_init_fold_identity(&init_val),
7287                                |a, b| merge_preduce_init_partials(a, b, &block, &subs, cap),
7288                            );
7289                        pmap_progress.finish();
7290                        self.push(result);
7291                        Ok(())
7292                    }
7293                    Op::PMapReduceWithBlocks(map_idx, reduce_idx) => {
7294                        let list = self.pop().to_list();
7295                        let progress_flag = self.pop().is_true();
7296                        let map_i = *map_idx as usize;
7297                        let reduce_i = *reduce_idx as usize;
7298                        let subs = self.interp.subs.clone();
7299                        let scope_capture = self.interp.scope.capture();
7300                        if list.is_empty() {
7301                            self.push(PerlValue::UNDEF);
7302                            return Ok(());
7303                        }
7304                        if list.len() == 1 {
7305                            let mut local_interp = Interpreter::new();
7306                            local_interp.subs = subs.clone();
7307                            local_interp.scope.restore_capture(&scope_capture);
7308                            local_interp
7309                                .scope
7310                                .set_topic(list.into_iter().next().unwrap());
7311                            let map_block = self.blocks[map_i].clone();
7312                            let v = match local_interp.exec_block_no_scope(&map_block) {
7313                                Ok(v) => v,
7314                                Err(_) => PerlValue::UNDEF,
7315                            };
7316                            self.push(v);
7317                            return Ok(());
7318                        }
7319                        let pmap_progress = PmapProgress::new(progress_flag, list.len());
7320                        let map_range = self
7321                            .block_bytecode_ranges
7322                            .get(map_i)
7323                            .and_then(|r| r.as_ref())
7324                            .copied();
7325                        let reduce_range = self
7326                            .block_bytecode_ranges
7327                            .get(reduce_i)
7328                            .and_then(|r| r.as_ref())
7329                            .copied();
7330                        if let (Some((map_start, map_end)), Some((reduce_start, reduce_end))) =
7331                            (map_range, reduce_range)
7332                        {
7333                            let shared = Arc::new(ParallelBlockVmShared::from_vm(self));
7334                            let result = list
7335                                .into_par_iter()
7336                                .map(|item| {
7337                                    let mut local_interp = Interpreter::new();
7338                                    local_interp.subs = subs.clone();
7339                                    local_interp.scope.restore_capture(&scope_capture);
7340                                    local_interp.scope.set_topic(item);
7341                                    let mut vm = shared.worker_vm(&mut local_interp);
7342                                    let mut op_count = 0u64;
7343                                    let val = match vm.run_block_region(
7344                                        map_start,
7345                                        map_end,
7346                                        &mut op_count,
7347                                    ) {
7348                                        Ok(val) => val,
7349                                        Err(_) => PerlValue::UNDEF,
7350                                    };
7351                                    pmap_progress.tick();
7352                                    val
7353                                })
7354                                .reduce_with(|a, b| {
7355                                    let mut local_interp = Interpreter::new();
7356                                    local_interp.subs = subs.clone();
7357                                    local_interp.scope.restore_capture(&scope_capture);
7358                                    let _ = local_interp.scope.set_scalar("a", a.clone());
7359                                    let _ = local_interp.scope.set_scalar("b", b.clone());
7360                                    let _ = local_interp.scope.set_scalar("_0", a);
7361                                    let _ = local_interp.scope.set_scalar("_1", b);
7362                                    let mut vm = shared.worker_vm(&mut local_interp);
7363                                    let mut op_count = 0u64;
7364                                    match vm.run_block_region(
7365                                        reduce_start,
7366                                        reduce_end,
7367                                        &mut op_count,
7368                                    ) {
7369                                        Ok(val) => val,
7370                                        Err(_) => PerlValue::UNDEF,
7371                                    }
7372                                });
7373                            pmap_progress.finish();
7374                            self.push(result.unwrap_or(PerlValue::UNDEF));
7375                            Ok(())
7376                        } else {
7377                            let map_block = self.blocks[map_i].clone();
7378                            let reduce_block = self.blocks[reduce_i].clone();
7379                            let result = list
7380                                .into_par_iter()
7381                                .map(|item| {
7382                                    let mut local_interp = Interpreter::new();
7383                                    local_interp.subs = subs.clone();
7384                                    local_interp.scope.restore_capture(&scope_capture);
7385                                    local_interp.scope.set_topic(item);
7386                                    let val = match local_interp.exec_block_no_scope(&map_block) {
7387                                        Ok(val) => val,
7388                                        Err(_) => PerlValue::UNDEF,
7389                                    };
7390                                    pmap_progress.tick();
7391                                    val
7392                                })
7393                                .reduce_with(|a, b| {
7394                                    let mut local_interp = Interpreter::new();
7395                                    local_interp.subs = subs.clone();
7396                                    local_interp.scope.restore_capture(&scope_capture);
7397                                    let _ = local_interp.scope.set_scalar("a", a.clone());
7398                                    let _ = local_interp.scope.set_scalar("b", b.clone());
7399                                    let _ = local_interp.scope.set_scalar("_0", a);
7400                                    let _ = local_interp.scope.set_scalar("_1", b);
7401                                    match local_interp.exec_block_no_scope(&reduce_block) {
7402                                        Ok(val) => val,
7403                                        Err(_) => PerlValue::UNDEF,
7404                                    }
7405                                });
7406                            pmap_progress.finish();
7407                            self.push(result.unwrap_or(PerlValue::UNDEF));
7408                            Ok(())
7409                        }
7410                    }
7411                    Op::PcacheWithBlock(block_idx) => {
7412                        let list = self.pop().to_list();
7413                        let progress_flag = self.pop().is_true();
7414                        let idx = *block_idx as usize;
7415                        let subs = self.interp.subs.clone();
7416                        let scope_capture = self.interp.scope.capture();
7417                        let block = self.blocks[idx].clone();
7418                        let cache = &*crate::pcache::GLOBAL_PCACHE;
7419                        let pmap_progress = PmapProgress::new(progress_flag, list.len());
7420                        if let Some(&(start, end)) =
7421                            self.block_bytecode_ranges.get(idx).and_then(|r| r.as_ref())
7422                        {
7423                            let shared = Arc::new(ParallelBlockVmShared::from_vm(self));
7424                            let results: Vec<PerlValue> = list
7425                                .into_par_iter()
7426                                .map(|item| {
7427                                    let k = crate::pcache::cache_key(&item);
7428                                    if let Some(v) = cache.get(&k) {
7429                                        pmap_progress.tick();
7430                                        return v.clone();
7431                                    }
7432                                    let mut local_interp = Interpreter::new();
7433                                    local_interp.subs = subs.clone();
7434                                    local_interp.scope.restore_capture(&scope_capture);
7435                                    local_interp.scope.set_topic(item.clone());
7436                                    let mut vm = shared.worker_vm(&mut local_interp);
7437                                    let mut op_count = 0u64;
7438                                    let val = match vm.run_block_region(start, end, &mut op_count) {
7439                                        Ok(v) => v,
7440                                        Err(_) => PerlValue::UNDEF,
7441                                    };
7442                                    cache.insert(k, val.clone());
7443                                    pmap_progress.tick();
7444                                    val
7445                                })
7446                                .collect();
7447                            pmap_progress.finish();
7448                            self.push(PerlValue::array(results));
7449                            Ok(())
7450                        } else {
7451                            let results: Vec<PerlValue> = list
7452                                .into_par_iter()
7453                                .map(|item| {
7454                                    let k = crate::pcache::cache_key(&item);
7455                                    if let Some(v) = cache.get(&k) {
7456                                        pmap_progress.tick();
7457                                        return v.clone();
7458                                    }
7459                                    let mut local_interp = Interpreter::new();
7460                                    local_interp.subs = subs.clone();
7461                                    local_interp.scope.restore_capture(&scope_capture);
7462                                    local_interp.scope.set_topic(item.clone());
7463                                    let val = match local_interp.exec_block_no_scope(&block) {
7464                                        Ok(v) => v,
7465                                        Err(_) => PerlValue::UNDEF,
7466                                    };
7467                                    cache.insert(k, val.clone());
7468                                    pmap_progress.tick();
7469                                    val
7470                                })
7471                                .collect();
7472                            pmap_progress.finish();
7473                            self.push(PerlValue::array(results));
7474                            Ok(())
7475                        }
7476                    }
7477                    Op::Pselect { n_rx, has_timeout } => {
7478                        let timeout = if *has_timeout {
7479                            let t = self.pop().to_number();
7480                            Some(std::time::Duration::from_secs_f64(t.max(0.0)))
7481                        } else {
7482                            None
7483                        };
7484                        let mut rx_vals = Vec::with_capacity(*n_rx as usize);
7485                        for _ in 0..*n_rx {
7486                            rx_vals.push(self.pop());
7487                        }
7488                        rx_vals.reverse();
7489                        let line = self.line();
7490                        let v = crate::pchannel::pselect_recv_with_optional_timeout(
7491                            &rx_vals, timeout, line,
7492                        )?;
7493                        self.push(v);
7494                        Ok(())
7495                    }
7496                    Op::PGrepWithBlock(block_idx) => {
7497                        let list = self.pop().to_list();
7498                        let progress_flag = self.pop().is_true();
7499                        let idx = *block_idx as usize;
7500                        let subs = self.interp.subs.clone();
7501                        let (scope_capture, atomic_arrays, atomic_hashes) =
7502                            self.interp.scope.capture_with_atomics();
7503                        let pmap_progress = PmapProgress::new(progress_flag, list.len());
7504                        let n_workers = rayon::current_num_threads();
7505                        let pool: Vec<Mutex<Interpreter>> = (0..n_workers)
7506                            .map(|_| {
7507                                let mut interp = Interpreter::new();
7508                                interp.subs = subs.clone();
7509                                interp.scope.restore_capture(&scope_capture);
7510                                interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
7511                                interp.enable_parallel_guard();
7512                                Mutex::new(interp)
7513                            })
7514                            .collect();
7515                        if let Some(&(start, end)) =
7516                            self.block_bytecode_ranges.get(idx).and_then(|r| r.as_ref())
7517                        {
7518                            let shared = Arc::new(ParallelBlockVmShared::from_vm(self));
7519                            let results: Vec<PerlValue> = list
7520                                .into_par_iter()
7521                                .filter_map(|item| {
7522                                    let tid =
7523                                        rayon::current_thread_index().unwrap_or(0) % pool.len();
7524                                    let mut local_interp = pool[tid].lock();
7525                                    local_interp.scope.set_topic(item.clone());
7526                                    let mut vm = shared.worker_vm(&mut local_interp);
7527                                    let mut op_count = 0u64;
7528                                    let keep = match vm.run_block_region(start, end, &mut op_count)
7529                                    {
7530                                        Ok(val) => val.is_true(),
7531                                        Err(_) => false,
7532                                    };
7533                                    pmap_progress.tick();
7534                                    if keep {
7535                                        Some(item)
7536                                    } else {
7537                                        None
7538                                    }
7539                                })
7540                                .collect();
7541                            pmap_progress.finish();
7542                            self.push(PerlValue::array(results));
7543                            Ok(())
7544                        } else {
7545                            let block = self.blocks[idx].clone();
7546                            let results: Vec<PerlValue> = list
7547                                .into_par_iter()
7548                                .filter_map(|item| {
7549                                    let tid =
7550                                        rayon::current_thread_index().unwrap_or(0) % pool.len();
7551                                    let mut local_interp = pool[tid].lock();
7552                                    local_interp.scope.set_topic(item.clone());
7553                                    local_interp.scope_push_hook();
7554                                    let keep = match local_interp.exec_block_no_scope(&block) {
7555                                        Ok(val) => val.is_true(),
7556                                        Err(_) => false,
7557                                    };
7558                                    local_interp.scope_pop_hook();
7559                                    pmap_progress.tick();
7560                                    if keep {
7561                                        Some(item)
7562                                    } else {
7563                                        None
7564                                    }
7565                                })
7566                                .collect();
7567                            pmap_progress.finish();
7568                            self.push(PerlValue::array(results));
7569                            Ok(())
7570                        }
7571                    }
7572                    Op::PMapsWithBlock(block_idx) => {
7573                        let val = self.pop();
7574                        let block = self.blocks[*block_idx as usize].clone();
7575                        let source = crate::map_stream::into_pull_iter(val);
7576                        let sub = self.interp.anon_coderef_from_block(&block);
7577                        let (capture, atomic_arrays, atomic_hashes) =
7578                            self.interp.scope.capture_with_atomics();
7579                        let out = PerlValue::iterator(Arc::new(
7580                            crate::map_stream::PMapStreamIterator::new(
7581                                source,
7582                                sub,
7583                                self.interp.subs.clone(),
7584                                capture,
7585                                atomic_arrays,
7586                                atomic_hashes,
7587                                false,
7588                            ),
7589                        ));
7590                        self.push(out);
7591                        Ok(())
7592                    }
7593                    Op::PFlatMapsWithBlock(block_idx) => {
7594                        let val = self.pop();
7595                        let block = self.blocks[*block_idx as usize].clone();
7596                        let source = crate::map_stream::into_pull_iter(val);
7597                        let sub = self.interp.anon_coderef_from_block(&block);
7598                        let (capture, atomic_arrays, atomic_hashes) =
7599                            self.interp.scope.capture_with_atomics();
7600                        let out = PerlValue::iterator(Arc::new(
7601                            crate::map_stream::PMapStreamIterator::new(
7602                                source,
7603                                sub,
7604                                self.interp.subs.clone(),
7605                                capture,
7606                                atomic_arrays,
7607                                atomic_hashes,
7608                                true,
7609                            ),
7610                        ));
7611                        self.push(out);
7612                        Ok(())
7613                    }
7614                    Op::PGrepsWithBlock(block_idx) => {
7615                        let val = self.pop();
7616                        let block = self.blocks[*block_idx as usize].clone();
7617                        let source = crate::map_stream::into_pull_iter(val);
7618                        let sub = self.interp.anon_coderef_from_block(&block);
7619                        let (capture, atomic_arrays, atomic_hashes) =
7620                            self.interp.scope.capture_with_atomics();
7621                        let out = PerlValue::iterator(Arc::new(
7622                            crate::map_stream::PGrepStreamIterator::new(
7623                                source,
7624                                sub,
7625                                self.interp.subs.clone(),
7626                                capture,
7627                                atomic_arrays,
7628                                atomic_hashes,
7629                            ),
7630                        ));
7631                        self.push(out);
7632                        Ok(())
7633                    }
7634                    Op::PForWithBlock(block_idx) => {
7635                        let line = self.line();
7636                        let list = self.pop().to_list();
7637                        let progress_flag = self.pop().is_true();
7638                        let pmap_progress = PmapProgress::new(progress_flag, list.len());
7639                        let idx = *block_idx as usize;
7640                        let subs = self.interp.subs.clone();
7641                        let (scope_capture, atomic_arrays, atomic_hashes) =
7642                            self.interp.scope.capture_with_atomics();
7643                        let first_err: Arc<Mutex<Option<PerlError>>> = Arc::new(Mutex::new(None));
7644                        let n_workers = rayon::current_num_threads();
7645                        let pool: Vec<Mutex<Interpreter>> = (0..n_workers)
7646                            .map(|_| {
7647                                let mut interp = Interpreter::new();
7648                                interp.subs = subs.clone();
7649                                interp.scope.restore_capture(&scope_capture);
7650                                interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
7651                                interp.enable_parallel_guard();
7652                                Mutex::new(interp)
7653                            })
7654                            .collect();
7655                        if let Some(&(start, end)) =
7656                            self.block_bytecode_ranges.get(idx).and_then(|r| r.as_ref())
7657                        {
7658                            let shared = Arc::new(ParallelBlockVmShared::from_vm(self));
7659                            list.into_par_iter().for_each(|item| {
7660                                if first_err.lock().is_some() {
7661                                    return;
7662                                }
7663                                let tid = rayon::current_thread_index().unwrap_or(0) % pool.len();
7664                                let mut local_interp = pool[tid].lock();
7665                                local_interp.scope.set_topic(item);
7666                                let mut vm = shared.worker_vm(&mut local_interp);
7667                                let mut op_count = 0u64;
7668                                match vm.run_block_region(start, end, &mut op_count) {
7669                                    Ok(_) => {}
7670                                    Err(e) => {
7671                                        let mut g = first_err.lock();
7672                                        if g.is_none() {
7673                                            *g = Some(e);
7674                                        }
7675                                    }
7676                                }
7677                                pmap_progress.tick();
7678                            });
7679                        } else {
7680                            let block = self.blocks[idx].clone();
7681                            list.into_par_iter().for_each(|item| {
7682                                if first_err.lock().is_some() {
7683                                    return;
7684                                }
7685                                let tid = rayon::current_thread_index().unwrap_or(0) % pool.len();
7686                                let mut local_interp = pool[tid].lock();
7687                                local_interp.scope.set_topic(item);
7688                                local_interp.scope_push_hook();
7689                                match local_interp.exec_block_no_scope(&block) {
7690                                    Ok(_) => {}
7691                                    Err(e) => {
7692                                        let stryke = match e {
7693                                            FlowOrError::Error(stryke) => stryke,
7694                                            FlowOrError::Flow(_) => PerlError::runtime(
7695                                                "return/last/next/redo not supported inside pfor block",
7696                                                line,
7697                                            ),
7698                                        };
7699                                        let mut g = first_err.lock();
7700                                        if g.is_none() {
7701                                            *g = Some(stryke);
7702                                        }
7703                                    }
7704                                }
7705                                local_interp.scope_pop_hook();
7706                                pmap_progress.tick();
7707                            });
7708                        }
7709                        pmap_progress.finish();
7710                        if let Some(e) = first_err.lock().take() {
7711                            return Err(e);
7712                        }
7713                        self.push(PerlValue::UNDEF);
7714                        Ok(())
7715                    }
7716                    Op::PSortWithBlock(block_idx) => {
7717                        let mut items = self.pop().to_list();
7718                        let progress_flag = self.pop().is_true();
7719                        let pmap_progress = PmapProgress::new(progress_flag, 2);
7720                        pmap_progress.tick();
7721                        let idx = *block_idx as usize;
7722                        let subs = self.interp.subs.clone();
7723                        let (scope_capture, atomic_arrays, atomic_hashes) =
7724                            self.interp.scope.capture_with_atomics();
7725                        if let Some(&(start, end)) =
7726                            self.block_bytecode_ranges.get(idx).and_then(|r| r.as_ref())
7727                        {
7728                            let shared = Arc::new(ParallelBlockVmShared::from_vm(self));
7729                            items.par_sort_by(|a, b| {
7730                                let mut local_interp = Interpreter::new();
7731                                local_interp.subs = subs.clone();
7732                                local_interp.scope.restore_capture(&scope_capture);
7733                                local_interp
7734                                    .scope
7735                                    .restore_atomics(&atomic_arrays, &atomic_hashes);
7736                                local_interp.enable_parallel_guard();
7737                                let _ = local_interp.scope.set_scalar("a", a.clone());
7738                                let _ = local_interp.scope.set_scalar("b", b.clone());
7739                                let _ = local_interp.scope.set_scalar("_0", a.clone());
7740                                let _ = local_interp.scope.set_scalar("_1", b.clone());
7741                                let mut vm = shared.worker_vm(&mut local_interp);
7742                                let mut op_count = 0u64;
7743                                match vm.run_block_region(start, end, &mut op_count) {
7744                                    Ok(v) => {
7745                                        let n = v.to_int();
7746                                        if n < 0 {
7747                                            std::cmp::Ordering::Less
7748                                        } else if n > 0 {
7749                                            std::cmp::Ordering::Greater
7750                                        } else {
7751                                            std::cmp::Ordering::Equal
7752                                        }
7753                                    }
7754                                    Err(_) => std::cmp::Ordering::Equal,
7755                                }
7756                            });
7757                        } else {
7758                            let block = self.blocks[idx].clone();
7759                            items.par_sort_by(|a, b| {
7760                                let mut local_interp = Interpreter::new();
7761                                local_interp.subs = subs.clone();
7762                                local_interp.scope.restore_capture(&scope_capture);
7763                                local_interp
7764                                    .scope
7765                                    .restore_atomics(&atomic_arrays, &atomic_hashes);
7766                                local_interp.enable_parallel_guard();
7767                                let _ = local_interp.scope.set_scalar("a", a.clone());
7768                                let _ = local_interp.scope.set_scalar("b", b.clone());
7769                                let _ = local_interp.scope.set_scalar("_0", a.clone());
7770                                let _ = local_interp.scope.set_scalar("_1", b.clone());
7771                                local_interp.scope_push_hook();
7772                                let ord = match local_interp.exec_block_no_scope(&block) {
7773                                    Ok(v) => {
7774                                        let n = v.to_int();
7775                                        if n < 0 {
7776                                            std::cmp::Ordering::Less
7777                                        } else if n > 0 {
7778                                            std::cmp::Ordering::Greater
7779                                        } else {
7780                                            std::cmp::Ordering::Equal
7781                                        }
7782                                    }
7783                                    Err(_) => std::cmp::Ordering::Equal,
7784                                };
7785                                local_interp.scope_pop_hook();
7786                                ord
7787                            });
7788                        }
7789                        pmap_progress.tick();
7790                        pmap_progress.finish();
7791                        self.push(PerlValue::array(items));
7792                        Ok(())
7793                    }
7794                    Op::PSortWithBlockFast(tag) => {
7795                        let mut items = self.pop().to_list();
7796                        let progress_flag = self.pop().is_true();
7797                        let pmap_progress = PmapProgress::new(progress_flag, 2);
7798                        pmap_progress.tick();
7799                        let mode = match *tag {
7800                            0 => SortBlockFast::Numeric,
7801                            1 => SortBlockFast::String,
7802                            2 => SortBlockFast::NumericRev,
7803                            3 => SortBlockFast::StringRev,
7804                            _ => SortBlockFast::Numeric,
7805                        };
7806                        items.par_sort_by(|a, b| sort_magic_cmp(a, b, mode));
7807                        pmap_progress.tick();
7808                        pmap_progress.finish();
7809                        self.push(PerlValue::array(items));
7810                        Ok(())
7811                    }
7812                    Op::PSortNoBlockParallel => {
7813                        let mut items = self.pop().to_list();
7814                        let progress_flag = self.pop().is_true();
7815                        let pmap_progress = PmapProgress::new(progress_flag, 2);
7816                        pmap_progress.tick();
7817                        items.par_sort_by(|a, b| a.to_string().cmp(&b.to_string()));
7818                        pmap_progress.tick();
7819                        pmap_progress.finish();
7820                        self.push(PerlValue::array(items));
7821                        Ok(())
7822                    }
7823                    Op::FanWithBlock(block_idx) => {
7824                        let line = self.line();
7825                        let n = self.pop().to_int().max(0) as usize;
7826                        let progress_flag = self.pop().is_true();
7827                        self.run_fan_block(*block_idx, n, line, progress_flag)?;
7828                        Ok(())
7829                    }
7830                    Op::FanWithBlockAuto(block_idx) => {
7831                        let line = self.line();
7832                        let n = self.interp.parallel_thread_count();
7833                        let progress_flag = self.pop().is_true();
7834                        self.run_fan_block(*block_idx, n, line, progress_flag)?;
7835                        Ok(())
7836                    }
7837                    Op::FanCapWithBlock(block_idx) => {
7838                        let line = self.line();
7839                        let n = self.pop().to_int().max(0) as usize;
7840                        let progress_flag = self.pop().is_true();
7841                        self.run_fan_cap_block(*block_idx, n, line, progress_flag)?;
7842                        Ok(())
7843                    }
7844                    Op::FanCapWithBlockAuto(block_idx) => {
7845                        let line = self.line();
7846                        let n = self.interp.parallel_thread_count();
7847                        let progress_flag = self.pop().is_true();
7848                        self.run_fan_cap_block(*block_idx, n, line, progress_flag)?;
7849                        Ok(())
7850                    }
7851
7852                    Op::AsyncBlock(block_idx) => {
7853                        let block = self.blocks[*block_idx as usize].clone();
7854                        let subs = self.interp.subs.clone();
7855                        let (scope_capture, atomic_arrays, atomic_hashes) =
7856                            self.interp.scope.capture_with_atomics();
7857                        let result_slot: Arc<Mutex<Option<PerlResult<PerlValue>>>> =
7858                            Arc::new(Mutex::new(None));
7859                        let join_slot: Arc<Mutex<Option<std::thread::JoinHandle<()>>>> =
7860                            Arc::new(Mutex::new(None));
7861                        let rs = Arc::clone(&result_slot);
7862                        let h = std::thread::spawn(move || {
7863                            let mut local_interp = Interpreter::new();
7864                            local_interp.subs = subs;
7865                            local_interp.scope.restore_capture(&scope_capture);
7866                            local_interp
7867                                .scope
7868                                .restore_atomics(&atomic_arrays, &atomic_hashes);
7869                            local_interp.enable_parallel_guard();
7870                            local_interp.scope_push_hook();
7871                            let out = match local_interp.exec_block_no_scope(&block) {
7872                                Ok(v) => Ok(v),
7873                                Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
7874                                Err(FlowOrError::Error(e)) => Err(e),
7875                                Err(_) => Ok(PerlValue::UNDEF),
7876                            };
7877                            local_interp.scope_pop_hook();
7878                            *rs.lock() = Some(out);
7879                        });
7880                        *join_slot.lock() = Some(h);
7881                        self.push(PerlValue::async_task(Arc::new(PerlAsyncTask {
7882                            result: result_slot,
7883                            join: join_slot,
7884                        })));
7885                        Ok(())
7886                    }
7887                    Op::Await => {
7888                        let v = self.pop();
7889                        if let Some(t) = v.as_async_task() {
7890                            let r = t.await_result();
7891                            self.push(r?);
7892                        } else {
7893                            self.push(v);
7894                        }
7895                        Ok(())
7896                    }
7897
7898                    Op::LoadCurrentSub => {
7899                        if let Some(sub) = self.interp.current_sub_stack.last().cloned() {
7900                            self.push(PerlValue::code_ref(sub));
7901                        } else {
7902                            self.push(PerlValue::UNDEF);
7903                        }
7904                        Ok(())
7905                    }
7906
7907                    Op::DeferBlock => {
7908                        let coderef = self.pop();
7909                        self.interp.scope.push_defer(coderef);
7910                        Ok(())
7911                    }
7912
7913                    // ── try / catch / finally ──
7914                    Op::TryPush { .. } => {
7915                        self.try_stack.push(TryFrame {
7916                            try_push_op_idx: self.ip - 1,
7917                        });
7918                        Ok(())
7919                    }
7920                    Op::TryContinueNormal => {
7921                        let frame = self.try_stack.last().ok_or_else(|| {
7922                            PerlError::runtime("TryContinueNormal without active try", self.line())
7923                        })?;
7924                        let Op::TryPush {
7925                            finally_ip,
7926                            after_ip,
7927                            ..
7928                        } = &self.ops[frame.try_push_op_idx]
7929                        else {
7930                            return Err(PerlError::runtime(
7931                                "TryContinueNormal: corrupt try frame",
7932                                self.line(),
7933                            ));
7934                        };
7935                        if let Some(fin_ip) = *finally_ip {
7936                            self.ip = fin_ip;
7937                            Ok(())
7938                        } else {
7939                            self.try_stack.pop();
7940                            self.ip = *after_ip;
7941                            Ok(())
7942                        }
7943                    }
7944                    Op::TryFinallyEnd => {
7945                        let frame = self.try_stack.pop().ok_or_else(|| {
7946                            PerlError::runtime("TryFinallyEnd without active try", self.line())
7947                        })?;
7948                        let Op::TryPush { after_ip, .. } = &self.ops[frame.try_push_op_idx] else {
7949                            return Err(PerlError::runtime(
7950                                "TryFinallyEnd: corrupt try frame",
7951                                self.line(),
7952                            ));
7953                        };
7954                        self.ip = *after_ip;
7955                        Ok(())
7956                    }
7957                    Op::CatchReceive(idx) => {
7958                        let msg = self.pending_catch_error.take().ok_or_else(|| {
7959                            PerlError::runtime(
7960                                "CatchReceive without pending exception",
7961                                self.line(),
7962                            )
7963                        })?;
7964                        let n = names[*idx as usize].as_str();
7965                        self.interp.scope_pop_hook();
7966                        self.interp.scope_push_hook();
7967                        self.interp.scope.declare_scalar(n, PerlValue::string(msg));
7968                        self.interp.english_note_lexical_scalar(n);
7969                        Ok(())
7970                    }
7971
7972                    Op::DeclareMySyncScalar(name_idx) => {
7973                        let val = self.pop();
7974                        let n = names[*name_idx as usize].as_str();
7975                        let stored = if val.is_mysync_deque_or_heap() {
7976                            val
7977                        } else {
7978                            PerlValue::atomic(Arc::new(Mutex::new(val)))
7979                        };
7980                        self.interp.scope.declare_scalar(n, stored);
7981                        Ok(())
7982                    }
7983                    Op::DeclareMySyncArray(name_idx) => {
7984                        let val = self.pop();
7985                        let n = names[*name_idx as usize].as_str();
7986                        self.interp.scope.declare_atomic_array(n, val.to_list());
7987                        Ok(())
7988                    }
7989                    Op::DeclareMySyncHash(name_idx) => {
7990                        let val = self.pop();
7991                        let n = names[*name_idx as usize].as_str();
7992                        let items = val.to_list();
7993                        let mut map = IndexMap::new();
7994                        let mut i = 0usize;
7995                        while i + 1 < items.len() {
7996                            map.insert(items[i].to_string(), items[i + 1].clone());
7997                            i += 2;
7998                        }
7999                        self.interp.scope.declare_atomic_hash(n, map);
8000                        Ok(())
8001                    }
8002                    Op::RuntimeSubDecl(idx) => {
8003                        let rs = &self.runtime_sub_decls[*idx as usize];
8004                        let key = self.interp.qualify_sub_key(&rs.name);
8005                        let captured = self.interp.scope.capture();
8006                        let closure_env = if captured.is_empty() {
8007                            None
8008                        } else {
8009                            Some(captured)
8010                        };
8011                        let mut sub = PerlSub {
8012                            name: rs.name.clone(),
8013                            params: rs.params.clone(),
8014                            body: rs.body.clone(),
8015                            closure_env,
8016                            prototype: rs.prototype.clone(),
8017                            fib_like: None,
8018                        };
8019                        sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&sub);
8020                        self.interp.subs.insert(key, Arc::new(sub));
8021                        Ok(())
8022                    }
8023                    Op::Tie {
8024                        target_kind,
8025                        name_idx,
8026                        argc,
8027                    } => {
8028                        let argc = *argc as usize;
8029                        let mut stack_vals = Vec::with_capacity(argc);
8030                        for _ in 0..argc {
8031                            stack_vals.push(self.pop());
8032                        }
8033                        stack_vals.reverse();
8034                        let name = names[*name_idx as usize].as_str();
8035                        let line = self.line();
8036                        self.interp
8037                            .tie_execute(*target_kind, name, stack_vals, line)
8038                            .map_err(|e| e.at_line(line))?;
8039                        Ok(())
8040                    }
8041                    Op::FormatDecl(idx) => {
8042                        let (basename, lines) = &self.format_decls[*idx as usize];
8043                        let line = self.line();
8044                        self.interp
8045                            .install_format_decl(basename.as_str(), lines, line)
8046                            .map_err(|e| e.at_line(line))?;
8047                        Ok(())
8048                    }
8049                    Op::UseOverload(idx) => {
8050                        let pairs = &self.use_overload_entries[*idx as usize];
8051                        self.interp.install_use_overload_pairs(pairs);
8052                        Ok(())
8053                    }
8054                    Op::ScalarCompoundAssign { name_idx, op: op_b } => {
8055                        let rhs = self.pop();
8056                        let n = names[*name_idx as usize].as_str();
8057                        let op = scalar_compound_op_from_byte(*op_b).ok_or_else(|| {
8058                            PerlError::runtime("ScalarCompoundAssign: invalid op byte", self.line())
8059                        })?;
8060                        let en = self.interp.english_scalar_name(n);
8061                        let val = self
8062                            .interp
8063                            .scalar_compound_assign_scalar_target(en, op, rhs)
8064                            .map_err(|e| e.at_line(self.line()))?;
8065                        self.push(val);
8066                        Ok(())
8067                    }
8068
8069                    Op::SetGlobalPhase(phase) => {
8070                        let s = match *phase {
8071                            crate::bytecode::GP_START => "START",
8072                            crate::bytecode::GP_UNITCHECK => "UNITCHECK",
8073                            crate::bytecode::GP_CHECK => "CHECK",
8074                            crate::bytecode::GP_INIT => "INIT",
8075                            crate::bytecode::GP_RUN => "RUN",
8076                            crate::bytecode::GP_END => "END",
8077                            _ => {
8078                                return Err(PerlError::runtime(
8079                                    format!("SetGlobalPhase: invalid phase byte {}", phase),
8080                                    self.line(),
8081                                ));
8082                            }
8083                        };
8084                        self.interp.global_phase = s.to_string();
8085                        Ok(())
8086                    }
8087
8088                    // ── Halt ──
8089                    Op::Halt => {
8090                        self.halt = true;
8091                        Ok(())
8092                    }
8093                    Op::EvalAstExpr(idx) => {
8094                        let expr = &self.ast_eval_exprs[*idx as usize];
8095                        let val = match self.interp.eval_expr_ctx(expr, self.interp.wantarray_kind)
8096                        {
8097                            Ok(v) => v,
8098                            Err(crate::interpreter::FlowOrError::Error(e)) => return Err(e),
8099                            Err(crate::interpreter::FlowOrError::Flow(f)) => {
8100                                return Err(PerlError::runtime(
8101                                    format!("unexpected flow control in EvalAstExpr: {:?}", f),
8102                                    self.line(),
8103                                ));
8104                            }
8105                        };
8106                        self.push(val);
8107                        Ok(())
8108                    }
8109                }
8110            })();
8111            if let (Some(prof), Some(t0)) = (&mut self.interp.profiler, op_prof_t0) {
8112                prof.on_line(&self.interp.file, line, t0.elapsed());
8113            }
8114            if let Err(e) = __op_res {
8115                if self.try_recover_from_exception(&e)? {
8116                    continue;
8117                }
8118                return Err(e);
8119            }
8120            // Blessed refcount drops enqueue from `PerlValue::drop`; drain before the next opcode
8121            // so `$x = undef; f()` runs `DESTROY` before `f` (Perl semantics).
8122            if crate::pending_destroy::pending_destroy_vm_sync_needed() {
8123                self.interp.drain_pending_destroys(line)?;
8124            }
8125            if self.exit_main_dispatch {
8126                if let Some(v) = self.exit_main_dispatch_value.take() {
8127                    last = v;
8128                }
8129                break;
8130            }
8131            if self.halt {
8132                break;
8133            }
8134        }
8135
8136        if !self.stack.is_empty() {
8137            last = self.stack.last().cloned().unwrap_or(PerlValue::UNDEF);
8138            // Drain iterators left on the stack so side effects fire
8139            // (e.g. `pmaps { system(...) } @list` with no consumer).
8140            if last.is_iterator() {
8141                let iter = last.clone().into_iterator();
8142                while iter.next_item().is_some() {}
8143                last = PerlValue::UNDEF;
8144            }
8145        }
8146
8147        Ok(last)
8148    }
8149
8150    /// Called from Cranelift (`stryke_jit_call_sub`) to run a compiled sub by bytecode IP with `i64` args.
8151    pub(crate) fn jit_trampoline_run_sub(
8152        &mut self,
8153        entry_ip: usize,
8154        want: WantarrayCtx,
8155        args: &[i64],
8156    ) -> PerlResult<PerlValue> {
8157        let saved_wa = self.interp.wantarray_kind;
8158        for a in args {
8159            self.push(PerlValue::integer(*a));
8160        }
8161        let stack_base = self.stack.len() - args.len();
8162        let mut sub_prof_t0 = None;
8163        if let Some(nidx) = self.sub_entry_name_idx(entry_ip) {
8164            sub_prof_t0 = self.interp.profiler.is_some().then(std::time::Instant::now);
8165            if let Some(p) = &mut self.interp.profiler {
8166                p.enter_sub(self.names[nidx as usize].as_str());
8167            }
8168        }
8169        self.call_stack.push(CallFrame {
8170            return_ip: 0,
8171            stack_base,
8172            scope_depth: self.interp.scope.depth(),
8173            saved_wantarray: saved_wa,
8174            jit_trampoline_return: true,
8175            block_region: false,
8176            sub_profiler_start: sub_prof_t0,
8177        });
8178        self.interp.wantarray_kind = want;
8179        self.interp.scope_push_hook();
8180        if let Some(nidx) = self.sub_entry_name_idx(entry_ip) {
8181            let nm = self.names[nidx as usize].as_str();
8182            if let Some(sub) = self.interp.subs.get(nm).cloned() {
8183                if let Some(ref env) = sub.closure_env {
8184                    self.interp.scope.restore_capture(env);
8185                }
8186            }
8187        }
8188        self.ip = entry_ip;
8189        self.jit_trampoline_out = None;
8190        self.jit_trampoline_depth = self.jit_trampoline_depth.saturating_add(1);
8191        let mut op_count = 0u64;
8192        let last = PerlValue::UNDEF;
8193        let r = self.run_main_dispatch_loop(last, &mut op_count, true);
8194        self.jit_trampoline_depth = self.jit_trampoline_depth.saturating_sub(1);
8195        r?;
8196        self.jit_trampoline_out.take().ok_or_else(|| {
8197            PerlError::runtime("JIT trampoline: subroutine did not return", self.line())
8198        })
8199    }
8200
8201    #[inline]
8202    fn find_sub_entry(&self, name_idx: u16) -> Option<(usize, bool)> {
8203        self.sub_entry_by_name.get(&name_idx).copied()
8204    }
8205
8206    /// Name pool index for a compiled sub entry IP (for closure env + JIT trampoline).
8207    fn sub_entry_name_idx(&self, entry_ip: usize) -> Option<u16> {
8208        for &(n, ip, _) in &self.sub_entries {
8209            if ip == entry_ip {
8210                return Some(n);
8211            }
8212        }
8213        None
8214    }
8215
8216    fn exec_builtin(&mut self, id: u16, args: Vec<PerlValue>) -> PerlResult<PerlValue> {
8217        let line = self.line();
8218        let bid = BuiltinId::from_u16(id);
8219        match bid {
8220            Some(BuiltinId::Length) => {
8221                let val = args.into_iter().next().unwrap_or(PerlValue::UNDEF);
8222                Ok(if let Some(a) = val.as_array_vec() {
8223                    PerlValue::integer(a.len() as i64)
8224                } else if let Some(h) = val.as_hash_map() {
8225                    PerlValue::integer(h.len() as i64)
8226                } else if let Some(b) = val.as_bytes_arc() {
8227                    PerlValue::integer(b.len() as i64)
8228                } else {
8229                    PerlValue::integer(val.to_string().len() as i64)
8230                })
8231            }
8232            Some(BuiltinId::Defined) => {
8233                let val = args.into_iter().next().unwrap_or(PerlValue::UNDEF);
8234                Ok(PerlValue::integer(if val.is_undef() { 0 } else { 1 }))
8235            }
8236            Some(BuiltinId::Abs) => {
8237                let val = args.into_iter().next().unwrap_or(PerlValue::UNDEF);
8238                Ok(PerlValue::float(val.to_number().abs()))
8239            }
8240            Some(BuiltinId::Int) => {
8241                let val = args.into_iter().next().unwrap_or(PerlValue::UNDEF);
8242                Ok(PerlValue::integer(val.to_number() as i64))
8243            }
8244            Some(BuiltinId::Sqrt) => {
8245                let val = args.into_iter().next().unwrap_or(PerlValue::UNDEF);
8246                Ok(PerlValue::float(val.to_number().sqrt()))
8247            }
8248            Some(BuiltinId::Sin) => {
8249                let val = args.into_iter().next().unwrap_or(PerlValue::UNDEF);
8250                Ok(PerlValue::float(val.to_number().sin()))
8251            }
8252            Some(BuiltinId::Cos) => {
8253                let val = args.into_iter().next().unwrap_or(PerlValue::UNDEF);
8254                Ok(PerlValue::float(val.to_number().cos()))
8255            }
8256            Some(BuiltinId::Atan2) => {
8257                let mut it = args.into_iter();
8258                let y = it.next().unwrap_or(PerlValue::UNDEF);
8259                let x = it.next().unwrap_or(PerlValue::UNDEF);
8260                Ok(PerlValue::float(y.to_number().atan2(x.to_number())))
8261            }
8262            Some(BuiltinId::Exp) => {
8263                let val = args.into_iter().next().unwrap_or(PerlValue::UNDEF);
8264                Ok(PerlValue::float(val.to_number().exp()))
8265            }
8266            Some(BuiltinId::Log) => {
8267                let val = args.into_iter().next().unwrap_or(PerlValue::UNDEF);
8268                Ok(PerlValue::float(val.to_number().ln()))
8269            }
8270            Some(BuiltinId::Rand) => {
8271                let upper = match args.len() {
8272                    0 => 1.0,
8273                    _ => args[0].to_number(),
8274                };
8275                Ok(PerlValue::float(self.interp.perl_rand(upper)))
8276            }
8277            Some(BuiltinId::Srand) => {
8278                let seed = match args.len() {
8279                    0 => None,
8280                    _ => Some(args[0].to_number()),
8281                };
8282                Ok(PerlValue::integer(self.interp.perl_srand(seed)))
8283            }
8284            Some(BuiltinId::Crypt) => {
8285                let mut it = args.into_iter();
8286                let p = it.next().unwrap_or(PerlValue::UNDEF).to_string();
8287                let salt = it.next().unwrap_or(PerlValue::UNDEF).to_string();
8288                Ok(PerlValue::string(crate::crypt_util::perl_crypt(&p, &salt)))
8289            }
8290            Some(BuiltinId::Fc) => {
8291                let s = args.into_iter().next().unwrap_or(PerlValue::UNDEF);
8292                Ok(PerlValue::string(default_case_fold_str(&s.to_string())))
8293            }
8294            Some(BuiltinId::Pos) => {
8295                let key = if args.is_empty() {
8296                    "_".to_string()
8297                } else {
8298                    args[0].to_string()
8299                };
8300                Ok(self
8301                    .interp
8302                    .regex_pos
8303                    .get(&key)
8304                    .copied()
8305                    .flatten()
8306                    .map(|n| PerlValue::integer(n as i64))
8307                    .unwrap_or(PerlValue::UNDEF))
8308            }
8309            Some(BuiltinId::Study) => {
8310                let s = args.into_iter().next().unwrap_or(PerlValue::UNDEF);
8311                Ok(Interpreter::study_return_value(&s.to_string()))
8312            }
8313            Some(BuiltinId::Chr) => {
8314                let n = args.into_iter().next().unwrap_or(PerlValue::UNDEF).to_int() as u32;
8315                Ok(PerlValue::string(
8316                    char::from_u32(n).map(|c| c.to_string()).unwrap_or_default(),
8317                ))
8318            }
8319            Some(BuiltinId::Ord) => {
8320                let s = args
8321                    .into_iter()
8322                    .next()
8323                    .unwrap_or(PerlValue::UNDEF)
8324                    .to_string();
8325                Ok(PerlValue::integer(
8326                    s.chars().next().map(|c| c as i64).unwrap_or(0),
8327                ))
8328            }
8329            Some(BuiltinId::Hex) => {
8330                let s = args
8331                    .into_iter()
8332                    .next()
8333                    .unwrap_or(PerlValue::UNDEF)
8334                    .to_string();
8335                let clean = s.trim().trim_start_matches("0x").trim_start_matches("0X");
8336                Ok(PerlValue::integer(
8337                    i64::from_str_radix(clean, 16).unwrap_or(0),
8338                ))
8339            }
8340            Some(BuiltinId::Oct) => {
8341                let s = args
8342                    .into_iter()
8343                    .next()
8344                    .unwrap_or(PerlValue::UNDEF)
8345                    .to_string();
8346                let s = s.trim();
8347                let n = if s.starts_with("0x") || s.starts_with("0X") {
8348                    i64::from_str_radix(&s[2..], 16).unwrap_or(0)
8349                } else if s.starts_with("0b") || s.starts_with("0B") {
8350                    i64::from_str_radix(&s[2..], 2).unwrap_or(0)
8351                } else if s.starts_with("0o") || s.starts_with("0O") {
8352                    i64::from_str_radix(&s[2..], 8).unwrap_or(0)
8353                } else {
8354                    i64::from_str_radix(s.trim_start_matches('0'), 8).unwrap_or(0)
8355                };
8356                Ok(PerlValue::integer(n))
8357            }
8358            Some(BuiltinId::Uc) => {
8359                let s = args
8360                    .into_iter()
8361                    .next()
8362                    .unwrap_or(PerlValue::UNDEF)
8363                    .to_string();
8364                Ok(PerlValue::string(s.to_uppercase()))
8365            }
8366            Some(BuiltinId::Lc) => {
8367                let s = args
8368                    .into_iter()
8369                    .next()
8370                    .unwrap_or(PerlValue::UNDEF)
8371                    .to_string();
8372                Ok(PerlValue::string(s.to_lowercase()))
8373            }
8374            Some(BuiltinId::Ref) => {
8375                let val = args.into_iter().next().unwrap_or(PerlValue::UNDEF);
8376                Ok(val.ref_type())
8377            }
8378            Some(BuiltinId::Scalar) => {
8379                let val = args.into_iter().next().unwrap_or(PerlValue::UNDEF);
8380                Ok(val.scalar_context())
8381            }
8382            Some(BuiltinId::Join) => {
8383                let mut iter = args.into_iter();
8384                let sep = iter.next().unwrap_or(PerlValue::UNDEF).to_string();
8385                let list = iter.next().unwrap_or(PerlValue::UNDEF).to_list();
8386                let mut strs = Vec::with_capacity(list.len());
8387                for v in list {
8388                    let s = match self.interp.stringify_value(v, line) {
8389                        Ok(s) => s,
8390                        Err(FlowOrError::Error(e)) => return Err(e),
8391                        Err(FlowOrError::Flow(_)) => {
8392                            return Err(PerlError::runtime("join: unexpected control flow", line));
8393                        }
8394                    };
8395                    strs.push(s);
8396                }
8397                Ok(PerlValue::string(strs.join(&sep)))
8398            }
8399            Some(BuiltinId::Split) => {
8400                let mut iter = args.into_iter();
8401                let pat = iter
8402                    .next()
8403                    .unwrap_or(PerlValue::string(" ".into()))
8404                    .to_string();
8405                let s = iter.next().unwrap_or(PerlValue::UNDEF).to_string();
8406                let lim = iter.next().map(|v| v.to_int() as usize);
8407
8408                // Special case: empty pattern splits into characters (Perl behavior)
8409                let parts: Vec<PerlValue> = if pat.is_empty() {
8410                    let chars: Vec<PerlValue> = s
8411                        .chars()
8412                        .map(|c| PerlValue::string(c.to_string()))
8413                        .collect();
8414                    if let Some(l) = lim {
8415                        chars.into_iter().take(l).collect()
8416                    } else {
8417                        chars
8418                    }
8419                } else {
8420                    let re =
8421                        regex::Regex::new(&pat).unwrap_or_else(|_| regex::Regex::new(" ").unwrap());
8422                    if let Some(l) = lim {
8423                        re.splitn(&s, l)
8424                            .map(|p| PerlValue::string(p.to_string()))
8425                            .collect()
8426                    } else {
8427                        re.split(&s)
8428                            .map(|p| PerlValue::string(p.to_string()))
8429                            .collect()
8430                    }
8431                };
8432                Ok(PerlValue::array(parts))
8433            }
8434            Some(BuiltinId::Sprintf) => {
8435                // sprintf arg list is Perl list context; flatten ranges / arrays / reverse
8436                // output into individual format arguments (same splatting as printf).
8437                let mut flat: Vec<PerlValue> = Vec::with_capacity(args.len());
8438                for a in args.into_iter() {
8439                    if let Some(items) = a.as_array_vec() {
8440                        flat.extend(items);
8441                    } else {
8442                        flat.push(a);
8443                    }
8444                }
8445                let args = flat;
8446                if args.is_empty() {
8447                    return Ok(PerlValue::string(String::new()));
8448                }
8449                let fmt = args[0].to_string();
8450                let rest = &args[1..];
8451                match self.interp.perl_sprintf_stringify(&fmt, rest, line) {
8452                    Ok(s) => Ok(PerlValue::string(s)),
8453                    Err(FlowOrError::Error(e)) => Err(e),
8454                    Err(FlowOrError::Flow(_)) => {
8455                        Err(PerlError::runtime("sprintf: unexpected control flow", line))
8456                    }
8457                }
8458            }
8459            Some(BuiltinId::Reverse) => {
8460                let val = args.into_iter().next().unwrap_or(PerlValue::UNDEF);
8461                Ok(if let Some(mut a) = val.as_array_vec() {
8462                    a.reverse();
8463                    PerlValue::array(a)
8464                } else if let Some(s) = val.as_str() {
8465                    PerlValue::string(s.chars().rev().collect())
8466                } else {
8467                    PerlValue::string(val.to_string().chars().rev().collect())
8468                })
8469            }
8470            Some(BuiltinId::Die) => {
8471                let mut msg = String::new();
8472                for a in &args {
8473                    msg.push_str(&a.to_string());
8474                }
8475                if msg.is_empty() {
8476                    msg = "Died".to_string();
8477                }
8478                if !msg.ends_with('\n') {
8479                    msg.push_str(&self.interp.die_warn_at_suffix(line));
8480                    msg.push('\n');
8481                }
8482                Err(PerlError::die(msg, line))
8483            }
8484            Some(BuiltinId::Warn) => {
8485                let mut msg = String::new();
8486                for a in &args {
8487                    msg.push_str(&a.to_string());
8488                }
8489                if msg.is_empty() {
8490                    msg = "Warning: something's wrong".to_string();
8491                }
8492                if !msg.ends_with('\n') {
8493                    msg.push_str(&self.interp.die_warn_at_suffix(line));
8494                    msg.push('\n');
8495                }
8496                eprint!("{}", msg);
8497                Ok(PerlValue::integer(1))
8498            }
8499            Some(BuiltinId::Exit) => {
8500                let code = args
8501                    .into_iter()
8502                    .next()
8503                    .map(|v| v.to_int() as i32)
8504                    .unwrap_or(0);
8505                Err(PerlError::new(
8506                    ErrorKind::Exit(code),
8507                    "",
8508                    line,
8509                    &self.interp.file,
8510                ))
8511            }
8512            Some(BuiltinId::System) => {
8513                let cmd = args
8514                    .iter()
8515                    .map(|a| a.to_string())
8516                    .collect::<Vec<_>>()
8517                    .join(" ");
8518                let status = std::process::Command::new("sh")
8519                    .arg("-c")
8520                    .arg(&cmd)
8521                    .status();
8522                match status {
8523                    Ok(s) => {
8524                        self.interp.record_child_exit_status(s);
8525                        Ok(PerlValue::integer(s.code().unwrap_or(-1) as i64))
8526                    }
8527                    Err(e) => {
8528                        self.interp.errno = e.to_string();
8529                        self.interp.child_exit_status = -1;
8530                        Ok(PerlValue::integer(-1))
8531                    }
8532                }
8533            }
8534            Some(BuiltinId::Ssh) => self.interp.ssh_builtin_execute(&args),
8535            Some(BuiltinId::Chomp) => {
8536                // Chomp modifies the variable in-place — but in CallBuiltin we get the value, not a reference.
8537                // Return the number of chars removed (like Perl).
8538                let val = args.into_iter().next().unwrap_or(PerlValue::UNDEF);
8539                let s = val.to_string();
8540                Ok(PerlValue::integer(if s.ends_with('\n') { 1 } else { 0 }))
8541            }
8542            Some(BuiltinId::Chop) => {
8543                let val = args.into_iter().next().unwrap_or(PerlValue::UNDEF);
8544                let s = val.to_string();
8545                Ok(s.chars()
8546                    .last()
8547                    .map(|c| PerlValue::string(c.to_string()))
8548                    .unwrap_or(PerlValue::UNDEF))
8549            }
8550            Some(BuiltinId::Substr) => {
8551                let s = args.first().map(|v| v.to_string()).unwrap_or_default();
8552                let slen = s.len() as i64;
8553                let off = args.get(1).map(|v| v.to_int()).unwrap_or(0);
8554                let start = if off < 0 { (slen + off).max(0) } else { off }.min(slen) as usize;
8555
8556                let end = if let Some(l_val) = args.get(2) {
8557                    let l = l_val.to_int();
8558                    if l < 0 {
8559                        (slen + l).max(start as i64)
8560                    } else {
8561                        (start as i64 + l).min(slen)
8562                    }
8563                } else {
8564                    slen
8565                } as usize;
8566
8567                Ok(PerlValue::string(
8568                    s.get(start..end).unwrap_or("").to_string(),
8569                ))
8570            }
8571            Some(BuiltinId::Index) => {
8572                let s = args.first().map(|v| v.to_string()).unwrap_or_default();
8573                let sub = args.get(1).map(|v| v.to_string()).unwrap_or_default();
8574                let pos = args.get(2).map(|v| v.to_int() as usize).unwrap_or(0);
8575                Ok(PerlValue::integer(
8576                    s[pos..].find(&sub).map(|i| (i + pos) as i64).unwrap_or(-1),
8577                ))
8578            }
8579            Some(BuiltinId::Rindex) => {
8580                let s = args.first().map(|v| v.to_string()).unwrap_or_default();
8581                let sub = args.get(1).map(|v| v.to_string()).unwrap_or_default();
8582                let end = args
8583                    .get(2)
8584                    .map(|v| v.to_int() as usize + sub.len())
8585                    .unwrap_or(s.len());
8586                Ok(PerlValue::integer(
8587                    s[..end.min(s.len())]
8588                        .rfind(&sub)
8589                        .map(|i| i as i64)
8590                        .unwrap_or(-1),
8591                ))
8592            }
8593            Some(BuiltinId::Ucfirst) => {
8594                let s = args
8595                    .into_iter()
8596                    .next()
8597                    .unwrap_or(PerlValue::UNDEF)
8598                    .to_string();
8599                let mut chars = s.chars();
8600                let result = match chars.next() {
8601                    Some(c) => c.to_uppercase().to_string() + chars.as_str(),
8602                    None => String::new(),
8603                };
8604                Ok(PerlValue::string(result))
8605            }
8606            Some(BuiltinId::Lcfirst) => {
8607                let s = args
8608                    .into_iter()
8609                    .next()
8610                    .unwrap_or(PerlValue::UNDEF)
8611                    .to_string();
8612                let mut chars = s.chars();
8613                let result = match chars.next() {
8614                    Some(c) => c.to_lowercase().to_string() + chars.as_str(),
8615                    None => String::new(),
8616                };
8617                Ok(PerlValue::string(result))
8618            }
8619            Some(BuiltinId::Splice) => self.interp.splice_builtin_execute(&args, line),
8620            Some(BuiltinId::Unshift) => self.interp.unshift_builtin_execute(&args, line),
8621            Some(BuiltinId::Printf) => {
8622                // Flatten list-context operands (ranges, arrays, `reverse`, …) so format
8623                // placeholders line up with individual values instead of an array reference.
8624                let mut flat: Vec<PerlValue> = Vec::with_capacity(args.len());
8625                for a in args.into_iter() {
8626                    if let Some(items) = a.as_array_vec() {
8627                        flat.extend(items);
8628                    } else {
8629                        flat.push(a);
8630                    }
8631                }
8632                let args = flat;
8633                let (fmt, rest): (String, &[PerlValue]) = if args.is_empty() {
8634                    let s = match self
8635                        .interp
8636                        .stringify_value(self.interp.scope.get_scalar("_").clone(), line)
8637                    {
8638                        Ok(s) => s,
8639                        Err(FlowOrError::Error(e)) => return Err(e),
8640                        Err(FlowOrError::Flow(_)) => {
8641                            return Err(PerlError::runtime(
8642                                "printf: unexpected control flow",
8643                                line,
8644                            ));
8645                        }
8646                    };
8647                    (s, &[])
8648                } else {
8649                    (args[0].to_string(), &args[1..])
8650                };
8651                let out = match self.interp.perl_sprintf_stringify(&fmt, rest, line) {
8652                    Ok(s) => s,
8653                    Err(FlowOrError::Error(e)) => return Err(e),
8654                    Err(FlowOrError::Flow(_)) => {
8655                        return Err(PerlError::runtime("printf: unexpected control flow", line));
8656                    }
8657                };
8658                print!("{}", out);
8659                if self.interp.output_autoflush {
8660                    let _ = io::stdout().flush();
8661                }
8662                Ok(PerlValue::integer(1))
8663            }
8664            Some(BuiltinId::Open) => {
8665                if args.len() < 2 {
8666                    return Err(PerlError::runtime(
8667                        "open requires at least 2 arguments",
8668                        line,
8669                    ));
8670                }
8671                let handle_name = args[0].to_string();
8672                let mode_s = args[1].to_string();
8673                let file_opt = args.get(2).map(|v| v.to_string());
8674                self.interp
8675                    .open_builtin_execute(handle_name, mode_s, file_opt, line)
8676            }
8677            Some(BuiltinId::Close) => {
8678                let name = args
8679                    .into_iter()
8680                    .next()
8681                    .unwrap_or(PerlValue::UNDEF)
8682                    .to_string();
8683                self.interp.close_builtin_execute(name)
8684            }
8685            Some(BuiltinId::Eof) => self.interp.eof_builtin_execute(&args, line),
8686            Some(BuiltinId::ReadLine) => {
8687                let h = if args.is_empty() {
8688                    None
8689                } else {
8690                    Some(args[0].to_string())
8691                };
8692                self.interp.readline_builtin_execute(h.as_deref())
8693            }
8694            Some(BuiltinId::ReadLineList) => {
8695                let h = if args.is_empty() {
8696                    None
8697                } else {
8698                    Some(args[0].to_string())
8699                };
8700                self.interp.readline_builtin_execute_list(h.as_deref())
8701            }
8702            Some(BuiltinId::Exec) => {
8703                let cmd = args
8704                    .iter()
8705                    .map(|a| a.to_string())
8706                    .collect::<Vec<_>>()
8707                    .join(" ");
8708                let status = std::process::Command::new("sh")
8709                    .arg("-c")
8710                    .arg(&cmd)
8711                    .status();
8712                std::process::exit(status.map(|s| s.code().unwrap_or(-1)).unwrap_or(-1));
8713            }
8714            Some(BuiltinId::Chdir) => {
8715                let path = args
8716                    .into_iter()
8717                    .next()
8718                    .unwrap_or(PerlValue::UNDEF)
8719                    .to_string();
8720                Ok(PerlValue::integer(
8721                    if std::env::set_current_dir(&path).is_ok() {
8722                        1
8723                    } else {
8724                        0
8725                    },
8726                ))
8727            }
8728            Some(BuiltinId::Mkdir) => {
8729                let path = args.first().map(|v| v.to_string()).unwrap_or_default();
8730                Ok(PerlValue::integer(if std::fs::create_dir(&path).is_ok() {
8731                    1
8732                } else {
8733                    0
8734                }))
8735            }
8736            Some(BuiltinId::Unlink) => {
8737                let mut count = 0i64;
8738                for a in &args {
8739                    if std::fs::remove_file(a.to_string()).is_ok() {
8740                        count += 1;
8741                    }
8742                }
8743                Ok(PerlValue::integer(count))
8744            }
8745            Some(BuiltinId::Rmdir) => self.interp.builtin_rmdir_execute(&args, line),
8746            Some(BuiltinId::Utime) => self.interp.builtin_utime_execute(&args, line),
8747            Some(BuiltinId::Umask) => self.interp.builtin_umask_execute(&args, line),
8748            Some(BuiltinId::Getcwd) => self.interp.builtin_getcwd_execute(&args, line),
8749            Some(BuiltinId::Pipe) => self.interp.builtin_pipe_execute(&args, line),
8750            Some(BuiltinId::Rename) => {
8751                let old = args.first().map(|v| v.to_string()).unwrap_or_default();
8752                let new = args.get(1).map(|v| v.to_string()).unwrap_or_default();
8753                Ok(crate::perl_fs::rename_paths(&old, &new))
8754            }
8755            Some(BuiltinId::Chmod) => {
8756                if args.is_empty() {
8757                    return Ok(PerlValue::integer(0));
8758                }
8759                let mode = args[0].to_int();
8760                let paths: Vec<String> = args.iter().skip(1).map(|v| v.to_string()).collect();
8761                Ok(PerlValue::integer(crate::perl_fs::chmod_paths(
8762                    &paths, mode,
8763                )))
8764            }
8765            Some(BuiltinId::Chown) => {
8766                if args.len() < 3 {
8767                    return Ok(PerlValue::integer(0));
8768                }
8769                let uid = args[0].to_int();
8770                let gid = args[1].to_int();
8771                let paths: Vec<String> = args.iter().skip(2).map(|v| v.to_string()).collect();
8772                Ok(PerlValue::integer(crate::perl_fs::chown_paths(
8773                    &paths, uid, gid,
8774                )))
8775            }
8776            Some(BuiltinId::Stat) => {
8777                let path = args.first().map(|v| v.to_string()).unwrap_or_default();
8778                Ok(crate::perl_fs::stat_path(&path, false))
8779            }
8780            Some(BuiltinId::Lstat) => {
8781                let path = args.first().map(|v| v.to_string()).unwrap_or_default();
8782                Ok(crate::perl_fs::stat_path(&path, true))
8783            }
8784            Some(BuiltinId::Link) => {
8785                let old = args.first().map(|v| v.to_string()).unwrap_or_default();
8786                let new = args.get(1).map(|v| v.to_string()).unwrap_or_default();
8787                Ok(crate::perl_fs::link_hard(&old, &new))
8788            }
8789            Some(BuiltinId::Symlink) => {
8790                let old = args.first().map(|v| v.to_string()).unwrap_or_default();
8791                let new = args.get(1).map(|v| v.to_string()).unwrap_or_default();
8792                Ok(crate::perl_fs::link_sym(&old, &new))
8793            }
8794            Some(BuiltinId::Readlink) => {
8795                let path = args.first().map(|v| v.to_string()).unwrap_or_default();
8796                Ok(crate::perl_fs::read_link(&path))
8797            }
8798            Some(BuiltinId::Glob) => {
8799                let pats: Vec<String> = args.iter().map(|v| v.to_string()).collect();
8800                Ok(crate::perl_fs::glob_patterns(&pats))
8801            }
8802            Some(BuiltinId::Files) => {
8803                let dir = if args.is_empty() {
8804                    ".".to_string()
8805                } else {
8806                    args[0].to_string()
8807                };
8808                Ok(crate::perl_fs::list_files(&dir))
8809            }
8810            Some(BuiltinId::Filesf) => {
8811                let dir = if args.is_empty() {
8812                    ".".to_string()
8813                } else {
8814                    args[0].to_string()
8815                };
8816                Ok(crate::perl_fs::list_filesf(&dir))
8817            }
8818            Some(BuiltinId::FilesfRecursive) => {
8819                let dir = if args.is_empty() {
8820                    ".".to_string()
8821                } else {
8822                    args[0].to_string()
8823                };
8824                Ok(PerlValue::iterator(std::sync::Arc::new(
8825                    crate::value::FsWalkIterator::new(&dir, true),
8826                )))
8827            }
8828            Some(BuiltinId::Dirs) => {
8829                let dir = if args.is_empty() {
8830                    ".".to_string()
8831                } else {
8832                    args[0].to_string()
8833                };
8834                Ok(crate::perl_fs::list_dirs(&dir))
8835            }
8836            Some(BuiltinId::DirsRecursive) => {
8837                let dir = if args.is_empty() {
8838                    ".".to_string()
8839                } else {
8840                    args[0].to_string()
8841                };
8842                Ok(PerlValue::iterator(std::sync::Arc::new(
8843                    crate::value::FsWalkIterator::new(&dir, false),
8844                )))
8845            }
8846            Some(BuiltinId::SymLinks) => {
8847                let dir = if args.is_empty() {
8848                    ".".to_string()
8849                } else {
8850                    args[0].to_string()
8851                };
8852                Ok(crate::perl_fs::list_sym_links(&dir))
8853            }
8854            Some(BuiltinId::Sockets) => {
8855                let dir = if args.is_empty() {
8856                    ".".to_string()
8857                } else {
8858                    args[0].to_string()
8859                };
8860                Ok(crate::perl_fs::list_sockets(&dir))
8861            }
8862            Some(BuiltinId::Pipes) => {
8863                let dir = if args.is_empty() {
8864                    ".".to_string()
8865                } else {
8866                    args[0].to_string()
8867                };
8868                Ok(crate::perl_fs::list_pipes(&dir))
8869            }
8870            Some(BuiltinId::BlockDevices) => {
8871                let dir = if args.is_empty() {
8872                    ".".to_string()
8873                } else {
8874                    args[0].to_string()
8875                };
8876                Ok(crate::perl_fs::list_block_devices(&dir))
8877            }
8878            Some(BuiltinId::CharDevices) => {
8879                let dir = if args.is_empty() {
8880                    ".".to_string()
8881                } else {
8882                    args[0].to_string()
8883                };
8884                Ok(crate::perl_fs::list_char_devices(&dir))
8885            }
8886            Some(BuiltinId::Executables) => {
8887                let dir = if args.is_empty() {
8888                    ".".to_string()
8889                } else {
8890                    args[0].to_string()
8891                };
8892                Ok(crate::perl_fs::list_executables(&dir))
8893            }
8894            Some(BuiltinId::GlobPar) => {
8895                let pats: Vec<String> = args.iter().map(|v| v.to_string()).collect();
8896                Ok(crate::perl_fs::glob_par_patterns(&pats))
8897            }
8898            Some(BuiltinId::GlobParProgress) => {
8899                let progress = args.last().map(|v| v.is_true()).unwrap_or(false);
8900                let pats: Vec<String> = args[..args.len().saturating_sub(1)]
8901                    .iter()
8902                    .map(|v| v.to_string())
8903                    .collect();
8904                Ok(crate::perl_fs::glob_par_patterns_with_progress(
8905                    &pats, progress,
8906                ))
8907            }
8908            Some(BuiltinId::ParSed) => self.interp.builtin_par_sed(&args, line, false),
8909            Some(BuiltinId::ParSedProgress) => self.interp.builtin_par_sed(&args, line, true),
8910            Some(BuiltinId::Opendir) => {
8911                let handle = args.first().map(|v| v.to_string()).unwrap_or_default();
8912                let path = args.get(1).map(|v| v.to_string()).unwrap_or_default();
8913                Ok(self.interp.opendir_handle(&handle, &path))
8914            }
8915            Some(BuiltinId::Readdir) => {
8916                let handle = args.first().map(|v| v.to_string()).unwrap_or_default();
8917                Ok(self.interp.readdir_handle(&handle))
8918            }
8919            Some(BuiltinId::ReaddirList) => {
8920                let handle = args.first().map(|v| v.to_string()).unwrap_or_default();
8921                Ok(self.interp.readdir_handle_list(&handle))
8922            }
8923            Some(BuiltinId::Closedir) => {
8924                let handle = args.first().map(|v| v.to_string()).unwrap_or_default();
8925                Ok(self.interp.closedir_handle(&handle))
8926            }
8927            Some(BuiltinId::Rewinddir) => {
8928                let handle = args.first().map(|v| v.to_string()).unwrap_or_default();
8929                Ok(self.interp.rewinddir_handle(&handle))
8930            }
8931            Some(BuiltinId::Telldir) => {
8932                let handle = args.first().map(|v| v.to_string()).unwrap_or_default();
8933                Ok(self.interp.telldir_handle(&handle))
8934            }
8935            Some(BuiltinId::Seekdir) => {
8936                let handle = args.first().map(|v| v.to_string()).unwrap_or_default();
8937                let pos = args.get(1).map(|v| v.to_int().max(0) as usize).unwrap_or(0);
8938                Ok(self.interp.seekdir_handle(&handle, pos))
8939            }
8940            Some(BuiltinId::Slurp) => {
8941                let path = args
8942                    .into_iter()
8943                    .next()
8944                    .unwrap_or(PerlValue::UNDEF)
8945                    .to_string();
8946                read_file_text_perl_compat(&path)
8947                    .map(PerlValue::string)
8948                    .map_err(|e| PerlError::runtime(format!("slurp: {}", e), line))
8949            }
8950            Some(BuiltinId::Capture) => {
8951                let cmd = args
8952                    .into_iter()
8953                    .next()
8954                    .unwrap_or(PerlValue::UNDEF)
8955                    .to_string();
8956                crate::capture::run_capture(self.interp, &cmd, line)
8957            }
8958            Some(BuiltinId::Ppool) => {
8959                let n = args
8960                    .first()
8961                    .map(|v| v.to_int().max(0) as usize)
8962                    .unwrap_or(1);
8963                crate::ppool::create_pool(n)
8964            }
8965            Some(BuiltinId::Wantarray) => Ok(match self.interp.wantarray_kind {
8966                crate::interpreter::WantarrayCtx::Void => PerlValue::UNDEF,
8967                crate::interpreter::WantarrayCtx::Scalar => PerlValue::integer(0),
8968                crate::interpreter::WantarrayCtx::List => PerlValue::integer(1),
8969            }),
8970            Some(BuiltinId::FetchUrl) => {
8971                let url = args
8972                    .into_iter()
8973                    .next()
8974                    .unwrap_or(PerlValue::UNDEF)
8975                    .to_string();
8976                ureq::get(&url)
8977                    .call()
8978                    .map_err(|e| PerlError::runtime(format!("fetch_url: {}", e), line))
8979                    .and_then(|r| {
8980                        r.into_string()
8981                            .map(PerlValue::string)
8982                            .map_err(|e| PerlError::runtime(format!("fetch_url: {}", e), line))
8983                    })
8984            }
8985            Some(BuiltinId::Pchannel) => {
8986                if args.is_empty() {
8987                    Ok(crate::pchannel::create_pair())
8988                } else if args.len() == 1 {
8989                    let n = args[0].to_int().max(1) as usize;
8990                    Ok(crate::pchannel::create_bounded_pair(n))
8991                } else {
8992                    Err(PerlError::runtime(
8993                        "pchannel() takes 0 or 1 arguments (capacity)",
8994                        line,
8995                    ))
8996                }
8997            }
8998            Some(BuiltinId::Pselect) => crate::pchannel::pselect_recv(&args, line),
8999            Some(BuiltinId::DequeNew) => {
9000                if !args.is_empty() {
9001                    return Err(PerlError::runtime("deque() takes no arguments", line));
9002                }
9003                Ok(PerlValue::deque(Arc::new(Mutex::new(VecDeque::new()))))
9004            }
9005            Some(BuiltinId::HeapNew) => {
9006                if args.len() != 1 {
9007                    return Err(PerlError::runtime(
9008                        "heap() expects one comparator sub",
9009                        line,
9010                    ));
9011                }
9012                let a0 = args.into_iter().next().unwrap_or(PerlValue::UNDEF);
9013                if let Some(sub) = a0.as_code_ref() {
9014                    Ok(PerlValue::heap(Arc::new(Mutex::new(PerlHeap {
9015                        items: Vec::new(),
9016                        cmp: Arc::clone(&sub),
9017                    }))))
9018                } else {
9019                    Err(PerlError::runtime("heap() requires a code reference", line))
9020                }
9021            }
9022            Some(BuiltinId::BarrierNew) => {
9023                let n = args
9024                    .first()
9025                    .map(|v| v.to_int().max(1) as usize)
9026                    .unwrap_or(1);
9027                Ok(PerlValue::barrier(PerlBarrier(Arc::new(Barrier::new(n)))))
9028            }
9029            Some(BuiltinId::Pipeline) => {
9030                let mut items = Vec::new();
9031                for v in args {
9032                    if let Some(a) = v.as_array_vec() {
9033                        items.extend(a);
9034                    } else {
9035                        items.push(v);
9036                    }
9037                }
9038                Ok(PerlValue::pipeline(Arc::new(Mutex::new(PipelineInner {
9039                    source: items,
9040                    ops: Vec::new(),
9041                    has_scalar_terminal: false,
9042                    par_stream: false,
9043                    streaming: false,
9044                    streaming_workers: 0,
9045                    streaming_buffer: 256,
9046                }))))
9047            }
9048            Some(BuiltinId::ParPipeline) => {
9049                if crate::par_pipeline::is_named_par_pipeline_args(&args) {
9050                    return crate::par_pipeline::run_par_pipeline(self.interp, &args, line);
9051                }
9052                let mut items = Vec::new();
9053                for v in args {
9054                    if let Some(a) = v.as_array_vec() {
9055                        items.extend(a);
9056                    } else {
9057                        items.push(v);
9058                    }
9059                }
9060                Ok(PerlValue::pipeline(Arc::new(Mutex::new(PipelineInner {
9061                    source: items,
9062                    ops: Vec::new(),
9063                    has_scalar_terminal: false,
9064                    par_stream: true,
9065                    streaming: false,
9066                    streaming_workers: 0,
9067                    streaming_buffer: 256,
9068                }))))
9069            }
9070            Some(BuiltinId::ParPipelineStream) => {
9071                if crate::par_pipeline::is_named_par_pipeline_args(&args) {
9072                    return crate::par_pipeline::run_par_pipeline_streaming(
9073                        self.interp,
9074                        &args,
9075                        line,
9076                    );
9077                }
9078                self.interp.builtin_par_pipeline_stream_new(&args, line)
9079            }
9080            Some(BuiltinId::Each) => {
9081                let _arg = args.into_iter().next().unwrap_or(PerlValue::UNDEF);
9082                Ok(PerlValue::array(vec![]))
9083            }
9084            Some(BuiltinId::Readpipe) => {
9085                let cmd = args
9086                    .into_iter()
9087                    .next()
9088                    .unwrap_or(PerlValue::UNDEF)
9089                    .to_string();
9090                crate::capture::run_readpipe(self.interp, &cmd, line)
9091            }
9092            Some(BuiltinId::Eval) => {
9093                let arg = args.into_iter().next().unwrap_or(PerlValue::UNDEF);
9094                self.interp.eval_nesting += 1;
9095                let out = if let Some(sub) = arg.as_code_ref() {
9096                    match self.interp.exec_block(&sub.body) {
9097                        Ok(v) => {
9098                            self.interp.clear_eval_error();
9099                            Ok(v)
9100                        }
9101                        Err(crate::interpreter::FlowOrError::Error(e)) => {
9102                            self.interp.set_eval_error_from_perl_error(&e);
9103                            Ok(PerlValue::UNDEF)
9104                        }
9105                        Err(crate::interpreter::FlowOrError::Flow(_)) => {
9106                            self.interp.clear_eval_error();
9107                            Ok(PerlValue::UNDEF)
9108                        }
9109                    }
9110                } else {
9111                    let code = arg.to_string();
9112                    match crate::parse_and_run_string(&code, self.interp) {
9113                        Ok(v) => {
9114                            self.interp.clear_eval_error();
9115                            Ok(v)
9116                        }
9117                        Err(e) => {
9118                            self.interp.set_eval_error_from_perl_error(&e);
9119                            Ok(PerlValue::UNDEF)
9120                        }
9121                    }
9122                };
9123                self.interp.eval_nesting -= 1;
9124                out
9125            }
9126            Some(BuiltinId::Do) => {
9127                let filename = args
9128                    .into_iter()
9129                    .next()
9130                    .unwrap_or(PerlValue::UNDEF)
9131                    .to_string();
9132                match read_file_text_perl_compat(&filename) {
9133                    Ok(code) => {
9134                        let code = crate::data_section::strip_perl_end_marker(&code);
9135                        crate::parse_and_run_string_in_file(code, self.interp, &filename)
9136                            .or(Ok(PerlValue::UNDEF))
9137                    }
9138                    Err(_) => Ok(PerlValue::UNDEF),
9139                }
9140            }
9141            Some(BuiltinId::Require) => {
9142                let name = args
9143                    .into_iter()
9144                    .next()
9145                    .unwrap_or(PerlValue::UNDEF)
9146                    .to_string();
9147                self.interp.require_execute(&name, line)
9148            }
9149            Some(BuiltinId::Bless) => {
9150                let ref_val = args.first().cloned().unwrap_or(PerlValue::UNDEF);
9151                let class = args
9152                    .get(1)
9153                    .map(|v| v.to_string())
9154                    .unwrap_or_else(|| self.interp.scope.get_scalar("__PACKAGE__").to_string());
9155                Ok(PerlValue::blessed(Arc::new(
9156                    crate::value::BlessedRef::new_blessed(class, ref_val),
9157                )))
9158            }
9159            Some(BuiltinId::Caller) => Ok(PerlValue::array(vec![
9160                PerlValue::string("main".into()),
9161                PerlValue::string(self.interp.file.clone()),
9162                PerlValue::integer(line as i64),
9163            ])),
9164            // Parallel ops (shouldn't reach here — handled by block ops)
9165            Some(BuiltinId::PMap)
9166            | Some(BuiltinId::PGrep)
9167            | Some(BuiltinId::PFor)
9168            | Some(BuiltinId::PSort)
9169            | Some(BuiltinId::Fan)
9170            | Some(BuiltinId::MapBlock)
9171            | Some(BuiltinId::GrepBlock)
9172            | Some(BuiltinId::SortBlock)
9173            | Some(BuiltinId::Sort) => Ok(PerlValue::UNDEF),
9174            _ => Err(PerlError::runtime(
9175                format!("Unimplemented builtin {:?}", bid),
9176                line,
9177            )),
9178        }
9179    }
9180}
9181
9182/// Integer fast-path comparison helper.
9183#[inline]
9184fn int_cmp(
9185    a: &PerlValue,
9186    b: &PerlValue,
9187    int_op: fn(&i64, &i64) -> bool,
9188    float_op: fn(f64, f64) -> bool,
9189) -> PerlValue {
9190    if let (Some(x), Some(y)) = (a.as_integer(), b.as_integer()) {
9191        PerlValue::integer(if int_op(&x, &y) { 1 } else { 0 })
9192    } else {
9193        PerlValue::integer(if float_op(a.to_number(), b.to_number()) {
9194            1
9195        } else {
9196            0
9197        })
9198    }
9199}
9200
9201/// Block JIT hook: string concat with `use overload` / `""` stringify (matches [`Op::Concat`]).
9202///
9203/// # Safety
9204///
9205/// `vm` must be a valid, non-null pointer to a live [`VM`] for the duration of this call.
9206#[no_mangle]
9207pub unsafe extern "C" fn stryke_jit_concat_vm(vm: *mut std::ffi::c_void, a: i64, b: i64) -> i64 {
9208    let vm: &mut VM<'static> = unsafe { &mut *(vm as *mut VM<'static>) };
9209    let pa = PerlValue::from_raw_bits(crate::jit::perl_value_bits_from_jit_string_operand(a));
9210    let pb = PerlValue::from_raw_bits(crate::jit::perl_value_bits_from_jit_string_operand(b));
9211    match vm.concat_stack_values(pa, pb) {
9212        Ok(pv) => pv.raw_bits() as i64,
9213        Err(_) => PerlValue::UNDEF.raw_bits() as i64,
9214    }
9215}
9216
9217/// Cranelift host hook: re-enter the VM for [`Op::Call`] to a compiled sub (stack-args, scalar `i64` args).
9218/// `sub_ip`, `argc`, `wa` are passed as `i64` for a uniform Cranelift signature.
9219///
9220/// # Safety
9221///
9222/// `vm` must be a valid, non-null pointer to a live [`VM`] for the duration of this call (JIT only
9223/// invokes this while the VM is executing).
9224#[no_mangle]
9225pub unsafe extern "C" fn stryke_jit_call_sub(
9226    vm: *mut std::ffi::c_void,
9227    sub_ip: i64,
9228    argc: i64,
9229    wa: i64,
9230    a0: i64,
9231    a1: i64,
9232    a2: i64,
9233    a3: i64,
9234    a4: i64,
9235    a5: i64,
9236    a6: i64,
9237    a7: i64,
9238) -> i64 {
9239    let vm: &mut VM<'static> = unsafe { &mut *(vm as *mut VM<'static>) };
9240    let want = WantarrayCtx::from_byte(wa as u8);
9241    if want != WantarrayCtx::Scalar {
9242        return PerlValue::UNDEF.raw_bits() as i64;
9243    }
9244    let argc = argc.clamp(0, 8) as usize;
9245    let args = [a0, a1, a2, a3, a4, a5, a6, a7];
9246    let args = &args[..argc];
9247    match vm.jit_trampoline_run_sub(sub_ip as usize, want, args) {
9248        Ok(pv) => {
9249            if let Some(n) = pv.as_integer() {
9250                n
9251            } else {
9252                pv.raw_bits() as i64
9253            }
9254        }
9255        Err(_) => PerlValue::UNDEF.raw_bits() as i64,
9256    }
9257}
9258
9259#[cfg(test)]
9260mod tests {
9261    use super::*;
9262    use crate::bytecode::{Chunk, Op};
9263    use crate::value::PerlValue;
9264
9265    fn run_chunk(chunk: &Chunk) -> PerlResult<PerlValue> {
9266        let mut interp = Interpreter::new();
9267        let mut vm = VM::new(chunk, &mut interp);
9268        vm.execute()
9269    }
9270
9271    /// Block-JIT-eligible loop: `for ($i=0; $i<limit; $i++) { $sum += $i }` — sum 0..limit-1.
9272    fn block_jit_sum_chunk(limit: i64) -> Chunk {
9273        let mut c = Chunk::new();
9274        let ni = c.intern_name("i");
9275        let ns = c.intern_name("sum");
9276        c.emit(Op::LoadInt(0), 1);
9277        c.emit(Op::DeclareScalarSlot(0, ni), 1);
9278        c.emit(Op::LoadInt(0), 1);
9279        c.emit(Op::DeclareScalarSlot(1, ns), 1);
9280        c.emit(Op::GetScalarSlot(0), 1);
9281        c.emit(Op::LoadInt(limit), 1);
9282        c.emit(Op::NumLt, 1);
9283        c.emit(Op::JumpIfFalse(15), 1);
9284        c.emit(Op::GetScalarSlot(1), 1);
9285        c.emit(Op::GetScalarSlot(0), 1);
9286        c.emit(Op::Add, 1);
9287        c.emit(Op::SetScalarSlot(1), 1);
9288        c.emit(Op::PostIncSlot(0), 1);
9289        c.emit(Op::Pop, 1);
9290        c.emit(Op::Jump(4), 1);
9291        c.emit(Op::GetScalarSlot(1), 1);
9292        c.emit(Op::Halt, 1);
9293        c
9294    }
9295
9296    #[test]
9297    fn jit_disabled_same_result_as_jit_block_loop() {
9298        let limit = 500i64;
9299        let chunk = block_jit_sum_chunk(limit);
9300        let expect = limit * (limit - 1) / 2;
9301
9302        let mut interp_on = Interpreter::new();
9303        let mut vm_on = VM::new(&chunk, &mut interp_on);
9304        assert_eq!(vm_on.execute().expect("vm").to_int(), expect);
9305
9306        let mut interp_off = Interpreter::new();
9307        let mut vm_off = VM::new(&chunk, &mut interp_off);
9308        vm_off.set_jit_enabled(false);
9309        assert_eq!(vm_off.execute().expect("vm").to_int(), expect);
9310    }
9311
9312    #[test]
9313    fn vm_add_two_integers() {
9314        let mut c = Chunk::new();
9315        c.emit(Op::LoadInt(2), 1);
9316        c.emit(Op::LoadInt(3), 1);
9317        c.emit(Op::Add, 1);
9318        c.emit(Op::Halt, 1);
9319        let v = run_chunk(&c).expect("vm");
9320        assert_eq!(v.to_int(), 5);
9321    }
9322
9323    #[test]
9324    fn vm_sub_mul_div() {
9325        let mut c = Chunk::new();
9326        c.emit(Op::LoadInt(10), 1);
9327        c.emit(Op::LoadInt(3), 1);
9328        c.emit(Op::Sub, 1);
9329        c.emit(Op::Halt, 1);
9330        assert_eq!(run_chunk(&c).expect("vm").to_int(), 7);
9331
9332        let mut c = Chunk::new();
9333        c.emit(Op::LoadInt(6), 1);
9334        c.emit(Op::LoadInt(7), 1);
9335        c.emit(Op::Mul, 1);
9336        c.emit(Op::Halt, 1);
9337        assert_eq!(run_chunk(&c).expect("vm").to_int(), 42);
9338
9339        let mut c = Chunk::new();
9340        c.emit(Op::LoadInt(20), 1);
9341        c.emit(Op::LoadInt(4), 1);
9342        c.emit(Op::Div, 1);
9343        c.emit(Op::Halt, 1);
9344        assert_eq!(run_chunk(&c).expect("vm").to_int(), 5);
9345    }
9346
9347    #[test]
9348    fn vm_mod_and_pow() {
9349        let mut c = Chunk::new();
9350        c.emit(Op::LoadInt(17), 1);
9351        c.emit(Op::LoadInt(5), 1);
9352        c.emit(Op::Mod, 1);
9353        c.emit(Op::Halt, 1);
9354        assert_eq!(run_chunk(&c).expect("vm").to_int(), 2);
9355
9356        let mut c = Chunk::new();
9357        c.emit(Op::LoadInt(2), 1);
9358        c.emit(Op::LoadInt(3), 1);
9359        c.emit(Op::Pow, 1);
9360        c.emit(Op::Halt, 1);
9361        assert_eq!(run_chunk(&c).expect("vm").to_int(), 8);
9362    }
9363
9364    #[test]
9365    fn vm_negate() {
9366        let mut c = Chunk::new();
9367        c.emit(Op::LoadInt(7), 1);
9368        c.emit(Op::Negate, 1);
9369        c.emit(Op::Halt, 1);
9370        assert_eq!(run_chunk(&c).expect("vm").to_int(), -7);
9371    }
9372
9373    #[test]
9374    fn vm_dup_and_pop() {
9375        let mut c = Chunk::new();
9376        c.emit(Op::LoadInt(1), 1);
9377        c.emit(Op::Dup, 1);
9378        c.emit(Op::Add, 1);
9379        c.emit(Op::Halt, 1);
9380        assert_eq!(run_chunk(&c).expect("vm").to_int(), 2);
9381
9382        let mut c = Chunk::new();
9383        c.emit(Op::LoadInt(1), 1);
9384        c.emit(Op::LoadInt(2), 1);
9385        c.emit(Op::Pop, 1);
9386        c.emit(Op::Halt, 1);
9387        assert_eq!(run_chunk(&c).expect("vm").to_int(), 1);
9388    }
9389
9390    #[test]
9391    fn vm_set_get_scalar() {
9392        let mut c = Chunk::new();
9393        let i = c.intern_name("v");
9394        c.emit(Op::LoadInt(99), 1);
9395        c.emit(Op::SetScalar(i), 1);
9396        c.emit(Op::GetScalar(i), 1);
9397        c.emit(Op::Halt, 1);
9398        assert_eq!(run_chunk(&c).expect("vm").to_int(), 99);
9399    }
9400
9401    #[test]
9402    fn vm_scalar_plain_roundtrip_and_keep() {
9403        let mut c = Chunk::new();
9404        let i = c.intern_name("plainvar");
9405        c.emit(Op::LoadInt(99), 1);
9406        c.emit(Op::SetScalarPlain(i), 1);
9407        c.emit(Op::GetScalarPlain(i), 1);
9408        c.emit(Op::Halt, 1);
9409        assert_eq!(run_chunk(&c).expect("vm").to_int(), 99);
9410
9411        let mut c = Chunk::new();
9412        let k = c.intern_name("keepme");
9413        c.emit(Op::LoadInt(5), 1);
9414        c.emit(Op::SetScalarKeepPlain(k), 1);
9415        c.emit(Op::Halt, 1);
9416        assert_eq!(run_chunk(&c).expect("vm").to_int(), 5);
9417    }
9418
9419    #[test]
9420    fn vm_get_scalar_plain_skips_special_global_zero() {
9421        let mut c = Chunk::new();
9422        let idx = c.intern_name("0");
9423        c.emit(Op::GetScalar(idx), 1);
9424        c.emit(Op::Halt, 1);
9425        assert_eq!(run_chunk(&c).expect("vm").to_string(), "stryke");
9426
9427        let mut c = Chunk::new();
9428        let idx = c.intern_name("0");
9429        c.emit(Op::GetScalarPlain(idx), 1);
9430        c.emit(Op::Halt, 1);
9431        assert!(run_chunk(&c).expect("vm").is_undef());
9432    }
9433
9434    #[test]
9435    fn vm_slot_pre_post_inc_dec() {
9436        let mut c = Chunk::new();
9437        c.emit(Op::LoadInt(10), 1);
9438        c.emit(Op::DeclareScalarSlot(0, u16::MAX), 1);
9439        c.emit(Op::PostIncSlot(0), 1);
9440        c.emit(Op::Pop, 1);
9441        c.emit(Op::GetScalarSlot(0), 1);
9442        c.emit(Op::Halt, 1);
9443        assert_eq!(run_chunk(&c).expect("vm").to_int(), 11);
9444
9445        let mut c = Chunk::new();
9446        c.emit(Op::LoadInt(0), 1);
9447        c.emit(Op::DeclareScalarSlot(0, u16::MAX), 1);
9448        c.emit(Op::PreIncSlot(0), 1);
9449        c.emit(Op::Halt, 1);
9450        assert_eq!(run_chunk(&c).expect("vm").to_int(), 1);
9451
9452        let mut c = Chunk::new();
9453        c.emit(Op::LoadInt(5), 1);
9454        c.emit(Op::DeclareScalarSlot(0, u16::MAX), 1);
9455        c.emit(Op::PreDecSlot(0), 1);
9456        c.emit(Op::Halt, 1);
9457        assert_eq!(run_chunk(&c).expect("vm").to_int(), 4);
9458
9459        let mut c = Chunk::new();
9460        c.emit(Op::LoadInt(3), 1);
9461        c.emit(Op::DeclareScalarSlot(0, u16::MAX), 1);
9462        c.emit(Op::PostDecSlot(0), 1);
9463        c.emit(Op::Pop, 1);
9464        c.emit(Op::GetScalarSlot(0), 1);
9465        c.emit(Op::Halt, 1);
9466        assert_eq!(run_chunk(&c).expect("vm").to_int(), 2);
9467    }
9468
9469    #[test]
9470    fn vm_str_eq_ne_heap_strings() {
9471        let mut c = Chunk::new();
9472        let a = c.add_constant(PerlValue::string("same".into()));
9473        let b = c.add_constant(PerlValue::string("same".into()));
9474        c.emit(Op::LoadConst(a), 1);
9475        c.emit(Op::LoadConst(b), 1);
9476        c.emit(Op::StrEq, 1);
9477        c.emit(Op::Halt, 1);
9478        assert_eq!(run_chunk(&c).expect("vm").to_int(), 1);
9479
9480        let mut c = Chunk::new();
9481        let a = c.add_constant(PerlValue::string("a".into()));
9482        let b = c.add_constant(PerlValue::string("b".into()));
9483        c.emit(Op::LoadConst(a), 1);
9484        c.emit(Op::LoadConst(b), 1);
9485        c.emit(Op::StrNe, 1);
9486        c.emit(Op::Halt, 1);
9487        assert_eq!(run_chunk(&c).expect("vm").to_int(), 1);
9488    }
9489
9490    #[test]
9491    fn vm_num_eq_ine() {
9492        let mut c = Chunk::new();
9493        c.emit(Op::LoadInt(1), 1);
9494        c.emit(Op::LoadInt(1), 1);
9495        c.emit(Op::NumEq, 1);
9496        c.emit(Op::Halt, 1);
9497        assert_eq!(run_chunk(&c).expect("vm").to_int(), 1);
9498
9499        let mut c = Chunk::new();
9500        c.emit(Op::LoadInt(1), 1);
9501        c.emit(Op::LoadInt(2), 1);
9502        c.emit(Op::NumNe, 1);
9503        c.emit(Op::Halt, 1);
9504        assert_eq!(run_chunk(&c).expect("vm").to_int(), 1);
9505    }
9506
9507    #[test]
9508    fn vm_num_ordering() {
9509        for (a, b, op, want) in [
9510            (1i64, 2i64, Op::NumLt, 1),
9511            (3i64, 2i64, Op::NumGt, 1),
9512            (2i64, 2i64, Op::NumLe, 1),
9513            (2i64, 2i64, Op::NumGe, 1),
9514        ] {
9515            let mut c = Chunk::new();
9516            c.emit(Op::LoadInt(a), 1);
9517            c.emit(Op::LoadInt(b), 1);
9518            c.emit(op, 1);
9519            c.emit(Op::Halt, 1);
9520            assert_eq!(run_chunk(&c).expect("vm").to_int(), want);
9521        }
9522    }
9523
9524    #[test]
9525    fn vm_concat_and_str_cmp() {
9526        let mut c = Chunk::new();
9527        let i1 = c.add_constant(PerlValue::string("a".into()));
9528        let i2 = c.add_constant(PerlValue::string("b".into()));
9529        c.emit(Op::LoadConst(i1), 1);
9530        c.emit(Op::LoadConst(i2), 1);
9531        c.emit(Op::Concat, 1);
9532        c.emit(Op::Halt, 1);
9533        assert_eq!(run_chunk(&c).expect("vm").to_string(), "ab");
9534
9535        let mut c = Chunk::new();
9536        let i1 = c.add_constant(PerlValue::string("a".into()));
9537        let i2 = c.add_constant(PerlValue::string("b".into()));
9538        c.emit(Op::LoadConst(i1), 1);
9539        c.emit(Op::LoadConst(i2), 1);
9540        c.emit(Op::StrCmp, 1);
9541        c.emit(Op::Halt, 1);
9542        let v = run_chunk(&c).expect("vm");
9543        assert!(v.to_int() < 0);
9544    }
9545
9546    #[test]
9547    fn vm_log_not() {
9548        let mut c = Chunk::new();
9549        c.emit(Op::LoadInt(0), 1);
9550        c.emit(Op::LogNot, 1);
9551        c.emit(Op::Halt, 1);
9552        assert_eq!(run_chunk(&c).expect("vm").to_int(), 1);
9553    }
9554
9555    #[test]
9556    fn vm_bit_and_or_xor_not() {
9557        let mut c = Chunk::new();
9558        c.emit(Op::LoadInt(0b1100), 1);
9559        c.emit(Op::LoadInt(0b1010), 1);
9560        c.emit(Op::BitAnd, 1);
9561        c.emit(Op::Halt, 1);
9562        assert_eq!(run_chunk(&c).expect("vm").to_int(), 0b1000);
9563
9564        let mut c = Chunk::new();
9565        c.emit(Op::LoadInt(0b1100), 1);
9566        c.emit(Op::LoadInt(0b1010), 1);
9567        c.emit(Op::BitOr, 1);
9568        c.emit(Op::Halt, 1);
9569        assert_eq!(run_chunk(&c).expect("vm").to_int(), 0b1110);
9570
9571        let mut c = Chunk::new();
9572        c.emit(Op::LoadInt(0b1100), 1);
9573        c.emit(Op::LoadInt(0b1010), 1);
9574        c.emit(Op::BitXor, 1);
9575        c.emit(Op::Halt, 1);
9576        assert_eq!(run_chunk(&c).expect("vm").to_int(), 0b0110);
9577
9578        let mut c = Chunk::new();
9579        c.emit(Op::LoadInt(0), 1);
9580        c.emit(Op::BitNot, 1);
9581        c.emit(Op::Halt, 1);
9582        assert!((run_chunk(&c).expect("vm").to_int() & 0xFF) != 0);
9583    }
9584
9585    #[test]
9586    fn vm_shl_shr() {
9587        let mut c = Chunk::new();
9588        c.emit(Op::LoadInt(1), 1);
9589        c.emit(Op::LoadInt(3), 1);
9590        c.emit(Op::Shl, 1);
9591        c.emit(Op::Halt, 1);
9592        assert_eq!(run_chunk(&c).expect("vm").to_int(), 8);
9593
9594        let mut c = Chunk::new();
9595        c.emit(Op::LoadInt(16), 1);
9596        c.emit(Op::LoadInt(2), 1);
9597        c.emit(Op::Shr, 1);
9598        c.emit(Op::Halt, 1);
9599        assert_eq!(run_chunk(&c).expect("vm").to_int(), 4);
9600    }
9601
9602    #[test]
9603    fn vm_load_undef_float_constant() {
9604        let mut c = Chunk::new();
9605        c.emit(Op::LoadUndef, 1);
9606        c.emit(Op::Halt, 1);
9607        assert!(run_chunk(&c).expect("vm").is_undef());
9608
9609        let mut c = Chunk::new();
9610        c.emit(Op::LoadFloat(2.5), 1);
9611        c.emit(Op::Halt, 1);
9612        assert!((run_chunk(&c).expect("vm").to_number() - 2.5).abs() < 1e-9);
9613    }
9614
9615    #[test]
9616    fn vm_jump_skips_ops() {
9617        let mut c = Chunk::new();
9618        let j = c.emit(Op::Jump(0), 1);
9619        c.emit(Op::LoadInt(1), 1);
9620        c.emit(Op::LoadInt(2), 1);
9621        c.emit(Op::Add, 1);
9622        c.patch_jump_here(j);
9623        c.emit(Op::LoadInt(40), 1);
9624        c.emit(Op::Halt, 1);
9625        assert_eq!(run_chunk(&c).expect("vm").to_int(), 40);
9626    }
9627
9628    #[test]
9629    fn vm_jump_if_false() {
9630        let mut c = Chunk::new();
9631        c.emit(Op::LoadInt(0), 1);
9632        let j = c.emit(Op::JumpIfFalse(0), 1);
9633        c.emit(Op::LoadInt(1), 1);
9634        c.emit(Op::Halt, 1);
9635        c.patch_jump_here(j);
9636        c.emit(Op::LoadInt(2), 1);
9637        c.emit(Op::Halt, 1);
9638        assert_eq!(run_chunk(&c).expect("vm").to_int(), 2);
9639    }
9640
9641    #[test]
9642    fn vm_call_builtin_defined() {
9643        let mut c = Chunk::new();
9644        c.emit(Op::LoadUndef, 1);
9645        c.emit(Op::CallBuiltin(BuiltinId::Defined as u16, 1), 1);
9646        c.emit(Op::Halt, 1);
9647        assert_eq!(run_chunk(&c).expect("vm").to_int(), 0);
9648    }
9649
9650    #[test]
9651    fn vm_call_builtin_length_string() {
9652        let mut c = Chunk::new();
9653        let idx = c.add_constant(PerlValue::string("abc".into()));
9654        c.emit(Op::LoadConst(idx), 1);
9655        c.emit(Op::CallBuiltin(BuiltinId::Length as u16, 1), 1);
9656        c.emit(Op::Halt, 1);
9657        assert_eq!(run_chunk(&c).expect("vm").to_int(), 3);
9658    }
9659
9660    #[test]
9661    fn vm_make_array_two() {
9662        let mut c = Chunk::new();
9663        c.emit(Op::LoadInt(1), 1);
9664        c.emit(Op::LoadInt(2), 1);
9665        c.emit(Op::MakeArray(2), 1);
9666        c.emit(Op::Halt, 1);
9667        let v = run_chunk(&c).expect("vm");
9668        let a = v.as_array_vec().expect("array");
9669        assert_eq!(a.len(), 2);
9670        assert_eq!(a[0].to_int(), 1);
9671        assert_eq!(a[1].to_int(), 2);
9672    }
9673
9674    #[test]
9675    fn vm_spaceship() {
9676        let mut c = Chunk::new();
9677        c.emit(Op::LoadInt(1), 1);
9678        c.emit(Op::LoadInt(2), 1);
9679        c.emit(Op::Spaceship, 1);
9680        c.emit(Op::Halt, 1);
9681        assert_eq!(run_chunk(&c).expect("vm").to_int(), -1);
9682    }
9683
9684    #[test]
9685    fn compiled_try_catch_catches_die_via_vm() {
9686        let program = crate::parse(
9687            r#"
9688        try {
9689            die "boom";
9690        } catch ($err) {
9691            42;
9692        }
9693    "#,
9694        )
9695        .expect("parse");
9696        let chunk = crate::compiler::Compiler::new()
9697            .compile_program(&program)
9698            .expect("compile");
9699        let tp = chunk
9700            .ops
9701            .iter()
9702            .position(|o| matches!(o, Op::TryPush { .. }))
9703            .expect("TryPush op");
9704        match &chunk.ops[tp] {
9705            Op::TryPush {
9706                catch_ip, after_ip, ..
9707            } => {
9708                assert_ne!(*catch_ip, 0, "catch_ip must be patched");
9709                assert_ne!(*after_ip, 0, "after_ip must be patched");
9710            }
9711            _ => unreachable!(),
9712        }
9713        let mut interp = Interpreter::new();
9714        let mut vm = VM::new(&chunk, &mut interp);
9715        vm.set_jit_enabled(false);
9716        let v = vm.execute().expect("vm should catch die");
9717        assert_eq!(v.to_int(), 42);
9718    }
9719}