feat(syntax): add ToJsInt32 trait for f64 (#3132)

This commit is contained in:
Boshen 2024-04-29 21:13:04 +08:00 committed by GitHub
parent 870d11f1bb
commit f1ccbd4856
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 93 additions and 95 deletions

View file

@ -40,3 +40,87 @@ 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
}
}

View file

@ -1,83 +0,0 @@
/// This file copied from [Boa](https://github.com/boa-dev/boa/blob/61567687cf4bfeca6bd548c3e72b6965e74b2461/core/engine/src/builtins/number/conversions.rs)
/// Converts a 64-bit floating point number to an `i32` according to the [`ToInt32`][ToInt32] algorithm.
///
/// [ToInt32]: https://tc39.es/ecma262/#sec-toint32
#[allow(clippy::float_cmp, clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
pub(crate) fn f64_to_int32(number: f64) -> 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
}
}
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
}
/// Converts a 64-bit floating point number to an `u32` according to the [`ToUint32`][ToUint32] algorithm.
///
/// [ToUint32]: https://tc39.es/ecma262/#sec-touint32
#[allow(clippy::cast_sign_loss)]
pub(crate) fn f64_to_uint32(number: f64) -> u32 {
f64_to_int32(number) as u32
}

View file

@ -4,15 +4,13 @@ use oxc_allocator::{Box, Vec};
use oxc_ast::{ast::*, visit::walk_mut, VisitMut};
use oxc_span::{Atom, SPAN};
use oxc_syntax::{
number::{NumberBase, ToJsString},
number::{NumberBase, ToJsInt32, ToJsString},
operator::{AssignmentOperator, BinaryOperator, LogicalOperator, UnaryOperator},
};
use rustc_hash::FxHashMap;
use crate::context::Ctx;
use super::conversions::{f64_to_int32, f64_to_uint32};
pub struct TypeScriptEnum<'a> {
ctx: Ctx<'a>,
enums: FxHashMap<Atom<'a>, FxHashMap<Atom<'a>, ConstantValue>>,
@ -376,7 +374,7 @@ impl<'a> TypeScriptEnum<'a> {
}
}
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss, clippy::cast_sign_loss)]
fn eval_binary_expression(
&self,
expr: &BinaryExpression<'a>,
@ -414,22 +412,22 @@ impl<'a> TypeScriptEnum<'a> {
match expr.operator {
BinaryOperator::ShiftRight => Some(ConstantValue::Number(f64::from(
f64_to_int32(left).wrapping_shr(f64_to_uint32(right)),
left.to_js_int_32().wrapping_shr(right.to_js_int_32() as u32),
))),
BinaryOperator::ShiftRightZeroFill => Some(ConstantValue::Number(f64::from(
f64_to_uint32(left).wrapping_shr(f64_to_uint32(right)),
(left.to_js_int_32() as u32).wrapping_shr(right.to_js_int_32() as u32),
))),
BinaryOperator::ShiftLeft => Some(ConstantValue::Number(f64::from(
f64_to_int32(left).wrapping_shl(f64_to_uint32(right)),
left.to_js_int_32().wrapping_shl(right.to_js_int_32() as u32),
))),
BinaryOperator::BitwiseXOR => {
Some(ConstantValue::Number(f64::from(f64_to_int32(left) ^ f64_to_int32(right))))
Some(ConstantValue::Number(f64::from(left.to_js_int_32() ^ right.to_js_int_32())))
}
BinaryOperator::BitwiseOR => {
Some(ConstantValue::Number(f64::from(f64_to_int32(left) | f64_to_int32(right))))
Some(ConstantValue::Number(f64::from(left.to_js_int_32() | right.to_js_int_32())))
}
BinaryOperator::BitwiseAnd => {
Some(ConstantValue::Number(f64::from(f64_to_int32(left) & f64_to_int32(right))))
Some(ConstantValue::Number(f64::from(left.to_js_int_32() & right.to_js_int_32())))
}
BinaryOperator::Multiplication => Some(ConstantValue::Number(left * right)),
BinaryOperator::Division => Some(ConstantValue::Number(left / right)),
@ -467,7 +465,7 @@ impl<'a> TypeScriptEnum<'a> {
UnaryOperator::UnaryPlus => Some(ConstantValue::Number(value)),
UnaryOperator::UnaryNegation => Some(ConstantValue::Number(-value)),
UnaryOperator::BitwiseNot => {
Some(ConstantValue::Number(f64::from(!f64_to_int32(value))))
Some(ConstantValue::Number(f64::from(!value.to_js_int_32())))
}
_ => None,
}

View file

@ -1,6 +1,5 @@
mod annotations;
mod collector;
mod conversions;
mod diagnostics;
mod r#enum;
mod module;