patch 9.2.0096: has() function is slow due to linear feature scan

Commit: 
https://github.com/vim/vim/commit/327e0e34c907abafbf356700705a3676c036ca65
Author: Yasuhiro Matsumoto <[email protected]>
Date:   Mon Mar 2 20:41:44 2026 +0000

    patch 9.2.0096: has() function is slow due to linear feature scan
    
    Problem:  The has() function is slow because it performs a linear scan
              of the feature list for every call.
    Solution: Move common runtime checks and the patch-version parser to the
              beginning of the f_has() function (Yasuhiro Matsumoto).
    
    closes: #19550
    
    Signed-off-by: Yasuhiro Matsumoto <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/src/evalfunc.c b/src/evalfunc.c
index 139d940cc..3c872af56 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -7760,214 +7760,217 @@ f_has(typval_T *argvars, typval_T *rettv)
        return;
 
     name = tv_get_string(&argvars[0]);
-    for (i = 0; has_list[i].name != NULL; ++i)
-       if (STRICMP(name, has_list[i].name) == 0)
+
+    // Fast-path: check features not in has_list[] first to avoid the full
+    // linear scan for very common queries like has('patch-...').
+    if (STRNICMP(name, "patch", 5) == 0)
+    {
+       x = TRUE;
+       if (name[5] == '-'
+               && STRLEN(name) >= 11
+               && (name[6] >= '1' && name[6] <= '9'))
        {
-           x = TRUE;
-           n = has_list[i].present;
-           break;
+           char        *end;
+           int major, minor;
+
+           // This works for patch-8.1.2, patch-9.0.3, patch-10.0.4, etc.
+           // Not for patch-9.10.5.
+           major = (int)strtoul((char *)name + 6, &end, 10);
+           if (*end == '.' && vim_isdigit(end[1])
+                   && end[2] == '.' && vim_isdigit(end[3]))
+           {
+               minor = atoi(end + 1);
+
+               // Expect "patch-9.9.01234".
+               n = (major < VIM_VERSION_MAJOR
+                    || (major == VIM_VERSION_MAJOR
+                        && (minor < VIM_VERSION_MINOR
+                            || (minor == VIM_VERSION_MINOR
+                                && has_patch(atoi(end + 3))))));
+           }
        }
+       else if (SAFE_isdigit(name[5]))
+           n = has_patch(atoi((char *)name + 5));
+    }
+    else if (STRICMP(name, "vim_starting") == 0)
+    {
+       x = TRUE;
+       n = (starting != 0);
+    }
+    else if (STRICMP(name, "ttyin") == 0)
+    {
+       x = TRUE;
+       n = mch_input_isatty();
+    }
+    else if (STRICMP(name, "ttyout") == 0)
+    {
+       x = TRUE;
+       n = stdout_isatty;
+    }
+    else if (STRICMP(name, "multi_byte_encoding") == 0)
+    {
+       x = TRUE;
+       n = has_mbyte;
+    }
+    else if (STRICMP(name, "gui_running") == 0)
+    {
+       x = TRUE;
+#ifdef FEAT_GUI
+       n = (gui.in_use || gui.starting);
+#endif
+    }
+    else if (STRICMP(name, "browse") == 0)
+    {
+       x = TRUE;
+#if defined(FEAT_GUI) && defined(FEAT_BROWSE)
+       n = gui.in_use; // gui_mch_browse() works when GUI is running
+#endif
+    }
+    else if (STRICMP(name, "syntax_items") == 0)
+    {
+       x = TRUE;
+#ifdef FEAT_SYN_HL
+       n = syntax_present(curwin);
+#endif
+    }
+    else if (STRICMP(name, "vcon") == 0)
+    {
+       x = TRUE;
+#ifdef FEAT_VTP
+       n = is_term_win32() && has_vtp_working();
+#endif
+    }
+    else if (STRICMP(name, "netbeans_enabled") == 0)
+    {
+       x = TRUE;
+#ifdef FEAT_NETBEANS_INTG
+       n = netbeans_active();
+#endif
+    }
+    else if (STRICMP(name, "mouse_gpm_enabled") == 0)
+    {
+       x = TRUE;
+#ifdef FEAT_MOUSE_GPM
+       n = gpm_enabled();
+#endif
+    }
+    else if (STRICMP(name, "conpty") == 0)
+    {
+       x = TRUE;
+#if defined(FEAT_TERMINAL) && defined(MSWIN)
+       n = use_conpty();
+#endif
+    }
+    else if (STRICMP(name, "clipboard_working") == 0)
+    {
+       x = TRUE;
+#ifdef FEAT_CLIPBOARD
+       n = clipmethod == CLIPMETHOD_PROVIDER ? TRUE : clip_star.available;
+#endif
+    }
+    else if (STRICMP(name, "unnamedplus") == 0)
+    {
+       x = TRUE;
+#ifdef FEAT_CLIPBOARD
+       // The + register is available when clipmethod is set to a provider,
+       // but becomes unavailable if on a platform that doesn't support it
+       // and clipmethod is "none".
+       // (Windows, MacOS).
+# if defined(FEAT_X11) || defined(FEAT_WAYLAND_CLIPBOARD)
+       n = TRUE;
+# elif defined(FEAT_EVAL)
+       if (clipmethod == CLIPMETHOD_PROVIDER)
+           n = TRUE;
+       else
+           n = FALSE;
+# else
+       n = FALSE;
+# endif
+#endif
+    }
 
-    // features also in has_list[] but sometimes enabled at runtime
-    if (x == TRUE && n == FALSE)
+    // Look up in has_list[] only if not already handled above.
+    if (x == FALSE)
     {
-       if (0)
+       for (i = 0; has_list[i].name != NULL; ++i)
+           if (STRICMP(name, has_list[i].name) == 0)
+           {
+               x = TRUE;
+               n = has_list[i].present;
+               break;
+           }
+
+       // features also in has_list[] but sometimes enabled at runtime
+       if (x == TRUE && n == FALSE)
        {
-           // intentionally empty
-       }
+           if (0)
+           {
+               // intentionally empty
+           }
 #ifdef VIMDLL
-       else if (STRICMP(name, "filterpipe") == 0)
-           n = gui.in_use || gui.starting;
+           else if (STRICMP(name, "filterpipe") == 0)
+               n = gui.in_use || gui.starting;
 #endif
 #if defined(USE_ICONV) && defined(DYNAMIC_ICONV)
-       else if (STRICMP(name, "iconv") == 0)
-           n = iconv_enabled(FALSE);
+           else if (STRICMP(name, "iconv") == 0)
+               n = iconv_enabled(FALSE);
 #endif
 #ifdef DYNAMIC_LUA
-       else if (STRICMP(name, "lua") == 0)
-           n = lua_enabled(FALSE);
+           else if (STRICMP(name, "lua") == 0)
+               n = lua_enabled(FALSE);
 #endif
 #ifdef DYNAMIC_MZSCHEME
-       else if (STRICMP(name, "mzscheme") == 0)
-           n = mzscheme_enabled(FALSE);
+           else if (STRICMP(name, "mzscheme") == 0)
+               n = mzscheme_enabled(FALSE);
 #endif
 #ifdef DYNAMIC_PERL
-       else if (STRICMP(name, "perl") == 0)
-           n = perl_enabled(FALSE);
+           else if (STRICMP(name, "perl") == 0)
+               n = perl_enabled(FALSE);
 #endif
 #ifdef DYNAMIC_PYTHON
-       else if (STRICMP(name, "python") == 0)
-           n = python_enabled(FALSE);
+           else if (STRICMP(name, "python") == 0)
+               n = python_enabled(FALSE);
 #endif
 #ifdef DYNAMIC_PYTHON3
-       else if (STRICMP(name, "python3") == 0)
-           n = python3_enabled(FALSE);
+           else if (STRICMP(name, "python3") == 0)
+               n = python3_enabled(FALSE);
 #endif
 #if defined(DYNAMIC_PYTHON) || defined(DYNAMIC_PYTHON3)
-       else if (STRICMP(name, "pythonx") == 0)
-       {
+           else if (STRICMP(name, "pythonx") == 0)
+           {
 # if defined(DYNAMIC_PYTHON) && defined(DYNAMIC_PYTHON3)
-           if (p_pyx == 0)
-               n = python3_enabled(FALSE) || python_enabled(FALSE);
-           else if (p_pyx == 3)
-               n = python3_enabled(FALSE);
-           else if (p_pyx == 2)
-               n = python_enabled(FALSE);
+               if (p_pyx == 0)
+                   n = python3_enabled(FALSE) || python_enabled(FALSE);
+               else if (p_pyx == 3)
+                   n = python3_enabled(FALSE);
+               else if (p_pyx == 2)
+                   n = python_enabled(FALSE);
 # elif defined(DYNAMIC_PYTHON)
-           n = python_enabled(FALSE);
+               n = python_enabled(FALSE);
 # elif defined(DYNAMIC_PYTHON3)
-           n = python3_enabled(FALSE);
+               n = python3_enabled(FALSE);
 # endif
-       }
+           }
 #endif
 #ifdef DYNAMIC_RUBY
-       else if (STRICMP(name, "ruby") == 0)
-           n = ruby_enabled(FALSE);
+           else if (STRICMP(name, "ruby") == 0)
+               n = ruby_enabled(FALSE);
 #endif
 #ifdef DYNAMIC_TCL
-       else if (STRICMP(name, "tcl") == 0)
-           n = tcl_enabled(FALSE);
+           else if (STRICMP(name, "tcl") == 0)
+               n = tcl_enabled(FALSE);
 #endif
 #ifdef DYNAMIC_SODIUM
-       else if (STRICMP(name, "sodium") == 0)
-           n = sodium_enabled(FALSE);
+           else if (STRICMP(name, "sodium") == 0)
+               n = sodium_enabled(FALSE);
 #endif
 #if defined(FEAT_TERMINAL) && defined(MSWIN)
-       else if (STRICMP(name, "terminal") == 0)
-           n = terminal_enabled();
+           else if (STRICMP(name, "terminal") == 0)
+               n = terminal_enabled();
 #endif
 #ifdef DYNAMIC_GPM
-       else if (STRICMP(name, "mouse_gpm") == 0)
-           n = gpm_available();
-#endif
-    }
-
-    // features not in has_list[]
-    if (x == FALSE)
-    {
-       if (STRNICMP(name, "patch", 5) == 0)
-       {
-           x = TRUE;
-           if (name[5] == '-'
-                   && STRLEN(name) >= 11
-                   && (name[6] >= '1' && name[6] <= '9'))
-           {
-               char    *end;
-               int     major, minor;
-
-               // This works for patch-8.1.2, patch-9.0.3, patch-10.0.4, etc.
-               // Not for patch-9.10.5.
-               major = (int)strtoul((char *)name + 6, &end, 10);
-               if (*end == '.' && vim_isdigit(end[1])
-                       && end[2] == '.' && vim_isdigit(end[3]))
-               {
-                   minor = atoi(end + 1);
-
-                   // Expect "patch-9.9.01234".
-                   n = (major < VIM_VERSION_MAJOR
-                        || (major == VIM_VERSION_MAJOR
-                            && (minor < VIM_VERSION_MINOR
-                                || (minor == VIM_VERSION_MINOR
-                                    && has_patch(atoi(end + 3))))));
-               }
-           }
-           else if (SAFE_isdigit(name[5]))
-               n = has_patch(atoi((char *)name + 5));
-       }
-       else if (STRICMP(name, "vim_starting") == 0)
-       {
-           x = TRUE;
-           n = (starting != 0);
-       }
-       else if (STRICMP(name, "ttyin") == 0)
-       {
-           x = TRUE;
-           n = mch_input_isatty();
-       }
-       else if (STRICMP(name, "ttyout") == 0)
-       {
-           x = TRUE;
-           n = stdout_isatty;
-       }
-       else if (STRICMP(name, "multi_byte_encoding") == 0)
-       {
-           x = TRUE;
-           n = has_mbyte;
-       }
-       else if (STRICMP(name, "gui_running") == 0)
-       {
-           x = TRUE;
-#ifdef FEAT_GUI
-           n = (gui.in_use || gui.starting);
-#endif
-       }
-       else if (STRICMP(name, "browse") == 0)
-       {
-           x = TRUE;
-#if defined(FEAT_GUI) && defined(FEAT_BROWSE)
-           n = gui.in_use;     // gui_mch_browse() works when GUI is running
-#endif
-       }
-       else if (STRICMP(name, "syntax_items") == 0)
-       {
-           x = TRUE;
-#ifdef FEAT_SYN_HL
-           n = syntax_present(curwin);
-#endif
-       }
-       else if (STRICMP(name, "vcon") == 0)
-       {
-           x = TRUE;
-#ifdef FEAT_VTP
-           n = is_term_win32() && has_vtp_working();
-#endif
-       }
-       else if (STRICMP(name, "netbeans_enabled") == 0)
-       {
-           x = TRUE;
-#ifdef FEAT_NETBEANS_INTG
-           n = netbeans_active();
-#endif
-       }
-       else if (STRICMP(name, "mouse_gpm_enabled") == 0)
-       {
-           x = TRUE;
-#ifdef FEAT_MOUSE_GPM
-           n = gpm_enabled();
-#endif
-       }
-       else if (STRICMP(name, "conpty") == 0)
-       {
-           x = TRUE;
-#if defined(FEAT_TERMINAL) && defined(MSWIN)
-           n = use_conpty();
-#endif
-       }
-       else if (STRICMP(name, "clipboard_working") == 0)
-       {
-           x = TRUE;
-#ifdef FEAT_CLIPBOARD
-           n = clipmethod == CLIPMETHOD_PROVIDER ? TRUE : clip_star.available;
-#endif
-       }
-       else if (STRICMP(name, "unnamedplus") == 0)
-       {
-           x = TRUE;
-#ifdef FEAT_CLIPBOARD
-           // The + register is available when clipmethod is set to a provider,
-           // but becomes unavailable if on a platform that doesn't support it
-           // and clipmethod is "none".
-           // (Windows, MacOS).
-# if defined(FEAT_X11) || defined(FEAT_WAYLAND_CLIPBOARD)
-           n = TRUE;
-# elif defined(FEAT_EVAL)
-           if (clipmethod == CLIPMETHOD_PROVIDER)
-               n = TRUE;
-           else
-               n = FALSE;
-# else
-           n = FALSE;
-# endif
+           else if (STRICMP(name, "mouse_gpm") == 0)
+               n = gpm_available();
 #endif
        }
     }
diff --git a/src/version.c b/src/version.c
index dde1211ce..31e444098 100644
--- a/src/version.c
+++ b/src/version.c
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    96,
 /**/
     95,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1vxAN1-003gRc-Dp%40256bit.org.

Raspunde prin e-mail lui