commit 7ada3f622cc608f4a05db5a87ea9d5e88623f39d
parent 209596b8a0efc2568c32863ca6990825240d26f1
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sat, 13 Jun 2026 11:42:56 -0700
perf(pp): replay no-## macro bodies by pointer, skip 2 copies/expansion (A3)
(cherry picked from commit 63b977a44b0ec25624f25c879d2a9d2941cce7a2)
Diffstat:
3 files changed, 78 insertions(+), 2 deletions(-)
diff --git a/lang/cpp/pp/pp.c b/lang/cpp/pp/pp.c
@@ -77,6 +77,15 @@ void src_next_raw_into(Pp* pp, Tok* out, HidesetId* hs_out, u8* src_kind_out) {
*out = s->toks[s->i];
if (hs_out) *hs_out = s->hs ? s->hs[s->i] : s->hs_uniform;
if (src_kind_out) *src_kind_out = SRC_BUF;
+ /* Pointer-replayed body: apply the per-invocation loc and (first token
+ * only) BOL/SPACE flags the old fresh-copy path baked in. */
+ if (s->has_loc_override) {
+ if (s->i == 0) {
+ out->flags = (u16)((out->flags & ~(TF_AT_BOL | TF_HAS_SPACE)) |
+ s->first_flags_or);
+ }
+ out->loc = s->loc_override;
+ }
++s->i;
return;
}
@@ -175,6 +184,28 @@ void push_buf_uniform(Pp* pp, Tok* toks, u32 n, HidesetId hs_uniform) {
src_push(pp, s);
}
+/* Push an immutable token buffer (a no-`##` macro body) for pointer-replay.
+ * The const-cast on `toks` is sound: replay is strictly read-only — the two
+ * read sites (src_next_raw_into, the pp_pull_into fast path) load each token by
+ * value and never write back through s->toks. The per-read loc/flags override
+ * (has_loc_override) reproduces the mutations the old fresh-copy path baked in,
+ * so the shared body is surfaced byte-for-byte without copying it. */
+void push_buf_replay(Pp* pp, const Tok* toks, u32 n, HidesetId hs_uniform,
+ SrcLoc loc_override, u16 first_flags_or) {
+ TokSrc s;
+ memset(&s, 0, sizeof(s));
+ s.kind = SRC_BUF;
+ s.toks = (Tok*)toks;
+ s.hs = NULL;
+ s.hs_uniform = hs_uniform;
+ s.has_loc_override = 1;
+ s.loc_override = loc_override;
+ s.first_flags_or = first_flags_or;
+ s.i = 0;
+ s.n = n;
+ src_push(pp, s);
+}
+
/* ============================================================
* Public streaming entries
* ============================================================ */
diff --git a/lang/cpp/pp/pp_expand.c b/lang/cpp/pp/pp_expand.c
@@ -261,6 +261,7 @@ void do_define(Pp* pp, const Tok* line, u32 n) {
m->body_len = body_n;
for (j = 0; j < body_n; ++j) {
Tok t = line[i + j];
+ if (t.kind == TOK_PP_PASTE) m->has_paste = 1;
if (m->is_func && t.kind == TOK_IDENT) {
u32 p;
for (p = 0; p < m->n_params; ++p) {
@@ -369,6 +370,20 @@ static void expand_object_macro(Pp* pp, const Macro* m, const Tok* invoke,
if (m->body_len == 0) {
return; /* placemarker: nothing to push */
}
+ /* No `##`: an object body has no params and no placemarkers, so the paste
+ * phase is a pure identity copy. Skip both copies — replay the immutable
+ * definition-time m->body by pointer, applying the per-invocation loc and
+ * first-token flag transfer at read time (push_buf_replay). Byte-identical
+ * to the slow path (same tokens, same loc, same first-token flags), with
+ * zero per-invocation token copies or body allocs. */
+ if (!m->has_paste) {
+ u16 ff = (u16)((m->body[0].flags & ~(TF_AT_BOL | TF_HAS_SPACE)) |
+ (invoke->flags & (TF_AT_BOL | TF_HAS_SPACE)));
+ hs = hs_add(pp, invoke_hs, m->name);
+ push_buf_replay(pp, m->body, m->body_len, hs, invoke->loc, ff);
+ return;
+ }
+
/* Run the body through the paste phase: object-like macros may use
* `##`. There are no parameters, so phase 1 reduces to a copy. */
tmp = arena_array(pp->xarena, Tok, m->body_len);
@@ -980,6 +995,16 @@ static void pp_pull_into(Pp* pp, Tok* out, int skip_nl) {
*out = s->toks[s->i];
hs = s->hs ? s->hs[s->i] : s->hs_uniform;
src_kind = SRC_BUF;
+ /* Pointer-replayed body: apply the per-invocation loc and (first token
+ * only) BOL/SPACE flags the old fresh-copy path baked in. Identical to
+ * the slow read site in src_next_raw_into so both paths agree. */
+ if (s->has_loc_override) {
+ if (s->i == 0) {
+ out->flags = (u16)((out->flags & ~(TF_AT_BOL | TF_HAS_SPACE)) |
+ s->first_flags_or);
+ }
+ out->loc = s->loc_override;
+ }
++s->i;
} else {
src_next_raw_into(pp, out, &hs, &src_kind);
diff --git a/lang/cpp/pp/pp_priv.h b/lang/cpp/pp/pp_priv.h
@@ -30,7 +30,11 @@ typedef struct Macro {
SrcLoc def_loc;
u8 is_func;
u8 is_variadic;
- u8 pad[2];
+ /* Pure cache of the body (NOT part of macro identity): 1 iff the body
+ * contains at least one TOK_PP_PASTE (`##`). Lets a no-`##` body be
+ * replayed by pointer without the per-invocation copy + paste pass. */
+ u8 has_paste;
+ u8 pad[1];
u32 n_params;
Sym* params; /* parameter names */
Tok* body; /* body tokens; TOK_PP_PARAM kind + v.punct=idx */
@@ -75,7 +79,16 @@ typedef struct TokSrc {
* (e.g. argument pre-expansion) explicitly pops the scope when done.
* This bounds expansion to a single argument's token stream. */
u8 scope_top;
- u8 pad[2];
+ /* Read-time loc/flags override for a pointer-replayed macro body (SRC_BUF).
+ * When has_loc_override, every token read from this buffer gets
+ * out->loc = loc_override, and the FIRST token (s->i == 0 at read) also has
+ * its BOL/SPACE bits replaced by first_flags_or. This reproduces the
+ * mutations expand_object_macro used to bake into a fresh body copy, so the
+ * immutable definition-time m->body can be replayed in place. 0 = no
+ * override (every push_buf/push_buf_uniform memsets the whole TokSrc). */
+ u8 has_loc_override;
+ u8 pad[1];
+ u16 first_flags_or;
/* SRC_LEX */
Lexer* lex;
/* SRC_BUF */
@@ -88,6 +101,8 @@ typedef struct TokSrc {
* (no alloc, no fill, one scalar instead of n copies). */
HidesetId* hs;
HidesetId hs_uniform;
+ /* Per-token loc applied when has_loc_override (see scope_top block). */
+ SrcLoc loc_override;
u32 i;
u32 n;
/* #line state (SRC_LEX only). line_delta is added to every emitted
@@ -415,6 +430,11 @@ void src_push(Pp* pp, TokSrc s);
void src_pop(Pp* pp);
void push_buf(Pp* pp, Tok* toks, HidesetId* hs, u32 n);
void push_buf_uniform(Pp* pp, Tok* toks, u32 n, HidesetId hs_uniform);
+/* Push an immutable, read-only token buffer (e.g. a no-`##` macro body) for
+ * pointer-replay. The buffer is never written through s->toks; per-read
+ * loc/flags overrides reproduce the mutations the old body-copy path baked in. */
+void push_buf_replay(Pp* pp, const Tok* toks, u32 n, HidesetId hs_uniform,
+ SrcLoc loc_override, u16 first_flags_or);
/* pp_next_raw is the mutual-recursion entry: expand_arg_to_eof calls it,
* and pp_next_raw drives directives and expansion. Declared non-static so