Skip to main content

vm/
bytecode.rs

1use std::collections::{HashMap, hash_map};
2use std::fmt;
3use std::hash::{BuildHasherDefault, Hash, Hasher};
4use std::sync::{Arc, OnceLock};
5
6use crate::compiler::TypeSchema;
7
8pub type SharedString = Arc<String>;
9pub type SharedBytes = Arc<Vec<u8>>;
10pub type SharedArray = Arc<Vec<Value>>;
11pub type SharedMap = Arc<VmMap>;
12
13type VmMapStorage = HashMap<MapKey, Value, BuildHasherDefault<StableHasher>>;
14
15/// Runtime map storage for VM values.
16///
17/// Keys and values may be any runtime [`Value`]. Key equality is hybrid:
18/// scalars, strings, and bytes compare by value, while arrays and maps compare by
19/// heap-object identity. Float keys use canonicalized IEEE bits: `0.0` /
20/// `-0.0` are treated as the same key, while `NaN` keys only compare equal
21/// when their bit patterns match. Duplicate inserts overwrite the prior value
22/// for the same key.
23///
24/// Heap-backed keys remain stable after insertion. Values are reference-counted
25/// and container writes detach before mutation, so later writes through an
26/// alias create a new heap object instead of mutating a key already stored in
27/// the map.
28#[derive(Clone, Default)]
29pub struct VmMap {
30    entries: VmMapStorage,
31    cached_len: usize,
32}
33
34#[derive(Clone, Debug)]
35struct MapKey(Value);
36
37pub struct VmMapIter<'a> {
38    inner: hash_map::Iter<'a, MapKey, Value>,
39}
40
41pub struct VmMapIntoIter {
42    inner: hash_map::IntoIter<MapKey, Value>,
43}
44
45#[derive(Default)]
46pub(crate) struct StableHasher(u64);
47
48impl Hasher for StableHasher {
49    fn finish(&self) -> u64 {
50        self.0
51    }
52
53    fn write(&mut self, bytes: &[u8]) {
54        const OFFSET_BASIS: u64 = 0xcbf29ce484222325;
55        const PRIME: u64 = 0x100000001b3;
56
57        if self.0 == 0 {
58            self.0 = OFFSET_BASIS;
59        }
60        for byte in bytes {
61            self.0 ^= u64::from(*byte);
62            self.0 = self.0.wrapping_mul(PRIME);
63        }
64    }
65}
66
67impl VmMap {
68    pub fn new() -> Self {
69        Self::default()
70    }
71
72    pub fn from_entries(entries: Vec<(Value, Value)>) -> Self {
73        let mut out = Self::new();
74        for (key, value) in entries {
75            out.insert(key, value);
76        }
77        out
78    }
79
80    pub fn len(&self) -> usize {
81        debug_assert_eq!(self.cached_len, self.entries.len());
82        self.cached_len
83    }
84
85    pub fn is_empty(&self) -> bool {
86        self.len() == 0
87    }
88
89    pub fn iter(&self) -> VmMapIter<'_> {
90        VmMapIter {
91            inner: self.entries.iter(),
92        }
93    }
94
95    pub fn get(&self, key: &Value) -> Option<&Value> {
96        self.entries.get(&MapKey::new(key.clone()))
97    }
98
99    pub fn insert(&mut self, key: Value, value: Value) -> Option<Value> {
100        let replaced = self.entries.insert(MapKey::new(key), value);
101        self.cached_len = self.entries.len();
102        replaced
103    }
104
105    pub fn remove(&mut self, key: &Value) -> Option<Value> {
106        let removed = self.entries.remove(&MapKey::new(key.clone()));
107        self.cached_len = self.entries.len();
108        removed
109    }
110}
111
112#[allow(dead_code)]
113pub(crate) fn vm_map_len_field_offset() -> usize {
114    std::mem::offset_of!(VmMap, cached_len)
115}
116
117impl From<Vec<(Value, Value)>> for VmMap {
118    fn from(value: Vec<(Value, Value)>) -> Self {
119        Self::from_entries(value)
120    }
121}
122
123impl IntoIterator for VmMap {
124    type Item = (Value, Value);
125    type IntoIter = VmMapIntoIter;
126
127    fn into_iter(self) -> Self::IntoIter {
128        VmMapIntoIter {
129            inner: self.entries.into_iter(),
130        }
131    }
132}
133
134impl<'a> IntoIterator for &'a VmMap {
135    type Item = (&'a Value, &'a Value);
136    type IntoIter = VmMapIter<'a>;
137
138    fn into_iter(self) -> Self::IntoIter {
139        self.iter()
140    }
141}
142
143impl fmt::Debug for VmMap {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        f.debug_map().entries(self.iter()).finish()
146    }
147}
148
149impl PartialEq for VmMap {
150    fn eq(&self, other: &Self) -> bool {
151        self.entries == other.entries
152    }
153}
154
155impl Eq for VmMap {}
156
157impl MapKey {
158    fn new(value: Value) -> Self {
159        Self(value)
160    }
161
162    fn value(&self) -> &Value {
163        &self.0
164    }
165
166    fn into_value(self) -> Value {
167        self.0
168    }
169}
170
171impl PartialEq for MapKey {
172    fn eq(&self, other: &Self) -> bool {
173        map_key_eq(&self.0, &other.0)
174    }
175}
176
177impl Eq for MapKey {}
178
179impl Hash for MapKey {
180    fn hash<H: Hasher>(&self, state: &mut H) {
181        hash_map_key(&self.0, state);
182    }
183}
184
185impl<'a> Iterator for VmMapIter<'a> {
186    type Item = (&'a Value, &'a Value);
187
188    fn next(&mut self) -> Option<Self::Item> {
189        self.inner.next().map(|(key, value)| (key.value(), value))
190    }
191}
192
193impl Iterator for VmMapIntoIter {
194    type Item = (Value, Value);
195
196    fn next(&mut self) -> Option<Self::Item> {
197        self.inner
198            .next()
199            .map(|(key, value)| (key.into_value(), value))
200    }
201}
202
203fn hash_map_key(value: &Value, state: &mut impl Hasher) {
204    match value {
205        Value::Null => {
206            6u8.hash(state);
207        }
208        Value::Int(value) => {
209            0u8.hash(state);
210            value.hash(state);
211        }
212        Value::Float(value) => {
213            1u8.hash(state);
214            canonical_float_key_bits(*value).hash(state);
215        }
216        Value::Bool(value) => {
217            2u8.hash(state);
218            value.hash(state);
219        }
220        Value::String(value) => {
221            3u8.hash(state);
222            value.hash(state);
223        }
224        Value::Bytes(value) => {
225            4u8.hash(state);
226            value.hash(state);
227        }
228        Value::Array(values) => {
229            5u8.hash(state);
230            Arc::as_ptr(values).hash(state);
231        }
232        Value::Map(entries) => {
233            6u8.hash(state);
234            Arc::as_ptr(entries).hash(state);
235        }
236    }
237}
238
239fn map_key_eq(lhs: &Value, rhs: &Value) -> bool {
240    match (lhs, rhs) {
241        (Value::Null, Value::Null) => true,
242        (Value::Int(lhs), Value::Int(rhs)) => lhs == rhs,
243        (Value::Float(lhs), Value::Float(rhs)) => {
244            canonical_float_key_bits(*lhs) == canonical_float_key_bits(*rhs)
245        }
246        (Value::Bool(lhs), Value::Bool(rhs)) => lhs == rhs,
247        (Value::String(lhs), Value::String(rhs)) => lhs == rhs,
248        (Value::Bytes(lhs), Value::Bytes(rhs)) => lhs == rhs,
249        (Value::Array(lhs), Value::Array(rhs)) => Arc::ptr_eq(lhs, rhs),
250        (Value::Map(lhs), Value::Map(rhs)) => Arc::ptr_eq(lhs, rhs),
251        _ => false,
252    }
253}
254
255/// Hash a value structurally for VM-internal cache keys.
256///
257/// The hasher itself is a small deterministic 64-bit FNV-1a-style accumulator.
258/// Arrays hash recursively in order and maps hash recursively without caring
259/// about entry order, so the result is stable across allocations.
260#[allow(dead_code)]
261pub(crate) fn hash_value(value: &Value, state: &mut impl Hasher) {
262    match value {
263        Value::Null => {
264            6u8.hash(state);
265        }
266        Value::Int(value) => {
267            0u8.hash(state);
268            value.hash(state);
269        }
270        Value::Float(value) => {
271            1u8.hash(state);
272            canonical_float_key_bits(*value).hash(state);
273        }
274        Value::Bool(value) => {
275            2u8.hash(state);
276            value.hash(state);
277        }
278        Value::String(value) => {
279            3u8.hash(state);
280            value.hash(state);
281        }
282        Value::Bytes(value) => {
283            4u8.hash(state);
284            value.hash(state);
285        }
286        Value::Array(values) => {
287            5u8.hash(state);
288            values.len().hash(state);
289            for value in values.iter() {
290                hash_value(value, state);
291            }
292        }
293        Value::Map(entries) => {
294            6u8.hash(state);
295            entries.len().hash(state);
296            let mut entry_hashes = entries
297                .iter()
298                .map(|(key, value)| {
299                    let mut entry_hasher = StableHasher::default();
300                    hash_value(key, &mut entry_hasher);
301                    hash_value(value, &mut entry_hasher);
302                    entry_hasher.finish()
303                })
304                .collect::<Vec<_>>();
305            entry_hashes.sort_unstable();
306            for entry_hash in entry_hashes {
307                entry_hash.hash(state);
308            }
309        }
310    }
311}
312
313fn canonical_float_key_bits(value: f64) -> u64 {
314    if value == 0.0 {
315        0.0f64.to_bits()
316    } else {
317        value.to_bits()
318    }
319}
320
321#[derive(Clone, Debug)]
322pub enum Value {
323    Null,
324    Int(i64),
325    Float(f64),
326    Bool(bool),
327    String(SharedString),
328    Bytes(SharedBytes),
329    Array(SharedArray),
330    Map(SharedMap),
331}
332
333#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
334#[repr(u8)]
335pub enum ValueType {
336    Unknown = 0,
337    Null = 1,
338    Int = 2,
339    Float = 3,
340    Bool = 4,
341    String = 5,
342    Bytes = 6,
343    Array = 7,
344    Map = 8,
345}
346
347#[derive(Clone, Debug, Default, PartialEq, Eq)]
348pub struct TypeMap {
349    pub strict_types: bool,
350    pub local_types: Vec<ValueType>,
351    pub local_schemas: Vec<Option<TypeSchema>>,
352    pub callable_slots: Vec<bool>,
353    pub optional_slots: Vec<bool>,
354    pub operand_types: HashMap<usize, (ValueType, ValueType)>,
355}
356
357impl Value {
358    pub fn string(value: impl Into<String>) -> Self {
359        Self::String(Arc::new(value.into()))
360    }
361
362    pub fn array(values: Vec<Value>) -> Self {
363        Self::Array(Arc::new(values))
364    }
365
366    pub fn bytes(value: impl Into<Vec<u8>>) -> Self {
367        Self::Bytes(Arc::new(value.into()))
368    }
369
370    pub fn map(entries: Vec<(Value, Value)>) -> Self {
371        Self::Map(Arc::new(VmMap::from(entries)))
372    }
373
374    pub fn into_owned_string(self) -> Result<String, Self> {
375        match self {
376            Self::String(value) => Ok(unwrap_or_clone_shared(value)),
377            other => Err(other),
378        }
379    }
380
381    pub fn into_owned_array(self) -> Result<Vec<Value>, Self> {
382        match self {
383            Self::Array(values) => Ok(unwrap_or_clone_shared(values)),
384            other => Err(other),
385        }
386    }
387
388    pub fn into_owned_bytes(self) -> Result<Vec<u8>, Self> {
389        match self {
390            Self::Bytes(value) => Ok(unwrap_or_clone_shared(value)),
391            other => Err(other),
392        }
393    }
394
395    pub fn into_owned_map(self) -> Result<VmMap, Self> {
396        match self {
397            Self::Map(entries) => Ok(unwrap_or_clone_shared(entries)),
398            other => Err(other),
399        }
400    }
401}
402
403pub(crate) fn unwrap_or_clone_shared<T: Clone>(value: Arc<T>) -> T {
404    match Arc::try_unwrap(value) {
405        Ok(inner) => inner,
406        Err(shared) => (*shared).clone(),
407    }
408}
409
410impl PartialEq for Value {
411    fn eq(&self, other: &Self) -> bool {
412        match (self, other) {
413            (Self::Null, Self::Null) => true,
414            (Self::Int(lhs), Self::Int(rhs)) => lhs == rhs,
415            (Self::Float(lhs), Self::Float(rhs)) => lhs == rhs,
416            (Self::Bool(lhs), Self::Bool(rhs)) => lhs == rhs,
417            (Self::String(lhs), Self::String(rhs)) => lhs == rhs,
418            (Self::Bytes(lhs), Self::Bytes(rhs)) => lhs == rhs,
419            (Self::Array(lhs), Self::Array(rhs)) => lhs == rhs,
420            (Self::Map(lhs), Self::Map(rhs)) => lhs == rhs,
421            _ => false,
422        }
423    }
424}
425
426#[derive(Clone, Debug, PartialEq, Eq, Hash)]
427pub struct HostImport {
428    pub name: String,
429    pub arity: u8,
430    pub return_type: ValueType,
431}
432
433#[allow(dead_code)]
434#[derive(Debug)]
435pub(crate) struct DecodedInstructionData {
436    pub(crate) ldc_values: Box<[Option<Value>]>,
437    pub(crate) jump_targets: Box<[Option<usize>]>,
438    pub(crate) local_indices: Box<[Option<u8>]>,
439}
440
441impl DecodedInstructionData {
442    fn build(program: &Program) -> Self {
443        let mut ldc_values = vec![None; program.code.len()];
444        let mut jump_targets = vec![None; program.code.len()];
445        let mut local_indices = vec![None; program.code.len()];
446        let mut ip = 0usize;
447        while ip < program.code.len() {
448            let opcode = match OpCode::try_from(program.code[ip]) {
449                Ok(opcode) => opcode,
450                Err(_) => break,
451            };
452            match opcode {
453                OpCode::Ldc => {
454                    if let Some(raw_index) = read_u32_at(&program.code, ip + 1)
455                        && let Some(value) = program.constants.get(raw_index as usize)
456                    {
457                        ldc_values[ip] = Some(value.clone());
458                    }
459                }
460                OpCode::Br | OpCode::Brfalse => {
461                    if let Some(target) = read_u32_at(&program.code, ip + 1) {
462                        jump_targets[ip] = Some(target as usize);
463                    }
464                }
465                OpCode::Ldloc | OpCode::Stloc => {
466                    if let Some(index) = program.code.get(ip + 1).copied() {
467                        local_indices[ip] = Some(index);
468                    }
469                }
470                _ => {}
471            }
472            ip = ip.saturating_add(1 + opcode.operand_len());
473        }
474        Self {
475            ldc_values: ldc_values.into_boxed_slice(),
476            jump_targets: jump_targets.into_boxed_slice(),
477            local_indices: local_indices.into_boxed_slice(),
478        }
479    }
480}
481
482#[derive(Clone, Debug)]
483pub struct Program {
484    pub constants: Vec<Value>,
485    pub code: Vec<u8>,
486    pub local_count: usize,
487    pub imports: Vec<HostImport>,
488    pub debug: Option<crate::debug_info::DebugInfo>,
489    pub type_map: Option<TypeMap>,
490    #[allow(dead_code)]
491    decoded_instruction_data_cache: Arc<OnceLock<Arc<DecodedInstructionData>>>,
492    operand_type_hints_cache: Arc<OnceLock<Option<Arc<[u8]>>>>,
493}
494
495impl Program {
496    pub fn new(constants: Vec<Value>, code: Vec<u8>) -> Self {
497        let local_count = infer_local_count_from_code(&code);
498        Self {
499            constants,
500            code,
501            local_count,
502            imports: Vec::new(),
503            debug: None,
504            type_map: None,
505            decoded_instruction_data_cache: Arc::new(OnceLock::new()),
506            operand_type_hints_cache: Arc::new(OnceLock::new()),
507        }
508    }
509
510    pub fn with_debug(
511        constants: Vec<Value>,
512        code: Vec<u8>,
513        debug: Option<crate::debug_info::DebugInfo>,
514    ) -> Self {
515        let local_count = infer_local_count_from_code(&code);
516        Self {
517            constants,
518            code,
519            local_count,
520            imports: Vec::new(),
521            debug,
522            type_map: None,
523            decoded_instruction_data_cache: Arc::new(OnceLock::new()),
524            operand_type_hints_cache: Arc::new(OnceLock::new()),
525        }
526    }
527
528    pub fn with_imports_and_debug(
529        constants: Vec<Value>,
530        code: Vec<u8>,
531        imports: Vec<HostImport>,
532        debug: Option<crate::debug_info::DebugInfo>,
533    ) -> Self {
534        let local_count = infer_local_count_from_code(&code);
535        Self {
536            constants,
537            code,
538            local_count,
539            imports,
540            debug,
541            type_map: None,
542            decoded_instruction_data_cache: Arc::new(OnceLock::new()),
543            operand_type_hints_cache: Arc::new(OnceLock::new()),
544        }
545    }
546
547    pub fn with_local_count(mut self, local_count: usize) -> Self {
548        self.local_count = local_count;
549        self
550    }
551
552    pub fn with_type_map(mut self, type_map: TypeMap) -> Self {
553        self.type_map = Some(type_map);
554        self.operand_type_hints_cache = Arc::new(OnceLock::new());
555        self
556    }
557
558    #[allow(dead_code)]
559    pub(crate) fn shared_decoded_instruction_data(&self) -> Arc<DecodedInstructionData> {
560        Arc::clone(
561            self.decoded_instruction_data_cache
562                .get_or_init(|| Arc::new(DecodedInstructionData::build(self))),
563        )
564    }
565
566    #[allow(dead_code)]
567    pub(crate) fn shared_operand_type_hints(&self) -> Option<Arc<[u8]>> {
568        self.operand_type_hints_cache
569            .get_or_init(|| build_operand_type_hints(self.code.len(), self.type_map.as_ref()))
570            .clone()
571    }
572}
573
574#[allow(dead_code)]
575fn build_operand_type_hints(code_len: usize, type_map: Option<&TypeMap>) -> Option<Arc<[u8]>> {
576    let type_map = type_map?;
577    if type_map.operand_types.is_empty() {
578        return None;
579    }
580
581    let mut hints = vec![0u8; code_len];
582    for (offset, (lhs, rhs)) in &type_map.operand_types {
583        let Some(entry) = hints.get_mut(*offset) else {
584            continue;
585        };
586        *entry = (*lhs as u8) | ((*rhs as u8) << 4);
587    }
588    Some(Arc::from(hints.into_boxed_slice()))
589}
590
591#[allow(dead_code)]
592fn read_u32_at(code: &[u8], offset: usize) -> Option<u32> {
593    let bytes = code.get(offset..offset + 4)?;
594    Some(u32::from_le_bytes(bytes.try_into().ok()?))
595}
596
597fn infer_local_count_from_code(code: &[u8]) -> usize {
598    let mut ip = 0usize;
599    let mut max_local_index: Option<u8> = None;
600
601    while let Some(&opcode) = code.get(ip) {
602        ip += 1;
603        let Ok(opcode) = OpCode::try_from(opcode) else {
604            break;
605        };
606        let operand_len = opcode.operand_len();
607        if ip + operand_len > code.len() {
608            break;
609        }
610        match opcode {
611            OpCode::Ldloc | OpCode::Stloc => {
612                let index = code[ip];
613                max_local_index = Some(max_local_index.map_or(index, |prev| prev.max(index)));
614            }
615            _ => {}
616        }
617        ip += operand_len;
618    }
619
620    max_local_index.map_or(0, |index| index as usize + 1)
621}
622
623#[derive(Clone, Copy, Debug, PartialEq, Eq)]
624#[repr(u8)]
625pub enum OpCode {
626    Nop = 0x00,
627    Ret = 0x01,
628    Ldc = 0x02,
629    Add = 0x03,
630    Sub = 0x04,
631    Mul = 0x05,
632    Div = 0x06,
633    Neg = 0x07,
634    Ceq = 0x08,
635    Clt = 0x09,
636    Cgt = 0x0A,
637    Br = 0x0B,
638    Brfalse = 0x0C,
639    Pop = 0x0D,
640    Dup = 0x0E,
641    Ldloc = 0x0F,
642    Stloc = 0x10,
643    Call = 0x11,
644    Shl = 0x12,
645    Shr = 0x13,
646    Mod = 0x14,
647    And = 0x15,
648    Or = 0x16,
649    Not = 0x17,
650    Lshr = 0x18,
651}
652
653impl TryFrom<u8> for OpCode {
654    type Error = ();
655
656    fn try_from(value: u8) -> Result<Self, Self::Error> {
657        match value {
658            x if x == Self::Nop as u8 => Ok(Self::Nop),
659            x if x == Self::Ret as u8 => Ok(Self::Ret),
660            x if x == Self::Ldc as u8 => Ok(Self::Ldc),
661            x if x == Self::Add as u8 => Ok(Self::Add),
662            x if x == Self::Sub as u8 => Ok(Self::Sub),
663            x if x == Self::Mul as u8 => Ok(Self::Mul),
664            x if x == Self::Div as u8 => Ok(Self::Div),
665            x if x == Self::Neg as u8 => Ok(Self::Neg),
666            x if x == Self::Ceq as u8 => Ok(Self::Ceq),
667            x if x == Self::Clt as u8 => Ok(Self::Clt),
668            x if x == Self::Cgt as u8 => Ok(Self::Cgt),
669            x if x == Self::Br as u8 => Ok(Self::Br),
670            x if x == Self::Brfalse as u8 => Ok(Self::Brfalse),
671            x if x == Self::Pop as u8 => Ok(Self::Pop),
672            x if x == Self::Dup as u8 => Ok(Self::Dup),
673            x if x == Self::Ldloc as u8 => Ok(Self::Ldloc),
674            x if x == Self::Stloc as u8 => Ok(Self::Stloc),
675            x if x == Self::Call as u8 => Ok(Self::Call),
676            x if x == Self::Shl as u8 => Ok(Self::Shl),
677            x if x == Self::Shr as u8 => Ok(Self::Shr),
678            x if x == Self::Mod as u8 => Ok(Self::Mod),
679            x if x == Self::And as u8 => Ok(Self::And),
680            x if x == Self::Or as u8 => Ok(Self::Or),
681            x if x == Self::Not as u8 => Ok(Self::Not),
682            x if x == Self::Lshr as u8 => Ok(Self::Lshr),
683            _ => Err(()),
684        }
685    }
686}
687
688impl OpCode {
689    pub const fn operand_len(self) -> usize {
690        match self {
691            Self::Nop
692            | Self::Ret
693            | Self::Add
694            | Self::Sub
695            | Self::Mul
696            | Self::Div
697            | Self::Neg
698            | Self::Ceq
699            | Self::Clt
700            | Self::Cgt
701            | Self::Pop
702            | Self::Dup
703            | Self::Shl
704            | Self::Shr
705            | Self::Mod
706            | Self::And
707            | Self::Or
708            | Self::Not
709            | Self::Lshr => 0,
710            Self::Ldc | Self::Br | Self::Brfalse => 4,
711            Self::Ldloc | Self::Stloc => 1,
712            Self::Call => 3,
713        }
714    }
715
716    pub fn mnemonic(self) -> &'static str {
717        match self {
718            OpCode::Nop => "nop",
719            OpCode::Ret => "ret",
720            OpCode::Ldc => "ldc",
721            OpCode::Add => "add",
722            OpCode::Sub => "sub",
723            OpCode::Mul => "mul",
724            OpCode::Div => "div",
725            OpCode::Neg => "neg",
726            OpCode::Ceq => "ceq",
727            OpCode::Clt => "clt",
728            OpCode::Cgt => "cgt",
729            OpCode::Br => "br",
730            OpCode::Brfalse => "brfalse",
731            OpCode::Pop => "pop",
732            OpCode::Dup => "dup",
733            OpCode::Ldloc => "ldloc",
734            OpCode::Stloc => "stloc",
735            OpCode::Call => "call",
736            OpCode::Shl => "shl",
737            OpCode::Shr => "shr",
738            OpCode::Mod => "mod",
739            OpCode::And => "and",
740            OpCode::Or => "or",
741            OpCode::Not => "not",
742            OpCode::Lshr => "lshr",
743        }
744    }
745
746    pub fn parse_mnemonic(op: &str) -> Option<Self> {
747        match op {
748            "nop" => Some(OpCode::Nop),
749            "ret" => Some(OpCode::Ret),
750            "ldc" => Some(OpCode::Ldc),
751            "add" => Some(OpCode::Add),
752            "sub" => Some(OpCode::Sub),
753            "mul" => Some(OpCode::Mul),
754            "div" => Some(OpCode::Div),
755            "neg" => Some(OpCode::Neg),
756            "ceq" => Some(OpCode::Ceq),
757            "clt" => Some(OpCode::Clt),
758            "cgt" => Some(OpCode::Cgt),
759            "br" => Some(OpCode::Br),
760            "brfalse" => Some(OpCode::Brfalse),
761            "pop" => Some(OpCode::Pop),
762            "dup" => Some(OpCode::Dup),
763            "ldloc" => Some(OpCode::Ldloc),
764            "stloc" => Some(OpCode::Stloc),
765            "call" => Some(OpCode::Call),
766            "shl" => Some(OpCode::Shl),
767            "shr" => Some(OpCode::Shr),
768            "mod" => Some(OpCode::Mod),
769            "and" => Some(OpCode::And),
770            "or" => Some(OpCode::Or),
771            "not" => Some(OpCode::Not),
772            "lshr" => Some(OpCode::Lshr),
773            _ => None,
774        }
775    }
776}
777
778#[cfg(test)]
779mod tests {
780    use super::*;
781
782    #[test]
783    fn heap_value_clone_shares_backing() {
784        let string = Value::string("hello");
785        let string_clone = string.clone();
786        let (Value::String(lhs), Value::String(rhs)) = (&string, &string_clone) else {
787            panic!("expected string values");
788        };
789        assert!(Arc::ptr_eq(lhs, rhs));
790
791        let array = Value::array(vec![Value::Int(1), Value::Int(2)]);
792        let array_clone = array.clone();
793        let (Value::Array(lhs), Value::Array(rhs)) = (&array, &array_clone) else {
794            panic!("expected array values");
795        };
796        assert!(Arc::ptr_eq(lhs, rhs));
797
798        let bytes = Value::bytes([1u8, 2, 3]);
799        let bytes_clone = bytes.clone();
800        let (Value::Bytes(lhs), Value::Bytes(rhs)) = (&bytes, &bytes_clone) else {
801            panic!("expected bytes values");
802        };
803        assert!(Arc::ptr_eq(lhs, rhs));
804
805        let map = Value::map(vec![(Value::string("k"), Value::Int(9))]);
806        let map_clone = map.clone();
807        let (Value::Map(lhs), Value::Map(rhs)) = (&map, &map_clone) else {
808            panic!("expected map values");
809        };
810        assert!(Arc::ptr_eq(lhs, rhs));
811    }
812
813    #[test]
814    fn bytes_map_key_uses_value_lookup() {
815        let key = Value::bytes([0x01u8, 0x02, 0x03]);
816        let expected = Value::Bool(true);
817
818        let mut map = VmMap::new();
819        map.insert(key, expected.clone());
820
821        assert_eq!(
822            map.get(&Value::bytes([0x01u8, 0x02, 0x03])),
823            Some(&expected)
824        );
825        assert_eq!(map.get(&Value::bytes([0x01u8, 0x02, 0x04])), None);
826    }
827
828    #[test]
829    fn composite_map_key_remains_stable_after_alias_detach() {
830        let source_key = Value::array(vec![Value::Int(1), Value::Int(2)]);
831        let alias = source_key.clone();
832        let lookup_key = source_key.clone();
833        let expected = Value::string("kept");
834
835        let mut map = VmMap::new();
836        map.insert(source_key, expected.clone());
837
838        let mutated_alias = match alias {
839            Value::Array(values) => {
840                let mut owned = unwrap_or_clone_shared(values);
841                owned[0] = Value::Int(9);
842                Value::array(owned)
843            }
844            other => panic!("expected array alias, got {other:?}"),
845        };
846
847        assert_eq!(map.get(&lookup_key), Some(&expected));
848        assert_eq!(
849            map.get(&Value::array(vec![Value::Int(1), Value::Int(2)])),
850            None
851        );
852        assert_eq!(map.get(&mutated_alias), None);
853    }
854
855    #[test]
856    fn nested_map_keys_use_identity_lookup() {
857        let nested_key = Value::map(vec![
858            (Value::string("a"), Value::Int(1)),
859            (Value::string("b"), Value::Int(2)),
860        ]);
861        let lookup_key = nested_key.clone();
862        let structural_peer = Value::map(vec![
863            (Value::string("b"), Value::Int(2)),
864            (Value::string("a"), Value::Int(1)),
865        ]);
866        let expected = Value::Bool(true);
867
868        let mut map = VmMap::new();
869        map.insert(nested_key, expected.clone());
870
871        assert_eq!(map.get(&lookup_key), Some(&expected));
872        assert_eq!(map.get(&structural_peer), None);
873    }
874
875    #[test]
876    fn vm_map_cached_len_stays_in_sync() {
877        let mut map = VmMap::new();
878        assert_eq!(map.len(), 0);
879
880        assert_eq!(map.insert(Value::string("a"), Value::Int(1)), None);
881        assert_eq!(map.len(), 1);
882
883        assert_eq!(
884            map.insert(Value::string("a"), Value::Int(2)),
885            Some(Value::Int(1))
886        );
887        assert_eq!(map.len(), 1);
888
889        assert_eq!(map.insert(Value::string("b"), Value::Int(3)), None);
890        assert_eq!(map.len(), 2);
891
892        assert_eq!(map.remove(&Value::string("missing")), None);
893        assert_eq!(map.len(), 2);
894
895        assert_eq!(map.remove(&Value::string("a")), Some(Value::Int(2)));
896        assert_eq!(map.len(), 1);
897    }
898}