commit a4ff4c6cd46cad0d2e356bd9c5389677c183e84f
parent 8797d6e49ef2c4981803664a667297c39953fcae
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 11 Jun 2026 11:45:21 -0700
perf(parse): classify each decl-spec token's keyword identity exactly once
parse_decl_specs re-decided keyword-ness per candidate: ~26 is_kw probes
plus a trailing ident_kw, every token. Add classify_kw() (alias-aware,
mirrors is_kw's match — classify_kw(p,t)==k iff is_kw(p,t,k)) and call it
once at the top of the loop into a local CKw; the dispatch chain and the
typedef-name fall-through compare against it. One classification per
token instead of up to 27.
Byte-identical (only the deliberate HW-SHA link UUID differs from golden;
all -O0/-O1 .o, -g, -S, -E, diagnostics, splice battery unchanged), and
gated on the keyword/alias suites (test-parse-ok 3880/0, test-parse-err
128/0, test-cg-api, test-toy). Measured marginal: type-decl +7.4%,
global-decl +1.7% (best-of-15 vs the same kit without this change).
Diffstat:
2 files changed, 54 insertions(+), 27 deletions(-)
diff --git a/lang/c/parse/parse_priv.h b/lang/c/parse/parse_priv.h
@@ -465,6 +465,27 @@ static inline int is_kw(const Parser* p, const Tok* t, CKw k) {
return 0;
}
+/* Classify a token's keyword identity ONCE: the canonical keyword (via kw_map)
+ * or, for the GNU alias spellings, the keyword they alias to. Returns the same
+ * CKw that `is_kw(p, t, k)` matches on — i.e. `classify_kw(p, t) == k` is exactly
+ * `is_kw(p, t, k)` for every k — so a caller that tests a token against many
+ * keywords can classify it once and compare, instead of re-deciding keyword-ness
+ * per candidate. MUST stay in lockstep with is_kw's alias set above. */
+static inline CKw classify_kw(const Parser* p, const Tok* t) {
+ Sym s;
+ CKw k;
+ if (t->kind != TOK_IDENT) return KW_NONE;
+ s = t->v.ident;
+ k = ident_kw_inline(p, s); /* canonical spellings (kw_map) */
+ if (k != KW_NONE) return k;
+ if (s == p->sym_alignof_alias) return KW_ALIGNOF;
+ if (s == p->sym_asm_alias) return KW_BUILTIN_ASM;
+ if (s == p->sym_inline_alias || s == p->sym_inline_alias2) return KW_INLINE;
+ if (s == p->sym_restrict_alias || s == p->sym_restrict_alias2) return KW_RESTRICT;
+ if (s == p->sym_thread_alias) return KW_THREAD_LOCAL;
+ return KW_NONE;
+}
+
static inline int c_type_is_scalar(const Type* ty) {
return type_is_arith(ty) || type_is_ptr(ty);
}
diff --git a/lang/c/parse/parse_type.c b/lang/c/parse/parse_type.c
@@ -614,6 +614,10 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) {
loc = tok_loc(&p->cur);
for (;;) {
Tok t = p->cur;
+ /* Classify the token's keyword identity exactly once per iteration; the
+ * decl-spec dispatch below compares against this instead of re-deciding
+ * keyword-ness per candidate (was ~26 is_kw probes/token). */
+ CKw tkw = classify_kw(p, &t);
if (starts_attr(p)) {
Attr* a = parse_attribute_spec_list(p);
if (a) {
@@ -625,8 +629,8 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) {
seen = 1;
continue;
}
- if (is_kw(p, &t, KW_STRUCT) || is_kw(p, &t, KW_UNION)) {
- TypeKind kind = is_kw(p, &t, KW_STRUCT) ? TY_STRUCT : TY_UNION;
+ if (tkw == KW_STRUCT || tkw == KW_UNION) {
+ TypeKind kind = tkw == KW_STRUCT ? TY_STRUCT : TY_UNION;
Attr* anon_attrs = NULL;
if (tagged_ty || acc.saw_explicit_type) {
perr(p, "conflicting type specifiers (struct/union mixed)");
@@ -638,7 +642,7 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) {
seen = 1;
continue;
}
- if (is_kw(p, &t, KW_ENUM)) {
+ if (tkw == KW_ENUM) {
Attr* anon_attrs = NULL;
if (tagged_ty || acc.saw_explicit_type) {
perr(p, "conflicting type specifiers (enum mixed)");
@@ -650,47 +654,47 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) {
seen = 1;
continue;
}
- if (is_kw(p, &t, KW_VOID)) {
+ if (tkw == KW_VOID) {
acc.saw_void = 1;
acc.saw_explicit_type = 1;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_CHAR)) {
+ } else if (tkw == KW_CHAR) {
acc.saw_char = 1;
acc.saw_explicit_type = 1;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_INT)) {
+ } else if (tkw == KW_INT) {
acc.saw_int = 1;
acc.saw_explicit_type = 1;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_SHORT)) {
+ } else if (tkw == KW_SHORT) {
acc.saw_short = 1;
acc.saw_explicit_type = 1;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_LONG)) {
+ } else if (tkw == KW_LONG) {
acc.long_count++;
acc.saw_explicit_type = 1;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_SIGNED)) {
+ } else if (tkw == KW_SIGNED) {
acc.saw_signed = 1;
acc.saw_explicit_type = 1;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_UNSIGNED)) {
+ } else if (tkw == KW_UNSIGNED) {
acc.saw_unsigned = 1;
acc.saw_explicit_type = 1;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_BOOL)) {
+ } else if (tkw == KW_BOOL) {
acc.saw_bool = 1;
acc.saw_explicit_type = 1;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_FLOAT) || is_kw(p, &t, KW_FLOAT16)) {
+ } else if (tkw == KW_FLOAT || tkw == KW_FLOAT16) {
/* _Float16 is intentionally aliased to 32-bit float (a deliberate
* approximation; see test/parse/cases/float16_01_decl, which asserts
* sizeof(_Float16) == sizeof(float)). Real 16-bit semantics would be a
@@ -699,7 +703,7 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) {
acc.saw_explicit_type = 1;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_DOUBLE)) {
+ } else if (tkw == KW_DOUBLE) {
acc.saw_double = 1;
acc.saw_explicit_type = 1;
advance(p);
@@ -721,33 +725,33 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) {
acc.saw_explicit_type = 1;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_STATIC)) {
+ } else if (tkw == KW_STATIC) {
if (storage_seen) perr(p, "multiple storage-class specifiers");
storage_seen = 1;
out->storage_explicit = 1;
out->storage = DS_STATIC;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_EXTERN)) {
+ } else if (tkw == KW_EXTERN) {
if (storage_seen) perr(p, "multiple storage-class specifiers");
storage_seen = 1;
out->storage_explicit = 1;
out->storage = DS_EXTERN;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_CONST)) {
+ } else if (tkw == KW_CONST) {
out->quals |= Q_CONST;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_VOLATILE)) {
+ } else if (tkw == KW_VOLATILE) {
out->quals |= Q_VOLATILE;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_RESTRICT)) {
+ } else if (tkw == KW_RESTRICT) {
out->quals |= Q_RESTRICT;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_ATOMIC)) {
+ } else if (tkw == KW_ATOMIC) {
Tok n = peek1(p);
if (is_punct(&n, '(')) {
const Type* inner;
@@ -767,14 +771,14 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) {
out->quals |= Q_ATOMIC;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_TYPEDEF)) {
+ } else if (tkw == KW_TYPEDEF) {
if (storage_seen) perr(p, "multiple storage-class specifiers");
storage_seen = 1;
out->storage_explicit = 1;
out->storage = DS_TYPEDEF;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_ALIGNAS)) {
+ } else if (tkw == KW_ALIGNAS) {
u32 a = 0;
advance(p); /* `_Alignas` */
expect_punct(p, '(', "'(' after _Alignas");
@@ -792,26 +796,26 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) {
expect_punct(p, ')', "')' after _Alignas argument");
if (a > out->align) out->align = a;
seen = 1;
- } else if (is_kw(p, &t, KW_INLINE)) {
+ } else if (tkw == KW_INLINE) {
out->flags |= DF_INLINE;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_THREAD_LOCAL)) {
+ } else if (tkw == KW_THREAD_LOCAL) {
out->flags |= DF_THREAD;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_NORETURN)) {
+ } else if (tkw == KW_NORETURN) {
out->flags |= DF_NORETURN;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_REGISTER)) {
+ } else if (tkw == KW_REGISTER) {
if (storage_seen) perr(p, "multiple storage-class specifiers");
storage_seen = 1;
out->storage_explicit = 1;
out->storage = DS_REGISTER;
advance(p);
seen = 1;
- } else if (is_kw(p, &t, KW_AUTO)) {
+ } else if (tkw == KW_AUTO) {
if (storage_seen) perr(p, "multiple storage-class specifiers");
storage_seen = 1;
out->storage_explicit = 1;
@@ -819,7 +823,9 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) {
advance(p);
seen = 1;
} else if (!acc.saw_explicit_type && !tagged_ty && t.kind == TOK_IDENT &&
- ident_kw(p, t.v.ident) == KW_NONE) {
+ tkw == KW_NONE) {
+ /* tkw == KW_NONE here is exactly ident_kw(t)==KW_NONE: this is the
+ * terminal else-if, so every keyword/alias branch above already failed. */
if (t.v.ident == p->sym_b_va_list) {
if (!p->type_b_va_list)
p->type_b_va_list = c_abi_va_list_type(p->abi, p->pool);