From 856cab5000221b431ae28a62d54b966f07820c95 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Sat, 12 Oct 2024 09:29:46 +0000 Subject: [PATCH] refactor(ecmascript): move ToInt32 from `oxc_syntax` to `oxc_ecmascript` (#6471) --- crates/oxc_ecmascript/src/lib.rs | 3 +- crates/oxc_ecmascript/src/to_int_32.rs | 83 ++++++++++++++++++ crates/oxc_isolated_declarations/src/enum.rs | 22 ++--- .../src/ast_passes/peephole_fold_constants.rs | 10 ++- .../string_methods.rs | 8 +- .../peephole_substitute_alternate_syntax.rs | 6 +- crates/oxc_syntax/CHANGELOG.md | 2 +- crates/oxc_syntax/src/number.rs | 84 ------------------- crates/oxc_transformer/CHANGELOG.md | 2 +- crates/oxc_transformer/src/typescript/enum.rs | 22 ++--- 10 files changed, 122 insertions(+), 120 deletions(-) create mode 100644 crates/oxc_ecmascript/src/to_int_32.rs diff --git a/crates/oxc_ecmascript/src/lib.rs b/crates/oxc_ecmascript/src/lib.rs index 54dfca81ec385..fb6d01599e00e 100644 --- a/crates/oxc_ecmascript/src/lib.rs +++ b/crates/oxc_ecmascript/src/lib.rs @@ -4,8 +4,9 @@ mod bound_names; mod is_simple_parameter_list; mod private_bound_identifiers; mod prop_name; +mod to_int_32; pub use self::{ bound_names::BoundNames, is_simple_parameter_list::IsSimpleParameterList, - private_bound_identifiers::PrivateBoundIdentifiers, prop_name::PropName, + private_bound_identifiers::PrivateBoundIdentifiers, prop_name::PropName, to_int_32::ToInt32, }; diff --git a/crates/oxc_ecmascript/src/to_int_32.rs b/crates/oxc_ecmascript/src/to_int_32.rs new file mode 100644 index 0000000000000..d0a69a48cf16d --- /dev/null +++ b/crates/oxc_ecmascript/src/to_int_32.rs @@ -0,0 +1,83 @@ +/// Converts a 64-bit floating point number to an `i32` according to the [`ToInt32`][ToInt32] algorithm. +/// +/// [ToInt32]: https://tc39.es/ecma262/#sec-toint32 +/// +/// This is copied from [Boa](https://github.com/boa-dev/boa/blob/61567687cf4bfeca6bd548c3e72b6965e74b2461/core/engine/src/builtins/number/conversions.rs) +pub trait ToInt32 { + fn to_int_32(&self) -> i32; +} + +impl ToInt32 for f64 { + #[allow(clippy::float_cmp, clippy::cast_possible_truncation, clippy::cast_possible_wrap)] + fn to_int_32(&self) -> i32 { + const SIGN_MASK: u64 = 0x8000_0000_0000_0000; + const EXPONENT_MASK: u64 = 0x7FF0_0000_0000_0000; + const SIGNIFICAND_MASK: u64 = 0x000F_FFFF_FFFF_FFFF; + const HIDDEN_BIT: u64 = 0x0010_0000_0000_0000; + const PHYSICAL_SIGNIFICAND_SIZE: i32 = 52; // Excludes the hidden bit. + const SIGNIFICAND_SIZE: i32 = 53; + + const EXPONENT_BIAS: i32 = 0x3FF + PHYSICAL_SIGNIFICAND_SIZE; + const DENORMAL_EXPONENT: i32 = -EXPONENT_BIAS + 1; + + fn is_denormal(number: f64) -> bool { + (number.to_bits() & EXPONENT_MASK) == 0 + } + + fn exponent(number: f64) -> i32 { + if is_denormal(number) { + return DENORMAL_EXPONENT; + } + + let d64 = number.to_bits(); + let biased_e = ((d64 & EXPONENT_MASK) >> PHYSICAL_SIGNIFICAND_SIZE) as i32; + + biased_e - EXPONENT_BIAS + } + + fn significand(number: f64) -> u64 { + let d64 = number.to_bits(); + let significand = d64 & SIGNIFICAND_MASK; + + if is_denormal(number) { + significand + } else { + significand + HIDDEN_BIT + } + } + + fn sign(number: f64) -> i64 { + if (number.to_bits() & SIGN_MASK) == 0 { + 1 + } else { + -1 + } + } + + let number = *self; + + if number.is_finite() && number <= f64::from(i32::MAX) && number >= f64::from(i32::MIN) { + let i = number as i32; + if f64::from(i) == number { + return i; + } + } + + let exponent = exponent(number); + let bits = if exponent < 0 { + if exponent <= -SIGNIFICAND_SIZE { + return 0; + } + + significand(number) >> -exponent + } else { + if exponent > 31 { + return 0; + } + + (significand(number) << exponent) & 0xFFFF_FFFF + }; + + (sign(number) * (bits as i64)) as i32 + } +} diff --git a/crates/oxc_isolated_declarations/src/enum.rs b/crates/oxc_isolated_declarations/src/enum.rs index 0e9150b12d02b..8ff44ed085319 100644 --- a/crates/oxc_isolated_declarations/src/enum.rs +++ b/crates/oxc_isolated_declarations/src/enum.rs @@ -1,11 +1,13 @@ +use rustc_hash::FxHashMap; + #[allow(clippy::wildcard_imports)] use oxc_ast::ast::*; +use oxc_ecmascript::ToInt32; use oxc_span::{Atom, GetSpan, SPAN}; use oxc_syntax::{ - number::{NumberBase, ToJsInt32, ToJsString}, + number::{NumberBase, ToJsString}, operator::{BinaryOperator, UnaryOperator}, }; -use rustc_hash::FxHashMap; use crate::{diagnostics::enum_member_initializers, IsolatedDeclarations}; @@ -223,22 +225,22 @@ impl<'a> IsolatedDeclarations<'a> { match expr.operator { BinaryOperator::ShiftRight => Some(ConstantValue::Number(f64::from( - left.to_js_int_32().wrapping_shr(right.to_js_int_32() as u32), + left.to_int_32().wrapping_shr(right.to_int_32() as u32), ))), BinaryOperator::ShiftRightZeroFill => Some(ConstantValue::Number(f64::from( - (left.to_js_int_32() as u32).wrapping_shr(right.to_js_int_32() as u32), + (left.to_int_32() as u32).wrapping_shr(right.to_int_32() as u32), ))), BinaryOperator::ShiftLeft => Some(ConstantValue::Number(f64::from( - left.to_js_int_32().wrapping_shl(right.to_js_int_32() as u32), + left.to_int_32().wrapping_shl(right.to_int_32() as u32), ))), BinaryOperator::BitwiseXOR => { - Some(ConstantValue::Number(f64::from(left.to_js_int_32() ^ right.to_js_int_32()))) + Some(ConstantValue::Number(f64::from(left.to_int_32() ^ right.to_int_32()))) } BinaryOperator::BitwiseOR => { - Some(ConstantValue::Number(f64::from(left.to_js_int_32() | right.to_js_int_32()))) + Some(ConstantValue::Number(f64::from(left.to_int_32() | right.to_int_32()))) } BinaryOperator::BitwiseAnd => { - Some(ConstantValue::Number(f64::from(left.to_js_int_32() & right.to_js_int_32()))) + Some(ConstantValue::Number(f64::from(left.to_int_32() & right.to_int_32()))) } BinaryOperator::Multiplication => Some(ConstantValue::Number(left * right)), BinaryOperator::Division => Some(ConstantValue::Number(left / right)), @@ -276,9 +278,7 @@ impl<'a> IsolatedDeclarations<'a> { match expr.operator { UnaryOperator::UnaryPlus => Some(ConstantValue::Number(value)), UnaryOperator::UnaryNegation => Some(ConstantValue::Number(-value)), - UnaryOperator::BitwiseNot => { - Some(ConstantValue::Number(f64::from(!value.to_js_int_32()))) - } + UnaryOperator::BitwiseNot => Some(ConstantValue::Number(f64::from(!value.to_int_32()))), _ => None, } } diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index b1f2010fff66e..c7f5bfa0d42fb 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -3,10 +3,12 @@ use std::ops::Neg; use num_bigint::BigInt; use num_traits::Zero; + use oxc_ast::ast::*; +use oxc_ecmascript::ToInt32; use oxc_span::{GetSpan, Span, SPAN}; use oxc_syntax::{ - number::{NumberBase, ToJsInt32}, + number::NumberBase, operator::{BinaryOperator, LogicalOperator, UnaryOperator}, }; use oxc_traverse::{Ancestor, Traverse, TraverseCtx}; @@ -195,7 +197,7 @@ impl<'a> PeepholeFoldConstants { }) } Expression::NumericLiteral(n) => is_valid(n.value).then(|| { - let value = !n.value.to_js_int_32(); + let value = !n.value.to_int_32(); ctx.ast.expression_numeric_literal( SPAN, value.into(), @@ -231,7 +233,7 @@ impl<'a> PeepholeFoldConstants { // `-~1` -> `2` if let Expression::NumericLiteral(n) = &mut un.argument { is_valid(n.value).then(|| { - let value = !n.value.to_js_int_32().wrapping_neg(); + let value = !n.value.to_int_32().wrapping_neg(); ctx.ast.expression_numeric_literal( SPAN, value.into(), @@ -933,7 +935,7 @@ impl<'a> PeepholeFoldConstants { #[allow(clippy::cast_sign_loss)] let right_val_int = right_val as u32; - let bits = left_val.to_js_int_32(); + let bits = left_val.to_int_32(); let result_val: f64 = match op { BinaryOperator::ShiftLeft => f64::from(bits.wrapping_shl(right_val_int)), diff --git a/crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods/string_methods.rs b/crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods/string_methods.rs index 48bb421928ee9..e72230e333bfd 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods/string_methods.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods/string_methods.rs @@ -1,4 +1,4 @@ -use oxc_syntax::number::ToJsInt32; +use oxc_ecmascript::ToInt32; pub(super) struct StringUtils; @@ -9,7 +9,7 @@ impl StringUtils { search_value: Option<&str>, from_index: Option, ) -> isize { - let from_index = from_index.map_or(0, |x| x.to_js_int_32().max(0)) as usize; + let from_index = from_index.map_or(0, |x| x.to_int_32().max(0)) as usize; let Some(search_value) = search_value else { return -1; }; @@ -25,8 +25,8 @@ impl StringUtils { ) -> isize { let Some(search_value) = search_value else { return -1 }; - let from_index = from_index - .map_or(usize::MAX, |x| x.to_js_int_32().max(0) as usize + search_value.len()); + let from_index = + from_index.map_or(usize::MAX, |x| x.to_int_32().max(0) as usize + search_value.len()); string .chars() diff --git a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs index 68a0e2e4bf94e..b954baf98071c 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs @@ -1,8 +1,8 @@ use oxc_allocator::Vec; use oxc_ast::{ast::*, NONE}; +use oxc_ecmascript::ToInt32; use oxc_semantic::IsGlobalReference; use oxc_span::{GetSpan, SPAN}; -use oxc_syntax::number::ToJsInt32; use oxc_syntax::{ number::NumberBase, operator::{BinaryOperator, UnaryOperator}, @@ -303,7 +303,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax { let target = expr.left.as_simple_assignment_target_mut()?; if matches!(expr.operator, AssignmentOperator::Subtraction) { match &expr.right { - Expression::NumericLiteral(num) if num.value.to_js_int_32() == 1 => { + Expression::NumericLiteral(num) if num.value.to_int_32() == 1 => { // The `_` will not be placed to the target code. let target = std::mem::replace( target, @@ -315,7 +315,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax { if matches!(un.operator, UnaryOperator::UnaryNegation) => { if let Expression::NumericLiteral(num) = &un.argument { - (num.value.to_js_int_32() == 1).then(|| { + (num.value.to_int_32() == 1).then(|| { // The `_` will not be placed to the target code. let target = std::mem::replace( target, diff --git a/crates/oxc_syntax/CHANGELOG.md b/crates/oxc_syntax/CHANGELOG.md index 4cf2d5e033fd9..63002f14dfc4d 100644 --- a/crates/oxc_syntax/CHANGELOG.md +++ b/crates/oxc_syntax/CHANGELOG.md @@ -193,7 +193,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Features -- f1ccbd4 syntax: Add `ToJsInt32` trait for f64 (#3132) (Boshen) +- f1ccbd4 syntax: Add `ToInt32` trait for f64 (#3132) (Boshen) - 870d11f syntax: Add `ToJsString` trait for f64 (#3131) (Boshen) - 46c02ae traverse: Add scope flags to `TraverseCtx` (#3229) (overlookmotel) diff --git a/crates/oxc_syntax/src/number.rs b/crates/oxc_syntax/src/number.rs index 1b9e6cd1f1ea7..159deb58e6554 100644 --- a/crates/oxc_syntax/src/number.rs +++ b/crates/oxc_syntax/src/number.rs @@ -48,87 +48,3 @@ impl ToJsString for f64 { buffer.format(*self).to_string() } } - -/// Converts a 64-bit floating point number to an `i32` according to the [`ToInt32`][ToInt32] algorithm. -/// -/// [ToInt32]: https://tc39.es/ecma262/#sec-toint32 -/// -/// This is copied from [Boa](https://github.com/boa-dev/boa/blob/61567687cf4bfeca6bd548c3e72b6965e74b2461/core/engine/src/builtins/number/conversions.rs) -pub trait ToJsInt32 { - fn to_js_int_32(&self) -> i32; -} - -impl ToJsInt32 for f64 { - #[allow(clippy::float_cmp, clippy::cast_possible_truncation, clippy::cast_possible_wrap)] - fn to_js_int_32(&self) -> i32 { - const SIGN_MASK: u64 = 0x8000_0000_0000_0000; - const EXPONENT_MASK: u64 = 0x7FF0_0000_0000_0000; - const SIGNIFICAND_MASK: u64 = 0x000F_FFFF_FFFF_FFFF; - const HIDDEN_BIT: u64 = 0x0010_0000_0000_0000; - const PHYSICAL_SIGNIFICAND_SIZE: i32 = 52; // Excludes the hidden bit. - const SIGNIFICAND_SIZE: i32 = 53; - - const EXPONENT_BIAS: i32 = 0x3FF + PHYSICAL_SIGNIFICAND_SIZE; - const DENORMAL_EXPONENT: i32 = -EXPONENT_BIAS + 1; - - fn is_denormal(number: f64) -> bool { - (number.to_bits() & EXPONENT_MASK) == 0 - } - - fn exponent(number: f64) -> i32 { - if is_denormal(number) { - return DENORMAL_EXPONENT; - } - - let d64 = number.to_bits(); - let biased_e = ((d64 & EXPONENT_MASK) >> PHYSICAL_SIGNIFICAND_SIZE) as i32; - - biased_e - EXPONENT_BIAS - } - - fn significand(number: f64) -> u64 { - let d64 = number.to_bits(); - let significand = d64 & SIGNIFICAND_MASK; - - if is_denormal(number) { - significand - } else { - significand + HIDDEN_BIT - } - } - - fn sign(number: f64) -> i64 { - if (number.to_bits() & SIGN_MASK) == 0 { - 1 - } else { - -1 - } - } - - let number = *self; - - if number.is_finite() && number <= f64::from(i32::MAX) && number >= f64::from(i32::MIN) { - let i = number as i32; - if f64::from(i) == number { - return i; - } - } - - let exponent = exponent(number); - let bits = if exponent < 0 { - if exponent <= -SIGNIFICAND_SIZE { - return 0; - } - - significand(number) >> -exponent - } else { - if exponent > 31 { - return 0; - } - - (significand(number) << exponent) & 0xFFFF_FFFF - }; - - (sign(number) * (bits as i64)) as i32 - } -} diff --git a/crates/oxc_transformer/CHANGELOG.md b/crates/oxc_transformer/CHANGELOG.md index 388b7150fcd4e..b685e6a047165 100644 --- a/crates/oxc_transformer/CHANGELOG.md +++ b/crates/oxc_transformer/CHANGELOG.md @@ -753,7 +753,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Features -- f1ccbd4 syntax: Add `ToJsInt32` trait for f64 (#3132) (Boshen) +- f1ccbd4 syntax: Add `ToInt32` trait for f64 (#3132) (Boshen) - 870d11f syntax: Add `ToJsString` trait for f64 (#3131) (Boshen) - 34dd53c transformer: Report ambient module cannot be nested error (#3253) (Dunqing) - 1b29e63 transformer: Do not elide jsx imports if a jsx element appears somewhere (#3237) (Dunqing) diff --git a/crates/oxc_transformer/src/typescript/enum.rs b/crates/oxc_transformer/src/typescript/enum.rs index 7628bd0c6c4d4..719b3a7baf0af 100644 --- a/crates/oxc_transformer/src/typescript/enum.rs +++ b/crates/oxc_transformer/src/typescript/enum.rs @@ -1,15 +1,17 @@ +use rustc_hash::FxHashMap; + use oxc_allocator::Vec; use oxc_ast::{ast::*, visit::walk_mut, VisitMut, NONE}; +use oxc_ecmascript::ToInt32; use oxc_span::{Atom, Span, SPAN}; use oxc_syntax::{ node::NodeId, - number::{NumberBase, ToJsInt32, ToJsString}, + number::{NumberBase, ToJsString}, operator::{AssignmentOperator, BinaryOperator, LogicalOperator, UnaryOperator}, reference::ReferenceFlags, symbol::SymbolFlags, }; use oxc_traverse::{Traverse, TraverseCtx}; -use rustc_hash::FxHashMap; pub struct TypeScriptEnum<'a> { enums: FxHashMap, FxHashMap, ConstantValue>>, @@ -475,22 +477,22 @@ impl<'a> TypeScriptEnum<'a> { match expr.operator { BinaryOperator::ShiftRight => Some(ConstantValue::Number(f64::from( - left.to_js_int_32().wrapping_shr(right.to_js_int_32() as u32), + left.to_int_32().wrapping_shr(right.to_int_32() as u32), ))), BinaryOperator::ShiftRightZeroFill => Some(ConstantValue::Number(f64::from( - (left.to_js_int_32() as u32).wrapping_shr(right.to_js_int_32() as u32), + (left.to_int_32() as u32).wrapping_shr(right.to_int_32() as u32), ))), BinaryOperator::ShiftLeft => Some(ConstantValue::Number(f64::from( - left.to_js_int_32().wrapping_shl(right.to_js_int_32() as u32), + left.to_int_32().wrapping_shl(right.to_int_32() as u32), ))), BinaryOperator::BitwiseXOR => { - Some(ConstantValue::Number(f64::from(left.to_js_int_32() ^ right.to_js_int_32()))) + Some(ConstantValue::Number(f64::from(left.to_int_32() ^ right.to_int_32()))) } BinaryOperator::BitwiseOR => { - Some(ConstantValue::Number(f64::from(left.to_js_int_32() | right.to_js_int_32()))) + Some(ConstantValue::Number(f64::from(left.to_int_32() | right.to_int_32()))) } BinaryOperator::BitwiseAnd => { - Some(ConstantValue::Number(f64::from(left.to_js_int_32() & right.to_js_int_32()))) + Some(ConstantValue::Number(f64::from(left.to_int_32() & right.to_int_32()))) } BinaryOperator::Multiplication => Some(ConstantValue::Number(left * right)), BinaryOperator::Division => Some(ConstantValue::Number(left / right)), @@ -527,9 +529,7 @@ impl<'a> TypeScriptEnum<'a> { match expr.operator { UnaryOperator::UnaryPlus => Some(ConstantValue::Number(value)), UnaryOperator::UnaryNegation => Some(ConstantValue::Number(-value)), - UnaryOperator::BitwiseNot => { - Some(ConstantValue::Number(f64::from(!value.to_js_int_32()))) - } + UnaryOperator::BitwiseNot => Some(ConstantValue::Number(f64::from(!value.to_int_32()))), _ => None, } }