Skip to content

Commit

Permalink
perf(es/codegen): Reduce allocation using compact_str (#10008)
Browse files Browse the repository at this point in the history
**Description:**

Previously, we allocated if there's an escape character. With this PR, we allocate if there's an escape character **and** it's long enough.
  • Loading branch information
kdy1 authored Feb 9, 2025
1 parent f9f4cac commit 7d7319f
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 26 deletions.
6 changes: 6 additions & 0 deletions .changeset/three-nails-repair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
swc_core: minor
swc_ecma_codegen: minor
---

perf(es/codegen): Reduce allocation using `compact_str`
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 10 additions & 9 deletions crates/swc_ecma_codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@ serde-impl = ["swc_ecma_ast/serde"]
bench = false

[dependencies]
ascii = { workspace = true }
memchr = { workspace = true }
num-bigint = { workspace = true, features = ["serde"] }
once_cell = { workspace = true }
regex = { workspace = true }
rustc-hash = { workspace = true }
serde = { workspace = true }
sourcemap = { workspace = true }
tracing = { workspace = true }
ascii = { workspace = true }
compact_str = { workspace = true }
memchr = { workspace = true }
num-bigint = { workspace = true, features = ["serde"] }
once_cell = { workspace = true }
regex = { workspace = true }
rustc-hash = { workspace = true }
serde = { workspace = true }
sourcemap = { workspace = true }
tracing = { workspace = true }

swc_allocator = { version = "3.0.0", path = "../swc_allocator", default-features = false }
swc_atoms = { version = "3.1.0", path = "../swc_atoms" }
Expand Down
50 changes: 34 additions & 16 deletions crates/swc_ecma_codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
#![allow(clippy::nonminimal_bool)]
#![allow(non_local_definitions)]

use std::{borrow::Cow, fmt::Write, io, str};
use std::{borrow::Cow, fmt::Write, io, ops::Deref, str};

use ascii::AsciiChar;
use compact_str::{format_compact, CompactString};
use memchr::memmem::Finder;
use once_cell::sync::Lazy;
use swc_allocator::maybe::vec::Vec;
Expand Down Expand Up @@ -108,7 +109,23 @@ where
pub wr: W,
}

fn replace_close_inline_script(raw: &str) -> Cow<str> {
enum CowStr<'a> {
Borrowed(&'a str),
Owned(CompactString),
}

impl Deref for CowStr<'_> {
type Target = str;

fn deref(&self) -> &str {
match self {
CowStr::Borrowed(s) => s,
CowStr::Owned(s) => s.as_str(),
}
}
}

fn replace_close_inline_script(raw: &str) -> CowStr {
let chars = raw.as_bytes();
let pattern_len = 8; // </script>

Expand All @@ -128,16 +145,16 @@ fn replace_close_inline_script(raw: &str) -> Cow<str> {
.peekable();

if matched_indexes.peek().is_none() {
return Cow::Borrowed(raw);
return CowStr::Borrowed(raw);
}

let mut result = String::from(raw);
let mut result = CompactString::new(raw);

for (offset, i) in matched_indexes.enumerate() {
result.insert(i + 1 + offset, '\\');
}

Cow::Owned(result)
CowStr::Owned(result)
}

static NEW_LINE_TPL_REGEX: Lazy<regex::Regex> = Lazy::new(|| regex::Regex::new(r"\\n|\n").unwrap());
Expand Down Expand Up @@ -684,10 +701,11 @@ where
let (quote_char, mut value) = get_quoted_utf16(&node.value, self.cfg.ascii_only, target);

if self.cfg.inline_script {
value = Cow::Owned(
value = CowStr::Owned(
replace_close_inline_script(&value)
.replace("\x3c!--", "\\x3c!--")
.replace("--\x3e", "--\\x3e"),
.replace("--\x3e", "--\\x3e")
.into(),
);
}

Expand Down Expand Up @@ -3965,13 +3983,13 @@ fn get_template_element_from_raw(s: &str, ascii_only: bool) -> String {
buf
}

fn get_ascii_only_ident(sym: &str, may_need_quote: bool, target: EsVersion) -> Cow<str> {
fn get_ascii_only_ident(sym: &str, may_need_quote: bool, target: EsVersion) -> CowStr {
if sym.is_ascii() {
return Cow::Borrowed(sym);
return CowStr::Borrowed(sym);
}

let mut first = true;
let mut buf = String::with_capacity(sym.len() + 8);
let mut buf = CompactString::with_capacity(sym.len() + 8);
let mut iter = sym.chars().peekable();
let mut need_quote = false;

Expand Down Expand Up @@ -4133,14 +4151,14 @@ fn get_ascii_only_ident(sym: &str, may_need_quote: bool, target: EsVersion) -> C
}

if need_quote {
Cow::Owned(format!("\"{}\"", buf))
CowStr::Owned(format_compact!("\"{}\"", buf))
} else {
Cow::Owned(buf)
CowStr::Owned(buf)
}
}

/// Returns `(quote_char, value)`
fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiChar, Cow<str>) {
fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiChar, CowStr) {
// Fast path: If the string is ASCII and doesn't need escaping, we can avoid
// allocation
if v.is_ascii() {
Expand Down Expand Up @@ -4172,7 +4190,7 @@ fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiChar,
if (quote_char == AsciiChar::Apostrophe && single_quote_count == 0)
|| (quote_char == AsciiChar::Quotation && double_quote_count == 0)
{
return (quote_char, Cow::Borrowed(v));
return (quote_char, CowStr::Borrowed(v));
}
}
}
Expand Down Expand Up @@ -4207,7 +4225,7 @@ fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiChar,

// Add 1 for each escaped quote
let capacity = v.len() + escape_count;
let mut buf = String::with_capacity(capacity);
let mut buf = CompactString::with_capacity(capacity);

let mut iter = v.chars().peekable();
while let Some(c) = iter.next() {
Expand Down Expand Up @@ -4354,7 +4372,7 @@ fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiChar,
}
}

(quote_char, Cow::Owned(buf))
(quote_char, CowStr::Owned(buf))
}

fn handle_invalid_unicodes(s: &str) -> Cow<str> {
Expand Down
2 changes: 1 addition & 1 deletion crates/swc_ecma_codegen/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ CONTENT\r

#[test]
fn test_get_quoted_utf16() {
fn combine((quote_char, s): (AsciiChar, Cow<str>)) -> String {
fn combine((quote_char, s): (AsciiChar, CowStr<'_>)) -> String {
let mut new = String::with_capacity(s.len() + 2);
new.push(quote_char.as_char());
new.push_str(s.as_ref());
Expand Down

0 comments on commit 7d7319f

Please sign in to comment.