develooper Front page | perl.perl5.porters | Postings from May 2016

[perl #127952] Perl_ck_refassign: Assertion `left->op_type ==OP_SREFGEN' failed (op.c:10498)

Aaron Crane via RT
May 15, 2016 16:58
[perl #127952] Perl_ck_refassign: Assertion `left->op_type ==OP_SREFGEN' failed (op.c:10498)
Message ID:
Zefram <> wrote:
> Brian Carpenter wrote:
>>perl -e '0,!n||!\r=0' triggers an assertion failure
> The initial "0," is not required, but all of the rest is.  The assertion
> is that the lhs of the assignment is an srefgen op, and it's found to
> actually be an ex-not op whose child is an srefgen op.  Something goes
> awry in optimising that lhs: that not op can't be validly optimised out.
> The same bug can be seen when using the same expression in rvalue context:
> $ perl -le '$a = !n||!\r; print $a || "false"'
> SCALAR(0xc8eb08)
> This clearly should have printed "false".  Nearby expressions behave
> as expected:
> $ perl -le '$a = !"n"||!\r; print $a || "false"'
> false
> $ perl -le '$a = !n||!\"r"; print $a || "false"'
> false
> $ perl -le '$a = !!!n||!\r; print $a || "false"'
> false
> $ perl -le '$a = !n||\r; print $a || "false"'
> SCALAR(0x246fb08)
> $ perl -le '$a = n||!\r; print $a || "false"'
> n
> $ perl -le '$a = n||\r; print $a || "false"'
> n

Thanks — your analysis there let me work out what was going on here. Other incorrect cases include:

$ perl -le 'print scalar( !r || !n )'
$ perl -le 'print scalar( !r && !n )'
$ perl -le 'print scalar( !do { "r" } || !n )'
$ perl -le 'print scalar( !do { "r" } && !n )'

This bug is fixed in blead by f15d05806fb7522031b75cb5a8784727ae03b98a:

[perl #127952] misoptimization for negated constant-ish on lhs of logop

Negations were being incorrectly deleted from the op tree for an OP_AND or
OP_OR (corresponding to Perl code with any of `&& || and or`, or postfix
"if" or "unless") whose left-hand side looks like "!BAREWORD" or "!do {
'const' }" and whose right-hand side is a non-constant-foldable negation.

The symptom in the reported case was an assertion failure in ck_refassign
for an srefgen op, caused by such an OP_NOT having been nulled. But other
cases exist that instead yielded incorrect results.

The underlying cause is that two optimisations in S_new_logop() were
partially interfering with each other. One of those attempts to optimise
code like "!$x && !$y" to the equivalent of "!($x || $y)", saving a
negation op; this is valid by De Morgan's laws. If it detects code of
this form, it nulls out the negations on each side of the "&&", and makes
a note to wrap the op it generates inside a new OP_NOT.

The other optimisation looks at the left-hand arm, and if it's a constant at
compile time, avoids the entire logop in favour of directly evaluating the
lhs or rhs as appropriate, and eliding whichever arm is no longer needed.
This optimisation is important for code like this:

    use constant DEBUG => …;
    print_debug_output() if DEBUG;

because it allows the entire statement to be eliminated when DEBUG is false.

When both conditions were true simultaneously, the De Morgan optimisation
was applied before the constant-based arm elision. But the arm elision
involved returning early from S_new_logop(), so the code later in that
function that wraps the generated op in a new OP_NOT never had a chance to
run. This meant that "!X && !Y" when X is constant was being compiled as if
it were in fact "X || Y", which is clearly incorrect.

This is, however, a very rare situation: it requires the lhs to be an OP_NOT
that dominates an OP_CONST (possibly with some intervening OP_LINESEQ or
similar). But OP_NOT is constant-foldable, so that doesn't normally happen.
The two ways for it to happen are:

- The constant is a bareword (since even though barewords are constants,
  they don't currently participate in constant folding)

- The constant is hidden inside one or more layers of do{} (since that
  serves as a barrier to constant folding, but the arm-elision optimisation
  is smart enough to search recursively through the optree for the real

The fix is much simpler than the explanation: apply the optimisations in the
opposite order, so that when arm elision returns early, the negation ops
haven't yet been nulled.

Aaron Crane **

via perlbug:  queue: perl5 status: open Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at | Group listing | About