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}