refactor(lexer): compile forward bracket scans separately for each CPU feature set (#28630)

Part of effort to move to runtime CPU feature detection.

Apply `cpu_versions!` to `lt_run_split`, `arrow_after_params`, `skip_trivia_fwd`, `list_closer`, `group_closer`, `past_raw`, `resolve_lists`, `kill`, `resolve_groups`, `skip_raw_literal`, `past_raw_string` and `raw_template_end` in `type_context/bytes.rs`, keeping their inline attributes. `skip_trivia_nl` takes `cpu`. It's `#[inline(always)]`, so it stays that way. `kill`, `past_raw_string` and `skip_trivia_nl` don't use `cpu` yet.

`coalesce`, `jsx_over_generic`, `gt_follower`, `type_list_legal` and `type_args_at` pass `cpu` to them. Level 3 is compiled only once, so `Walk::step_op` uses `BASELINE` for `lt_run_split`.

This changes codegen slightly. Register allocation changes in the cold `raw_template_end` on all platforms, and operand order in `skip_raw_literal` on aarch64.
This commit is contained in:
overlookmotel committed 2026-11-01 23:34:13 +00:00
1 parent 8600a91fbe
commit 22515cf000
5 files changed
+84 -51

No files matched your search

@@ -176,7 +176,7 @@ pub unsafe fn coalesce(
&lanes.disambiguate.brackets,
&lanes.disambiguate.closers,
);
if lt_run_split(&tokens, p) {
if lt_run_split(&tokens, p, cpu) {
cursor = munch_walk(src, n, st, opch, kind, p + 2, cpu);
continue;
}
@@ -9,6 +9,8 @@ use super::*;
impl Walk {
pub(super) fn step_op(&mut self, tokens: &Tokens, pos: usize, newline: bool) -> usize {
let cpu = BASELINE;
let c = tokens.src[pos];
let mut len = if is_op_char(c) || c == b'/' { tokens.op_len(pos) } else { 1 };
let c1 = tokens.src[pos + 1];
@@ -82,7 +84,7 @@ impl Walk {
&& tokens.ts
&& !self.operand_allowed()
&& !self.no_type_args
&& lt_run_split(tokens, pos) =>
&& lt_run_split(tokens, pos, cpu) =>
{
self.less_than(tokens, pos);
return pos + 1;
@@ -20,9 +20,12 @@ use crate::{
token::{OP_KIND_BASE, matches_tk, tk},
};
use crate::pipeline::disambiguate::{
FORWARD_SCAN_CAP,
common::{Closers, Open, Tokens, bits},
use crate::pipeline::{
cpu::{Cpu, apply, cpu_versions},
disambiguate::{
FORWARD_SCAN_CAP,
common::{Closers, Open, Tokens, bits},
},
};
/// Bytes the forward angle match reacts to. Everything else is skipped without touching a bitmap.
@@ -56,12 +59,13 @@ static GT_SCAN_DELIM: [bool; 256] = {
/// first `>`) and from `a << b > (c)` (no `=>` after the parameters). Every
/// reject path returns false, i.e. today's fused `<<`, so a wrong answer can
/// never split a real shift.
#[apply(cpu_versions)]
#[inline(never)]
pub fn lt_run_split(tokens: &Tokens, lt: usize) -> bool {
pub fn lt_run_split(tokens: &Tokens, lt: usize, cpu: impl Cpu) -> bool {
let Tokens { src, st, n, .. } = *tokens;
// A TypeParameter starts with an identifier (or the `const` modifier),
// which is what makes `<<=` cost two bytes to reject.
let head = skip_trivia_fwd(src, n, lt + 2);
let head = skip_trivia_fwd(src, n, lt + 2, cpu);
if head >= n {
return false;
}
@@ -73,34 +77,36 @@ pub fn lt_run_split(tokens: &Tokens, lt: usize) -> bool {
if !bits::get(st, lt + 1) {
return false;
}
let Some(gt) = list_closer(tokens, lt + 1) else {
let Some(gt) = list_closer(tokens, lt + 1, cpu) else {
return false;
};
let lp = skip_trivia_fwd(src, n, gt + 1);
if lp >= n || src[lp] != b'(' || !arrow_after_params(tokens, lp) {
let lp = skip_trivia_fwd(src, n, gt + 1, cpu);
if lp >= n || src[lp] != b'(' || !arrow_after_params(tokens, lp, cpu) {
return false;
}
// The outer list must close too, or this was a comparison against a
// generic arrow function and the parser would have backtracked as well.
list_closer(tokens, lt).is_some()
list_closer(tokens, lt, cpu).is_some()
}
pub(super) fn arrow_after_params(tokens: &Tokens, lp: usize) -> bool {
#[apply(cpu_versions)]
pub(super) fn arrow_after_params(tokens: &Tokens, lp: usize, cpu: impl Cpu) -> bool {
let Tokens { src, n, .. } = *tokens;
let Some(rp) = group_closer(tokens, lp) else {
let Some(rp) = group_closer(tokens, lp, cpu) else {
return false;
};
let ar = skip_trivia_fwd(src, n, rp + 1);
let ar = skip_trivia_fwd(src, n, rp + 1, cpu);
ar < n && src[ar] == b'=' && src[ar + 1] == b'>'
}
pub(super) fn skip_trivia_fwd(src: &[u8], n: usize, i: usize) -> usize {
skip_trivia_nl(src, n, i).0
#[apply(cpu_versions)]
pub(super) fn skip_trivia_fwd(src: &[u8], n: usize, i: usize, cpu: impl Cpu) -> usize {
skip_trivia_nl(src, n, i, cpu).0
}
/// The first non-trivia byte at or after i (or n), and whether a line terminator came first.
#[inline(always)]
pub(super) fn skip_trivia_nl(src: &[u8], n: usize, mut i: usize) -> (usize, bool) {
pub(super) fn skip_trivia_nl(src: &[u8], n: usize, mut i: usize, _cpu: impl Cpu) -> (usize, bool) {
let mut broke = false;
if i < n && !is_ws(src[i]) && src[i] != b'/' && src[i] < 0x80 {
return (i, false);
@@ -129,31 +135,34 @@ pub(super) fn skip_trivia_nl(src: &[u8], n: usize, mut i: usize) -> (usize, bool
(i, broke)
}
pub(super) fn list_closer(tokens: &Tokens, lt: usize) -> Option<usize> {
#[apply(cpu_versions)]
pub(super) fn list_closer(tokens: &Tokens, lt: usize, cpu: impl Cpu) -> Option<usize> {
match tokens.closers.get(lt) {
Some(r) => r.closer(),
None => resolve_lists(tokens, lt),
None => resolve_lists(tokens, lt, cpu),
}
}
fn group_closer(tokens: &Tokens, lp: usize) -> Option<usize> {
#[apply(cpu_versions)]
fn group_closer(tokens: &Tokens, lp: usize, cpu: impl Cpu) -> Option<usize> {
match tokens.closers.get(lp) {
Some(r) => r.closer(),
None => resolve_groups(tokens, lp),
None => resolve_groups(tokens, lp, cpu),
}
}
#[apply(cpu_versions)]
#[inline]
fn past_raw(tokens: &Tokens, lim: usize, i: usize) -> Option<usize> {
fn past_raw(tokens: &Tokens, lim: usize, i: usize, cpu: impl Cpu) -> Option<usize> {
let (src, kind) = (tokens.src, tokens.kind);
let j = skip_raw_literal(src, kind, lim, i);
let j = skip_raw_literal(src, kind, lim, i, cpu);
if j != i {
return (j < lim).then_some(j);
}
// Asked from the JSX carve, a template head may be carved while its tail is still
// raw text (its substitution `}` a punctuator): cross the literal whole.
if tokens.base_kind(i) == tk!(TemplateHead) {
let (close, end) = raw_template_end(src, lim, i);
let (close, end) = raw_template_end(src, lim, i, cpu);
if close < lim && tokens.base_kind(close) >= OP_KIND_BASE {
return (end <= lim).then_some(end);
}
@@ -162,8 +171,9 @@ fn past_raw(tokens: &Tokens, lim: usize, i: usize) -> Option<usize> {
}
/// The > closing the < at start, if the brackets inside balance and no ; on its level ends it.
#[apply(cpu_versions)]
#[inline(never)]
fn resolve_lists(tokens: &Tokens, start: usize) -> Option<usize> {
fn resolve_lists(tokens: &Tokens, start: usize, cpu: impl Cpu) -> Option<usize> {
let Tokens { src, st, opch, n, closers, .. } = *tokens;
let budget = start + FORWARD_SCAN_CAP;
let mut stack = closers.lists.borrow_mut();
@@ -173,7 +183,7 @@ fn resolve_lists(tokens: &Tokens, start: usize) -> Option<usize> {
let mut i = start + 1;
while i < n {
if bits::get(st, i) {
match past_raw(tokens, n, i) {
match past_raw(tokens, n, i, cpu) {
Some(j) if j != i => {
i = j;
continue;
@@ -217,23 +227,27 @@ fn resolve_lists(tokens: &Tokens, start: usize) -> Option<usize> {
b'(' => parens += 1,
b')' => {
parens -= 1;
kill(&mut stack, closers, i > budget, |e| e.parens > parens);
kill(&mut stack, closers, i > budget, |e| e.parens > parens, cpu);
}
b'[' => brackets += 1,
b']' => {
brackets -= 1;
kill(&mut stack, closers, i > budget, |e| e.brackets > brackets);
kill(&mut stack, closers, i > budget, |e| e.brackets > brackets, cpu);
}
b'{' => braces += 1,
b'}' => {
if !matches_tk!(tokens.base_kind(i), TemplateMiddle | TemplateTail) {
braces -= 1;
kill(&mut stack, closers, i > budget, |e| e.braces > braces);
kill(&mut stack, closers, i > budget, |e| e.braces > braces, cpu);
}
}
_ => kill(&mut stack, closers, i > budget, |e| {
e.parens == parens && e.brackets == brackets && e.braces == braces
}),
_ => kill(
&mut stack,
closers,
i > budget,
|e| e.parens == parens && e.brackets == brackets && e.braces == braces,
cpu,
),
}
if stack[0].dead > 0 {
// start died, and everything above it went with it.
@@ -251,7 +265,14 @@ fn resolve_lists(tokens: &Tokens, start: usize) -> Option<usize> {
}
/// Live openers' bracket counts only grow upward, so nothing below the first survivor is doomed.
fn kill(stack: &mut Vec<Open>, closers: &Closers, record: bool, doomed: impl Fn(&Open) -> bool) {
#[apply(cpu_versions)]
fn kill(
stack: &mut Vec<Open>,
closers: &Closers,
record: bool,
doomed: impl Fn(&Open) -> bool,
_cpu: impl Cpu,
) {
let mut dead = 0u32;
while let Some(top) = stack.last() {
if top.dead > 0 {
@@ -271,8 +292,9 @@ fn kill(stack: &mut Vec<Open>, closers: &Closers, record: bool, doomed: impl Fn(
}
}
#[apply(cpu_versions)]
#[inline(never)]
fn resolve_groups(tokens: &Tokens, start: usize) -> Option<usize> {
fn resolve_groups(tokens: &Tokens, start: usize, cpu: impl Cpu) -> Option<usize> {
let Tokens { src, st, n, closers, .. } = *tokens;
let budget = start + FORWARD_SCAN_CAP;
let mut open = closers.groups.borrow_mut();
@@ -281,7 +303,7 @@ fn resolve_groups(tokens: &Tokens, start: usize) -> Option<usize> {
let mut i = start + 1;
while i < n {
if bits::get(st, i) {
match past_raw(tokens, n, i) {
match past_raw(tokens, n, i, cpu) {
Some(j) if j != i => {
i = j;
continue;
@@ -317,8 +339,15 @@ fn resolve_groups(tokens: &Tokens, start: usize) -> Option<usize> {
/// If a comment, string or template carve has not reached yet starts at the token start `i`, the
/// position just past it; else `i`. Forward scans from before the carve cursor cross raw text;
/// carved literals have cleared interiors and are never re-entered.
#[apply(cpu_versions)]
#[inline]
pub(super) fn skip_raw_literal(src: &[u8], kind: &[u8], n: usize, i: usize) -> usize {
pub(super) fn skip_raw_literal(
src: &[u8],
kind: &[u8],
n: usize,
i: usize,
cpu: impl Cpu,
) -> usize {
if kind[i] < OP_KIND_BASE {
return i;
}
@@ -332,13 +361,14 @@ pub(super) fn skip_raw_literal(src: &[u8], kind: &[u8], n: usize, i: usize) -> u
}
_ => i,
},
b'"' | b'\'' => past_raw_string(src, n, i, c),
b'`' => raw_template_end(src, n, i).1.min(n),
b'"' | b'\'' => past_raw_string(src, n, i, c, cpu),
b'`' => raw_template_end(src, n, i, cpu).1.min(n),
_ => i,
}
}
fn past_raw_string(src: &[u8], lim: usize, i: usize, quote: u8) -> usize {
#[apply(cpu_versions)]
fn past_raw_string(src: &[u8], lim: usize, i: usize, quote: u8, _cpu: impl Cpu) -> usize {
let mut j = i + 1;
while j < lim {
let d = src[j];
@@ -358,7 +388,8 @@ fn past_raw_string(src: &[u8], lim: usize, i: usize, quote: u8) -> usize {
/// end (exclusive) of the whole literal, over raw bytes. `lim` when the `}` is not found before
/// `lim`, `lim + 1` when the literal does not end before it. Cold: a template inside a
/// speculated type-argument list.
pub(super) fn raw_template_end(src: &[u8], lim: usize, i: usize) -> (usize, usize) {
#[apply(cpu_versions)]
pub(super) fn raw_template_end(src: &[u8], lim: usize, i: usize, cpu: impl Cpu) -> (usize, usize) {
let mut last_close = lim;
let mut subs: Vec<u32> = Vec::new();
let mut in_text = true;
@@ -407,7 +438,7 @@ pub(super) fn raw_template_end(src: &[u8], lim: usize, i: usize) -> (usize, usiz
in_text = true;
j += 1;
}
b'"' | b'\'' => j = past_raw_string(src, lim, j, c),
b'"' | b'\'' => j = past_raw_string(src, lim, j, c, cpu),
b'/' => match src[j + 1] {
b'/' => j = line_terminator_after(src, lim, j + 2),
b'*' => {
@@ -22,13 +22,13 @@ pub fn jsx_over_generic(
walks: &mut Walks,
lt: usize,
lp: usize,
_cpu: impl Cpu,
cpu: impl Cpu,
) -> (bool, bool) {
let site = context::before(tokens, walks, lt);
if site.in_type || site.type_params {
return (false, false);
}
if arrow_after_params(tokens, lp) {
if arrow_after_params(tokens, lp, cpu) {
return (false, site.operand);
}
(true, false)
@@ -25,9 +25,9 @@ use super::bytes::{
};
#[apply(cpu_versions)]
fn gt_follower(tokens: &Tokens, i: usize, _cpu: impl Cpu) -> bool {
fn gt_follower(tokens: &Tokens, i: usize, cpu: impl Cpu) -> bool {
let (src, n) = (tokens.src, tokens.n);
let (i, broke) = skip_trivia_nl(src, n, i);
let (i, broke) = skip_trivia_nl(src, n, i, cpu);
if i >= n {
return true;
}
@@ -103,7 +103,7 @@ fn type_list_legal(tokens: &Tokens, lo: usize, hi: usize, cpu: impl Cpu) -> bool
}
// Text carve has not reached yet: a raw comment is trivia, a raw string or template is a
// literal type.
let j = skip_raw_literal(src, kind, hi, w);
let j = skip_raw_literal(src, kind, hi, w, cpu);
if j != w {
let c0 = src[w];
if c0 == b'/' {
@@ -164,7 +164,7 @@ fn type_list_legal(tokens: &Tokens, lo: usize, hi: usize, cpu: impl Cpu) -> bool
start = if start {
type_prefix_kind(kk)
|| (kk == tk!(KwAbstract)
&& tokens.ident_is(skip_trivia_fwd(src, hi, w + 8), b"new"))
&& tokens.ident_is(skip_trivia_fwd(src, hi, w + 8, cpu), b"new"))
} else {
matches_tk!(kk, KwExtends | KwIs | KwIn | KwAs)
};
@@ -181,7 +181,7 @@ fn type_list_legal(tokens: &Tokens, lo: usize, hi: usize, cpu: impl Cpu) -> bool
// text; its substitution `}` is still a punctuator. Read the literal as a whole then
// (a template type), instead of its tail as tokens.
if k == tk!(TemplateHead) {
let (close, end) = raw_template_end(src, hi, w);
let (close, end) = raw_template_end(src, hi, w, cpu);
if close < hi && tokens.base_kind(close) >= OP_KIND_BASE {
if end > hi {
return false;
@@ -303,7 +303,7 @@ fn type_list_legal(tokens: &Tokens, lo: usize, hi: usize, cpu: impl Cpu) -> bool
return false;
}
// An optional member, parameter or tuple element; else the ? of a conditional.
let after = src[skip_trivia_fwd(src, hi, w + 1)];
let after = src[skip_trivia_fwd(src, hi, w + 1, cpu)];
let optional = match open.last() {
Some(b'{') => true,
Some(b'(') => matches!(after, b':' | b',' | b')'),
@@ -330,7 +330,7 @@ fn type_list_legal(tokens: &Tokens, lo: usize, hi: usize, cpu: impl Cpu) -> bool
}
b'-' | b'+' => {
// A sign before a numeric literal, or a mapped type's readonly or ? modifier.
let a = skip_trivia_fwd(src, hi, w + 1);
let a = skip_trivia_fwd(src, hi, w + 1, cpu);
let number = c == b'-' && start && (is_digit(src[a]) || src[a] == b'.');
let modifier = open.last() == Some(&b'{')
&& (src[a] == b'?' || (start && tokens.ident_is(a, b"readonly")));
@@ -384,7 +384,7 @@ pub fn type_args_at(tokens: &Tokens, lt: usize, cpu: impl Cpu) -> bool {
if let Some(yes) = closers.get(lt).and_then(|r| r.args) {
return yes;
}
let yes = list_closer(tokens, lt).is_some_and(|gt| list_is_type_args(tokens, lt, gt, cpu));
let yes = list_closer(tokens, lt, cpu).is_some_and(|gt| list_is_type_args(tokens, lt, gt, cpu));
closers.set_args(lt, yes);
yes
}