1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use std::fmt;
use std::hash::{Hash, Hasher};

use crate::boxed::refs::Gc;
use crate::boxed::*;
use crate::task;

/// Opaque type for a function's captures
///
/// This has a meaning specific to the implementation of the function. This may be a dummy value
/// (typically [`Nil`]) for functions that don't capture, a single boxed value or a collection of
/// multiple boxed values. The only external contract is that it must be a boxed value to allow for
/// garbage collection.
pub type Captures = Gc<Any>;

/// Entry point for executing a function
pub type ThunkEntry = extern "C" fn(&mut task::Task, Captures, Gc<Any>) -> Gc<Any>;

/// Boxed function value with optional captures
///
/// This is typically used in places where functions are used as values or stored in collections.
/// For example, placing a function in a list will create a `FunThunk`. When taking an function as a
/// parameter to an RFI function it's typically better to use a typed
/// [`callback::Callback`](crate::callback::Callback).
#[repr(C, align(16))]
pub struct FunThunk {
    header: Header,
    pub(crate) captures: Captures,
    entry: ThunkEntry,
}

impl Boxed for FunThunk {}
impl UniqueTagged for FunThunk {}

impl FunThunk {
    /// Constructs a new function value with the given captures and entry point
    pub fn new(heap: &mut impl AsHeap, captures: Captures, entry: ThunkEntry) -> Gc<FunThunk> {
        heap.as_heap_mut().place_box(FunThunk {
            header: Self::TYPE_TAG.to_heap_header(Self::size()),
            captures,
            entry,
        })
    }

    /// Returns the box size for functions
    pub fn size() -> BoxSize {
        BoxSize::Size32
    }

    /// Applies this function on the passed task with the given arguments
    pub fn apply(&self, task: &mut task::Task, arg_list: Gc<Any>) -> Gc<Any> {
        (self.entry)(task, self.captures, arg_list)
    }
}

impl PartialEq for FunThunk {
    fn eq(&self, _: &FunThunk) -> bool {
        // There is no reliable way to compare functions so they're always inequal
        false
    }
}

impl Eq for FunThunk {}

impl Hash for FunThunk {
    fn hash<H: Hasher>(&self, state: &mut H) {
        Self::TYPE_TAG.hash(state);
        state.write_usize(self as *const _ as usize);
    }
}

impl fmt::Debug for FunThunk {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        write!(formatter, "FunThunk({:p})", self)
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::boxed;
    use crate::boxed::heap::Heap;
    use std::mem;

    extern "C" fn identity_entry(
        _: &mut task::Task,
        _captures: Captures,
        rest: Gc<Any>,
    ) -> Gc<Any> {
        rest
    }

    extern "C" fn return_42_entry(
        task: &mut task::Task,
        _captures: Captures,
        _rest: Gc<Any>,
    ) -> Gc<Any> {
        Int::new(task, 32).as_any_ref()
    }

    #[test]
    fn sizes() {
        assert_eq!(32, mem::size_of::<FunThunk>());
    }

    #[test]
    fn equality() {
        let mut heap = Heap::empty();

        let nil_captures = boxed::NIL_INSTANCE.as_any_ref();
        let boxed_identity1 = FunThunk::new(&mut heap, nil_captures, identity_entry);
        let boxed_identity2 = FunThunk::new(&mut heap, nil_captures, identity_entry);
        let boxed_return = FunThunk::new(&mut heap, nil_captures, return_42_entry);

        assert_ne!(boxed_identity1, boxed_return);
        // We use pointer identity for now
        assert_ne!(boxed_identity1, boxed_identity2);
    }
}