mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
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:
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
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user