perf(codegen): remove branch from print_space_before_identifier (#27842)

Perf optimization. Continuation of #27841.

That PR simplified `print_space_before_identifier`. Remove the branch on "was last byte ASCII?" by using a small lookup table.

This removes 2 or 3 instructions from this very hot path.

The previous implementation already used a lookup table for ASCII bytes. The table here is twice as large (128 bytes -> 256 bytes) but the 2nd half of it is only read when the source code contains an identifier ending in a non-ASCII character, which is extremely rare. So in 99.99% of cases, the cache usage of this larger table is no greater than the previous version.
This commit is contained in:
overlookmotel committed 2026-10-16 20:05:30 +00:00
1 parent eeff416a60
commit a6456b7506
2 files changed
+38 -13

No files matched your search

+37 -12
View File
@@ -47,6 +47,39 @@ pub use options::{CodegenOptions, CommentOptions, LegalComment};
// Re-export `IndentChar` from `oxc_data_structures`
pub use oxc_data_structures::code_buffer::IndentChar;
#[repr(C, align(128))]
struct Align128<T>(pub(crate) T);
/// Lookup table for whether a byte which is last byte of output when printing an identifier
/// is one that needs a space printed after it.
///
/// Used by [`print_space_before_identifier`].
///
/// Non-ASCII characters can appear in various places (e.g. in strings),
/// but in such cases an ASCII character (e.g. `"`) is always printed after them.
/// Only case in which last byte of output can be non-ASCII when printing an identifier
/// or keyword (when [`print_space_before_identifier`] is called) is when what was
/// last printed was an identifier ending with a non-ASCII character.
/// So non-ASCII must be an identifier, and a space is required.
///
/// Non-ASCII characters in identifiers are extremely rare in real-world code,
/// so only the first 128 bytes of this table will ever be read.
/// Aligned on 128, so occupies 2 x 128-byte cache lines on Apple Silicon,
/// and 2 pairs of 64-byte cache lines on x86.
///
/// [`print_space_before_identifier`]: Codegen::print_space_before_identifier
static IDENT_BYTES: Align128<[bool; 256]> = {
let mut slots = [true; 256];
let mut i = 0u8;
while i < 128 {
slots[i as usize] = is_identifier_part_ascii(i as char);
i += 1;
}
Align128(slots)
};
/// Output from [`Codegen::build`]
#[non_exhaustive]
pub struct CodegenReturn<'a> {
@@ -683,19 +716,11 @@ impl<'a> Codegen<'a> {
fn print_space_before_identifier(&mut self) {
let Some(byte) = self.last_byte() else { return };
if self.need_space_before_identifier != self.code.len() {
// Non-ASCII characters can appear in various places (e.g. in strings),
// but in such cases an ASCII character (e.g. `"`) is always printed after them.
// Only case in which last byte of output can be non-ASCII when printing an identifier
// or keyword (when this function is called) is when what was last printed was
// an identifier ending with a non-ASCII character.
// So non-ASCII must be an identifier, and a space is required.
if byte.is_ascii() && !is_identifier_part_ascii(byte as char) {
return;
}
let force_print_space = self.need_space_before_identifier == self.code.len();
let last_byte_is_ident = IDENT_BYTES.0[byte as usize];
if force_print_space || last_byte_is_ident {
self.print_hard_space();
}
self.print_hard_space();
}
#[inline]
+1 -1
View File
@@ -131,7 +131,7 @@ pub fn is_identifier_part(c: char) -> bool {
}
#[inline]
pub fn is_identifier_part_ascii(c: char) -> bool {
pub const fn is_identifier_part_ascii(c: char) -> bool {
ASCII_ID_FLAGS.0[c as usize] & ID_CONTINUE != 0
}