master 7eb0fd17e36 1/2: Cache resolved tree-sitter languages (bug#81334)

Eli Zaretskii <[email protected]> Sat, 1 Aug 2026 05:47:34 -0400 (EDT)
Newsgroups gmane.emacs.diffs
Message-ID <[email protected]>
branch: master
commit 7eb0fd17e3648280ecb82100adaa51090c26ed25
Author: Rahul Martim Juliato <[email protected]>
Commit: Eli Zaretskii <[email protected]>

    Cache resolved tree-sitter languages (bug#81334)
    
    * src/treesit.c (treesit_lang_cache_get)
    (treesit_lang_cache_put): New functions implementing a
    per-session cache of resolved languages.
    (treesit_load_language): Consult and populate the cache, to
    avoid repeating the dlopen/dlsym and ABI-version check on every
    parser creation.
---
 src/treesit.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 56 insertions(+)

diff --git a/src/treesit.c b/src/treesit.c
index 73094bfbbdf..09a9006b876 100644
--- a/src/treesit.c
+++ b/src/treesit.c
@@ -632,6 +632,56 @@ struct treesit_loaded_lang
   const char *filename;
 };
 
+/* Cache of loaded languages, keyed by language symbols.  Resolving a
+   language does dlopen + dlsym plus an ABI-version check that creates a
+   throwaway TSParser; repeating that for every parser created is
+   expensive when a mode creates many embedded parsers (e.g. one
+   markdown-inline parser per block).  We don't support
+   unloading/reloading languages so we can cache loaded languages for
+   the entire session; in fact, we _want_ to cache the language object
+   so it stays stable for the whole session.  */
+struct treesit_lang_cache_entry
+{
+  /* Symbol for the cached langauge, never gc'ed.  */
+  Lisp_Object symbol;
+  /* The cached language object.  */
+  struct treesit_loaded_lang lang;
+};
+
+/* The cache, a monotonically growing array on the heap.  */
+static struct treesit_lang_cache_entry *treesit_lang_cache = NULL;
+/* Number of available slots in the allocated space.  */
+static ptrdiff_t treesit_lang_cache_size = 0;
+/* Number of existing cache entry in the array.  */
+static ptrdiff_t treesit_lang_cache_used = 0;
+
+/* Return the cached language object for LANGUAGE_SYMBOL, or {NULL,
+   NULL} if there is none.  */
+static struct treesit_loaded_lang
+treesit_lang_cache_get (Lisp_Object language_symbol)
+{
+  for (ptrdiff_t i = 0; i < treesit_lang_cache_used; i++)
+    if (EQ (treesit_lang_cache[i].symbol, language_symbol))
+      return treesit_lang_cache[i].lang;
+  return (struct treesit_loaded_lang) { NULL, NULL };
+}
+
+/* Cache LOADED_LANG keyed by LANGUAGE_SYMBOL.  */
+static void
+treesit_lang_cache_put (Lisp_Object language_symbol,
+			struct treesit_loaded_lang loaded_lang)
+{
+  if (treesit_lang_cache_used == treesit_lang_cache_size)
+    {
+      treesit_lang_cache
+	= xpalloc (treesit_lang_cache, &treesit_lang_cache_size, 1, -1,
+		   sizeof *treesit_lang_cache);
+    }
+  treesit_lang_cache[treesit_lang_cache_used].symbol = language_symbol;
+  treesit_lang_cache[treesit_lang_cache_used].lang = loaded_lang;
+  treesit_lang_cache_used++;
+}
+
 /* Translate a symbol treesit-<lang> to a C name treesit_<lang>.  */
 static void
 treesit_symbol_to_c_name (char *symbol_name)
@@ -762,6 +812,11 @@ static struct treesit_loaded_lang
 treesit_load_language (Lisp_Object language_symbol,
 		       Lisp_Object *signal_symbol, Lisp_Object *signal_data)
 {
+  struct treesit_loaded_lang cached
+    = treesit_lang_cache_get (language_symbol);
+  if (cached.lang != NULL)
+    return cached;
+
   Lisp_Object symbol_name = Fsymbol_name (language_symbol);
 
   CHECK_LIST (Vtreesit_extra_load_path);
@@ -886,6 +941,7 @@ treesit_load_language (Lisp_Object language_symbol,
   dynlib_addr ((void (*)) langfn, &loaded_lang.filename, &sym);
 
   loaded_lang.lang = lang;
+  treesit_lang_cache_put (language_symbol, loaded_lang);
   return loaded_lang;
 }