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