rune/core/object/
func.rs

1use super::{
2    super::gc::{Block, Context},
3    CloneIn, IntoObject, LispVec, ObjCell, display_slice,
4};
5use super::{Object, WithLifetime};
6use crate::{
7    core::{
8        env::Env,
9        gc::{GcHeap, Rt, Slot},
10    },
11    derive_GcMoveable,
12};
13use anyhow::{Result, bail, ensure};
14use rune_macros::Trace;
15use std::fmt::{self, Debug, Display};
16
17#[derive(PartialEq, Eq, Trace)]
18pub(crate) struct ByteFnPrototype {
19    #[no_trace]
20    pub(crate) args: FnArgs,
21    #[no_trace]
22    pub(crate) depth: usize,
23    #[no_trace]
24    pub(super) op_codes: Box<[u8]>,
25    // TODO: remove a level of pointer indirection here.
26    pub(super) constants: Slot<&'static LispVec>,
27}
28
29/// A function implemented in lisp. Note that all functions are byte compiled,
30/// so this contains the byte-code representation of the function.
31#[derive(PartialEq, Eq, Trace)]
32pub(crate) struct ByteFn(GcHeap<ByteFnPrototype>);
33
34derive_GcMoveable!(ByteFn);
35
36impl std::ops::Deref for ByteFn {
37    type Target = ByteFnPrototype;
38
39    fn deref(&self) -> &Self::Target {
40        &self.0
41    }
42}
43
44define_unbox!(ByteFn, Func, &'ob ByteFn);
45
46impl ByteFn {
47    pub(in crate::core) fn new(inner: ByteFnPrototype, constant: bool) -> ByteFn {
48        ByteFn(GcHeap::new(inner, constant))
49    }
50    // SAFETY: The caller must ensure that the constants are part of the same
51    // block as the bytecode function. Otherwise they will be collected and we
52    // will have a dangling pointer. Also this type must immediatly be put into
53    // the GC heap, because holding it past garbage collections is unsafe.
54    pub(crate) unsafe fn make(
55        op_codes: &[u8],
56        consts: &LispVec,
57        args: FnArgs,
58        depth: usize,
59    ) -> ByteFnPrototype {
60        let op_codes = op_codes.to_vec().into_boxed_slice();
61        #[cfg(miri)]
62        {
63            // TODO: the opcodes live outside of the heap because we are relying
64            // on them having a stable address. Therefore they get leaked. We
65            // need to find some way to address this.
66            unsafe extern "Rust" {
67                fn miri_static_root(ptr: *const u8);
68            }
69            let ptr: *const u8 = op_codes.as_ptr();
70            miri_static_root(ptr)
71        }
72        ByteFnPrototype {
73            constants: unsafe { Slot::new(consts.with_lifetime()) },
74            op_codes,
75            args,
76            depth,
77        }
78    }
79}
80
81impl ByteFnPrototype {
82    pub(crate) fn codes(&self) -> &[u8] {
83        &self.op_codes
84    }
85
86    pub(crate) fn consts<'ob>(&'ob self) -> &'ob [Object<'ob>] {
87        unsafe { std::mem::transmute::<&'ob [ObjCell], &'ob [Object<'ob>]>(&self.constants) }
88    }
89
90    pub(crate) fn index<'ob>(&self, index: usize, cx: &'ob Context) -> Option<Object<'ob>> {
91        match index {
92            0 => Some((self.args.into_arg_spec() as i64).into()),
93            1 => Some(cx.add(self.codes().to_vec())),
94            2 => Some(cx.add(self.consts())),
95            3 => Some(self.depth.into()),
96            _ => None,
97        }
98    }
99
100    pub(crate) const fn len(&self) -> usize {
101        4
102    }
103}
104
105impl<'new> CloneIn<'new, &'new Self> for ByteFn {
106    fn clone_in<const C: bool>(&self, bk: &'new Block<C>) -> super::Gc<&'new Self> {
107        let constants = self.constants.clone_in(bk);
108        let byte_fn =
109            unsafe { ByteFn::make(&self.op_codes, constants.untag(), self.args, self.depth) };
110        byte_fn.into_obj(bk)
111    }
112}
113
114impl Display for ByteFn {
115    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
116        let spec = self.args.into_arg_spec();
117        let code = display_slice(&self.op_codes);
118        let consts = display_slice(&self.constants);
119        let depth = self.depth;
120        write!(f, "#[{spec} {code} {consts} {depth}]")
121    }
122}
123
124impl Debug for ByteFn {
125    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126        f.debug_struct("ByteFn")
127            .field("args", &self.args)
128            .field("op_code", &self.op_codes)
129            .field("constants", &self.constants)
130            .finish_non_exhaustive()
131    }
132}
133
134/// Argument requirments to a function.
135#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
136pub(crate) struct FnArgs {
137    /// a &rest argument.
138    pub(crate) rest: bool,
139    /// minimum required arguments.
140    pub(crate) required: u16,
141    /// &optional arguments.
142    pub(crate) optional: u16,
143    /// If this function is advised.
144    pub(crate) advice: bool,
145}
146
147impl FnArgs {
148    pub(crate) fn from_arg_spec(spec: i64) -> Result<Self> {
149        // The spec is an integer of the form NNNNNNNRMMMMMMM where the 7bit
150        // MMMMMMM specifies the minimum number of arguments, the 7-bit NNNNNNN
151        // specifies the maximum number of arguments (ignoring &rest) and the R
152        // bit specifies whether there is a &rest argument to catch the
153        // left-over arguments.
154        ensure!(spec >= 0, "Invalid bytecode argument spec: bits out of range");
155        ensure!(spec <= 0x7FFF, "Invalid bytecode argument spec: bits out of range");
156        let spec = spec as u16;
157        let required = spec & 0x7F;
158        let max = (spec >> 8) & 0x7F;
159        let Some(optional) = max.checked_sub(required) else {
160            bail!("Invalid bytecode argument spec: max of {max} was smaller then min {required}")
161        };
162        let rest = spec & 0x80 != 0;
163        Ok(FnArgs { required, optional, rest, advice: false })
164    }
165
166    pub(crate) fn into_arg_spec(self) -> u64 {
167        let mut spec = self.required;
168        let max = self.required + self.optional;
169        spec |= max << 8;
170        spec |= u16::from(self.rest) << 7;
171        u64::from(spec)
172    }
173}
174
175pub(crate) type BuiltInFn =
176    for<'ob> fn(usize, &mut Rt<Env>, &'ob mut Context) -> Result<Object<'ob>>;
177
178#[derive(Eq)]
179pub(crate) struct SubrFn {
180    pub(crate) subr: BuiltInFn,
181    pub(crate) args: FnArgs,
182    pub(crate) name: &'static str,
183}
184define_unbox!(SubrFn, Func, &'ob SubrFn);
185
186impl SubrFn {
187    pub(crate) fn call<'ob>(
188        &self,
189        arg_cnt: usize,
190        env: &mut Rt<Env>,
191        cx: &'ob mut Context,
192    ) -> Result<Object<'ob>> {
193        (self.subr)(arg_cnt, env, cx)
194    }
195}
196
197impl<'new> WithLifetime<'new> for &SubrFn {
198    type Out = &'new SubrFn;
199
200    unsafe fn with_lifetime(self) -> Self::Out {
201        &*(self as *const SubrFn)
202    }
203}
204
205impl Display for SubrFn {
206    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
207        write!(f, "#<subr {}>", &self.name)
208    }
209}
210
211impl Debug for SubrFn {
212    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
213        write!(f, "{self}")
214    }
215}
216
217impl PartialEq for SubrFn {
218    #[expect(clippy::fn_to_numeric_cast_any)]
219    fn eq(&self, other: &Self) -> bool {
220        let lhs = self.subr as *const BuiltInFn;
221        let rhs = other.subr as *const BuiltInFn;
222        lhs == rhs
223    }
224}
225
226#[cfg(test)]
227mod test {
228    use super::*;
229
230    fn check_arg_spec(spec: i64) {
231        assert_eq!(spec, FnArgs::from_arg_spec(spec).unwrap().into_arg_spec().try_into().unwrap());
232    }
233
234    #[test]
235    fn test_arg_spec() {
236        check_arg_spec(0);
237        check_arg_spec(257);
238        check_arg_spec(513);
239        check_arg_spec(128);
240        check_arg_spec(771);
241
242        assert!(FnArgs::from_arg_spec(12345).is_err());
243        assert!(FnArgs::from_arg_spec(1).is_err());
244        assert!(FnArgs::from_arg_spec(0xFFFF).is_err());
245    }
246}