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}