diff --git a/crates/oxc/src/compiler.rs b/crates/oxc/src/compiler.rs index 068829057..532d2af4b 100644 --- a/crates/oxc/src/compiler.rs +++ b/crates/oxc/src/compiler.rs @@ -186,7 +186,14 @@ pub trait CompilerInterface { source_text: &'a str, source_path: &Path, ) -> SemanticBuilderReturn<'a> { - SemanticBuilder::new(source_text) + let mut builder = SemanticBuilder::new(source_text); + + if self.transform_options().is_some() { + // Estimate transformer will triple scopes, symbols, references + builder = builder.with_excess_capacity(2.0); + } + + builder .with_check_syntax_error(self.check_semantic_error()) .with_scope_tree_child_ids(self.semantic_child_scope_ids()) .build_module_record(source_path, program) diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index a5ff8f0c8..30605bd47 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -99,6 +99,7 @@ pub struct SemanticBuilder<'a> { build_jsdoc: bool, jsdoc: JSDocBuilder<'a>, stats: Option, + excess_capacity: f64, /// Should additional syntax checks be performed? /// @@ -146,6 +147,7 @@ impl<'a> SemanticBuilder<'a> { build_jsdoc: false, jsdoc: JSDocBuilder::new(source_text, trivias), stats: None, + excess_capacity: 0.0, check_syntax_error: false, cfg: None, class_table_builder: ClassTableBuilder::new(), @@ -208,6 +210,24 @@ impl<'a> SemanticBuilder<'a> { self } + /// Request `SemanticBuilder` to allocate excess capacity for scopes, symbols, and references. + /// + /// `excess_capacity` is provided as a fraction. + /// e.g. to over-allocate by 20%, pass `0.2` as `excess_capacity`. + /// + /// Has no effect if a `Stats` object is provided with [`SemanticBuilder::with_stats`], + /// only if `SemanticBuilder` is calculating stats itself. + /// + /// This is useful when you intend to modify `Semantic`, adding more `nodes`, `scopes`, `symbols`, + /// or `references`. Allocating excess capacity for these additions at the outset prevents + /// `Semantic`'s data structures needing to grow later on which involves memory copying. + /// For large ASTs with a lot of semantic data, re-allocation can be very costly. + #[must_use] + pub fn with_excess_capacity(mut self, excess_capacity: f64) -> Self { + self.excess_capacity = excess_capacity; + self + } + /// Get the built module record from `build_module_record` pub fn module_record(&self) -> Arc { Arc::clone(&self.module_record) @@ -248,10 +268,11 @@ impl<'a> SemanticBuilder<'a> { // // If user did not provide existing `Stats`, calculate them by visiting AST. let (stats, check_stats) = if let Some(stats) = self.stats { - (stats, false) + (stats, None) } else { let stats = Stats::count(program); - (stats, true) + let stats_with_excess = stats.increase_by(self.excess_capacity); + (stats_with_excess, Some(stats)) }; self.nodes.reserve(stats.nodes as usize); self.scope.reserve(stats.scopes as usize); @@ -262,7 +283,7 @@ impl<'a> SemanticBuilder<'a> { // Check that estimated counts accurately (unless in release mode) #[cfg(debug_assertions)] - if check_stats { + if let Some(stats) = check_stats { #[allow(clippy::cast_possible_truncation)] let actual_stats = Stats::new( self.nodes.len() as u32, diff --git a/crates/oxc_semantic/src/stats.rs b/crates/oxc_semantic/src/stats.rs index bcf5db4a9..056a081d8 100644 --- a/crates/oxc_semantic/src/stats.rs +++ b/crates/oxc_semantic/src/stats.rs @@ -96,6 +96,23 @@ impl Stats { counter.stats } + /// Increase scope, symbol, and reference counts by provided `excess`. + /// + /// `excess` is provided as a fraction. + /// e.g. to over-allocate by 20%, pass `0.2` as `excess`. + #[must_use] + pub fn increase_by(mut self, excess: f64) -> Self { + let factor = excess + 1.0; + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::cast_lossless)] + let increase = |n: u32| (n as f64 * factor) as u32; + + self.scopes = increase(self.scopes); + self.symbols = increase(self.symbols); + self.references = increase(self.references); + + self + } + /// Assert that estimated [`Stats`] match actual. /// /// # Panics diff --git a/crates/oxc_transformer/examples/transformer.rs b/crates/oxc_transformer/examples/transformer.rs index b36d32ff7..faacd9a73 100644 --- a/crates/oxc_transformer/examples/transformer.rs +++ b/crates/oxc_transformer/examples/transformer.rs @@ -40,6 +40,8 @@ fn main() { let mut program = ret.program; let (symbols, scopes) = SemanticBuilder::new(&source_text) + // Estimate transformer will triple scopes, symbols, references + .with_excess_capacity(2.0) .build(&program) .semantic .into_symbol_table_and_scope_tree(); diff --git a/crates/oxc_wasm/src/lib.rs b/crates/oxc_wasm/src/lib.rs index 24b3cd357..110d889eb 100644 --- a/crates/oxc_wasm/src/lib.rs +++ b/crates/oxc_wasm/src/lib.rs @@ -192,7 +192,12 @@ impl Oxc { self.ir = format!("{:#?}", program.body); self.ast = program.serialize(&self.serializer)?; - let semantic_ret = SemanticBuilder::new(source_text) + let mut semantic_builder = SemanticBuilder::new(source_text); + if run_options.transform.unwrap_or_default() { + // Estimate transformer will triple scopes, symbols, references + semantic_builder = semantic_builder.with_excess_capacity(2.0); + } + let semantic_ret = semantic_builder .with_trivias(trivias.clone()) .with_check_syntax_error(true) .with_cfg(true) diff --git a/napi/transform/src/transformer.rs b/napi/transform/src/transformer.rs index 03babe243..a3a0c84f8 100644 --- a/napi/transform/src/transformer.rs +++ b/napi/transform/src/transformer.rs @@ -100,6 +100,8 @@ pub fn transform( fn transpile(ctx: &TransformContext<'_>) -> CodegenReturn { let (symbols, scopes) = SemanticBuilder::new(ctx.source_text()) + // Estimate transformer will triple scopes, symbols, references + .with_excess_capacity(2.0) .build(&ctx.program()) .semantic .into_symbol_table_and_scope_tree(); diff --git a/tasks/benchmark/benches/transformer.rs b/tasks/benchmark/benches/transformer.rs index d73aaf5f8..141d78221 100644 --- a/tasks/benchmark/benches/transformer.rs +++ b/tasks/benchmark/benches/transformer.rs @@ -30,6 +30,8 @@ fn bench_transformer(criterion: &mut Criterion) { Parser::new(&allocator, source_text, source_type).parse(); let program = allocator.alloc(program); let (symbols, scopes) = SemanticBuilder::new(source_text) + // Estimate transformer will triple scopes, symbols, references + .with_excess_capacity(2.0) .build(program) .semantic .into_symbol_table_and_scope_tree();