Skip to content

Commit

Permalink
feat(semantic): add SemanticBuilder::with_excess_capacity (oxc-proj…
Browse files Browse the repository at this point in the history
…ect#5762)

Add `SemanticBuilder::with_excess_capacity` method to request that `SemanticBuilder` over-allocate space in `Semantic`'s `Vec`s.

Use this method to reserve 200% extra capacity for transformer to create more scopes, symbols and references.

200% is an unscientific guess of how much extra capacity is required. Obviously it depends on what transforms are enabled and content of the source code.
  • Loading branch information
overlookmotel committed Sep 14, 2024
1 parent f61e8b5 commit 3230ae5
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 5 deletions.
9 changes: 8 additions & 1 deletion crates/oxc/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
27 changes: 24 additions & 3 deletions crates/oxc_semantic/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ pub struct SemanticBuilder<'a> {
build_jsdoc: bool,
jsdoc: JSDocBuilder<'a>,
stats: Option<Stats>,
excess_capacity: f64,

/// Should additional syntax checks be performed?
///
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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<ModuleRecord> {
Arc::clone(&self.module_record)
Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand Down
17 changes: 17 additions & 0 deletions crates/oxc_semantic/src/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions crates/oxc_transformer/examples/transformer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
7 changes: 6 additions & 1 deletion crates/oxc_wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions napi/transform/src/transformer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions tasks/benchmark/benches/transformer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit 3230ae5

Please sign in to comment.