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