Skip to main content

just_engine/runner/jit/
reg_vm.rs

1//! Register-based bytecode virtual machine.
2
3use crate::runner::ds::error::JErrorType;
4use crate::runner::ds::lex_env::JsLexEnvironmentType;
5use crate::runner::ds::object_property::PropertyKey;
6use crate::runner::ds::operations::type_conversion::to_string;
7use crate::runner::ds::value::{JsNumberType, JsValue, JsValueOrSelf};
8use crate::runner::plugin::registry::BuiltInRegistry;
9use crate::runner::plugin::types::EvalContext;
10
11use super::reg_bytecode::{RegChunk, RegOpCode};
12
13/// Result of VM execution.
14pub enum RegVmResult {
15    Ok(JsValue),
16    Error(JErrorType),
17}
18
19#[derive(Clone)]
20struct EnvCacheEntry {
21    version: u64,
22    env: JsLexEnvironmentType,
23}
24
25#[derive(Clone)]
26struct PropCacheEntry {
27    obj: crate::runner::ds::object::JsObjectType,
28    prop: String,
29}
30
31pub struct RegVm<'a> {
32    chunk: &'a RegChunk,
33    ip: usize,
34    registers: Vec<JsValue>,
35    get_var_cache: Vec<Option<EnvCacheEntry>>,
36    set_var_cache: Vec<Option<EnvCacheEntry>>,
37    get_prop_cache: Vec<Option<PropCacheEntry>>,
38    ctx: EvalContext,
39    registry: &'a BuiltInRegistry,
40}
41
42impl<'a> RegVm<'a> {
43    pub fn new(chunk: &'a RegChunk, ctx: EvalContext, registry: &'a BuiltInRegistry) -> Self {
44        RegVm {
45            chunk,
46            ip: 0,
47            registers: vec![JsValue::Undefined; chunk.register_count as usize],
48            get_var_cache: vec![None; chunk.code.len()],
49            set_var_cache: vec![None; chunk.code.len()],
50            get_prop_cache: vec![None; chunk.code.len()],
51            ctx,
52            registry,
53        }
54    }
55
56    pub fn into_ctx(mut self) -> EvalContext {
57        self.sync_locals_into_ctx();
58        self.ctx
59    }
60
61    pub fn run(&mut self) -> RegVmResult {
62        loop {
63            if self.ip >= self.chunk.code.len() {
64                return RegVmResult::Ok(JsValue::Undefined);
65            }
66            let instr_index = self.ip;
67            let instr = &self.chunk.code[instr_index];
68            self.ip += 1;
69            match instr.op {
70                RegOpCode::LoadConst => {
71                    let val = self.chunk.constants[instr.imm as usize].clone();
72                    self.write_reg(instr.dst, val);
73                }
74                RegOpCode::LoadUndefined => self.write_reg(instr.dst, JsValue::Undefined),
75                RegOpCode::LoadNull => self.write_reg(instr.dst, JsValue::Null),
76                RegOpCode::LoadTrue => self.write_reg(instr.dst, JsValue::Boolean(true)),
77                RegOpCode::LoadFalse => self.write_reg(instr.dst, JsValue::Boolean(false)),
78                RegOpCode::Move => {
79                    let val = self.read_reg(instr.src1).clone();
80                    self.write_reg(instr.dst, val);
81                }
82                RegOpCode::Add => {
83                    let a = self.read_reg(instr.src1).clone();
84                    let b = self.read_reg(instr.src2).clone();
85                    self.write_reg(instr.dst, self.js_add(a, b));
86                }
87                RegOpCode::Sub => {
88                    let a = self.read_reg(instr.src1).clone();
89                    let b = self.read_reg(instr.src2).clone();
90                    self.write_reg(instr.dst, self.js_sub(a, b));
91                }
92                RegOpCode::Mul => {
93                    let a = self.read_reg(instr.src1).clone();
94                    let b = self.read_reg(instr.src2).clone();
95                    self.write_reg(instr.dst, self.js_mul(a, b));
96                }
97                RegOpCode::Div => {
98                    let a = self.read_reg(instr.src1).clone();
99                    let b = self.read_reg(instr.src2).clone();
100                    self.write_reg(instr.dst, self.js_div(a, b));
101                }
102                RegOpCode::Mod => {
103                    let a = self.read_reg(instr.src1).clone();
104                    let b = self.read_reg(instr.src2).clone();
105                    self.write_reg(instr.dst, self.js_mod(a, b));
106                }
107                RegOpCode::Negate => {
108                    let val = self.read_reg(instr.src1).clone();
109                    self.write_reg(instr.dst, self.js_negate(val));
110                }
111                RegOpCode::BitAnd => {
112                    let a = self.read_reg(instr.src1).clone();
113                    let b = self.read_reg(instr.src2).clone();
114                    self.write_reg(instr.dst, self.js_bit_and(a, b));
115                }
116                RegOpCode::BitOr => {
117                    let a = self.read_reg(instr.src1).clone();
118                    let b = self.read_reg(instr.src2).clone();
119                    self.write_reg(instr.dst, self.js_bit_or(a, b));
120                }
121                RegOpCode::BitXor => {
122                    let a = self.read_reg(instr.src1).clone();
123                    let b = self.read_reg(instr.src2).clone();
124                    self.write_reg(instr.dst, self.js_bit_xor(a, b));
125                }
126                RegOpCode::BitNot => {
127                    let val = self.read_reg(instr.src1).clone();
128                    let a = self.to_i32(val);
129                    self.write_reg(instr.dst, JsValue::Number(JsNumberType::Integer((!a) as i64)));
130                }
131                RegOpCode::ShiftLeft => {
132                    let a = self.read_reg(instr.src1).clone();
133                    let b = self.read_reg(instr.src2).clone();
134                    self.write_reg(instr.dst, self.js_shift_left(a, b));
135                }
136                RegOpCode::ShiftRight => {
137                    let a = self.read_reg(instr.src1).clone();
138                    let b = self.read_reg(instr.src2).clone();
139                    self.write_reg(instr.dst, self.js_shift_right(a, b));
140                }
141                RegOpCode::UShiftRight => {
142                    let a = self.read_reg(instr.src1).clone();
143                    let b = self.read_reg(instr.src2).clone();
144                    self.write_reg(instr.dst, self.js_ushift_right(a, b));
145                }
146                RegOpCode::StrictEqual => {
147                    let res = self.js_strict_equal(self.read_reg(instr.src1), self.read_reg(instr.src2));
148                    self.write_reg(instr.dst, JsValue::Boolean(res));
149                }
150                RegOpCode::StrictNotEqual => {
151                    let res = !self.js_strict_equal(self.read_reg(instr.src1), self.read_reg(instr.src2));
152                    self.write_reg(instr.dst, JsValue::Boolean(res));
153                }
154                RegOpCode::Equal => {
155                    let res = self.js_abstract_equal(self.read_reg(instr.src1), self.read_reg(instr.src2));
156                    self.write_reg(instr.dst, JsValue::Boolean(res));
157                }
158                RegOpCode::NotEqual => {
159                    let res = !self.js_abstract_equal(self.read_reg(instr.src1), self.read_reg(instr.src2));
160                    self.write_reg(instr.dst, JsValue::Boolean(res));
161                }
162                RegOpCode::LessThan => {
163                    let res = self.js_less_than(self.read_reg(instr.src1), self.read_reg(instr.src2));
164                    self.write_reg(instr.dst, JsValue::Boolean(res));
165                }
166                RegOpCode::LessEqual => {
167                    let res = !self.js_less_than(self.read_reg(instr.src2), self.read_reg(instr.src1));
168                    self.write_reg(instr.dst, JsValue::Boolean(res));
169                }
170                RegOpCode::GreaterThan => {
171                    let res = self.js_less_than(self.read_reg(instr.src2), self.read_reg(instr.src1));
172                    self.write_reg(instr.dst, JsValue::Boolean(res));
173                }
174                RegOpCode::GreaterEqual => {
175                    let res = !self.js_less_than(self.read_reg(instr.src1), self.read_reg(instr.src2));
176                    self.write_reg(instr.dst, JsValue::Boolean(res));
177                }
178                RegOpCode::Not => {
179                    let val = self.read_reg(instr.src1).clone();
180                    self.write_reg(instr.dst, JsValue::Boolean(!self.is_truthy(&val)));
181                }
182                RegOpCode::TypeOf => {
183                    let val = self.read_reg(instr.src1).clone();
184                    let type_str = match &val {
185                        JsValue::Undefined => "undefined",
186                        JsValue::Null => "object",
187                        JsValue::Boolean(_) => "boolean",
188                        JsValue::Number(_) => "number",
189                        JsValue::String(_) => "string",
190                        JsValue::Symbol(_) => "symbol",
191                        JsValue::Object(_) => "object",
192                    };
193                    self.write_reg(instr.dst, JsValue::String(type_str.to_string()));
194                }
195                RegOpCode::UnaryPlus => {
196                    let val = self.read_reg(instr.src1).clone();
197                    self.write_reg(instr.dst, self.to_number_value(val));
198                }
199                RegOpCode::GetVar => {
200                    let name = self.chunk.get_name(instr.imm);
201                    match self.get_var_cached(instr_index, name) {
202                        Ok(val) => self.write_reg(instr.dst, val),
203                        Err(e) => return RegVmResult::Error(e),
204                    }
205                }
206                RegOpCode::SetVar => {
207                    let name = self.chunk.get_name(instr.imm);
208                    let val = self.read_reg(instr.src1).clone();
209                    if let Err(e) = self.set_var_cached(instr_index, name, val) {
210                        return RegVmResult::Error(e);
211                    }
212                }
213                RegOpCode::DeclareVar => {
214                    let name = self.chunk.get_name(instr.imm);
215                    if !self.ctx.has_var_binding(name) {
216                        if let Err(e) = self.ctx.create_var_binding(name) {
217                            return RegVmResult::Error(e);
218                        }
219                    }
220                }
221                RegOpCode::DeclareLet => {
222                    let name = self.chunk.get_name(instr.imm);
223                    if let Err(e) = self.ctx.create_binding(name, false) {
224                        return RegVmResult::Error(e);
225                    }
226                }
227                RegOpCode::DeclareConst => {
228                    let name = self.chunk.get_name(instr.imm);
229                    if let Err(e) = self.ctx.create_binding(name, true) {
230                        return RegVmResult::Error(e);
231                    }
232                }
233                RegOpCode::InitVar => {
234                    let name = self.chunk.get_name(instr.imm);
235                    let val = self.read_reg(instr.src1).clone();
236                    if let Err(e) = self.ctx.initialize_var_binding(name, val.clone()) {
237                        return RegVmResult::Error(e);
238                    }
239                    let _ = self.ctx.set_var_binding(name, val);
240                }
241                RegOpCode::InitBinding => {
242                    let name = self.chunk.get_name(instr.imm);
243                    let val = self.read_reg(instr.src1).clone();
244                    if let Err(e) = self.ctx.initialize_binding(name, val) {
245                        return RegVmResult::Error(e);
246                    }
247                }
248                RegOpCode::GetProp => {
249                    let obj_val = self.read_reg(instr.src1).clone();
250                    let prop_name = self.chunk.get_name(instr.imm);
251                    match self.get_prop_cached(instr_index, &obj_val, prop_name) {
252                        Ok(val) => self.write_reg(instr.dst, val),
253                        Err(e) => return RegVmResult::Error(e),
254                    }
255                }
256                RegOpCode::SetProp => {
257                    let obj_val = self.read_reg(instr.src1).clone();
258                    let val = self.read_reg(instr.src2).clone();
259                    let prop_name = self.chunk.get_name(instr.imm);
260                    if let Err(e) = self.set_prop_value(&obj_val, prop_name, val.clone()) {
261                        return RegVmResult::Error(e);
262                    }
263                    self.write_reg(instr.dst, val);
264                }
265                RegOpCode::GetElem => {
266                    let obj_val = self.read_reg(instr.src1).clone();
267                    let key_val = self.read_reg(instr.src2).clone();
268                    let prop_name = match self.to_property_key(&key_val) {
269                        Ok(key) => key,
270                        Err(e) => return RegVmResult::Error(e),
271                    };
272                    match self.get_prop_cached(instr_index, &obj_val, &prop_name) {
273                        Ok(val) => self.write_reg(instr.dst, val),
274                        Err(e) => return RegVmResult::Error(e),
275                    }
276                }
277                RegOpCode::SetElem => {
278                    let obj_val = self.read_reg(instr.src1).clone();
279                    let key_val = self.read_reg(instr.src2).clone();
280                    let val = self.read_reg(instr.dst).clone();
281                    let prop_name = match self.to_property_key(&key_val) {
282                        Ok(key) => key,
283                        Err(e) => return RegVmResult::Error(e),
284                    };
285                    if let Err(e) = self.set_prop_value(&obj_val, &prop_name, val.clone()) {
286                        return RegVmResult::Error(e);
287                    }
288                }
289                RegOpCode::Jump => {
290                    self.ip = instr.imm as usize;
291                }
292                RegOpCode::JumpIfFalse => {
293                    let val = self.read_reg(instr.src1).clone();
294                    if !self.is_truthy(&val) {
295                        self.ip = instr.imm as usize;
296                    }
297                }
298                RegOpCode::JumpIfTrue => {
299                    let val = self.read_reg(instr.src1).clone();
300                    if self.is_truthy(&val) {
301                        self.ip = instr.imm as usize;
302                    }
303                }
304                RegOpCode::Call => {
305                    let _callee = self.read_reg(instr.src1).clone();
306                    self.write_reg(instr.dst, JsValue::Undefined);
307                }
308                RegOpCode::CallMethod => {
309                    let object = self.read_reg(instr.src1).clone();
310                    let method_name = self.chunk.get_name(instr.src2);
311                    let obj_name = self.resolve_object_name(&object);
312                    if let Some(ref name) = obj_name {
313                        if let Some(builtin_fn) = self.registry.get_method(name, method_name) {
314                            match builtin_fn.call(&mut self.ctx, object, Vec::new()) {
315                                Ok(result) => {
316                                    self.write_reg(instr.dst, result);
317                                    continue;
318                                }
319                                Err(e) => return RegVmResult::Error(e),
320                            }
321                        }
322                    }
323                    self.write_reg(instr.dst, JsValue::Undefined);
324                }
325                RegOpCode::Return => {
326                    let val = self.read_reg(instr.src1).clone();
327                    return RegVmResult::Ok(val);
328                }
329                RegOpCode::Halt => {
330                    return RegVmResult::Ok(JsValue::Undefined);
331                }
332            }
333        }
334    }
335
336    fn read_reg(&self, reg: u32) -> &JsValue {
337        self.registers.get(reg as usize).unwrap_or(&JsValue::Undefined)
338    }
339
340    fn write_reg(&mut self, reg: u32, value: JsValue) {
341        if let Some(slot) = self.registers.get_mut(reg as usize) {
342            *slot = value;
343        }
344    }
345
346    fn get_var_cached(&mut self, instr_index: usize, name: &str) -> Result<JsValue, JErrorType> {
347        if let Some(Some(entry)) = self.get_var_cache.get(instr_index) {
348            if entry.version == self.ctx.current_lex_env_version() {
349                if let Ok(val) = self.ctx.get_binding_in_env(&entry.env, name) {
350                    return Ok(val);
351                }
352            }
353        }
354        let (val, env) = self.ctx.get_binding_with_env(name)?;
355        let entry = EnvCacheEntry {
356            version: self.ctx.current_lex_env_version(),
357            env,
358        };
359        if let Some(slot) = self.get_var_cache.get_mut(instr_index) {
360            *slot = Some(entry);
361        }
362        Ok(val)
363    }
364
365    fn set_var_cached(
366        &mut self,
367        instr_index: usize,
368        name: &str,
369        value: JsValue,
370    ) -> Result<(), JErrorType> {
371        if let Some(Some(entry)) = self.set_var_cache.get(instr_index) {
372            if entry.version == self.ctx.current_lex_env_version() {
373                if self
374                    .ctx
375                    .set_binding_in_env_cached(&entry.env, name, value.clone())
376                    .is_ok()
377                {
378                    return Ok(());
379                }
380            }
381        }
382        let env = self.ctx.set_binding_with_env(name, value)?;
383        let entry = EnvCacheEntry {
384            version: self.ctx.current_lex_env_version(),
385            env,
386        };
387        if let Some(slot) = self.set_var_cache.get_mut(instr_index) {
388            *slot = Some(entry);
389        }
390        Ok(())
391    }
392
393    fn get_prop_cached(
394        &mut self,
395        instr_index: usize,
396        obj_val: &JsValue,
397        prop_name: &str,
398    ) -> Result<JsValue, JErrorType> {
399        if let JsValue::Object(obj) = obj_val {
400            if let Some(Some(entry)) = self.get_prop_cache.get(instr_index) {
401                if std::rc::Rc::ptr_eq(obj, &entry.obj) && entry.prop == prop_name {
402                    let prop_key = PropertyKey::Str(prop_name.to_string());
403                    return obj
404                        .borrow()
405                        .as_js_object()
406                        .get(&mut self.ctx.ctx_stack, &prop_key, JsValueOrSelf::SelfValue);
407                }
408            }
409
410            let prop_key = PropertyKey::Str(prop_name.to_string());
411            let val = obj
412                .borrow()
413                .as_js_object()
414                .get(&mut self.ctx.ctx_stack, &prop_key, JsValueOrSelf::SelfValue)?;
415            let entry = PropCacheEntry {
416                obj: obj.clone(),
417                prop: prop_name.to_string(),
418            };
419            if let Some(slot) = self.get_prop_cache.get_mut(instr_index) {
420                *slot = Some(entry);
421            }
422            Ok(val)
423        } else if matches!(obj_val, JsValue::Undefined | JsValue::Null) {
424            Err(JErrorType::TypeError("Cannot read property of null/undefined".to_string()))
425        } else {
426            Ok(JsValue::Undefined)
427        }
428    }
429
430    fn set_prop_value(
431        &mut self,
432        obj_val: &JsValue,
433        prop_name: &str,
434        value: JsValue,
435    ) -> Result<(), JErrorType> {
436        match obj_val {
437            JsValue::Object(obj) => {
438                let prop_key = PropertyKey::Str(prop_name.to_string());
439                let mut obj_ref = obj.borrow_mut();
440                let ok = obj_ref
441                    .as_js_object_mut()
442                    .set(&mut self.ctx.ctx_stack, prop_key, value, JsValueOrSelf::SelfValue)?;
443                if ok {
444                    Ok(())
445                } else {
446                    Err(JErrorType::TypeError("Failed to set property".to_string()))
447                }
448            }
449            _ => Err(JErrorType::TypeError("Cannot set property on non-object".to_string())),
450        }
451    }
452
453    fn to_property_key(&mut self, value: &JsValue) -> Result<String, JErrorType> {
454        to_string(&mut self.ctx.ctx_stack, value)
455    }
456
457    fn sync_locals_into_ctx(&mut self) {
458        for local in &self.chunk.locals {
459            let name = self.chunk.get_name(local.name_idx);
460            let val = self.read_reg(local.reg).clone();
461            if !self.ctx.has_var_binding(name) {
462                let _ = self.ctx.create_var_binding(name);
463                let _ = self.ctx.initialize_var_binding(name, val.clone());
464            } else {
465                let _ = self.ctx.set_var_binding(name, val.clone());
466            }
467        }
468    }
469
470    fn resolve_object_name(&self, value: &JsValue) -> Option<String> {
471        match value {
472            JsValue::String(_) => Some("String".to_string()),
473            JsValue::Number(_) => Some("Number".to_string()),
474            _ => None,
475        }
476    }
477
478    fn is_truthy(&self, val: &JsValue) -> bool {
479        match val {
480            JsValue::Undefined | JsValue::Null => false,
481            JsValue::Boolean(b) => *b,
482            JsValue::Number(n) => match n {
483                JsNumberType::NaN => false,
484                JsNumberType::Integer(i) => *i != 0,
485                JsNumberType::Float(f) => *f != 0.0,
486                JsNumberType::PositiveInfinity => true,
487                JsNumberType::NegativeInfinity => true,
488            },
489            JsValue::String(s) => !s.is_empty(),
490            JsValue::Symbol(_) => true,
491            JsValue::Object(_) => true,
492        }
493    }
494
495    fn to_f64(&self, val: &JsValue) -> f64 {
496        match val {
497            JsValue::Undefined => f64::NAN,
498            JsValue::Null => 0.0,
499            JsValue::Boolean(b) => if *b { 1.0 } else { 0.0 },
500            JsValue::Number(n) => self.num_to_f64(n),
501            JsValue::String(s) => s.parse::<f64>().unwrap_or(f64::NAN),
502            JsValue::Symbol(_) => f64::NAN,
503            JsValue::Object(_) => f64::NAN,
504        }
505    }
506
507    fn num_to_f64(&self, n: &JsNumberType) -> f64 {
508        match n {
509            JsNumberType::Integer(i) => *i as f64,
510            JsNumberType::Float(f) => *f,
511            JsNumberType::NaN => f64::NAN,
512            JsNumberType::PositiveInfinity => f64::INFINITY,
513            JsNumberType::NegativeInfinity => f64::NEG_INFINITY,
514        }
515    }
516
517    fn f64_to_jsvalue(&self, n: f64) -> JsValue {
518        if n.is_nan() {
519            JsValue::Number(JsNumberType::NaN)
520        } else if n == f64::INFINITY {
521            JsValue::Number(JsNumberType::PositiveInfinity)
522        } else if n == f64::NEG_INFINITY {
523            JsValue::Number(JsNumberType::NegativeInfinity)
524        } else if n.fract() == 0.0 && n >= i64::MIN as f64 && n <= i64::MAX as f64 {
525            JsValue::Number(JsNumberType::Integer(n as i64))
526        } else {
527            JsValue::Number(JsNumberType::Float(n))
528        }
529    }
530
531    fn to_number_value(&self, val: JsValue) -> JsValue {
532        match val {
533            JsValue::Number(_) => val,
534            _ => self.f64_to_jsvalue(self.to_f64(&val)),
535        }
536    }
537
538    fn js_add(&self, a: JsValue, b: JsValue) -> JsValue {
539        match (&a, &b) {
540            (JsValue::String(sa), JsValue::String(sb)) => JsValue::String(format!("{}{}", sa, sb)),
541            (JsValue::String(sa), _) => JsValue::String(format!("{}{}", sa, self.to_display_string(&b))),
542            (_, JsValue::String(sb)) => JsValue::String(format!("{}{}", self.to_display_string(&a), sb)),
543            _ => {
544                if let Some(val) = self.fast_number_binop(&a, &b, |x, y| x + y) {
545                    return val;
546                }
547                self.f64_to_jsvalue(self.to_f64(&a) + self.to_f64(&b))
548            }
549        }
550    }
551
552    fn js_sub(&self, a: JsValue, b: JsValue) -> JsValue {
553        if let Some(val) = self.fast_number_binop(&a, &b, |x, y| x - y) {
554            return val;
555        }
556        self.f64_to_jsvalue(self.to_f64(&a) - self.to_f64(&b))
557    }
558
559    fn js_mul(&self, a: JsValue, b: JsValue) -> JsValue {
560        if let Some(val) = self.fast_number_binop(&a, &b, |x, y| x * y) {
561            return val;
562        }
563        self.f64_to_jsvalue(self.to_f64(&a) * self.to_f64(&b))
564    }
565
566    fn js_div(&self, a: JsValue, b: JsValue) -> JsValue {
567        if let Some(nb) = self.as_number(&b) {
568            let nb = self.num_to_f64(nb);
569            if nb == 0.0 {
570                let na = self.to_f64(&a);
571                if na == 0.0 || na.is_nan() {
572                    return JsValue::Number(JsNumberType::NaN);
573                } else if na > 0.0 {
574                    return JsValue::Number(JsNumberType::PositiveInfinity);
575                } else {
576                    return JsValue::Number(JsNumberType::NegativeInfinity);
577                }
578            }
579            if let Some(val) = self.fast_number_binop(&a, &b, |x, y| x / y) {
580                return val;
581            }
582        }
583
584        let nb = self.to_f64(&b);
585        if nb == 0.0 {
586            let na = self.to_f64(&a);
587            if na == 0.0 || na.is_nan() {
588                JsValue::Number(JsNumberType::NaN)
589            } else if na > 0.0 {
590                JsValue::Number(JsNumberType::PositiveInfinity)
591            } else {
592                JsValue::Number(JsNumberType::NegativeInfinity)
593            }
594        } else {
595            self.f64_to_jsvalue(self.to_f64(&a) / nb)
596        }
597    }
598
599    fn js_mod(&self, a: JsValue, b: JsValue) -> JsValue {
600        if let Some(val) = self.fast_number_binop(&a, &b, |x, y| x % y) {
601            return val;
602        }
603        let na = self.to_f64(&a);
604        let nb = self.to_f64(&b);
605        if nb == 0.0 {
606            JsValue::Number(JsNumberType::NaN)
607        } else {
608            self.f64_to_jsvalue(na % nb)
609        }
610    }
611
612    fn js_negate(&self, a: JsValue) -> JsValue {
613        if let JsValue::Number(n) = &a {
614            return self.f64_to_jsvalue(-self.num_to_f64(n));
615        }
616        self.f64_to_jsvalue(-self.to_f64(&a))
617    }
618
619    fn js_bit_and(&self, a: JsValue, b: JsValue) -> JsValue {
620        let av = self.to_i32(a);
621        let bv = self.to_i32(b);
622        JsValue::Number(JsNumberType::Integer((av & bv) as i64))
623    }
624
625    fn js_bit_or(&self, a: JsValue, b: JsValue) -> JsValue {
626        let av = self.to_i32(a);
627        let bv = self.to_i32(b);
628        JsValue::Number(JsNumberType::Integer((av | bv) as i64))
629    }
630
631    fn js_bit_xor(&self, a: JsValue, b: JsValue) -> JsValue {
632        let av = self.to_i32(a);
633        let bv = self.to_i32(b);
634        JsValue::Number(JsNumberType::Integer((av ^ bv) as i64))
635    }
636
637    fn js_shift_left(&self, a: JsValue, b: JsValue) -> JsValue {
638        let av = self.to_i32(a);
639        let bv = self.to_u32(b);
640        JsValue::Number(JsNumberType::Integer((av << (bv & 0x1f)) as i64))
641    }
642
643    fn js_shift_right(&self, a: JsValue, b: JsValue) -> JsValue {
644        let av = self.to_i32(a);
645        let bv = self.to_u32(b);
646        JsValue::Number(JsNumberType::Integer((av >> (bv & 0x1f)) as i64))
647    }
648
649    fn js_ushift_right(&self, a: JsValue, b: JsValue) -> JsValue {
650        let av = self.to_u32(a);
651        let bv = self.to_u32(b);
652        JsValue::Number(JsNumberType::Integer((av >> (bv & 0x1f)) as i64))
653    }
654
655    fn to_i32(&self, val: JsValue) -> i32 {
656        self.to_f64(&val) as i32
657    }
658
659    fn to_u32(&self, val: JsValue) -> u32 {
660        self.to_f64(&val) as u32
661    }
662
663    fn js_strict_equal(&self, a: &JsValue, b: &JsValue) -> bool {
664        match (a, b) {
665            (JsValue::Undefined, JsValue::Undefined) => true,
666            (JsValue::Null, JsValue::Null) => true,
667            (JsValue::Boolean(a), JsValue::Boolean(b)) => a == b,
668            (JsValue::Number(a), JsValue::Number(b)) => self.num_to_f64(a) == self.num_to_f64(b),
669            (JsValue::String(a), JsValue::String(b)) => a == b,
670            (JsValue::Symbol(a), JsValue::Symbol(b)) => a == b,
671            (JsValue::Object(a), JsValue::Object(b)) => std::rc::Rc::ptr_eq(a, b),
672            _ => false,
673        }
674    }
675
676    fn js_abstract_equal(&self, a: &JsValue, b: &JsValue) -> bool {
677        self.js_strict_equal(a, b)
678    }
679
680    fn js_less_than(&self, a: &JsValue, b: &JsValue) -> bool {
681        match (a, b) {
682            (JsValue::String(sa), JsValue::String(sb)) => sa < sb,
683            _ => self.to_f64(a) < self.to_f64(b),
684        }
685    }
686
687    fn as_number<'b>(&self, v: &'b JsValue) -> Option<&'b JsNumberType> {
688        match v {
689            JsValue::Number(n) => Some(n),
690            _ => None,
691        }
692    }
693
694    fn fast_number_binop(
695        &self,
696        a: &JsValue,
697        b: &JsValue,
698        op: fn(f64, f64) -> f64,
699    ) -> Option<JsValue> {
700        match (self.as_number(a), self.as_number(b)) {
701            (Some(na), Some(nb)) => {
702                let fa = self.num_to_f64(na);
703                let fb = self.num_to_f64(nb);
704                Some(self.f64_to_jsvalue(op(fa, fb)))
705            }
706            _ => None,
707        }
708    }
709
710    fn to_display_string(&self, val: &JsValue) -> String {
711        match val {
712            JsValue::Undefined => "undefined".to_string(),
713            JsValue::Null => "null".to_string(),
714            JsValue::Boolean(b) => b.to_string(),
715            JsValue::Number(n) => match n {
716                JsNumberType::Integer(i) => i.to_string(),
717                JsNumberType::Float(f) => f.to_string(),
718                JsNumberType::NaN => "NaN".to_string(),
719                JsNumberType::PositiveInfinity => "Infinity".to_string(),
720                JsNumberType::NegativeInfinity => "-Infinity".to_string(),
721            },
722            JsValue::String(s) => s.clone(),
723            JsValue::Symbol(_) => "symbol".to_string(),
724            JsValue::Object(_) => "[object]".to_string(),
725        }
726    }
727}