kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 39c384b15f4d072000fa24e54ed52c85de2d4c38
parent 7ada3f622cc608f4a05db5a87ea9d5e88623f39d
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 13 Jun 2026 11:53:37 -0700

perf(pp): reuse paste lexer + cache <paste> Sym, keep file_id ordering (A2)

(cherry picked from commit 4e255d5f78e6e4ee9988f7a3cb8845633879bb4c)

Diffstat:
Minclude/kit/source.h | 6++++++
Mlang/cpp/lex/lex.c | 54++++++++++++++++++++++++++++++++++++++++++++----------
Mlang/cpp/lex/lex.h | 7+++++++
Mlang/cpp/pp/pp.c | 3+++
Mlang/cpp/pp/pp_expand.c | 18++++++++++++++----
Mlang/cpp/pp/pp_priv.h | 8++++++++
Msrc/api/source.c | 6++++++
Msrc/core/core.h | 3+++
Msrc/core/source.c | 23++++++++++++++++++-----
9 files changed, 109 insertions(+), 19 deletions(-)

diff --git a/include/kit/source.h b/include/kit/source.h @@ -15,6 +15,12 @@ KIT_API KitStatus kit_source_add_file(KitCompiler*, const char* path, int system_header, uint32_t* file_id_out); KIT_API KitStatus kit_source_add_memory(KitCompiler*, KitSlice name, uint32_t* file_id_out); +/* As kit_source_add_memory but takes a pre-interned name Sym: the caller has + * already interned the name (e.g. a fixed "<paste>" reused across many opens), + * so this skips the redundant intern + strlen while drawing the same + * sequential file_id. */ +KIT_API KitStatus kit_source_add_memory_sym(KitCompiler*, KitSym name, + uint32_t* file_id_out); KIT_API KitStatus kit_source_add_builtin(KitCompiler*, KitSlice name, uint32_t* file_id_out); /* `system` is the spelling form (<...> vs "..."); `resolved_system` records diff --git a/lang/cpp/lex/lex.c b/lang/cpp/lex/lex.c @@ -320,27 +320,61 @@ static SrcLoc lex_here(const Lexer* l) { return loc; } -Lexer* lex_open_mem(Compiler* c, const char* name, const char* src, - size_t len) { - Heap* h = (Heap*)kit_compiler_context(c)->heap; - Lexer* l = (Lexer*)h->alloc(h, sizeof(*l), _Alignof(Lexer)); - if (!l) return NULL; - memset(l, 0, sizeof(*l)); - l->c = c; - l->heap = h; +/* Fold splices on (src, len) and reset the scanner cursor/line state to the + * start of the buffer. Shared by lex_open_mem_sym (fresh lexer) and + * lex_reset_mem (re-pointed lexer). Does NOT touch file_id, which the caller + * sets, nor the punct_sym cache, which stays valid across resets (the interned + * Sym for a spelling is stable). */ +static void lex_point_at(Lexer* l, const char* src, size_t len) { lex_fold_splices(l, src ? src : "", src ? len : 0); l->cur = l->src; l->end = l->src + l->len; l->line = 1; l->line_start = l->src; - l->file_id = 0; - (void)kit_source_add_memory(c, kit_slice_cstr(name), &l->file_id); + l->dstate = 0; if (l->splices) lex_catchup_splices(l); /* a splice folded at offset 0 -> line 2 */ l->at_bol = 1; l->had_space = 0; +} + +Lexer* lex_open_mem_sym(Compiler* c, Sym name, const char* src, size_t len) { + Heap* h = (Heap*)kit_compiler_context(c)->heap; + Lexer* l = (Lexer*)h->alloc(h, sizeof(*l), _Alignof(Lexer)); + if (!l) return NULL; + memset(l, 0, sizeof(*l)); + l->c = c; + l->heap = h; + l->file_id = 0; + (void)kit_source_add_memory_sym(c, name, &l->file_id); + lex_point_at(l, src, len); return l; } +Lexer* lex_open_mem(Compiler* c, const char* name, const char* src, + size_t len) { + Sym sym = kit_sym_intern(c, kit_slice_cstr(name)); + return lex_open_mem_sym(c, sym, src, len); +} + +/* Re-point an existing memory lexer at a fresh buffer for reuse across many + * tiny opens (e.g. `<paste>` buffers), avoiding a fresh alloc(sizeof Lexer) + + * lex_close free pair per open. Frees the previous folded buffer/splices (if + * any), re-registers a fresh sequential file_id (the count is byte-observable + * via DWARF, so it must still be bumped per open), then re-folds + resets the + * scanner. The borrowed (src, len) must outlive the next use of the lexer. */ +void lex_reset_mem(Lexer* l, Sym name, const char* src, size_t len) { + if (l->owns_src) l->heap->free(l->heap, (char*)l->src, l->len); + if (l->splices) + l->heap->free(l->heap, l->splices, l->nsplices * sizeof(u32)); + l->owns_src = 0; + l->src = NULL; + l->splices = NULL; + l->nsplices = 0; + l->file_id = 0; + (void)kit_source_add_memory_sym(l->c, name, &l->file_id); + lex_point_at(l, src, len); +} + void lex_close(Lexer* l) { if (!l) return; if (l->owns_src) l->heap->free(l->heap, (char*)l->src, l->len); diff --git a/lang/cpp/lex/lex.h b/lang/cpp/lex/lex.h @@ -89,6 +89,13 @@ typedef struct Lexer Lexer; * The borrowed (src, len) buffer must outlive the Lexer, which for a pushed * Lexer means outliving pp_free. */ Lexer* lex_open_mem(Compiler*, const char* name, const char* src, size_t len); +/* As lex_open_mem but takes a pre-interned name Sym, so a fixed name reused + * across many opens (e.g. "<paste>") is interned just once by the caller. */ +Lexer* lex_open_mem_sym(Compiler*, Sym name, const char* src, size_t len); +/* Re-point an existing memory lexer at a fresh buffer, reusing its allocation + * (no per-open alloc/free of the Lexer). Still registers a fresh sequential + * file_id per call, so the file_id order is identical to lex_open_mem_sym. */ +void lex_reset_mem(Lexer*, Sym name, const char* src, size_t len); void lex_close(Lexer*); /* Skip a leading `#!` script-interpreter ("shebang") line so an executable diff --git a/lang/cpp/pp/pp.c b/lang/cpp/pp/pp.c @@ -932,6 +932,9 @@ void pp_free(Pp* pp) { h = pp_heap(pp); /* Pop / close any remaining lex sources. */ while (pp->nsources) src_pop(pp); + /* The reused token-paste lexer is never pushed as a source, so close it + * directly here (it is lazily opened on the first `##`). */ + if (pp->paste_lex) lex_close(pp->paste_lex); pp_xfree(pp, pp->sources, sizeof(TokSrc) * pp->sources_cap); MacroTab_fini(&pp->macros); pp_xfree(pp, pp->hsets, sizeof(Hideset*) * pp->hsets_cap); diff --git a/lang/cpp/pp/pp_expand.c b/lang/cpp/pp/pp_expand.c @@ -730,19 +730,29 @@ static Tok paste_tokens(Pp* pp, Tok lhs, Tok rhs, SrcLoc loc) { buf[alen + blen] = '\n'; buf[alen + blen + 1] = 0; - lex = lex_open_mem(pp->c, "<paste>", buf, alen + blen + 1); + /* Intern the "<paste>" name once and reuse a single lexer across pastes: the + * lexer is re-pointed (not reallocated) per paste, but still draws a fresh + * sequential file_id so the file_id order matches a per-paste open exactly. + * t1.loc is overwritten below, so the paste file_id is dead for output. */ + if (!pp->paste_name_sym) { + pp->paste_name_sym = kit_sym_intern(pp->c, kit_slice_cstr("<paste>")); + } + if (pp->paste_lex) { + lex_reset_mem(pp->paste_lex, pp->paste_name_sym, buf, alen + blen + 1); + } else { + pp->paste_lex = + lex_open_mem_sym(pp->c, pp->paste_name_sym, buf, alen + blen + 1); + } + lex = pp->paste_lex; t1 = lex_next(lex); t2 = lex_next(lex); if (t1.kind == TOK_EOF) { /* Both empty (shouldn't reach here since we handled placemarkers). */ - lex_close(lex); return lhs; } if (t2.kind != TOK_NEWLINE && t2.kind != TOK_EOF) { - lex_close(lex); compiler_panic(pp->c, loc, "token pasting yields multiple tokens, invalid"); } - lex_close(lex); /* Inherit positional flags from LHS (it sat in the same slot). */ t1.flags = (u16)((t1.flags & ~(TF_AT_BOL | TF_HAS_SPACE)) | diff --git a/lang/cpp/pp/pp_priv.h b/lang/cpp/pp/pp_priv.h @@ -289,6 +289,14 @@ struct Pp { Sym val_date_str; Sym val_time_str; + /* Token-paste (`##`) scratch. paste_name_sym is the interned "<paste>" name, + * cached so each paste open doesn't re-intern the literal. paste_lex is a + * single reused memory lexer (re-pointed via lex_reset_mem per paste) so the + * thousands of tiny paste buffers don't each alloc/free a 288B Lexer. Both + * are lazily initialized on the first paste and torn down in pp_free. */ + Sym paste_name_sym; + Lexer* paste_lex; + /* Defined-operator handling during #if expansion. * * The first prepass in eval_if_expr replaces `defined X` / `defined diff --git a/src/api/source.c b/src/api/source.c @@ -18,6 +18,12 @@ KitStatus kit_source_add_memory(KitCompiler* c, KitSlice name, return source_add_memory(c->sources, name, file_id_out); } +KitStatus kit_source_add_memory_sym(KitCompiler* c, KitSym name, + uint32_t* file_id_out) { + if (!c || !file_id_out) return KIT_INVALID; + return source_add_memory_sym(c->sources, name, file_id_out); +} + KitStatus kit_source_add_builtin(KitCompiler* c, KitSlice name, uint32_t* file_id_out) { if (!c || !file_id_out) return KIT_INVALID; diff --git a/src/core/core.h b/src/core/core.h @@ -101,6 +101,9 @@ void source_free(SourceManager*); KitStatus source_add_file(SourceManager*, const char* path, int system_header, u32* id_out); KitStatus source_add_memory(SourceManager*, KitSlice name, u32* id_out); +/* Register an in-memory file from a pre-interned name Sym (skips the per-call + * intern + strlen; the file_id sequence is identical to source_add_memory). */ +KitStatus source_add_memory_sym(SourceManager*, Sym name, u32* id_out); KitStatus source_add_builtin(SourceManager*, KitSlice name, u32* id_out); KitStatus source_add_include(SourceManager*, u32 includer_file_id, u32 included_file_id, SrcLoc include_loc, diff --git a/src/core/source.c b/src/core/source.c @@ -91,13 +91,15 @@ void source_free(SourceManager* sm) { sm->heap->free(sm->heap, sm, sizeof(*sm)); } -static KitStatus file_register(SourceManager* sm, Slice name, - SourceFileKind kind, int system_header, - u32* id_out) { - Sym sym; +/* Register a file slot from a pre-interned name Sym (skips the intern). Draws + * the next sequential file_id from sm->nfiles++ exactly like file_register, so + * the file_id sequence -- which is byte-observable through DWARF line tables and + * diagnostics -- is unchanged. */ +static KitStatus file_register_sym(SourceManager* sm, Sym sym, + SourceFileKind kind, int system_header, + u32* id_out) { u32 id; if (files_grow(sm, sm->nfiles + 1)) return KIT_NOMEM; - sym = pool_intern_slice(sm->c->global, name); id = sm->nfiles++; memset(&sm->files[id], 0, sizeof(sm->files[id])); sm->files[id].info.id = id; @@ -109,6 +111,13 @@ static KitStatus file_register(SourceManager* sm, Slice name, return KIT_OK; } +static KitStatus file_register(SourceManager* sm, Slice name, + SourceFileKind kind, int system_header, + u32* id_out) { + Sym sym = pool_intern_slice(sm->c->global, name); + return file_register_sym(sm, sym, kind, system_header, id_out); +} + KitStatus source_add_file(SourceManager* sm, const char* path, int system_header, u32* id_out) { return file_register(sm, slice_from_cstr(path ? path : ""), SRC_FILE_REAL, @@ -119,6 +128,10 @@ KitStatus source_add_memory(SourceManager* sm, KitSlice name, u32* id_out) { return file_register(sm, name, SRC_FILE_MEMORY, 0, id_out); } +KitStatus source_add_memory_sym(SourceManager* sm, Sym name, u32* id_out) { + return file_register_sym(sm, name, SRC_FILE_MEMORY, 0, id_out); +} + KitStatus source_add_builtin(SourceManager* sm, KitSlice name, u32* id_out) { return file_register(sm, name, SRC_FILE_BUILTIN, 0, id_out); }