Commit cad50495be for perl
commit cad50495be02261c206d127fa523482e1af84eab
Author: David Mitchell <davem@iabyn.nospamdeletethisbit.com>
Date: Sat Sep 12 09:29:25 2026 +0100
regex SLC: ignore inner patterns
There are two main issues with calling a separate sub-pattern via e.g.
(?1) or (??{ $subpattern_qr }) as regards the super-linear cache (SLC).
The first is that, in general, sub-patterns don't know their calling
environment - e.g. whether they're nested within another (...)* -
which means that some of the assumptions the regex compiler relied on
when compiling the inner regex may be voided. Also, sub-patterns will
have their own set of WHILEM ids which don't mean the same thing as
their caller, so can't be used on the same cache. The cache may also
need to be a different size.
The second issue is that WHILEM nodes to the left of of the sub-pattern
call can no longer rely on the rest-of-pattern always being the same
(technically (?1) can be relied on, but (??{...}) certainly can't).
Both those issues are currently addressed by setting
reginfo->poscache_maxiter back to zero at the start of, the end of, and
when stack-popping from executing, the sub-pattern. This has the effect
of resetting both the cache countdown, and of clearing/resetting the
whole cache once the countdown has finished.
Some time soon I intend to move to an implementation where there is a
separate countdown and cache per WHILEM node rather than a global
per-match countdown. This commit and the next one are intended to move
away from relying on setting poscache_maxiter back to zero, which is a
crude technique anyway. It also potentially has problems if the reset
countdown finishes and actually re-enables the cache while executing the
inner pattern.
This commit addresses the first issue, while the next commit will
address the second.
This commit makes use of the existing cur_eval variable, which becomes
non-NULL during a call to a separate sub-pattern. The rule is simple:
skip setting a cache entry if cur_eval is true.
This commit still keeps the poscache_maxiter = 0 mechanism in place for
now as that is still needed for the second issue, addressed in the next
commit.
diff --git a/pod/perlreguts.pod b/pod/perlreguts.pod
index 1f423a5bea..266097a426 100644
--- a/pod/perlreguts.pod
+++ b/pod/perlreguts.pod
@@ -1308,6 +1308,19 @@ heading 'RT #79152'.
=item *
+In a similar fashion, a nested sub-pattern called by e.g. C<(?1)> or
+C<(?{{ $inner_qr })> may have a variable rest-of-pattern depending on what
+surrounds it. Because of this, none of its C<WHILEM> nodes are treated as
+suitable candidates for the SLC while executing the inner pattern. This is
+enforced by checking C<cur_eval>, which is non-zero whenever an inner
+pattern is being executed.
+
+Another reason for not using the cache is that the inner pattern will have
+a different set of C<WHILEM> node ids, and accommodating them would
+complicate the code.
+
+=item *
+
There is currently a hard limit of 15 cache-participating C<WHILEM> nodes
per regex.
diff --git a/regexec.c b/regexec.c
index 8b124a4067..89aa71fd80 100644
--- a/regexec.c
+++ b/regexec.c
@@ -8733,14 +8733,6 @@ S_regmatch(pTHX_ regmatch_info *reginfo, char *startpos, regnode *prog)
* At this point we expect the stack context to be
* set up correctly */
- /* invalidate the S-L poscache. We're now executing a
- * different set of WHILEM ops (and their associated
- * indexes) against the same string, so the bits in the
- * cache are meaningless. Setting maxiter to zero forces
- * the cache to be invalidated and zeroed before reuse.
- * XXX This is too dramatic a measure. Ideally we should
- * save the old cache and restore when running the outer
- * pattern again */
reginfo->poscache_maxiter = 0;
/* the new regexp might have a different is_utf8_pat than we do */
@@ -9136,6 +9128,8 @@ NULL
}
if ( FLAGS(scan)
+ /* not running a (??{...}) or (?N) sub-pattern */
+ && !cur_eval
/* -1 => disable cache */
&& PL_re_superlinear_cache_delay != -1)
{
diff --git a/t/re/pat.t b/t/re/pat.t
index 7f9d1e3e19..e9dfab022b 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 => 1310; # Update this when adding/deleting tests.
+plan tests => 1312; # Update this when adding/deleting tests.
run_tests() unless caller;
@@ -2691,6 +2691,12 @@ SKIP:
=~ /^(a*?)(?!(a{6}|a{5})*$)/, 'SLC +ve');
is($+[1], "12", 'SLC +ve $+[1]');
+ # Test running a nested external sub-pattern
+
+ my $inner_qr = qr/(x(a|bc)*y)/;
+ ok("xayxay" =~ /^.*(??{ $inner_qr }){2,3}/, 'SLC nested qr');
+ is($&, "xayxay", 'SLC nested qr $&');
+
}
{