Skip to content

Commit 35c3d59

Browse files
committed
op.c/peep.c - Optimise away empty else{} blocks
This is a replacement for #22745, which aimed to prevent the associated `OP_STUB` from being created in `Perl_newCONDOP` and subsequently removed in peep.c. However, doing that turned out to be unavoidable, since it is not safe to omit the `OP_STUB` if it is in scalar context, but `Perl_newCONDOP` runs before context is applied. This commit: * Re-adds the logic in `Perl_op_scope` to not wrap a bare `OP_STUB` in an `ENTER/LEAVE` pair. * Removes any relevant `OP_STUB`s in non-scalar context in `Perl_rpeep`. * Adds B::Deparse support * Adds a test based on the failures seen in #22866
1 parent d6f09a8 commit 35c3d59

File tree

6 files changed

+57
-8
lines changed

6 files changed

+57
-8
lines changed

lib/B/Deparse.pm

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# This is based on the module of the same name by Malcolm Beattie,
88
# but essentially none of his code remains.
99

10-
package B::Deparse 1.85;
10+
package B::Deparse 1.86;
1111
use strict;
1212
use Carp;
1313
use B qw(class main_root main_start main_cv svref_2object opnumber perlstring
@@ -4034,13 +4034,22 @@ sub pp_cond_expr {
40344034
my $true = $cond->sibling;
40354035
my $false = $true->sibling;
40364036
my $cuddle = $self->{'cuddle'};
4037-
unless ($cx < 1 and (is_scope($true) and $true->name ne "null") and
4038-
(is_scope($false) || is_ifelse_cont($false))
4039-
and $self->{'expand'} < 7) {
4040-
$cond = $self->deparse($cond, 8);
4041-
$true = $self->deparse($true, 6);
4042-
$false = $self->deparse($false, 8);
4043-
return $self->maybe_parens("$cond ? $true : $false", $cx, 8);
4037+
4038+
if (class($false) eq "NULL") { # Empty else {} block was optimised away
4039+
unless ($cx < 1 and (is_scope($true) and $true->name ne "null")) {
4040+
$cond = $self->deparse($cond, 8);
4041+
$true = $self->deparse($true, 6);
4042+
return $self->maybe_parens("$cond ? $true : ()", $cx, 8);
4043+
}
4044+
} else { # Both true and false branches are present
4045+
unless ($cx < 1 and (is_scope($true) and $true->name ne "null")
4046+
and (is_scope($false) || is_ifelse_cont($false))
4047+
and $self->{'expand'} < 7) {
4048+
$cond = $self->deparse($cond, 8);
4049+
$true = $self->deparse($true, 6);
4050+
$false = $self->deparse($false, 8);
4051+
return $self->maybe_parens("$cond ? $true : $false", $cx, 8);
4052+
}
40444053
}
40454054

40464055
$cond = $self->deparse($cond, 1);

lib/B/Deparse.t

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3455,3 +3455,7 @@ $_ = (!$p) =~ s/1//r;
34553455
my($x, $y, $z);
34563456
$z = 1 + ($x ^^ $y);
34573457
$z = ($x ^^= $y);
3458+
####
3459+
# Else block of a ternary is optimised away
3460+
my $x;
3461+
my(@y) = $x ? [1, 2] : ();

op.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4524,6 +4524,11 @@ Perl_op_scope(pTHX_ OP *o)
45244524
{
45254525
if (o) {
45264526
if (o->op_flags & OPf_PARENS || PERLDB_NOOPT || TAINTING_get) {
4527+
4528+
/* This also makes eliding empty if/else blocks simpler. */
4529+
if (OP_TYPE_IS(o, OP_STUB) && (o->op_flags & OPf_PARENS))
4530+
return o;
4531+
45274532
o = op_prepend_elem(OP_LINESEQ,
45284533
newOP(OP_ENTER, (o->op_flags & OPf_WANT)), o);
45294534
OpTYPE_set(o, OP_LEAVE);

peep.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3588,6 +3588,21 @@ Perl_rpeep(pTHX_ OP *o)
35883588
S_check_for_bool_cxt(o, 1, OPpTRUEBOOL, 0);
35893589
/* FALLTHROUGH */
35903590
case OP_COND_EXPR:
3591+
if (o->op_type == OP_COND_EXPR) {
3592+
/* Is there an empty "else" block or ternary false branch?
3593+
If so, optimise away the OP_STUB if safe to do so. */
3594+
if (o->op_next->op_type == OP_STUB &&
3595+
((o->op_next->op_flags & OPf_WANT) != OPf_WANT_SCALAR)
3596+
) {
3597+
OP *stub = o->op_next;
3598+
assert(stub == OpSIBLING(OpSIBLING( cLOGOP->op_first )));
3599+
assert(!(stub->op_flags & OPf_KIDS));
3600+
o->op_next = stub->op_next;
3601+
op_sibling_splice(o, OpSIBLING(cLOGOP->op_first), 1, NULL);
3602+
op_free(stub);
3603+
}
3604+
}
3605+
/* FALLTHROUGH */
35913606
case OP_MAPWHILE:
35923607
case OP_ANDASSIGN:
35933608
case OP_ORASSIGN:

t/op/cond.t

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,15 @@ is( !$x ? 0 : 1, 1, 'run time, false');
2727
is $@, "", "SEGV in Perl_scalar";
2828
}
2929

30+
# [GH #22866] The OP_STUB associated with an empty list should not
31+
# be optimised away if it's in scalar context (as it pushes PL_sv_undef
32+
# to the stack. In that event, these cases will trigger an assert under
33+
# DEBUGGING builds.
34+
35+
{
36+
my $x;
37+
$x = ( $x ) ? "JAPH" : ();
38+
$x = ( $x ) ? () : "JAPH";
39+
}
3040

3141
done_testing();

t/perf/opcount.t

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,4 +1106,10 @@ test_opcount(0, "substr with const zero offset (gv)",
11061106
sassign => 1
11071107
});
11081108

1109+
test_opcount(0, "Empty else{} blocks are optimised away",
1110+
sub { my $x; ($x) ? 1 : () },
1111+
{
1112+
stub => 0
1113+
});
1114+
11091115
done_testing();

0 commit comments

Comments
 (0)