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
2 changes: 1 addition & 1 deletion crates/oxc_minifier/examples/minifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ fn minify(
mangle: mangle.then(MangleOptions::default),
compress: Some(CompressOptions::smallest()),
};
let ret = Minifier::new(options).build(allocator, &mut program);
let ret = Minifier::new(options).minify(allocator, &mut program);
Codegen::new()
.with_options(CodegenOptions {
source_map_path,
Expand Down
9 changes: 5 additions & 4 deletions crates/oxc_minifier/src/compressor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,23 @@ impl<'a> Compressor<'a> {
PeepholeOptimizations::new().run_in_loop(program, &mut ctx)
}

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

pub fn dead_code_elimination_with_scoping(
self,
program: &mut Program<'a>,
scoping: Scoping,
options: CompressOptions,
) {
) -> u8 {
let state = MinifierState::new(program.source_type, options);
let mut ctx = ReusableTraverseCtx::new(state, scoping, self.allocator);
let normalize_options =
NormalizeOptions { convert_while_to_fors: false, convert_const_to_let: false };
Normalize::new(normalize_options).build(program, &mut ctx);
DeadCodeElimination::new().build(program, &mut ctx);
DeadCodeElimination::new().run_in_loop(program, &mut ctx);
1
}
}
30 changes: 26 additions & 4 deletions crates/oxc_minifier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,43 @@ pub struct Minifier {
options: MinifierOptions,
}

impl Minifier {
impl<'a> Minifier {
pub fn new(options: MinifierOptions) -> Self {
Self { options }
}

pub fn build<'a>(self, allocator: &'a Allocator, program: &mut Program<'a>) -> MinifierReturn {
pub fn minify(self, allocator: &'a Allocator, program: &mut Program<'a>) -> MinifierReturn {
self.build(false, allocator, program)
}

pub fn dce(self, allocator: &'a Allocator, program: &mut Program<'a>) -> MinifierReturn {
self.build(true, allocator, program)
}

fn build(
self,
dce: bool,
allocator: &'a Allocator,
program: &mut Program<'a>,
) -> MinifierReturn {
let (stats, iterations) = self
.options
.compress
.map(|options| {
let semantic = SemanticBuilder::new().build(program).semantic;
let stats = semantic.stats();
let scoping = semantic.into_scoping();
let iterations =
Compressor::new(allocator).build_with_scoping(program, scoping, options);
let compressor = Compressor::new(allocator);
let iterations = if dce {
let options = CompressOptions {
target: options.target,
treeshake: options.treeshake,
..CompressOptions::dce()
};
compressor.dead_code_elimination_with_scoping(program, scoping, options)
} else {
compressor.build_with_scoping(program, scoping, options)
};
(stats, iterations)
})
.unwrap_or_default();
Expand Down
55 changes: 43 additions & 12 deletions crates/oxc_minifier/src/peephole/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ impl<'a> PeepholeOptimizations {
Self { iteration: 0, changed: false }
}

pub fn build(
fn run_once(
&mut self,
program: &mut Program<'a>,
ctx: &mut ReusableTraverseCtx<'a, MinifierState<'a>>,
Expand All @@ -62,7 +62,7 @@ impl<'a> PeepholeOptimizations {
) -> u8 {
loop {
self.changed = false;
self.build(program, ctx);
self.run_once(program, ctx);
if !self.changed {
break;
}
Expand Down Expand Up @@ -352,23 +352,62 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {

pub struct DeadCodeElimination {
inner: PeepholeOptimizations,
iteration: u8,
changed: bool,
}

impl<'a> DeadCodeElimination {
pub fn new() -> Self {
Self { inner: PeepholeOptimizations::new() }
Self { inner: PeepholeOptimizations::new(), iteration: 0, changed: false }
}

pub fn build(
fn run_once(
&mut self,
program: &mut Program<'a>,
ctx: &mut ReusableTraverseCtx<'a, MinifierState<'a>>,
) {
traverse_mut_with_ctx(self, program, ctx);
}

pub fn run_in_loop(
&mut self,
program: &mut Program<'a>,
ctx: &mut ReusableTraverseCtx<'a, MinifierState<'a>>,
) -> u8 {
loop {
self.changed = false;
self.run_once(program, ctx);
if !self.changed {
break;
}
if self.iteration > 10 {
debug_assert!(false, "Ran loop more than 10 times.");
break;
}
self.iteration += 1;
}
self.iteration
}
}

impl<'a> Traverse<'a, MinifierState<'a>> for DeadCodeElimination {
fn enter_program(&mut self, _program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
ctx.state.symbol_values.clear();
ctx.state.changed = false;
}

fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
// Remove unused references by visiting the AST again and diff the collected references.
let refs_before =
ctx.scoping().resolved_references().flatten().copied().collect::<FxHashSet<_>>();
let mut counter = ReferencesCounter::default();
counter.visit_program(program);
for reference_id_to_remove in refs_before.difference(&counter.refs) {
ctx.scoping_mut().delete_reference(*reference_id_to_remove);
}
self.changed = ctx.state.changed;
}

fn exit_variable_declarator(
&mut self,
decl: &mut VariableDeclarator<'a>,
Expand All @@ -385,15 +424,7 @@ impl<'a> Traverse<'a, MinifierState<'a>> for DeadCodeElimination {

fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
let mut ctx = Ctx::new(ctx);
let changed = ctx.state.changed;
ctx.state.changed = false;
self.inner.minimize_statements(stmts, &mut ctx);
if ctx.state.changed {
self.inner.minimize_statements(stmts, &mut ctx);
} else {
ctx.state.changed = changed;
}
stmts.retain(|stmt| !matches!(stmt, Statement::EmptyStatement(_)));
}

fn exit_expression(&mut self, e: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
Expand Down
10 changes: 1 addition & 9 deletions crates/oxc_minifier/src/peephole/remove_dead_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,13 @@ impl<'a> PeepholeOptimizations {
} else {
if_stmt.alternate = Some(new_stmt);
}
ctx.state.changed = true;
}
}
Some(Statement::BlockStatement(s)) if s.body.is_empty() => {
if_stmt.alternate = None;
ctx.state.changed = true;
}
Some(Statement::EmptyStatement(_)) => {
if_stmt.alternate = None;
ctx.state.changed = true;
}
_ => {}
}
Expand Down Expand Up @@ -132,12 +129,7 @@ impl<'a> PeepholeOptimizations {
if_stmt.consequent = ctx.ast.statement_empty(if_stmt.consequent.span());
}
}
return Some(ctx.ast.statement_if(
if_stmt.span,
if_stmt.test.take_in(ctx.ast),
if_stmt.consequent.take_in(ctx.ast),
if_stmt.alternate.as_mut().map(|alternate| alternate.take_in(ctx.ast)),
));
return None;
}
return Some(if boolean {
if_stmt.consequent.take_in(ctx.ast)
Expand Down
14 changes: 13 additions & 1 deletion crates/oxc_minifier/tests/peephole/dead_code_elimination.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,18 @@ fn dce_if_statement() {
test("if (typeof 1 !== 'number') { REMOVE; }", "");
test("if (typeof false !== 'boolean') { REMOVE; }", "");
test("if (typeof 1 === 'string') { REMOVE; }", "");

// Complicated
test(
"if (unknown)
for (var x = 1; x-- > 0; )
if (foo++, false) foo++;
else 'Side effect free code to be dropped';
else throw new Error();",
"if (unknown) {
for (var x = 1; x-- > 0;) if (foo++, false);
} else throw new Error();",
);
}

#[test]
Expand Down Expand Up @@ -143,7 +155,7 @@ fn dce_logical_expression() {

test(
"const x = 'keep'; const y = 'remove'; foo(x || y), foo(y && x)",
"const x = 'keep'; const y = 'remove'; foo(x), foo(x);",
"const x = 'keep'; foo(x), foo(x);",
);
}

Expand Down
2 changes: 1 addition & 1 deletion napi/minify/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub fn minify(
let parser_ret = Parser::new(&allocator, &source_text, source_type).parse();
let mut program = parser_ret.program;

let scoping = Minifier::new(minifier_options).build(&allocator, &mut program).scoping;
let scoping = Minifier::new(minifier_options).minify(&allocator, &mut program).scoping;

let mut codegen_options = match &options.codegen {
// Need to remove all comments.
Expand Down
2 changes: 1 addition & 1 deletion napi/playground/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ impl Oxc {
CompressOptions::default()
}),
};
Minifier::new(options).build(&allocator, &mut program).scoping
Minifier::new(options).minify(&allocator, &mut program).scoping
} else {
None
};
Expand Down
2 changes: 1 addition & 1 deletion tasks/coverage/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ impl Test262RuntimeCase {

let symbol_table = if minify {
Minifier::new(MinifierOptions { mangle: None, ..MinifierOptions::default() })
.build(&allocator, &mut program)
.minify(&allocator, &mut program)
.scoping
} else {
None
Expand Down
2 changes: 1 addition & 1 deletion tasks/minsize/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ fn minify(source_text: &str, source_type: SourceType, options: Options) -> (Stri
mangle: (!options.compress_only).then(MangleOptions::default),
compress: Some(CompressOptions::default()),
})
.build(&allocator, &mut program);
.minify(&allocator, &mut program);
let code = Codegen::new()
.with_options(CodegenOptions { minify: !options.compress_only, ..CodegenOptions::minify() })
.with_scoping(ret.scoping)
Expand Down
Loading