monkey-gc 0.15.0

QuickJS-style GC runtime for Monkey (bytecode VM with cycle collector)
Documentation
#[cfg(test)]
mod tests {
    use std::collections::HashMap;
    use std::rc::Rc;

    use object::Object;

    use crate::value::{
        alloc_value, export_object, get_value, import_object, HashKey, Value, ValueCell,
    };
    use crate::GcHeap;

    #[test]
    fn import_export_integer_roundtrip() {
        let mut heap = GcHeap::new();
        let original = Object::Integer(42);
        let reference = import_object(&mut heap, &original);
        assert_eq!(export_object(&heap, reference), original);
    }

    #[test]
    fn import_export_string_roundtrip() {
        let mut heap = GcHeap::new();
        let original = Object::String("monkey".to_string());
        let reference = import_object(&mut heap, &original);
        assert_eq!(export_object(&heap, reference), original);
    }

    #[test]
    fn import_export_array_roundtrip() {
        let mut heap = GcHeap::new();
        let original = Object::Array(vec![
            Rc::new(Object::Integer(1)),
            Rc::new(Object::String("two".to_string())),
            Rc::new(Object::Boolean(true)),
        ]);
        let reference = import_object(&mut heap, &original);
        assert_eq!(export_object(&heap, reference), original);
    }

    #[test]
    fn import_export_hash_roundtrip() {
        let mut heap = GcHeap::new();
        let original = Object::Hash(
            vec![
                (Rc::new(Object::Integer(1)), Rc::new(Object::Integer(10))),
                (
                    Rc::new(Object::String("k".to_string())),
                    Rc::new(Object::String("v".to_string())),
                ),
            ]
            .into_iter()
            .collect::<HashMap<_, _>>(),
        );
        let reference = import_object(&mut heap, &original);
        assert_eq!(export_object(&heap, reference), original);
    }

    #[test]
    fn import_export_nested_array_roundtrip() {
        let mut heap = GcHeap::new();
        let original = Object::Array(vec![Rc::new(Object::Array(vec![
            Rc::new(Object::Integer(1)),
            Rc::new(Object::Integer(2)),
        ]))]);
        let reference = import_object(&mut heap, &original);
        assert_eq!(export_object(&heap, reference), original);
    }

    #[test]
    fn hash_key_from_value_matches_object() {
        let mut heap = GcHeap::new();
        for object in [
            Object::Integer(7),
            Object::Boolean(false),
            Object::String("x".to_string()),
        ] {
            let reference = import_object(&mut heap, &object);
            assert_eq!(
                HashKey::from_value(get_value(&heap, reference)),
                HashKey::from_object(&object)
            );
        }
    }

    #[test]
    fn alloc_value_increments_child_refcounts() {
        let mut heap = GcHeap::new();
        let child = alloc_value(&mut heap, Value::Integer(1));
        assert_eq!(heap.ref_count(child), 1);

        let parent = alloc_value(&mut heap, Value::Array(vec![child]));
        assert_eq!(heap.ref_count(child), 2);

        heap.free(parent);
        assert_eq!(heap.ref_count(child), 1);
    }

    #[test]
    fn import_object_releases_temporary_child_refs() {
        let mut heap = GcHeap::new();
        let original = Object::Array(vec![Rc::new(Object::Array(vec![
            Rc::new(Object::Integer(1)),
            Rc::new(Object::Integer(2)),
        ]))]);

        let root = import_object(&mut heap, &original);
        let nested = match get_value(&heap, root) {
            Value::Array(items) => items[0],
            other => panic!("expected root array, got {:?}", other),
        };
        let leaves = match get_value(&heap, nested) {
            Value::Array(items) => items.clone(),
            other => panic!("expected nested array, got {:?}", other),
        };

        assert_eq!(heap.ref_count(root), 1);
        assert_eq!(heap.ref_count(nested), 1);
        for leaf in &leaves {
            assert_eq!(heap.ref_count(*leaf), 1);
        }

        heap.free(root);
        assert!(!heap.exists(root));
        assert!(!heap.exists(nested));
        for leaf in leaves {
            assert!(!heap.exists(leaf));
        }
    }

    #[test]
    fn value_cycle_collected_by_gc() {
        let mut heap = GcHeap::new();
        let node_a = alloc_value(&mut heap, Value::Array(vec![]));
        let node_b = alloc_value(&mut heap, Value::Array(vec![node_a]));

        let node_b_edge = heap.dup(node_b);
        match &mut heap
            .runtime_mut()
            .object_downcast_mut::<ValueCell>(node_a.0)
            .expect("node_a should be a ValueCell")
            .value
        {
            Value::Array(items) => items.push(node_b_edge),
            other => panic!("expected node_a array, got {:?}", other),
        }

        heap.free(node_a);
        heap.free(node_b);
        heap.run_gc();
        assert!(!heap.exists(node_a));
        assert!(!heap.exists(node_b));
    }
}