summaryrefslogtreecommitdiff
path: root/t/opbasic
diff options
context:
space:
mode:
authorDavid Mitchell <davem@iabyn.com>2017-08-08 18:42:14 +0100
committerDavid Mitchell <davem@iabyn.com>2017-10-31 15:31:26 +0000
commite839e6ed99c6b25aee589f56bb58de2f8fa00f41 (patch)
tree30bab03fdd8e73c4cb6e5b2d33ab1f428693a3a8 /t/opbasic
parentc0acf911f65b2badbd72efa28edb2d197639a51b (diff)
downloadperl-e839e6ed99c6b25aee589f56bb58de2f8fa00f41.tar.gz
Add OP_MULTICONCAT op
Allow multiple OP_CONCAT, OP_CONST ops, plus optionally an OP_SASSIGN or OP_STRINGIFY, to be combined into a single OP_MULTICONCAT op, which can make things a *lot* faster: 4x or more. In more detail: it will optimise into a single OP_MULTICONCAT, most expressions of the form LHS RHS where LHS is one of (empty) my $lexical = $lexical = $lexical .= expression = expression .= and RHS is one of (A . B . C . ...) where A,B,C etc are expressions and/or string constants "aAbBc..." where a,A,b,B etc are expressions and/or string constants sprintf "..%s..%s..", A,B,.. where the format is a constant string containing only '%s' and '%%' elements, and A,B, etc are scalar expressions (so only a fixed, compile-time-known number of args: no arrays or list context function calls etc) It doesn't optimise other forms, such as ($a . $b) . ($c. $d) ((($a .= $b) .= $c) .= $d); (although sub-parts of those expressions might be converted to an OP_MULTICONCAT). This is partly because it would be hard to maintain the correct ordering of tie or overload calls. The compiler uses heuristics to determine when to convert: in general, expressions involving a single OP_CONCAT aren't converted, unless some other saving can be made, for example if an OP_CONST can be eliminated, or in the presence of 'my $x = .. ' which OP_MULTICONCAT can apply OPpTARGET_MY to, but OP_CONST can't. The multiconcat op is of type UNOP_AUX, with the op_aux structure directly holding a pointer to a single constant char* string plus a list of segment lengths. So for "a=$a b=$b\n"; the constant string is "a= b=\n", and the segment lengths are (2,3,1). If the constant string has different non-utf8 and utf8 representations (such as "\x80") then both variants are pre-computed and stored in the aux struct, along with two sets of segment lengths. For all the above LHS types, any SASSIGN op is optimised away. For a LHS of '$lex=', '$lex.=' or 'my $lex=', the PADSV is optimised away too. For example where $a and $b are lexical vars, this statement: my $c = "a=$a, b=$b\n"; formerly compiled to const[PV "a="] s padsv[$a:1,3] s concat[t4] sK/2 const[PV ", b="] s concat[t5] sKS/2 padsv[$b:1,3] s concat[t6] sKS/2 const[PV "\n"] s concat[t7] sKS/2 padsv[$c:2,3] sRM*/LVINTRO sassign vKS/2 and now compiles to: padsv[$a:1,3] s padsv[$b:1,3] s multiconcat("a=, b=\n",2,4,1)[$c:2,3] vK/LVINTRO,TARGMY,STRINGIFY In terms of how much faster it is, this code: my $a = "the quick brown fox jumps over the lazy dog"; my $b = "to be, or not to be; sorry, what was the question again?"; for my $i (1..10_000_000) { my $c = "a=$a, b=$b\n"; } runs 2.7 times faster, and if you throw utf8 mixtures in it gets even better. This loop runs 4 times faster: my $s; my $a = "ab\x{100}cde"; my $b = "fghij"; my $c = "\x{101}klmn"; for my $i (1..10_000_000) { $s = "\x{100}wxyz"; $s .= "foo=$a bar=$b baz=$c"; } The main ways in which OP_MULTICONCAT gains its speed are: * any OP_CONSTs are eliminated, and the constant bits (already in the right encoding) are copied directly from the constant string attached to the op's aux structure. * It optimises away any SASSIGN op, and possibly a PADSV op on the LHS, in all cases; OP_CONCAT only did this in very limited circumstances. * Because it has a holistic view of the entire concatenation expression, it can do the whole thing in one efficient go, rather than creating and copying intermediate results. pp_multiconcat() goes to considerable efforts to avoid inefficiencies. For example it will only SvGROW() the target once, and to the exact size needed, no matter what mix of utf8 and non-utf8 appear on the LHS and RHS. It never allocates any temporary SVs except possibly in the case of tie or overloading. * It does all its own appending and utf8 handling rather than calling out to functions like sv_catsv(). * It's very good at handling the LHS appearing on the RHS; for example in $x = "abcd"; $x = "-$x-$x-"; It will do roughly the equivalent of the following (where targ is $x); SvPV_force(targ); SvGROW(targ, 11); p = SvPVX(targ); Move(p, p+1, 4, char); Copy("-", p, 1, char); Copy("-", p+5, 1, char); Copy(p+1, p+6, 4, char); Copy("-", p+10, 1, char); SvCUR(targ) = 11; p[11] = '\0'; Formerly, pp_concat would have used multiple PADTMPs or temporary SVs to handle situations like that. The code is quite big; both S_maybe_multiconcat() and pp_multiconcat() (the main compile-time and runtime parts of the implementation) are over 700 lines each. It turns out that when you combine multiple ops, the number of edge cases grows exponentially ;-)
Diffstat (limited to 't/opbasic')
-rw-r--r--t/opbasic/concat.t651
1 files changed, 646 insertions, 5 deletions
diff --git a/t/opbasic/concat.t b/t/opbasic/concat.t
index 7802fc98ce..55965c1702 100644
--- a/t/opbasic/concat.t
+++ b/t/opbasic/concat.t
@@ -5,12 +5,13 @@ BEGIN {
@INC = '../lib';
}
-# ok() functions from other sources (e.g., t/test.pl) may use concatenation,
-# but that is what is being tested in this file. Hence, we place this file
-# in the directory where do not use t/test.pl, and we write an ok() function
-# specially written to avoid any concatenation.
+# ok()/is() functions from other sources (e.g., t/test.pl) may use
+# concatenation, but that is what is being tested in this file. Hence, we
+# place this file in the directory where do not use t/test.pl, and we
+# write functions specially written to avoid any concatenation.
my $test = 1;
+
sub ok {
my($ok, $name) = @_;
@@ -22,7 +23,23 @@ sub ok {
return $ok;
}
-print "1..31\n";
+sub is {
+ my($got, $expected, $name) = @_;
+
+ my $ok = $got eq $expected;
+
+ printf "%sok %d - %s\n", ($ok ? "" : "not "), $test, $name;
+
+ if (!$ok) {
+ printf "# Failed test at line %d\n", (caller)[2];
+ printf "# got: %s\n#expected: %s\n", $got, $expected;
+ }
+
+ $test++;
+ return $ok;
+}
+
+print "1..251\n";
($a, $b, $c) = qw(foo bar);
@@ -132,6 +149,7 @@ sub beq { use bytes; $_[0] eq $_[1]; }
my $up = "\x{100}\xB6";
my $x1 = $p;
my $y1 = $u;
+ my ($x2, $x3, $x4, $y2);
use bytes;
ok(beq($p.$u, $p.$b), "perl #26905, left eq bytes");
@@ -144,11 +162,15 @@ sub beq { use bytes; $_[0] eq $_[1]; }
$y1 .= $p;
$y2 = $u . $p;
+ $x3 = $p; $x3 .= $u . $u;
+ $x4 = $p . $u . $u;
+
no bytes;
ok(beq($x1, $x2), "perl #26905, left, .= vs = . in bytes");
ok(beq($y1, $y2), "perl #26905, right, .= vs = . in bytes");
ok(($x1 eq $x2), "perl #26905, left, .= vs = . in chars");
ok(($y1 eq $y2), "perl #26905, right, .= vs = . in chars");
+ ok(($x3 eq $x4), "perl #26905, twin, .= vs = . in chars");
}
{
@@ -164,8 +186,627 @@ sub beq { use bytes; $_[0] eq $_[1]; }
ok($x eq "ab-append-", "Appending to something initialized using constant folding");
}
+# non-POK consts
+
+{
+ my $a = "a";
+ my $b;
+ $b = $a . $a . 1;
+ ok($b eq "aa1", "aa1");
+ $b = 2 . $a . $a;
+ ok($b eq "2aa", "2aa");
+}
+
# [perl #124160]
package o { use overload "." => sub { $_[0] }, fallback => 1 }
$o = bless [], "o";
ok(ref(CORE::state $y = "a $o b") eq 'o',
'state $y = "foo $bar baz" does not stringify; only concats');
+
+
+# multiconcat: utf8 dest with non-utf8 args should grow dest sufficiently.
+# This is mainly for valgrind or ASAN to detect problems with.
+
+{
+ my $s = "\x{100}";
+ my $t = "\x80" x 1024;
+ $s .= "-$t-";
+ ok length($s) == 1027, "utf8 dest with non-utf8 args";
+}
+
+# target on RHS
+
+{
+ my $a = "abc";
+ $a .= $a;
+ ok($a eq 'abcabc', 'append self');
+
+ $a = "abc";
+ $a = $a . $a;
+ ok($a eq 'abcabc', 'double self');
+
+ $a = "abc";
+ $a .= $a . $a;
+ ok($a eq 'abcabcabc', 'append double self');
+
+ $a = "abc";
+ $a = "$a-$a";
+ ok($a eq 'abc-abc', 'double self with const');
+
+ $a = "abc";
+ $a .= "$a-$a";
+ ok($a eq 'abcabc-abc', 'append double self with const');
+
+ $a = "abc";
+ $a .= $a . $a . $a;
+ ok($a eq 'abcabcabcabc', 'append triple self');
+
+ $a = "abc";
+ $a = "$a-$a=$a";
+ ok($a eq 'abc-abc=abc', 'triple self with const');
+
+ $a = "abc";
+ $a .= "$a-$a=$a";
+ ok($a eq 'abcabc-abc=abc', 'append triple self with const');
+}
+
+# test the sorts of optree which may (or may not) get optimised into
+# a single MULTICONCAT op. It's based on a loop in t/perf/opcount.t,
+# but here the loop is unwound as we would need to use concat to
+# generate the expected results to compare with the actual results,
+# which would rather defeat the object.
+
+{
+ my ($a1, $a2, $a3) = qw(1 2 3);
+ our $pkg;
+ my $lex;
+
+ is("-", '-', '"-"');
+ is("-", '-', '"-"');
+ is("-", '-', '"-"');
+ is("-", '-', '"-"');
+ is($a1, '1', '$a1');
+ is("-".$a1, '-1', '"-".$a1');
+ is($a1."-", '1-', '$a1."-"');
+ is("-".$a1."-", '-1-', '"-".$a1."-"');
+ is("$a1", '1', '"$a1"');
+ is("-$a1", '-1', '"-$a1"');
+ is("$a1-", '1-', '"$a1-"');
+ is("-$a1-", '-1-', '"-$a1-"');
+ is($a1.$a2, '12', '$a1.$a2');
+ is($a1."-".$a2, '1-2', '$a1."-".$a2');
+ is("-".$a1."-".$a2, '-1-2', '"-".$a1."-".$a2');
+ is($a1."-".$a2."-", '1-2-', '$a1."-".$a2."-"');
+ is("-".$a1."-".$a2."-", '-1-2-', '"-".$a1."-".$a2."-"');
+ is("$a1$a2", '12', '"$a1$a2"');
+ is("$a1-$a2", '1-2', '"$a1-$a2"');
+ is("-$a1-$a2", '-1-2', '"-$a1-$a2"');
+ is("$a1-$a2-", '1-2-', '"$a1-$a2-"');
+ is("-$a1-$a2-", '-1-2-', '"-$a1-$a2-"');
+ is($a1.$a2.$a3, '123', '$a1.$a2.$a3');
+ is($a1."-".$a2."-".$a3, '1-2-3', '$a1."-".$a2."-".$a3');
+ is("-".$a1."-".$a2."-".$a3, '-1-2-3', '"-".$a1."-".$a2."-".$a3');
+ is($a1."-".$a2."-".$a3."-", '1-2-3-', '$a1."-".$a2."-".$a3."-"');
+ is("-".$a1."-".$a2."-".$a3."-", '-1-2-3-', '"-".$a1."-".$a2."-".$a3."-"');
+ is("$a1$a2$a3", '123', '"$a1$a2$a3"');
+ is("$a1-$a2-$a3", '1-2-3', '"$a1-$a2-$a3"');
+ is("-$a1-$a2-$a3", '-1-2-3', '"-$a1-$a2-$a3"');
+ is("$a1-$a2-$a3-", '1-2-3-', '"$a1-$a2-$a3-"');
+ is("-$a1-$a2-$a3-", '-1-2-3-', '"-$a1-$a2-$a3-"');
+ $pkg = "-";
+ is($pkg, '-', '$pkg = "-"');
+ $pkg = "-";
+ is($pkg, '-', '$pkg = "-"');
+ $pkg = "-";
+ is($pkg, '-', '$pkg = "-"');
+ $pkg = "-";
+ is($pkg, '-', '$pkg = "-"');
+ $pkg = $a1;
+ is($pkg, '1', '$pkg = $a1');
+ $pkg = "-".$a1;
+ is($pkg, '-1', '$pkg = "-".$a1');
+ $pkg = $a1."-";
+ is($pkg, '1-', '$pkg = $a1."-"');
+ $pkg = "-".$a1."-";
+ is($pkg, '-1-', '$pkg = "-".$a1."-"');
+ $pkg = "$a1";
+ is($pkg, '1', '$pkg = "$a1"');
+ $pkg = "-$a1";
+ is($pkg, '-1', '$pkg = "-$a1"');
+ $pkg = "$a1-";
+ is($pkg, '1-', '$pkg = "$a1-"');
+ $pkg = "-$a1-";
+ is($pkg, '-1-', '$pkg = "-$a1-"');
+ $pkg = $a1.$a2;
+ is($pkg, '12', '$pkg = $a1.$a2');
+ $pkg = $a1."-".$a2;
+ is($pkg, '1-2', '$pkg = $a1."-".$a2');
+ $pkg = "-".$a1."-".$a2;
+ is($pkg, '-1-2', '$pkg = "-".$a1."-".$a2');
+ $pkg = $a1."-".$a2."-";
+ is($pkg, '1-2-', '$pkg = $a1."-".$a2."-"');
+ $pkg = "-".$a1."-".$a2."-";
+ is($pkg, '-1-2-', '$pkg = "-".$a1."-".$a2."-"');
+ $pkg = "$a1$a2";
+ is($pkg, '12', '$pkg = "$a1$a2"');
+ $pkg = "$a1-$a2";
+ is($pkg, '1-2', '$pkg = "$a1-$a2"');
+ $pkg = "-$a1-$a2";
+ is($pkg, '-1-2', '$pkg = "-$a1-$a2"');
+ $pkg = "$a1-$a2-";
+ is($pkg, '1-2-', '$pkg = "$a1-$a2-"');
+ $pkg = "-$a1-$a2-";
+ is($pkg, '-1-2-', '$pkg = "-$a1-$a2-"');
+ $pkg = $a1.$a2.$a3;
+ is($pkg, '123', '$pkg = $a1.$a2.$a3');
+ $pkg = $a1."-".$a2."-".$a3;
+ is($pkg, '1-2-3', '$pkg = $a1."-".$a2."-".$a3');
+ $pkg = "-".$a1."-".$a2."-".$a3;
+ is($pkg, '-1-2-3', '$pkg = "-".$a1."-".$a2."-".$a3');
+ $pkg = $a1."-".$a2."-".$a3."-";
+ is($pkg, '1-2-3-', '$pkg = $a1."-".$a2."-".$a3."-"');
+ $pkg = "-".$a1."-".$a2."-".$a3."-";
+ is($pkg, '-1-2-3-', '$pkg = "-".$a1."-".$a2."-".$a3."-"');
+ $pkg = "$a1$a2$a3";
+ is($pkg, '123', '$pkg = "$a1$a2$a3"');
+ $pkg = "$a1-$a2-$a3";
+ is($pkg, '1-2-3', '$pkg = "$a1-$a2-$a3"');
+ $pkg = "-$a1-$a2-$a3";
+ is($pkg, '-1-2-3', '$pkg = "-$a1-$a2-$a3"');
+ $pkg = "$a1-$a2-$a3-";
+ is($pkg, '1-2-3-', '$pkg = "$a1-$a2-$a3-"');
+ $pkg = "-$a1-$a2-$a3-";
+ is($pkg, '-1-2-3-', '$pkg = "-$a1-$a2-$a3-"');
+ $pkg = 'P';
+ $pkg .= "-";
+ is($pkg, 'P-', '$pkg .= "-"');
+ $pkg = 'P';
+ $pkg .= "-";
+ is($pkg, 'P-', '$pkg .= "-"');
+ $pkg = 'P';
+ $pkg .= "-";
+ is($pkg, 'P-', '$pkg .= "-"');
+ $pkg = 'P';
+ $pkg .= "-";
+ is($pkg, 'P-', '$pkg .= "-"');
+ $pkg = 'P';
+ $pkg .= $a1;
+ is($pkg, 'P1', '$pkg .= $a1');
+ $pkg = 'P';
+ $pkg .= "-".$a1;
+ is($pkg, 'P-1', '$pkg .= "-".$a1');
+ $pkg = 'P';
+ $pkg .= $a1."-";
+ is($pkg, 'P1-', '$pkg .= $a1."-"');
+ $pkg = 'P';
+ $pkg .= "-".$a1."-";
+ is($pkg, 'P-1-', '$pkg .= "-".$a1."-"');
+ $pkg = 'P';
+ $pkg .= "$a1";
+ is($pkg, 'P1', '$pkg .= "$a1"');
+ $pkg = 'P';
+ $pkg .= "-$a1";
+ is($pkg, 'P-1', '$pkg .= "-$a1"');
+ $pkg = 'P';
+ $pkg .= "$a1-";
+ is($pkg, 'P1-', '$pkg .= "$a1-"');
+ $pkg = 'P';
+ $pkg .= "-$a1-";
+ is($pkg, 'P-1-', '$pkg .= "-$a1-"');
+ $pkg = 'P';
+ $pkg .= $a1.$a2;
+ is($pkg, 'P12', '$pkg .= $a1.$a2');
+ $pkg = 'P';
+ $pkg .= $a1."-".$a2;
+ is($pkg, 'P1-2', '$pkg .= $a1."-".$a2');
+ $pkg = 'P';
+ $pkg .= "-".$a1."-".$a2;
+ is($pkg, 'P-1-2', '$pkg .= "-".$a1."-".$a2');
+ $pkg = 'P';
+ $pkg .= $a1."-".$a2."-";
+ is($pkg, 'P1-2-', '$pkg .= $a1."-".$a2."-"');
+ $pkg = 'P';
+ $pkg .= "-".$a1."-".$a2."-";
+ is($pkg, 'P-1-2-', '$pkg .= "-".$a1."-".$a2."-"');
+ $pkg = 'P';
+ $pkg .= "$a1$a2";
+ is($pkg, 'P12', '$pkg .= "$a1$a2"');
+ $pkg = 'P';
+ $pkg .= "$a1-$a2";
+ is($pkg, 'P1-2', '$pkg .= "$a1-$a2"');
+ $pkg = 'P';
+ $pkg .= "-$a1-$a2";
+ is($pkg, 'P-1-2', '$pkg .= "-$a1-$a2"');
+ $pkg = 'P';
+ $pkg .= "$a1-$a2-";
+ is($pkg, 'P1-2-', '$pkg .= "$a1-$a2-"');
+ $pkg = 'P';
+ $pkg .= "-$a1-$a2-";
+ is($pkg, 'P-1-2-', '$pkg .= "-$a1-$a2-"');
+ $pkg = 'P';
+ $pkg .= $a1.$a2.$a3;
+ is($pkg, 'P123', '$pkg .= $a1.$a2.$a3');
+ $pkg = 'P';
+ $pkg .= $a1."-".$a2."-".$a3;
+ is($pkg, 'P1-2-3', '$pkg .= $a1."-".$a2."-".$a3');
+ $pkg = 'P';
+ $pkg .= "-".$a1."-".$a2."-".$a3;
+ is($pkg, 'P-1-2-3', '$pkg .= "-".$a1."-".$a2."-".$a3');
+ $pkg = 'P';
+ $pkg .= $a1."-".$a2."-".$a3."-";
+ is($pkg, 'P1-2-3-', '$pkg .= $a1."-".$a2."-".$a3."-"');
+ $pkg = 'P';
+ $pkg .= "-".$a1."-".$a2."-".$a3."-";
+ is($pkg, 'P-1-2-3-', '$pkg .= "-".$a1."-".$a2."-".$a3."-"');
+ $pkg = 'P';
+ $pkg .= "$a1$a2$a3";
+ is($pkg, 'P123', '$pkg .= "$a1$a2$a3"');
+ $pkg = 'P';
+ $pkg .= "$a1-$a2-$a3";
+ is($pkg, 'P1-2-3', '$pkg .= "$a1-$a2-$a3"');
+ $pkg = 'P';
+ $pkg .= "-$a1-$a2-$a3";
+ is($pkg, 'P-1-2-3', '$pkg .= "-$a1-$a2-$a3"');
+ $pkg = 'P';
+ $pkg .= "$a1-$a2-$a3-";
+ is($pkg, 'P1-2-3-', '$pkg .= "$a1-$a2-$a3-"');
+ $pkg = 'P';
+ $pkg .= "-$a1-$a2-$a3-";
+ is($pkg, 'P-1-2-3-', '$pkg .= "-$a1-$a2-$a3-"');
+ $lex = "-";
+ is($lex, '-', '$lex = "-"');
+ $lex = "-";
+ is($lex, '-', '$lex = "-"');
+ $lex = "-";
+ is($lex, '-', '$lex = "-"');
+ $lex = "-";
+ is($lex, '-', '$lex = "-"');
+ $lex = $a1;
+ is($lex, '1', '$lex = $a1');
+ $lex = "-".$a1;
+ is($lex, '-1', '$lex = "-".$a1');
+ $lex = $a1."-";
+ is($lex, '1-', '$lex = $a1."-"');
+ $lex = "-".$a1."-";
+ is($lex, '-1-', '$lex = "-".$a1."-"');
+ $lex = "$a1";
+ is($lex, '1', '$lex = "$a1"');
+ $lex = "-$a1";
+ is($lex, '-1', '$lex = "-$a1"');
+ $lex = "$a1-";
+ is($lex, '1-', '$lex = "$a1-"');
+ $lex = "-$a1-";
+ is($lex, '-1-', '$lex = "-$a1-"');
+ $lex = $a1.$a2;
+ is($lex, '12', '$lex = $a1.$a2');
+ $lex = $a1."-".$a2;
+ is($lex, '1-2', '$lex = $a1."-".$a2');
+ $lex = "-".$a1."-".$a2;
+ is($lex, '-1-2', '$lex = "-".$a1."-".$a2');
+ $lex = $a1."-".$a2."-";
+ is($lex, '1-2-', '$lex = $a1."-".$a2."-"');
+ $lex = "-".$a1."-".$a2."-";
+ is($lex, '-1-2-', '$lex = "-".$a1."-".$a2."-"');
+ $lex = "$a1$a2";
+ is($lex, '12', '$lex = "$a1$a2"');
+ $lex = "$a1-$a2";
+ is($lex, '1-2', '$lex = "$a1-$a2"');
+ $lex = "-$a1-$a2";
+ is($lex, '-1-2', '$lex = "-$a1-$a2"');
+ $lex = "$a1-$a2-";
+ is($lex, '1-2-', '$lex = "$a1-$a2-"');
+ $lex = "-$a1-$a2-";
+ is($lex, '-1-2-', '$lex = "-$a1-$a2-"');
+ $lex = $a1.$a2.$a3;
+ is($lex, '123', '$lex = $a1.$a2.$a3');
+ $lex = $a1."-".$a2."-".$a3;
+ is($lex, '1-2-3', '$lex = $a1."-".$a2."-".$a3');
+ $lex = "-".$a1."-".$a2."-".$a3;
+ is($lex, '-1-2-3', '$lex = "-".$a1."-".$a2."-".$a3');
+ $lex = $a1."-".$a2."-".$a3."-";
+ is($lex, '1-2-3-', '$lex = $a1."-".$a2."-".$a3."-"');
+ $lex = "-".$a1."-".$a2."-".$a3."-";
+ is($lex, '-1-2-3-', '$lex = "-".$a1."-".$a2."-".$a3."-"');
+ $lex = "$a1$a2$a3";
+ is($lex, '123', '$lex = "$a1$a2$a3"');
+ $lex = "$a1-$a2-$a3";
+ is($lex, '1-2-3', '$lex = "$a1-$a2-$a3"');
+ $lex = "-$a1-$a2-$a3";
+ is($lex, '-1-2-3', '$lex = "-$a1-$a2-$a3"');
+ $lex = "$a1-$a2-$a3-";
+ is($lex, '1-2-3-', '$lex = "$a1-$a2-$a3-"');
+ $lex = "-$a1-$a2-$a3-";
+ is($lex, '-1-2-3-', '$lex = "-$a1-$a2-$a3-"');
+ $lex = 'L';
+ $lex .= "-";
+ is($lex, 'L-', '$lex .= "-"');
+ $lex = 'L';
+ $lex .= "-";
+ is($lex, 'L-', '$lex .= "-"');
+ $lex = 'L';
+ $lex .= "-";
+ is($lex, 'L-', '$lex .= "-"');
+ $lex = 'L';
+ $lex .= "-";
+ is($lex, 'L-', '$lex .= "-"');
+ $lex = 'L';
+ $lex .= $a1;
+ is($lex, 'L1', '$lex .= $a1');
+ $lex = 'L';
+ $lex .= "-".$a1;
+ is($lex, 'L-1', '$lex .= "-".$a1');
+ $lex = 'L';
+ $lex .= $a1."-";
+ is($lex, 'L1-', '$lex .= $a1."-"');
+ $lex = 'L';
+ $lex .= "-".$a1."-";
+ is($lex, 'L-1-', '$lex .= "-".$a1."-"');
+ $lex = 'L';
+ $lex .= "$a1";
+ is($lex, 'L1', '$lex .= "$a1"');
+ $lex = 'L';
+ $lex .= "-$a1";
+ is($lex, 'L-1', '$lex .= "-$a1"');
+ $lex = 'L';
+ $lex .= "$a1-";
+ is($lex, 'L1-', '$lex .= "$a1-"');
+ $lex = 'L';
+ $lex .= "-$a1-";
+ is($lex, 'L-1-', '$lex .= "-$a1-"');
+ $lex = 'L';
+ $lex .= $a1.$a2;
+ is($lex, 'L12', '$lex .= $a1.$a2');
+ $lex = 'L';
+ $lex .= $a1."-".$a2;
+ is($lex, 'L1-2', '$lex .= $a1."-".$a2');
+ $lex = 'L';
+ $lex .= "-".$a1."-".$a2;
+ is($lex, 'L-1-2', '$lex .= "-".$a1."-".$a2');
+ $lex = 'L';
+ $lex .= $a1."-".$a2."-";
+ is($lex, 'L1-2-', '$lex .= $a1."-".$a2."-"');
+ $lex = 'L';
+ $lex .= "-".$a1."-".$a2."-";
+ is($lex, 'L-1-2-', '$lex .= "-".$a1."-".$a2."-"');
+ $lex = 'L';
+ $lex .= "$a1$a2";
+ is($lex, 'L12', '$lex .= "$a1$a2"');
+ $lex = 'L';
+ $lex .= "$a1-$a2";
+ is($lex, 'L1-2', '$lex .= "$a1-$a2"');
+ $lex = 'L';
+ $lex .= "-$a1-$a2";
+ is($lex, 'L-1-2', '$lex .= "-$a1-$a2"');
+ $lex = 'L';
+ $lex .= "$a1-$a2-";
+ is($lex, 'L1-2-', '$lex .= "$a1-$a2-"');
+ $lex = 'L';
+ $lex .= "-$a1-$a2-";
+ is($lex, 'L-1-2-', '$lex .= "-$a1-$a2-"');
+ $lex = 'L';
+ $lex .= $a1.$a2.$a3;
+ is($lex, 'L123', '$lex .= $a1.$a2.$a3');
+ $lex = 'L';
+ $lex .= $a1."-".$a2."-".$a3;
+ is($lex, 'L1-2-3', '$lex .= $a1."-".$a2."-".$a3');
+ $lex = 'L';
+ $lex .= "-".$a1."-".$a2."-".$a3;
+ is($lex, 'L-1-2-3', '$lex .= "-".$a1."-".$a2."-".$a3');
+ $lex = 'L';
+ $lex .= $a1."-".$a2."-".$a3."-";
+ is($lex, 'L1-2-3-', '$lex .= $a1."-".$a2."-".$a3."-"');
+ $lex = 'L';
+ $lex .= "-".$a1."-".$a2."-".$a3."-";
+ is($lex, 'L-1-2-3-', '$lex .= "-".$a1."-".$a2."-".$a3."-"');
+ $lex = 'L';
+ $lex .= "$a1$a2$a3";
+ is($lex, 'L123', '$lex .= "$a1$a2$a3"');
+ $lex = 'L';
+ $lex .= "$a1-$a2-$a3";
+ is($lex, 'L1-2-3', '$lex .= "$a1-$a2-$a3"');
+ $lex = 'L';
+ $lex .= "-$a1-$a2-$a3";
+ is($lex, 'L-1-2-3', '$lex .= "-$a1-$a2-$a3"');
+ $lex = 'L';
+ $lex .= "$a1-$a2-$a3-";
+ is($lex, 'L1-2-3-', '$lex .= "$a1-$a2-$a3-"');
+ $lex = 'L';
+ $lex .= "-$a1-$a2-$a3-";
+ is($lex, 'L-1-2-3-', '$lex .= "-$a1-$a2-$a3-"');
+ {
+ my $l = "-";
+ is($l, '-', 'my $l = "-"');
+ }
+ {
+ my $l = "-";
+ is($l, '-', 'my $l = "-"');
+ }
+ {
+ my $l = "-";
+ is($l, '-', 'my $l = "-"');
+ }
+ {
+ my $l = "-";
+ is($l, '-', 'my $l = "-"');
+ }
+ {
+ my $l = $a1;
+ is($l, '1', 'my $l = $a1');
+ }
+ {
+ my $l = "-".$a1;
+ is($l, '-1', 'my $l = "-".$a1');
+ }
+ {
+ my $l = $a1."-";
+ is($l, '1-', 'my $l = $a1."-"');
+ }
+ {
+ my $l = "-".$a1."-";
+ is($l, '-1-', 'my $l = "-".$a1."-"');
+ }
+ {
+ my $l = "$a1";
+ is($l, '1', 'my $l = "$a1"');
+ }
+ {
+ my $l = "-$a1";
+ is($l, '-1', 'my $l = "-$a1"');
+ }
+ {
+ my $l = "$a1-";
+ is($l, '1-', 'my $l = "$a1-"');
+ }
+ {
+ my $l = "-$a1-";
+ is($l, '-1-', 'my $l = "-$a1-"');
+ }
+ {
+ my $l = $a1.$a2;
+ is($l, '12', 'my $l = $a1.$a2');
+ }
+ {
+ my $l = $a1."-".$a2;
+ is($l, '1-2', 'my $l = $a1."-".$a2');
+ }
+ {
+ my $l = "-".$a1."-".$a2;
+ is($l, '-1-2', 'my $l = "-".$a1."-".$a2');
+ }
+ {
+ my $l = $a1."-".$a2."-";
+ is($l, '1-2-', 'my $l = $a1."-".$a2."-"');
+ }
+ {
+ my $l = "-".$a1."-".$a2."-";
+ is($l, '-1-2-', 'my $l = "-".$a1."-".$a2."-"');
+ }
+ {
+ my $l = "$a1$a2";
+ is($l, '12', 'my $l = "$a1$a2"');
+ }
+ {
+ my $l = "$a1-$a2";
+ is($l, '1-2', 'my $l = "$a1-$a2"');
+ }
+ {
+ my $l = "-$a1-$a2";
+ is($l, '-1-2', 'my $l = "-$a1-$a2"');
+ }
+ {
+ my $l = "$a1-$a2-";
+ is($l, '1-2-', 'my $l = "$a1-$a2-"');
+ }
+ {
+ my $l = "-$a1-$a2-";
+ is($l, '-1-2-', 'my $l = "-$a1-$a2-"');
+ }
+ {
+ my $l = $a1.$a2.$a3;
+ is($l, '123', 'my $l = $a1.$a2.$a3');
+ }
+ {
+ my $l = $a1."-".$a2."-".$a3;
+ is($l, '1-2-3', 'my $l = $a1."-".$a2."-".$a3');
+ }
+ {
+ my $l = "-".$a1."-".$a2."-".$a3;
+ is($l, '-1-2-3', 'my $l = "-".$a1."-".$a2."-".$a3');
+ }
+ {
+ my $l = $a1."-".$a2."-".$a3."-";
+ is($l, '1-2-3-', 'my $l = $a1."-".$a2."-".$a3."-"');
+ }
+ {
+ my $l = "-".$a1."-".$a2."-".$a3."-";
+ is($l, '-1-2-3-', 'my $l = "-".$a1."-".$a2."-".$a3."-"');
+ }
+ {
+ my $l = "$a1$a2$a3";
+ is($l, '123', 'my $l = "$a1$a2$a3"');
+ }
+ {
+ my $l = "$a1-$a2-$a3";
+ is($l, '1-2-3', 'my $l = "$a1-$a2-$a3"');
+ }
+ {
+ my $l = "-$a1-$a2-$a3";
+ is($l, '-1-2-3', 'my $l = "-$a1-$a2-$a3"');
+ }
+ {
+ my $l = "$a1-$a2-$a3-";
+ is($l, '1-2-3-', 'my $l = "$a1-$a2-$a3-"');
+ }
+ {
+ my $l = "-$a1-$a2-$a3-";
+ is($l, '-1-2-3-', 'my $l = "-$a1-$a2-$a3-"');
+ }
+}
+
+# multiconcat optimises away scalar assign, and is responsible
+# for handling the assign itself. If the LHS is something weird,
+# make sure it's handled ok
+
+{
+ my $a = 'a';
+ my $b = 'b';
+ my $o = 'o';
+
+ my $re = qr/abc/;
+ $$re = $a . $b;
+ is($$re, "ab", '$$re = $a . $b');
+
+ #passing a hash elem to a sub creates a PVLV
+ my $s = sub { $_[0] = $a . $b; };
+ my %h;
+ $s->($h{foo});
+ is($h{foo}, "ab", "PVLV");
+
+ # assigning a string to a typeglob creates an alias
+ $Foo = 'myfoo';
+ *Bar = ("F" . $o . $o);
+ is($Bar, "myfoo", '*Bar = "Foo"');
+
+ # while that same typeglob also appearing on the RHS returns
+ # a stringified value
+
+ package QPR {
+ ${'*QPR::Bar*QPR::BarBaz'} = 'myfoobarbaz';
+ *Bar = (*Bar . *Bar . "Baz");
+ ::is($Bar, "myfoobarbaz", '*Bar = (*Bar . *Bar . "Baz")');
+ }
+}
+
+# distinguish between '=' and '.=' where the LHS has the OPf_MOD flag
+
+{
+ my $foo = "foo";
+ my $a . $foo; # weird but legal
+ is($a, '', 'my $a . $foo');
+ my $b; $b .= $foo;
+ is($b, 'foo', 'my $b; $b .= $foo');
+}
+
+# distinguish between nested appends and concats; the former is
+# affected by the change of value of the target on each concat.
+# This is why multiconcat shouldn't be used in that case
+
+{
+ my $a = "a";
+ (($a .= $a) .= $a) .= $a;
+ is($a, "aaaaaaaa", '(($a .= $a) .= $a) .= $a;');
+}
+
+# check everything works ok near the max arg size of a multiconcat
+
+{
+ my @a = map "<$_>", 0..99;
+ for my $i (60..68) { # check each side of 64 threshold
+ my $c = join '.', map "\$a[$_]", 0..$i;
+ my $got = eval $c or die $@;
+ my $empty = ''; # don't use a const string in case join'' ever
+ # gets optimised into a multiconcat
+ my $expected = join $empty, @a[0..$i];
+ is($got, $expected, "long concat chain $i");
+ }
+}