Skip to main content

stryke/
vm.rs

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