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