[Support] Optimize (.*) regex matches
authorNikita Popov <npopov@redhat.com>
Thu, 14 Apr 2022 09:49:35 +0000 (11:49 +0200)
committerNikita Popov <npopov@redhat.com>
Tue, 19 Apr 2022 07:55:21 +0000 (09:55 +0200)
If capturing groups are used, the regex matcher handles something
like `(.*)suffix` by first doing a maximal match of `.*`, trying to
match `suffix` afterward, and then reducing the maximal stop
position one by one until this finally succeeds. This makes the
match quadratic in the length of the line (with large constant factors).

This is particularly problematic because regexes of this form are
ubiquitous in FileCheck (something like `[[VAR:%.*]] = ...` falls
in this category), making FileCheck executions much slower than
they have any right to be.

This implements a very crude optimization that checks if suffix
starts with a fixed character, and steps back to the last occurrence
of that character, instead of stepping back by one character at a
time. This drops FileCheck time on
clang/test/CodeGen/RISCV/rvv-intrinsics/vloxseg_mask.c from
7.3 seconds to 2.7 seconds.

An obvious further improvement would be to check more than one
character (once again, this is particularly relevant for FileCheck,
because the next character is usually a space, which happens to
have many occurrences).

This should help with https://github.com/llvm/llvm-project/issues/54821.

llvm/lib/Support/regengine.inc

index 41787af..02680e2 100644 (file)
@@ -53,6 +53,7 @@
 #define        at      sat
 #define        match   smat
 #define        nope    snope
+#define step_back      sstep_back
 #endif
 #ifdef LNAMES
 #define        matcher lmatcher
@@ -65,6 +66,7 @@
 #define        at      lat
 #define        match   lmat
 #define        nope    lnope
+#define step_back      lstep_back
 #endif
 
 /* another structure passed up and down to avoid zillions of parameters */
@@ -288,6 +290,38 @@ matcher(struct re_guts *g, const char *string, size_t nmatch,
        return(0);
 }
 
+/* Step back from "stop" to a position where the strip startst..stopst might
+ * match. This can always conservatively return "stop - 1", but may return an
+ * earlier position if matches at later positions are impossible. */
+static const char *
+step_back(struct re_guts *g, const char *start, const char *stop, sopno startst,
+          sopno stopst)
+{
+       /* Always step back at least one character. */
+       assert(stop > start);
+       const char *res = stop - 1;
+
+       /* Check whether the strip startst..stropst starts with a fixed character,
+        * ignoring any closing parentheses. If not, return a conservative result. */
+       for (;;) {
+               if (startst >= stopst)
+                       return res;
+               if (OP(g->strip[startst]) != ORPAREN)
+                       break;
+               startst++;
+       }
+       if (OP(g->strip[startst]) != OCHAR)
+               return res;
+
+       /* Find the character that starts the following match. */
+       char ch = OPND(g->strip[startst]);
+       for (; res != start; --res) {
+               if (*res == ch)
+                       break;
+       }
+       return res;
+}
+
 /*
  - dissect - figure out what matched what, no back references
  */
@@ -358,7 +392,7 @@ dissect(struct match *m, const char *start, const char *stop, sopno startst,
                                if (tail == stop)
                                        break;          /* yes! */
                                /* no -- try a shorter match for this one */
-                               stp = rest - 1;
+                               stp = step_back(m->g, sp, rest, es, stopst);
                                assert(stp >= sp);      /* it did work */
                        }
                        ssub = ss + 1;
@@ -383,7 +417,7 @@ dissect(struct match *m, const char *start, const char *stop, sopno startst,
                                if (tail == stop)
                                        break;          /* yes! */
                                /* no -- try a shorter match for this one */
-                               stp = rest - 1;
+                               stp = step_back(m->g, sp, rest, es, stopst);
                                assert(stp >= sp);      /* it did work */
                        }
                        ssub = ss + 1;
@@ -1032,3 +1066,4 @@ pchar(int ch)
 #undef at
 #undef match
 #undef nope
+#undef step_back