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
use crate::{GuardedArgs, Hash, Protocol, Stack, UnitFn, Value, Vm, VmError, VmErrorKind};

/// Trait used for integrating an instance function call.
pub(crate) trait ProtocolCaller {
    /// Call the given protocol function.
    fn call_protocol_fn<A>(
        self,
        protocol: Protocol,
        target: Value,
        args: A,
    ) -> Result<Value, VmError>
    where
        A: GuardedArgs;
}

/// Use the global environment caller.
///
/// This allocates its own stack and virtual machine for the call.
pub(crate) struct EnvProtocolCaller;

impl ProtocolCaller for EnvProtocolCaller {
    fn call_protocol_fn<A>(
        self,
        protocol: Protocol,
        target: Value,
        args: A,
    ) -> Result<Value, VmError>
    where
        A: GuardedArgs,
    {
        return crate::env::with(|context, unit| {
            let count = args.count() + 1;
            let hash = Hash::instance_function(target.type_hash()?, protocol.hash);

            if let Some(UnitFn::Offset {
                offset,
                args: expected,
                call,
            }) = unit.lookup(hash)
            {
                check_args(count, expected)?;

                let mut stack = Stack::with_capacity(count);
                stack.push(target);

                // Safety: We hold onto the guard until the vm has completed.
                let _guard = unsafe { args.unsafe_into_stack(&mut stack)? };

                let mut vm = Vm::new_with_stack(context.clone(), unit.clone(), stack);
                vm.set_ip(offset);
                return call.call_with_vm(vm);
            }

            let handler = match context.lookup(hash) {
                Some(handler) => handler,
                None => return Err(VmError::from(VmErrorKind::MissingFunction { hash })),
            };

            let mut stack = Stack::with_capacity(count);
            stack.push(target);

            // Safety: We hold onto the guard until the vm has completed.
            let _guard = unsafe { args.unsafe_into_stack(&mut stack)? };

            handler(&mut stack, count)?;
            Ok(stack.pop()?)
        });

        /// Check that arguments matches expected or raise the appropriate error.
        fn check_args(args: usize, expected: usize) -> Result<(), VmError> {
            if args != expected {
                return Err(VmError::from(VmErrorKind::BadArgumentCount {
                    actual: args,
                    expected,
                }));
            }

            Ok(())
        }
    }
}

impl ProtocolCaller for &mut Vm {
    fn call_protocol_fn<A>(
        self,
        protocol: Protocol,
        target: Value,
        args: A,
    ) -> Result<Value, VmError>
    where
        A: GuardedArgs,
    {
        if !self.call_instance_fn(target, protocol, args)? {
            return Err(VmError::from(VmErrorKind::MissingFunction {
                hash: protocol.hash,
            }));
        }

        Ok(self.stack.pop()?)
    }
}