Skip to main content

stryke/
vm.rs

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