Skip to main content

candela_vm/
vm.rs

1// This file is derived from keel (https://github.com/horacehoff/keel),
2// Copyright 2026 Horace Hoff, licensed under the Apache License, Version 2.0.
3// It has been modified by the candela authors. See the NOTICE file.
4use crate::captured_output::out;
5use crate::captured_output::outln;
6use crate::data::Data;
7use crate::data::DataHash;
8use crate::data::FALSE;
9use crate::data::FUNCTION_TEXT;
10use crate::data::NULL;
11use crate::data::TRUE;
12use crate::data::format_float;
13use crate::embed::HostDispatch;
14use crate::embed::Value;
15use crate::errors::ErrType;
16use crate::errors::ErrorCtx;
17use crate::errors::RuntimeError;
18use crate::errors::call_site_name;
19use crate::gc::GcState;
20use crate::gc::LIST_ENTRY_BYTES;
21use crate::gc::MAP_ENTRY_BYTES;
22use crate::gc::MarkBits;
23use crate::gc::alloc_array;
24use crate::gc::alloc_map;
25use crate::gc::rescan_list;
26use crate::gc::rescan_map;
27use crate::gc::shade_overwritten;
28use crate::instr::Instr;
29use crate::instr::LibFunc;
30use crate::instr::LibFuncVoid;
31use crate::instr::type_code;
32#[cfg(not(target_arch = "wasm32"))]
33use crate::native::Entry;
34use crate::native::NativeCode;
35#[cfg(not(target_arch = "wasm32"))]
36use crate::native::NativeCtx;
37#[cfg(not(target_arch = "wasm32"))]
38use crate::native::abi;
39use crate::rt::DataType;
40use crate::rt::DynamicLibFn;
41use crate::rt::EnumType;
42use crate::rt::ErrorCatch;
43use crate::rt::HostFnSig;
44use crate::rt::Pools;
45use crate::rt::Span;
46use crate::rt::Struct;
47use indexmap::IndexMap;
48use lexical_core::FormattedSize;
49use memchr::memmem;
50use smol_strc::ToSmolStr;
51use std::cell::Cell;
52use std::fs;
53use std::hash::BuildHasherDefault;
54use std::hint::cold_path;
55use std::hint::unreachable_unchecked;
56use std::io::Write;
57use std::ops::Index;
58use std::ops::IndexMut;
59
60#[path = "ffi.rs"]
61mod ffi;
62
63#[cfg(target_arch = "wasm32")]
64use crate::errors::wasm_error;
65
66/// How many calls may stand at once. A recursion that never returns pushes a
67/// frame per call, so an unbounded one grows the frame stack until the
68/// operating system kills the process; the call past this many frames raises
69/// `call_depth_exceeded` instead. The limit sits far above what a working
70/// recursion needs, and the frames a run this deep stands on stay within a few
71/// hundred megabytes even for a function that saves many registers per level.
72pub const CALL_DEPTH_LIMIT: usize = 1_000_000;
73
74pub type ObjectPool = Pool<Vec<Data>>;
75
76/// A candela map: `Data`-keyed, hashed by the raw value bits, and walked in the
77/// order its keys were first inserted. Every surface that walks a map (a `for`
78/// loop, `keys`, `values`, printing, json) reads it in that one order, so a
79/// program that builds a map the same way twice prints it the same way twice.
80pub type CandelaMap = IndexMap<Data, Data, BuildHasherDefault<DataHash>>;
81pub type MapPool = Pool<CandelaMap>;
82
83/// Whether two values are the same as a map key or a list element: the same
84/// value, or two strings with the same text. A string longer than six bytes
85/// lives in the string pool, and the same text can sit in more than one slot.
86#[inline(always)]
87#[must_use]
88pub fn same_key(a: Data, b: Data, strings: &StringPool) -> bool {
89    a == b || (a.is_large_str() && b.is_large_str() && a.as_str(strings) == b.as_str(strings))
90}
91
92/// A key as a map looks it up, matching a stored key by [`same_key`]. It
93/// hashes as [`Data::as_map_key`] files the key, so one that matches sits in
94/// the bucket this looks in.
95pub struct KeyByText<'a> {
96    pub key: Data,
97    pub strings: &'a StringPool,
98}
99
100impl std::hash::Hash for KeyByText<'_> {
101    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
102        self.key.as_map_key(self.strings).hash(state);
103    }
104}
105
106impl indexmap::Equivalent<Data> for KeyByText<'_> {
107    fn equivalent(&self, stored: &Data) -> bool {
108        same_key(self.key, *stored, self.strings)
109    }
110}
111
112/// Puts `value` under `key`, replacing the value of a key with the same text
113/// if the map holds one, and answers the value it replaced. Every insert goes
114/// through here, so every key a map holds is filed by [`Data::as_map_key`].
115pub fn map_insert(
116    map: &mut CandelaMap,
117    key: Data,
118    value: Data,
119    strings: &StringPool,
120) -> Option<Data> {
121    if let Some(slot) = map.get_mut(&KeyByText { key, strings }) {
122        return Some(std::mem::replace(slot, value));
123    }
124    map.insert(key.as_map_key(strings), value);
125    None
126}
127
128/// The character count a slot has not been asked for yet.
129const UNCOUNTED: u32 = u32::MAX;
130/// The slot id a cursor that points at no string carries.
131const NO_STRING: u32 = u32::MAX;
132
133/// The most bytes a freed string slot keeps room for.
134const KEEP_FREED_STRING_CAPACITY: usize = 256;
135
136/// Where a walk over a pooled string stopped.
137#[derive(Clone, Copy)]
138struct StrCursor {
139    id: u32,
140    char_idx: u32,
141    byte_off: u32,
142}
143
144/// A cursor standing on no string at all.
145const NO_CURSOR: StrCursor = StrCursor {
146    id: NO_STRING,
147    char_idx: 0,
148    byte_off: 0,
149};
150
151/// One pooled string and what has been worked out about it. The count sits
152/// next to the string's own header so reading both costs one cache line.
153struct PooledStr {
154    text: String,
155    char_len: Cell<u32>,
156}
157
158/// Every string too long to sit inside a [`Data`], with the character count of
159/// each one remembered.
160///
161/// A position on a string counts characters, so an index or a slice has to find
162/// where character `i` starts. Counting a long string's characters for every
163/// one of those would turn a loop over it into quadratic work, so a slot's
164/// count is taken once and kept until the slot holds a different string. A
165/// string whose character count equals its byte length holds nothing but
166/// single-byte characters, and on it a character position is a byte position;
167/// that is the route indexing and slicing take. The cursor remembers where the
168/// last walk stopped, so stepping through a multi-byte string forwards resumes
169/// there instead of restarting at its first byte.
170///
171/// The pool also carries the collector's marks for its strings. Interning a
172/// string hands out a slot it found by its text, and the slot it finds has to
173/// count as reached by a cycle in progress, which is what [`Self::shade`] does
174/// without the interning caller needing the collector.
175pub struct StringPool {
176    strings: Vec<PooledStr>,
177    cursor: Cell<StrCursor>,
178    marks: MarkBits,
179    /// The pool's length when the cycle in progress began. A slot at or past
180    /// it was allocated since, and counts as marked.
181    mark_limit: u32,
182}
183
184impl Default for StringPool {
185    fn default() -> Self {
186        Self::with_capacity(0)
187    }
188}
189
190impl StringPool {
191    #[must_use]
192    pub fn with_capacity(capacity: usize) -> Self {
193        Self {
194            strings: Vec::with_capacity(capacity),
195            cursor: Cell::new(NO_CURSOR),
196            marks: MarkBits::default(),
197            mark_limit: 0,
198        }
199    }
200    #[must_use]
201    pub fn from_strings(strings: Vec<String>) -> Self {
202        Self {
203            strings: strings
204                .into_iter()
205                .map(|text| PooledStr {
206                    text,
207                    char_len: Cell::new(UNCOUNTED),
208                })
209                .collect(),
210            cursor: Cell::new(NO_CURSOR),
211            marks: MarkBits::default(),
212            mark_limit: 0,
213        }
214    }
215    #[inline(always)]
216    #[must_use]
217    pub const fn len(&self) -> usize {
218        self.strings.len()
219    }
220    #[inline(always)]
221    #[must_use]
222    pub const fn is_empty(&self) -> bool {
223        self.strings.is_empty()
224    }
225    #[inline(always)]
226    pub fn push(&mut self, value: String) {
227        self.strings.push(PooledStr {
228            text: value,
229            char_len: Cell::new(UNCOUNTED),
230        });
231    }
232    #[inline(always)]
233    pub fn iter(&self) -> impl Iterator<Item = &String> {
234        self.strings.iter().map(|s| &s.text)
235    }
236    /// A slot to write a different string into. What was worked out about the
237    /// slot describes the string being replaced, so it goes.
238    #[inline(always)]
239    pub fn get_mut(&mut self, index: usize) -> &mut String {
240        if self.cursor.get().id as usize == index {
241            self.cursor.set(NO_CURSOR);
242        }
243        let slot = unsafe { self.strings.get_unchecked_mut(index) };
244        slot.char_len.set(UNCOUNTED);
245        &mut slot.text
246    }
247    /// Starts a cycle's marks: none, over the slots the pool has now.
248    pub(crate) fn begin_marking(&mut self) {
249        self.marks.reset(self.len());
250        self.mark_limit = self.len() as u32;
251    }
252    /// Ends a cycle's marks. Every slot counts as marked until the next cycle
253    /// begins, so shading one is a no-op.
254    pub(crate) const fn end_marking(&mut self) {
255        self.mark_limit = 0;
256    }
257    /// Marks slot `id` as reached by the cycle in progress.
258    #[inline(always)]
259    pub(crate) fn shade(&mut self, id: usize) {
260        if id < self.mark_limit as usize {
261            self.marks.insert(id);
262        }
263    }
264    /// [`shade`](Self::shade) for the trace: answers the bytes the string
265    /// holds when this marked it, and nothing when it was marked already or
266    /// was allocated since the cycle began.
267    #[inline(always)]
268    pub(crate) fn shade_counted(&mut self, id: usize) -> usize {
269        if id < self.mark_limit as usize && !self.marks.insert(id) {
270            unsafe { self.strings.get_unchecked(id).text.capacity() }
271        } else {
272            0
273        }
274    }
275    /// The pool's length when the cycle in progress began.
276    pub(crate) const fn mark_limit(&self) -> usize {
277        self.mark_limit as usize
278    }
279    /// The cycle's marks.
280    pub(crate) const fn marks(&self) -> &MarkBits {
281        &self.marks
282    }
283    /// Empties a slot the collector freed. A long string's buffer goes back
284    /// to the allocator rather than waiting in a dead slot for the next
285    /// string written there.
286    pub fn release(&mut self, index: usize) {
287        let text = self.get_mut(index);
288        if text.capacity() > KEEP_FREED_STRING_CAPACITY {
289            *text = String::new();
290        } else {
291            text.clear();
292        }
293    }
294    /// The character count of the string in `id`, walked once and then
295    /// remembered on the slot.
296    #[inline(always)]
297    #[must_use]
298    pub fn char_len(&self, id: usize) -> usize {
299        let slot = unsafe { self.strings.get_unchecked(id) };
300        let remembered = slot.char_len.get();
301        if remembered == UNCOUNTED {
302            count_and_remember(slot)
303        } else {
304            remembered as usize
305        }
306    }
307    /// Whether every character in `id` takes a single byte, answered from the
308    /// remembered count rather than a second walk.
309    #[inline(always)]
310    #[must_use]
311    pub fn is_ascii(&self, id: usize) -> bool {
312        self.char_len(id) == self[id].len()
313    }
314    /// The byte offset character `char_idx` starts at. The string has at least
315    /// `char_idx` characters; at exactly that many the answer is its byte
316    /// length, which is what a slice bound on the end of the string asks for.
317    #[must_use]
318    pub fn byte_offset(&self, id: usize, char_idx: usize) -> usize {
319        let bytes = self[id].as_bytes();
320        let cursor = self.cursor.get();
321        let (mut at_char, mut at_byte) =
322            if cursor.id as usize == id && cursor.char_idx as usize <= char_idx {
323                (cursor.char_idx as usize, cursor.byte_off as usize)
324            } else {
325                (0, 0)
326            };
327        while at_char < char_idx {
328            at_byte += utf8_width(bytes[at_byte]);
329            at_char += 1;
330        }
331        self.cursor.set(StrCursor {
332            id: id as u32,
333            char_idx: at_char as u32,
334            byte_off: at_byte as u32,
335        });
336        at_byte
337    }
338}
339
340impl Index<usize> for StringPool {
341    type Output = String;
342    #[inline(always)]
343    fn index(&self, index: usize) -> &Self::Output {
344        unsafe { &self.strings.get_unchecked(index).text }
345    }
346}
347
348/// How many bytes the character starting with `first` takes. A byte below
349/// `0x80` stands for itself; every other character says how many bytes it
350/// spans in the leading one bits of its first byte.
351#[inline(always)]
352pub(crate) const fn utf8_width(first: u8) -> usize {
353    let leading = first.leading_ones() as usize;
354    if leading == 0 { 1 } else { leading }
355}
356
357/// Counts a slot's characters the first time anything asks, which happens once
358/// per string. Out of line so the instructions that read the count stay small.
359#[cold]
360#[inline(never)]
361fn count_and_remember(slot: &PooledStr) -> usize {
362    let counted = count_chars(&slot.text);
363    if counted < UNCOUNTED as usize {
364        slot.char_len.set(counted as u32);
365    }
366    counted
367}
368
369/// The number of characters in `s`. A continuation byte matches `0b10xx_xxxx`,
370/// which as a signed byte is below `-0x40`; every other byte starts a
371/// character.
372#[must_use]
373pub(crate) fn count_chars(s: &str) -> usize {
374    s.as_bytes().iter().filter(|&&b| (b as i8) >= -0x40).count()
375}
376
377/// The byte offset character `char_idx` starts at in `s`, for a `char_idx` no
378/// greater than the number of characters in `s`.
379#[inline(always)]
380#[must_use]
381pub(crate) fn char_byte_offset(s: &str, char_idx: usize) -> usize {
382    let bytes = s.as_bytes();
383    let mut at_byte = 0;
384    for _ in 0..char_idx {
385        at_byte += utf8_width(bytes[at_byte]);
386    }
387    at_byte
388}
389
390/// Compares two values with the string-comparison instructions.
391///
392/// The compiler emits these whenever either operand is statically a `string`,
393/// so the other operand can still turn out to be something else at run time:
394/// a parameter left un-annotated, or a value the checker only knows as `any`.
395/// Reading a non-string as a string is not meaningful, so a mismatched pair is
396/// unequal. Two values that are neither strings fall back to identity, which is
397/// how the plain equality instruction compares them.
398#[inline(always)]
399fn str_eq(x: Data, y: Data, string_pool: &StringPool) -> bool {
400    if x.is_string() && y.is_string() {
401        x.as_str(string_pool) == y.as_str(string_pool)
402    } else {
403        x == y
404    }
405}
406
407/// Whether `count` is a number of bits an `int` can be shifted by.
408///
409/// `int` holds 64 bits, so a wider shift, or a negative one, names no result.
410/// The compiler asks the same question of a count written as a literal, so that
411/// one is reported before the program runs.
412#[inline(always)]
413#[must_use]
414pub const fn shift_count_in_range(count: i64) -> bool {
415    count >= 0 && count < i64::BITS as i64
416}
417
418/// Whether `v` has the type [`type_code`] `code` names.
419fn has_type_code(v: Data, code: Data, obj_pool: &ObjectPool, map_pool: &MapPool) -> bool {
420    // A list entry checks a collection and what it holds.
421    if code.is_array() {
422        let entry = &obj_pool[code.as_array()];
423        let held = |codes: Data, item: &Data| {
424            let codes = &obj_pool[codes.as_array()];
425            codes.is_empty()
426                || codes
427                    .iter()
428                    .any(|code| has_type_code(*item, *code, obj_pool, map_pool))
429        };
430        return match entry[0].as_int() {
431            type_code::LIST => {
432                v.is_array()
433                    && !v.is_function()
434                    && obj_pool[v.as_array()]
435                        .iter()
436                        .all(|item| held(entry[1], item))
437            }
438            type_code::MAP => {
439                v.is_map()
440                    && map_pool[v.as_map()]
441                        .iter()
442                        .all(|(key, value)| held(entry[1], key) && held(entry[2], value))
443            }
444            _ => false,
445        };
446    }
447    let code = code.as_int();
448    let id = (code >> type_code::KIND_BITS) as u16;
449    match code & ((1 << type_code::KIND_BITS) - 1) {
450        type_code::INT => v.is_int(),
451        type_code::FLOAT => v.is_float(),
452        type_code::STRING => v.is_string(),
453        type_code::BOOL => v.is_bool(),
454        type_code::NULL => v.is_null(),
455        type_code::LIST => v.is_array() && !v.is_function(),
456        type_code::MAP => v.is_map(),
457        type_code::FUNCTION => v.is_function(),
458        type_code::STRUCT => v.is_struct() && v.struct_type_id() == id,
459        type_code::ENUM => v.is_enum() && v.enum_type_id() == id,
460        _ => false,
461    }
462}
463
464/// The types a list of [`type_code`]s names, as a report writes them.
465#[cold]
466fn type_codes_text(
467    codes: &[Data],
468    obj_pool: &ObjectPool,
469    structs: &[Struct],
470    enums: &[EnumType],
471) -> String {
472    let names: Vec<String> = codes
473        .iter()
474        .map(|code| {
475            // What a position of a collection holds: `any` where nothing is
476            // checked, and a union in parentheses where one has to be.
477            let held = |codes: Data| {
478                let codes = &obj_pool[codes.as_array()];
479                if codes.is_empty() {
480                    "any".to_owned()
481                } else {
482                    type_codes_text(codes, obj_pool, structs, enums)
483                }
484            };
485            if code.is_array() {
486                let entry = &obj_pool[code.as_array()];
487                return if entry[0].as_int() == type_code::MAP {
488                    format!("{{{}: {}}}", held(entry[1]), held(entry[2]))
489                } else {
490                    let elements = held(entry[1]);
491                    if elements.contains('|') {
492                        format!("({elements})[]")
493                    } else {
494                        format!("{elements}[]")
495                    }
496                };
497            }
498            let code = code.as_int();
499            let id = (code >> type_code::KIND_BITS) as usize;
500            match code & ((1 << type_code::KIND_BITS) - 1) {
501                type_code::INT => "int",
502                type_code::FLOAT => "float",
503                type_code::STRING => "string",
504                type_code::BOOL => "bool",
505                type_code::NULL => "null",
506                type_code::LIST => "list",
507                type_code::MAP => "map",
508                type_code::FUNCTION => "function",
509                type_code::STRUCT => structs.get(id).map_or("struct", |s| s.name.as_str()),
510                type_code::ENUM => enums.get(id).map_or("enum", |e| e.name.as_str()),
511                _ => "value",
512            }
513            .to_owned()
514        })
515        .collect();
516    names.join("|")
517}
518
519fn obj_eq(
520    x: Data,
521    y: Data,
522    obj_pool: &ObjectPool,
523    map_pool: &MapPool,
524    string_pool: &StringPool,
525) -> bool {
526    if x == y {
527        return true;
528    }
529    if x.tag() != y.tag() {
530        return false;
531    }
532    if x.is_string() && y.is_string() {
533        return x.as_str(string_pool) == y.as_str(string_pool);
534    }
535    // Enum values compare like structs: `x.tag() == y.tag()` above already
536    // proved same enum type id, and element 0 of the pool entry is the variant
537    // tag, so an element-wise compare covers variant and payload together.
538    if (x.is_array() || x.is_struct() || x.is_enum())
539        && (y.is_array() || y.is_struct() || y.is_enum())
540    {
541        let x_obj = &obj_pool[x.as_array()];
542        let y_obj = &obj_pool[y.as_array()];
543        if x_obj.len() != y_obj.len() {
544            return false;
545        }
546        if x_obj == y_obj {
547            return true;
548        }
549        for (x, y) in x_obj.iter().zip(y_obj) {
550            if !obj_eq(*x, *y, obj_pool, map_pool, string_pool) {
551                return false;
552            }
553        }
554        return true;
555    }
556    if x.is_map() && y.is_map() {
557        let x_map = &map_pool[x.as_map()];
558        let y_map = &map_pool[y.as_map()];
559        if x_map.len() != y_map.len() {
560            return false;
561        }
562        if x_map == y_map {
563            return true;
564        }
565        for (k, v) in x_map {
566            if !y_map
567                .get(&KeyByText {
568                    key: *k,
569                    strings: string_pool,
570                })
571                .is_some_and(|y_v| obj_eq(*v, *y_v, obj_pool, map_pool, string_pool))
572            {
573                return false;
574            }
575        }
576        return true;
577    }
578    false
579}
580
581struct CallFrame {
582    return_addr: u16,
583    return_reg: u16,
584    /// The callsite whose live registers this frame pushed onto the recursion
585    /// stack, or `None` when the call saved nothing. `SaveFrame` fills it in;
586    /// every way out of that call reads it back, so the registers the call
587    /// overwrote are restored once, whether the callee returns a value or not.
588    saved_callsite: Option<u16>,
589}
590
591/// Winds the recursion stack back to where `handle`'s `try` began.
592///
593/// The call the `try` wrapped is the first frame the catch drops, and on its
594/// way in it saved the registers the resuming function still reads. That
595/// function carries on where the call left it, so it gets those registers
596/// back, the same values a return of the call would have handed over. The
597/// calls made below the abort never return, and the levels they saved go with
598/// them. A `try` that throws without calling anything has nothing above its
599/// own depth and leaves the stack alone.
600#[cold]
601#[inline(never)]
602fn unwind_to_catch(
603    handle: &ErrorCatch,
604    call_frames: &[CallFrame],
605    callsite_registers: &[Vec<u16>],
606    recursion_stack: &mut RegisterFile,
607    regs: Regs,
608) {
609    let Some(aborted) = call_frames.get(handle.call_frames_len as usize) else {
610        return;
611    };
612    let base = handle.recursion_len as usize;
613    // The aborted call saved nothing when it was an ordinary call, so the
614    // resuming function has its registers already and only the deeper levels
615    // go.
616    let saved_regs: &[u16] = match aborted.saved_callsite {
617        Some(callsite_id) => unsafe { callsite_registers.get_unchecked(callsite_id as usize) },
618        None => &[],
619    };
620    restore_registers(base, saved_regs, recursion_stack, regs);
621}
622
623/// Makes room on the call stack for one more frame, or raises
624/// `call_depth_exceeded` when the run already stands [`CALL_DEPTH_LIMIT`]
625/// calls deep. Answers `None` when the call can go ahead, or the instruction a
626/// catch resumes at.
627///
628/// The calls native code has standing below this run count towards the
629/// depth without being on the stack, so the stack stops short of the limit by
630/// that many, and the calls themselves test one thing: whether the stack is
631/// full.
632#[cold]
633#[inline(never)]
634fn frame_room(m: &mut Machine<'_>, i: usize, regs: Regs) -> Option<usize> {
635    let depth = m.call_frames.len();
636    let room = CALL_DEPTH_LIMIT - m.native_below;
637    if depth >= room {
638        let instr = m.instr_at(i);
639        return Some(catch_error(
640            ErrType::CallDepthExceeded(call_site_name(m.err_ctx, instr), CALL_DEPTH_LIMIT),
641            instr,
642            &mut m.failure,
643            &mut m.error_handles,
644            m.handles_floor,
645            &mut m.call_frames,
646            &mut m.args,
647            m.callsite_registers,
648            &mut m.recursion_stack,
649            regs,
650            m.r,
651            m.obj_pool,
652            m.map_pool,
653            m.str_pool,
654            m.gc,
655        ));
656    }
657    m.call_frames.reserve_exact(depth.max(64).min(room - depth));
658    None
659}
660
661/// Makes room in a full list for one more element, and tells the collector
662/// the bytes the list grew by. Out of line, so a push that has room pays one
663/// test.
664#[cold]
665#[inline(never)]
666fn grow_list(list: &mut Vec<Data>, gc: &mut GcState) {
667    let before = list.capacity();
668    list.reserve(1);
669    gc.note_bytes((list.capacity() - before) * LIST_ENTRY_BYTES);
670}
671
672/// Inserts into a map through [`map_insert`], and tells the collector the
673/// bytes the map grew by when it had to.
674#[inline(always)]
675fn map_insert_counted(
676    map: &mut CandelaMap,
677    key: Data,
678    value: Data,
679    strings: &StringPool,
680    gc: &mut GcState,
681) -> Option<Data> {
682    let before = map.capacity();
683    let old = map_insert(map, key, value, strings);
684    if map.capacity() != before {
685        gc.note_bytes(map.capacity().saturating_sub(before) * MAP_ENTRY_BYTES);
686    }
687    old
688}
689
690/// Makes room on the recursion stack for `count` more saved registers.
691#[cold]
692#[inline(never)]
693fn grow_recursion_stack(stack: &mut Vec<Data>, count: usize) {
694    stack.reserve(count);
695}
696
697/// Hands a run-time error to the innermost `try` that is still open. When none
698/// is, it records the error in `failure` and answers [`FAILED`], which ends the
699/// run. A `try` below `floor` belongs to a run further out, which native code
700/// separates from this one; the error travels there as a failure of this run.
701///
702/// A caught error unwinds the calls the `try` wrapped, puts the error's name in
703/// the catch's register, and answers the instruction the catch starts at. The
704/// message is built while the throwing frame is still standing: a thrown value
705/// the unwind is about to overwrite is still a garbage-collection root here.
706///
707/// Every instruction that can fail calls this out of line, so the dispatch
708/// loop carries none of it.
709#[cold]
710#[inline(never)]
711fn catch_error(
712    err: ErrType,
713    instr: Instr,
714    failure: &mut Option<RuntimeError>,
715    error_handles: &mut Vec<ErrorCatch>,
716    floor: usize,
717    call_frames: &mut Vec<CallFrame>,
718    args: &mut Vec<u16>,
719    callsite_registers: &[Vec<u16>],
720    recursion_stack: &mut RegisterFile,
721    regs: Regs,
722    r: &RegisterFile,
723    obj_pool: &mut ObjectPool,
724    map_pool: &mut MapPool,
725    str_pool: &mut StringPool,
726    gc: &mut GcState,
727) -> usize {
728    if error_handles.len() <= floor {
729        *failure = Some(RuntimeError::new(instr, err));
730        return FAILED;
731    }
732    let caught = Data::string(
733        err.kind(),
734        obj_pool,
735        map_pool,
736        str_pool,
737        r,
738        recursion_stack,
739        gc,
740    );
741    resume_at_catch(
742        caught,
743        error_handles,
744        call_frames,
745        args,
746        callsite_registers,
747        recursion_stack,
748        regs,
749    )
750}
751
752/// Hands an error a run further in already recorded to the innermost `try`
753/// of this run that is still open, the way [`catch_error`] hands one raised
754/// here. When there is none, the error stays this run's failure.
755#[cold]
756#[inline(never)]
757#[cfg(not(target_arch = "wasm32"))]
758fn catch_failure(m: &mut Machine<'_>, error: RuntimeError) -> usize {
759    if m.error_handles.len() <= m.handles_floor {
760        m.failure = Some(error);
761        return FAILED;
762    }
763    let caught = Data::string(
764        error.code.as_str(),
765        m.obj_pool,
766        m.map_pool,
767        m.str_pool,
768        m.r,
769        &m.recursion_stack,
770        m.gc,
771    );
772    resume_at_catch(
773        caught,
774        &mut m.error_handles,
775        &mut m.call_frames,
776        &mut m.args,
777        m.callsite_registers,
778        &mut m.recursion_stack,
779        m.regs,
780    )
781}
782
783/// Unwinds to the innermost open `try`, puts `caught` in its register, and
784/// answers the instruction its catch starts at.
785fn resume_at_catch(
786    caught: Data,
787    error_handles: &mut Vec<ErrorCatch>,
788    call_frames: &mut Vec<CallFrame>,
789    args: &mut Vec<u16>,
790    callsite_registers: &[Vec<u16>],
791    recursion_stack: &mut RegisterFile,
792    mut regs: Regs,
793) -> usize {
794    let err_handle = error_handles.pop_unchecked();
795    unwind_to_catch(
796        &err_handle,
797        call_frames,
798        callsite_registers,
799        recursion_stack,
800        regs,
801    );
802    unsafe {
803        args.set_len(err_handle.args_len as usize);
804        call_frames.set_len(err_handle.call_frames_len as usize);
805    }
806    regs[err_handle.error_reg] = caught;
807    err_handle.catch_loc as usize
808}
809
810/// Puts back the registers `SaveFrame` pushed for `callsite` on the way into a
811/// call that is now returning, and drops them from the recursion stack. The top
812/// of the stack belongs to that call, so the saved values sit in the last
813/// `callsite_registers[callsite].len()` slots.
814#[inline(always)]
815fn restore_saved_registers(
816    callsite: u16,
817    callsite_registers: &[Vec<u16>],
818    recursion_stack: &mut RegisterFile,
819    regs: Regs,
820) {
821    let saved = unsafe { callsite_registers.get_unchecked(callsite as usize) };
822    restore_registers(
823        recursion_stack.len() - saved.len(),
824        saved,
825        recursion_stack,
826        regs,
827    );
828}
829
830/// Copies the `regs` a call saved at `base` back into the register file, and
831/// drops everything from `base` up off the recursion stack.
832#[inline(always)]
833fn restore_registers(
834    base: usize,
835    saved_regs: &[u16],
836    recursion_stack: &mut RegisterFile,
837    mut regs: Regs,
838) {
839    let saved = unsafe { recursion_stack.0.as_ptr().add(base) };
840    // Most calls save one register, and a loop costs more to set up than
841    // that one copy.
842    if let [reg] = saved_regs {
843        regs[*reg] = unsafe { Data::load(saved) };
844    } else {
845        for (k, reg) in saved_regs.iter().enumerate() {
846            regs[*reg] = unsafe { Data::load(saved.add(k)) };
847        }
848    }
849    unsafe {
850        recursion_stack.0.set_len(base);
851    }
852}
853
854pub trait UncheckedVecOps<T> {
855    fn pop_unchecked(&mut self) -> T;
856    /// Pushes onto a vector that has room for one more.
857    fn push_unchecked(&mut self, value: T);
858}
859pub trait UncheckedSliceOps<T: Copy> {
860    /// # Safety
861    ///
862    /// `src` must not be longer than `self`; the copy is unchecked in release
863    /// builds.
864    unsafe fn copy_from_slice_unchecked(&mut self, src: &[T]);
865}
866
867impl<T> UncheckedVecOps<T> for Vec<T> {
868    #[inline(always)]
869    fn pop_unchecked(&mut self) -> T {
870        debug_assert!(!self.is_empty());
871        let new_len = self.len() - 1;
872        unsafe {
873            self.set_len(new_len);
874            self.as_mut_ptr().add(new_len).read()
875        }
876    }
877    #[inline(always)]
878    fn push_unchecked(&mut self, value: T) {
879        debug_assert!(self.len() < self.capacity());
880        unsafe {
881            self.as_mut_ptr().add(self.len()).write(value);
882            self.set_len(self.len() + 1);
883        }
884    }
885}
886
887impl<T: Copy> UncheckedSliceOps<T> for [T] {
888    #[inline(always)]
889    unsafe fn copy_from_slice_unchecked(&mut self, src: &[T]) {
890        debug_assert_eq!(self.len(), src.len());
891        unsafe { core::ptr::copy_nonoverlapping(src.as_ptr(), self.as_mut_ptr(), self.len()) };
892    }
893}
894
895#[repr(transparent)]
896pub struct Pool<T>(pub Vec<T>);
897
898impl<T> Pool<T> {
899    #[inline(always)]
900    pub const fn len(&self) -> usize {
901        self.0.len()
902    }
903    #[inline(always)]
904    pub fn push(&mut self, value: T) {
905        self.0.push(value);
906    }
907    #[inline(always)]
908    pub fn with_capacity(capacity: usize) -> Self {
909        Self(Vec::with_capacity(capacity))
910    }
911    #[inline(always)]
912    pub fn iter(&self) -> std::slice::Iter<'_, T> {
913        self.0.iter()
914    }
915    #[inline(always)]
916    pub fn get_mut(&mut self, index: usize) -> &mut T {
917        unsafe { self.0.get_unchecked_mut(index) }
918    }
919    #[inline(always)]
920    pub const fn as_mut_ptr(&mut self) -> *mut T {
921        self.0.as_mut_ptr()
922    }
923    #[inline(always)]
924    pub fn split_at_mut(&mut self, mid: usize) -> (&mut [T], &mut [T]) {
925        unsafe { self.0.split_at_mut_unchecked(mid) }
926    }
927    #[inline(always)]
928    pub const fn is_empty(&self) -> bool {
929        self.0.is_empty()
930    }
931}
932
933impl<T> Index<usize> for Pool<T> {
934    type Output = T;
935    #[inline(always)]
936    fn index(&self, index: usize) -> &Self::Output {
937        unsafe { self.0.get_unchecked(index) }
938    }
939}
940impl<T> IndexMut<usize> for Pool<T> {
941    #[inline(always)]
942    fn index_mut(&mut self, index: usize) -> &mut T {
943        unsafe { self.0.get_unchecked_mut(index) }
944    }
945}
946
947#[repr(transparent)]
948pub struct RegisterFile(pub Vec<Data>);
949
950impl RegisterFile {
951    #[inline(always)]
952    pub const fn len(&self) -> usize {
953        self.0.len()
954    }
955    #[inline(always)]
956    pub const fn is_empty(&self) -> bool {
957        self.0.is_empty()
958    }
959}
960
961impl Index<u16> for RegisterFile {
962    type Output = Data;
963    #[inline(always)]
964    fn index(&self, index: u16) -> &Self::Output {
965        unsafe { self.0.get_unchecked(index as usize) }
966    }
967}
968impl IndexMut<u16> for RegisterFile {
969    #[inline(always)]
970    fn index_mut(&mut self, index: u16) -> &mut Data {
971        unsafe { self.0.get_unchecked_mut(index as usize) }
972    }
973}
974impl Index<usize> for RegisterFile {
975    type Output = Data;
976    #[inline(always)]
977    fn index(&self, index: usize) -> &Self::Output {
978        unsafe { self.0.get_unchecked(index) }
979    }
980}
981impl IndexMut<usize> for RegisterFile {
982    #[inline(always)]
983    fn index_mut(&mut self, index: usize) -> &mut Data {
984        unsafe { self.0.get_unchecked_mut(index) }
985    }
986}
987
988/// The registers of one run, addressed through the register file's base
989/// pointer. [`execute`] makes one at the start of a run and reads and writes
990/// every register through it; the file is never resized while a program runs,
991/// so the pointer stays valid to the end.
992#[derive(Clone, Copy)]
993pub struct Regs(*mut Data);
994
995impl Regs {
996    #[inline(always)]
997    fn new(file: &mut RegisterFile) -> Self {
998        Self(file.0.as_mut_ptr())
999    }
1000}
1001
1002impl Regs {
1003    /// The value in register `index`, read a word at a time (see
1004    /// [`Data::load`]). Every instruction that copies a whole value out of a
1005    /// register reads it this way.
1006    #[inline(always)]
1007    fn get(self, index: u16) -> Data {
1008        unsafe { Data::load(self.0.add(index as usize)) }
1009    }
1010}
1011
1012impl Index<u16> for Regs {
1013    type Output = Data;
1014    #[inline(always)]
1015    fn index(&self, index: u16) -> &Data {
1016        unsafe { &*self.0.add(index as usize) }
1017    }
1018}
1019impl IndexMut<u16> for Regs {
1020    #[inline(always)]
1021    fn index_mut(&mut self, index: u16) -> &mut Data {
1022        unsafe { &mut *self.0.add(index as usize) }
1023    }
1024}
1025
1026/// What [`run_cold`] answers when the program has stopped.
1027const HALTED: usize = usize::MAX;
1028/// What [`catch_error`] and [`run_cold`] answer when an error no `try` catches
1029/// has stopped the program. It sits just below [`HALTED`], so one comparison
1030/// tells either from an instruction index.
1031const FAILED: usize = usize::MAX - 1;
1032/// What [`run_cold`] answers when a run native code started on the
1033/// interpreter has returned to it.
1034const RETURNED: usize = usize::MAX - 2;
1035/// What a run answers when a panic in a host function has to unwind past
1036/// native code. The panic is held in [`Machine::panic`] and resumed once no
1037/// native frame stands between it and the host.
1038const PANICKED: usize = usize::MAX - 3;
1039
1040/// Frames of native code the stack keeps room for, at most: this many bytes
1041/// divided by the largest frame one of its functions takes. A call past them
1042/// carries on in the interpreter, whose frames live on the heap, so a deep
1043/// recursion never runs the thread out of stack.
1044#[cfg(not(target_arch = "wasm32"))]
1045const NATIVE_STACK_BYTES: usize = 256 << 10;
1046
1047/// The stack one trip from native code back into the interpreter takes, in
1048/// bytes: the helper and the interpreter's own frame.
1049#[cfg(not(target_arch = "wasm32"))]
1050const REENTRY_BYTES: usize = 4 << 10;
1051
1052/// Everything one run of [`execute`] works with besides the instruction index
1053/// and the registers.
1054///
1055/// The dispatch loop runs the instructions a program spends its time in, and
1056/// hands the rest to [`run_cold`] out of line. Both reach the pools, the call
1057/// stack and the rest through this one value, which leaves the loop little to
1058/// keep in machine registers besides the instruction pointer and the base of
1059/// the register file.
1060struct Machine<'a> {
1061    /// The register file, read by the collector to find what is still live.
1062    /// Instructions reach the registers through [`Regs`].
1063    r: &'a RegisterFile,
1064    regs: Regs,
1065    instructions: &'a [Instr],
1066    obj_pool: &'a mut ObjectPool,
1067    map_pool: &'a mut MapPool,
1068    str_pool: &'a mut StringPool,
1069    gc: &'a mut GcState,
1070    err_ctx: &'a ErrorCtx<'a>,
1071    callsite_registers: &'a [Vec<u16>],
1072    dyn_libs: &'a [DynamicLibFn],
1073    structs: &'a [Struct],
1074    enums: &'a [EnumType],
1075    host_sigs: &'a [HostFnSig],
1076    host_dispatch: &'a [HostDispatch],
1077    args: Vec<u16>,
1078    call_frames: Vec<CallFrame>,
1079    recursion_stack: RegisterFile,
1080    handle: crate::captured_output::OutputHandle,
1081    dyn_lib_args: Vec<u64>,
1082    host_call_args: Vec<Value>,
1083    keep_alive: Vec<Box<[u8]>>,
1084    error_handles: Vec<ErrorCatch>,
1085    /// The error that stopped the run, once one no `try` caught has.
1086    failure: Option<RuntimeError>,
1087    /// The program's machine code, when it carries some this machine runs.
1088    native: Option<&'a NativeCode>,
1089    /// What native code shares with this run.
1090    #[cfg(not(target_arch = "wasm32"))]
1091    ctx: NativeCtx,
1092    /// Calls native code has standing below the interpreter that are not on
1093    /// `call_frames`. They count towards the call depth all the same.
1094    native_below: usize,
1095    /// How many more native frames the stack keeps room for.
1096    #[cfg(not(target_arch = "wasm32"))]
1097    native_frames_left: usize,
1098    /// The `try`s below this index belong to a run further out than the one
1099    /// executing, which native code stands between.
1100    handles_floor: usize,
1101    /// A panic on its way out past native code.
1102    panic: Option<Box<dyn std::any::Any + Send>>,
1103}
1104
1105impl Machine<'_> {
1106    /// The instruction the program was compiled with at `i`, for an error to
1107    /// name: a call the native code load rewrote reads back as the call it
1108    /// was.
1109    #[inline(always)]
1110    fn instr_at(&self, i: usize) -> Instr {
1111        let current = unsafe { *self.instructions.get_unchecked(i) };
1112        match self.native {
1113            Some(native) => native.original(i, current),
1114            None => current,
1115        }
1116    }
1117}
1118
1119/// Runs `instructions` from `start` until the program halts.
1120///
1121/// # Errors
1122///
1123/// Returns the [`RuntimeError`] that stopped the program when one no `try`
1124/// caught was raised. The registers and pools are left as the error found
1125/// them.
1126#[allow(unused_unsafe)]
1127pub fn execute(
1128    instructions: &[Instr],
1129    r: &mut RegisterFile,
1130    Pools {
1131        objs: obj_pool,
1132        maps: map_pool,
1133        strings: str_pool,
1134        gc,
1135    }: &mut Pools,
1136    err_ctx: &ErrorCtx<'_>,
1137    callsite_registers: &[Vec<u16>],
1138    dyn_libs: &[DynamicLibFn],
1139    structs: &[Struct],
1140    enums: &[EnumType],
1141    allocated_arg_count: usize,
1142    allocated_call_depth: usize,
1143    // Embedding: `host` function signatures and the Rust closures they dispatch
1144    // to, both indexed by host-function id. Empty for the CLI/REPL/WASM paths.
1145    // Marshalling is driven by the runtime `Data` tag, so the signatures are
1146    // read only to name a function in a report (kept here, too, for future
1147    // per-argument coercion).
1148    host_sigs: &[HostFnSig],
1149    host_dispatch: &[HostDispatch],
1150    // Instruction index to begin execution at. `0` runs `main`; the embedding
1151    // `Program::call` passes the entry index of an appended call trampoline.
1152    start: usize,
1153    // The machine code the program was loaded with, if any.
1154    native: Option<&NativeCode>,
1155) -> Result<(), RuntimeError> {
1156    // Every register access goes through the file's base pointer, which stays
1157    // put for the whole run: nothing resizes the register file while a program
1158    // runs. Reading it through `r` instead would reload the pointer from the
1159    // file on every access.
1160    let regs = Regs::new(r);
1161    let recursion_capacity = allocated_call_depth * r.len();
1162    #[cfg(not(target_arch = "wasm32"))]
1163    let dylibs = native.map_or(std::ptr::null(), |n| n.dylibs.as_ptr());
1164    #[cfg(not(target_arch = "wasm32"))]
1165    let native_frames_left =
1166        native.map_or(0, |n| NATIVE_STACK_BYTES / (n.frame_bytes as usize).max(16));
1167    let mut m = Machine {
1168        r,
1169        regs,
1170        instructions,
1171        obj_pool,
1172        map_pool,
1173        str_pool,
1174        gc,
1175        err_ctx,
1176        callsite_registers,
1177        dyn_libs,
1178        structs,
1179        enums,
1180        host_sigs,
1181        host_dispatch,
1182        args: Vec::with_capacity(allocated_arg_count),
1183        call_frames: Vec::with_capacity(allocated_call_depth.clamp(1, CALL_DEPTH_LIMIT)),
1184        recursion_stack: RegisterFile(Vec::with_capacity(recursion_capacity)),
1185        handle: crate::captured_output::stdout(),
1186        dyn_lib_args: Vec::new(),
1187        host_call_args: Vec::new(),
1188        keep_alive: Vec::new(),
1189        error_handles: Vec::new(),
1190        failure: None,
1191        native,
1192        #[cfg(not(target_arch = "wasm32"))]
1193        ctx: NativeCtx {
1194            regs: regs.0,
1195            objs: std::ptr::null(),
1196            status: abi::STATUS_OK,
1197            err_at: 0,
1198            err_a: 0,
1199            err_b: 0,
1200            marking: 0,
1201            ret: [0; 2],
1202            ceiling: 0,
1203            segment_base: 0,
1204            dylibs,
1205            machine: std::ptr::null_mut(),
1206            shade: native_shade,
1207            print: native_print,
1208            call_vm: native_call_vm,
1209            push_roots: native_push_roots,
1210            pop_roots: native_pop_roots,
1211        },
1212        native_below: 0,
1213        #[cfg(not(target_arch = "wasm32"))]
1214        native_frames_left,
1215        handles_floor: 0,
1216        panic: None,
1217    };
1218
1219    #[cfg(not(target_arch = "wasm32"))]
1220    let stop = match native.and_then(|n| n.main) {
1221        Some(main) if start == 0 => run_native_main(&mut m, main),
1222        _ => run(&mut m, start),
1223    };
1224    // A browser has no memory it lets code run from, so nothing native ever
1225    // loads there.
1226    #[cfg(target_arch = "wasm32")]
1227    let stop = run(&mut m, start);
1228    if stop == PANICKED
1229        && let Some(payload) = m.panic.take()
1230    {
1231        std::panic::resume_unwind(payload);
1232    }
1233    m.failure.map_or(Ok(()), Err)
1234}
1235
1236/// [`run`] out of line, for a run native code starts. [`execute`] has the
1237/// loop inlined, where the machine is a local it keeps on its own stack.
1238#[inline(never)]
1239#[cfg(not(target_arch = "wasm32"))]
1240fn run_nested(m: &mut Machine<'_>, start: usize) -> usize {
1241    run(m, start)
1242}
1243
1244/// Runs the instructions from `start` until the program halts or fails, or,
1245/// in a run native code started, until the call it made returns. Answers
1246/// which: [`HALTED`], [`FAILED`], [`RETURNED`] or [`PANICKED`].
1247#[allow(unused_unsafe)]
1248#[inline(always)]
1249fn run(m: &mut Machine<'_>, start: usize) -> usize {
1250    let instructions = m.instructions;
1251    // The instruction running, as a pointer: the loop steps and jumps it
1252    // directly, and turns it back into an index only where one is recorded.
1253    let base = instructions.as_ptr();
1254    let mut ip = unsafe { base.add(start) };
1255    let mut regs = m.regs;
1256
1257    macro_rules! error_with_catch {
1258        ($err:expr) => {
1259            let resume = catch_error(
1260                $err,
1261                unsafe { *ip },
1262                &mut m.failure,
1263                &mut m.error_handles,
1264                m.handles_floor,
1265                &mut m.call_frames,
1266                &mut m.args,
1267                m.callsite_registers,
1268                &mut m.recursion_stack,
1269                regs,
1270                m.r,
1271                m.obj_pool,
1272                m.map_pool,
1273                m.str_pool,
1274                m.gc,
1275            );
1276            if resume == FAILED {
1277                return FAILED;
1278            }
1279            ip = unsafe { base.add(resume) };
1280            continue;
1281        };
1282    }
1283
1284    loop {
1285        match unsafe { *ip } {
1286            Instr::Jmp(size) => {
1287                ip = unsafe { ip.add(size as usize) };
1288                continue;
1289            }
1290            Instr::JmpBack(size) => {
1291                ip = unsafe { ip.sub(size as usize) };
1292                continue;
1293            }
1294            Instr::Mov(tgt, dest) => regs[dest] = regs.get(tgt),
1295            Instr::MovChain(dest, mid, src) => {
1296                regs[dest] = regs.get(mid);
1297                regs[mid] = regs.get(src);
1298            }
1299            Instr::SetInt(dest, n) => regs[dest] = Data::int(i64::from(n)),
1300            Instr::SetBool(b, dest) => regs[dest] = b.into(),
1301            Instr::CallFunc(new_loc, return_id) => {
1302                if m.call_frames.len() == m.call_frames.capacity()
1303                    && let Some(catch) = frame_room(m, index_of(base, ip), regs)
1304                {
1305                    if catch == FAILED {
1306                        return FAILED;
1307                    }
1308                    ip = unsafe { base.add(catch) };
1309                    continue;
1310                }
1311                m.call_frames.push_unchecked(CallFrame {
1312                    return_addr: index_of(base, ip) as u16,
1313                    return_reg: return_id,
1314                    saved_callsite: None,
1315                });
1316                ip = unsafe { base.add(new_loc as usize) };
1317                continue;
1318            }
1319            Instr::CallFuncRecursive(new_loc, _) => {
1320                ip = unsafe { base.add(new_loc as usize) };
1321                continue;
1322            }
1323            Instr::CallIndirect(callee) => {
1324                // The first slot of a function value is where its body starts.
1325                // The `SaveFrame` just above pushed the frame, so the jump is
1326                // all that is left to do.
1327                let value_id = regs[callee].as_array();
1328                ip = unsafe { base.add(m.obj_pool[value_id].get_unchecked(0).as_int() as usize) };
1329                continue;
1330            }
1331            Instr::VoidReturn => {
1332                // Nothing to return, but a recursive call still has to hand the
1333                // caller back the registers the call overwrote before jumping.
1334                let call_frame = m.call_frames.pop_unchecked();
1335                if let Some(callsite_id) = call_frame.saved_callsite {
1336                    restore_saved_registers(
1337                        callsite_id,
1338                        m.callsite_registers,
1339                        &mut m.recursion_stack,
1340                        regs,
1341                    );
1342                }
1343                ip = unsafe { base.add(call_frame.return_addr as usize) };
1344            }
1345            Instr::SaveFrame(relative_func_loc, return_register, callsite_id) => {
1346                if m.call_frames.len() == m.call_frames.capacity()
1347                    && let Some(catch) = frame_room(m, index_of(base, ip), regs)
1348                {
1349                    if catch == FAILED {
1350                        return FAILED;
1351                    }
1352                    ip = unsafe { base.add(catch) };
1353                    continue;
1354                }
1355                m.call_frames.push_unchecked(CallFrame {
1356                    return_addr: (index_of(base, ip) as u16) + relative_func_loc,
1357                    return_reg: return_register,
1358                    saved_callsite: Some(callsite_id),
1359                });
1360                let saved = unsafe { m.callsite_registers.get_unchecked(callsite_id as usize) };
1361                let stack = &mut m.recursion_stack.0;
1362                if stack.capacity() - stack.len() < saved.len() {
1363                    grow_recursion_stack(stack, saved.len());
1364                }
1365                unsafe {
1366                    let top = stack.as_mut_ptr().add(stack.len());
1367                    // Most calls save one register, and a loop costs more to
1368                    // set up than that one copy.
1369                    if let [reg] = saved[..] {
1370                        top.write(regs.get(reg));
1371                    } else {
1372                        for (k, &reg) in saved.iter().enumerate() {
1373                            top.add(k).write(regs.get(reg));
1374                        }
1375                    }
1376                    stack.set_len(stack.len() + saved.len());
1377                }
1378            }
1379            Instr::Return(tgt) => {
1380                // Pop the latest call frame, set the return value and jump back to the callsite
1381                let call_frame = m.call_frames.pop_unchecked();
1382                ip = unsafe { base.add(call_frame.return_addr as usize) };
1383                regs[call_frame.return_reg] = regs.get(tgt);
1384            }
1385            Instr::RecursiveReturn(tgt) => {
1386                let call_frame = m.call_frames.pop_unchecked();
1387                let temp = regs.get(tgt);
1388                if let Some(callsite_id) = call_frame.saved_callsite {
1389                    restore_saved_registers(
1390                        callsite_id,
1391                        m.callsite_registers,
1392                        &mut m.recursion_stack,
1393                        regs,
1394                    );
1395                }
1396                ip = unsafe { base.add(call_frame.return_addr as usize) };
1397                regs[call_frame.return_reg] = temp;
1398            }
1399            Instr::IsFalseJmp(cond_id, size) => {
1400                if regs[cond_id].is_false() {
1401                    ip = unsafe { ip.add(size as usize) };
1402                    continue;
1403                }
1404            }
1405            Instr::IsTrueJmp(cond_id, size) => {
1406                if regs[cond_id].is_true() {
1407                    ip = unsafe { ip.add(size as usize) };
1408                    continue;
1409                }
1410            }
1411            Instr::AddFloat(o1, o2, dest) => {
1412                regs[dest] = (regs[o1].as_float() + regs[o2].as_float()).into();
1413            }
1414            Instr::AddInt(o1, o2, dest) => {
1415                regs[dest] = (regs[o1].as_int() + regs[o2].as_int()).into();
1416            }
1417            Instr::MulFloat(o1, o2, dest) => {
1418                regs[dest] = (regs[o1].as_float() * regs[o2].as_float()).into();
1419            }
1420            Instr::MulInt(o1, o2, dest) => {
1421                regs[dest] = (regs[o1].as_int() * regs[o2].as_int()).into();
1422            }
1423            Instr::DivFloat(o1, o2, dest) => {
1424                regs[dest] = (regs[o1].as_float() / regs[o2].as_float()).into();
1425            }
1426            Instr::SubFloat(o1, o2, dest) => {
1427                regs[dest] = (regs[o1].as_float() - regs[o2].as_float()).into();
1428            }
1429            Instr::SubInt(o1, o2, dest) => {
1430                regs[dest] = (regs[o1].as_int() - regs[o2].as_int()).into();
1431            }
1432            Instr::IncInt(reg) => regs[reg].inc_int(),
1433            Instr::DecInt(reg) => regs[reg].dec_int(),
1434            Instr::IncIntTo(src, dst) => {
1435                let s = regs[src];
1436                regs[dst].inc_into(s);
1437            }
1438            Instr::DecIntTo(src, dst) => {
1439                let s = regs[src];
1440                regs[dst].dec_into(s);
1441            }
1442            Instr::Eq(o1, o2, dest) => {
1443                regs[dest] = (regs.get(o1) == regs.get(o2)).into();
1444            }
1445            Instr::NotEqJmp(o1, o2, jump_size) => {
1446                if regs.get(o1) != regs.get(o2) {
1447                    ip = unsafe { ip.add(jump_size as usize) };
1448                    continue;
1449                }
1450            }
1451            Instr::NotEq(o1, o2, dest) => {
1452                regs[dest] = (regs.get(o1) != regs.get(o2)).into();
1453            }
1454            Instr::EqJmp(o1, o2, jump_size) => {
1455                if regs.get(o1) == regs.get(o2) {
1456                    ip = unsafe { ip.add(jump_size as usize) };
1457                    continue;
1458                }
1459            }
1460            Instr::SupFloat(o1, o2, dest) => {
1461                regs[dest] = (regs[o1].as_float() > regs[o2].as_float()).into();
1462            }
1463            Instr::SupInt(o1, o2, dest) => {
1464                regs[dest] = (regs[o1].as_int() > regs[o2].as_int()).into();
1465            }
1466            Instr::InfEqFloatJmp(o1, o2, jump_size) => {
1467                if regs[o1].as_float() <= regs[o2].as_float() {
1468                    ip = unsafe { ip.add(jump_size as usize) };
1469                    continue;
1470                }
1471            }
1472            Instr::InfEqIntJmp(o1, o2, jump_size) => {
1473                if regs[o1].as_int() <= regs[o2].as_int() {
1474                    ip = unsafe { ip.add(jump_size as usize) };
1475                    continue;
1476                }
1477            }
1478            Instr::SupEqFloat(o1, o2, dest) => {
1479                regs[dest] = (regs[o1].as_float() >= regs[o2].as_float()).into();
1480            }
1481            Instr::SupEqInt(o1, o2, dest) => {
1482                regs[dest] = (regs[o1].as_int() >= regs[o2].as_int()).into();
1483            }
1484            Instr::InfFloatJmp(o1, o2, jump_size) => {
1485                if regs[o1].as_float() < regs[o2].as_float() {
1486                    ip = unsafe { ip.add(jump_size as usize) };
1487                    continue;
1488                }
1489            }
1490            Instr::InfIntJmp(o1, o2, jump_size) => {
1491                if regs[o1].as_int() < regs[o2].as_int() {
1492                    ip = unsafe { ip.add(jump_size as usize) };
1493                    continue;
1494                }
1495            }
1496            Instr::StepIntJmpBack(counter, end, jump_size) => {
1497                regs[counter].inc_int();
1498                if regs[counter].as_int() < regs[end].as_int() {
1499                    ip = unsafe { ip.sub(jump_size as usize) };
1500                    continue;
1501                }
1502            }
1503            Instr::AddIntImm(src, imm, dest) => {
1504                regs[dest] = (regs[src].as_int() + i64::from(imm)).into();
1505            }
1506            Instr::InfIntJmpBack(o1, o2, jump_size) => {
1507                if regs[o1].as_int() < regs[o2].as_int() {
1508                    ip = unsafe { ip.sub(jump_size as usize) };
1509                    continue;
1510                }
1511            }
1512            Instr::InfFloat(o1, o2, dest) => {
1513                regs[dest] = (regs[o1].as_float() < regs[o2].as_float()).into();
1514            }
1515            Instr::InfInt(o1, o2, dest) => {
1516                regs[dest] = (regs[o1].as_int() < regs[o2].as_int()).into();
1517            }
1518            Instr::SupEqFloatJmp(o1, o2, jump_size) => {
1519                if regs[o1].as_float() >= regs[o2].as_float() {
1520                    ip = unsafe { ip.add(jump_size as usize) };
1521                    continue;
1522                }
1523            }
1524            Instr::SupEqIntJmp(o1, o2, jump_size) => {
1525                if regs[o1].as_int() >= regs[o2].as_int() {
1526                    ip = unsafe { ip.add(jump_size as usize) };
1527                    continue;
1528                }
1529            }
1530            Instr::InfEqFloat(o1, o2, dest) => {
1531                regs[dest] = (regs[o1].as_float() <= regs[o2].as_float()).into();
1532            }
1533            Instr::InfEqInt(o1, o2, dest) => {
1534                regs[dest] = (regs[o1].as_int() <= regs[o2].as_int()).into();
1535            }
1536            Instr::SupFloatJmp(o1, o2, jump_size) => {
1537                if regs[o1].as_float() > regs[o2].as_float() {
1538                    ip = unsafe { ip.add(jump_size as usize) };
1539                    continue;
1540                }
1541            }
1542            Instr::SupIntJmp(o1, o2, jump_size) => {
1543                if regs[o1].as_int() > regs[o2].as_int() {
1544                    ip = unsafe { ip.add(jump_size as usize) };
1545                    continue;
1546                }
1547            }
1548            Instr::BoolAnd(o1, o2, dest) => {
1549                regs[dest] = (regs[o1].as_bool() && regs[o2].as_bool()).into();
1550            }
1551            Instr::BoolOr(o1, o2, dest) => {
1552                regs[dest] = (regs[o1].as_bool() || regs[o2].as_bool()).into();
1553            }
1554            Instr::NegBool(src, dest) => {
1555                regs[dest] = (!regs[src].as_bool()).into();
1556            }
1557            Instr::NegFloat(tgt, dest) => {
1558                regs[dest] = (-regs[tgt].as_float()).into();
1559            }
1560            Instr::NegInt(tgt, dest) => {
1561                regs[dest] = (-regs[tgt].as_int()).into();
1562            }
1563            Instr::StoreFuncArg(id) => m.args.push(id),
1564            // Each store into the heap marks the value it overwrites while the
1565            // collector is marking; see the barrier rules in `gc`.
1566            Instr::ObjElemMov(new_elem_reg_id, array_id, idx) => {
1567                let arr = m.obj_pool.get_mut(array_id as usize);
1568                let slot = unsafe { arr.get_unchecked_mut(idx as usize) };
1569                if m.gc.marking() {
1570                    shade_overwritten(m.gc, m.str_pool, *slot);
1571                }
1572                *slot = regs.get(new_elem_reg_id);
1573            }
1574            Instr::SetElementObj(array_reg_id, new_elem_reg_id, idx) => {
1575                let array = m.obj_pool.get_mut(regs[array_reg_id].as_array());
1576                let index = regs[idx].as_int();
1577                if index < 0 || (index as u64) >= array.len() as u64 {
1578                    error_with_catch!(ErrType::IndexOutOfBounds(array.len(), index));
1579                }
1580                let slot = unsafe { array.get_unchecked_mut(index as usize) };
1581                if m.gc.marking() {
1582                    shade_overwritten(m.gc, m.str_pool, *slot);
1583                }
1584                *slot = regs.get(new_elem_reg_id);
1585            }
1586            Instr::SetFieldStruct(struct_reg_id, new_elem_reg_id, idx) => {
1587                let s = m.obj_pool.get_mut(regs[struct_reg_id].as_struct());
1588                let slot = unsafe { s.get_unchecked_mut(idx as usize) };
1589                if m.gc.marking() {
1590                    shade_overwritten(m.gc, m.str_pool, *slot);
1591                }
1592                *slot = regs.get(new_elem_reg_id);
1593            }
1594            // The field holds a float before and after, so the store needs no
1595            // barrier: the collector never follows a float.
1596            Instr::AddFieldFloat(struct_reg_id, value_reg_id, idx) => {
1597                let value = regs[value_reg_id].as_float();
1598                let s = m.obj_pool.get_mut(regs[struct_reg_id].as_struct());
1599                let slot = unsafe { s.get_unchecked_mut(idx as usize) };
1600                *slot = (slot.as_float() + value).into();
1601            }
1602            Instr::GetIndexArray(array_reg_id, index, dest) => {
1603                let idx = regs[index].as_int();
1604                let arr_id = regs[array_reg_id].as_array();
1605                let array = &m.obj_pool[arr_id];
1606                if idx < 0 || (idx as u64) >= array.len() as u64 {
1607                    error_with_catch!(ErrType::IndexOutOfBounds(array.len(), idx));
1608                }
1609                regs[dest] = unsafe { Data::load(array.as_ptr().add(idx as usize)) };
1610            }
1611            Instr::GetFieldStruct(struct_reg_id, index, dest) => {
1612                let struct_id = regs[struct_reg_id].as_struct();
1613                let s = &m.obj_pool[struct_id];
1614                regs[dest] = unsafe { Data::load(s.as_ptr().add(index as usize)) }
1615            }
1616            Instr::LoadCell(cell, dest) => {
1617                let slot = &m.obj_pool[regs[cell].as_array()];
1618                regs[dest] = unsafe { Data::load(slot.as_ptr()) };
1619            }
1620            Instr::StoreCell(cell, src) => {
1621                let value = regs.get(src);
1622                let cell = m.obj_pool.get_mut(regs[cell].as_array());
1623                let slot = unsafe { cell.get_unchecked_mut(0) };
1624                if m.gc.marking() {
1625                    shade_overwritten(m.gc, m.str_pool, *slot);
1626                }
1627                *slot = value;
1628            }
1629            Instr::Push(array, element) => {
1630                let list = m.obj_pool.get_mut(regs[array].as_array());
1631                if list.len() == list.capacity() {
1632                    grow_list(list, m.gc);
1633                }
1634                // There is room, so the element is written in place, which is
1635                // the push without its capacity test.
1636                unsafe {
1637                    let len = list.len();
1638                    list.as_mut_ptr().add(len).write(regs.get(element));
1639                    list.set_len(len + 1);
1640                }
1641            }
1642            Instr::CloneStruct(src_reg, dest_reg) => {
1643                let new_id = alloc_array(
1644                    m.obj_pool,
1645                    m.map_pool,
1646                    m.str_pool,
1647                    m.r,
1648                    &m.recursion_stack,
1649                    m.gc,
1650                ) as usize;
1651                let src_reg = regs[src_reg];
1652                let src = &m.obj_pool[src_reg.as_struct()];
1653                let len = src.len();
1654                unsafe {
1655                    let src_ptr = src.as_ptr();
1656                    let dst = m.obj_pool.get_mut(new_id);
1657                    dst.reserve_exact(len);
1658                    dst.set_len(len);
1659                    std::ptr::copy_nonoverlapping(src_ptr, dst.as_mut_ptr(), len);
1660                }
1661                regs[dest_reg] = Data::struct_instance(src_reg.struct_type_id(), new_id as u32);
1662            }
1663            // Named one by one rather than caught with `_`: a match that
1664            // names every instruction compiles to a jump table that needs no
1665            // range check before the jump.
1666            Instr::Print(..)
1667            | Instr::ObjNotEqJmp(..)
1668            | Instr::ObjEqJmp(..)
1669            | Instr::StrNotEqJmp(..)
1670            | Instr::StrEqJmp(..)
1671            | Instr::AddArray(..)
1672            | Instr::AddStr(..)
1673            | Instr::DivInt(..)
1674            | Instr::ModFloat(..)
1675            | Instr::ModInt(..)
1676            | Instr::PowFloat(..)
1677            | Instr::PowInt(..)
1678            | Instr::BitAndInt(..)
1679            | Instr::BitOrInt(..)
1680            | Instr::BitXorInt(..)
1681            | Instr::BitNotInt(..)
1682            | Instr::ShlInt(..)
1683            | Instr::ShrInt(..)
1684            | Instr::ObjEq(..)
1685            | Instr::ObjNotEq(..)
1686            | Instr::StrEq(..)
1687            | Instr::StrNotEq(..)
1688            | Instr::SupStr(..)
1689            | Instr::SupEqStr(..)
1690            | Instr::InfStr(..)
1691            | Instr::InfEqStr(..)
1692            | Instr::CallDynamicLibFunc(..)
1693            | Instr::CallHostFunc(..)
1694            | Instr::CallLibFunc(..)
1695            | Instr::CallLibFuncVoid(..)
1696            | Instr::StartErrorCatch(..)
1697            | Instr::StopErrorCatch
1698            | Instr::ThrowError(..)
1699            | Instr::EmptyArray(..)
1700            | Instr::EmptyFnValue(..)
1701            | Instr::CloneArray(..)
1702            | Instr::CloneEnum(..)
1703            | Instr::SetElementString(..)
1704            | Instr::GetIndexString(..)
1705            | Instr::GetSliceArray(..)
1706            | Instr::GetSliceString(..)
1707            | Instr::Remove(..)
1708            | Instr::MapGet(..)
1709            | Instr::MapInsert(..)
1710            | Instr::MapInsertReg(..)
1711            | Instr::MapRemove(..)
1712            | Instr::CloneMap(..)
1713            | Instr::NewCell(..)
1714            | Instr::NativeExit
1715            | Instr::Halt(..) => {
1716                let next = run_cold(m, instructions, index_of(base, ip), regs);
1717                if next >= PANICKED {
1718                    return next;
1719                }
1720                ip = unsafe { base.add(next) };
1721                continue;
1722            }
1723            // A call into machine code, out of line: what it costs next to the
1724            // work of the call is small, and it keeps the loop small.
1725            #[cfg(not(target_arch = "wasm32"))]
1726            Instr::CallNative(function, ret) => {
1727                let next = call_native(m, index_of(base, ip), function, ret, false);
1728                if next >= PANICKED {
1729                    return next;
1730                }
1731                ip = unsafe { base.add(next) };
1732                continue;
1733            }
1734            #[cfg(not(target_arch = "wasm32"))]
1735            Instr::CallNativeRec(function, ret) => {
1736                let next = call_native(m, index_of(base, ip), function, ret, true);
1737                if next >= PANICKED {
1738                    return next;
1739                }
1740                ip = unsafe { base.add(next) };
1741                continue;
1742            }
1743            #[cfg(target_arch = "wasm32")]
1744            Instr::CallNative(..) | Instr::CallNativeRec(..) => unsafe { unreachable_unchecked() },
1745        }
1746        ip = unsafe { ip.add(1) };
1747    }
1748}
1749
1750/// Runs a call the load pointed at machine code: `function` of the loaded
1751/// code, from the call at instruction `i`, its result bound for `ret`.
1752/// `recursive` for a call whose `SaveFrame` has pushed its frame already.
1753/// Answers the instruction to carry on at, or why the run stops.
1754///
1755/// The call runs on the interpreter instead once the stack has no room left
1756/// for native frames, which is what keeps a deep recursion from running the
1757/// thread out of stack: the interpreter's frames are on the heap.
1758#[inline(never)]
1759#[cfg(not(target_arch = "wasm32"))]
1760fn call_native(m: &mut Machine<'_>, i: usize, function: u16, ret: u16, recursive: bool) -> usize {
1761    let native = unsafe { m.native.unwrap_unchecked() };
1762    let f = unsafe { native.functions.get_unchecked(function as usize) };
1763    // The calls standing, counting the callee's when its frame is pushed.
1764    let standing = m.call_frames.len() + m.native_below;
1765    if m.native_frames_left == 0 {
1766        if !recursive {
1767            if m.call_frames.len() == m.call_frames.capacity()
1768                && let Some(catch) = frame_room(m, i, m.regs)
1769            {
1770                return catch;
1771            }
1772            m.call_frames.push_unchecked(CallFrame {
1773                return_addr: i as u16,
1774                return_reg: ret,
1775                saved_callsite: None,
1776            });
1777        }
1778        return f.loc as usize;
1779    }
1780    if !recursive && standing >= CALL_DEPTH_LIMIT {
1781        m.ctx.status = abi::STATUS_OK;
1782        return native_failed(m, i, abi::STATUS_DEPTH);
1783    }
1784    let depth = if recursive { standing } else { standing + 1 };
1785    let below = depth - 1;
1786    let ceiling = CALL_DEPTH_LIMIT.min(below + m.native_frames_left);
1787    let status = enter_native(m, f.entry, below, ceiling, (ceiling - depth) as i64);
1788    if status != abi::STATUS_OK {
1789        return native_failed(m, i, status);
1790    }
1791    let value = Data::from_words(m.ctx.ret[0], m.ctx.ret[1] as i64);
1792    let mut regs = m.regs;
1793    if recursive {
1794        // What `RecursiveReturn` or `VoidReturn` does at the end of the body.
1795        let call_frame = m.call_frames.pop_unchecked();
1796        if let Some(callsite_id) = call_frame.saved_callsite {
1797            restore_saved_registers(
1798                callsite_id,
1799                m.callsite_registers,
1800                &mut m.recursion_stack,
1801                regs,
1802            );
1803        }
1804        if f.returns {
1805            regs[call_frame.return_reg] = value;
1806        }
1807        return call_frame.return_addr as usize + 1;
1808    }
1809    if f.returns {
1810        regs[ret] = value;
1811    }
1812    i + 1
1813}
1814
1815/// Runs `main` as machine code.
1816#[cfg(not(target_arch = "wasm32"))]
1817fn run_native_main(m: &mut Machine<'_>, main: usize) -> usize {
1818    let native = unsafe { m.native.unwrap_unchecked() };
1819    let entry = native.functions[main].entry;
1820    // `main` stands at depth 0: the functions it calls are the first frames.
1821    let ceiling = CALL_DEPTH_LIMIT.min(m.native_frames_left);
1822    let status = enter_native(m, entry, 0, ceiling, ceiling as i64);
1823    if status == abi::STATUS_OK || status == abi::STATUS_HALT {
1824        return HALTED;
1825    }
1826    match native_failed(m, 0, status) {
1827        stop @ (HALTED | FAILED | RETURNED | PANICKED) => stop,
1828        resume => run_nested(m, resume),
1829    }
1830}
1831
1832/// Calls a native entry point with the context brought up to date, and
1833/// answers the status it comes back with.
1834#[cfg(not(target_arch = "wasm32"))]
1835fn enter_native(m: &mut Machine<'_>, entry: Entry, below: usize, ceiling: usize, rem: i64) -> u32 {
1836    let machine: *mut Machine<'_> = m;
1837    unsafe {
1838        let ctx = &raw mut (*machine).ctx;
1839        (*ctx).objs = (*machine).obj_pool.0.as_ptr().cast();
1840        (*ctx).marking = u64::from((*machine).gc.marking());
1841        (*ctx).machine = machine.cast();
1842        (*ctx).status = abi::STATUS_OK;
1843        let saved = ((*ctx).ceiling, (*ctx).segment_base);
1844        (*ctx).ceiling = ceiling as i64;
1845        (*ctx).segment_base = below as i64;
1846        let status = entry(ctx, rem);
1847        (*ctx).ceiling = saved.0;
1848        (*ctx).segment_base = saved.1;
1849        status
1850    }
1851}
1852
1853/// Raises, in this run, what stopped a call into native code, the way the
1854/// instruction it stopped at would have raised it on the interpreter. `i` is
1855/// the call the run made into the code.
1856#[cold]
1857#[inline(never)]
1858#[cfg(not(target_arch = "wasm32"))]
1859fn native_failed(m: &mut Machine<'_>, i: usize, status: u32) -> usize {
1860    // Where the error happened: the instruction the code recorded, or the
1861    // call into the code when the call itself was refused.
1862    let at = if m.ctx.status == status {
1863        m.ctx.err_at as usize
1864    } else {
1865        i
1866    };
1867    let err = match status {
1868        abi::STATUS_HALT => return HALTED,
1869        abi::STATUS_PANIC => return PANICKED,
1870        abi::STATUS_VM => {
1871            return match m.failure.take() {
1872                Some(error) => catch_failure(m, error),
1873                None => FAILED,
1874            };
1875        }
1876        abi::STATUS_INDEX => ErrType::IndexOutOfBounds(m.ctx.err_a as usize, m.ctx.err_b),
1877        abi::STATUS_SHIFT => ErrType::ShiftCountOutOfRange(m.ctx.err_a),
1878        _ => {
1879            ErrType::CallDepthExceeded(call_site_name(m.err_ctx, m.instr_at(at)), CALL_DEPTH_LIMIT)
1880        }
1881    };
1882    let instr = m.instr_at(at);
1883    catch_error(
1884        err,
1885        instr,
1886        &mut m.failure,
1887        &mut m.error_handles,
1888        m.handles_floor,
1889        &mut m.call_frames,
1890        &mut m.args,
1891        m.callsite_registers,
1892        &mut m.recursion_stack,
1893        m.regs,
1894        m.r,
1895        m.obj_pool,
1896        m.map_pool,
1897        m.str_pool,
1898        m.gc,
1899    )
1900}
1901
1902/// The run a helper was called from.
1903///
1904/// # Safety
1905///
1906/// `ctx` is the context of a run whose native code is calling the helper, so
1907/// the machine it names is alive and nothing else uses it until the helper
1908/// returns.
1909#[cfg(not(target_arch = "wasm32"))]
1910unsafe fn machine<'a>(ctx: *mut NativeCtx) -> &'a mut Machine<'a> {
1911    unsafe { &mut *(*ctx).machine.cast::<Machine<'a>>() }
1912}
1913
1914/// Shades what a store native code makes overwrites, while the collector
1915/// marks.
1916#[cfg(not(target_arch = "wasm32"))]
1917extern "C" fn native_shade(ctx: *mut NativeCtx, boxed: u64, int: u64) {
1918    let m = unsafe { machine(ctx) };
1919    shade_overwritten(m.gc, m.str_pool, Data::from_words(boxed, int as i64));
1920}
1921
1922/// `print` for native code.
1923#[cfg(not(target_arch = "wasm32"))]
1924extern "C" fn native_print(ctx: *mut NativeCtx, boxed: u64, int: u64) {
1925    let m = unsafe { machine(ctx) };
1926    print_value(
1927        &mut m.handle,
1928        Data::from_words(boxed, int as i64),
1929        m.obj_pool,
1930        m.map_pool,
1931        m.str_pool,
1932        m.structs,
1933        m.enums,
1934    );
1935}
1936
1937/// Keeps `count` values native code holds alive across a call that can
1938/// collect, on the stack the collector reads as roots.
1939#[cfg(not(target_arch = "wasm32"))]
1940extern "C" fn native_push_roots(ctx: *mut NativeCtx, values: *const u64, count: u64) {
1941    let m = unsafe { machine(ctx) };
1942    push_values(&mut m.recursion_stack, values, count);
1943}
1944
1945/// Drops the `count` values [`native_push_roots`] kept last.
1946#[cfg(not(target_arch = "wasm32"))]
1947extern "C" fn native_pop_roots(ctx: *mut NativeCtx, count: u64) {
1948    let m = unsafe { machine(ctx) };
1949    let len = m.recursion_stack.len() - count as usize;
1950    m.recursion_stack.0.truncate(len);
1951}
1952
1953/// Pushes `count` values, two words each, onto `stack`.
1954#[cfg(not(target_arch = "wasm32"))]
1955fn push_values(stack: &mut RegisterFile, values: *const u64, count: u64) {
1956    for k in 0..count as usize {
1957        let (boxed, int) = unsafe { (*values.add(2 * k), *values.add(2 * k + 1)) };
1958        stack.0.push(Data::from_words(boxed, int as i64));
1959    }
1960}
1961
1962/// Runs a function on the interpreter for native code: from its first
1963/// instruction `entry` until it returns, with its result landing in
1964/// `ret_reg`. `at` is the call the code makes, `rem` how far below the
1965/// ceiling the calling function stands, and `roots` the `count` values it
1966/// holds that have to outlive whatever the function allocates.
1967#[cfg(not(target_arch = "wasm32"))]
1968extern "C" fn native_call_vm(
1969    ctx: *mut NativeCtx,
1970    entry: u64,
1971    ret_reg: u64,
1972    at: u64,
1973    rem: i64,
1974    roots: *const u64,
1975    count: u64,
1976) -> u32 {
1977    let m = unsafe { machine(ctx) };
1978    if cfg!(panic = "unwind") {
1979        // A host function may panic, and a panic cannot unwind through
1980        // machine code. It is held here and resumed where no native frame
1981        // stands in its way.
1982        let run =
1983            std::panic::AssertUnwindSafe(|| call_vm(m, entry, ret_reg, at, rem, roots, count));
1984        match std::panic::catch_unwind(run) {
1985            Ok(status) => status,
1986            Err(payload) => {
1987                let m = unsafe { machine(ctx) };
1988                m.panic = Some(payload);
1989                m.ctx.status = abi::STATUS_PANIC;
1990                abi::STATUS_PANIC
1991            }
1992        }
1993    } else {
1994        call_vm(m, entry, ret_reg, at, rem, roots, count)
1995    }
1996}
1997
1998/// [`native_call_vm`] itself.
1999#[cfg(not(target_arch = "wasm32"))]
2000fn call_vm(
2001    m: &mut Machine<'_>,
2002    entry: u64,
2003    ret_reg: u64,
2004    at: u64,
2005    rem: i64,
2006    roots: *const u64,
2007    count: u64,
2008) -> u32 {
2009    let native = unsafe { m.native.unwrap_unchecked() };
2010    // The calls standing, the caller's included.
2011    let depth = (m.ctx.ceiling - rem) as usize;
2012    if depth >= CALL_DEPTH_LIMIT {
2013        m.ctx.status = abi::STATUS_DEPTH;
2014        m.ctx.err_at = at as u32;
2015        return abi::STATUS_DEPTH;
2016    }
2017    let saved = (
2018        m.native_below,
2019        m.native_frames_left,
2020        m.handles_floor,
2021        m.ctx.ceiling,
2022        m.ctx.segment_base,
2023    );
2024    // The native frames of this stretch, and the trip back into the
2025    // interpreter, come out of what the stack keeps for native code.
2026    let frame = (native.frame_bytes as usize).max(16);
2027    let used = depth.saturating_sub(m.ctx.segment_base as usize) + REENTRY_BYTES.div_ceil(frame);
2028    m.native_frames_left = m.native_frames_left.saturating_sub(used);
2029    m.native_below = depth - m.call_frames.len();
2030
2031    // The call's frame, returning to where this run ends. The stack keeps
2032    // below the limit by the native calls standing under it.
2033    let room = CALL_DEPTH_LIMIT - m.native_below;
2034    if m.call_frames.capacity() > room {
2035        m.call_frames.shrink_to(room.max(m.call_frames.len()));
2036    }
2037    if m.call_frames.len() == m.call_frames.capacity() {
2038        let len = m.call_frames.len();
2039        m.call_frames.reserve_exact(len.max(64).min(room - len));
2040    }
2041    m.call_frames.push(CallFrame {
2042        return_addr: (native.exit - 1) as u16,
2043        return_reg: ret_reg as u16,
2044        saved_callsite: None,
2045    });
2046    let roots_at = m.recursion_stack.len();
2047    push_values(&mut m.recursion_stack, roots, count);
2048    m.handles_floor = m.error_handles.len();
2049
2050    let stop = run_nested(m, entry as usize);
2051
2052    (
2053        m.native_below,
2054        m.native_frames_left,
2055        m.handles_floor,
2056        m.ctx.ceiling,
2057        m.ctx.segment_base,
2058    ) = saved;
2059    m.ctx.objs = m.obj_pool.0.as_ptr().cast();
2060    m.ctx.marking = u64::from(m.gc.marking());
2061    let status = match stop {
2062        RETURNED => {
2063            m.recursion_stack.0.truncate(roots_at);
2064            return abi::STATUS_OK;
2065        }
2066        HALTED => abi::STATUS_HALT,
2067        PANICKED => abi::STATUS_PANIC,
2068        _ => abi::STATUS_VM,
2069    };
2070    m.ctx.status = status;
2071    status
2072}
2073
2074/// Prints `tgt` followed by a newline, the way `print` shows every kind of
2075/// value.
2076#[inline(never)]
2077fn print_value(
2078    handle: &mut crate::captured_output::OutputHandle,
2079    tgt: Data,
2080    obj_pool: &ObjectPool,
2081    map_pool: &MapPool,
2082    str_pool: &StringPool,
2083    structs: &[Struct],
2084    enums: &[EnumType],
2085) {
2086    if tgt.is_string() {
2087        outln!(*handle, "{}", tgt.as_str(str_pool));
2088    } else if tgt.is_int() {
2089        outln!(*handle, "{}", tgt.as_int());
2090    } else if tgt.is_float() {
2091        outln!(*handle, "{}", format_float(tgt.as_float()));
2092    } else if tgt.is_bool() {
2093        outln!(*handle, "{}", tgt.as_bool());
2094    } else if tgt.is_function() {
2095        outln!(*handle, "{FUNCTION_TEXT}");
2096    } else if tgt.is_array() {
2097        let array = &obj_pool[tgt.as_array()];
2098        out!(*handle, "[");
2099        for (idx, item) in array.iter().enumerate() {
2100            if idx != 0 {
2101                out!(*handle, ",");
2102            }
2103            out!(
2104                *handle,
2105                "{}",
2106                item.format(obj_pool, str_pool, map_pool, structs, enums, false)
2107            );
2108        }
2109        outln!(*handle, "]");
2110    } else if tgt.is_struct() {
2111        let s = unsafe { structs.get_unchecked(tgt.struct_type_id() as usize) };
2112        let s_name = &s.name;
2113        let s_fields = &s.fields;
2114        out!(*handle, "{s_name} {{");
2115        for (idx, item) in obj_pool[tgt.as_struct()].iter().enumerate() {
2116            if idx != 0 {
2117                out!(*handle, ",");
2118            }
2119            out!(
2120                *handle,
2121                "{}:{}",
2122                unsafe { &s_fields.get_unchecked(idx).0 },
2123                item.format(obj_pool, str_pool, map_pool, structs, enums, false)
2124            );
2125        }
2126        outln!(*handle, "}}");
2127    } else if tgt.is_enum() {
2128        let e = unsafe { enums.get_unchecked(tgt.enum_type_id() as usize) };
2129        let entry = &obj_pool[tgt.as_enum()];
2130        let tag = entry[0].as_int() as usize;
2131        let variant = unsafe { e.variants.get_unchecked(tag) };
2132        if entry.len() <= 1 {
2133            outln!(*handle, "{}", variant.name);
2134        } else {
2135            out!(*handle, "{}(", variant.name);
2136            for (idx, item) in entry[1..].iter().enumerate() {
2137                if idx != 0 {
2138                    out!(*handle, ",");
2139                }
2140                out!(
2141                    *handle,
2142                    "{}",
2143                    item.format(obj_pool, str_pool, map_pool, structs, enums, false)
2144                );
2145            }
2146            outln!(*handle, ")");
2147        }
2148    } else if tgt.is_map() {
2149        let m = &map_pool[tgt.as_map()];
2150        out!(*handle, "{{");
2151        for (i, (key, val)) in m.iter().enumerate() {
2152            if i != 0 {
2153                out!(*handle, ",");
2154            }
2155            out!(
2156                *handle,
2157                "{}:{}",
2158                key.format(obj_pool, str_pool, map_pool, structs, enums, false),
2159                val.format(obj_pool, str_pool, map_pool, structs, enums, false),
2160            );
2161        }
2162        outln!(*handle, "}}");
2163    } else if tgt.is_null() {
2164        outln!(*handle, "null");
2165    }
2166}
2167
2168/// The index of the instruction `ip` points at.
2169#[inline(always)]
2170fn index_of(base: *const Instr, ip: *const Instr) -> usize {
2171    unsafe { ip.offset_from_unsigned(base) }
2172}
2173
2174/// Runs one instruction the dispatch loop in [`execute`] does not run itself,
2175/// and answers the index to carry on at, or [`HALTED`] once the program has
2176/// stopped.
2177///
2178/// These are the instructions that allocate, format, call out of the VM or
2179/// otherwise do enough work that one more call is noise next to it. Keeping
2180/// them out of the loop keeps the loop small enough for its hot state to stay
2181/// in machine registers.
2182#[allow(unused_unsafe)]
2183#[cold]
2184#[inline(never)]
2185fn run_cold(m: &mut Machine<'_>, instructions: &[Instr], mut i: usize, mut regs: Regs) -> usize {
2186    let Machine {
2187        r,
2188        regs: _,
2189        instructions: _,
2190        obj_pool,
2191        map_pool,
2192        str_pool,
2193        gc,
2194        err_ctx: _,
2195        callsite_registers,
2196        dyn_libs,
2197        structs,
2198        enums,
2199        host_sigs,
2200        host_dispatch,
2201        args,
2202        call_frames,
2203        recursion_stack,
2204        handle,
2205        dyn_lib_args,
2206        host_call_args,
2207        keep_alive,
2208        error_handles,
2209        failure,
2210        native: _,
2211        #[cfg(not(target_arch = "wasm32"))]
2212            ctx: _,
2213        native_below: _,
2214        #[cfg(not(target_arch = "wasm32"))]
2215            native_frames_left: _,
2216        handles_floor,
2217        panic: _,
2218    } = m;
2219    let r: &RegisterFile = r;
2220    let obj_pool: &mut ObjectPool = obj_pool;
2221    let map_pool: &mut MapPool = map_pool;
2222    let str_pool: &mut StringPool = str_pool;
2223    let gc: &mut GcState = gc;
2224    let callsite_registers: &[Vec<u16>] = callsite_registers;
2225    let dyn_libs: &[DynamicLibFn] = dyn_libs;
2226    let structs: &[Struct] = structs;
2227    let enums: &[EnumType] = enums;
2228    let host_sigs: &[HostFnSig] = host_sigs;
2229    let host_dispatch: &[HostDispatch] = host_dispatch;
2230
2231    macro_rules! string {
2232        ($e: expr) => {
2233            Data::string($e, obj_pool, map_pool, str_pool, r, recursion_stack, gc)
2234        };
2235    }
2236
2237    macro_rules! error_with_catch {
2238        ($err:expr) => {
2239            i = caught!($err);
2240            continue;
2241        };
2242        ($err:expr, $label:lifetime) => {
2243            i = caught!($err);
2244            continue $label;
2245        };
2246    }
2247
2248    macro_rules! caught {
2249        ($err:expr) => {
2250            catch_error(
2251                $err,
2252                unsafe { *instructions.get_unchecked(i) },
2253                failure,
2254                error_handles,
2255                *handles_floor,
2256                call_frames,
2257                args,
2258                callsite_registers,
2259                recursion_stack,
2260                regs,
2261                r,
2262                obj_pool,
2263                map_pool,
2264                str_pool,
2265                gc,
2266            )
2267        };
2268    }
2269
2270    /// Orders two string registers with `$op` and writes the answer.
2271    ///
2272    /// The compiler emits these for two operands it typed as `string`, so one
2273    /// can still turn out to be something else at run time: a value that
2274    /// crossed a boundary the compiler could not check, such as a variadic
2275    /// host function returning something other than what its block declares.
2276    /// Ordering bytes that are not a string is not meaningful, so such an
2277    /// operand raises, the way joining one does.
2278    macro_rules! str_order {
2279        ($o1:expr, $o2:expr, $dest:expr, $op:tt) => {
2280            let d1 = regs[$o1];
2281            let d2 = regs[$o2];
2282            if !d1.is_string() || !d2.is_string() {
2283                let culprit = if d1.is_string() { d2 } else { d1 };
2284                error_with_catch!(ErrType::NotAString(culprit.type_name()));
2285            }
2286            regs[$dest] = (d1.as_str(str_pool) $op d2.as_str(str_pool)).into();
2287        };
2288    }
2289
2290    // A one-pass loop: an instruction that jumps sets `i` and leaves with
2291    // `continue`, the way it would in the dispatch loop.
2292    #[allow(clippy::never_loop)]
2293    'main: for _ in 0..1 {
2294        match unsafe { *instructions.get_unchecked(i) } {
2295            // No library loads here: each binding is one of the runtime's own
2296            // functions, which the standard library's modules resolve to.
2297            #[cfg(target_arch = "wasm32")]
2298            Instr::CallDynamicLibFunc(fn_id, dest) => {
2299                use crate::intrinsics::{Arg, Ret};
2300                let func = unsafe { dyn_libs.get_unchecked(fn_id as usize) };
2301                let ret = {
2302                    // A short string lives inside its value, so the values are
2303                    // held here for as long as the arguments borrow them.
2304                    let mut values = [NULL; 2];
2305                    for (value, reg) in values.iter_mut().zip(args.iter()) {
2306                        *value = regs[*reg];
2307                    }
2308                    let mut call = [Arg::Int(0); 2];
2309                    for (slot, data) in call.iter_mut().zip(values.iter()) {
2310                        *slot = if data.is_int() {
2311                            Arg::Int(data.as_int())
2312                        } else if data.is_float() {
2313                            Arg::Float(data.as_float())
2314                        } else if data.is_string() {
2315                            Arg::Str(data.as_str(str_pool))
2316                        } else {
2317                            Arg::Int(0)
2318                        };
2319                    }
2320                    (func.intrinsic)(&call[..args.len().min(2)])
2321                };
2322                args.clear();
2323                regs[dest] = match ret {
2324                    Ret::Int(v) => v.into(),
2325                    Ret::Float(v) => v.into(),
2326                    Ret::Str(v) => string!(v),
2327                    Ret::Null => NULL,
2328                };
2329            }
2330            #[cfg(not(target_arch = "wasm32"))]
2331            Instr::CallDynamicLibFunc(fn_id, dest) => {
2332                dyn_lib_args.clear();
2333                dyn_lib_args.reserve_exact(args.len());
2334                let args_ptr = dyn_lib_args.as_mut_ptr();
2335                // Each argument libffi reads, borrowed from its slot in
2336                // `dyn_lib_args` or from `keep_alive`.
2337                let mut ffi_args: Vec<libffi::middle::Arg> = Vec::with_capacity(args.len());
2338                keep_alive.clear();
2339
2340                for idx in 0..args.len() {
2341                    let data = regs[unsafe { *args.get_unchecked(idx) }];
2342                    if data.is_int() {
2343                        unsafe {
2344                            args_ptr.add(idx).write(data.as_int() as u64);
2345                            ffi_args.push(libffi::middle::Arg::new(&*args_ptr.add(idx)));
2346                        }
2347                    } else if data.is_float() {
2348                        unsafe {
2349                            args_ptr.add(idx).write(data.as_float().to_bits());
2350                            ffi_args.push(libffi::middle::Arg::new(&*args_ptr.add(idx)));
2351                        }
2352                    } else if data.is_string() {
2353                        let bytes = if let Ok(b) = std::ffi::CString::new(data.as_str(str_pool)) {
2354                            b.into_bytes_with_nul().into_boxed_slice()
2355                        } else {
2356                            error_with_catch!(ErrType::NullByteInString, 'main);
2357                        };
2358                        let ptr = bytes.as_ptr() as u64;
2359                        keep_alive.push(bytes);
2360                        unsafe {
2361                            args_ptr.add(idx).write(ptr);
2362                            ffi_args.push(libffi::middle::Arg::new(&*args_ptr.add(idx)));
2363                        }
2364                    } else if data.is_array() {
2365                        let ptr = ffi::array_to_c_ptr(data, obj_pool, str_pool, keep_alive) as u64;
2366                        unsafe {
2367                            args_ptr.add(idx).write(ptr);
2368                            ffi_args.push(libffi::middle::Arg::new(&*args_ptr.add(idx)));
2369                        }
2370                    } else if data.is_struct() {
2371                        let b = ffi::candela_struct_to_c_struct(
2372                            data.as_struct(),
2373                            obj_pool,
2374                            str_pool,
2375                            keep_alive,
2376                        )
2377                        .into_boxed_slice();
2378                        let ptr = &raw const *b;
2379                        keep_alive.push(b);
2380                        ffi_args.push(libffi::middle::Arg::new(unsafe { &*ptr }));
2381                    } else {
2382                        unsafe { unreachable_unchecked() }
2383                    }
2384                }
2385                args.clear();
2386
2387                let func = unsafe { dyn_libs.get_unchecked(fn_id as usize) };
2388                // Call the function, and convert the result back into Data
2389                regs[dest] = unsafe {
2390                    match func.get_return_type() {
2391                        DataType::Int => func.cif.call::<i64>(func.ptr, &ffi_args).into(),
2392                        DataType::Float => func.cif.call::<f64>(func.ptr, &ffi_args).into(),
2393                        DataType::String => {
2394                            let ptr = func
2395                                .cif
2396                                .call::<*const std::ffi::c_char>(func.ptr, &ffi_args);
2397                            if ptr.is_null() {
2398                                cold_path();
2399                                NULL
2400                            } else {
2401                                string!(
2402                                    std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned()
2403                                )
2404                            }
2405                        }
2406                        DataType::Struct(struct_idx) => {
2407                            let struct_fields =
2408                                unsafe { &structs.get_unchecked(*struct_idx as usize).fields };
2409                            let (struct_size, _, field_offsets) =
2410                                ffi::get_struct_size_datatype(struct_fields, structs);
2411                            let mut return_buf: Vec<u8> = vec![0; struct_size];
2412                            func.cif.call_return_into(
2413                                func.ptr,
2414                                &ffi_args,
2415                                libffi::middle::ret(&mut return_buf[..]),
2416                            );
2417
2418                            let new_id = ffi::c_struct_to_candela_struct(
2419                                &return_buf,
2420                                &field_offsets,
2421                                obj_pool,
2422                                map_pool,
2423                                str_pool,
2424                                struct_fields,
2425                                r,
2426                                recursion_stack,
2427                                gc,
2428                                structs,
2429                            );
2430                            Data::struct_instance(*struct_idx, new_id)
2431                        }
2432                        DataType::Null => {
2433                            func.cif.call::<()>(func.ptr, &ffi_args);
2434                            NULL
2435                        }
2436                        DataType::Array(_) => {
2437                            error_with_catch!(ErrType::CArrayReturnTypeNotSupported);
2438                        }
2439                        t => {
2440                            error_with_catch!(ErrType::InvalidReturnType(t));
2441                        }
2442                    }
2443                };
2444            }
2445            Instr::CallHostFunc(fn_id, dest) => {
2446                let dispatch = unsafe { host_dispatch.get_unchecked(fn_id as usize) };
2447
2448                // Marshal each argument register (scalar or heap handle) into a
2449                // host `Value`, recursively for arrays/maps/structs.
2450                host_call_args.clear();
2451                for idx in 0..args.len() {
2452                    let data = regs[unsafe { *args.get_unchecked(idx) }];
2453                    host_call_args.push(crate::embed::unmarshal_value(
2454                        data, obj_pool, map_pool, str_pool, structs, enums,
2455                    ));
2456                }
2457                args.clear();
2458
2459                match (**dispatch)(host_call_args) {
2460                    // Marshal the result back, allocating arrays/maps into the
2461                    // pools. `Value::Null` (including a void closure's `()`)
2462                    // becomes NULL, which is what register 0 expects for a
2463                    // discarded result.
2464                    Ok(value) => {
2465                        regs[dest] = crate::embed::marshal_value(
2466                            &value,
2467                            r,
2468                            recursion_stack,
2469                            obj_pool,
2470                            map_pool,
2471                            str_pool,
2472                            gc,
2473                        );
2474                    }
2475                    // The closure raised. Report it where the script called it,
2476                    // naming the function the way the script spells it.
2477                    Err(err) => {
2478                        let function = host_sigs.get(fn_id as usize).map_or_else(
2479                            || format!("#{fn_id}"),
2480                            |sig| crate::embed::qualified_name(&sig.namespace, &sig.name),
2481                        );
2482                        error_with_catch!(ErrType::HostFn(&function, err.message()));
2483                    }
2484                }
2485            }
2486            Instr::AddStr(o1, o2, dest) => {
2487                let d1 = regs[o1];
2488                let d2 = regs[o2];
2489                // Concatenation is compiled from static string types, so both
2490                // operands are strings unless a value crossed a boundary the
2491                // compiler could not check: a variadic host function returning
2492                // something other than what its block declares. Reading that as
2493                // a string would read unrelated bits, so it raises instead.
2494                if !d1.is_string() || !d2.is_string() {
2495                    let culprit = if d1.is_string() { d2 } else { d1 };
2496                    error_with_catch!(ErrType::NotAString(culprit.type_name()));
2497                }
2498                let left = d1.as_str(str_pool);
2499                let right = d2.as_str(str_pool);
2500                let mut s = String::with_capacity(left.len() + right.len());
2501                s.push_str(left);
2502                s.push_str(right);
2503                regs[dest] = string!(s);
2504            }
2505            Instr::EmptyArray(arr_reg_id) => {
2506                let array_id = alloc_array(obj_pool, map_pool, str_pool, r, recursion_stack, gc);
2507                regs[arr_reg_id] = Data::array(array_id);
2508            }
2509            Instr::EmptyFnValue(value_reg_id) => {
2510                let array_id = alloc_array(obj_pool, map_pool, str_pool, r, recursion_stack, gc);
2511                regs[value_reg_id] = Data::function(array_id);
2512            }
2513            Instr::CloneArray(src_reg, dest_reg, len) => {
2514                gc.note_bytes(len as usize * LIST_ENTRY_BYTES);
2515                let src_id = regs[src_reg].as_array();
2516                let new_id =
2517                    alloc_array(obj_pool, map_pool, str_pool, r, recursion_stack, gc) as usize;
2518                let src_ptr = obj_pool[src_id].as_ptr();
2519                let dst = obj_pool.get_mut(new_id);
2520                dst.reserve_exact(len as usize);
2521                unsafe {
2522                    dst.set_len(len as usize);
2523                    std::ptr::copy_nonoverlapping(src_ptr, dst.as_mut_ptr(), len as usize);
2524                }
2525                regs[dest_reg] = Data::array(new_id as u32);
2526            }
2527            Instr::CloneEnum(src_reg, dest_reg) => {
2528                let new_id =
2529                    alloc_array(obj_pool, map_pool, str_pool, r, recursion_stack, gc) as usize;
2530                let src_reg = regs[src_reg];
2531                let src = &obj_pool[src_reg.as_enum()];
2532                let len = src.len();
2533                unsafe {
2534                    let src_ptr = src.as_ptr();
2535                    let dst = obj_pool.get_mut(new_id);
2536                    dst.reserve_exact(len);
2537                    dst.set_len(len);
2538                    std::ptr::copy_nonoverlapping(src_ptr, dst.as_mut_ptr(), len);
2539                }
2540                regs[dest_reg] = Data::enum_instance(src_reg.enum_type_id(), new_id as u32);
2541            }
2542            Instr::AddArray(o1, o2, dest) => {
2543                let arr_a_id = regs[o1].as_array();
2544                let arr_b_id = regs[o2].as_array();
2545                let array_id = alloc_array(obj_pool, map_pool, str_pool, r, recursion_stack, gc);
2546                let array_idx = array_id as usize;
2547                unsafe {
2548                    let array_pool_ptr = obj_pool.as_mut_ptr();
2549                    let arr_a = &*array_pool_ptr.add(arr_a_id);
2550                    let arr_b = &*array_pool_ptr.add(arr_b_id);
2551                    let combined = &mut *array_pool_ptr.add(array_idx);
2552
2553                    combined.reserve(arr_a.len() + arr_b.len());
2554                    combined.extend_from_slice(arr_a);
2555                    combined.extend_from_slice(arr_b);
2556                    gc.note_bytes(combined.capacity() * LIST_ENTRY_BYTES);
2557                }
2558                regs[dest] = Data::array(array_id);
2559            }
2560            Instr::DivInt(o1, o2, dest) => {
2561                let b = regs[o2].as_int();
2562                if b == 0 {
2563                    error_with_catch!(ErrType::DivisionByZero);
2564                }
2565                regs[dest] = (regs[o1].as_int() / b).into();
2566            }
2567            Instr::ModFloat(o1, o2, dest) => {
2568                regs[dest] = (regs[o1].as_float() % regs[o2].as_float()).into();
2569            }
2570            Instr::ModInt(o1, o2, dest) => {
2571                let b = regs[o2].as_int();
2572                if b == 0 {
2573                    error_with_catch!(ErrType::ModuloByZero);
2574                }
2575                regs[dest] = (regs[o1].as_int() % b).into();
2576            }
2577            Instr::PowFloat(o1, o2, dest) => {
2578                regs[dest] = (regs[o1].as_float().powf(regs[o2].as_float())).into();
2579            }
2580            Instr::PowInt(o1, o2, dest) => {
2581                let exp = regs[o2].as_int();
2582                if exp < 0 {
2583                    error_with_catch!(ErrType::NegativeExponent(exp));
2584                }
2585                regs[dest] = (regs[o1].as_int().pow(exp as u32)).into();
2586            }
2587            Instr::BitAndInt(o1, o2, dest) => {
2588                regs[dest] = (regs[o1].as_int() & regs[o2].as_int()).into();
2589            }
2590            Instr::BitOrInt(o1, o2, dest) => {
2591                regs[dest] = (regs[o1].as_int() | regs[o2].as_int()).into();
2592            }
2593            Instr::BitXorInt(o1, o2, dest) => {
2594                regs[dest] = (regs[o1].as_int() ^ regs[o2].as_int()).into();
2595            }
2596            Instr::BitNotInt(o1, dest) => {
2597                regs[dest] = (!regs[o1].as_int()).into();
2598            }
2599            Instr::ShlInt(o1, o2, dest) => {
2600                let count = regs[o2].as_int();
2601                if !shift_count_in_range(count) {
2602                    error_with_catch!(ErrType::ShiftCountOutOfRange(count));
2603                }
2604                regs[dest] = (regs[o1].as_int() << count).into();
2605            }
2606            Instr::ShrInt(o1, o2, dest) => {
2607                let count = regs[o2].as_int();
2608                if !shift_count_in_range(count) {
2609                    error_with_catch!(ErrType::ShiftCountOutOfRange(count));
2610                }
2611                regs[dest] = (regs[o1].as_int() >> count).into();
2612            }
2613            Instr::ObjEq(o1, o2, dest) => {
2614                regs[dest] = obj_eq(regs[o1], regs[o2], obj_pool, map_pool, str_pool).into();
2615            }
2616            Instr::ObjNotEqJmp(o1, o2, jump_size) => {
2617                if !obj_eq(regs[o1], regs[o2], obj_pool, map_pool, str_pool) {
2618                    i += jump_size as usize;
2619                    continue;
2620                }
2621            }
2622            Instr::StrNotEqJmp(o1, o2, jump_size) => {
2623                if !str_eq(regs[o1], regs[o2], str_pool) {
2624                    i += jump_size as usize;
2625                    continue;
2626                }
2627            }
2628            Instr::ObjNotEq(o1, o2, dest) => {
2629                regs[dest] = (!obj_eq(regs[o1], regs[o2], obj_pool, map_pool, str_pool)).into();
2630            }
2631            Instr::StrEq(o1, o2, dest) => {
2632                regs[dest] = str_eq(regs[o1], regs[o2], str_pool).into();
2633            }
2634            Instr::StrNotEq(o1, o2, dest) => {
2635                regs[dest] = (!str_eq(regs[o1], regs[o2], str_pool)).into();
2636            }
2637            Instr::ObjEqJmp(o1, o2, jump_size) => {
2638                if obj_eq(regs[o1], regs[o2], obj_pool, map_pool, str_pool) {
2639                    i += jump_size as usize;
2640                    continue;
2641                }
2642            }
2643            Instr::StrEqJmp(o1, o2, jump_size) => {
2644                if str_eq(regs[o1], regs[o2], str_pool) {
2645                    i += jump_size as usize;
2646                    continue;
2647                }
2648            }
2649            Instr::SupStr(o1, o2, dest) => {
2650                str_order!(o1, o2, dest, >);
2651            }
2652            Instr::SupEqStr(o1, o2, dest) => {
2653                str_order!(o1, o2, dest, >=);
2654            }
2655            Instr::InfStr(o1, o2, dest) => {
2656                str_order!(o1, o2, dest, <);
2657            }
2658            Instr::InfEqStr(o1, o2, dest) => {
2659                str_order!(o1, o2, dest, <=);
2660            }
2661            Instr::Print(tgt) => {
2662                print_value(
2663                    handle, regs[tgt], obj_pool, map_pool, str_pool, structs, enums,
2664                );
2665            }
2666            Instr::SetElementString(string_reg_id, new_str_reg_id, idx) => {
2667                let index = regs[idx].as_int();
2668                let temp_str_reg_id = regs[string_reg_id];
2669                let source_string = temp_str_reg_id.as_str(str_pool);
2670                if index < 0 || (index as u64) >= source_string.len() as u64 {
2671                    error_with_catch!(ErrType::IndexOutOfBounds(source_string.len(), index));
2672                }
2673                let mut temp = source_string.to_owned();
2674                temp.remove(index as usize);
2675                temp.insert_str(index as usize, regs[new_str_reg_id].as_str(str_pool));
2676                regs[string_reg_id] = string!(temp);
2677            }
2678            Instr::GetSliceArray(array_reg_id, idx_start_id, dest_reg_id) => {
2679                let idx_start = regs[idx_start_id].as_int();
2680                let idx_end = regs[args.pop_unchecked()].as_int();
2681                let arr_id = regs[array_reg_id].as_array();
2682                let array = &obj_pool[arr_id];
2683                // A slice runs from `start` to `end` with `0 <= start <= end <= len`,
2684                // so a start sitting on the end of the list is in range and yields
2685                // an empty list. A negative bound casts to a huge `u64` and is
2686                // rejected by the same comparison.
2687                if (idx_end as u64) > array.len() as u64
2688                    || (idx_start as u64) > array.len() as u64
2689                    || idx_start > idx_end
2690                {
2691                    error_with_catch!(ErrType::SliceOutOfBounds(array.len(), idx_start, idx_end));
2692                }
2693                let new_array_id =
2694                    alloc_array(obj_pool, map_pool, str_pool, r, recursion_stack, gc);
2695                unsafe {
2696                    if arr_id < (new_array_id as usize) {
2697                        let (left, right) = obj_pool.split_at_mut(new_array_id as usize);
2698                        right.get_unchecked_mut(0).extend_from_slice(
2699                            left.get_unchecked(arr_id)
2700                                .get_unchecked((idx_start as usize)..(idx_end as usize)),
2701                        );
2702                    } else {
2703                        let (left, right) = obj_pool.split_at_mut(arr_id);
2704                        left.get_unchecked_mut(new_array_id as usize)
2705                            .extend_from_slice(
2706                                right
2707                                    .get_unchecked(0)
2708                                    .get_unchecked((idx_start as usize)..(idx_end as usize)),
2709                            );
2710                    }
2711                }
2712                gc.note_bytes(obj_pool[new_array_id as usize].capacity() * LIST_ENTRY_BYTES);
2713                regs[dest_reg_id] = Data::array(new_array_id);
2714            }
2715            // A string is indexed by character, so the position has to be
2716            // turned into the byte offset that character starts at. On a
2717            // string of single-byte characters the two are the same and the
2718            // index reads straight out of the bytes.
2719            Instr::GetIndexString(tgt, index, dest) => {
2720                let idx = regs[index].as_int();
2721                let tgt_data = regs[tgt];
2722                let char_len = tgt_data.str_char_len(str_pool);
2723                if idx < 0 || (idx as u64) >= char_len as u64 {
2724                    error_with_catch!(ErrType::IndexOutOfBounds(char_len, idx));
2725                }
2726                let bytes = tgt_data.as_str(str_pool).as_bytes();
2727                // As many characters as bytes means every character takes one
2728                // of them, so the position is already the offset and the
2729                // character it names is that one byte. A character takes at
2730                // most four bytes either way, so the answer always fits the
2731                // inlined string form.
2732                let (start, end) = if char_len == bytes.len() {
2733                    (idx as usize, idx as usize + 1)
2734                } else {
2735                    let start = tgt_data.str_byte_offset(idx as usize, str_pool);
2736                    (
2737                        start,
2738                        start + utf8_width(unsafe { *bytes.get_unchecked(start) }),
2739                    )
2740                };
2741                regs[dest] = Data::small_str(unsafe {
2742                    std::str::from_utf8_unchecked(bytes.get_unchecked(start..end))
2743                });
2744            }
2745            Instr::GetSliceString(str_reg_id, idx_start, dest_reg_id) => {
2746                let idx_start = regs[idx_start].as_int();
2747                let idx_end = regs[args.pop_unchecked()].as_int();
2748                let src = regs[str_reg_id];
2749                let char_len = src.str_char_len(str_pool);
2750                // Same rule as a list slice: a start on the end of the string is
2751                // in range and yields "".
2752                if (idx_end as u64) > char_len as u64
2753                    || (idx_start as u64) > char_len as u64
2754                    || idx_start > idx_end
2755                {
2756                    error_with_catch!(ErrType::SliceOutOfBounds(char_len, idx_start, idx_end));
2757                }
2758                let text = src.as_str(str_pool);
2759                let (start, end) = if char_len == text.len() {
2760                    (idx_start as usize, idx_end as usize)
2761                } else {
2762                    // The end is found by walking on from the start rather
2763                    // than from the first byte again, which is also what
2764                    // leaves a walk over the string standing at the start of
2765                    // this cut for the next one.
2766                    let start = src.str_byte_offset(idx_start as usize, str_pool);
2767                    let span = char_byte_offset(&text[start..], (idx_end - idx_start) as usize);
2768                    (start, start + span)
2769                };
2770                // The cut is copied out before the pool is written to, because
2771                // storing the result can move the string it was taken from.
2772                let part = src.as_str(str_pool)[start..end].to_smolstr();
2773                regs[dest_reg_id] = string!(part.as_str());
2774            }
2775            Instr::NewCell(src, dest) => {
2776                let cell_id = alloc_array(obj_pool, map_pool, str_pool, r, recursion_stack, gc);
2777                obj_pool.get_mut(cell_id as usize).push(regs.get(src));
2778                regs[dest] = Data::array(cell_id);
2779            }
2780            Instr::Remove(array, idx) => {
2781                let id = regs[array].as_array();
2782                let arr = obj_pool.get_mut(id);
2783                let index = regs[idx].as_int();
2784                if index < 0 || (index as u64) >= arr.len() as u64 {
2785                    error_with_catch!(ErrType::IndexOutOfBounds(arr.len(), index));
2786                }
2787                let removed = arr.remove(index as usize);
2788                if gc.marking() {
2789                    shade_overwritten(gc, str_pool, removed);
2790                    rescan_list(gc, id);
2791                }
2792            }
2793            Instr::MapGet(map_reg_id, key_reg_id, dest_reg_id) => {
2794                regs[dest_reg_id] = if let Some(elem) = unsafe {
2795                    map_pool[regs[map_reg_id].as_map()].get(&KeyByText {
2796                        key: regs[key_reg_id],
2797                        strings: str_pool,
2798                    })
2799                } {
2800                    *elem
2801                } else {
2802                    cold_path();
2803                    error_with_catch!(ErrType::UnknownMapKey(
2804                        regs[key_reg_id]
2805                            .format(obj_pool, str_pool, map_pool, structs, enums, false)
2806                            .as_str()
2807                    ));
2808                };
2809            }
2810            Instr::MapInsert(map_pool_id, key_reg_id, val_reg_id) => {
2811                let old = map_insert_counted(
2812                    &mut map_pool[map_pool_id as usize],
2813                    regs[key_reg_id],
2814                    regs[val_reg_id],
2815                    str_pool,
2816                    gc,
2817                );
2818                if let Some(old) = old
2819                    && gc.marking()
2820                {
2821                    shade_overwritten(gc, str_pool, old);
2822                }
2823            }
2824            Instr::MapInsertReg(map_reg_id, key_reg_id, val_reg_id) => {
2825                let old = map_insert_counted(
2826                    &mut map_pool[regs[map_reg_id].as_map()],
2827                    regs[key_reg_id],
2828                    regs[val_reg_id],
2829                    str_pool,
2830                    gc,
2831                );
2832                if let Some(old) = old
2833                    && gc.marking()
2834                {
2835                    shade_overwritten(gc, str_pool, old);
2836                }
2837            }
2838            // A key that is not in the map leaves it as it was: taking an entry
2839            // out is a request for the key to be absent, and it already is.
2840            Instr::MapRemove(map_reg_id, key_reg_id) => {
2841                let id = regs[map_reg_id].as_map();
2842                // `shift_remove`, not `swap_remove`: the entries after the one
2843                // taken out keep their order, and a key put back afterwards
2844                // lands at the end.
2845                let removed = map_pool[id].shift_remove_entry(&KeyByText {
2846                    key: regs[key_reg_id],
2847                    strings: str_pool,
2848                });
2849                if let Some((key, value)) = removed
2850                    && gc.marking()
2851                {
2852                    shade_overwritten(gc, str_pool, key);
2853                    shade_overwritten(gc, str_pool, value);
2854                    rescan_map(gc, id);
2855                }
2856            }
2857            Instr::CloneMap(src_reg, dest_reg) => {
2858                let new_map = map_pool[regs[src_reg].as_map()].clone();
2859                gc.note_bytes(new_map.capacity() * MAP_ENTRY_BYTES);
2860                let new_id = alloc_map(obj_pool, map_pool, str_pool, r, recursion_stack, gc);
2861                unsafe {
2862                    map_pool[new_id as usize] = new_map;
2863                }
2864                regs[dest_reg] = Data::map(new_id);
2865            }
2866            Instr::CallLibFunc(LibFunc::Uppercase, source_string_reg_id, dest_reg_id) => {
2867                regs[dest_reg_id] =
2868                    string!(regs[source_string_reg_id].as_str(str_pool).to_uppercase());
2869            }
2870            Instr::CallLibFunc(LibFunc::Lowercase, source_string_reg_id, dest_reg_id) => {
2871                regs[dest_reg_id] =
2872                    string!(regs[source_string_reg_id].as_str(str_pool).to_lowercase());
2873            }
2874            Instr::CallLibFunc(LibFunc::Contains, tgt, dest) => {
2875                let reg = regs[tgt];
2876                if reg.is_string() {
2877                    let str = reg.as_str(str_pool);
2878                    let temp_arg = regs[args.pop_unchecked()];
2879                    let arg = temp_arg.as_str(str_pool);
2880                    // regs[dest] = str.contains(arg).into();
2881                    regs[dest] = memmem::find(str.as_bytes(), arg.as_bytes())
2882                        .is_some()
2883                        .into();
2884                } else if reg.is_array() {
2885                    let arg = regs[args.pop_unchecked()];
2886                    regs[dest] = obj_pool[reg.as_array()]
2887                        .iter()
2888                        .any(|&x| same_key(x, arg, str_pool))
2889                        .into();
2890                } else if reg.is_map() {
2891                    let arg = regs[args.pop_unchecked()];
2892                    regs[dest] = map_pool[reg.as_map()]
2893                        .contains_key(&KeyByText {
2894                            key: arg,
2895                            strings: str_pool,
2896                        })
2897                        .into();
2898                }
2899            }
2900            Instr::CallLibFunc(LibFunc::Trim, tgt, dest) => {
2901                regs[dest] = string!(regs[tgt].as_str(str_pool).trim());
2902            }
2903            Instr::CallLibFunc(LibFunc::TrimSequence, tgt, dest) => {
2904                let temp_arg = regs[args.pop_unchecked()];
2905                let arg = temp_arg.as_str(str_pool);
2906                let chars: Vec<char> = arg.chars().collect();
2907                regs[dest] = string!(regs[tgt].as_str(str_pool).trim_matches(&chars[..]));
2908            }
2909            Instr::CallLibFunc(LibFunc::Find, tgt, dest) => {
2910                let reg = regs[tgt];
2911                if reg.is_string() {
2912                    let str = reg.as_str(str_pool);
2913                    let temp_elem = regs[args.pop_unchecked()];
2914                    let element = temp_elem.as_str(str_pool);
2915                    regs[dest] =
2916                        if let Some(byte_idx) = memmem::find(str.as_bytes(), element.as_bytes()) {
2917                            // The search works in bytes, and the answer is a
2918                            // character position: on a string of single-byte
2919                            // characters they are the same, otherwise count the
2920                            // characters the match sits past.
2921                            if reg.str_is_ascii(str_pool) {
2922                                byte_idx as i64
2923                            } else {
2924                                count_chars(&str[..byte_idx]) as i64
2925                            }
2926                        } else {
2927                            cold_path();
2928                            -1
2929                        }
2930                        .into();
2931                } else if reg.is_array() {
2932                    let arr_id = reg.as_array();
2933                    let element = regs[args.pop_unchecked()];
2934                    regs[dest] = if let Some(idx) = obj_pool[arr_id]
2935                        .iter()
2936                        .position(|&x| same_key(x, element, str_pool))
2937                    {
2938                        idx as i64
2939                    } else {
2940                        cold_path();
2941                        -1
2942                    }
2943                    .into();
2944                }
2945            }
2946            Instr::CallLibFunc(LibFunc::IsFloat, tgt, dest) => {
2947                let temp_tgt = regs[tgt];
2948                let num = temp_tgt.as_str(str_pool);
2949                regs[dest] = (num.parse::<i64>().is_err() && num.parse::<f64>().is_ok()).into();
2950            }
2951            Instr::CallLibFunc(LibFunc::IsInt, tgt, dest) => {
2952                regs[dest] = regs[tgt].as_str(str_pool).parse::<i64>().is_ok().into();
2953            }
2954            Instr::CallLibFunc(LibFunc::TrimLeft, tgt, dest) => {
2955                regs[dest] = string!(regs[tgt].as_str(str_pool).trim_start());
2956            }
2957            Instr::CallLibFunc(LibFunc::TrimRight, tgt, dest) => {
2958                regs[dest] = string!(regs[tgt].as_str(str_pool).trim_end());
2959            }
2960            Instr::CallLibFunc(LibFunc::TrimSequenceLeft, tgt, dest) => {
2961                let chars: Vec<char> = regs[args.pop_unchecked()]
2962                    .as_str(str_pool)
2963                    .chars()
2964                    .collect();
2965                regs[dest] = string!(regs[tgt].as_str(str_pool).trim_start_matches(&chars[..]));
2966            }
2967            Instr::CallLibFunc(LibFunc::TrimSequenceRight, tgt, dest) => {
2968                let chars: Vec<char> = regs[args.pop_unchecked()]
2969                    .as_str(str_pool)
2970                    .chars()
2971                    .collect();
2972                regs[dest] = string!(regs[tgt].as_str(str_pool).trim_end_matches(&chars[..]));
2973            }
2974            Instr::CallLibFunc(LibFunc::Repeat, tgt, dest) => {
2975                let reg = regs[tgt];
2976                if reg.is_string() {
2977                    let str = reg.as_str(str_pool);
2978                    let repeat_count = regs[args.pop_unchecked()].as_int();
2979                    regs[dest] = string!(str.repeat(repeat_count as usize));
2980                } else if reg.is_array() {
2981                    let repeat_count = regs[args.pop_unchecked()].as_int();
2982                    let array_id =
2983                        alloc_array(obj_pool, map_pool, str_pool, r, recursion_stack, gc);
2984                    obj_pool[array_id as usize] =
2985                        obj_pool[reg.as_array()].repeat(repeat_count as usize);
2986                    gc.note_bytes(obj_pool[array_id as usize].capacity() * LIST_ENTRY_BYTES);
2987                    regs[dest] = Data::array(array_id);
2988                }
2989            }
2990            Instr::CallLibFunc(LibFunc::Round, tgt, dest) => {
2991                regs[dest] = regs[tgt].as_float().round().into();
2992            }
2993            Instr::CallLibFunc(LibFunc::Abs, tgt, dest) => {
2994                let tgt = regs[tgt];
2995                regs[dest] = if tgt.is_float() {
2996                    tgt.as_float().abs().into()
2997                } else {
2998                    tgt.as_int().abs().into()
2999                }
3000            }
3001            Instr::CallLibFunc(LibFunc::Reverse, tgt, dest) => {
3002                regs[dest] = string!(regs[tgt].as_str(str_pool).chars().rev().collect::<String>());
3003            }
3004            Instr::CallLibFuncVoid(LibFuncVoid::Reverse, tgt, _) => {
3005                let id = regs[tgt].as_array();
3006                obj_pool.get_mut(id).reverse();
3007                if gc.marking() {
3008                    rescan_list(gc, id);
3009                }
3010            }
3011            Instr::CallLibFunc(LibFunc::SqrtFloat, tgt, dest) => {
3012                regs[dest] = regs[tgt].as_float().sqrt().into();
3013            }
3014            Instr::CallLibFunc(LibFunc::Float, tgt, dest) => {
3015                let reg = regs[tgt];
3016                if reg.is_int() {
3017                    regs[dest] = (reg.as_int() as f64).into();
3018                } else if reg.is_string() {
3019                    let str = reg.as_str(str_pool);
3020                    regs[dest] = if let Ok(f) = lexical_core::parse::<f64>(str.as_bytes()) {
3021                        f.into()
3022                    } else {
3023                        error_with_catch!(ErrType::InvalidFloat);
3024                    }
3025                }
3026            }
3027            Instr::CallLibFunc(LibFunc::Int, tgt, dest) => {
3028                let reg = regs[tgt];
3029                if reg.is_float() {
3030                    regs[dest] = (reg.as_float() as i64).into();
3031                } else if reg.is_string() {
3032                    let str = reg.as_str(str_pool);
3033                    regs[dest] = if let Ok(i) = lexical_core::parse::<i64>(str.as_bytes()) {
3034                        i.into()
3035                    } else {
3036                        cold_path();
3037                        error_with_catch!(ErrType::InvalidInt);
3038                    }
3039                }
3040            }
3041            Instr::CallLibFunc(LibFunc::Str, tgt, dest) => {
3042                let value = regs[tgt];
3043                regs[dest] = if value.is_int() {
3044                    let mut buffer = [0u8; i64::FORMATTED_SIZE_DECIMAL];
3045                    let digits = lexical_core::write(value.as_int(), &mut buffer);
3046                    string!(unsafe { str::from_utf8_unchecked(digits) })
3047                } else if value.is_float() {
3048                    string!(format_float(value.as_float()).as_str())
3049                } else if value.is_bool() {
3050                    Data::small_str(if value.as_bool() { "true" } else { "false" })
3051                } else if value.is_string() {
3052                    value
3053                } else {
3054                    string!(
3055                        value
3056                            .format(obj_pool, str_pool, map_pool, structs, enums, false)
3057                            .as_str()
3058                    )
3059                };
3060            }
3061            Instr::CallLibFunc(LibFunc::Bool, tgt, dest) => {
3062                let temp_tgt = regs[tgt];
3063                let str = temp_tgt.as_str(str_pool);
3064                regs[dest] = if str == "true" {
3065                    TRUE
3066                } else if str == "false" {
3067                    FALSE
3068                } else {
3069                    cold_path();
3070                    error_with_catch!(ErrType::InvalidBool);
3071                }
3072            }
3073            #[cfg(target_arch = "wasm32")]
3074            Instr::CallLibFunc(LibFunc::Input, _, _) => wasm_error("WASM does not support input()"),
3075            #[cfg(not(target_arch = "wasm32"))]
3076            Instr::CallLibFunc(LibFunc::Input, tgt, dest) => {
3077                let temp_tgt = regs[tgt];
3078                let str_msg = temp_tgt.as_str(str_pool);
3079                out!(*handle, "{str_msg}");
3080                // The prompt has no newline of its own, so it sits in the
3081                // buffer until something pushes it out. A lost prompt is not
3082                // worth ending the run over either.
3083                let _ = handle.flush();
3084                let mut line = String::new();
3085                std::io::stdin().read_line(&mut line).unwrap();
3086                regs[dest] = string!(line.trim_end_matches(['\n', '\r']));
3087            }
3088            Instr::CallLibFunc(LibFunc::Floor, tgt, dest) => {
3089                regs[dest] = regs[tgt].as_float().floor().into();
3090            }
3091            Instr::CallLibFunc(LibFunc::TheAnswer, _, dest) => {
3092                outln!(
3093                    *handle,
3094                    "The answer to the Ultimate Question of Life, the Universe, and Everything is 42."
3095                );
3096                regs[dest] = 42.into();
3097            }
3098            Instr::CallLibFunc(LibFunc::Len, tgt, dest) => {
3099                let reg = regs[tgt];
3100                if reg.is_array() {
3101                    regs[dest] = (obj_pool[reg.as_array()].len() as i64).into();
3102                } else if reg.is_string() {
3103                    regs[dest] = (reg.str_char_len(str_pool) as i64).into();
3104                } else if reg.is_map() {
3105                    regs[dest] = (map_pool[reg.as_map()].len() as i64).into();
3106                } else {
3107                    unsafe { unreachable_unchecked() }
3108                }
3109            }
3110            Instr::CallLibFunc(LibFunc::Keys, tgt, dest) => {
3111                let map_data = regs[tgt];
3112                let out_id = alloc_array(obj_pool, map_pool, str_pool, r, recursion_stack, gc);
3113                let keys: Vec<Data> = map_pool[map_data.as_map()].keys().copied().collect();
3114                let out = obj_pool.get_mut(out_id as usize);
3115                out.clear();
3116                out.extend(keys);
3117                gc.note_bytes(out.capacity() * LIST_ENTRY_BYTES);
3118                regs[dest] = Data::array(out_id);
3119            }
3120            Instr::CallLibFunc(LibFunc::Values, tgt, dest) => {
3121                let map_data = regs[tgt];
3122                let out_id = alloc_array(obj_pool, map_pool, str_pool, r, recursion_stack, gc);
3123                let vals: Vec<Data> = map_pool[map_data.as_map()].values().copied().collect();
3124                let out = obj_pool.get_mut(out_id as usize);
3125                out.clear();
3126                out.extend(vals);
3127                gc.note_bytes(out.capacity() * LIST_ENTRY_BYTES);
3128                regs[dest] = Data::array(out_id);
3129            }
3130            Instr::CallLibFunc(LibFunc::JsonParse, tgt, dest) => {
3131                let input = regs[tgt].as_str(str_pool).to_owned();
3132                match crate::json::json_parse(
3133                    &input,
3134                    obj_pool,
3135                    map_pool,
3136                    str_pool,
3137                    r,
3138                    recursion_stack,
3139                    gc,
3140                ) {
3141                    Ok(value) => regs[dest] = value,
3142                    Err(reason) => {
3143                        error_with_catch!(ErrType::JsonParse(reason));
3144                    }
3145                }
3146            }
3147            Instr::CallLibFunc(LibFunc::JsonStringify, tgt, dest) => {
3148                let json = crate::json::json_stringify(
3149                    regs[tgt], obj_pool, map_pool, str_pool, structs, enums,
3150                );
3151                regs[dest] = string!(json);
3152            }
3153            Instr::CallLibFunc(LibFunc::IsIntVal, tgt, dest) => {
3154                regs[dest] = regs[tgt].is_int().into();
3155            }
3156            Instr::CallLibFunc(LibFunc::IsFloatVal, tgt, dest) => {
3157                regs[dest] = regs[tgt].is_float().into();
3158            }
3159            Instr::CallLibFunc(LibFunc::IsStrVal, tgt, dest) => {
3160                regs[dest] = regs[tgt].is_string().into();
3161            }
3162            Instr::CallLibFunc(LibFunc::IsBoolVal, tgt, dest) => {
3163                regs[dest] = regs[tgt].is_bool().into();
3164            }
3165            Instr::CallLibFunc(LibFunc::IsListVal, tgt, dest) => {
3166                regs[dest] = regs[tgt].is_array().into();
3167            }
3168            Instr::CallLibFunc(LibFunc::IsMapVal, tgt, dest) => {
3169                regs[dest] = regs[tgt].is_map().into();
3170            }
3171            Instr::CallLibFunc(LibFunc::IsNullVal, tgt, dest) => {
3172                regs[dest] = regs[tgt].is_null().into();
3173            }
3174            Instr::CallLibFunc(LibFunc::AsIntVal, tgt, dest) => {
3175                let v = regs[tgt];
3176                if v.is_int() {
3177                    regs[dest] = v;
3178                } else {
3179                    error_with_catch!(ErrType::BadDowncast("int", v.type_name()));
3180                }
3181            }
3182            Instr::CallLibFunc(LibFunc::AsFloatVal, tgt, dest) => {
3183                let v = regs[tgt];
3184                if v.is_float() {
3185                    regs[dest] = v;
3186                } else {
3187                    error_with_catch!(ErrType::BadDowncast("float", v.type_name()));
3188                }
3189            }
3190            Instr::CallLibFunc(LibFunc::AsStrVal, tgt, dest) => {
3191                let v = regs[tgt];
3192                if v.is_string() {
3193                    regs[dest] = v;
3194                } else {
3195                    error_with_catch!(ErrType::BadDowncast("string", v.type_name()));
3196                }
3197            }
3198            Instr::CallLibFunc(LibFunc::AsBoolVal, tgt, dest) => {
3199                let v = regs[tgt];
3200                if v.is_bool() {
3201                    regs[dest] = v;
3202                } else {
3203                    error_with_catch!(ErrType::BadDowncast("bool", v.type_name()));
3204                }
3205            }
3206            Instr::CallLibFunc(LibFunc::AsListVal, tgt, dest) => {
3207                let v = regs[tgt];
3208                if v.is_array() {
3209                    regs[dest] = v;
3210                } else {
3211                    error_with_catch!(ErrType::BadDowncast("list", v.type_name()));
3212                }
3213            }
3214            Instr::CallLibFunc(LibFunc::AsMapVal, tgt, dest) => {
3215                let v = regs[tgt];
3216                if v.is_map() {
3217                    regs[dest] = v;
3218                } else {
3219                    error_with_catch!(ErrType::BadDowncast("map", v.type_name()));
3220                }
3221            }
3222            Instr::CallLibFunc(LibFunc::AsTypeVal, tgt, dest) => {
3223                let codes = &obj_pool[regs[args.pop_unchecked()].as_array()];
3224                let v = regs[tgt];
3225                if codes
3226                    .iter()
3227                    .any(|code| has_type_code(v, *code, obj_pool, map_pool))
3228                {
3229                    // A function value whose first slot says nowhere was never
3230                    // compiled for a call through a value: it reached the `any`
3231                    // with a bare parameter or a type parameter, and nothing
3232                    // said what a call would pass it.
3233                    if v.is_function() && !obj_pool[v.as_array()][0].is_int() {
3234                        error_with_catch!(ErrType::BadDowncast(
3235                            "function with annotated parameters",
3236                            "function"
3237                        ));
3238                    }
3239                    regs[dest] = v;
3240                } else {
3241                    let wanted = type_codes_text(codes, obj_pool, structs, enums);
3242                    error_with_catch!(ErrType::BadDowncast(&wanted, v.type_name()));
3243                }
3244            }
3245            Instr::CallLibFunc(LibFunc::IsTypeVal, tgt, dest) => {
3246                let codes = &obj_pool[regs[args.pop_unchecked()].as_array()];
3247                let v = regs[tgt];
3248                regs[dest] = codes
3249                    .iter()
3250                    .any(|code| has_type_code(v, *code, obj_pool, map_pool))
3251                    .into();
3252            }
3253            Instr::CallLibFunc(LibFunc::StartsWith, source_register, dest_register) => {
3254                regs[dest_register] = regs[source_register]
3255                    .as_str(str_pool)
3256                    .starts_with(regs[args.pop_unchecked()].as_str(str_pool))
3257                    .into();
3258            }
3259            Instr::CallLibFunc(LibFunc::EndsWith, source_register, dest_register) => {
3260                regs[dest_register] = regs[source_register]
3261                    .as_str(str_pool)
3262                    .ends_with(regs[args.pop_unchecked()].as_str(str_pool))
3263                    .into();
3264            }
3265            #[allow(clippy::no_effect_replace)]
3266            Instr::CallLibFunc(LibFunc::Replace, source_register, dest_register) => {
3267                regs[dest_register] = string!(regs[source_register].as_str(str_pool).replace(
3268                    regs[args.pop_unchecked()].as_str(str_pool),
3269                    regs[args.pop_unchecked()].as_str(str_pool),
3270                ));
3271            }
3272            Instr::CallLibFunc(LibFunc::Split, source_register, dest_register) => {
3273                let source = regs[source_register];
3274                let separator = unsafe { args.pop_unchecked() };
3275                if source.is_string() {
3276                    let output_id =
3277                        alloc_array(obj_pool, map_pool, str_pool, r, recursion_stack, gc);
3278                    // Each part's allocation can run the collector, so the
3279                    // list sits on the recursion stack, a root, while it is
3280                    // filled. The source and separator stay in their
3281                    // registers, so their text stays where it is.
3282                    recursion_stack.0.push(Data::array(output_id));
3283                    let source = source.as_str(str_pool);
3284                    let separator_data = regs[separator];
3285                    let separator = separator_data.as_str(str_pool);
3286                    obj_pool
3287                        .get_mut(output_id as usize)
3288                        .reserve(source.len() / 4);
3289                    let separator_len = separator.len();
3290                    if separator_len == 0 {
3291                        // An empty separator answers the characters. Matching
3292                        // it at every byte offset instead would report a match
3293                        // before the first character and after the last, and
3294                        // would cut a multi-byte character in half. A character
3295                        // is at most four bytes, so every part fits the inline
3296                        // string form and none reaches the pool.
3297                        let output = obj_pool.get_mut(output_id as usize);
3298                        for (byte_i, character) in source.char_indices() {
3299                            output.push(Data::small_str(
3300                                &source[byte_i..byte_i + character.len_utf8()],
3301                            ));
3302                        }
3303                    } else {
3304                        let mut i = 0;
3305                        for part_i in memmem::find_iter(source.as_bytes(), separator.as_bytes()) {
3306                            let part = string!(&source[i..part_i]);
3307                            obj_pool.get_mut(output_id as usize).push(part);
3308                            i = part_i + separator_len;
3309                        }
3310                        let part = string!(&source[i..]);
3311                        obj_pool.get_mut(output_id as usize).push(part);
3312                    }
3313                    recursion_stack.0.pop();
3314                    gc.note_bytes(obj_pool[output_id as usize].capacity() * LIST_ENTRY_BYTES);
3315                    regs[dest_register] = Data::array(output_id);
3316                } else if source.is_array() {
3317                    let source_array_id = source.as_array();
3318                    let separator = regs[separator];
3319                    let source_array = &obj_pool[source_array_id];
3320                    let source_len = source_array.len();
3321
3322                    let mut split_ranges: Vec<(usize, usize)> =
3323                        Vec::with_capacity(source_array.len() + 1);
3324
3325                    let mut start = 0;
3326                    for (idx, item) in source_array.iter().enumerate() {
3327                        if *item == separator {
3328                            split_ranges.push((start, idx));
3329                            start = idx + 1;
3330                        }
3331                    }
3332                    split_ranges.push((start, source_array.len()));
3333
3334                    // One array per range. Each goes onto the recursion stack
3335                    // as soon as it is made, because that stack is a
3336                    // collection root and a Rust-local buffer is not: the next
3337                    // range's allocation can run the collector, which would
3338                    // free the lists made before it and hand their slots out
3339                    // again.
3340                    let base = recursion_stack.len();
3341                    for (start, end) in split_ranges {
3342                        let dest_array_id =
3343                            alloc_array(obj_pool, map_pool, str_pool, r, recursion_stack, gc)
3344                                as usize;
3345                        unsafe {
3346                            if dest_array_id < source_array_id {
3347                                let (left, right) = obj_pool.split_at_mut(source_array_id);
3348                                left.get_unchecked_mut(dest_array_id).extend_from_slice(
3349                                    right.get_unchecked(0).get_unchecked(start..end),
3350                                );
3351                            } else {
3352                                let (left, right) = obj_pool.split_at_mut(dest_array_id);
3353                                right.get_unchecked_mut(0).extend_from_slice(
3354                                    left.get_unchecked(source_array_id)
3355                                        .get_unchecked(start..end),
3356                                );
3357                            }
3358                        }
3359                        recursion_stack.0.push(Data::array(dest_array_id as u32));
3360                    }
3361
3362                    let array_id =
3363                        alloc_array(obj_pool, map_pool, str_pool, r, recursion_stack, gc);
3364                    let parts = obj_pool.get_mut(array_id as usize);
3365                    parts.extend(recursion_stack.0.drain(base..));
3366                    gc.note_bytes(source_len * LIST_ENTRY_BYTES);
3367
3368                    regs[dest_register] = Data::array(array_id);
3369                }
3370            }
3371            Instr::CallLibFunc(LibFunc::Range, max, dest) => {
3372                let min = args.pop().map_or(0, |reg_id| regs[reg_id].as_int());
3373                let max = regs[max].as_int();
3374                let output_array_id =
3375                    alloc_array(obj_pool, map_pool, str_pool, r, recursion_stack, gc);
3376                let range_arr = obj_pool.get_mut(output_array_id as usize);
3377                range_arr.extend((min..max).map(Data::from));
3378                gc.note_bytes(range_arr.capacity() * LIST_ENTRY_BYTES);
3379                regs[dest] = Data::array(output_array_id);
3380            }
3381            Instr::CallLibFunc(LibFunc::JoinStringArray, tgt, dest) => {
3382                let temp_separator: Option<Data> = args.pop().map(|arg| regs[arg]);
3383                let separator = temp_separator.as_ref().map_or("", |d| d.as_str(str_pool));
3384                let array = &obj_pool[regs[tgt].as_array()];
3385                let total_len: usize = array
3386                    .iter()
3387                    .map(|x| x.as_str(str_pool).len())
3388                    .sum::<usize>()
3389                    + separator
3390                        .len()
3391                        .saturating_mul(array.len().saturating_sub(1));
3392                let mut output = String::with_capacity(total_len);
3393                for (i, x) in array.iter().enumerate() {
3394                    if i > 0 {
3395                        output.push_str(separator);
3396                    }
3397                    output.push_str(x.as_str(str_pool));
3398                }
3399                regs[dest] = string!(output);
3400            }
3401            // -----
3402            // FILE SYSTEM FUNCTIONS
3403            // -----
3404            Instr::CallLibFunc(LibFunc::FsRead, path, dest_reg_id) => {
3405                regs[dest_reg_id] =
3406                    string!(match fs::read_to_string(regs[path].as_str(str_pool)) {
3407                        Ok(p) => p,
3408                        Err(e) => {
3409                            cold_path();
3410                            error_with_catch!(ErrType::from(e.kind()));
3411                        }
3412                    });
3413            }
3414            Instr::CallLibFunc(LibFunc::FsExists, path, dest_reg_id) => {
3415                regs[dest_reg_id] = match fs::exists(regs[path].as_str(str_pool)) {
3416                    Ok(b) => b.into(),
3417                    Err(e) => {
3418                        error_with_catch!(ErrType::from(e.kind()));
3419                    }
3420                }
3421            }
3422            // Overwrites a file, will create it if it doesn't exist
3423            Instr::CallLibFuncVoid(LibFuncVoid::FsWrite, path, contents) => {
3424                if let Err(e) =
3425                    fs::write(regs[path].as_str(str_pool), regs[contents].as_str(str_pool))
3426                {
3427                    error_with_catch!(ErrType::from(e.kind()));
3428                }
3429            }
3430            // Appends to a file, will create if it doesn't exist
3431            Instr::CallLibFuncVoid(LibFuncVoid::FsAppend, path, contents) => {
3432                match fs::OpenOptions::new()
3433                    .append(true)
3434                    .create(true)
3435                    .open(regs[path].as_str(str_pool))
3436                {
3437                    Ok(mut f) => {
3438                        if let Err(e) = f.write_all(regs[contents].as_str(str_pool).as_bytes()) {
3439                            error_with_catch!(ErrType::from(e.kind()));
3440                        }
3441                    }
3442                    Err(e) => {
3443                        error_with_catch!(ErrType::from(e.kind()));
3444                    }
3445                }
3446            }
3447            // Deletes the file located at `path`, throwing an error if it doesn't exist.
3448            Instr::CallLibFuncVoid(LibFuncVoid::FsDelete, path, _) => {
3449                if let Err(e) = fs::remove_file(regs[path].as_str(str_pool)) {
3450                    error_with_catch!(ErrType::from(e.kind()));
3451                }
3452            }
3453            // Deletes the empty directory located at `path`
3454            Instr::CallLibFuncVoid(LibFuncVoid::FsDeleteDir, path, _) => {
3455                if let Err(e) = fs::remove_dir(regs[path].as_str(str_pool)) {
3456                    error_with_catch!(ErrType::from(e.kind()));
3457                }
3458            }
3459            #[cfg(target_arch = "wasm32")]
3460            Instr::CallLibFunc(LibFunc::Argv, _, dest) => {
3461                regs[dest] = Data::array(alloc_array(
3462                    obj_pool,
3463                    map_pool,
3464                    str_pool,
3465                    r,
3466                    recursion_stack,
3467                    gc,
3468                ))
3469            }
3470            #[cfg(not(target_arch = "wasm32"))]
3471            Instr::CallLibFunc(LibFunc::Argv, _, dest) => {
3472                // The strings are rooted on the recursion stack while the rest
3473                // are made, since each one's allocation can run the collector,
3474                // and the list is made last for the same reason.
3475                let base = recursion_stack.len();
3476                for arg in std::env::args().skip(crate::rt::argv_skip()) {
3477                    let s = string!(arg);
3478                    recursion_stack.0.push(s);
3479                }
3480                let array_id = alloc_array(obj_pool, map_pool, str_pool, r, recursion_stack, gc);
3481                let list = obj_pool.get_mut(array_id as usize);
3482                list.extend(recursion_stack.0.drain(base..));
3483                regs[dest] = Data::array(array_id);
3484            }
3485            Instr::CallLibFuncVoid(LibFuncVoid::Sort, tgt, _) => {
3486                let id = regs[tgt].as_array();
3487                if gc.marking() {
3488                    rescan_list(gc, id);
3489                }
3490                let array = obj_pool.get_mut(id);
3491                if !array.is_empty() {
3492                    if array[0].is_int() {
3493                        array.sort_unstable_by_key(|x| x.as_int());
3494                    } else if array[0].is_float() {
3495                        array.sort_unstable_by(|a, b| {
3496                            a.as_float()
3497                                .partial_cmp(&b.as_float())
3498                                .unwrap_or(std::cmp::Ordering::Equal)
3499                        });
3500                    } else if array[0].is_string() {
3501                        array.sort_unstable_by(|a, b| a.as_str(str_pool).cmp(b.as_str(str_pool)));
3502                    }
3503                }
3504            }
3505            Instr::StartErrorCatch(jmp_size, err_reg_id) => {
3506                error_handles.push(ErrorCatch {
3507                    catch_loc: (i as u32) + (jmp_size as u32),
3508                    error_reg: err_reg_id,
3509                    call_frames_len: call_frames.len() as u32,
3510                    args_len: args.len() as u32,
3511                    recursion_len: recursion_stack.len() as u32,
3512                });
3513            }
3514            Instr::StopErrorCatch => unsafe {
3515                error_handles.pop_unchecked();
3516            },
3517            Instr::ThrowError(error_reg_id) => {
3518                error_with_catch!(ErrType::Custom(regs[error_reg_id].as_str(str_pool)));
3519            }
3520            Instr::NativeExit => {
3521                i = RETURNED;
3522                break;
3523            }
3524            Instr::Halt(code) => {
3525                cold_path();
3526
3527                #[cfg(not(target_arch = "wasm32"))]
3528                if code != 0 {
3529                    // An exit status is a C `int`, so a wider code arrives at
3530                    // the operating system as its low 32 bits.
3531                    std::process::exit(regs[code].as_int() as i32);
3532                }
3533
3534                i = HALTED;
3535                break;
3536            }
3537            // The dispatch loop runs every other instruction itself.
3538            _ => unreachable!(),
3539        }
3540        i += 1;
3541    }
3542    i
3543}