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
9 changes: 6 additions & 3 deletions crates/oxc/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,11 @@ pub trait CompilerInterface {
.build(&program)
.semantic
.into_scoping();
Compressor::new(&allocator, CompressOptions::default())
.dead_code_elimination_with_scoping(scoping, &mut program);
Compressor::new(&allocator).dead_code_elimination_with_scoping(
&mut program,
scoping,
CompressOptions::smallest(),
);
}
}

Expand Down Expand Up @@ -276,7 +279,7 @@ pub trait CompilerInterface {
program: &mut Program<'a>,
options: CompressOptions,
) {
Compressor::new(allocator, options).build(program);
Compressor::new(allocator).build(program, options);
}

fn mangle(&self, program: &mut Program<'_>, options: MangleOptions) -> Scoping {
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_minifier/examples/dce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ fn main() -> std::io::Result<()> {
fn dce(allocator: &Allocator, source_text: &str, source_type: SourceType, nospace: bool) -> String {
let ret = Parser::new(allocator, source_text, source_type).parse();
let mut program = ret.program;
Compressor::new(allocator, CompressOptions::default()).dead_code_elimination(&mut program);
Compressor::new(allocator).dead_code_elimination(&mut program, CompressOptions::smallest());
Codegen::new()
.with_options(CodegenOptions { minify: nospace, ..CodegenOptions::default() })
.build(&program)
Expand Down
38 changes: 22 additions & 16 deletions crates/oxc_minifier/src/compressor.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::rc::Rc;

use oxc_allocator::Allocator;
use oxc_ast::ast::*;
use oxc_semantic::{Scoping, SemanticBuilder};
Expand All @@ -16,37 +14,45 @@ use crate::{

pub struct Compressor<'a> {
allocator: &'a Allocator,
options: Rc<CompressOptions>,
}

impl<'a> Compressor<'a> {
pub fn new(allocator: &'a Allocator, options: CompressOptions) -> Self {
Self { allocator, options: Rc::new(options) }
pub fn new(allocator: &'a Allocator) -> Self {
Self { allocator }
}

pub fn build(self, program: &mut Program<'a>) {
pub fn build(self, program: &mut Program<'a>, options: CompressOptions) {
let scoping = SemanticBuilder::new().build(program).semantic.into_scoping();
self.build_with_scoping(scoping, program);
self.build_with_scoping(program, scoping, options);
}

pub fn build_with_scoping(self, scoping: Scoping, program: &mut Program<'a>) {
let state = MinifierState::new(Rc::clone(&self.options));
pub fn build_with_scoping(
self,
program: &mut Program<'a>,
scoping: Scoping,
options: CompressOptions,
) {
let state = MinifierState::new(options);
let mut ctx = ReusableTraverseCtx::new(state, scoping, self.allocator);
let normalize_options =
NormalizeOptions { convert_while_to_fors: true, convert_const_to_let: true };
Normalize::new(normalize_options).build(program, &mut ctx);
PeepholeOptimizations::new(self.options.target, self.options.keep_names)
.run_in_loop(program, &mut ctx);
LatePeepholeOptimizations::new(self.options.target).build(program, &mut ctx);
PeepholeOptimizations::new().run_in_loop(program, &mut ctx);
LatePeepholeOptimizations::new().build(program, &mut ctx);
}

pub fn dead_code_elimination(self, program: &mut Program<'a>) {
pub fn dead_code_elimination(self, program: &mut Program<'a>, options: CompressOptions) {
let scoping = SemanticBuilder::new().build(program).semantic.into_scoping();
self.dead_code_elimination_with_scoping(scoping, program);
self.dead_code_elimination_with_scoping(program, scoping, options);
}

pub fn dead_code_elimination_with_scoping(self, scoping: Scoping, program: &mut Program<'a>) {
let state = MinifierState::new(Rc::clone(&self.options));
pub fn dead_code_elimination_with_scoping(
self,
program: &mut Program<'a>,
scoping: Scoping,
options: CompressOptions,
) {
let state = MinifierState::new(options);
let mut ctx = ReusableTraverseCtx::new(state, scoping, self.allocator);
let normalize_options =
NormalizeOptions { convert_while_to_fors: false, convert_const_to_let: false };
Expand Down
8 changes: 6 additions & 2 deletions crates/oxc_minifier/src/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use oxc_semantic::{IsGlobalReference, Scoping};
use oxc_span::format_atom;
use oxc_syntax::reference::ReferenceId;

use crate::state::MinifierState;
use crate::{options::CompressOptions, state::MinifierState};

pub type TraverseCtx<'a> = oxc_traverse::TraverseCtx<'a, MinifierState<'a>>;

Expand Down Expand Up @@ -93,10 +93,14 @@ pub fn is_exact_int64(num: f64) -> bool {
}

impl<'a> Ctx<'a, '_> {
fn scoping(&self) -> &Scoping {
pub fn scoping(&self) -> &Scoping {
self.0.scoping()
}

pub fn options(&self) -> &CompressOptions {
&self.0.state.options
}

pub fn is_global_reference(&self, ident: &IdentifierReference<'a>) -> bool {
ident.is_global_reference(self.0.scoping())
}
Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_minifier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ impl Minifier {
}

pub fn build<'a>(self, allocator: &'a Allocator, program: &mut Program<'a>) -> MinifierReturn {
let stats = if let Some(compress) = self.options.compress {
let stats = if let Some(options) = self.options.compress {
let semantic = SemanticBuilder::new().build(program).semantic;
let stats = semantic.stats();
let scoping = semantic.into_scoping();
Compressor::new(allocator, compress).build_with_scoping(scoping, program);
Compressor::new(allocator).build_with_scoping(program, scoping, options);
stats
} else {
Stats::default()
Expand Down
3 changes: 1 addition & 2 deletions crates/oxc_minifier/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,9 @@ pub struct CompressOptions {
pub treeshake: TreeShakeOptions,
}

#[expect(clippy::derivable_impls)]
impl Default for CompressOptions {
fn default() -> Self {
Self { drop_console: false, ..Self::smallest() }
Self::smallest()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ impl<'a> PeepholeOptimizations {
}

// Try using the "??" or "?." operators
if self.target >= ESTarget::ES2020 {
if ctx.options().target >= ESTarget::ES2020 {
if let Expression::BinaryExpression(test_binary) = &mut expr.test {
if let Some(is_negate) = match test_binary.operator {
BinaryOperator::Inequality => Some(true),
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_minifier/src/peephole/minimize_conditions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ impl<'a> PeepholeOptimizations {
expr: &mut AssignmentExpression<'a>,
ctx: &mut Ctx<'a, '_>,
) -> bool {
if self.target < ESTarget::ES2020 {
if ctx.options().target < ESTarget::ES2020 {
return false;
}
if !matches!(expr.operator, AssignmentOperator::Assign) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ impl<'a> PeepholeOptimizations {
expr: &mut LogicalExpression<'a>,
ctx: &mut Ctx<'a, '_>,
) -> Option<Expression<'a>> {
if self.target < ESTarget::ES2020 {
if ctx.options().target < ESTarget::ES2020 {
return None;
}
let Expression::AssignmentExpression(assignment_expr) = &mut expr.right else {
Expand Down
25 changes: 6 additions & 19 deletions crates/oxc_minifier/src/peephole/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,11 @@ use rustc_hash::FxHashSet;
use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_data_structures::stack::NonEmptyStack;
use oxc_syntax::{es_target::ESTarget, scope::ScopeId};
use oxc_syntax::scope::ScopeId;
use oxc_traverse::{ReusableTraverseCtx, Traverse, traverse_mut_with_ctx};

use crate::{
ctx::{Ctx, TraverseCtx},
options::CompressOptionsKeepNames,
state::MinifierState,
};

Expand All @@ -40,9 +39,6 @@ pub struct State {
}

pub struct PeepholeOptimizations {
target: ESTarget,
keep_names: CompressOptionsKeepNames,

/// Walk the ast in a fixed point loop until no changes are made.
/// `prev_function_changed`, `functions_changed` and `current_function` track changes
/// in top level and each function. No minification code are run if the function is not changed
Expand All @@ -56,10 +52,8 @@ pub struct PeepholeOptimizations {
}

impl<'a> PeepholeOptimizations {
pub fn new(target: ESTarget, keep_names: CompressOptionsKeepNames) -> Self {
pub fn new() -> Self {
Self {
target,
keep_names,
iteration: 0,
prev_functions_changed: FxHashSet::default(),
functions_changed: FxHashSet::default(),
Expand Down Expand Up @@ -405,13 +399,11 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {

/// Changes that do not interfere with optimizations that are run inside the fixed-point loop,
/// which can be done as a last AST pass.
pub struct LatePeepholeOptimizations {
target: ESTarget,
}
pub struct LatePeepholeOptimizations;

impl<'a> LatePeepholeOptimizations {
pub fn new(target: ESTarget) -> Self {
Self { target }
pub fn new() -> Self {
Self
}

pub fn build(
Expand Down Expand Up @@ -463,12 +455,7 @@ pub struct DeadCodeElimination {

impl<'a> DeadCodeElimination {
pub fn new() -> Self {
Self {
inner: PeepholeOptimizations::new(
ESTarget::ESNext,
CompressOptionsKeepNames::all_true(),
),
}
Self { inner: PeepholeOptimizations::new() }
}

pub fn build(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ impl<'a> PeepholeOptimizations {
}

// try optional chaining and nullish coalescing
if self.target >= ESTarget::ES2020 {
if ctx.options().target >= ESTarget::ES2020 {
let LogicalExpression {
span: logical_span,
left: logical_left,
Expand Down
10 changes: 5 additions & 5 deletions crates/oxc_minifier/src/peephole/replace_known_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ impl<'a> PeepholeOptimizations {
object: &Expression<'a>,
ctx: &mut Ctx<'a, '_>,
) -> Option<Expression<'a>> {
if self.target < ESTarget::ES2016 {
if ctx.options().target < ESTarget::ES2016 {
return None;
}
if !Self::validate_global_reference(object, "Math", ctx)
Expand Down Expand Up @@ -726,7 +726,7 @@ impl<'a> PeepholeOptimizations {
}
}
Expression::StringLiteral(base_str) => {
if self.target < ESTarget::ES2015
if ctx.state.options.target < ESTarget::ES2015
|| args.is_empty()
|| !args.iter().all(Argument::is_expression)
{
Expand Down Expand Up @@ -917,15 +917,15 @@ impl<'a> PeepholeOptimizations {
"NEGATIVE_INFINITY" => num(span, f64::NEG_INFINITY),
"NaN" => num(span, f64::NAN),
"MAX_SAFE_INTEGER" => {
if self.target < ESTarget::ES2016 {
if ctx.options().target < ESTarget::ES2016 {
num(span, 2.0f64.powi(53) - 1.0)
} else {
// 2**53 - 1
pow_with_expr(span, 2.0, 53.0, BinaryOperator::Subtraction, 1.0)
}
}
"MIN_SAFE_INTEGER" => {
if self.target < ESTarget::ES2016 {
if ctx.options().target < ESTarget::ES2016 {
num(span, -(2.0f64.powi(53) - 1.0))
} else {
// -(2**53 - 1)
Expand All @@ -937,7 +937,7 @@ impl<'a> PeepholeOptimizations {
}
}
"EPSILON" => {
if self.target < ESTarget::ES2016 {
if ctx.options().target < ESTarget::ES2016 {
return None;
}
// 2**-52
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1023,7 +1023,7 @@ impl<'a> PeepholeOptimizations {
state: &mut State,
ctx: &mut Ctx<'a, '_>,
) {
if self.keep_names.function {
if ctx.options().keep_names.function {
return;
}

Expand All @@ -1044,7 +1044,7 @@ impl<'a> PeepholeOptimizations {
state: &mut State,
ctx: &mut Ctx<'a, '_>,
) {
if self.keep_names.class {
if ctx.options().keep_names.class {
return;
}

Expand Down Expand Up @@ -1171,7 +1171,7 @@ impl<'a> LatePeepholeOptimizations {
}

pub fn substitute_catch_clause(&self, catch: &mut CatchClause<'a>, ctx: &mut Ctx<'a, '_>) {
if self.target >= ESTarget::ES2019 {
if ctx.options().target >= ESTarget::ES2019 {
if let Some(param) = &catch.param {
if let BindingPatternKind::BindingIdentifier(ident) = &param.pattern.kind {
if catch.body.body.is_empty()
Expand Down
6 changes: 2 additions & 4 deletions crates/oxc_minifier/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::rc::Rc;

use rustc_hash::FxHashMap;

use oxc_ecmascript::constant_evaluation::ConstantValue;
Expand All @@ -8,7 +6,7 @@ use oxc_semantic::SymbolId;
use crate::CompressOptions;

pub struct MinifierState<'a> {
pub options: Rc<CompressOptions>,
pub options: CompressOptions,

/// Constant values evaluated from expressions.
///
Expand All @@ -18,7 +16,7 @@ pub struct MinifierState<'a> {
}

impl MinifierState<'_> {
pub fn new(options: Rc<CompressOptions>) -> Self {
pub fn new(options: CompressOptions) -> Self {
Self { options, constant_values: FxHashMap::default() }
}
}
2 changes: 1 addition & 1 deletion crates/oxc_minifier/src/tester.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub fn run(source_text: &str, options: Option<CompressOptions>) -> String {
assert!(ret.errors.is_empty(), "{source_text}");
let mut program = ret.program;
if let Some(options) = options {
Compressor::new(&allocator, options).build(&mut program);
Compressor::new(&allocator).build(&mut program, options);
}
Codegen::new()
.with_options(CodegenOptions {
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_minifier/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub(crate) fn run(
assert!(ret.errors.is_empty(), "{source_text}");
let mut program = ret.program;
if let Some(options) = options {
Compressor::new(&allocator, options).build(&mut program);
Compressor::new(&allocator).build(&mut program, options);
}
Codegen::new()
.with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fn run(source_text: &str, source_type: SourceType, options: Option<CompressOptio
let mut ret = Parser::new(&allocator, source_text, source_type).parse();
let program = &mut ret.program;
if let Some(options) = options {
Compressor::new(&allocator, options).dead_code_elimination(program);
Compressor::new(&allocator).dead_code_elimination(program, options);
}
Codegen::new().build(program).code
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ pub fn test(source_text: &str, expected: &str, config: ReplaceGlobalDefinesConfi
let _ = ReplaceGlobalDefines::new(&allocator, config).build(scoping, &mut program);
// Run DCE, to align pipeline in crates/oxc/src/compiler.rs
let scoping = SemanticBuilder::new().build(&program).semantic.into_scoping();
Compressor::new(&allocator, CompressOptions::default())
.dead_code_elimination_with_scoping(scoping, &mut program);
Compressor::new(&allocator).dead_code_elimination_with_scoping(
&mut program,
scoping,
CompressOptions::smallest(),
);
let result = Codegen::new()
.with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() })
.build(&program)
Expand Down
2 changes: 1 addition & 1 deletion tasks/benchmark/benches/minifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fn bench_minifier(criterion: &mut Criterion) {

let options = CompressOptions::smallest();
runner.run(|| {
Compressor::new(&allocator, options).build_with_scoping(scoping, &mut program);
Compressor::new(&allocator).build_with_scoping(&mut program, scoping, options);
});
});
});
Expand Down
Loading