Skip to content

Commit

Permalink
refactor(minifier): add a folder struct for constant folding (#4057)
Browse files Browse the repository at this point in the history
  • Loading branch information
Boshen committed Jul 5, 2024
1 parent aaac2d8 commit edb557c
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 169 deletions.
17 changes: 9 additions & 8 deletions crates/oxc_minifier/src/compressor/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#![allow(clippy::unused_self)]

mod ast_util;
mod fold;
pub(crate) mod ast_util;
mod options;
mod util;

Expand All @@ -16,24 +15,26 @@ use oxc_syntax::{
precedence::GetPrecedence,
};

use crate::folder::Folder;
// use crate::ast_passes::RemoveParens;

pub use self::options::CompressOptions;

pub struct Compressor<'a> {
ast: AstBuilder<'a>,
options: CompressOptions,

// prepass: RemoveParens<'a>,
folder: Folder<'a>,
}

const SPAN: Span = Span::new(0, 0);

impl<'a> Compressor<'a> {
pub fn new(allocator: &'a Allocator, options: CompressOptions) -> Self {
Self {
ast: AstBuilder::new(allocator),
options, /* prepass: RemoveParens::new(allocator) */
}
let ast = AstBuilder::new(allocator);
let folder = Folder::new(ast).with_evaluate(options.evaluate);
Self { ast, options /* prepass: RemoveParens::new(allocator) */, folder }
}

pub fn build(mut self, program: &mut Program<'a>) {
Expand Down Expand Up @@ -328,7 +329,7 @@ impl<'a> VisitMut<'a> for Compressor<'a> {
fn visit_statement(&mut self, stmt: &mut Statement<'a>) {
self.compress_block(stmt);
self.compress_while(stmt);
self.fold_condition(stmt);
self.folder.fold_condition(stmt);
walk_mut::walk_statement(self, stmt);
}

Expand All @@ -348,7 +349,7 @@ impl<'a> VisitMut<'a> for Compressor<'a> {
fn visit_expression(&mut self, expr: &mut Expression<'a>) {
walk_mut::walk_expression(self, expr);
self.compress_console(expr);
self.fold_expression(expr);
self.folder.fold_expression(expr);
if !self.compress_undefined(expr) {
self.compress_boolean(expr);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,183 +2,44 @@
//!
//! <https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/PeepholeFoldConstants.java>

mod tri;
mod ty;
mod util;

use std::{cmp::Ordering, mem};

use num_bigint::BigInt;
#[allow(clippy::wildcard_imports)]
use oxc_ast::ast::*;
use oxc_ast::{ast::*, AstBuilder};
use oxc_span::{Atom, GetSpan, Span};
use oxc_syntax::{
number::NumberBase,
operator::{BinaryOperator, LogicalOperator, UnaryOperator},
};

use super::{
ast_util::{
get_boolean_value, get_number_value, get_side_free_bigint_value,
get_side_free_number_value, get_side_free_string_value, get_string_value, is_exact_int64,
IsLiteralValue, MayHaveSideEffects, NumberValue,
},
Compressor,
use crate::compressor::ast_util::{
get_boolean_value, get_number_value, get_side_free_bigint_value, get_side_free_number_value,
get_side_free_string_value, get_string_value, is_exact_int64, IsLiteralValue,
MayHaveSideEffects, NumberValue,
};

/// Tri state
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Tri {
True,
False,
Unknown,
}

impl Tri {
pub fn not(self) -> Self {
match self {
Self::True => Self::False,
Self::False => Self::True,
Self::Unknown => Self::Unknown,
}
}

pub fn xor(self, other: Self) -> Self {
self.for_int(-self.value() * other.value())
}

pub fn for_int(self, int: i8) -> Self {
match int {
-1 => Self::False,
1 => Self::True,
_ => Self::Unknown,
}
}

pub fn for_boolean(boolean: bool) -> Self {
if boolean {
Self::True
} else {
Self::False
}
}
use tri::Tri;
use ty::Ty;
use util::bigint_less_than_number;

pub fn value(self) -> i8 {
match self {
Self::True => 1,
Self::False => -1,
Self::Unknown => 0,
}
}
pub struct Folder<'a> {
ast: AstBuilder<'a>,
evaluate: bool,
}

/// ported from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L1250)
#[allow(clippy::cast_possible_truncation)]
fn bigint_less_than_number(
bigint_value: &BigInt,
number_value: &NumberValue,
invert: Tri,
will_negative: bool,
) -> Tri {
// if invert is false, then the number is on the right in tryAbstractRelationalComparison
// if it's true, then the number is on the left
match number_value {
NumberValue::NaN => Tri::for_boolean(will_negative),
NumberValue::PositiveInfinity => Tri::True.xor(invert),
NumberValue::NegativeInfinity => Tri::False.xor(invert),
NumberValue::Number(num) => {
if let Some(Ordering::Equal | Ordering::Greater) =
num.abs().partial_cmp(&2_f64.powi(53))
{
Tri::Unknown
} else {
let number_as_bigint = BigInt::from(*num as i64);

match bigint_value.cmp(&number_as_bigint) {
Ordering::Less => Tri::True.xor(invert),
Ordering::Greater => Tri::False.xor(invert),
Ordering::Equal => {
if is_exact_int64(*num) {
Tri::False
} else {
Tri::for_boolean(num.is_sign_positive()).xor(invert)
}
}
}
}
}
impl<'a> Folder<'a> {
pub fn new(ast: AstBuilder<'a>) -> Self {
Self { ast, evaluate: false }
}
}

/// JavaScript Language Type
///
/// <https://tc39.es/ecma262/#sec-ecmascript-language-types>
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Ty {
BigInt,
Boolean,
Null,
Number,
Object,
Str,
Void,
Undetermined,
}

impl<'a> From<&Expression<'a>> for Ty {
fn from(expr: &Expression<'a>) -> Self {
// TODO: complete this
match expr {
Expression::BigIntLiteral(_) => Self::BigInt,
Expression::BooleanLiteral(_) => Self::Boolean,
Expression::NullLiteral(_) => Self::Null,
Expression::NumericLiteral(_) => Self::Number,
Expression::StringLiteral(_) => Self::Str,
Expression::ObjectExpression(_)
| Expression::ArrayExpression(_)
| Expression::RegExpLiteral(_)
| Expression::FunctionExpression(_) => Self::Object,
Expression::Identifier(ident) => match ident.name.as_str() {
"undefined" => Self::Void,
"NaN" | "Infinity" => Self::Number,
_ => Self::Undetermined,
},
Expression::UnaryExpression(unary_expr) => match unary_expr.operator {
UnaryOperator::Void => Self::Void,
UnaryOperator::UnaryNegation => {
let argument_ty = Self::from(&unary_expr.argument);
if argument_ty == Self::BigInt {
return Self::BigInt;
}
Self::Number
}
UnaryOperator::UnaryPlus => Self::Number,
UnaryOperator::LogicalNot => Self::Boolean,
UnaryOperator::Typeof => Self::Str,
_ => Self::Undetermined,
},
Expression::BinaryExpression(binary_expr) => match binary_expr.operator {
BinaryOperator::Addition => {
let left_ty = Self::from(&binary_expr.left);
let right_ty = Self::from(&binary_expr.right);

if left_ty == Self::Str || right_ty == Self::Str {
return Self::Str;
}

// There are some pretty weird cases for object types:
// {} + [] === "0"
// [] + {} === "[object Object]"
if left_ty == Self::Object || right_ty == Self::Object {
return Self::Undetermined;
}

Self::Undetermined
}
_ => Self::Undetermined,
},
_ => Self::Undetermined,
}
pub fn with_evaluate(mut self, yes: bool) -> Self {
self.evaluate = yes;
self
}
}

impl<'a> Compressor<'a> {
pub(crate) fn fold_expression<'b>(&mut self, expr: &'b mut Expression<'a>) {
let folded_expr = match expr {
Expression::BinaryExpression(binary_expr) => match binary_expr.operator {
Expand Down Expand Up @@ -209,7 +70,7 @@ impl<'a> Compressor<'a> {
// don't match (even though the produced code is valid). Additionally, We'll likely
// want to add `evaluate` checks for all constant folding, not just additions, but
// we're adding this here until a decision is made.
BinaryOperator::Addition if self.options.evaluate => {
BinaryOperator::Addition if self.evaluate => {
self.try_fold_addition(binary_expr.span, &binary_expr.left, &binary_expr.right)
}
_ => None,
Expand Down
45 changes: 45 additions & 0 deletions crates/oxc_minifier/src/folder/tri.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/// Tri state
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Tri {
True,
False,
Unknown,
}

impl Tri {
pub fn not(self) -> Self {
match self {
Self::True => Self::False,
Self::False => Self::True,
Self::Unknown => Self::Unknown,
}
}

pub fn xor(self, other: Self) -> Self {
self.for_int(-self.value() * other.value())
}

pub fn for_int(self, int: i8) -> Self {
match int {
-1 => Self::False,
1 => Self::True,
_ => Self::Unknown,
}
}

pub fn for_boolean(boolean: bool) -> Self {
if boolean {
Self::True
} else {
Self::False
}
}

pub fn value(self) -> i8 {
match self {
Self::True => 1,
Self::False => -1,
Self::Unknown => 0,
}
}
}
74 changes: 74 additions & 0 deletions crates/oxc_minifier/src/folder/ty.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use oxc_ast::ast::*;
use oxc_syntax::operator::{BinaryOperator, UnaryOperator};

/// JavaScript Language Type
///
/// <https://tc39.es/ecma262/#sec-ecmascript-language-types>
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Ty {
BigInt,
Boolean,
Null,
Number,
Object,
Str,
Void,
Undetermined,
}

impl<'a> From<&Expression<'a>> for Ty {
fn from(expr: &Expression<'a>) -> Self {
// TODO: complete this
match expr {
Expression::BigIntLiteral(_) => Self::BigInt,
Expression::BooleanLiteral(_) => Self::Boolean,
Expression::NullLiteral(_) => Self::Null,
Expression::NumericLiteral(_) => Self::Number,
Expression::StringLiteral(_) => Self::Str,
Expression::ObjectExpression(_)
| Expression::ArrayExpression(_)
| Expression::RegExpLiteral(_)
| Expression::FunctionExpression(_) => Self::Object,
Expression::Identifier(ident) => match ident.name.as_str() {
"undefined" => Self::Void,
"NaN" | "Infinity" => Self::Number,
_ => Self::Undetermined,
},
Expression::UnaryExpression(unary_expr) => match unary_expr.operator {
UnaryOperator::Void => Self::Void,
UnaryOperator::UnaryNegation => {
let argument_ty = Self::from(&unary_expr.argument);
if argument_ty == Self::BigInt {
return Self::BigInt;
}
Self::Number
}
UnaryOperator::UnaryPlus => Self::Number,
UnaryOperator::LogicalNot => Self::Boolean,
UnaryOperator::Typeof => Self::Str,
_ => Self::Undetermined,
},
Expression::BinaryExpression(binary_expr) => match binary_expr.operator {
BinaryOperator::Addition => {
let left_ty = Self::from(&binary_expr.left);
let right_ty = Self::from(&binary_expr.right);

if left_ty == Self::Str || right_ty == Self::Str {
return Self::Str;
}

// There are some pretty weird cases for object types:
// {} + [] === "0"
// [] + {} === "[object Object]"
if left_ty == Self::Object || right_ty == Self::Object {
return Self::Undetermined;
}

Self::Undetermined
}
_ => Self::Undetermined,
},
_ => Self::Undetermined,
}
}
}
Loading

0 comments on commit edb557c

Please sign in to comment.