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