Skip to main content

pipa/
value.rs

1use crate::runtime::atom::Atom;
2
3const QNAN_BASE: u64 = 0x7FF8_0000_0000_0000;
4
5const TAG_UNDEFINED: u64 = 0x0;
6const TAG_NULL: u64 = 0x1;
7const TAG_BOOL: u64 = 0x2;
8const TAG_INT: u64 = 0x3;
9const TAG_STRING: u64 = 0x4;
10const TAG_OBJECT: u64 = 0x5;
11const TAG_SYMBOL: u64 = 0x6;
12const TAG_BIGINT: u64 = 0x7;
13const TAG_FUNC: u64 = 0x8;
14const TAG_TDZ: u64 = 0x9;
15
16const TAG_SHIFT: u64 = 47;
17const PAYLOAD_MASK: u64 = 0x0000_7FFF_FFFF_FFFF;
18
19const CANONICAL_NAN: u64 = QNAN_BASE | (0xF << TAG_SHIFT) | 0x1;
20
21#[derive(Clone, Copy, Debug)]
22pub struct JSValue(u64);
23
24impl JSValue {
25    pub fn raw_bits(&self) -> u64 {
26        self.0
27    }
28
29    pub fn from_raw_bits(bits: u64) -> Self {
30        JSValue(bits)
31    }
32
33    const fn is_tagged(bits: u64) -> bool {
34        ((bits >> 48) & 0x7FF8) == 0x7FF8
35    }
36
37    const fn pack(tag: u64, payload: u64) -> u64 {
38        QNAN_BASE | (tag << TAG_SHIFT) | (payload & PAYLOAD_MASK)
39    }
40
41    #[inline(always)]
42    fn get_tag(&self) -> u64 {
43        (self.0 >> TAG_SHIFT) & 0xF
44    }
45
46    #[inline(always)]
47    fn get_payload(&self) -> u64 {
48        self.0 & PAYLOAD_MASK
49    }
50
51    #[inline(always)]
52    pub fn new_int(i: i64) -> Self {
53        let sign = ((i as u64) >> 63) << 63;
54        let payload = (i as u64) & PAYLOAD_MASK;
55        JSValue(Self::pack(TAG_INT, payload) | sign)
56    }
57
58    #[inline]
59    fn is_fast_int(f: f64) -> Option<i64> {
60        let bits = f.to_bits();
61        let exponent = ((bits >> 52) & 0x7FF) as i32;
62        if exponent == 0 {
63            if (bits & 0x000F_FFFF_FFFF_FFFF) == 0 {
64                return Some(0i64);
65            }
66            return None;
67        }
68        if exponent >= 0x7FF {
69            return None;
70        }
71
72        if exponent < 1023 {
73            return None;
74        }
75
76        let exp_shift = (exponent - 1023) as u32;
77        if exp_shift >= 52 {
78        } else {
79            let frac_bits = 52 - exp_shift;
80            let frac_mask = (1u64 << frac_bits) - 1;
81            if (bits & frac_mask) != 0 {
82                return None;
83            }
84        }
85
86        let int_val = f as i64;
87        const MAX_SAFE_INT: i64 = 1i64 << 47;
88        if int_val >= -MAX_SAFE_INT && int_val < MAX_SAFE_INT {
89            Some(int_val)
90        } else {
91            None
92        }
93    }
94
95    pub fn new_float(f: f64) -> Self {
96        if let Some(int_val) = Self::is_fast_int(f) {
97            return JSValue::new_int(int_val);
98        }
99
100        let bits = f.to_bits();
101        if !Self::is_tagged(bits) {
102            return JSValue(bits);
103        }
104
105        JSValue(CANONICAL_NAN)
106    }
107
108    #[inline(always)]
109    pub fn is_float(&self) -> bool {
110        !Self::is_tagged(self.0) || self.0 == CANONICAL_NAN
111    }
112
113    #[inline(always)]
114    pub fn new_string(atom: Atom) -> Self {
115        JSValue(Self::pack(TAG_STRING, atom.0 as u64))
116    }
117
118    #[inline(always)]
119    pub fn new_object(ptr: usize) -> Self {
120        JSValue(Self::pack(TAG_OBJECT, Self::compress_ptr(ptr)))
121    }
122
123    #[inline(always)]
124    pub fn new_function(ptr: usize) -> Self {
125        JSValue(Self::pack(TAG_FUNC, Self::compress_ptr(ptr)))
126    }
127
128    pub fn new_symbol(atom: Atom) -> Self {
129        JSValue(Self::pack(TAG_SYMBOL, atom.0 as u64))
130    }
131
132    pub fn new_symbol_with_id(atom: Atom, id: u32) -> Self {
133        JSValue(Self::pack(TAG_SYMBOL, (id as u64) << 32 | atom.0 as u64))
134    }
135
136    pub fn get_symbol_id(&self) -> u32 {
137        debug_assert!(self.is_symbol());
138        (self.get_payload() >> 32) as u32
139    }
140
141    pub fn new_bigint(ptr: usize) -> Self {
142        JSValue(Self::pack(TAG_BIGINT, Self::compress_ptr(ptr)))
143    }
144
145    #[inline(always)]
146    fn compress_ptr(ptr: usize) -> u64 {
147        let compressed = (ptr >> 3) as u64;
148        compressed & PAYLOAD_MASK
149    }
150
151    #[inline(always)]
152    fn decompress_ptr(compressed: u64) -> usize {
153        (compressed << 3) as usize
154    }
155
156    pub const fn undefined() -> Self {
157        JSValue(Self::pack(TAG_UNDEFINED, 0))
158    }
159
160    pub const fn null() -> Self {
161        JSValue(Self::pack(TAG_NULL, 1))
162    }
163
164    pub const fn bool(b: bool) -> Self {
165        JSValue(Self::pack(TAG_BOOL, if b { 1 } else { 0 }))
166    }
167
168    #[inline(always)]
169    pub fn is_undefined(&self) -> bool {
170        Self::is_tagged(self.0) && self.get_tag() == TAG_UNDEFINED
171    }
172
173    #[inline(always)]
174    pub fn is_null(&self) -> bool {
175        Self::is_tagged(self.0) && self.get_tag() == TAG_NULL
176    }
177
178    #[inline(always)]
179    pub fn is_null_or_undefined(&self) -> bool {
180        Self::is_tagged(self.0) && self.get_tag() <= TAG_NULL
181    }
182
183    #[inline(always)]
184    pub fn is_bool(&self) -> bool {
185        Self::is_tagged(self.0) && self.get_tag() == TAG_BOOL
186    }
187
188    #[inline(always)]
189    pub fn is_int(&self) -> bool {
190        Self::is_tagged(self.0) && self.get_tag() == TAG_INT
191    }
192
193    #[inline(always)]
194    pub fn both_int(a: &JSValue, b: &JSValue) -> bool {
195        const INT_TAG_BITS: u64 = QNAN_BASE | (TAG_INT << TAG_SHIFT);
196        const TAG_MASK: u64 = QNAN_BASE | (0xF << TAG_SHIFT);
197        (a.0 & TAG_MASK) == INT_TAG_BITS && (b.0 & TAG_MASK) == INT_TAG_BITS
198    }
199
200    #[inline(always)]
201    pub fn both_object(a: &JSValue, b: &JSValue) -> bool {
202        const OBJ_TAG_BITS: u64 = QNAN_BASE | (TAG_OBJECT << TAG_SHIFT);
203        const TAG_MASK: u64 = QNAN_BASE | (0xF << TAG_SHIFT);
204        (a.0 & TAG_MASK) == OBJ_TAG_BITS && (b.0 & TAG_MASK) == OBJ_TAG_BITS
205    }
206
207    #[inline(always)]
208    pub fn both_raw_float(a: &JSValue, b: &JSValue) -> bool {
209        !Self::is_tagged(a.0) && !Self::is_tagged(b.0)
210    }
211
212    #[inline(always)]
213    pub fn new_float_raw(f: f64) -> Self {
214        let bits = f.to_bits();
215        if !Self::is_tagged(bits) {
216            JSValue(bits)
217        } else {
218            JSValue(CANONICAL_NAN)
219        }
220    }
221
222    #[inline(always)]
223    pub fn is_string(&self) -> bool {
224        Self::is_tagged(self.0) && self.get_tag() == TAG_STRING
225    }
226
227    #[inline(always)]
228    pub fn is_object(&self) -> bool {
229        Self::is_tagged(self.0) && self.get_tag() == TAG_OBJECT
230    }
231
232    #[inline(always)]
233    pub fn is_function(&self) -> bool {
234        Self::is_tagged(self.0) && self.get_tag() == TAG_FUNC
235    }
236
237    #[inline(always)]
238    pub fn is_symbol(&self) -> bool {
239        Self::is_tagged(self.0) && self.get_tag() == TAG_SYMBOL
240    }
241
242    #[inline(always)]
243    pub fn is_bigint(&self) -> bool {
244        Self::is_tagged(self.0) && self.get_tag() == TAG_BIGINT
245    }
246
247    #[inline(always)]
248    pub fn is_object_like(&self) -> bool {
249        Self::is_tagged(self.0) && matches!(self.get_tag(), TAG_OBJECT | TAG_FUNC)
250    }
251
252    pub const fn new_tdz() -> Self {
253        JSValue(Self::pack(TAG_TDZ, 0))
254    }
255
256    #[inline(always)]
257    pub fn is_tdz(&self) -> bool {
258        Self::is_tagged(self.0) && self.get_tag() == TAG_TDZ
259    }
260
261    #[inline(always)]
262    pub fn get_int(&self) -> i64 {
263        let payload = self.get_payload();
264        let sign = (self.0 >> 63) as i64;
265        let extend = (-sign as u64) & !PAYLOAD_MASK;
266        (payload | extend) as i64
267    }
268
269    pub fn to_number(&self) -> f64 {
270        if !Self::is_tagged(self.0) {
271            return f64::from_bits(self.0);
272        }
273        if self.get_tag() == TAG_INT {
274            return self.get_int() as f64;
275        }
276        if self.get_tag() == TAG_BOOL {
277            return if self.get_bool() { 1.0 } else { 0.0 };
278        }
279        if self.get_tag() == TAG_NULL {
280            return 0.0;
281        }
282        f64::NAN
283    }
284
285    pub fn get_float(&self) -> f64 {
286        if !Self::is_tagged(self.0) {
287            return f64::from_bits(self.0);
288        }
289        if self.0 == CANONICAL_NAN {
290            return f64::NAN;
291        }
292        if self.get_tag() == TAG_INT {
293            return self.get_int() as f64;
294        }
295        if self.get_tag() == TAG_NULL {
296            return 0.0;
297        }
298        f64::NAN
299    }
300
301    #[inline(always)]
302    pub fn get_atom(&self) -> Atom {
303        Atom(self.get_payload() as u32)
304    }
305
306    #[inline(always)]
307    pub fn get_ptr(&self) -> usize {
308        Self::decompress_ptr(self.get_payload())
309    }
310
311    #[inline(always)]
312    pub unsafe fn object_from_ptr(ptr: usize) -> &'static crate::object::object::JSObject {
313        unsafe { &*(ptr as *const crate::object::object::JSObject) }
314    }
315
316    #[inline(always)]
317    pub unsafe fn object_from_ptr_mut(ptr: usize) -> &'static mut crate::object::object::JSObject {
318        unsafe { &mut *(ptr as *mut crate::object::object::JSObject) }
319    }
320
321    #[inline(always)]
322    pub unsafe fn function_from_ptr(ptr: usize) -> &'static crate::object::function::JSFunction {
323        unsafe { &*(ptr as *const crate::object::function::JSFunction) }
324    }
325
326    #[inline(always)]
327    pub unsafe fn function_from_ptr_mut(
328        ptr: usize,
329    ) -> &'static mut crate::object::function::JSFunction {
330        unsafe { &mut *(ptr as *mut crate::object::function::JSFunction) }
331    }
332
333    #[inline(always)]
334    pub fn as_object(&self) -> &crate::object::object::JSObject {
335        unsafe { &*(self.get_ptr() as *const crate::object::object::JSObject) }
336    }
337
338    #[inline(always)]
339    pub fn as_object_mut(&self) -> &mut crate::object::object::JSObject {
340        unsafe { &mut *(self.get_ptr() as *mut crate::object::object::JSObject) }
341    }
342
343    #[inline(always)]
344    pub fn as_function(&self) -> &crate::object::function::JSFunction {
345        unsafe { &*(self.get_ptr() as *const crate::object::function::JSFunction) }
346    }
347
348    #[inline(always)]
349    pub fn as_function_mut(&self) -> &mut crate::object::function::JSFunction {
350        unsafe { &mut *(self.get_ptr() as *mut crate::object::function::JSFunction) }
351    }
352
353    #[inline(always)]
354    pub fn get_bool(&self) -> bool {
355        self.get_payload() != 0
356    }
357
358    pub fn is_truthy(&self) -> bool {
359        if !Self::is_tagged(self.0) {
360            let f = f64::from_bits(self.0);
361            return f != 0.0 && !f.is_nan();
362        }
363        if self.is_float() {
364            let f = self.get_float();
365            return f != 0.0 && !f.is_nan();
366        }
367        match self.get_tag() {
368            TAG_UNDEFINED | TAG_NULL => false,
369            TAG_BOOL => self.get_bool(),
370            TAG_INT => self.get_int() != 0,
371            TAG_STRING => self.get_payload() != 0,
372            TAG_OBJECT | TAG_FUNC | TAG_SYMBOL | TAG_BIGINT => true,
373            TAG_TDZ => false,
374            _ => true,
375        }
376    }
377
378    #[inline(always)]
379    pub fn strict_eq(&self, other: &JSValue) -> bool {
380        if self.0 == other.0 {
381            if self.is_float() {
382                return !self.get_float().is_nan();
383            }
384            return true;
385        }
386
387        if self.is_bigint() && other.is_bigint() {
388            let obj_a = self.as_object();
389            let obj_b = other.as_object();
390            return obj_a.get_bigint_value() == obj_b.get_bigint_value();
391        }
392
393        if self.is_int() && other.is_float() {
394            let fv = other.get_float();
395            return !fv.is_nan() && (self.get_int() as f64) == fv;
396        }
397        if self.is_float() && other.is_int() {
398            let fv = self.get_float();
399            return !fv.is_nan() && fv == (other.get_int() as f64);
400        }
401
402        if self.is_float() && other.is_float() {
403            let a = self.get_float();
404            let b = other.get_float();
405            return !a.is_nan() && !b.is_nan() && a == b;
406        }
407
408        if self.is_string() && other.is_string() {
409            return self.get_atom().0 == other.get_atom().0;
410        }
411
412        false
413    }
414
415    pub fn debug_raw(&self) -> u64 {
416        self.0
417    }
418
419    pub fn get_data(&self) -> u64 {
420        self.get_payload()
421    }
422
423    #[inline]
424    pub fn retain_atoms_in(&self, ctx: &mut crate::runtime::JSContext) {
425        if self.is_string() || self.is_symbol() {
426            ctx.atom_table_mut().retain(self.get_atom());
427        }
428    }
429
430    #[inline]
431    pub fn release_atoms_in(&self, ctx: &mut crate::runtime::JSContext) {
432        if self.is_string() || self.is_symbol() {
433            ctx.atom_table_mut().release(self.get_atom());
434        }
435    }
436
437    #[inline]
438    pub fn release_atoms_in_table(&self, table: &mut crate::runtime::atom::AtomTable) {
439        if self.is_string() || self.is_symbol() {
440            table.release(self.get_atom());
441        }
442    }
443}
444
445#[derive(Debug, Clone, Copy, PartialEq, Eq)]
446pub enum JSValueType {
447    Undefined,
448    Null,
449    Boolean,
450    Number,
451    BigInt,
452    Symbol,
453    String,
454    Object,
455    Function,
456}
457
458impl JSValue {
459    pub fn get_type(&self) -> JSValueType {
460        if !Self::is_tagged(self.0) {
461            return JSValueType::Number;
462        }
463        match self.get_tag() {
464            TAG_UNDEFINED => JSValueType::Undefined,
465            TAG_NULL => JSValueType::Null,
466            TAG_BOOL => JSValueType::Boolean,
467            TAG_INT => JSValueType::Number,
468            TAG_BIGINT => JSValueType::BigInt,
469            TAG_SYMBOL => JSValueType::Symbol,
470            TAG_STRING => JSValueType::String,
471            TAG_OBJECT => JSValueType::Object,
472            TAG_FUNC => JSValueType::Function,
473            TAG_TDZ => JSValueType::Undefined,
474            _ => JSValueType::Undefined,
475        }
476    }
477}
478
479pub trait IntoJSValue {
480    fn into_jsvalue(self, ctx: &mut crate::runtime::JSContext) -> JSValue;
481}
482
483pub trait FromJSValue: Sized {
484    fn from_jsvalue(value: &JSValue, ctx: &crate::runtime::JSContext) -> Option<Self>
485    where
486        Self: Sized;
487}
488
489impl IntoJSValue for () {
490    fn into_jsvalue(self, _ctx: &mut crate::runtime::JSContext) -> JSValue {
491        JSValue::undefined()
492    }
493}
494
495impl IntoJSValue for bool {
496    fn into_jsvalue(self, _ctx: &mut crate::runtime::JSContext) -> JSValue {
497        JSValue::bool(self)
498    }
499}
500
501impl IntoJSValue for i32 {
502    fn into_jsvalue(self, _ctx: &mut crate::runtime::JSContext) -> JSValue {
503        JSValue::new_int(self as i64)
504    }
505}
506
507impl IntoJSValue for i64 {
508    fn into_jsvalue(self, _ctx: &mut crate::runtime::JSContext) -> JSValue {
509        JSValue::new_int(self)
510    }
511}
512
513impl IntoJSValue for f64 {
514    fn into_jsvalue(self, _ctx: &mut crate::runtime::JSContext) -> JSValue {
515        JSValue::new_float(self)
516    }
517}
518
519impl IntoJSValue for String {
520    fn into_jsvalue(self, ctx: &mut crate::runtime::JSContext) -> JSValue {
521        let atom = ctx.atom_table_mut().intern(&self);
522        JSValue::new_string(atom)
523    }
524}
525
526impl IntoJSValue for &str {
527    fn into_jsvalue(self, ctx: &mut crate::runtime::JSContext) -> JSValue {
528        let atom = ctx.atom_table_mut().intern(self);
529        JSValue::new_string(atom)
530    }
531}
532
533impl<T: IntoJSValue> IntoJSValue for Option<T> {
534    fn into_jsvalue(self, ctx: &mut crate::runtime::JSContext) -> JSValue {
535        match self {
536            Some(v) => v.into_jsvalue(ctx),
537            None => JSValue::null(),
538        }
539    }
540}