Skip to main content

pipa/object/
function.rs

1use crate::compiler::location::LineNumberTable;
2use crate::host::func::HostFunc;
3use crate::object::object::JSObject;
4use crate::runtime::atom::Atom;
5use crate::runtime::context::JSContext;
6use crate::util::FxHashMap;
7use crate::value::JSValue;
8use std::cell::Cell;
9use std::rc::Rc;
10
11const FLAG_IS_ARROW: u8 = 1 << 0;
12const FLAG_IS_ASYNC: u8 = 1 << 1;
13const FLAG_IS_GENERATOR: u8 = 1 << 2;
14const FLAG_IS_BUILTIN: u8 = 1 << 3;
15const FLAG_USES_ARGUMENTS: u8 = 1 << 4;
16const FLAG_IS_STRICT: u8 = 1 << 5;
17
18const FLAG_HAS_SYMBOL_ON_BASE: u8 = 1 << 6;
19const FLAG_BUILTIN_NEEDS_THIS: u8 = 1 << 7;
20
21#[derive(Debug, Clone, Default)]
22pub struct UpvalueData {
23    pub upvalues: FxHashMap<Atom, JSValue>,
24    pub upvalue_cells: FxHashMap<Atom, Rc<Cell<JSValue>>>,
25    pub upvalue_local_indices: FxHashMap<Atom, usize>,
26    pub upvalue_slots: Vec<Rc<Cell<JSValue>>>,
27    pub upvalue_slot_atoms: Vec<Atom>,
28}
29
30impl UpvalueData {
31    pub fn new() -> Self {
32        Self::default()
33    }
34}
35
36#[repr(C)]
37pub struct JSFunction {
38    pub base: JSObject,
39    pub name: Atom,
40    pub param_count: u32,
41    pub locals_count: u32,
42    flags: u8,
43    pub arity: u32,
44
45    pub builtin_atom: Option<Atom>,
46
47    pub builtin_func: Option<HostFunc>,
48    pub upvalues: Option<Box<UpvalueData>>,
49
50    pub bytecode: Option<crate::compiler::opcode::Bytecode>,
51
52    pub shared_nb_for_ic: Option<std::sync::Arc<crate::compiler::opcode::NestedBytecode>>,
53
54    pub cached_prototype_ptr: *mut crate::object::object::JSObject,
55
56    pub source_filename: String,
57    pub line_number_table: Option<std::sync::Arc<LineNumberTable>>,
58    pub source_text: Option<String>,
59}
60
61impl JSFunction {
62    pub fn new() -> Self {
63        JSFunction {
64            base: JSObject::new_function(),
65            name: Atom(0),
66            param_count: 0,
67            locals_count: 256,
68            flags: 0,
69            arity: 0,
70            builtin_atom: None,
71            builtin_func: None,
72            upvalues: None,
73            bytecode: None,
74            shared_nb_for_ic: None,
75            cached_prototype_ptr: std::ptr::null_mut(),
76            source_filename: String::new(),
77            line_number_table: None,
78            source_text: None,
79        }
80    }
81
82    pub fn new_builtin(name: Atom, arity: u32) -> Self {
83        let mut f = JSFunction::new();
84        f.name = name;
85        f.flags = FLAG_IS_BUILTIN;
86        f.arity = arity;
87        f
88    }
89
90    pub fn with_upvalues(mut self, upvalues_map: FxHashMap<Atom, JSValue>) -> Self {
91        let mut data = UpvalueData::new();
92        data.upvalues = upvalues_map;
93        self.upvalues = Some(Box::new(data));
94        self
95    }
96
97    pub fn is_callable(&self) -> bool {
98        true
99    }
100
101    #[inline(always)]
102    pub fn is_arrow(&self) -> bool {
103        self.flags & FLAG_IS_ARROW != 0
104    }
105    #[inline(always)]
106    pub fn set_is_arrow(&mut self, val: bool) {
107        if val {
108            self.flags |= FLAG_IS_ARROW;
109        } else {
110            self.flags &= !FLAG_IS_ARROW;
111        }
112    }
113
114    #[inline(always)]
115    pub fn is_async(&self) -> bool {
116        self.flags & FLAG_IS_ASYNC != 0
117    }
118    #[inline(always)]
119    pub fn set_is_async(&mut self, val: bool) {
120        if val {
121            self.flags |= FLAG_IS_ASYNC;
122        } else {
123            self.flags &= !FLAG_IS_ASYNC;
124        }
125    }
126
127    #[inline(always)]
128    pub fn is_generator(&self) -> bool {
129        self.flags & FLAG_IS_GENERATOR != 0
130    }
131    #[inline(always)]
132    pub fn set_is_generator(&mut self, val: bool) {
133        if val {
134            self.flags |= FLAG_IS_GENERATOR;
135        } else {
136            self.flags &= !FLAG_IS_GENERATOR;
137        }
138    }
139
140    #[inline(always)]
141    pub fn is_builtin(&self) -> bool {
142        self.flags & FLAG_IS_BUILTIN != 0
143    }
144    #[inline(always)]
145    pub fn set_is_builtin(&mut self, val: bool) {
146        if val {
147            self.flags |= FLAG_IS_BUILTIN;
148        } else {
149            self.flags &= !FLAG_IS_BUILTIN;
150        }
151    }
152
153    #[inline(always)]
154    pub fn uses_arguments(&self) -> bool {
155        self.flags & FLAG_USES_ARGUMENTS != 0
156    }
157    #[inline(always)]
158    pub fn builtin_needs_this(&self) -> bool {
159        self.flags & FLAG_BUILTIN_NEEDS_THIS != 0
160    }
161    #[inline(always)]
162    pub fn set_builtin_needs_this(&mut self, val: bool) {
163        if val {
164            self.flags |= FLAG_BUILTIN_NEEDS_THIS;
165        } else {
166            self.flags &= !FLAG_BUILTIN_NEEDS_THIS;
167        }
168    }
169    #[inline(always)]
170    pub fn set_uses_arguments(&mut self, val: bool) {
171        if val {
172            self.flags |= FLAG_USES_ARGUMENTS;
173        } else {
174            self.flags &= !FLAG_USES_ARGUMENTS;
175        }
176    }
177
178    #[inline(always)]
179    pub fn is_strict(&self) -> bool {
180        self.flags & FLAG_IS_STRICT != 0
181    }
182    #[inline(always)]
183    pub fn set_is_strict(&mut self, val: bool) {
184        if val {
185            self.flags |= FLAG_IS_STRICT;
186        } else {
187            self.flags &= !FLAG_IS_STRICT;
188        }
189    }
190
191    #[inline(always)]
192    pub fn has_symbol_on_base(&self) -> bool {
193        self.flags & FLAG_HAS_SYMBOL_ON_BASE != 0
194    }
195
196    #[inline(always)]
197    pub fn mark_has_symbol_prop(&mut self) {
198        self.flags |= FLAG_HAS_SYMBOL_ON_BASE;
199    }
200
201    #[inline]
202    pub fn upvalues_mut(&mut self) -> &mut UpvalueData {
203        if self.upvalues.is_none() {
204            self.upvalues = Some(Box::new(UpvalueData::new()));
205        }
206        self.upvalues.as_mut().unwrap()
207    }
208
209    #[inline]
210    pub fn upvalues_ref(&self) -> Option<&UpvalueData> {
211        self.upvalues.as_deref()
212    }
213
214    pub fn set_builtin_marker(&mut self, ctx: &mut JSContext, builtin_name: &str) {
215        use crate::object::object::PropertyDescriptor;
216
217        const BUILTIN_MARKER: &str = "__builtin__";
218        let atom = ctx.intern(builtin_name);
219        self.builtin_atom = Some(atom);
220
221        self.builtin_func = ctx.get_builtin_func(builtin_name);
222        if ctx.builtin_needs_this(builtin_name) {
223            self.set_builtin_needs_this(true);
224        }
225        if self.base.prototype.is_none() {
226            if let Some(fp) = ctx.get_function_prototype() {
227                self.base.prototype = Some(fp);
228            }
229        }
230        if let Some(display_name) = ctx.get_builtin_name(builtin_name) {
231            self.name = ctx.intern(display_name);
232        }
233        self.base
234            .set(ctx.intern(BUILTIN_MARKER), JSValue::new_string(atom));
235
236        let length_desc = PropertyDescriptor {
237            value: Some(JSValue::new_int(self.arity as i64)),
238            writable: false,
239            enumerable: false,
240            configurable: true,
241            get: None,
242            set: None,
243        };
244        self.base
245            .define_property(ctx.common_atoms.length, length_desc);
246
247        let name_desc = PropertyDescriptor {
248            value: Some(JSValue::new_string(self.name)),
249            writable: false,
250            enumerable: false,
251            configurable: true,
252            get: None,
253            set: None,
254        };
255        self.base.define_property(ctx.common_atoms.name, name_desc);
256    }
257}
258
259impl Default for JSFunction {
260    fn default() -> Self {
261        Self::new()
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::*;
268
269    #[test]
270    fn test_jsfunction_flag_accessors() {
271        let mut f = JSFunction::new();
272        assert!(!f.is_arrow());
273        assert!(!f.is_async());
274        assert!(!f.is_generator());
275        assert!(!f.is_builtin());
276
277        f.set_is_arrow(true);
278        assert!(f.is_arrow());
279
280        f.set_is_async(true);
281        assert!(f.is_async());
282
283        f.set_is_generator(true);
284        assert!(f.is_generator());
285
286        f.set_is_builtin(true);
287        assert!(f.is_builtin());
288
289        f.set_is_arrow(false);
290        assert!(!f.is_arrow());
291
292        assert!(f.is_async());
293        assert!(f.is_generator());
294        assert!(f.is_builtin());
295    }
296
297    #[test]
298    fn test_jsfunction_builtin_constructor() {
299        let f = JSFunction::new_builtin(Atom(42), 3);
300        assert!(f.is_builtin());
301        assert_eq!(f.arity, 3);
302        assert_eq!(f.name, Atom(42));
303    }
304
305    #[test]
306    fn test_upvalue_data_lazily_allocated() {
307        let mut f = JSFunction::new();
308        assert!(f.upvalues.is_none());
309
310        assert!(f.upvalues_ref().is_none());
311        assert!(f.upvalues.is_none());
312
313        f.upvalues_mut()
314            .upvalues
315            .insert(Atom(1), JSValue::new_int(42));
316        assert!(f.upvalues.is_some());
317        assert_eq!(
318            f.upvalues_ref()
319                .unwrap()
320                .upvalues
321                .get(&Atom(1))
322                .unwrap()
323                .get_int(),
324            42
325        );
326    }
327
328    #[test]
329    fn test_upvalue_data_slots_and_atoms() {
330        let mut f = JSFunction::new();
331        let cell = Rc::new(Cell::new(JSValue::new_int(99)));
332        f.upvalues_mut().upvalue_slots.push(cell.clone());
333        f.upvalues_mut().upvalue_slot_atoms.push(Atom(7));
334
335        let uv = f.upvalues_ref().unwrap();
336        assert_eq!(uv.upvalue_slots.len(), 1);
337        assert_eq!(uv.upvalue_slot_atoms[0], Atom(7));
338        assert_eq!(uv.upvalue_slots[0].get().get_int(), 99);
339    }
340
341    #[test]
342    fn test_with_upvalues() {
343        let mut map = FxHashMap::default();
344        map.insert(Atom(1), JSValue::new_int(10));
345        let f = JSFunction::new().with_upvalues(map);
346        assert!(f.upvalues.is_some());
347        assert_eq!(
348            f.upvalues_ref()
349                .unwrap()
350                .upvalues
351                .get(&Atom(1))
352                .unwrap()
353                .get_int(),
354            10
355        );
356    }
357}