In perl.git, the branch blead has been updated

<http://perl5.git.perl.org/perl.git/commitdiff/1703c1fc996f9d5943ebada0759bc5212289ae8e?hp=2d2826733b14efb7509c9c0c28d27bca6f31d681>

- Log -----------------------------------------------------------------
commit 1703c1fc996f9d5943ebada0759bc5212289ae8e
Merge: 2d28267 3de645a
Author: David Mitchell <da...@iabyn.com>
Date:   Sat Sep 8 15:42:56 2012 +0100

    [MERGE] only copy bits of regex match string
    
    When making a copy of the string being matched against (so that $1, $&
    et al continue to show the correct value even if the original string is
    subsequently modified), only copy that substring of the original string
    needed for the capture variables, rather than copying the whole string.
    
    This is a big win for code like
    
        $&;
        $_ = 'x' x 1_000_000;
        1 while /(.)/;
    
    Also, when pessimizing if the code contains $`, $& or $', record
    the presence of each variable separately, so that the determination of the
    substring range is based on each variable separately. So performance-wise,
    
       $&; /x/
    
    is now roughly equivalent to
    
       /(x)/
    
    whereas previously it was like
    
       /^(.*)(x)(.*)$/
    
    and
    
       $&; $'; /x/
    
    is now roughly equivalent to
    
       /(x)(.*)$/
    
    etc.
    
    Finally, this code (when not in the presence of $& etc)
    
        $_ = 'x' x 1_000_000;
        1 while /(.)/;
    
    used to skip the buffer copy for performance reasons, but suffered from $1
    etc changing if the original string changed. That's now been fixed too.

commit 3de645a82921698b4886d748e3a5a5ed98752f42
Author: David Mitchell <da...@iabyn.com>
Date:   Fri Sep 7 13:32:11 2012 +0100

    fix a bug in handling $+[0] and unicode
    
    The code to decide what substring of a pattern target to copy for the
    sake of $1, $& etc, would, in the absence of $&, only copy the minimum
    range needed to cover $1,$2,...., which might be a shorter range than
    what $& covers. This is fine most of the time, but, when calculating
    $+[0] on a unicode string, it needs a copy of the whole part of the string
    covered by $&, since it needs to convert the byte offest into a char
    offset.
    So to fix this, always copy as a minimum, the $& range.
    I suppose we could be more clever about this: detect the presence
    of @+ in the code, only do it for UTF8 etc; but this is simple
    and non-fragile.

M       regexec.c
M       t/re/re_tests

commit 6f1854a1fe246f8633ccd4d455563cb4210ceb39
Author: David Mitchell <da...@iabyn.com>
Date:   Sat Sep 1 11:43:53 2012 +0100

    m// and s///; don't copy TEMP/AMAGIC strings
    
    Currently pp_match and pp_subst make a copy of the match string if it's
    SvTEMP(), and in the case of pp_match, also if it's SvAMAGIC().
    
    This is no longer necessary, as the code will always copy the string
    anyway if its actually needed after the match, i.e. if it detects the
    presence of $1, $& or //p etc. Until a few commits ago, this wasn't the
    case for pp_match: it would sometimes skip copying even in the presence of
    $1 et al for efficiency reasons. Now that that's fixed, we can remove the
    SvTEMP() and SvAMAGIC() tests.
    
    As to why pp_subst did the SvTEMP test, I don't know: but removing it
    didn't make any tests fail!

M       pp_hot.c

commit fbfb1899dd79253696b441cc1c4968a1057c2574
Author: David Mitchell <da...@iabyn.com>
Date:   Sat Sep 1 11:23:58 2012 +0100

    tidy up patten match copying code
    
    (no functional changes).
    
    1. Remove some dead code from pp_split; it's protected by an assert
    that it could never be called.
    
    2. Simplify the flags settings for the call to CALLREGEXEC() in
    pp_substcont: on subsequent matches we always set REXEC_NOT_FIRST,
    which forces the regex engine not to copy anyway, so passing the
    REXEC_COPY_STR is pointless, as is the conditional code to set it.
    
    3. (whitespace change): split a conditional expression over 2 lines
    for easier reading.

M       pp.c
M       pp_ctl.c
M       pp_hot.c

commit a41aa44d9dc4a3ba586d871754bd11137bdc37a2
Author: David Mitchell <da...@iabyn.com>
Date:   Fri Aug 24 16:17:47 2012 +0100

    stop $foo =~ /(bar)/g skipping copy
    
    Normally in the presence of captures, a successful regex execution
    makes a copy of the matched string, so that $1 et al give the right
    value even if the original string is changed; i.e.
    
        $foo =~ /(123)/g;
        $foo = "bar";
        is("$1", "123");
    
    Until now that test would fail, because perl used to skip the copy for
    the scalar /(...)/g case (but not the C<$&; //g> case). This was to
    avoid a huge slowdown in code like the following:
    
        $x = 'x' x 1_000_000;
        1 while $x =~ /(.)/g;
    
    which would otherwise end up copying a 1Mb string a million times.
    
    Now that (with the last commit but one) we copy only the required
    substring of the original string (a 1-byte substring in the above
    example), we can remove this fast-but-incorrect hack.

M       pp_hot.c
M       t/re/pat_advanced.t
M       t/re/pat_psycho.t

commit 9414be0160a1f343d4ae75ec161fec610da39c84
Author: David Mitchell <da...@iabyn.com>
Date:   Fri Aug 24 15:49:21 2012 +0100

    rationalise t/re/pat_psycho.t
    
    Do some cleanup of this file, without changing its functionality.
    
    Once upon a time, the psycho tests were scattered throughout a single
    pat.t file, before being moved into their own file. Now that they're all
    in a single file, make the $PERL_SKIP_PSYCHO_TEST test a single "skip_all"
    test at the beginning of the file, rather than testing it separately in
    each code block.
    
    Also, make some of the test descriptions more useful, and add a bit of
    debugging output.

M       t/re/pat_psycho.t

commit 6502e08109cd003b2cdf39bc94ef35e52203240b
Author: David Mitchell <da...@iabyn.com>
Date:   Thu Jul 26 16:04:09 2012 +0100

    Don't copy all of the match string buffer
    
    When a pattern matches, and that pattern contains captures (or $`, $&, $'
    or /p are present), a copy is made of the whole original string, so
    that $1 et al continue to hold the correct value even if the original
    string is subsequently modified. This can have severe performance
    penalties; for example, this code causes a 1Mb buffer to be allocated,
    copied and freed a million times:
    
        $&;
        $x = 'x' x 1_000_000;
        1 while $x =~ /(.)/g;
    
    This commit changes this so that, where possible, only the needed
    substring of the original string is copied: in the above case, only a
    1-byte buffer is copied each time. Also, it now reuses or reallocs the
    buffer, rather than freeing and mallocing each time.
    
    Now that PL_sawampersand is a 3-bit flag indicating separately whether
    $`, $& and $' have been seen, they each contribute only their own
    individual penalty; which ones have been seen will limit the extent to
    which we can avoid copying the whole buffer.
    
    Note that the above code *without* the $& is not currently slow, but only
    because the copying is artificially disabled to avoid the performance hit.
    The next but one commit will remove that hack, meaning that it will still
    be fast, but will now be correct in the presence of a modified original
    string.
    
    We achieve this by by adding suboffset and subcoffset fields to the
    existing subbeg and sublen fields of a regex, to indicate how many bytes
    and characters have been skipped from the logical start of the string till
    the physical start of the buffer. To avoid copying stuff at the end, we
    just reduce sublen. For example, in this:
    
        "abcdefgh" =~ /(c)d/
    
    subbeg points to a malloced buffer containing "c\0"; sublen == 1,
    and suboffset == 2 (as does subcoffset).
    
    while if $& has been seen,
    
    subbeg points to a malloced buffer containing "cd\0"; sublen == 2,
    and suboffset == 2.
    
    If in addition $' has been seen, then
    
    subbeg points to a malloced buffer containing "cdefgh\0"; sublen == 6,
    and suboffset == 2.
    
    The regex engine won't do this by default; there are two new flag bits,
    REXEC_COPY_SKIP_PRE and REXEC_COPY_SKIP_POST, which in conjunction with
    REXEC_COPY_STR, request that the engine skip the start or end of the
    buffer (it will still copy in the presence of the relevant $`, $&, $',
    /p).
    
    Only pp_match has been enhanced to use these extra flags; substitution
    can't easily benefit, since the usual action of s///g is to copy the
    whole string first time round, then perform subsequent matching iterations
    against the copy, without further copying. So you still need to copy most
    of the buffer.

M       dump.c
M       ext/Devel-Peek/t/Peek.t
M       mg.c
M       pod/perlreapi.pod
M       pp.c
M       pp_ctl.c
M       pp_hot.c
M       regcomp.c
M       regexec.c
M       regexp.h
M       t/porting/known_pod_issues.dat
M       t/re/re_tests

commit 2c7b5d7698f52b86acffe19a7ec15e85c99337fe
Author: David Mitchell <da...@iabyn.com>
Date:   Thu Jul 26 15:35:39 2012 +0100

    Separate handling of ${^PREMATCH} from $` etc
    
    Currently the handling of getting the value, length etc of ${^PREMATCH}
    etc is identical to that of $` etc.
    
    Handle them separately, by adding RX_BUFF_IDX_CARET_PREMATCH etc
    constants to the existing RX_BUFF_IDX_PREMATCH set.
    
    This allows, when retrieving them, to always return undef if the current
    match didn't use //p. Previously the result depended on stuff such
    as whether the (non-//p) pattern included captures or not.
    
    The documentation for ${^PREMATCH} etc states that it's only guaranteed to
    return a defined value when the last pattern was //p.
    
    As well as making things more consistent, this is a necessary
    prerequisite for the following commit, which may not always copy the
    whole string during a non-//p match.

M       mg.c
M       regcomp.c
M       regexp.h
M       t/re/reg_pmod.t

commit ac0ba89f3ee4e5469c43dc0a34b548a9aa415f98
Author: David Mitchell <da...@iabyn.com>
Date:   Fri Jun 22 16:26:08 2012 +0100

    regexec_flags(): simplify length calculation
    
    The code to calculate the length of the string to copy was
    
        PL_regeol - startpos + (stringarg - strbeg);
    
    This is a hangover from the original (perl 3) regexp implementation
    that under //i, copied and folded the original buffer: so startpos might
    not equal stringarg. These days it always is (except under a match failure
    with (*COMMIT), and the code we're interested is only executed on success).
    
    So simplify to just PL_regeol - strbeg.

M       regexec.c

commit d3b97530399d61590a1500b52bdba553d657bda5
Author: David Mitchell <da...@iabyn.com>
Date:   Fri Jun 22 12:36:03 2012 +0100

    PL_sawampersand: use 3 bit flags rather than bool
    
    Set a separate flag for each of $`, $& and $'.
    It still works fine in boolean context.
    
    This will allow us to have more refined control over what parts
    of a match string to copy (we currently copy the whole string).

M       gv.c
M       intrpvar.h
M       perl.c
M       perl.h

commit 8fd1a95029bf0ff87a3064dec7d6645f40359f2c
Author: David Mitchell <da...@iabyn.com>
Date:   Wed Jun 20 14:17:05 2012 +0100

    document args to regexec_flags and API
    
    Document in the API, and clarify in the source code, what the arguments
    to Perl_regexec_flags are.
    
    NB: this info is based on code inspection, not any real knowledge on my
    part.

M       pod/perlreapi.pod
M       regexec.c
-----------------------------------------------------------------------

Summary of changes:
 dump.c                         |    4 +
 ext/Devel-Peek/t/Peek.t        |    2 +
 gv.c                           |   31 +++++++--
 intrpvar.h                     |    2 +-
 mg.c                           |   74 +++++++++++-----------
 perl.c                         |    7 +-
 perl.h                         |    6 ++
 pod/perlreapi.pod              |   66 ++++++++++++++++++-
 pp.c                           |   10 +--
 pp_ctl.c                       |   17 +++---
 pp_hot.c                       |   39 ++++++++----
 regcomp.c                      |   83 +++++++++++++++++-------
 regexec.c                      |  137 ++++++++++++++++++++++++++++++++++-----
 regexp.h                       |   34 +++++++++-
 t/porting/known_pod_issues.dat |    2 +-
 t/re/pat_advanced.t            |    1 -
 t/re/pat_psycho.t              |   65 ++++++++++++++++---
 t/re/re_tests                  |    6 ++
 t/re/reg_pmod.t                |   58 +++++++++++-------
 19 files changed, 485 insertions(+), 159 deletions(-)

diff --git a/dump.c b/dump.c
index ada6ae9..46893d6 100644
--- a/dump.c
+++ b/dump.c
@@ -2056,6 +2056,10 @@ Perl_do_sv_dump(pTHX_ I32 level, PerlIO *file, SV *sv, 
I32 nest, I32 maxnest, bo
                                (UV)(r->pre_prefix));
            Perl_dump_indent(aTHX_ level, file, "  SUBLEN = %"IVdf"\n",
                                (IV)(r->sublen));
+           Perl_dump_indent(aTHX_ level, file, "  SUBOFFSET = %"IVdf"\n",
+                               (IV)(r->suboffset));
+           Perl_dump_indent(aTHX_ level, file, "  SUBCOFFSET = %"IVdf"\n",
+                               (IV)(r->subcoffset));
            if (r->subbeg)
                Perl_dump_indent(aTHX_ level, file, "  SUBBEG = 0x%"UVxf" %s\n",
                            PTR2UV(r->subbeg),
diff --git a/ext/Devel-Peek/t/Peek.t b/ext/Devel-Peek/t/Peek.t
index 6913d59..164e2ff 100644
--- a/ext/Devel-Peek/t/Peek.t
+++ b/ext/Devel-Peek/t/Peek.t
@@ -350,6 +350,8 @@ do_test('reference to regexp',
     GOFS = 0
     PRE_PREFIX = 4
     SUBLEN = 0
+    SUBOFFSET = 0
+    SUBCOFFSET = 0
     SUBBEG = 0x0
     ENGINE = $ADDR
     MOTHER_RE = $ADDR
diff --git a/gv.c b/gv.c
index c6e474e..e29f2fd 100644
--- a/gv.c
+++ b/gv.c
@@ -1655,12 +1655,23 @@ Perl_gv_fetchpvn_flags(pTHX_ const char *nambeg, STRLEN 
full_len, I32 flags,
                    require_tie_mod(gv, name, 
newSVpvs("Tie::Hash::NamedCapture"), "TIEHASH", 0);
              }
              if (sv_type==SVt_PV || sv_type==SVt_PVGV) {
-              if (*name == '[')
-               require_tie_mod(gv,name,newSVpvs("arybase"),"FETCH",0);
-              else if (*name == '&' || *name == '`' || *name == '\'') {
-               PL_sawampersand = TRUE;
-               (void)GvSVn(gv);
-              }
+                switch (*name) {
+               case '[':
+                   require_tie_mod(gv,name,newSVpvs("arybase"),"FETCH",0);
+                    break;
+               case '`':
+                   PL_sawampersand |= SAWAMPERSAND_LEFT;
+                    (void)GvSVn(gv);
+                    break;
+               case '&':
+                   PL_sawampersand |= SAWAMPERSAND_MIDDLE;
+                    (void)GvSVn(gv);
+                    break;
+               case '\'':
+                   PL_sawampersand |= SAWAMPERSAND_RIGHT;
+                    (void)GvSVn(gv);
+                    break;
+                }
              }
            }
            else if (len == 3 && sv_type == SVt_PVAV
@@ -1866,7 +1877,13 @@ Perl_gv_fetchpvn_flags(pTHX_ const char *nambeg, STRLEN 
full_len, I32 flags,
                sv_type == SVt_PVCV ||
                sv_type == SVt_PVFM ||
                sv_type == SVt_PVIO
-               )) { PL_sawampersand = TRUE; }
+               )) { PL_sawampersand |=
+                        (*name == '`')
+                            ? SAWAMPERSAND_LEFT
+                            : (*name == '&')
+                                ? SAWAMPERSAND_MIDDLE
+                                : SAWAMPERSAND_RIGHT;
+                }
            goto magicalize;
 
        case ':':               /* $: */
diff --git a/intrpvar.h b/intrpvar.h
index f57fa7d..94b7425 100644
--- a/intrpvar.h
+++ b/intrpvar.h
@@ -292,7 +292,7 @@ The C variable which corresponds to Perl's $^W warning 
variable.
 */
 
 PERLVAR(I, dowarn,     U8)
-PERLVAR(I, sawampersand, bool)         /* must save all match strings */
+PERLVAR(I, sawampersand, U8)           /* must save all match strings */
 PERLVAR(I, unsafe,     bool)
 PERLVAR(I, exit_flags, U8)             /* was exit() unexpected, etc. */
 
diff --git a/mg.c b/mg.c
index 1f6d062..26cabbe 100644
--- a/mg.c
+++ b/mg.c
@@ -637,6 +637,8 @@ Perl_magic_regdata_cnt(pTHX_ SV *sv, MAGIC *mg)
     return (U32)-1;
 }
 
+/* @-, @+ */
+
 int
 Perl_magic_regdatum_get(pTHX_ SV *sv, MAGIC *mg)
 {
@@ -665,7 +667,9 @@ Perl_magic_regdatum_get(pTHX_ SV *sv, MAGIC *mg)
                    if (i > 0 && RX_MATCH_UTF8(rx)) {
                        const char * const b = RX_SUBBEG(rx);
                        if (b)
-                           i = utf8_length((U8*)b, (U8*)(b+i));
+                           i = RX_SUBCOFFSET(rx) +
+                                    utf8_length((U8*)b,
+                                        (U8*)(b-RX_SUBOFFSET(rx)+i));
                    }
 
                    sv_setiv(sv, i);
@@ -675,6 +679,8 @@ Perl_magic_regdatum_get(pTHX_ SV *sv, MAGIC *mg)
     return 0;
 }
 
+/* @-, @+ */
+
 int
 Perl_magic_regdatum_set(pTHX_ SV *sv, MAGIC *mg)
 {
@@ -913,9 +919,12 @@ Perl_magic_get(pTHX_ SV *sv, MAGIC *mg)
        if (nextchar == '\0') {       /* ^P */
            sv_setiv(sv, (IV)PL_perldb);
        } else if (strEQ(remaining, "REMATCH")) { /* $^PREMATCH */
-           goto do_prematch_fetch;
+
+            paren = RX_BUFF_IDX_CARET_PREMATCH;
+           goto do_numbuf_fetch;
        } else if (strEQ(remaining, "OSTMATCH")) { /* $^POSTMATCH */
-           goto do_postmatch_fetch;
+            paren = RX_BUFF_IDX_CARET_POSTMATCH;
+           goto do_numbuf_fetch;
        }
        break;
     case '\023':               /* ^S */
@@ -978,55 +987,46 @@ Perl_magic_get(pTHX_ SV *sv, MAGIC *mg)
        break;
     case '\015': /* $^MATCH */
        if (strEQ(remaining, "ATCH")) {
+            paren = RX_BUFF_IDX_CARET_FULLMATCH;
+           goto do_numbuf_fetch;
+        }
+
     case '1': case '2': case '3': case '4':
     case '5': case '6': case '7': case '8': case '9': case '&':
-           if (PL_curpm && (rx = PM_GETRE(PL_curpm))) {
-               /*
-                * Pre-threads, this was paren = atoi(GvENAME((const GV 
*)mg->mg_obj));
-                * XXX Does the new way break anything?
-                */
-               paren = atoi(mg->mg_ptr); /* $& is in [0] */
-               CALLREG_NUMBUF_FETCH(rx,paren,sv);
-               break;
-           }
-           sv_setsv(sv,&PL_sv_undef);
-       }
+        /*
+         * Pre-threads, this was paren = atoi(GvENAME((const GV *)mg->mg_obj));
+         * XXX Does the new way break anything?
+         */
+        paren = atoi(mg->mg_ptr); /* $& is in [0] */
+      do_numbuf_fetch:
+        if (PL_curpm && (rx = PM_GETRE(PL_curpm))) {
+            CALLREG_NUMBUF_FETCH(rx,paren,sv);
+            break;
+        }
+        sv_setsv(sv,&PL_sv_undef);
        break;
     case '+':
        if (PL_curpm && (rx = PM_GETRE(PL_curpm))) {
-           if (RX_LASTPAREN(rx)) {
-               CALLREG_NUMBUF_FETCH(rx,RX_LASTPAREN(rx),sv);
-               break;
-           }
+           paren = RX_LASTPAREN(rx);
+           if (paren)
+                goto do_numbuf_fetch;
        }
        sv_setsv(sv,&PL_sv_undef);
        break;
     case '\016':               /* ^N */
        if (PL_curpm && (rx = PM_GETRE(PL_curpm))) {
-           if (RX_LASTCLOSEPAREN(rx)) {
-               CALLREG_NUMBUF_FETCH(rx,RX_LASTCLOSEPAREN(rx),sv);
-               break;
-           }
-
+           paren = RX_LASTCLOSEPAREN(rx);
+           if (paren)
+                goto do_numbuf_fetch;
        }
        sv_setsv(sv,&PL_sv_undef);
        break;
     case '`':
-      do_prematch_fetch:
-       if (PL_curpm && (rx = PM_GETRE(PL_curpm))) {
-           CALLREG_NUMBUF_FETCH(rx,-2,sv);
-           break;
-       }
-       sv_setsv(sv,&PL_sv_undef);
-       break;
+        paren = RX_BUFF_IDX_PREMATCH;
+        goto do_numbuf_fetch;
     case '\'':
-      do_postmatch_fetch:
-       if (PL_curpm && (rx = PM_GETRE(PL_curpm))) {
-           CALLREG_NUMBUF_FETCH(rx,-1,sv);
-           break;
-       }
-       sv_setsv(sv,&PL_sv_undef);
-       break;
+        paren = RX_BUFF_IDX_POSTMATCH;
+        goto do_numbuf_fetch;
     case '.':
        if (GvIO(PL_last_in_gv)) {
            sv_setiv(sv, (IV)IoLINES(GvIOp(PL_last_in_gv)));
diff --git a/perl.c b/perl.c
index 8444218..7d65719 100644
--- a/perl.c
+++ b/perl.c
@@ -860,7 +860,7 @@ perl_destruct(pTHXx)
     PL_minus_F      = FALSE;
     PL_doswitches   = FALSE;
     PL_dowarn       = G_WARN_OFF;
-    PL_sawampersand = FALSE;   /* must save all match strings */
+    PL_sawampersand = 0;       /* must save all match strings */
     PL_unsafe       = FALSE;
 
     Safefree(PL_inplace);
@@ -2343,8 +2343,9 @@ STATIC void
 S_run_body(pTHX_ I32 oldscope)
 {
     dVAR;
-    DEBUG_r(PerlIO_printf(Perl_debug_log, "%s $` $& $' support.\n",
-                    PL_sawampersand ? "Enabling" : "Omitting"));
+    DEBUG_r(PerlIO_printf(Perl_debug_log, "%s $` $& $' support (0x%x).\n",
+                    PL_sawampersand ? "Enabling" : "Omitting",
+                    (unsigned int)(PL_sawampersand)));
 
     if (!PL_restartop) {
 #ifdef PERL_MAD
diff --git a/perl.h b/perl.h
index 2cc4e91..b299432 100644
--- a/perl.h
+++ b/perl.h
@@ -4854,6 +4854,12 @@ typedef enum {
 #define HINT_SORT_MERGESORT    0x00000002
 #define HINT_SORT_STABLE       0x00000100 /* sort styles (currently one) */
 
+/* flags for PL_sawampersand */
+
+#define SAWAMPERSAND_LEFT       1   /* saw $` */
+#define SAWAMPERSAND_MIDDLE     2   /* saw $& */
+#define SAWAMPERSAND_RIGHT      4   /* saw $' */
+
 /* Various states of the input record separator SV (rs) */
 #define RsSNARF(sv)   (! SvOK(sv))
 #define RsSIMPLE(sv)  (SvOK(sv) && (! SvPOK(sv) || SvCUR(sv)))
diff --git a/pod/perlreapi.pod b/pod/perlreapi.pod
index 35b6b74..1ccc6d8 100644
--- a/pod/perlreapi.pod
+++ b/pod/perlreapi.pod
@@ -209,7 +209,49 @@ faster than C<unpack>.
              I32 minend, SV* screamer,
              void* data, U32 flags);
 
-Execute a regexp.
+Execute a regexp. The arguments are
+
+=over 4
+
+=item rx
+
+The regular expression to execute.
+
+=item screamer
+
+This strangely-named arg is the SV to be matched against. Note that the
+actual char array to be matched against is supplied by the arguments
+described below; the SV is just used to determine UTF8ness, C<pos()> etc.
+
+=item strbeg
+
+Pointer to the physical start of the string.
+
+=item strend
+
+Pointer to the character following the physical end of the string (i.e.
+the \0).
+
+=item stringarg
+
+Pointer to the position in the string where matching should start; it might
+not be equal to C<strbeg> (for example in a later iteration of C</.../g>).
+
+=item minend
+
+Minimum length of string (measured in bytes from C<stringarg>) that must
+match; if the engine reaches the end of the match but hasn't reached this
+position in the string, it should fail.
+
+=item data
+
+Optimisation data; subject to change.
+
+=item flags
+
+Optimisation flags; subject to change.
+
+=back
 
 =head2 intuit
 
@@ -513,6 +555,8 @@ values.
         char *subbeg;  /* saved or original string so \digit works forever. */
         SV_SAVED_COPY  /* If non-NULL, SV which is COW from original */
         I32 sublen;    /* Length of string pointed by subbeg */
+       I32 suboffset;  /* byte offset of subbeg from logical start of str */
+       I32 subcoffset; /* suboffset equiv, but in chars (for @-/@+) */
 
         /* Information about the match that isn't often used */
         I32 prelen;           /* length of precomp */
@@ -653,9 +697,23 @@ occur at a floating offset from the start of the pattern. 
Used to do
 Fast-Boyer-Moore searches on the string to find out if its worth using
 the regex engine at all, and if so where in the string to search.
 
-=head2 C<subbeg> C<sublen> C<saved_copy>
-
-Used during execution phase for managing search and replace patterns.
+=head2 C<subbeg> C<sublen> C<saved_copy> C<suboffset> C<subcoffset>
+
+Used during the execution phase for managing search and replace patterns,
+and for providing the text for C<$&>, C<$1> etc. C<subbeg> points to a
+buffer (either the original string, or a copy in the case of
+C<RX_MATCH_COPIED(rx)>), and C<sublen> is the length of the buffer.  The
+C<RX_OFFS> start and end indices index into this buffer.
+
+In the presence of the C<REXEC_COPY_STR> flag, but with the addition of
+the C<REXEC_COPY_SKIP_PRE> or C<REXEC_COPY_SKIP_POST> flags, an engine
+can choose not to copy the full buffer (although it must still do so in
+the presence of C<RXf_PMf_KEEPCOPY> or the relevant bits being set in
+C<PL_sawampersand>). In this case, it may set C<suboffset> to indicate the
+number of bytes from the logical start of the buffer to the physical start
+(i.e. C<subbeg>). It should also set C<subcoffset>, the number of
+characters in the offset. The latter is needed to support C<@-> and C<@+>
+which work in characters, not bytes.
 
 =head2 C<wrapped> C<wraplen>
 
diff --git a/pp.c b/pp.c
index 29db8ed..e1a6c78 100644
--- a/pp.c
+++ b/pp.c
@@ -5549,13 +5549,9 @@ PP(pp_split)
            if (rex_return == 0)
                break;
            TAINT_IF(RX_MATCH_TAINTED(rx));
-           if (RX_MATCH_COPIED(rx) && RX_SUBBEG(rx) != orig) {
-               m = s;
-               s = orig;
-               orig = RX_SUBBEG(rx);
-               s = orig + (m - s);
-               strend = s + (strend - m);
-           }
+            /* we never pass the REXEC_COPY_STR flag, so it should
+             * never get copied */
+            assert(!RX_MATCH_COPIED(rx));
            m = RX_OFFS(rx)[0].start + orig;
 
            if (gimme_scalar) {
diff --git a/pp_ctl.c b/pp_ctl.c
index 1477373..af0d558 100644
--- a/pp_ctl.c
+++ b/pp_ctl.c
@@ -216,9 +216,7 @@ PP(pp_substcont)
        if (CxONCE(cx) || s < orig ||
                !CALLREGEXEC(rx, s, cx->sb_strend, orig,
                             (s == m) + RX_GOFS(rx), cx->sb_targ, NULL,
-                            ((cx->sb_rflags & REXEC_COPY_STR)
-                             ? (REXEC_IGNOREPOS|REXEC_NOT_FIRST)
-                             : 
(REXEC_COPY_STR|REXEC_IGNOREPOS|REXEC_NOT_FIRST))))
+                                (REXEC_IGNOREPOS|REXEC_NOT_FIRST)))
        {
            SV *targ = cx->sb_targ;
 
@@ -289,6 +287,7 @@ PP(pp_substcont)
     if (RX_MATCH_COPIED(rx) && RX_SUBBEG(rx) != orig) {
        m = s;
        s = orig;
+        assert(!RX_SUBOFFSET(rx));
        cx->sb_orig = orig = RX_SUBBEG(rx);
        s = orig + (m - s);
        cx->sb_strend = s + (cx->sb_strend - m);
@@ -353,9 +352,9 @@ Perl_rxres_save(pTHX_ void **rsp, REGEXP *rx)
 
     if (!p || p[1] < RX_NPARENS(rx)) {
 #ifdef PERL_OLD_COPY_ON_WRITE
-       i = 7 + RX_NPARENS(rx) * 2;
+       i = 7 + (RX_NPARENS(rx)+1) * 2;
 #else
-       i = 6 + RX_NPARENS(rx) * 2;
+       i = 6 + (RX_NPARENS(rx)+1) * 2;
 #endif
        if (!p)
            Newx(p, i, UV);
@@ -364,7 +363,7 @@ Perl_rxres_save(pTHX_ void **rsp, REGEXP *rx)
        *rsp = (void*)p;
     }
 
-    *p++ = PTR2UV(RX_MATCH_COPIED(rx) ? RX_SUBBEG(rx) : NULL);
+    *p++ = RX_MATCH_COPIED(rx) ? 1 : 0;
     RX_MATCH_COPIED_off(rx);
 
 #ifdef PERL_OLD_COPY_ON_WRITE
@@ -373,9 +372,10 @@ Perl_rxres_save(pTHX_ void **rsp, REGEXP *rx)
 #endif
 
     *p++ = RX_NPARENS(rx);
-
     *p++ = PTR2UV(RX_SUBBEG(rx));
     *p++ = (UV)RX_SUBLEN(rx);
+    *p++ = (UV)RX_SUBOFFSET(rx);
+    *p++ = (UV)RX_SUBCOFFSET(rx);
     for (i = 0; i <= RX_NPARENS(rx); ++i) {
        *p++ = (UV)RX_OFFS(rx)[i].start;
        *p++ = (UV)RX_OFFS(rx)[i].end;
@@ -403,9 +403,10 @@ S_rxres_restore(pTHX_ void **rsp, REGEXP *rx)
 #endif
 
     RX_NPARENS(rx) = *p++;
-
     RX_SUBBEG(rx) = INT2PTR(char*,*p++);
     RX_SUBLEN(rx) = (I32)(*p++);
+    RX_SUBOFFSET(rx) = (I32)*p++;
+    RX_SUBCOFFSET(rx) = (I32)*p++;
     for (i = 0; i <= RX_NPARENS(rx); ++i) {
        RX_OFFS(rx)[i].start = (I32)(*p++);
        RX_OFFS(rx)[i].end = (I32)(*p++);
diff --git a/pp_hot.c b/pp_hot.c
index 6c3f4f6..0d70dfc 100644
--- a/pp_hot.c
+++ b/pp_hot.c
@@ -1319,15 +1319,18 @@ PP(pp_match)
            }
        }
     }
-    /* XXX: comment out !global get safe $1 vars after a
-       match, BUT be aware that this leads to dramatic slowdowns on
-       /g matches against large strings.  So far a solution to this problem
-       appears to be quite tricky.
-       Test for the unsafe vars are TODO for now. */
-    if (       (!global && RX_NPARENS(rx))
-           || SvTEMP(TARG) || SvAMAGIC(TARG) || PL_sawampersand
-           || (RX_EXTFLAGS(rx) & (RXf_EVAL_SEEN|RXf_PMf_KEEPCOPY)))
-       r_flags |= REXEC_COPY_STR;
+    if (       RX_NPARENS(rx)
+            || PL_sawampersand
+            || (RX_EXTFLAGS(rx) & (RXf_EVAL_SEEN|RXf_PMf_KEEPCOPY))
+    ) {
+       r_flags |= (REXEC_COPY_STR|REXEC_COPY_SKIP_PRE);
+        /* in @a =~ /(.)/g, we iterate multiple times, but copy the buffer
+         * only on the first iteration. Therefore we need to copy $' as well
+         * as $&, to make the rest of the string available for captures in
+         * subsequent iterations */
+        if (! (global && gimme == G_ARRAY))
+            r_flags |= REXEC_COPY_SKIP_POST;
+    };
 
   play_it_again:
     if (global && RX_OFFS(rx)[0].start != -1) {
@@ -1472,6 +1475,8 @@ yup:                                      /* Confirmed by 
INTUIT */
     if (global) {
        /* FIXME - should rx->subbeg be const char *?  */
        RX_SUBBEG(rx) = (char *) truebase;
+       RX_SUBOFFSET(rx) = 0;
+       RX_SUBCOFFSET(rx) = 0;
        RX_OFFS(rx)[0].start = s - truebase;
        if (RX_MATCH_UTF8(rx)) {
            char * const t = (char*)utf8_hop((U8*)s, RX_MINLENRET(rx));
@@ -1507,6 +1512,8 @@ yup:                                      /* Confirmed by 
INTUIT */
 #endif
        }
        RX_SUBLEN(rx) = strend - t;
+       RX_SUBOFFSET(rx) = 0;
+       RX_SUBCOFFSET(rx) = 0;
        RX_MATCH_COPIED_on(rx);
        off = RX_OFFS(rx)[0].start = s - t;
        RX_OFFS(rx)[0].end = off + RX_MINLENRET(rx);
@@ -2127,9 +2134,13 @@ PP(pp_subst)
        pm = PL_curpm;
        rx = PM_GETRE(pm);
     }
-    r_flags = (RX_NPARENS(rx) || SvTEMP(TARG) || PL_sawampersand
-           || (RX_EXTFLAGS(rx) & (RXf_EVAL_SEEN|RXf_PMf_KEEPCOPY)) )
-              ? REXEC_COPY_STR : 0;
+
+    r_flags = (    RX_NPARENS(rx)
+                || PL_sawampersand
+                || (RX_EXTFLAGS(rx) & (RXf_EVAL_SEEN|RXf_PMf_KEEPCOPY))
+              )
+          ? REXEC_COPY_STR
+          : 0;
 
     orig = m = s;
     if (RX_EXTFLAGS(rx) & RXf_USE_INTUIT) {
@@ -2203,7 +2214,8 @@ PP(pp_subst)
 #ifdef PERL_OLD_COPY_ON_WRITE
        && !is_cow
 #endif
-       && (I32)clen <= RX_MINLENRET(rx) && (once || !(r_flags & 
REXEC_COPY_STR))
+        && (I32)clen <= RX_MINLENRET(rx)
+        && (once || !(r_flags & REXEC_COPY_STR))
        && !(RX_EXTFLAGS(rx) & RXf_LOOKBEHIND_SEEN)
        && (!doutf8 || SvUTF8(TARG))
        && !(rpm->op_pmflags & PMf_NONDESTRUCT))
@@ -2331,6 +2343,7 @@ PP(pp_subst)
            if (RX_MATCH_COPIED(rx) && RX_SUBBEG(rx) != orig) {
                m = s;
                s = orig;
+                assert(RX_SUBOFFSET(rx) == 0);
                orig = RX_SUBBEG(rx);
                s = orig + (m - s);
                strend = s + (strend - m);
diff --git a/regcomp.c b/regcomp.c
index 921c0e9..a9e92e1 100644
--- a/regcomp.c
+++ b/regcomp.c
@@ -6691,37 +6691,53 @@ Perl_reg_numbered_buff_fetch(pTHX_ REGEXP * const r, 
const I32 paren,
     char *s = NULL;
     I32 i = 0;
     I32 s1, t1;
+    I32 n = paren;
 
     PERL_ARGS_ASSERT_REG_NUMBERED_BUFF_FETCH;
         
-    if (!rx->subbeg) {
-        sv_setsv(sv,&PL_sv_undef);
-        return;
-    } 
-    else               
-    if (paren == RX_BUFF_IDX_PREMATCH && rx->offs[0].start != -1) {
-        /* $` */
+    if ( (    n == RX_BUFF_IDX_CARET_PREMATCH
+           || n == RX_BUFF_IDX_CARET_FULLMATCH
+           || n == RX_BUFF_IDX_CARET_POSTMATCH
+         )
+         && !(rx->extflags & RXf_PMf_KEEPCOPY)
+    )
+        goto ret_undef;
+
+    if (!rx->subbeg)
+        goto ret_undef;
+
+    if (n == RX_BUFF_IDX_CARET_FULLMATCH)
+        /* no need to distinguish between them any more */
+        n = RX_BUFF_IDX_FULLMATCH;
+
+    if ((n == RX_BUFF_IDX_PREMATCH || n == RX_BUFF_IDX_CARET_PREMATCH)
+        && rx->offs[0].start != -1)
+    {
+        /* $`, ${^PREMATCH} */
        i = rx->offs[0].start;
        s = rx->subbeg;
     }
     else 
-    if (paren == RX_BUFF_IDX_POSTMATCH && rx->offs[0].end != -1) {
-        /* $' */
-       s = rx->subbeg + rx->offs[0].end;
-       i = rx->sublen - rx->offs[0].end;
+    if ((n == RX_BUFF_IDX_POSTMATCH || n == RX_BUFF_IDX_CARET_POSTMATCH)
+        && rx->offs[0].end != -1)
+    {
+        /* $', ${^POSTMATCH} */
+       s = rx->subbeg - rx->suboffset + rx->offs[0].end;
+       i = rx->sublen + rx->suboffset - rx->offs[0].end;
     } 
     else
-    if ( 0 <= paren && paren <= (I32)rx->nparens &&
-        (s1 = rx->offs[paren].start) != -1 &&
-        (t1 = rx->offs[paren].end) != -1)
+    if ( 0 <= n && n <= (I32)rx->nparens &&
+        (s1 = rx->offs[n].start) != -1 &&
+        (t1 = rx->offs[n].end) != -1)
     {
-        /* $& $1 ... */
+        /* $&, ${^MATCH},  $1 ... */
         i = t1 - s1;
-        s = rx->subbeg + s1;
+        s = rx->subbeg + s1 - rx->suboffset;
     } else {
-        sv_setsv(sv,&PL_sv_undef);
-        return;
+        goto ret_undef;
     }          
+
+    assert(s >= rx->subbeg);
     assert(rx->sublen >= (s - rx->subbeg) + i );
     if (i >= 0) {
         const int oldtainted = PL_tainted;
@@ -6757,6 +6773,7 @@ Perl_reg_numbered_buff_fetch(pTHX_ REGEXP * const r, 
const I32 paren,
                 SvTAINTED_off(sv);
         }
     } else {
+      ret_undef:
         sv_setsv(sv,&PL_sv_undef);
         return;
     }
@@ -6783,13 +6800,18 @@ Perl_reg_numbered_buff_length(pTHX_ REGEXP * const r, 
const SV * const sv,
     struct regexp *const rx = (struct regexp *)SvANY(r);
     I32 i;
     I32 s1, t1;
+    I32 n = paren;
 
     PERL_ARGS_ASSERT_REG_NUMBERED_BUFF_LENGTH;
 
     /* Some of this code was originally in C<Perl_magic_len> in F<mg.c> */
-       switch (paren) {
-      /* $` / ${^PREMATCH} */
-      case RX_BUFF_IDX_PREMATCH:
+    switch (paren) {
+      case RX_BUFF_IDX_CARET_PREMATCH: /* ${^PREMATCH} */
+         if (!(rx->extflags & RXf_PMf_KEEPCOPY))
+            goto warn_undef;
+        /*FALLTHROUGH*/
+
+      case RX_BUFF_IDX_PREMATCH:       /* $` */
         if (rx->offs[0].start != -1) {
                        i = rx->offs[0].start;
                        if (i > 0) {
@@ -6799,8 +6821,11 @@ Perl_reg_numbered_buff_length(pTHX_ REGEXP * const r, 
const SV * const sv,
                        }
            }
         return 0;
-      /* $' / ${^POSTMATCH} */
-      case RX_BUFF_IDX_POSTMATCH:
+
+      case RX_BUFF_IDX_CARET_POSTMATCH: /* ${^POSTMATCH} */
+         if (!(rx->extflags & RXf_PMf_KEEPCOPY))
+            goto warn_undef;
+      case RX_BUFF_IDX_POSTMATCH:       /* $' */
            if (rx->offs[0].end != -1) {
                        i = rx->sublen - rx->offs[0].end;
                        if (i > 0) {
@@ -6810,6 +6835,13 @@ Perl_reg_numbered_buff_length(pTHX_ REGEXP * const r, 
const SV * const sv,
                        }
            }
         return 0;
+
+      case RX_BUFF_IDX_CARET_FULLMATCH: /* ${^MATCH} */
+         if (!(rx->extflags & RXf_PMf_KEEPCOPY))
+            goto warn_undef;
+        n = RX_BUFF_IDX_FULLMATCH;
+        /*FALLTHROUGH*/
+
       /* $& / ${^MATCH}, $1, $2, ... */
       default:
            if (paren <= (I32)rx->nparens &&
@@ -6819,6 +6851,7 @@ Perl_reg_numbered_buff_length(pTHX_ REGEXP * const r, 
const SV * const sv,
             i = t1 - s1;
             goto getlen;
         } else {
+          warn_undef:
             if (ckWARN(WARN_UNINITIALIZED))
                 report_uninit((const SV *)sv);
             return 0;
@@ -6826,7 +6859,7 @@ Perl_reg_numbered_buff_length(pTHX_ REGEXP * const r, 
const SV * const sv,
     }
   getlen:
     if (i > 0 && RXp_MATCH_UTF8(rx)) {
-        const char * const s = rx->subbeg + s1;
+        const char * const s = rx->subbeg - rx->suboffset + s1;
         const U8 *ep;
         STRLEN el;
 
@@ -14429,6 +14462,8 @@ Perl_save_re_context(pTHX)
 
     PL_reg_oldsaved = NULL;
     PL_reg_oldsavedlen = 0;
+    PL_reg_oldsavedoffset = 0;
+    PL_reg_oldsavedcoffset = 0;
     PL_reg_maxiter = 0;
     PL_reg_leftiter = 0;
     PL_reg_poscache = NULL;
diff --git a/regexec.c b/regexec.c
index 4c9a456..2dc2314 100644
--- a/regexec.c
+++ b/regexec.c
@@ -502,10 +502,13 @@ S_regcp_restore(pTHX_ regexp *rex, I32 ix)
 I32
 Perl_pregexec(pTHX_ REGEXP * const prog, char* stringarg, register char 
*strend,
         char *strbeg, I32 minend, SV *screamer, U32 nosave)
-/* strend: pointer to null at end of string */
-/* strbeg: real beginning of string */
-/* minend: end of match must be >=minend after stringarg. */
-/* nosave: For optimizations. */
+/* stringarg: the point in the string at which to begin matching */
+/* strend:    pointer to null at end of string */
+/* strbeg:    real beginning of string */
+/* minend:    end of match must be >= minend bytes after stringarg. */
+/* screamer:  SV being matched: only used for utf8 flag, pos() etc; string
+ *            itself is accessed via the pointers above */
+/* nosave:    For optimizations. */
 {
     PERL_ARGS_ASSERT_PREGEXEC;
 
@@ -2051,13 +2054,17 @@ S_find_byclass(pTHX_ regexp * prog, const regnode *c, 
char *s,
 I32
 Perl_regexec_flags(pTHX_ REGEXP * const rx, char *stringarg, register char 
*strend,
              char *strbeg, I32 minend, SV *sv, void *data, U32 flags)
-/* strend: pointer to null at end of string */
-/* strbeg: real beginning of string */
-/* minend: end of match must be >=minend after stringarg. */
-/* data: May be used for some additional optimizations. 
-         Currently its only used, with a U32 cast, for transmitting 
-         the ganch offset when doing a /g match. This will change */
-/* nosave: For optimizations. */
+/* stringarg: the point in the string at which to begin matching */
+/* strend:    pointer to null at end of string */
+/* strbeg:    real beginning of string */
+/* minend:    end of match must be >= minend bytes after stringarg. */
+/* sv:        SV being matched: only used for utf8 flag, pos() etc; string
+ *            itself is accessed via the pointers above */
+/* data:      May be used for some additional optimizations.
+              Currently its only used, with a U32 cast, for transmitting
+              the ganch offset when doing a /g match. This will change */
+/* nosave:    For optimizations. */
+
 {
     dVAR;
     struct regexp *const prog = (struct regexp *)SvANY(rx);
@@ -2559,9 +2566,7 @@ got_it:
 
     /* make sure $`, $&, $', and $digit will work later */
     if ( !(flags & REXEC_NOT_FIRST) ) {
-       RX_MATCH_COPY_FREE(rx);
        if (flags & REXEC_COPY_STR) {
-           const I32 i = PL_regeol - startpos + (stringarg - strbeg);
 #ifdef PERL_OLD_COPY_ON_WRITE
            if ((SvIsCOW(sv)
                 || (SvFLAGS(sv) & CAN_COW_MASK) == CAN_COW_FLAGS)) {
@@ -2573,17 +2578,105 @@ got_it:
                prog->saved_copy = sv_setsv_cow(prog->saved_copy, sv);
                prog->subbeg = (char *)SvPVX_const(prog->saved_copy);
                assert (SvPOKp(prog->saved_copy));
+                prog->sublen  = PL_regeol - strbeg;
+                prog->suboffset = 0;
+                prog->subcoffset = 0;
            } else
 #endif
            {
-               RX_MATCH_COPIED_on(rx);
-               s = savepvn(strbeg, i);
-               prog->subbeg = s;
-           }
-           prog->sublen = i;
+                I32 min = 0;
+                I32 max = PL_regeol - strbeg;
+                I32 sublen;
+
+                if (    (flags & REXEC_COPY_SKIP_POST)
+                    && !(RX_EXTFLAGS(rx) & RXf_PMf_KEEPCOPY) /* //p */
+                    && !(PL_sawampersand & SAWAMPERSAND_RIGHT)
+                ) { /* don't copy $' part of string */
+                    U32 n = 0;
+                    max = -1;
+                    /* calculate the right-most part of the string covered
+                     * by a capture. Due to look-ahead, this may be to
+                     * the right of $&, so we have to scan all captures */
+                    while (n <= prog->lastparen) {
+                        if (prog->offs[n].end > max)
+                            max = prog->offs[n].end;
+                        n++;
+                    }
+                    if (max == -1)
+                        max = (PL_sawampersand & SAWAMPERSAND_LEFT)
+                                ? prog->offs[0].start
+                                : 0;
+                    assert(max >= 0 && max <= PL_regeol - strbeg);
+                }
+
+                if (    (flags & REXEC_COPY_SKIP_PRE)
+                    && !(RX_EXTFLAGS(rx) & RXf_PMf_KEEPCOPY) /* //p */
+                    && !(PL_sawampersand & SAWAMPERSAND_LEFT)
+                ) { /* don't copy $` part of string */
+                    U32 n = 0;
+                    min = max;
+                    /* calculate the left-most part of the string covered
+                     * by a capture. Due to look-behind, this may be to
+                     * the left of $&, so we have to scan all captures */
+                    while (min && n <= prog->lastparen) {
+                        if (   prog->offs[n].start != -1
+                            && prog->offs[n].start < min)
+                        {
+                            min = prog->offs[n].start;
+                        }
+                        n++;
+                    }
+                    if ((PL_sawampersand & SAWAMPERSAND_RIGHT)
+                        && min >  prog->offs[0].end
+                    )
+                        min = prog->offs[0].end;
+
+                }
+
+                assert(min >= 0 && min <= max && min <= PL_regeol - strbeg);
+                sublen = max - min;
+
+                if (RX_MATCH_COPIED(rx)) {
+                    if (sublen > prog->sublen)
+                        prog->subbeg =
+                                (char*)saferealloc(prog->subbeg, sublen+1);
+                }
+                else
+                    prog->subbeg = (char*)safemalloc(sublen+1);
+                Copy(strbeg + min, prog->subbeg, sublen, char);
+                prog->subbeg[sublen] = '\0';
+                prog->suboffset = min;
+                prog->sublen = sublen;
+           }
+            RX_MATCH_COPIED_on(rx);
+            prog->subcoffset = prog->suboffset;
+            if (prog->suboffset && utf8_target) {
+                /* Convert byte offset to chars.
+                 * XXX ideally should only compute this if @-/@+
+                 * has been seen, a la PL_sawampersand ??? */
+
+                /* If there's a direct correspondence between the
+                 * string which we're matching and the original SV,
+                 * then we can use the utf8 len cache associated with
+                 * the SV. In particular, it means that under //g,
+                 * sv_pos_b2u() will use the previously cached
+                 * position to speed up working out the new length of
+                 * subcoffset, rather than counting from the start of
+                 * the string each time. This stops
+                 *   $x = "\x{100}" x 1E6; 1 while $x =~ /(.)/g;
+                 * from going quadratic */
+                if (SvPOKp(sv) && SvPVX(sv) == strbeg)
+                    sv_pos_b2u(sv, &(prog->subcoffset));
+                else
+                    prog->subcoffset = utf8_length((U8*)strbeg,
+                                        (U8*)(strbeg+prog->suboffset));
+            }
        }
        else {
+            RX_MATCH_COPY_FREE(rx);
            prog->subbeg = strbeg;
+           prog->suboffset = 0;
+           prog->subcoffset = 0;
            prog->sublen = PL_regeol - strbeg;  /* strend may have been 
modified */
        }
     }
@@ -2688,6 +2781,8 @@ S_regtry(pTHX_ regmatch_info *reginfo, char **startpos)
                $` inside (?{}) could fail... */
            PL_reg_oldsaved = prog->subbeg;
            PL_reg_oldsavedlen = prog->sublen;
+           PL_reg_oldsavedoffset = prog->suboffset;
+           PL_reg_oldsavedcoffset = prog->suboffset;
 #ifdef PERL_OLD_COPY_ON_WRITE
            PL_nrs = prog->saved_copy;
 #endif
@@ -2696,6 +2791,8 @@ S_regtry(pTHX_ regmatch_info *reginfo, char **startpos)
        else
            PL_reg_oldsaved = NULL;
        prog->subbeg = PL_bostr;
+       prog->suboffset = 0;
+       prog->subcoffset = 0;
        prog->sublen = PL_regeol - PL_bostr; /* strend may have been modified */
     }
 #ifdef DEBUGGING
@@ -4528,6 +4625,8 @@ S_regmatch(pTHX_ regmatch_info *reginfo, regnode *prog)
                 RXp_MATCH_COPIED_off(re);
                 re->subbeg = rex->subbeg;
                 re->sublen = rex->sublen;
+                re->suboffset = rex->suboffset;
+                re->subcoffset = rex->subcoffset;
                rei = RXi_GET(re);
                 DEBUG_EXECUTE_r(
                     debug_start_match(re_sv, utf8_target, locinput, PL_regeol,
@@ -7160,6 +7259,8 @@ restore_pos(pTHX_ void *arg)
        if (PL_reg_oldsaved) {
            rex->subbeg = PL_reg_oldsaved;
            rex->sublen = PL_reg_oldsavedlen;
+           rex->suboffset = PL_reg_oldsavedoffset;
+           rex->subcoffset = PL_reg_oldsavedcoffset;
 #ifdef PERL_OLD_COPY_ON_WRITE
            rex->saved_copy = PL_nrs;
 #endif
diff --git a/regexp.h b/regexp.h
index db36edd..3e245d0 100644
--- a/regexp.h
+++ b/regexp.h
@@ -124,6 +124,8 @@ struct reg_code_block {
        char *subbeg;                                                   \
        SV_SAVED_COPY   /* If non-NULL, SV which is COW from original */\
        I32 sublen;     /* Length of string pointed by subbeg */        \
+       I32 suboffset;  /* byte offset of subbeg from logical start of str */ \
+       I32 subcoffset; /* suboffset equiv, but in chars (for @-/@+) */ \
        /* Information about the match that isn't often used */         \
        /* offset from wrapped to the start of precomp */               \
        PERL_BITFIELD32 pre_prefix:4;                                   \
@@ -181,9 +183,12 @@ typedef struct regexp_engine {
   paren name. >= 1 is reserved for actual numbered captures, i.e. $1,
   $2 etc.
 */
-#define RX_BUFF_IDX_PREMATCH  -2 /* $` / ${^PREMATCH}  */
-#define RX_BUFF_IDX_POSTMATCH -1 /* $' / ${^POSTMATCH} */
-#define RX_BUFF_IDX_FULLMATCH      0 /* $& / ${^MATCH}     */
+#define RX_BUFF_IDX_CARET_PREMATCH  -5 /* ${^PREMATCH}  */
+#define RX_BUFF_IDX_CARET_POSTMATCH -4 /* ${^POSTMATCH} */
+#define RX_BUFF_IDX_CARET_FULLMATCH -3 /* ${^MATCH}     */
+#define RX_BUFF_IDX_PREMATCH        -2 /* $` */
+#define RX_BUFF_IDX_POSTMATCH       -1 /* $' */
+#define RX_BUFF_IDX_FULLMATCH        0 /* $& */
 
 /*
   Flags that are passed to the named_buff and named_buff_iter
@@ -474,6 +479,18 @@ get_regex_charset_name(const U32 flags, STRLEN* const lenp)
        assert(SvTYPE(_rx_subbeg) == SVt_REGEXP);                       \
        &SvANY(_rx_subbeg)->subbeg;                                     \
     }))
+#  define RX_SUBOFFSET(prog)                                           \
+    (*({                                                               \
+       const REGEXP *const _rx_suboffset = (prog);                     \
+       assert(SvTYPE(_rx_suboffset) == SVt_REGEXP);                    \
+       &SvANY(_rx_suboffset)->suboffset;                               \
+    }))
+#  define RX_SUBCOFFSET(prog)                                          \
+    (*({                                                               \
+       const REGEXP *const _rx_subcoffset = (prog);                    \
+       assert(SvTYPE(_rx_subcoffset) == SVt_REGEXP);                   \
+       &SvANY(_rx_subcoffset)->subcoffset;                             \
+    }))
 #  define RX_OFFS(prog)                                                        
\
     (*({                                                               \
        const REGEXP *const _rx_offs = (prog);                          \
@@ -490,6 +507,8 @@ get_regex_charset_name(const U32 flags, STRLEN* const lenp)
 #  define RX_EXTFLAGS(prog)    RXp_EXTFLAGS((struct regexp *)SvANY(prog))
 #  define RX_ENGINE(prog)      (((struct regexp *)SvANY(prog))->engine)
 #  define RX_SUBBEG(prog)      (((struct regexp *)SvANY(prog))->subbeg)
+#  define RX_SUBOFFSET(prog)   (((struct regexp *)SvANY(prog))->suboffset)
+#  define RX_SUBCOFFSET(prog)  (((struct regexp *)SvANY(prog))->subcoffset)
 #  define RX_OFFS(prog)                (((struct regexp *)SvANY(prog))->offs)
 #  define RX_NPARENS(prog)     (((struct regexp *)SvANY(prog))->nparens)
 #endif
@@ -538,6 +557,11 @@ get_regex_charset_name(const U32 flags, STRLEN* const lenp)
 #define REXEC_SCREAM   0x04            /* use scream table. */
 #define REXEC_IGNOREPOS        0x08            /* \G matches at start. */
 #define REXEC_NOT_FIRST        0x10            /* This is another iteration of 
//g. */
+                                    /* under REXEC_COPY_STR, it's ok for the
+                                     * engine (modulo PL_sawamperand etc)
+                                     * to skip copying ... */
+#define REXEC_COPY_SKIP_PRE  0x20   /* ...the $` part of the string, or */
+#define REXEC_COPY_SKIP_POST 0x40   /* ...the $' part of the string */
 
 #if defined(__GNUC__) && !defined(PERL_GCC_BRACE_GROUPS_FORBIDDEN)
 #  define ReREFCNT_inc(re)                                             \
@@ -760,6 +784,8 @@ typedef struct regmatch_slab {
 #define PL_reg_curpm           PL_reg_state.re_state_reg_curpm
 #define PL_reg_oldsaved                PL_reg_state.re_state_reg_oldsaved
 #define PL_reg_oldsavedlen     PL_reg_state.re_state_reg_oldsavedlen
+#define PL_reg_oldsavedoffset  PL_reg_state.re_state_reg_oldsavedoffset
+#define PL_reg_oldsavedcoffset PL_reg_state.re_state_reg_oldsavedcoffset
 #define PL_reg_maxiter         PL_reg_state.re_state_reg_maxiter
 #define PL_reg_leftiter                PL_reg_state.re_state_reg_leftiter
 #define PL_reg_poscache                PL_reg_state.re_state_reg_poscache
@@ -781,6 +807,8 @@ struct re_save_state {
     PMOP *re_state_reg_curpm;          /* from regexec.c */
     char *re_state_reg_oldsaved;       /* old saved substr during match */
     STRLEN re_state_reg_oldsavedlen;   /* old length of saved substr during 
match */
+    STRLEN re_state_reg_oldsavedoffset;        /* old offset of saved substr 
during match */
+    STRLEN re_state_reg_oldsavedcoffset;/* old coffset of saved substr during 
match */
     STRLEN re_state_reg_poscache_size; /* size of pos cache of WHILEM */
     I32 re_state_reg_oldpos;           /* from regexec.c */
     I32 re_state_reg_maxiter;          /* max wait until caching pos */
diff --git a/t/porting/known_pod_issues.dat b/t/porting/known_pod_issues.dat
index f316fa7..ba4ccf6 100644
--- a/t/porting/known_pod_issues.dat
+++ b/t/porting/known_pod_issues.dat
@@ -266,7 +266,7 @@ pod/perlpacktut.pod Verbatim line length including indents 
exceeds 79 by    6
 pod/perlperf.pod       Verbatim line length including indents exceeds 79 by    
154
 pod/perlpodspec.pod    Verbatim line length including indents exceeds 79 by    
9
 pod/perlpodstyle.pod   Verbatim line length including indents exceeds 79 by    
1
-pod/perlreapi.pod      Verbatim line length including indents exceeds 79 by    
17
+pod/perlreapi.pod      Verbatim line length including indents exceeds 79 by    
18
 pod/perlrebackslash.pod        Verbatim line length including indents exceeds 
79 by    1
 pod/perlref.pod        Verbatim line length including indents exceeds 79 by    
1
 pod/perlreguts.pod     Verbatim line length including indents exceeds 79 by    
17
diff --git a/t/re/pat_advanced.t b/t/re/pat_advanced.t
index 6692e1c..05cc191 100644
--- a/t/re/pat_advanced.t
+++ b/t/re/pat_advanced.t
@@ -1660,7 +1660,6 @@ $x='123';
 print ">$1<\n";
 EOP
 
-        local $::TODO = 'RT #86042';
         fresh_perl_is(<<'EOP', ">abc<\n", {}, 'no mention of $&');
 my $x; 
 ($x='abc')=~/(abc)/g; 
diff --git a/t/re/pat_psycho.t b/t/re/pat_psycho.t
index c5073a5..0433760 100644
--- a/t/re/pat_psycho.t
+++ b/t/re/pat_psycho.t
@@ -3,6 +3,9 @@
 # This is a home for regular expression tests that don't fit into
 # the format supported by re/regexp.t.  If you want to add a test
 # that does fit that format, add it to re/re_tests, not here.
+#
+# this file includes test that my burn a lot of CPU or otherwise be heavy
+# on resources. Set env var $PERL_SKIP_PSYCHO_TEST to skip this file
 
 use strict;
 use warnings;
@@ -21,7 +24,8 @@ BEGIN {
 }
 
 
-plan tests => 11;  # Update this when adding/deleting tests.
+skip_all('$PERL_SKIP_PSYCHO_TEST set') if $ENV{PERL_SKIP_PSYCHO_TEST};
+plan tests => 15;  # Update this when adding/deleting tests.
 
 run_tests() unless caller;
 
@@ -29,16 +33,17 @@ run_tests() unless caller;
 # Tests start here.
 #
 sub run_tests {
+    print "# Set PERL_SKIP_PSYCHO_TEST to skip these tests\n";
 
-  SKIP:
     {
-        print "# Set PERL_SKIP_PSYCHO_TEST to skip this test\n";
-        my @normal = qw [the are some normal words];
 
-        skip "Skipped Psycho", 2 * @normal if $ENV {PERL_SKIP_PSYCHO_TEST};
+       # stress test tries
+
+        my @normal = qw [the are some normal words];
 
         local $" = "|";
 
+       note "setting up trie psycho vars ...";
         my @psycho = (@normal, map chr $_, 255 .. 20000);
         my $psycho1 = "@psycho";
         for (my $i = @psycho; -- $i;) {
@@ -48,13 +53,12 @@ sub run_tests {
         my $psycho2 = "@psycho";
 
         foreach my $word (@normal) {
-            ok $word =~ /($psycho1)/ && $1 eq $word, 'Psycho';
-            ok $word =~ /($psycho2)/ && $1 eq $word, 'Psycho';
+            ok $word =~ /($psycho1)/ && $1 eq $word, qq{"$word" =~ 
/\$psycho1/};
+            ok $word =~ /($psycho2)/ && $1 eq $word, qq{"$word" =~ 
/\$psycho1/};
         }
     }
 
 
-  SKIP:
     {
         # stress test CURLYX/WHILEM.
         #
@@ -63,8 +67,6 @@ sub run_tests {
         # CURLYX and WHILEM blocks, except those related to LONGJMP, the
         # super-linear cache and warnings. It executes about 0.5M regexes
 
-        skip "No psycho tests" if $ENV {PERL_SKIP_PSYCHO_TEST};
-        print "# Set PERL_SKIP_PSYCHO_TEST to skip this test\n";
         my $r = qr/^
                     (?:
                         ( (?:a|z+)+ )
@@ -158,6 +160,49 @@ sub run_tests {
         }
         ok($ok, $msg);
     }
+
+
+    {
+       # these bits of test code used to run quadratically. If we break
+       # anything, they'll start to take minutes to run, rather than
+       # seconds. We don't actually measure times or set alarms, since
+       # that tends to be very fragile and prone to false positives.
+       # Instead, just hope that if someone is messing with
+       # performance-related code, they'll re-run the test suite and
+       # notice it suddenly takes a lot longer.
+
+       my $x;
+
+       $x = 'x' x 1_000_000;
+       1 while $x =~ /(.)/g;
+       pass "ascii =~ /(.)/";
+
+       {
+           local ${^UTF8CACHE} = 1; # defeat debugging
+           $x = "\x{100}" x 1_000_000;
+           1 while $x =~ /(.)/g;
+           pass "utf8 =~ /(.)/";
+       }
+
+       # run these in separate processes, since they set $&
+
+        fresh_perl_is(<<'EOF', "ok\n", {}, 'ascii =~ /(.)/, mention $&');
+$&;
+$x = 'x' x 1_000_000;
+1 while $x =~ /(.)/g;
+print "ok\n";
+EOF
+
+        fresh_perl_is(<<'EOF', "ok\n", {}, 'utf8 =~ /(.)/, mention $&');
+$&;
+local ${^UTF8CACHE} = 1; # defeat debugging
+$x = "\x{100}" x 1_000_000;
+1 while $x =~ /(.)/g;
+print "ok\n";
+EOF
+
+
+    }
 } # End of sub run_tests
 
 1;
diff --git a/t/re/re_tests b/t/re/re_tests
index f44bdc1..1aebbe6 100644
--- a/t/re/re_tests
+++ b/t/re/re_tests
@@ -163,6 +163,7 @@ ab|cd       abcd    y       $&      ab
 ()ef   def     y       $&-$1   ef-
 ()ef   def     y       $-[0]   1
 ()ef   def     y       $+[0]   3
+()\x{100}\x{1000}      d\x{100}\x{1000}        y       $+[0]   3
 ()ef   def     y       $-[1]   1
 ()ef   def     y       $+[1]   1
 *a     -       c       -       Quantifier follows nothing
@@ -1702,5 +1703,10 @@ ab[c\\\](??{"x"})]{3}d   ab\\](d y       -       -
 \W     \x{200D}        n       -       -
 
 /^(?d:\xdf|_)*_/i      \x{17f}\x{17f}_ y       $&      \x{17f}\x{17f}_
+#
+# check that @-, @+ count chars, not bytes; especially if beginning of
+# string is not copied
+
+(\x{100})      \x{2000}\x{2000}\x{2000}\x{100} y       $-[0]:$-[1]:$+[0]:$+[1] 
3:3:4:4
 
 # vim: softtabstop=0 noexpandtab
diff --git a/t/re/reg_pmod.t b/t/re/reg_pmod.t
index 301aeef..3190e03 100644
--- a/t/re/reg_pmod.t
+++ b/t/re/reg_pmod.t
@@ -11,38 +11,52 @@ use warnings;
 
 our @tests = (
     # /p      Pattern   PRE     MATCH   POST
-    [ '/p',   "456",    "123-", "456",  "-789"],
-    [ '(?p)', "456",    "123-", "456",  "-789"],
-    [ '',     "(456)",  "123-", "456",  "-789"],
-    [ '',     "456",    undef,  undef,  undef ],
+    [ '/p',   "345",    "12-", "345",  "-6789"],
+    [ '(?p)', "345",    "12-", "345",  "-6789"],
+    [ '',     "(345)",  undef,  undef,  undef ],
+    [ '',     "345",    undef,  undef,  undef ],
 );
 
-plan tests => 4 * @tests + 2;
+plan tests => 14 * @tests + 2;
 my $W = "";
 
 $SIG{__WARN__} = sub { $W.=join("",@_); };
 sub _u($$) { "$_[0] is ".(defined $_[1] ? "'$_[1]'" : "undef") }
 
-$_ = '123-456-789';
 foreach my $test (@tests) {
     my ($p, $pat,$l,$m,$r) = @$test;
-    my $test_name = $p eq '/p'   ? "/$pat/p"
-                  : $p eq '(?p)' ? "/(?p)$pat/"
-                  :                "/$pat/";
+    for my $sub (0,1) {
+       my $test_name = $p eq '/p'   ? "/$pat/p"
+                     : $p eq '(?p)' ? "/(?p)$pat/"
+                     :                "/$pat/";
+       $test_name = "s$test_name" if $sub;
 
-    #
-    # Cannot use if/else due to the scope invalidating ${^MATCH} and friends.
-    #
-    my $ok = ok $p eq '/p'   ? /$pat/p
-              : $p eq '(?p)' ? /(?p)$pat/
-              :                /$pat/
-              => $test_name;
-    SKIP: {
-        skip "/$pat/$p failed to match", 3
-            unless $ok;
-        is(${^PREMATCH},  $l,_u "$test_name: ^PREMATCH",$l);
-        is(${^MATCH},     $m,_u "$test_name: ^MATCH",$m );
-        is(${^POSTMATCH}, $r,_u "$test_name: ^POSTMATCH",$r );
+       #
+       # Cannot use if/else due to the scope invalidating ${^MATCH} and 
friends.
+       #
+       $_ = '12-345-6789';
+       my $ok =
+               $sub ?
+                       (   $p eq '/p'   ? s/$pat/abc/p
+                         : $p eq '(?p)' ? s/(?p)$pat/abc/
+                         :                s/$pat/abc/
+                       )
+                    :
+                       (   $p eq '/p'   ? /$pat/p
+                         : $p eq '(?p)' ? /(?p)$pat/
+                         :                /$pat/
+                       );
+       ok $ok, $test_name;
+       SKIP: {
+           skip "/$pat/$p failed to match", 6
+               unless $ok;
+           is(${^PREMATCH},  $l,_u "$test_name: ^PREMATCH",$l);
+           is(${^MATCH},     $m,_u "$test_name: ^MATCH",$m );
+           is(${^POSTMATCH}, $r,_u "$test_name: ^POSTMATCH",$r );
+           is(length ${^PREMATCH}, length $l, "$test_name: ^PREMATCH length");
+           is(length ${^MATCH},    length $m, "$test_name: ^MATCH length");
+           is(length ${^POSTMATCH},length $r, "$test_name: ^POSTMATCH length");
+       }
     }
 }
 is($W,"","No warnings should be produced");

--
Perl5 Master Repository

Reply via email to