mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
perf(oxc): conditionally rebuild semantic in compiler pipeline (#23153)
## What Track scoping/AST sync state with a single `scoping_dirty` flag in `Compiler::compile` so each downstream step (inject/define plugins, DCE, compress) only rebuilds semantic when it's actually needed. ## Details - `InjectGlobalVariables` and `ReplaceGlobalDefines` already return a `changed: bool`; this was previously ignored. It's now OR-ed into `scoping_dirty`. - `scoping_dirty` starts `true` only when the transformer runs — the parser, React Compiler (rebuilds scoping on success, returns the untouched scoping on bail-out), and any fresh `SemanticBuilder` all leave scoping in sync. - The pre-plugin rebuild is now gated on `scoping_dirty`, skipping a needless rebuild when no transform ran. - DCE and Compress are now a single mutually-exclusive `if/else`, both rebuilding semantic only when `scoping_dirty`. - `Compressor::build` (which always rebuilds semantic internally) is replaced with `build_with_scoping`, so when scoping is already in sync the compressor skips its own `SemanticBuilder` pass. - The three rebuild sites are extracted into a `rebuild_scoping` helper, which enables enum eval when a transform is configured so const-enum resolution matches the original semantic build. ## Note The `CompilerInterface::compress` trait method gains a `scoping: Scoping` parameter — a breaking change for external implementors that override it.
This commit is contained in:
1 file changed
+50
-16
+50
-16
@@ -9,7 +9,7 @@ use oxc_mangler::{MangleOptions, Mangler, ManglerReturn};
|
||||
use oxc_minifier::{CompressOptions, Compressor};
|
||||
use oxc_parser::{ParseOptions, Parser, ParserReturn};
|
||||
use oxc_react_compiler::{self, PluginOptions as ReactCompilerOptions};
|
||||
use oxc_semantic::{Scoping, SemanticBuilder, SemanticBuilderReturn};
|
||||
use oxc_semantic::{Scoping, SemanticBuilder, SemanticBuilderReturn, Stats};
|
||||
use oxc_span::SourceType;
|
||||
use oxc_transformer::{TransformOptions, Transformer, TransformerReturn};
|
||||
use oxc_transformer_plugins::{
|
||||
@@ -205,37 +205,54 @@ pub trait CompilerInterface {
|
||||
self.handle_errors(errors);
|
||||
}
|
||||
|
||||
// The transformer leaves symbols and scopes out of sync with the AST;
|
||||
// the parser, React Compiler, and any fresh semantic build leave them in
|
||||
// sync. Track the state so each consumer below only rebuilds when needed.
|
||||
let transform = self.transform_options().is_some();
|
||||
let mut scoping_dirty = transform;
|
||||
|
||||
let inject_options = self.inject_options();
|
||||
let define_options = self.define_options();
|
||||
let has_define = define_options.is_some();
|
||||
|
||||
// Symbols and scopes are out of sync.
|
||||
if inject_options.is_some() || define_options.is_some() {
|
||||
scoping =
|
||||
SemanticBuilder::new().with_stats(stats).build(&program).semantic.into_scoping();
|
||||
// The inject/define plugins require in-sync scoping as input.
|
||||
if scoping_dirty && (inject_options.is_some() || has_define) {
|
||||
scoping = rebuild_scoping(&program, stats, transform);
|
||||
scoping_dirty = false;
|
||||
}
|
||||
|
||||
if let Some(options) = inject_options {
|
||||
let ret = InjectGlobalVariables::new(&allocator, options).build(scoping, &mut program);
|
||||
scoping = ret.scoping;
|
||||
scoping_dirty |= ret.changed;
|
||||
}
|
||||
|
||||
if let Some(options) = define_options {
|
||||
let ret = ReplaceGlobalDefines::new(&allocator, options).build(scoping, &mut program);
|
||||
scoping = ret.scoping;
|
||||
// Run DCE if minification is disabled.
|
||||
if self.compress_options().is_none() {
|
||||
Compressor::new(&allocator).dead_code_elimination_with_scoping(
|
||||
&mut program,
|
||||
scoping,
|
||||
CompressOptions::dce(),
|
||||
);
|
||||
}
|
||||
scoping_dirty |= ret.changed;
|
||||
}
|
||||
|
||||
/* Compress */
|
||||
/* Compress / DCE */
|
||||
|
||||
// Both consumers need in-sync scoping; only rebuild when a preceding step
|
||||
// (transform or a plugin that mutated the AST) left it dirty. The branches
|
||||
// are mutually exclusive, so `scoping` is moved into exactly one of them.
|
||||
if let Some(options) = self.compress_options() {
|
||||
self.compress(&allocator, &mut program, options);
|
||||
if scoping_dirty {
|
||||
scoping = rebuild_scoping(&program, stats, transform);
|
||||
}
|
||||
self.compress(&allocator, &mut program, scoping, options);
|
||||
} else if has_define {
|
||||
// Run DCE if minification is disabled.
|
||||
if scoping_dirty {
|
||||
scoping = rebuild_scoping(&program, stats, transform);
|
||||
}
|
||||
Compressor::new(&allocator).dead_code_elimination_with_scoping(
|
||||
&mut program,
|
||||
scoping,
|
||||
CompressOptions::dce(),
|
||||
);
|
||||
}
|
||||
|
||||
/* Mangler */
|
||||
@@ -306,9 +323,10 @@ pub trait CompilerInterface {
|
||||
&self,
|
||||
allocator: &'a Allocator,
|
||||
program: &mut Program<'a>,
|
||||
scoping: Scoping,
|
||||
options: CompressOptions,
|
||||
) {
|
||||
Compressor::new(allocator).build(program, options);
|
||||
Compressor::new(allocator).build_with_scoping(program, scoping, options);
|
||||
}
|
||||
|
||||
fn mangle(&self, program: &mut Program<'_>, options: MangleOptions) -> ManglerReturn {
|
||||
@@ -336,3 +354,19 @@ pub trait CompilerInterface {
|
||||
.build(program)
|
||||
}
|
||||
}
|
||||
|
||||
/// Rebuild [`Scoping`] from scratch, reusing prior `stats` to size allocations.
|
||||
///
|
||||
/// Used to re-sync symbols and scopes with the AST after a step that left them
|
||||
/// out of date (the transformer, or an inject/define plugin that mutated nodes).
|
||||
///
|
||||
/// `enum_eval` must match the original semantic build so downstream transforms
|
||||
/// see the same const-enum resolution.
|
||||
fn rebuild_scoping(program: &Program<'_>, stats: Stats, enum_eval: bool) -> Scoping {
|
||||
SemanticBuilder::new()
|
||||
.with_stats(stats)
|
||||
.with_enum_eval(enum_eval)
|
||||
.build(program)
|
||||
.semantic
|
||||
.into_scoping()
|
||||
}
|
||||
Reference in new issue
Block a user