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