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