use std::collections::HashMap;
use react_compiler_hir::environment::Environment;
use react_compiler_hir::{
BinaryOperator, BlockKind, FloatValue, FunctionId, GotoVariant, HirFunction, IdentifierId,
InstructionValue, NonLocalBinding, Phi, Place, PrimitiveValue, PropertyLiteral, SourceLocation,
Terminal, UnaryOperator, UpdateOperator, format_js_number,
};
use react_compiler_lowering::{
get_reverse_postordered_blocks, mark_instruction_ids, mark_predecessors,
remove_dead_do_while_statements, remove_unnecessary_try_catch, remove_unreachable_for_updates,
};
use react_compiler_ssa::enter_ssa::placeholder_function;
use crate::merge_consecutive_blocks::merge_consecutive_blocks;
#[derive(Debug, Clone)]
enum Constant {
Primitive {
value: PrimitiveValue,
loc: Option<SourceLocation>,
},
LoadGlobal {
binding: NonLocalBinding,
loc: Option<SourceLocation>,
},
}
impl Constant {
fn into_instruction_value(self) -> InstructionValue {
match self {
Constant::Primitive { value, loc } => InstructionValue::Primitive { value, loc },
Constant::LoadGlobal { binding, loc } => InstructionValue::LoadGlobal { binding, loc },
}
}
}
type Constants = HashMap<IdentifierId, Constant>;
pub fn constant_propagation(func: &mut HirFunction, env: &mut Environment) {
let mut constants: Constants = HashMap::new();
constant_propagation_impl(func, env, &mut constants);
}
fn constant_propagation_impl(
func: &mut HirFunction,
env: &mut Environment,
constants: &mut Constants,
) {
loop {
let have_terminals_changed = apply_constant_propagation(func, env, constants);
if !have_terminals_changed {
break;
}
func.body.blocks = get_reverse_postordered_blocks(&func.body, &func.instructions);
remove_unreachable_for_updates(&mut func.body);
remove_dead_do_while_statements(&mut func.body);
remove_unnecessary_try_catch(&mut func.body);
mark_instruction_ids(&mut func.body, &mut func.instructions);
mark_predecessors(&mut func.body);
for (_block_id, block) in func.body.blocks.iter_mut() {
for phi in &mut block.phis {
phi.operands
.retain(|pred, _operand| block.preds.contains(pred));
}
}
react_compiler_ssa::eliminate_redundant_phi(func, env);
merge_consecutive_blocks(func, &mut env.functions);
}
}
fn apply_constant_propagation(
func: &mut HirFunction,
env: &mut Environment,
constants: &mut Constants,
) -> bool {
let mut has_changes = false;
let block_ids: Vec<_> = func.body.blocks.keys().copied().collect();
for block_id in block_ids {
let block = &func.body.blocks[&block_id];
let phi_updates: Vec<(IdentifierId, Constant)> = block
.phis
.iter()
.filter_map(|phi| {
let value = evaluate_phi(phi, constants)?;
Some((phi.place.identifier, value))
})
.collect();
for (id, value) in phi_updates {
constants.insert(id, value);
}
let block = &func.body.blocks[&block_id];
let instr_ids = block.instructions.clone();
let block_kind = block.kind;
let instr_count = instr_ids.len();
for (i, instr_id) in instr_ids.iter().enumerate() {
if block_kind == BlockKind::Sequence && i == instr_count - 1 {
continue;
}
let result = evaluate_instruction(constants, func, env, *instr_id);
if let Some(value) = result {
let lvalue_id = func.instructions[instr_id.0 as usize].lvalue.identifier;
constants.insert(lvalue_id, value);
}
}
let block = &func.body.blocks[&block_id];
match &block.terminal {
Terminal::If {
test,
consequent,
alternate,
id,
loc,
..
} => {
let test_value = read(constants, test);
if let Some(Constant::Primitive { value: ref prim, .. }) = test_value {
has_changes = true;
let target_block_id = if is_truthy(prim) {
*consequent
} else {
*alternate
};
let terminal = Terminal::Goto {
variant: GotoVariant::Break,
block: target_block_id,
id: *id,
loc: *loc,
};
func.body.blocks.get_mut(&block_id).unwrap().terminal = terminal;
}
}
Terminal::Unsupported { .. }
| Terminal::Unreachable { .. }
| Terminal::Throw { .. }
| Terminal::Return { .. }
| Terminal::Goto { .. }
| Terminal::Branch { .. }
| Terminal::Switch { .. }
| Terminal::DoWhile { .. }
| Terminal::While { .. }
| Terminal::For { .. }
| Terminal::ForOf { .. }
| Terminal::ForIn { .. }
| Terminal::Logical { .. }
| Terminal::Ternary { .. }
| Terminal::Optional { .. }
| Terminal::Label { .. }
| Terminal::Sequence { .. }
| Terminal::MaybeThrow { .. }
| Terminal::Try { .. }
| Terminal::Scope { .. }
| Terminal::PrunedScope { .. } => {
}
}
}
has_changes
}
fn evaluate_phi(phi: &Phi, constants: &Constants) -> Option<Constant> {
let mut value: Option<Constant> = None;
for (_pred, operand) in &phi.operands {
let operand_value = constants.get(&operand.identifier)?;
match &value {
None => {
value = Some(operand_value.clone());
continue;
}
Some(current) => match (current, operand_value) {
(
Constant::Primitive { value: a, .. },
Constant::Primitive { value: b, .. },
) => {
if !js_strict_equal(a, b) {
return None;
}
}
(
Constant::LoadGlobal { binding: a, .. },
Constant::LoadGlobal { binding: b, .. },
) => {
if a.name() != b.name() {
return None;
}
}
(Constant::Primitive { .. }, Constant::LoadGlobal { .. })
| (Constant::LoadGlobal { .. }, Constant::Primitive { .. }) => {
return None;
}
},
}
}
value
}
fn evaluate_instruction(
constants: &mut Constants,
func: &mut HirFunction,
env: &mut Environment,
instr_id: react_compiler_hir::InstructionId,
) -> Option<Constant> {
let instr = &func.instructions[instr_id.0 as usize];
match &instr.value {
InstructionValue::Primitive { value, loc } => Some(Constant::Primitive {
value: value.clone(),
loc: *loc,
}),
InstructionValue::LoadGlobal { binding, loc } => Some(Constant::LoadGlobal {
binding: binding.clone(),
loc: *loc,
}),
InstructionValue::ComputedLoad {
object,
property,
loc,
} => {
let prop_value = read(constants, property);
if let Some(Constant::Primitive {
value: ref prim, ..
}) = prop_value
{
match prim {
PrimitiveValue::String(s) if is_valid_identifier(s) => {
let object = object.clone();
let loc = *loc;
let new_property = PropertyLiteral::String(s.clone());
func.instructions[instr_id.0 as usize].value =
InstructionValue::PropertyLoad {
object,
property: new_property,
loc,
};
}
PrimitiveValue::Number(n) => {
let object = object.clone();
let loc = *loc;
let new_property = PropertyLiteral::Number(*n);
func.instructions[instr_id.0 as usize].value =
InstructionValue::PropertyLoad {
object,
property: new_property,
loc,
};
}
PrimitiveValue::Null
| PrimitiveValue::Undefined
| PrimitiveValue::Boolean(_)
| PrimitiveValue::String(_) => {}
}
}
None
}
InstructionValue::ComputedStore {
object,
property,
value,
loc,
} => {
let prop_value = read(constants, property);
if let Some(Constant::Primitive {
value: ref prim, ..
}) = prop_value
{
match prim {
PrimitiveValue::String(s) if is_valid_identifier(s) => {
let object = object.clone();
let store_value = value.clone();
let loc = *loc;
let new_property = PropertyLiteral::String(s.clone());
func.instructions[instr_id.0 as usize].value =
InstructionValue::PropertyStore {
object,
property: new_property,
value: store_value,
loc,
};
}
PrimitiveValue::Number(n) => {
let object = object.clone();
let store_value = value.clone();
let loc = *loc;
let new_property = PropertyLiteral::Number(*n);
func.instructions[instr_id.0 as usize].value =
InstructionValue::PropertyStore {
object,
property: new_property,
value: store_value,
loc,
};
}
PrimitiveValue::Null
| PrimitiveValue::Undefined
| PrimitiveValue::Boolean(_)
| PrimitiveValue::String(_) => {}
}
}
None
}
InstructionValue::PostfixUpdate {
lvalue,
operation,
value,
loc,
} => {
let previous = read(constants, value);
if let Some(Constant::Primitive {
value: PrimitiveValue::Number(n),
loc: prev_loc,
}) = previous
{
let prev_val = n.value();
let next_val = match operation {
UpdateOperator::Increment => prev_val + 1.0,
UpdateOperator::Decrement => prev_val - 1.0,
};
let lvalue_id = lvalue.identifier;
constants.insert(
lvalue_id,
Constant::Primitive {
value: PrimitiveValue::Number(FloatValue::new(next_val)),
loc: *loc,
},
);
return Some(Constant::Primitive {
value: PrimitiveValue::Number(n),
loc: prev_loc,
});
}
None
}
InstructionValue::PrefixUpdate {
lvalue,
operation,
value,
loc,
} => {
let previous = read(constants, value);
if let Some(Constant::Primitive {
value: PrimitiveValue::Number(n),
..
}) = previous
{
let prev_val = n.value();
let next_val = match operation {
UpdateOperator::Increment => prev_val + 1.0,
UpdateOperator::Decrement => prev_val - 1.0,
};
let result = Constant::Primitive {
value: PrimitiveValue::Number(FloatValue::new(next_val)),
loc: *loc,
};
let lvalue_id = lvalue.identifier;
constants.insert(lvalue_id, result.clone());
return Some(result);
}
None
}
InstructionValue::UnaryExpression {
operator,
value,
loc,
} => match operator {
UnaryOperator::Not => {
let operand = read(constants, value);
if let Some(Constant::Primitive {
value: ref prim, ..
}) = operand
{
let negated = !is_truthy(prim);
let loc = *loc;
let result = Constant::Primitive {
value: PrimitiveValue::Boolean(negated),
loc,
};
func.instructions[instr_id.0 as usize].value = InstructionValue::Primitive {
value: PrimitiveValue::Boolean(negated),
loc,
};
return Some(result);
}
None
}
UnaryOperator::Minus => {
let operand = read(constants, value);
if let Some(Constant::Primitive {
value: PrimitiveValue::Number(n),
..
}) = operand
{
let negated = n.value() * -1.0;
let loc = *loc;
let result = Constant::Primitive {
value: PrimitiveValue::Number(FloatValue::new(negated)),
loc,
};
func.instructions[instr_id.0 as usize].value = InstructionValue::Primitive {
value: PrimitiveValue::Number(FloatValue::new(negated)),
loc,
};
return Some(result);
}
None
}
UnaryOperator::Plus
| UnaryOperator::BitwiseNot
| UnaryOperator::TypeOf
| UnaryOperator::Void => None,
},
InstructionValue::BinaryExpression {
operator,
left,
right,
loc,
} => {
let lhs_value = read(constants, left);
let rhs_value = read(constants, right);
if let (
Some(Constant::Primitive { value: lhs, .. }),
Some(Constant::Primitive { value: rhs, .. }),
) = (&lhs_value, &rhs_value)
{
let result = evaluate_binary_op(*operator, lhs, rhs);
if let Some(ref prim) = result {
let loc = *loc;
func.instructions[instr_id.0 as usize].value = InstructionValue::Primitive {
value: prim.clone(),
loc,
};
return Some(Constant::Primitive {
value: prim.clone(),
loc,
});
}
}
None
}
InstructionValue::PropertyLoad {
object,
property,
loc,
} => {
let object_value = read(constants, object);
if let Some(Constant::Primitive {
value: PrimitiveValue::String(ref s),
..
}) = object_value
{
if let PropertyLiteral::String(prop_name) = property {
if prop_name == "length" {
let len = s.encode_utf16().count() as f64;
let loc = *loc;
let result = Constant::Primitive {
value: PrimitiveValue::Number(FloatValue::new(len)),
loc,
};
func.instructions[instr_id.0 as usize].value =
InstructionValue::Primitive {
value: PrimitiveValue::Number(FloatValue::new(len)),
loc,
};
return Some(result);
}
}
}
None
}
InstructionValue::TemplateLiteral {
subexprs,
quasis,
loc,
} => {
if subexprs.is_empty() {
let mut result_string = String::new();
for q in quasis {
match &q.cooked {
Some(cooked) => result_string.push_str(cooked),
None => return None,
}
}
let loc = *loc;
let result = Constant::Primitive {
value: PrimitiveValue::String(result_string.clone()),
loc,
};
func.instructions[instr_id.0 as usize].value = InstructionValue::Primitive {
value: PrimitiveValue::String(result_string),
loc,
};
return Some(result);
}
if subexprs.len() != quasis.len() - 1 {
return None;
}
if quasis.iter().any(|q| q.cooked.is_none()) {
return None;
}
let mut quasi_index = 0usize;
let mut result_string = quasis[quasi_index].cooked.as_ref().unwrap().clone();
quasi_index += 1;
for sub_expr in subexprs {
let sub_expr_value = read(constants, sub_expr);
let sub_prim = match sub_expr_value {
Some(Constant::Primitive { ref value, .. }) => value,
_ => return None,
};
let expression_str = match sub_prim {
PrimitiveValue::Null => "null".to_string(),
PrimitiveValue::Boolean(b) => b.to_string(),
PrimitiveValue::Number(n) => format_js_number(n.value()),
PrimitiveValue::String(s) => s.clone(),
PrimitiveValue::Undefined => return None,
};
let suffix = match &quasis[quasi_index].cooked {
Some(s) => s.clone(),
None => return None,
};
quasi_index += 1;
result_string.push_str(&expression_str);
result_string.push_str(&suffix);
}
let loc = *loc;
let result = Constant::Primitive {
value: PrimitiveValue::String(result_string.clone()),
loc,
};
func.instructions[instr_id.0 as usize].value = InstructionValue::Primitive {
value: PrimitiveValue::String(result_string),
loc,
};
Some(result)
}
InstructionValue::LoadLocal { place, .. } => {
let place_value = read(constants, place);
if let Some(ref constant) = place_value {
func.instructions[instr_id.0 as usize].value =
constant.clone().into_instruction_value();
}
place_value
}
InstructionValue::StoreLocal { lvalue, value, .. } => {
let place_value = read(constants, value);
if let Some(ref constant) = place_value {
let lvalue_id = lvalue.place.identifier;
constants.insert(lvalue_id, constant.clone());
}
place_value
}
InstructionValue::FunctionExpression {
lowered_func, ..
} => {
let func_id = lowered_func.func;
process_inner_function(func_id, env, constants);
None
}
InstructionValue::ObjectMethod {
lowered_func, ..
} => {
let func_id = lowered_func.func;
process_inner_function(func_id, env, constants);
None
}
InstructionValue::StartMemoize { deps, .. } => {
if let Some(deps) = deps {
let const_dep_indices: Vec<usize> = deps
.iter()
.enumerate()
.filter_map(|(i, dep)| {
if let react_compiler_hir::ManualMemoDependencyRoot::NamedLocal {
value,
..
} = &dep.root
{
let pv = read(constants, value);
if matches!(pv, Some(Constant::Primitive { .. })) {
return Some(i);
}
}
None
})
.collect();
for idx in const_dep_indices {
if let InstructionValue::StartMemoize {
deps: Some(ref mut deps),
..
} = func.instructions[instr_id.0 as usize].value
{
if let react_compiler_hir::ManualMemoDependencyRoot::NamedLocal {
constant,
..
} = &mut deps[idx].root
{
*constant = true;
}
}
}
}
None
}
InstructionValue::LoadContext { .. }
| InstructionValue::DeclareLocal { .. }
| InstructionValue::DeclareContext { .. }
| InstructionValue::StoreContext { .. }
| InstructionValue::Destructure { .. }
| InstructionValue::JSXText { .. }
| InstructionValue::NewExpression { .. }
| InstructionValue::CallExpression { .. }
| InstructionValue::MethodCall { .. }
| InstructionValue::TypeCastExpression { .. }
| InstructionValue::JsxExpression { .. }
| InstructionValue::ObjectExpression { .. }
| InstructionValue::ArrayExpression { .. }
| InstructionValue::JsxFragment { .. }
| InstructionValue::RegExpLiteral { .. }
| InstructionValue::MetaProperty { .. }
| InstructionValue::PropertyStore { .. }
| InstructionValue::PropertyDelete { .. }
| InstructionValue::ComputedDelete { .. }
| InstructionValue::StoreGlobal { .. }
| InstructionValue::TaggedTemplateExpression { .. }
| InstructionValue::Await { .. }
| InstructionValue::GetIterator { .. }
| InstructionValue::IteratorNext { .. }
| InstructionValue::NextPropertyOf { .. }
| InstructionValue::Debugger { .. }
| InstructionValue::FinishMemoize { .. }
| InstructionValue::UnsupportedNode { .. } => None,
}
}
fn process_inner_function(func_id: FunctionId, env: &mut Environment, constants: &mut Constants) {
let mut inner = std::mem::replace(
&mut env.functions[func_id.0 as usize],
placeholder_function(),
);
constant_propagation_impl(&mut inner, env, constants);
env.functions[func_id.0 as usize] = inner;
}
fn read(constants: &Constants, place: &Place) -> Option<Constant> {
constants.get(&place.identifier).cloned()
}
fn is_valid_identifier(s: &str) -> bool {
if s.is_empty() {
return false;
}
let mut chars = s.chars();
match chars.next() {
Some(c) if is_id_start(c) => {}
_ => return false,
}
if !chars.all(is_id_continue) {
return false;
}
!is_reserved_word(s)
}
fn is_reserved_word(s: &str) -> bool {
matches!(
s,
"break"
| "case"
| "catch"
| "continue"
| "debugger"
| "default"
| "do"
| "else"
| "finally"
| "for"
| "function"
| "if"
| "in"
| "instanceof"
| "new"
| "return"
| "switch"
| "this"
| "throw"
| "try"
| "typeof"
| "var"
| "void"
| "while"
| "with"
| "class"
| "const"
| "enum"
| "export"
| "extends"
| "import"
| "super"
| "implements"
| "interface"
| "let"
| "package"
| "private"
| "protected"
| "public"
| "static"
| "yield"
| "await"
| "delete"
| "null"
| "true"
| "false"
)
}
fn is_id_start(c: char) -> bool {
c == '_' || c == '$' || c.is_alphabetic()
}
fn is_id_continue(c: char) -> bool {
c == '$'
|| c == '_'
|| c.is_alphanumeric()
|| c == '\u{200C}' || c == '\u{200D}' }
fn is_truthy(value: &PrimitiveValue) -> bool {
match value {
PrimitiveValue::Null => false,
PrimitiveValue::Undefined => false,
PrimitiveValue::Boolean(b) => *b,
PrimitiveValue::Number(n) => {
let v = n.value();
v != 0.0 && !v.is_nan()
}
PrimitiveValue::String(s) => !s.is_empty(),
}
}
fn evaluate_binary_op(
operator: BinaryOperator,
lhs: &PrimitiveValue,
rhs: &PrimitiveValue,
) -> Option<PrimitiveValue> {
match operator {
BinaryOperator::Add => match (lhs, rhs) {
(PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
Some(PrimitiveValue::Number(FloatValue::new(l.value() + r.value())))
}
(PrimitiveValue::String(l), PrimitiveValue::String(r)) => {
let mut s = l.clone();
s.push_str(r);
Some(PrimitiveValue::String(s))
}
_ => None,
},
BinaryOperator::Subtract => match (lhs, rhs) {
(PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
Some(PrimitiveValue::Number(FloatValue::new(l.value() - r.value())))
}
_ => None,
},
BinaryOperator::Multiply => match (lhs, rhs) {
(PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
Some(PrimitiveValue::Number(FloatValue::new(l.value() * r.value())))
}
_ => None,
},
BinaryOperator::Divide => match (lhs, rhs) {
(PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
Some(PrimitiveValue::Number(FloatValue::new(l.value() / r.value())))
}
_ => None,
},
BinaryOperator::Modulo => match (lhs, rhs) {
(PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
Some(PrimitiveValue::Number(FloatValue::new(l.value() % r.value())))
}
_ => None,
},
BinaryOperator::Exponent => match (lhs, rhs) {
(PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(
PrimitiveValue::Number(FloatValue::new(l.value().powf(r.value()))),
),
_ => None,
},
BinaryOperator::BitwiseOr => match (lhs, rhs) {
(PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
let result = js_to_int32(l.value()) | js_to_int32(r.value());
Some(PrimitiveValue::Number(FloatValue::new(result as f64)))
}
_ => None,
},
BinaryOperator::BitwiseAnd => match (lhs, rhs) {
(PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
let result = js_to_int32(l.value()) & js_to_int32(r.value());
Some(PrimitiveValue::Number(FloatValue::new(result as f64)))
}
_ => None,
},
BinaryOperator::BitwiseXor => match (lhs, rhs) {
(PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
let result = js_to_int32(l.value()) ^ js_to_int32(r.value());
Some(PrimitiveValue::Number(FloatValue::new(result as f64)))
}
_ => None,
},
BinaryOperator::ShiftLeft => match (lhs, rhs) {
(PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
let result = js_to_int32(l.value()) << (js_to_uint32(r.value()) & 0x1f);
Some(PrimitiveValue::Number(FloatValue::new(result as f64)))
}
_ => None,
},
BinaryOperator::ShiftRight => match (lhs, rhs) {
(PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
let result = js_to_int32(l.value()) >> (js_to_uint32(r.value()) & 0x1f);
Some(PrimitiveValue::Number(FloatValue::new(result as f64)))
}
_ => None,
},
BinaryOperator::UnsignedShiftRight => match (lhs, rhs) {
(PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
let result = js_to_uint32(l.value()) >> (js_to_uint32(r.value()) & 0x1f);
Some(PrimitiveValue::Number(FloatValue::new(result as f64)))
}
_ => None,
},
BinaryOperator::LessThan => match (lhs, rhs) {
(PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
Some(PrimitiveValue::Boolean(l.value() < r.value()))
}
_ => None,
},
BinaryOperator::LessEqual => match (lhs, rhs) {
(PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
Some(PrimitiveValue::Boolean(l.value() <= r.value()))
}
_ => None,
},
BinaryOperator::GreaterThan => match (lhs, rhs) {
(PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
Some(PrimitiveValue::Boolean(l.value() > r.value()))
}
_ => None,
},
BinaryOperator::GreaterEqual => match (lhs, rhs) {
(PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
Some(PrimitiveValue::Boolean(l.value() >= r.value()))
}
_ => None,
},
BinaryOperator::StrictEqual => Some(PrimitiveValue::Boolean(js_strict_equal(lhs, rhs))),
BinaryOperator::StrictNotEqual => {
Some(PrimitiveValue::Boolean(!js_strict_equal(lhs, rhs)))
}
BinaryOperator::Equal => Some(PrimitiveValue::Boolean(js_abstract_equal(lhs, rhs))),
BinaryOperator::NotEqual => Some(PrimitiveValue::Boolean(!js_abstract_equal(lhs, rhs))),
BinaryOperator::In | BinaryOperator::InstanceOf => None,
}
}
fn js_strict_equal(lhs: &PrimitiveValue, rhs: &PrimitiveValue) -> bool {
match (lhs, rhs) {
(PrimitiveValue::Null, PrimitiveValue::Null) => true,
(PrimitiveValue::Undefined, PrimitiveValue::Undefined) => true,
(PrimitiveValue::Boolean(a), PrimitiveValue::Boolean(b)) => a == b,
(PrimitiveValue::Number(a), PrimitiveValue::Number(b)) => {
let av = a.value();
let bv = b.value();
if av.is_nan() || bv.is_nan() {
return false;
}
av == bv
}
(PrimitiveValue::String(a), PrimitiveValue::String(b)) => a == b,
_ => false,
}
}
fn js_to_number(s: &str) -> f64 {
let trimmed = s.trim();
if trimmed.is_empty() {
return 0.0;
}
if trimmed == "Infinity" || trimmed == "+Infinity" {
return f64::INFINITY;
}
if trimmed == "-Infinity" {
return f64::NEG_INFINITY;
}
if trimmed.starts_with("0x") || trimmed.starts_with("0X") {
return match u64::from_str_radix(&trimmed[2..], 16) {
Ok(v) => v as f64,
Err(_) => f64::NAN,
};
}
if trimmed.starts_with("0o") || trimmed.starts_with("0O") {
return match u64::from_str_radix(&trimmed[2..], 8) {
Ok(v) => v as f64,
Err(_) => f64::NAN,
};
}
if trimmed.starts_with("0b") || trimmed.starts_with("0B") {
return match u64::from_str_radix(&trimmed[2..], 2) {
Ok(v) => v as f64,
Err(_) => f64::NAN,
};
}
trimmed.parse::<f64>().unwrap_or(f64::NAN)
}
fn js_abstract_equal(lhs: &PrimitiveValue, rhs: &PrimitiveValue) -> bool {
match (lhs, rhs) {
(PrimitiveValue::Null, PrimitiveValue::Null) => true,
(PrimitiveValue::Undefined, PrimitiveValue::Undefined) => true,
(PrimitiveValue::Null, PrimitiveValue::Undefined)
| (PrimitiveValue::Undefined, PrimitiveValue::Null) => true,
(PrimitiveValue::Boolean(a), PrimitiveValue::Boolean(b)) => a == b,
(PrimitiveValue::Number(a), PrimitiveValue::Number(b)) => {
let av = a.value();
let bv = b.value();
if av.is_nan() || bv.is_nan() {
return false;
}
av == bv
}
(PrimitiveValue::String(a), PrimitiveValue::String(b)) => a == b,
(PrimitiveValue::Number(n), PrimitiveValue::String(s))
| (PrimitiveValue::String(s), PrimitiveValue::Number(n)) => {
let sv = js_to_number(s);
let nv = n.value();
if nv.is_nan() || sv.is_nan() {
false
} else {
nv == sv
}
}
(PrimitiveValue::Boolean(b), other) => {
let num = if *b { 1.0 } else { 0.0 };
js_abstract_equal(&PrimitiveValue::Number(FloatValue::new(num)), other)
}
(other, PrimitiveValue::Boolean(b)) => {
let num = if *b { 1.0 } else { 0.0 };
js_abstract_equal(other, &PrimitiveValue::Number(FloatValue::new(num)))
}
_ => false,
}
}
fn js_to_int32(n: f64) -> i32 {
if n.is_nan() || n.is_infinite() || n == 0.0 {
return 0;
}
let int64 = (n.trunc() as i64) & 0xFFFFFFFF;
if int64 >= 0x80000000 {
(int64 as u32) as i32
} else {
int64 as i32
}
}
fn js_to_uint32(n: f64) -> u32 {
js_to_int32(n) as u32
}