Skip to content

Commit 326e2e2

Browse files
committed
op.h/op.c/peep.c - Optimise away empty if{} blocks
This commit optimises away the OP_STUB associated with an empty `true` branch of an `OP_COND_EXPR`. The `OPf_SPECIAL` flag has been brought into use on the `OP_COND_EXPR` to indicate that it is the `else`, not the `if`, block that has been optimised away. This is purely for the benefit of B::Deparse.
1 parent 35c3d59 commit 326e2e2

File tree

5 files changed

+148
-13
lines changed

5 files changed

+148
-13
lines changed

lib/B/Deparse.pm

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4034,12 +4034,23 @@ sub pp_cond_expr {
40344034
my $true = $cond->sibling;
40354035
my $false = $true->sibling;
40364036
my $cuddle = $self->{'cuddle'};
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);
4037+
my $no_true = 0;
4038+
4039+
if (class($false) eq "NULL") { # Empty true or false block was optimised away
4040+
if (!($op->flags & OPf_SPECIAL)) { # It was an empty true block
4041+
my $temp = $false; $false = $true; $true = $temp;
4042+
$no_true = 1;
4043+
unless ($cx < 1 and (is_scope($false) and $false->name ne "null")) {
4044+
$cond = $self->deparse($cond, 8);
4045+
$false = $self->deparse($false, 6);
4046+
return $self->maybe_parens("$cond ? () : $false", $cx, 8);
4047+
}
4048+
} else { # Must have been an empty false block
4049+
unless ($cx < 1 and (is_scope($true) and $true->name ne "null")) {
4050+
$cond = $self->deparse($cond, 8);
4051+
$true = $self->deparse($true, 6);
4052+
return $self->maybe_parens("$cond ? $true : ()", $cx, 8);
4053+
}
40434054
}
40444055
} else { # Both true and false branches are present
40454056
unless ($cx < 1 and (is_scope($true) and $true->name ne "null")
@@ -4053,8 +4064,10 @@ sub pp_cond_expr {
40534064
}
40544065

40554066
$cond = $self->deparse($cond, 1);
4056-
$true = $self->deparse($true, 0);
4057-
my $head = $self->keyword("if") . " ($cond) {\n\t$true\n\b}";
4067+
$true = ($no_true) ? "\b" : $self->deparse($true, 0);
4068+
my $head = ($no_true)
4069+
? $self->keyword("if") . " ($cond) {\n\t();\n\b}"
4070+
: $self->keyword("if") . " ($cond) {\n\t$true\n\b}";
40584071
my @elsifs;
40594072
my $elsif;
40604073
while (!null($false) and is_ifelse_cont($false)) {
@@ -4069,13 +4082,24 @@ sub pp_cond_expr {
40694082
$newcond = $newcond->first->sibling;
40704083
}
40714084
$newcond = $self->deparse($newcond, 1);
4072-
$newtrue = $self->deparse($newtrue, 0);
4085+
4086+
if (null($false) && ! ($newop->flags & OPf_SPECIAL)) {
4087+
# An empty elsif "true" block has been optimised away
4088+
my $temp = $false; $false = $newtrue; $newtrue = $temp;
4089+
$newtrue = "();";
4090+
} else {
4091+
$newtrue = $self->deparse($newtrue, 0);
4092+
}
4093+
40734094
$elsif ||= $self->keyword("elsif");
40744095
push @elsifs, "$elsif ($newcond) {\n\t$newtrue\n\b}";
40754096
}
40764097
if (!null($false)) {
40774098
$false = $cuddle . $self->keyword("else") . " {\n\t" .
40784099
$self->deparse($false, 0) . "\n\b}\cK";
4100+
} elsif ($op->flags & OPf_SPECIAL) {
4101+
$false = $cuddle . $self->keyword("else") . " {\n\t" .
4102+
"();\n\b}\cK";
40794103
} else {
40804104
$false = "\cK";
40814105
}

lib/B/Deparse.t

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3456,6 +3456,85 @@ my($x, $y, $z);
34563456
$z = 1 + ($x ^^ $y);
34573457
$z = ($x ^^= $y);
34583458
####
3459-
# Else block of a ternary is optimised away
3459+
# Empty ? branch of a ternary is optimised away
3460+
my $x;
3461+
my(@y) = $x ? () : [1, 2];
3462+
####
3463+
# Empty : branch of a ternary is optimised away
34603464
my $x;
34613465
my(@y) = $x ? [1, 2] : ();
3466+
####
3467+
# Empty if {} block is optimised away
3468+
my($x, $y);
3469+
if ($x) {
3470+
();
3471+
}
3472+
else {
3473+
$y = 1;
3474+
}
3475+
####
3476+
# Empty else {} block is optimised away
3477+
my($x, $y);
3478+
if ($x) {
3479+
$y = 1;
3480+
}
3481+
else {
3482+
();
3483+
}
3484+
####
3485+
# Empty else {} preceded by an valid elsif
3486+
my($x, $y);
3487+
if ($x) {
3488+
$y = 1;
3489+
}
3490+
elsif ($y) {
3491+
$y = 2;
3492+
}
3493+
else {
3494+
();
3495+
}
3496+
####
3497+
# Empty elsif {} with valid else
3498+
my($x, $y);
3499+
if ($x) {
3500+
$y = 1;
3501+
}
3502+
elsif ($y) {
3503+
();
3504+
} else {
3505+
$y = 2;
3506+
}
3507+
####
3508+
# Deparse of empty elsif sandwich (filling)
3509+
my($x, $y);
3510+
if ($x) {
3511+
$y = 1;
3512+
}
3513+
elsif ($y) {
3514+
$y = 3;
3515+
}
3516+
elsif ($y) {
3517+
();
3518+
}
3519+
elsif ($y) {
3520+
$y = 4;
3521+
} else {
3522+
$y = 2;
3523+
}
3524+
####
3525+
# Deparse of empty elsif sandwich (bread)
3526+
my($x, $y);
3527+
if ($x) {
3528+
$y = 1;
3529+
}
3530+
elsif ($y) {
3531+
();
3532+
}
3533+
elsif ($y) {
3534+
$y = 3;
3535+
}
3536+
elsif ($y) {
3537+
();
3538+
} else {
3539+
$y = 2;
3540+
}

op.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ Deprecated. Use C<GIMME_V> instead.
164164
/* On OP_RETURN, module_true is in effect */
165165
/* On OP_NEXT/OP_LAST/OP_REDO, there is no
166166
* loop label */
167+
/* On OP_COND_EXPR, indicates that an empty
168+
* "else" condition was optimized away. */
167169
/* There is no room in op_flags for this one, so it has its own bit-
168170
field member (op_folded) instead. The flag is only used to tell
169171
op_convert_list to set op_folded. */

peep.c

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3589,14 +3589,38 @@ Perl_rpeep(pTHX_ OP *o)
35893589
/* FALLTHROUGH */
35903590
case OP_COND_EXPR:
35913591
if (o->op_type == OP_COND_EXPR) {
3592+
OP *stub = cLOGOP->op_other;
3593+
/* Is there an empty "if" block or ternary true branch?
3594+
If so, optimise away the OP_STUB if safe to do so. */
3595+
if (stub->op_type == OP_STUB &&
3596+
((stub->op_flags & OPf_WANT) != OPf_WANT_SCALAR)
3597+
) {
3598+
OP *trueop = OpSIBLING( cLOGOP->op_first );
3599+
3600+
assert((stub == trueop ) || (OP_TYPE_IS(trueop, OP_SCOPE) &&
3601+
((stub == cUNOPx(trueop)->op_first)) && !OpSIBLING(stub))
3602+
);
3603+
assert(!(stub->op_flags & OPf_KIDS));
3604+
3605+
cLOGOP->op_other = (stub->op_next == trueop) ?
3606+
stub->op_next->op_next :
3607+
stub->op_next;
3608+
3609+
op_sibling_splice(o, cLOGOP->op_first, 1, NULL);
3610+
3611+
if (stub != trueop) op_free(stub);
3612+
op_free(trueop);
3613+
} else
3614+
35923615
/* Is there an empty "else" block or ternary false branch?
35933616
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)
3617+
stub = o->op_next;
3618+
if (stub->op_type == OP_STUB &&
3619+
((stub->op_flags & OPf_WANT) != OPf_WANT_SCALAR)
35963620
) {
3597-
OP *stub = o->op_next;
35983621
assert(stub == OpSIBLING(OpSIBLING( cLOGOP->op_first )));
35993622
assert(!(stub->op_flags & OPf_KIDS));
3623+
o->op_flags |= OPf_SPECIAL; /* For B::Deparse */
36003624
o->op_next = stub->op_next;
36013625
op_sibling_splice(o, OpSIBLING(cLOGOP->op_first), 1, NULL);
36023626
op_free(stub);

t/perf/opcount.t

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

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

0 commit comments

Comments
 (0)