Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion crates/oxc_ecmascript/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
83 changes: 83 additions & 0 deletions crates/oxc_ecmascript/src/to_int_32.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
22 changes: 11 additions & 11 deletions crates/oxc_isolated_declarations/src/enum.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -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)),
Expand Down Expand Up @@ -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,
}
}
Expand Down
10 changes: 6 additions & 4 deletions crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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)),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use oxc_syntax::number::ToJsInt32;
use oxc_ecmascript::ToInt32;

pub(super) struct StringUtils;

Expand All @@ -9,7 +9,7 @@ impl StringUtils {
search_value: Option<&str>,
from_index: Option<f64>,
) -> 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;
};
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_syntax/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
84 changes: 0 additions & 84 deletions crates/oxc_syntax/src/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
2 changes: 1 addition & 1 deletion crates/oxc_transformer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading