From 26840a8f0719541bb2a23ddcd5487567008c9ee6 Mon Sep 17 00:00:00 2001 From: Daniel Bulant Date: Sat, 1 Feb 2025 20:52:05 +0100 Subject: [PATCH] refactor(codegen): accept SymbolTable instead of Mangler --- Cargo.lock | 2 +- crates/oxc/src/compiler.rs | 6 +-- crates/oxc_codegen/Cargo.toml | 2 +- crates/oxc_codegen/src/lib.rs | 22 +++++---- crates/oxc_mangler/src/lib.rs | 59 ++++++++++++++++-------- crates/oxc_minifier/examples/mangler.rs | 4 +- crates/oxc_minifier/examples/minifier.rs | 2 +- crates/oxc_minifier/src/lib.rs | 8 ++-- crates/oxc_minifier/tests/mangler/mod.rs | 4 +- crates/oxc_semantic/src/symbol.rs | 7 +++ crates/oxc_wasm/src/lib.rs | 6 +-- napi/minify/src/lib.rs | 7 ++- tasks/coverage/src/runtime/mod.rs | 6 +-- tasks/minsize/src/lib.rs | 2 +- 14 files changed, 85 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 170c66f1855d4..c64176f6a0d55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1669,8 +1669,8 @@ dependencies = [ "oxc_ast", "oxc_data_structures", "oxc_index", - "oxc_mangler", "oxc_parser", + "oxc_semantic", "oxc_sourcemap", "oxc_span", "oxc_syntax", diff --git a/crates/oxc/src/compiler.rs b/crates/oxc/src/compiler.rs index 17430cd86a75e..8130db10ae687 100644 --- a/crates/oxc/src/compiler.rs +++ b/crates/oxc/src/compiler.rs @@ -286,7 +286,7 @@ pub trait CompilerInterface { Compressor::new(allocator, options).build(program); } - fn mangle(&self, program: &mut Program<'_>, options: MangleOptions) -> Mangler { + fn mangle(&self, program: &mut Program<'_>, options: MangleOptions) -> SymbolTable { Mangler::new().with_options(options).build(program) } @@ -294,13 +294,13 @@ pub trait CompilerInterface { &self, program: &Program<'_>, source_path: &Path, - mangler: Option, + symbol_table: Option, options: CodegenOptions, ) -> CodegenReturn { let mut options = options; if self.enable_sourcemap() { options.source_map_path = Some(source_path.to_path_buf()); } - CodeGenerator::new().with_options(options).with_mangler(mangler).build(program) + CodeGenerator::new().with_options(options).with_symbol_table(symbol_table).build(program) } } diff --git a/crates/oxc_codegen/Cargo.toml b/crates/oxc_codegen/Cargo.toml index 0575d7b6c9271..6620dee2d34d8 100644 --- a/crates/oxc_codegen/Cargo.toml +++ b/crates/oxc_codegen/Cargo.toml @@ -24,7 +24,7 @@ oxc_allocator = { workspace = true } oxc_ast = { workspace = true } oxc_data_structures = { workspace = true } oxc_index = { workspace = true } -oxc_mangler = { workspace = true } +oxc_semantic = { workspace = true } oxc_sourcemap = { workspace = true } oxc_span = { workspace = true } oxc_syntax = { workspace = true } diff --git a/crates/oxc_codegen/src/lib.rs b/crates/oxc_codegen/src/lib.rs index cdf157e0173bb..dc00fa3e82766 100644 --- a/crates/oxc_codegen/src/lib.rs +++ b/crates/oxc_codegen/src/lib.rs @@ -19,7 +19,7 @@ use oxc_ast::ast::{ BindingIdentifier, BlockStatement, Comment, Expression, IdentifierReference, Program, Statement, }; use oxc_data_structures::stack::Stack; -use oxc_mangler::Mangler; +use oxc_semantic::SymbolTable; use oxc_span::{GetSpan, Span, SPAN}; use oxc_syntax::{ identifier::{is_identifier_part, is_identifier_part_ascii, LS, PS}, @@ -79,7 +79,7 @@ pub struct Codegen<'a> { /// Original source code of the AST source_text: &'a str, - mangler: Option, + symbol_table: Option, /// Output Code code: CodeBuffer, @@ -162,7 +162,7 @@ impl<'a> Codegen<'a> { Self { options, source_text: "", - mangler: None, + symbol_table: None, code: CodeBuffer::default(), needs_semicolon: false, need_space_before_dot: 0, @@ -194,10 +194,12 @@ impl<'a> Codegen<'a> { self } - /// Set the mangler for mangling identifiers. + /// Set the symbol table used for identifier renaming. + /// + /// Can be used for easy renaming of variables (based on semantic analysis). #[must_use] - pub fn with_mangler(mut self, mangler: Option) -> Self { - self.mangler = mangler; + pub fn with_symbol_table(mut self, symbol_table: Option) -> Self { + self.symbol_table = symbol_table; self } @@ -516,9 +518,9 @@ impl<'a> Codegen<'a> { } fn get_identifier_reference_name(&self, reference: &IdentifierReference<'a>) -> &'a str { - if let Some(mangler) = &self.mangler { + if let Some(symbol_table) = &self.symbol_table { if let Some(reference_id) = reference.reference_id.get() { - if let Some(name) = mangler.get_reference_name(reference_id) { + if let Some(name) = symbol_table.get_reference_name(reference_id) { // SAFETY: Hack the lifetime to be part of the allocator. return unsafe { std::mem::transmute_copy(&name) }; } @@ -528,9 +530,9 @@ impl<'a> Codegen<'a> { } fn get_binding_identifier_name(&self, ident: &BindingIdentifier<'a>) -> &'a str { - if let Some(mangler) = &self.mangler { + if let Some(symbol_table) = &self.symbol_table { if let Some(symbol_id) = ident.symbol_id.get() { - let name = mangler.get_symbol_name(symbol_id); + let name = symbol_table.get_name(symbol_id); // SAFETY: Hack the lifetime to be part of the allocator. return unsafe { std::mem::transmute_copy(&name) }; } diff --git a/crates/oxc_mangler/src/lib.rs b/crates/oxc_mangler/src/lib.rs index 2ba45c40374cb..d0847f734bdc2 100644 --- a/crates/oxc_mangler/src/lib.rs +++ b/crates/oxc_mangler/src/lib.rs @@ -7,12 +7,17 @@ use rustc_hash::FxHashSet; use oxc_allocator::{Allocator, Vec}; use oxc_ast::ast::{Declaration, Program, Statement}; use oxc_index::Idx; -use oxc_semantic::{ReferenceId, ScopeTree, Semantic, SemanticBuilder, SymbolId, SymbolTable}; +use oxc_semantic::{ScopeTree, Semantic, SemanticBuilder, SymbolId, SymbolTable}; use oxc_span::Atom; #[derive(Default, Debug, Clone, Copy)] pub struct MangleOptions { + /// Also mangle exported variables. pub top_level: bool, + /// Use more readable mangled names + /// (e.g. `slot_0`, `slot_1`, `slot_2`, ...) for debugging. + /// + /// Uses base54 if false. pub debug: bool, } @@ -20,6 +25,32 @@ type Slot = usize; /// # Name Mangler / Symbol Minification /// +/// ## Example +/// +/// ```rust +/// use oxc_codegen::{Codegen, CodegenOptions}; +/// use oxc_ast::ast::Program; +/// use oxc_parser::Parser; +/// use oxc_allocator::Allocator; +/// use oxc_span::SourceType; +/// use oxc_mangler::{MangleOptions, Mangler}; +/// +/// let allocator = Allocator::default(); +/// let source = "const result = 1 + 2;"; +/// let parsed = Parser::new(&allocator, source, SourceType::mjs()).parse(); +/// assert!(parsed.errors.is_empty()); +/// +/// let mangled_symbols = Mangler::new() +/// .with_options(MangleOptions { top_level: true, debug: true }) +/// .build(&parsed.program); +/// +/// let js = Codegen::new().with_symbol_table(mangled_symbols).build(&parsed.program); +/// // this will be `const a = 1 + 2;` if debug = false +/// assert_eq!(js.code, "const slot_0 = 1 + 2;\n"); +/// ``` +/// +/// ## Implementation +/// /// See: /// * [esbuild](https://github.com/evanw/esbuild/blob/v0.24.0/docs/architecture.md#symbol-minification) /// @@ -61,7 +92,7 @@ type Slot = usize; /// } /// ``` /// -/// ## Name Reuse Calculation +/// ### Name Reuse Calculation /// /// This improvement was inspired by [evanw/esbuild#2614](https://github.com/evanw/esbuild/pull/2614). /// @@ -112,8 +143,6 @@ type Slot = usize; /// - slot 3: `bar` #[derive(Default)] pub struct Mangler { - symbol_table: SymbolTable, - options: MangleOptions, } @@ -129,17 +158,10 @@ impl Mangler { self } - pub fn get_symbol_name(&self, symbol_id: SymbolId) -> &str { - self.symbol_table.get_name(symbol_id) - } - - pub fn get_reference_name(&self, reference_id: ReferenceId) -> Option<&str> { - let symbol_id = self.symbol_table.get_reference(reference_id).symbol_id()?; - Some(self.symbol_table.get_name(symbol_id)) - } - + /// Mangles the program. The resulting SymbolTable contains the mangled symbols - `program` is not modified. + /// Pass the symbol table to oxc_codegen to generate the mangled code. #[must_use] - pub fn build(self, program: &Program<'_>) -> Mangler { + pub fn build(self, program: &Program<'_>) -> SymbolTable { let semantic = SemanticBuilder::new().with_scope_tree_child_ids(true).build(program).semantic; self.build_with_semantic(semantic, program) @@ -149,7 +171,7 @@ impl Mangler { /// /// Panics if the child_ids does not exist in scope_tree. #[must_use] - pub fn build_with_semantic(self, semantic: Semantic<'_>, program: &Program<'_>) -> Mangler { + pub fn build_with_semantic(self, semantic: Semantic<'_>, program: &Program<'_>) -> SymbolTable { if self.options.debug { self.build_with_symbols_and_scopes_impl(semantic, program, debug_name) } else { @@ -161,11 +183,11 @@ impl Mangler { const CAPACITY: usize, G: Fn(usize) -> InlineString, >( - mut self, + self, semantic: Semantic<'_>, program: &Program<'_>, generate_name: G, - ) -> Mangler { + ) -> SymbolTable { let (mut symbol_table, scope_tree, ast_nodes) = semantic.into_symbols_scopes_nodes(); assert!(scope_tree.has_child_ids(), "child_id needs to be generated"); @@ -331,8 +353,7 @@ impl Mangler { } } - self.symbol_table = symbol_table; - self + symbol_table } fn tally_slot_frequencies<'a>( diff --git a/crates/oxc_minifier/examples/mangler.rs b/crates/oxc_minifier/examples/mangler.rs index 70449e81a57dc..4a4221e9facdb 100644 --- a/crates/oxc_minifier/examples/mangler.rs +++ b/crates/oxc_minifier/examples/mangler.rs @@ -38,8 +38,8 @@ fn main() -> std::io::Result<()> { fn mangler(source_text: &str, source_type: SourceType, debug: bool) -> String { let allocator = Allocator::default(); let ret = Parser::new(&allocator, source_text, source_type).parse(); - let mangler = Mangler::new() + let symbol_table = Mangler::new() .with_options(MangleOptions { debug, top_level: source_type.is_module() }) .build(&ret.program); - CodeGenerator::new().with_mangler(Some(mangler)).build(&ret.program).code + CodeGenerator::new().with_symbol_table(Some(symbol_table)).build(&ret.program).code } diff --git a/crates/oxc_minifier/examples/minifier.rs b/crates/oxc_minifier/examples/minifier.rs index 4d02d16c93155..57b5f2d746a1c 100644 --- a/crates/oxc_minifier/examples/minifier.rs +++ b/crates/oxc_minifier/examples/minifier.rs @@ -55,7 +55,7 @@ fn minify( let ret = Minifier::new(options).build(allocator, &mut program); CodeGenerator::new() .with_options(CodegenOptions { minify: nospace, ..CodegenOptions::default() }) - .with_mangler(ret.mangler) + .with_symbol_table(ret.symbol_table) .build(&program) .code } diff --git a/crates/oxc_minifier/src/lib.rs b/crates/oxc_minifier/src/lib.rs index ede3349e6f584..3068e77b95781 100644 --- a/crates/oxc_minifier/src/lib.rs +++ b/crates/oxc_minifier/src/lib.rs @@ -12,7 +12,7 @@ mod tester; use oxc_allocator::Allocator; use oxc_ast::ast::Program; use oxc_mangler::Mangler; -use oxc_semantic::{SemanticBuilder, Stats}; +use oxc_semantic::{SemanticBuilder, Stats, SymbolTable}; pub use oxc_mangler::MangleOptions; @@ -31,7 +31,7 @@ impl Default for MinifierOptions { } pub struct MinifierReturn { - pub mangler: Option, + pub symbol_table: Option, } pub struct Minifier { @@ -54,7 +54,7 @@ impl Minifier { } else { Stats::default() }; - let mangler = self.options.mangle.map(|options| { + let symbol_table = self.options.mangle.map(|options| { let semantic = SemanticBuilder::new() .with_stats(stats) .with_scope_tree_child_ids(true) @@ -62,6 +62,6 @@ impl Minifier { .semantic; Mangler::default().with_options(options).build_with_semantic(semantic, program) }); - MinifierReturn { mangler } + MinifierReturn { symbol_table } } } diff --git a/crates/oxc_minifier/tests/mangler/mod.rs b/crates/oxc_minifier/tests/mangler/mod.rs index d979475134710..16b37417c8bf4 100644 --- a/crates/oxc_minifier/tests/mangler/mod.rs +++ b/crates/oxc_minifier/tests/mangler/mod.rs @@ -11,9 +11,9 @@ fn mangle(source_text: &str, top_level: bool) -> String { let source_type = SourceType::mjs(); let ret = Parser::new(&allocator, source_text, source_type).parse(); let program = ret.program; - let mangler = + let symbol_table = Mangler::new().with_options(MangleOptions { debug: false, top_level }).build(&program); - CodeGenerator::new().with_mangler(Some(mangler)).build(&program).code + CodeGenerator::new().with_symbol_table(Some(symbol_table)).build(&program).code } #[test] diff --git a/crates/oxc_semantic/src/symbol.rs b/crates/oxc_semantic/src/symbol.rs index d1338d6ea496b..5e20a14191992 100644 --- a/crates/oxc_semantic/src/symbol.rs +++ b/crates/oxc_semantic/src/symbol.rs @@ -250,6 +250,13 @@ impl SymbolTable { &mut self.references[reference_id] } + /// Get the name of the symbol a reference is resolved to. Returns `None` if the reference is + /// not resolved. + #[inline] + pub fn get_reference_name(&self, reference_id: ReferenceId) -> Option<&str> { + self.get_name(self.references[reference_id].symbol_id()?).into() + } + /// Returns `true` if the corresponding [`Reference`] is resolved to a symbol. /// /// When `false`, this could either be a reference to a global value or an identifier that does diff --git a/crates/oxc_wasm/src/lib.rs b/crates/oxc_wasm/src/lib.rs index 89db47e73e472..206b63cd2f2f4 100644 --- a/crates/oxc_wasm/src/lib.rs +++ b/crates/oxc_wasm/src/lib.rs @@ -265,7 +265,7 @@ impl Oxc { } } - let mangler = if minifier_options.compress.unwrap_or_default() + let symbol_table = if minifier_options.compress.unwrap_or_default() || minifier_options.mangle.unwrap_or_default() { let compress_options = minifier_options.compress_options.unwrap_or_default(); @@ -281,13 +281,13 @@ impl Oxc { CompressOptions::all_false() }), }; - Minifier::new(options).build(&allocator, &mut program).mangler + Minifier::new(options).build(&allocator, &mut program).symbol_table } else { None }; self.codegen_text = CodeGenerator::new() - .with_mangler(mangler) + .with_symbol_table(symbol_table) .with_options(CodegenOptions { minify: minifier_options.whitespace.unwrap_or_default(), ..CodegenOptions::default() diff --git a/napi/minify/src/lib.rs b/napi/minify/src/lib.rs index ae9c6acbf9322..3c8b2dea1dbc0 100644 --- a/napi/minify/src/lib.rs +++ b/napi/minify/src/lib.rs @@ -39,7 +39,7 @@ pub fn minify( let mut program = Parser::new(&allocator, &source_text, source_type).parse().program; - let mangler = Minifier::new(minifier_options).build(&allocator, &mut program).mangler; + let symbol_table = Minifier::new(minifier_options).build(&allocator, &mut program).symbol_table; let mut codegen_options = match &options.codegen { Some(Either::A(false)) => CodegenOptions { minify: false, ..CodegenOptions::default() }, @@ -53,7 +53,10 @@ pub fn minify( codegen_options.source_map_path = Some(PathBuf::from(filename)); } - let ret = Codegen::new().with_options(codegen_options).with_mangler(mangler).build(&program); + let ret = Codegen::new() + .with_options(codegen_options) + .with_symbol_table(symbol_table) + .build(&program); Ok(MinifyResult { code: ret.code, map: ret.map.map(oxc_sourcemap::napi::SourceMap::from) }) } diff --git a/tasks/coverage/src/runtime/mod.rs b/tasks/coverage/src/runtime/mod.rs index 7462c2376248e..4815ee74385e7 100644 --- a/tasks/coverage/src/runtime/mod.rs +++ b/tasks/coverage/src/runtime/mod.rs @@ -208,17 +208,17 @@ impl Test262RuntimeCase { ); } - let mangler = if minify { + let symbol_table = if minify { Minifier::new(MinifierOptions { mangle: None, ..MinifierOptions::default() }) .build(&allocator, &mut program) - .mangler + .symbol_table } else { None }; let mut text = CodeGenerator::new() .with_options(CodegenOptions { minify, ..CodegenOptions::default() }) - .with_mangler(mangler) + .with_symbol_table(symbol_table) .build(&program) .code; if is_only_strict { diff --git a/tasks/minsize/src/lib.rs b/tasks/minsize/src/lib.rs index 9f144b2e850d0..b49ecba6ed7bc 100644 --- a/tasks/minsize/src/lib.rs +++ b/tasks/minsize/src/lib.rs @@ -146,7 +146,7 @@ fn minify(source_text: &str, source_type: SourceType) -> String { let ret = Minifier::new(MinifierOptions::default()).build(&allocator, &mut program); CodeGenerator::new() .with_options(CodegenOptions { minify: true, comments: false, ..CodegenOptions::default() }) - .with_mangler(ret.mangler) + .with_symbol_table(ret.symbol_table) .build(&program) .code }