commit 209596b8a0efc2568c32863ca6990825240d26f1
parent b50e883e3b179503f8c2340695356869e6b7e602
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sat, 13 Jun 2026 11:36:50 -0700
perf(pp): skip no-op arena_reset when xarena is pristine (A5)
(cherry picked from commit 0ba6525dd53dd5f07f075cddb8c5555755f1b96e)
Diffstat:
5 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/include/kit/support/arena.h b/include/kit/support/arena.h
@@ -14,6 +14,8 @@ typedef struct KitArena KitArena;
KIT_API KitStatus kit_arena_new(KitHeap*, size_t block_size, KitArena** out);
KIT_API void kit_arena_free(KitArena*);
KIT_API void kit_arena_reset(KitArena*);
+/* True when reset would be a no-op (nothing allocated since last reset). */
+KIT_API int kit_arena_is_empty(const KitArena*);
KIT_API void* kit_arena_alloc(KitArena*, size_t size, size_t align);
KIT_API void* kit_arena_zalloc(KitArena*, size_t size, size_t align);
KIT_API char* kit_arena_strdup(KitArena*, const char* s, size_t len);
diff --git a/lang/cpp/pp/pp_expand.c b/lang/cpp/pp/pp_expand.c
@@ -959,11 +959,15 @@ static void pp_pull_into(Pp* pp, Tok* out, int skip_nl) {
* reset site; it never fires mid-expansion (a SRC_BUF, incl. the
* scope_top arg-prescan buffer, is on top then) nor during raw argument
* collection (which uses src_next_raw, not this function). Skipped inside
- * #if expansion, whose bounded condition keeps its scratch live. */
+ * #if expansion, whose bounded condition keeps its scratch live. The reset
+ * is further skipped when the xarena is already at its baseline (a plain
+ * lexer run that allocated no scratch): kit_arena_is_empty is the pristine
+ * guard, so the dead reset on plain-token runs is elided -- byte-identical
+ * since resetting an already-pristine arena does nothing. */
if (!pp->in_if_expansion &&
(pp->nsources == 0 ||
pp->sources[pp->nsources - 1].kind == SRC_LEX)) {
- kit_arena_reset(pp->xarena);
+ if (!kit_arena_is_empty(pp->xarena)) kit_arena_reset(pp->xarena);
}
/* Fast path: top source is a non-exhausted SRC_BUF (the dominant
* macro-replay case). Pull the token, hideset, and kind inline so the
diff --git a/src/api/support_arena.c b/src/api/support_arena.c
@@ -32,6 +32,10 @@ void kit_arena_reset(KitArena* a) {
if (a) arena_reset(&a->inner);
}
+int kit_arena_is_empty(const KitArena* a) {
+ return a ? arena_is_empty(&a->inner) : 1;
+}
+
void* kit_arena_alloc(KitArena* a, size_t size, size_t align) {
return a ? arena_alloc(&a->inner, size, align) : NULL;
}
diff --git a/src/core/arena.c b/src/core/arena.c
@@ -47,12 +47,23 @@ void arena_fini(Arena* a) {
a->end = NULL;
}
+int arena_is_empty(const Arena* a) {
+ /* The pristine predicate: reset would be a no-op. Either nothing is
+ * allocated (head==NULL), or the head is the lone, non-oversize block with
+ * cur still at its base -- exactly the state arena_reset re-establishes. */
+ if (!a->head) return 1;
+ return a->head->next == NULL && a->head->cap <= a->block_size &&
+ a->cur == a->head->data;
+}
+
void arena_reset(Arena* a) {
/* Free every block past the head, then reuse the head as the O(1) reset
* baseline -- unless it is an oversize dedicated block (cap > block_size).
* The head is the most-recently allocated block, so a one-off large
* allocation just before reset would otherwise become a permanently
- * inflated baseline; release it and let the next alloc size a fresh one. */
+ * inflated baseline; release it and let the next alloc size a fresh one.
+ * Skip entirely when the arena is already at its reset baseline. */
+ if (arena_is_empty(a)) return;
if (a->head) {
ArenaBlock* b = a->head->next;
while (b) {
diff --git a/src/core/arena.h b/src/core/arena.h
@@ -17,6 +17,7 @@ struct Arena {
void arena_init(Arena*, Heap*, size_t block_size);
void arena_fini(Arena*);
void arena_reset(Arena*);
+int arena_is_empty(const Arena*);
void* arena_alloc(Arena*, size_t size, size_t align);
void* arena_zalloc(Arena*, size_t size, size_t align); /* zeroed; NULL on OOM */
char* arena_strdup(Arena*, const char* s, size_t len);