Skip to content

Commit 2a720db

Browse files
committed
perf(transformer/tagged-template-transform): improve performance
1 parent 7c46a9e commit 2a720db

File tree

1 file changed

+36
-14
lines changed

1 file changed

+36
-14
lines changed

crates/oxc_transformer/src/plugins/tagged_template_transform.rs

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ use crate::{
5252
state::TransformState,
5353
};
5454

55+
const SCRIPT_TAG: &[u8; 8] = b"</script";
56+
const SCRIPT_TAG_LEN: usize = SCRIPT_TAG.len();
57+
5558
pub struct TaggedTemplateTransform<'a, 'ctx> {
5659
ctx: &'ctx TransformCtx<'a>,
5760
}
@@ -71,27 +74,27 @@ impl<'a, 'ctx> TaggedTemplateTransform<'a, 'ctx> {
7174

7275
/// Check if the template literal contains a `</script` tag; note it is case-insensitive.
7376
fn contains_closing_script_tag(quasi: &TemplateLiteral) -> bool {
74-
const SCRIPT_TAG: &[u8] = b"</script";
75-
7677
quasi.quasis.iter().any(|quasi| {
7778
let raw = &quasi.value.raw;
7879

7980
// The raw string must be at least as long as the script tag
80-
if raw.len() < SCRIPT_TAG.len() {
81+
if raw.len() < SCRIPT_TAG_LEN {
8182
return false;
8283
}
8384

8485
let raw_bytes = raw.as_bytes();
8586
// Get the bytes up to the last possible starting position of the script tag
86-
let max_remain_len = raw_bytes.len().saturating_sub(SCRIPT_TAG.len());
87-
let raw_bytes_iter = raw_bytes[..=max_remain_len].iter().copied().enumerate();
88-
for (idx, byte) in raw_bytes_iter {
89-
if byte == b'<'
90-
&& SCRIPT_TAG
91-
.iter()
92-
.zip(raw_bytes[idx..].iter())
93-
.all(|(a, b)| *a == b.to_ascii_lowercase())
94-
{
87+
88+
let max_start_pos = raw_bytes.len() - SCRIPT_TAG_LEN;
89+
for (i, byte) in raw_bytes[..=max_start_pos].iter().copied().enumerate() {
90+
// The first character must be a `<`
91+
if byte != b'<' {
92+
continue;
93+
}
94+
95+
// Check if this position contains "</script"
96+
let slice = &raw_bytes[i..i + SCRIPT_TAG_LEN];
97+
if is_script_close_tag(slice) {
9598
return true;
9699
}
97100
}
@@ -101,8 +104,6 @@ impl<'a, 'ctx> TaggedTemplateTransform<'a, 'ctx> {
101104
}
102105

103106
/// Transform a tagged template expression to use the [`Helper::TaggedTemplateLiteral`] helper function
104-
// `#[inline]` so that compiler can see `expr` should be a `TaggedTemplateExpression` and reduce redundant checks
105-
#[inline]
106107
fn transform_tagged_template(&self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
107108
if !matches!(expr, Expression::TaggedTemplateExpression(tagged) if Self::contains_closing_script_tag(&tagged.quasi))
108109
{
@@ -236,3 +237,24 @@ impl<'a, 'ctx> TaggedTemplateTransform<'a, 'ctx> {
236237
binding
237238
}
238239
}
240+
241+
/// Check if `slice` is `</script`, regardless of case.
242+
///
243+
/// `slice.len()` must be 8.
244+
//
245+
// NOTE: This function is copied from `oxc_codegen/src/str.rs`.
246+
//
247+
// `#[inline(always)]` so that compiler can see from caller that `slice.len() == 8`
248+
// and so `slice.try_into().unwrap()` cannot fail. This function is only 4 instructions.
249+
#[expect(clippy::inline_always)]
250+
#[inline(always)]
251+
pub fn is_script_close_tag(slice: &[u8]) -> bool {
252+
// Compiler condenses these operations to an 8-byte read, u64 AND, and u64 compare.
253+
// https://godbolt.org/z/K8q68WGn6
254+
let mut bytes: [u8; 8] = slice.try_into().unwrap();
255+
for byte in bytes.iter_mut().skip(2) {
256+
// `| 32` converts ASCII upper case letters to lower case.
257+
*byte |= 32;
258+
}
259+
bytes == *SCRIPT_TAG
260+
}

0 commit comments

Comments
 (0)