Commit d0d5e9a44f for perl
commit d0d5e9a44f8054f91b0c334a76caa0af34a3423b
Author: Yves Orton <demerphq@gmail.com>
Date: Sun Oct 11 00:26:13 2026 +0200
regex: use the repeat scanner for all BMH shifts
Once we have checked a whole UV of octets which cannot start a match, an
identical UV cannot contain a start either. The BMH shift does not
affect that, so we need not restrict the repeat scanner to shifts of
zero or one.
For /walrus|cat/, A has a shift of three. We used to keep looking up
that shift on a long run of A's instead of scanning in UV sized chunks.
On Linux x86_64, nonthreaded GCC 13.3 -O2 builds, three scans of 200
million A's followed by cat drop from 0.489s to 0.048s. There is a cost
when input does not repeat: 32 scans of 1 MiB of random uppercase ASCII
for /abcdefgh|ijklmnop/ rise from 0.0101s to 0.0302s. Establishing the
rejected window checks octets which BMH could otherwise skip.
diff --git a/regexec.c b/regexec.c
index 81307fa6ac..9f3d285326 100644
--- a/regexec.c
+++ b/regexec.c
@@ -3582,19 +3582,17 @@ S_find_byclass(pTHX_ regexp * prog, const regnode *c, char *s,
distance = bmh_shift[uc[bmh_minlen - 1]];
}
- /* For shifts zero and one, a start-map miss
- * proves independently that no literal starts
- * here. The helper checks each later start
+ /* A start-map miss proves that no literal
+ * starts here, regardless of the BMH shift.
+ * The helper checks each later start
* until it has a full rejected word. Equal
* following words repeat those rejected
* octets, so they cannot contain a start.
* UTF-8 continuation octets cannot be starts
- * either. This proof does not depend on the
- * BMH shift. We use it for small shifts to
- * avoid walking repeated input one octet at
- * a time. */
- if (distance <= 1
- && !(start_map[*uc] & start_mask))
+ * either. The checked window therefore lets
+ * us skip equal windows without consulting
+ * their BMH anchors. */
+ if (!(start_map[*uc] & start_mask))
{
/* The helper consumes at least the
* rejected octet at uc. */
diff --git a/t/re/pat.t b/t/re/pat.t
index a4f353e070..376061ca89 100644
--- a/t/re/pat.t
+++ b/t/re/pat.t
@@ -28,7 +28,7 @@ skip_all_without_unicode_tables();
my $has_locales = locales_enabled('LC_CTYPE');
my $utf8_locale = find_utf8_ctype_locale();
-plan tests => 1786; # Update this when adding/deleting tests.
+plan tests => 2218; # Update this when adding/deleting tests.
run_tests() unless caller;
@@ -384,6 +384,48 @@ sub run_tests {
}
}
+ # These rejected octets have the maximum BMH shift. The start
+ # map independently permits repeated-window scanning, including
+ # when the shift reaches the 255-octet cap.
+ my $long_first = 'a' . ('Z' x 254);
+ my $long_second = 'b' . ('Y' x 254);
+ my $long_re = qr/\Q$long_first\E|\Q$long_second\E/;
+
+ for my $case ([3, qr/walrus|cat/, 'cat'],
+ [8, qr/abcdefgh|ijklmnop/, 'ijklmnop'],
+ [255, $long_re, $long_second])
+ {
+ my ($shift, $re, $word) = @$case;
+
+ for my $unit ('Q', 'QR', 'QQRR', 'QQQQRRRR') {
+ for my $offset (0 .. 7) {
+ my $run = ('X' x $offset) . ($unit x 320);
+ my $target = $run . $word;
+ utf8::upgrade($target) if $encoding eq 'UTF-8';
+
+ ok($target =~ /$re/ && $-[0] == length($run)
+ && $+[0] == length($target),
+ "AC $encoding shift $shift repeated '$unit' finds the match at alignment $offset");
+
+ utf8::upgrade($run) if $encoding eq 'UTF-8';
+
+ ok($run !~ /$re/,
+ "AC $encoding shift $shift repeated '$unit' rejects a miss at alignment $offset");
+ }
+ }
+
+ # Differing windows must return to ordinary scanning without
+ # losing a viable start inside the next window.
+ for my $offset (0 .. 7) {
+ my $run = ('Q' x 320) . ('R' x $offset);
+ my $target = $run . $word;
+ utf8::upgrade($target) if $encoding eq 'UTF-8';
+
+ ok($target =~ /$re/ && $-[0] == length($run),
+ "AC $encoding shift $shift finds a start in a differing window at offset $offset");
+ }
+ }
+
# Include runs shorter than a UV and tails shorter than a UV.
# The helper must not read beyond the buffer or miss its last start.
for my $run_length (0 .. 15) {