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