1use rustc_hash::FxHashMap;
28
29use react_compiler_diagnostics::JsString;
30use react_compiler_hir::environment::Environment;
31use react_compiler_hir::{
32 BinaryOperator, BlockKind, FloatValue, FunctionId, GotoVariant, HirFunction, IdentifierId,
33 InstructionValue, NonLocalBinding, Phi, Place, PrimitiveValue, PropertyLiteral, SourceLocation,
34 Terminal, UnaryOperator, UpdateOperator, format_js_number,
35};
36use react_compiler_lowering::{
37 get_reverse_postordered_blocks, mark_instruction_ids, mark_predecessors,
38 remove_dead_do_while_statements, remove_unnecessary_try_catch, remove_unreachable_for_updates,
39};
40use react_compiler_ssa::enter_ssa::placeholder_function;
41
42use crate::merge_consecutive_blocks::merge_consecutive_blocks;
43
44#[derive(Debug, Clone)]
51enum Constant {
52 Primitive {
53 value: PrimitiveValue,
54 loc: Option<SourceLocation>,
55 },
56 LoadGlobal {
57 binding: NonLocalBinding,
58 loc: Option<SourceLocation>,
59 },
60}
61
62impl Constant {
63 fn into_instruction_value(self) -> InstructionValue {
64 match self {
65 Constant::Primitive { value, loc } => InstructionValue::Primitive { value, loc },
66 Constant::LoadGlobal { binding, loc } => InstructionValue::LoadGlobal { binding, loc },
67 }
68 }
69}
70
71type Constants = FxHashMap<IdentifierId, Constant>;
74
75pub fn constant_propagation(func: &mut HirFunction, env: &mut Environment) {
80 let mut constants: Constants = FxHashMap::default();
81 constant_propagation_impl(func, env, &mut constants);
82}
83
84fn constant_propagation_impl(
85 func: &mut HirFunction,
86 env: &mut Environment,
87 constants: &mut Constants,
88) {
89 loop {
90 let have_terminals_changed = apply_constant_propagation(func, env, constants);
91 if !have_terminals_changed {
92 break;
93 }
94 func.body.blocks = get_reverse_postordered_blocks(&func.body, &func.instructions);
99 remove_unreachable_for_updates(&mut func.body);
100 remove_dead_do_while_statements(&mut func.body);
101 remove_unnecessary_try_catch(&mut func.body);
102 mark_instruction_ids(&mut func.body, &mut func.instructions);
103 mark_predecessors(&mut func.body);
104
105 for (_block_id, block) in func.body.blocks.iter_mut() {
107 for phi in &mut block.phis {
108 phi.operands
109 .retain(|pred, _operand| block.preds.contains(pred));
110 }
111 }
112
113 react_compiler_ssa::eliminate_redundant_phi(func, env);
118
119 merge_consecutive_blocks(func, &mut env.functions);
124
125 }
129}
130
131fn apply_constant_propagation(
132 func: &mut HirFunction,
133 env: &mut Environment,
134 constants: &mut Constants,
135) -> bool {
136 let mut has_changes = false;
137
138 let block_ids: Vec<_> = func.body.blocks.keys().copied().collect();
139 for block_id in block_ids {
140 let block = &func.body.blocks[&block_id];
141
142 let phi_updates: Vec<(IdentifierId, Constant)> = block
144 .phis
145 .iter()
146 .filter_map(|phi| {
147 let value = evaluate_phi(phi, constants)?;
148 Some((phi.place.identifier, value))
149 })
150 .collect();
151 for (id, value) in phi_updates {
152 constants.insert(id, value);
153 }
154
155 let block = &func.body.blocks[&block_id];
156 let instr_ids = block.instructions.clone();
157 let block_kind = block.kind;
158 let instr_count = instr_ids.len();
159
160 for (i, instr_id) in instr_ids.iter().enumerate() {
161 if block_kind == BlockKind::Sequence && i == instr_count - 1 {
162 continue;
167 }
168 let result = evaluate_instruction(constants, func, env, *instr_id);
169 if let Some(value) = result {
170 let lvalue_id = func.instructions[instr_id.0 as usize].lvalue.identifier;
171 constants.insert(lvalue_id, value);
172 }
173 }
174
175 let block = &func.body.blocks[&block_id];
176 match &block.terminal {
177 Terminal::If {
178 test,
179 consequent,
180 alternate,
181 id,
182 loc,
183 ..
184 } => {
185 let test_value = read(constants, test);
186 if let Some(Constant::Primitive {
187 value: ref prim, ..
188 }) = test_value
189 {
190 has_changes = true;
191 let target_block_id = if is_truthy(prim) {
192 *consequent
193 } else {
194 *alternate
195 };
196 let terminal = Terminal::Goto {
197 variant: GotoVariant::Break,
198 block: target_block_id,
199 id: *id,
200 loc: *loc,
201 };
202 func.body.blocks.get_mut(&block_id).unwrap().terminal = terminal;
203 }
204 }
205 Terminal::Unsupported { .. }
206 | Terminal::Unreachable { .. }
207 | Terminal::Throw { .. }
208 | Terminal::Return { .. }
209 | Terminal::Goto { .. }
210 | Terminal::Branch { .. }
211 | Terminal::Switch { .. }
212 | Terminal::DoWhile { .. }
213 | Terminal::While { .. }
214 | Terminal::For { .. }
215 | Terminal::ForOf { .. }
216 | Terminal::ForIn { .. }
217 | Terminal::Logical { .. }
218 | Terminal::Ternary { .. }
219 | Terminal::Optional { .. }
220 | Terminal::Label { .. }
221 | Terminal::Sequence { .. }
222 | Terminal::MaybeThrow { .. }
223 | Terminal::Try { .. }
224 | Terminal::Scope { .. }
225 | Terminal::PrunedScope { .. } => {
226 }
228 }
229 }
230
231 has_changes
232}
233
234fn evaluate_phi(phi: &Phi, constants: &Constants) -> Option<Constant> {
239 let mut value: Option<Constant> = None;
240 for (_pred, operand) in &phi.operands {
241 let operand_value = constants.get(&operand.identifier)?;
242
243 match &value {
244 None => {
245 value = Some(operand_value.clone());
247 continue;
248 }
249 Some(current) => match (current, operand_value) {
250 (Constant::Primitive { value: a, .. }, Constant::Primitive { value: b, .. }) => {
251 if !js_strict_equal(a, b) {
253 return None;
254 }
255 }
256 (
257 Constant::LoadGlobal { binding: a, .. },
258 Constant::LoadGlobal { binding: b, .. },
259 ) => {
260 if a.name() != b.name() {
262 return None;
263 }
264 }
265 (Constant::Primitive { .. }, Constant::LoadGlobal { .. })
267 | (Constant::LoadGlobal { .. }, Constant::Primitive { .. }) => {
268 return None;
269 }
270 },
271 }
272 }
273 value
274}
275
276fn evaluate_instruction(
281 constants: &mut Constants,
282 func: &mut HirFunction,
283 env: &mut Environment,
284 instr_id: react_compiler_hir::InstructionId,
285) -> Option<Constant> {
286 let instr = &func.instructions[instr_id.0 as usize];
287 match &instr.value {
288 InstructionValue::Primitive { value, loc } => Some(Constant::Primitive {
289 value: value.clone(),
290 loc: *loc,
291 }),
292 InstructionValue::LoadGlobal { binding, loc } => Some(Constant::LoadGlobal {
293 binding: binding.clone(),
294 loc: *loc,
295 }),
296 InstructionValue::ComputedLoad {
297 object,
298 property,
299 loc,
300 } => {
301 let prop_value = read(constants, property);
302 if let Some(Constant::Primitive {
303 value: ref prim, ..
304 }) = prop_value
305 {
306 match prim {
307 PrimitiveValue::String(s) if s.as_str().is_some_and(is_valid_identifier) => {
308 let object = object.clone();
309 let loc = *loc;
310 let new_property =
311 PropertyLiteral::String(s.as_str().expect("guarded utf8").to_string());
312 func.instructions[instr_id.0 as usize].value =
313 InstructionValue::PropertyLoad {
314 object,
315 property: new_property,
316 loc,
317 };
318 }
319 PrimitiveValue::Number(n) => {
320 let object = object.clone();
321 let loc = *loc;
322 let new_property = PropertyLiteral::Number(*n);
323 func.instructions[instr_id.0 as usize].value =
324 InstructionValue::PropertyLoad {
325 object,
326 property: new_property,
327 loc,
328 };
329 }
330 PrimitiveValue::Null
331 | PrimitiveValue::Undefined
332 | PrimitiveValue::Boolean(_)
333 | PrimitiveValue::String(_) => {}
334 }
335 }
336 None
337 }
338 InstructionValue::ComputedStore {
339 object,
340 property,
341 value,
342 loc,
343 } => {
344 let prop_value = read(constants, property);
345 if let Some(Constant::Primitive {
346 value: ref prim, ..
347 }) = prop_value
348 {
349 match prim {
350 PrimitiveValue::String(s) if s.as_str().is_some_and(is_valid_identifier) => {
351 let object = object.clone();
352 let store_value = value.clone();
353 let loc = *loc;
354 let new_property =
355 PropertyLiteral::String(s.as_str().expect("guarded utf8").to_string());
356 func.instructions[instr_id.0 as usize].value =
357 InstructionValue::PropertyStore {
358 object,
359 property: new_property,
360 value: store_value,
361 loc,
362 };
363 }
364 PrimitiveValue::Number(n) => {
365 let object = object.clone();
366 let store_value = value.clone();
367 let loc = *loc;
368 let new_property = PropertyLiteral::Number(*n);
369 func.instructions[instr_id.0 as usize].value =
370 InstructionValue::PropertyStore {
371 object,
372 property: new_property,
373 value: store_value,
374 loc,
375 };
376 }
377 PrimitiveValue::Null
378 | PrimitiveValue::Undefined
379 | PrimitiveValue::Boolean(_)
380 | PrimitiveValue::String(_) => {}
381 }
382 }
383 None
384 }
385 InstructionValue::PostfixUpdate {
386 lvalue,
387 operation,
388 value,
389 loc,
390 } => {
391 let previous = read(constants, value);
392 if let Some(Constant::Primitive {
393 value: PrimitiveValue::Number(n),
394 loc: prev_loc,
395 }) = previous
396 {
397 let prev_val = n.value();
398 let next_val = match operation {
399 UpdateOperator::Increment => prev_val + 1.0,
400 UpdateOperator::Decrement => prev_val - 1.0,
401 };
402 let lvalue_id = lvalue.identifier;
404 constants.insert(
405 lvalue_id,
406 Constant::Primitive {
407 value: PrimitiveValue::Number(FloatValue::new(next_val)),
408 loc: *loc,
409 },
410 );
411 return Some(Constant::Primitive {
413 value: PrimitiveValue::Number(n),
414 loc: prev_loc,
415 });
416 }
417 None
418 }
419 InstructionValue::PrefixUpdate {
420 lvalue,
421 operation,
422 value,
423 loc,
424 } => {
425 let previous = read(constants, value);
426 if let Some(Constant::Primitive {
427 value: PrimitiveValue::Number(n),
428 ..
429 }) = previous
430 {
431 let prev_val = n.value();
432 let next_val = match operation {
433 UpdateOperator::Increment => prev_val + 1.0,
434 UpdateOperator::Decrement => prev_val - 1.0,
435 };
436 let result = Constant::Primitive {
437 value: PrimitiveValue::Number(FloatValue::new(next_val)),
438 loc: *loc,
439 };
440 let lvalue_id = lvalue.identifier;
442 constants.insert(lvalue_id, result.clone());
443 return Some(result);
444 }
445 None
446 }
447 InstructionValue::UnaryExpression {
448 operator,
449 value,
450 loc,
451 } => match operator {
452 UnaryOperator::Not => {
453 let operand = read(constants, value);
454 if let Some(Constant::Primitive {
455 value: ref prim, ..
456 }) = operand
457 {
458 let negated = !is_truthy(prim);
459 let loc = *loc;
460 let result = Constant::Primitive {
461 value: PrimitiveValue::Boolean(negated),
462 loc,
463 };
464 func.instructions[instr_id.0 as usize].value = InstructionValue::Primitive {
465 value: PrimitiveValue::Boolean(negated),
466 loc,
467 };
468 return Some(result);
469 }
470 None
471 }
472 UnaryOperator::Minus => {
473 let operand = read(constants, value);
474 if let Some(Constant::Primitive {
475 value: PrimitiveValue::Number(n),
476 ..
477 }) = operand
478 {
479 let negated = n.value() * -1.0;
480 let loc = *loc;
481 let result = Constant::Primitive {
482 value: PrimitiveValue::Number(FloatValue::new(negated)),
483 loc,
484 };
485 func.instructions[instr_id.0 as usize].value = InstructionValue::Primitive {
486 value: PrimitiveValue::Number(FloatValue::new(negated)),
487 loc,
488 };
489 return Some(result);
490 }
491 None
492 }
493 UnaryOperator::Plus
494 | UnaryOperator::BitwiseNot
495 | UnaryOperator::TypeOf
496 | UnaryOperator::Void => None,
497 },
498 InstructionValue::BinaryExpression {
499 operator,
500 left,
501 right,
502 loc,
503 } => {
504 let lhs_value = read(constants, left);
505 let rhs_value = read(constants, right);
506 if let (
507 Some(Constant::Primitive { value: lhs, .. }),
508 Some(Constant::Primitive { value: rhs, .. }),
509 ) = (&lhs_value, &rhs_value)
510 {
511 let result = evaluate_binary_op(*operator, lhs, rhs);
512 if let Some(ref prim) = result {
513 let loc = *loc;
514 func.instructions[instr_id.0 as usize].value = InstructionValue::Primitive {
515 value: prim.clone(),
516 loc,
517 };
518 return Some(Constant::Primitive {
519 value: prim.clone(),
520 loc,
521 });
522 }
523 }
524 None
525 }
526 InstructionValue::PropertyLoad {
527 object,
528 property,
529 loc,
530 } => {
531 let object_value = read(constants, object);
532 if let Some(Constant::Primitive {
533 value: PrimitiveValue::String(ref s),
534 ..
535 }) = object_value
536 {
537 if let PropertyLiteral::String(prop_name) = property {
538 if prop_name == "length" {
539 let len = s.len_utf16() as f64;
541 let loc = *loc;
542 let result = Constant::Primitive {
543 value: PrimitiveValue::Number(FloatValue::new(len)),
544 loc,
545 };
546 func.instructions[instr_id.0 as usize].value =
547 InstructionValue::Primitive {
548 value: PrimitiveValue::Number(FloatValue::new(len)),
549 loc,
550 };
551 return Some(result);
552 }
553 }
554 }
555 None
556 }
557 InstructionValue::TemplateLiteral {
558 subexprs,
559 quasis,
560 loc,
561 } => {
562 if subexprs.is_empty() {
563 let mut result_string = String::new();
565 for q in quasis {
566 match &q.cooked {
567 Some(cooked) => result_string.push_str(cooked),
568 None => return None,
569 }
570 }
571 let loc = *loc;
572 let result = Constant::Primitive {
573 value: PrimitiveValue::String(JsString::from_marker_string(&result_string)),
574 loc,
575 };
576 func.instructions[instr_id.0 as usize].value = InstructionValue::Primitive {
577 value: PrimitiveValue::String(JsString::from_marker_string(&result_string)),
578 loc,
579 };
580 return Some(result);
581 }
582
583 if subexprs.len() != quasis.len() - 1 {
584 return None;
585 }
586
587 if quasis.iter().any(|q| q.cooked.is_none()) {
588 return None;
589 }
590
591 let mut quasi_index = 0usize;
592 let mut result_string = quasis[quasi_index].cooked.as_ref().unwrap().clone();
593 quasi_index += 1;
594
595 for sub_expr in subexprs {
596 let sub_expr_value = read(constants, sub_expr);
597 let sub_prim = match sub_expr_value {
598 Some(Constant::Primitive { ref value, .. }) => value,
599 _ => return None,
600 };
601
602 let expression_str = match sub_prim {
603 PrimitiveValue::Null => "null".to_string(),
604 PrimitiveValue::Boolean(b) => b.to_string(),
605 PrimitiveValue::Number(n) => format_js_number(n.value()),
606 PrimitiveValue::String(s) => s.to_marker_string(),
607 PrimitiveValue::Undefined => return None,
609 };
610
611 let suffix = match &quasis[quasi_index].cooked {
612 Some(s) => s.clone(),
613 None => return None,
614 };
615 quasi_index += 1;
616
617 result_string.push_str(&expression_str);
618 result_string.push_str(&suffix);
619 }
620
621 let loc = *loc;
622 let result = Constant::Primitive {
623 value: PrimitiveValue::String(JsString::from_marker_string(&result_string)),
624 loc,
625 };
626 func.instructions[instr_id.0 as usize].value = InstructionValue::Primitive {
627 value: PrimitiveValue::String(JsString::from_marker_string(&result_string)),
628 loc,
629 };
630 Some(result)
631 }
632 InstructionValue::LoadLocal { place, .. } => {
633 let place_value = read(constants, place);
634 if let Some(ref constant) = place_value {
635 func.instructions[instr_id.0 as usize].value =
637 constant.clone().into_instruction_value();
638 }
639 place_value
640 }
641 InstructionValue::StoreLocal { lvalue, value, .. } => {
642 let place_value = read(constants, value);
643 if let Some(ref constant) = place_value {
644 let lvalue_id = lvalue.place.identifier;
645 constants.insert(lvalue_id, constant.clone());
646 }
647 place_value
648 }
649 InstructionValue::FunctionExpression { lowered_func, .. } => {
650 let func_id = lowered_func.func;
651 process_inner_function(func_id, env, constants);
652 None
653 }
654 InstructionValue::ObjectMethod { lowered_func, .. } => {
655 let func_id = lowered_func.func;
656 process_inner_function(func_id, env, constants);
657 None
658 }
659 InstructionValue::StartMemoize { deps, .. } => {
660 if let Some(deps) = deps {
661 let const_dep_indices: Vec<usize> = deps
663 .iter()
664 .enumerate()
665 .filter_map(|(i, dep)| {
666 if let react_compiler_hir::ManualMemoDependencyRoot::NamedLocal {
667 value,
668 ..
669 } = &dep.root
670 {
671 let pv = read(constants, value);
672 if matches!(pv, Some(Constant::Primitive { .. })) {
673 return Some(i);
674 }
675 }
676 None
677 })
678 .collect();
679 for idx in const_dep_indices {
680 if let InstructionValue::StartMemoize {
681 deps: Some(ref mut deps),
682 ..
683 } = func.instructions[instr_id.0 as usize].value
684 {
685 if let react_compiler_hir::ManualMemoDependencyRoot::NamedLocal {
686 constant,
687 ..
688 } = &mut deps[idx].root
689 {
690 *constant = true;
691 }
692 }
693 }
694 }
695 None
696 }
697 InstructionValue::LoadContext { .. }
699 | InstructionValue::DeclareLocal { .. }
700 | InstructionValue::DeclareContext { .. }
701 | InstructionValue::StoreContext { .. }
702 | InstructionValue::Destructure { .. }
703 | InstructionValue::JSXText { .. }
704 | InstructionValue::NewExpression { .. }
705 | InstructionValue::CallExpression { .. }
706 | InstructionValue::MethodCall { .. }
707 | InstructionValue::TypeCastExpression { .. }
708 | InstructionValue::JsxExpression { .. }
709 | InstructionValue::ObjectExpression { .. }
710 | InstructionValue::ArrayExpression { .. }
711 | InstructionValue::JsxFragment { .. }
712 | InstructionValue::RegExpLiteral { .. }
713 | InstructionValue::MetaProperty { .. }
714 | InstructionValue::PropertyStore { .. }
715 | InstructionValue::PropertyDelete { .. }
716 | InstructionValue::ComputedDelete { .. }
717 | InstructionValue::StoreGlobal { .. }
718 | InstructionValue::TaggedTemplateExpression { .. }
719 | InstructionValue::Await { .. }
720 | InstructionValue::GetIterator { .. }
721 | InstructionValue::IteratorNext { .. }
722 | InstructionValue::NextPropertyOf { .. }
723 | InstructionValue::Debugger { .. }
724 | InstructionValue::FinishMemoize { .. }
725 | InstructionValue::UnsupportedNode { .. } => None,
726 }
727}
728
729fn process_inner_function(func_id: FunctionId, env: &mut Environment, constants: &mut Constants) {
734 let mut inner = std::mem::replace(
735 &mut env.functions[func_id.0 as usize],
736 placeholder_function(),
737 );
738 constant_propagation_impl(&mut inner, env, constants);
739 env.functions[func_id.0 as usize] = inner;
740}
741
742fn read(constants: &Constants, place: &Place) -> Option<Constant> {
747 constants.get(&place.identifier).cloned()
748}
749
750fn is_valid_identifier(s: &str) -> bool {
758 if s.is_empty() {
759 return false;
760 }
761 let mut chars = s.chars();
762 match chars.next() {
763 Some(c) if is_id_start(c) => {}
764 _ => return false,
765 }
766 if !chars.all(is_id_continue) {
767 return false;
768 }
769 !is_reserved_word(s)
770}
771
772fn is_reserved_word(s: &str) -> bool {
775 matches!(
776 s,
777 "break"
778 | "case"
779 | "catch"
780 | "continue"
781 | "debugger"
782 | "default"
783 | "do"
784 | "else"
785 | "finally"
786 | "for"
787 | "function"
788 | "if"
789 | "in"
790 | "instanceof"
791 | "new"
792 | "return"
793 | "switch"
794 | "this"
795 | "throw"
796 | "try"
797 | "typeof"
798 | "var"
799 | "void"
800 | "while"
801 | "with"
802 | "class"
803 | "const"
804 | "enum"
805 | "export"
806 | "extends"
807 | "import"
808 | "super"
809 | "implements"
810 | "interface"
811 | "let"
812 | "package"
813 | "private"
814 | "protected"
815 | "public"
816 | "static"
817 | "yield"
818 | "await"
819 | "delete"
820 | "null"
821 | "true"
822 | "false"
823 )
824}
825
826fn is_id_start(c: char) -> bool {
828 c == '_' || c == '$' || c.is_alphabetic()
829}
830
831fn is_id_continue(c: char) -> bool {
833 c == '$'
834 || c == '_'
835 || c.is_alphanumeric()
836 || c == '\u{200C}' || c == '\u{200D}' }
839
840fn is_truthy(value: &PrimitiveValue) -> bool {
845 match value {
846 PrimitiveValue::Null => false,
847 PrimitiveValue::Undefined => false,
848 PrimitiveValue::Boolean(b) => *b,
849 PrimitiveValue::Number(n) => {
850 let v = n.value();
851 v != 0.0 && !v.is_nan()
852 }
853 PrimitiveValue::String(s) => s.len_utf16() != 0,
854 }
855}
856
857fn evaluate_binary_op(
862 operator: BinaryOperator,
863 lhs: &PrimitiveValue,
864 rhs: &PrimitiveValue,
865) -> Option<PrimitiveValue> {
866 match operator {
867 BinaryOperator::Add => match (lhs, rhs) {
868 (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(PrimitiveValue::Number(
869 FloatValue::new(l.value() + r.value()),
870 )),
871 (PrimitiveValue::String(l), PrimitiveValue::String(r)) => {
872 let mut units = l.code_units();
875 units.extend(r.code_units());
876 Some(PrimitiveValue::String(
877 react_compiler_diagnostics::JsString::from_code_units(units),
878 ))
879 }
880 _ => None,
881 },
882 BinaryOperator::Subtract => match (lhs, rhs) {
883 (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(PrimitiveValue::Number(
884 FloatValue::new(l.value() - r.value()),
885 )),
886 _ => None,
887 },
888 BinaryOperator::Multiply => match (lhs, rhs) {
889 (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(PrimitiveValue::Number(
890 FloatValue::new(l.value() * r.value()),
891 )),
892 _ => None,
893 },
894 BinaryOperator::Divide => match (lhs, rhs) {
895 (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(PrimitiveValue::Number(
896 FloatValue::new(l.value() / r.value()),
897 )),
898 _ => None,
899 },
900 BinaryOperator::Modulo => match (lhs, rhs) {
901 (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(PrimitiveValue::Number(
902 FloatValue::new(l.value() % r.value()),
903 )),
904 _ => None,
905 },
906 BinaryOperator::Exponent => match (lhs, rhs) {
907 (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(PrimitiveValue::Number(
908 FloatValue::new(l.value().powf(r.value())),
909 )),
910 _ => None,
911 },
912 BinaryOperator::BitwiseOr => match (lhs, rhs) {
913 (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
914 let result = js_to_int32(l.value()) | js_to_int32(r.value());
915 Some(PrimitiveValue::Number(FloatValue::new(result as f64)))
916 }
917 _ => None,
918 },
919 BinaryOperator::BitwiseAnd => match (lhs, rhs) {
920 (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
921 let result = js_to_int32(l.value()) & js_to_int32(r.value());
922 Some(PrimitiveValue::Number(FloatValue::new(result as f64)))
923 }
924 _ => None,
925 },
926 BinaryOperator::BitwiseXor => match (lhs, rhs) {
927 (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
928 let result = js_to_int32(l.value()) ^ js_to_int32(r.value());
929 Some(PrimitiveValue::Number(FloatValue::new(result as f64)))
930 }
931 _ => None,
932 },
933 BinaryOperator::ShiftLeft => match (lhs, rhs) {
934 (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
935 let result = js_to_int32(l.value()) << (js_to_uint32(r.value()) & 0x1f);
936 Some(PrimitiveValue::Number(FloatValue::new(result as f64)))
937 }
938 _ => None,
939 },
940 BinaryOperator::ShiftRight => match (lhs, rhs) {
941 (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
942 let result = js_to_int32(l.value()) >> (js_to_uint32(r.value()) & 0x1f);
943 Some(PrimitiveValue::Number(FloatValue::new(result as f64)))
944 }
945 _ => None,
946 },
947 BinaryOperator::UnsignedShiftRight => match (lhs, rhs) {
948 (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
949 let result = js_to_uint32(l.value()) >> (js_to_uint32(r.value()) & 0x1f);
950 Some(PrimitiveValue::Number(FloatValue::new(result as f64)))
951 }
952 _ => None,
953 },
954 BinaryOperator::LessThan => match (lhs, rhs) {
955 (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
956 Some(PrimitiveValue::Boolean(l.value() < r.value()))
957 }
958 _ => None,
959 },
960 BinaryOperator::LessEqual => match (lhs, rhs) {
961 (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
962 Some(PrimitiveValue::Boolean(l.value() <= r.value()))
963 }
964 _ => None,
965 },
966 BinaryOperator::GreaterThan => match (lhs, rhs) {
967 (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
968 Some(PrimitiveValue::Boolean(l.value() > r.value()))
969 }
970 _ => None,
971 },
972 BinaryOperator::GreaterEqual => match (lhs, rhs) {
973 (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
974 Some(PrimitiveValue::Boolean(l.value() >= r.value()))
975 }
976 _ => None,
977 },
978 BinaryOperator::StrictEqual => Some(PrimitiveValue::Boolean(js_strict_equal(lhs, rhs))),
979 BinaryOperator::StrictNotEqual => Some(PrimitiveValue::Boolean(!js_strict_equal(lhs, rhs))),
980 BinaryOperator::Equal => Some(PrimitiveValue::Boolean(js_abstract_equal(lhs, rhs))),
981 BinaryOperator::NotEqual => Some(PrimitiveValue::Boolean(!js_abstract_equal(lhs, rhs))),
982 BinaryOperator::In | BinaryOperator::InstanceOf => None,
983 }
984}
985
986fn js_strict_equal(lhs: &PrimitiveValue, rhs: &PrimitiveValue) -> bool {
991 match (lhs, rhs) {
992 (PrimitiveValue::Null, PrimitiveValue::Null) => true,
993 (PrimitiveValue::Undefined, PrimitiveValue::Undefined) => true,
994 (PrimitiveValue::Boolean(a), PrimitiveValue::Boolean(b)) => a == b,
995 (PrimitiveValue::Number(a), PrimitiveValue::Number(b)) => {
996 let av = a.value();
997 let bv = b.value();
998 if av.is_nan() || bv.is_nan() {
1000 return false;
1001 }
1002 av == bv
1003 }
1004 (PrimitiveValue::String(a), PrimitiveValue::String(b)) => a == b,
1005 _ => false,
1007 }
1008}
1009
1010fn js_to_number(s: &str) -> f64 {
1013 let trimmed = s.trim();
1014 if trimmed.is_empty() {
1015 return 0.0;
1016 }
1017 if trimmed == "Infinity" || trimmed == "+Infinity" {
1018 return f64::INFINITY;
1019 }
1020 if trimmed == "-Infinity" {
1021 return f64::NEG_INFINITY;
1022 }
1023 if trimmed.starts_with("0x") || trimmed.starts_with("0X") {
1025 return match u64::from_str_radix(&trimmed[2..], 16) {
1026 Ok(v) => v as f64,
1027 Err(_) => f64::NAN,
1028 };
1029 }
1030 if trimmed.starts_with("0o") || trimmed.starts_with("0O") {
1032 return match u64::from_str_radix(&trimmed[2..], 8) {
1033 Ok(v) => v as f64,
1034 Err(_) => f64::NAN,
1035 };
1036 }
1037 if trimmed.starts_with("0b") || trimmed.starts_with("0B") {
1039 return match u64::from_str_radix(&trimmed[2..], 2) {
1040 Ok(v) => v as f64,
1041 Err(_) => f64::NAN,
1042 };
1043 }
1044 trimmed.parse::<f64>().unwrap_or(f64::NAN)
1045}
1046
1047fn js_abstract_equal(lhs: &PrimitiveValue, rhs: &PrimitiveValue) -> bool {
1048 match (lhs, rhs) {
1049 (PrimitiveValue::Null, PrimitiveValue::Null) => true,
1050 (PrimitiveValue::Undefined, PrimitiveValue::Undefined) => true,
1051 (PrimitiveValue::Null, PrimitiveValue::Undefined)
1052 | (PrimitiveValue::Undefined, PrimitiveValue::Null) => true,
1053 (PrimitiveValue::Boolean(a), PrimitiveValue::Boolean(b)) => a == b,
1054 (PrimitiveValue::Number(a), PrimitiveValue::Number(b)) => {
1055 let av = a.value();
1056 let bv = b.value();
1057 if av.is_nan() || bv.is_nan() {
1058 return false;
1059 }
1060 av == bv
1061 }
1062 (PrimitiveValue::String(a), PrimitiveValue::String(b)) => a == b,
1063 (PrimitiveValue::Number(n), PrimitiveValue::String(s))
1065 | (PrimitiveValue::String(s), PrimitiveValue::Number(n)) => {
1066 let sv = match s.as_str() {
1069 Some(utf8) => js_to_number(utf8),
1070 None => f64::NAN,
1071 };
1072 let nv = n.value();
1073 if nv.is_nan() || sv.is_nan() {
1074 false
1075 } else {
1076 nv == sv
1077 }
1078 }
1079 (PrimitiveValue::Boolean(b), other) => {
1080 let num = if *b { 1.0 } else { 0.0 };
1081 js_abstract_equal(&PrimitiveValue::Number(FloatValue::new(num)), other)
1082 }
1083 (other, PrimitiveValue::Boolean(b)) => {
1084 let num = if *b { 1.0 } else { 0.0 };
1085 js_abstract_equal(other, &PrimitiveValue::Number(FloatValue::new(num)))
1086 }
1087 _ => false,
1089 }
1090}
1091
1092fn js_to_int32(n: f64) -> i32 {
1098 if n.is_nan() || n.is_infinite() || n == 0.0 {
1099 return 0;
1100 }
1101 let int64 = (n.trunc() as i64) & 0xFFFFFFFF;
1103 if int64 >= 0x80000000 {
1105 (int64 as u32) as i32
1106 } else {
1107 int64 as i32
1108 }
1109}
1110
1111fn js_to_uint32(n: f64) -> u32 {
1113 js_to_int32(n) as u32
1114}