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