#!./perl
BEGIN {
chdir
't'
;
require
'./test.pl'
;
skip_all_if_miniperl(
"No B under miniperl"
);
@INC
=
'../lib'
;
}
{
my
%counts
;
sub
B::OP::test_opcount_callback {
my
(
$op
) =
@_
;
my
$name
=
$op
->name;
if
(
$name
eq
'null'
) {
my
$targ
=
$op
->targ;
if
(
$targ
) {
$name
=
"ex-"
.
substr
(B::ppname(
$targ
), 3);
}
}
$counts
{
$name
}++;
}
sub
test_opcount {
my
(
$debug
,
$desc
,
$coderef
,
$expected_counts
) =
@_
;
%counts
= ();
B::walkoptree(B::svref_2object(
$coderef
)->ROOT,
'test_opcount_callback'
);
if
(
$debug
) {
note(
sprintf
"%3d %s"
,
$counts
{
$_
},
$_
)
for
sort
keys
%counts
;
}
my
@exp
;
for
(
sort
keys
%$expected_counts
) {
my
(
$c
,
$e
) = (
$counts
{
$_
}//0,
$expected_counts
->{
$_
});
if
(
$c
!=
$e
) {
push
@exp
,
"expected $e, got $c: $_"
;
}
}
ok(!
@exp
,
$desc
);
if
(
@exp
) {
diag(
$_
)
for
@exp
;
}
}
}
test_opcount(0,
"basic aelemfast"
,
sub
{
our
@a
;
$a
[0] = 1 },
{
aelem
=> 0,
aelemfast
=> 1,
'ex-aelem'
=> 1,
}
);
{
test_opcount(0,
"bench.pl empty loop"
,
sub
{
for
my
$x
(1..
$ARGV
[0]) { 1; } },
{
aelemfast
=> 1,
and
=> 1,
const
=> 1,
enteriter
=> 1,
iter
=> 1,
leaveloop
=> 1,
leavesub
=> 1,
lineseq
=> 2,
nextstate
=> 2,
null
=> 1,
pushmark
=> 1,
unstack
=> 1,
}
);
no
warnings
'void'
;
test_opcount(0,
"bench.pl active loop"
,
sub
{
for
my
$x
(1..
$ARGV
[0]) {
$x
; } },
{
aelemfast
=> 1,
and
=> 1,
const
=> 1,
enteriter
=> 1,
iter
=> 1,
leaveloop
=> 1,
leavesub
=> 1,
lineseq
=> 2,
nextstate
=> 2,
null
=> 1,
padsv
=> 1,
pushmark
=> 1,
unstack
=> 1,
}
);
}
{
my
(
@agg_lex
,
%agg_lex
,
$i_lex
,
$r_lex
);
our
(
@agg_pkg
,
%agg_pkg
,
$i_pkg
,
$r_pkg
);
my
$f
;
my
@bodies
= (
'[0]'
,
'[128]'
,
'[$i_lex]'
,
'[$i_pkg]'
,
'{foo}'
,
'{$i_lex}'
,
'{$i_pkg}'
,
);
for
my
$prefix
(
'$f->()->'
,
'$agg_lex'
,
'$agg_pkg'
,
'$r_lex->'
,
'$r_pkg->'
)
{
for
my
$mod
(
''
,
'local'
,
'exists'
,
'delete'
) {
for
my
$body0
(
@bodies
) {
for
my
$body1
(
''
,
@bodies
) {
for
my
$body2
(
''
,
'[2*$i_lex]'
) {
my
$code
=
"$mod $prefix$body0$body1$body2"
;
my
$sub
=
"sub { $code }"
;
my
$coderef
=
eval
$sub
or
die
"eval '$sub': $@"
;
my
%c
= (
aelem
=> 0,
aelemfast
=> 0,
aelemfast_lex
=> 0,
exists
=> 0,
delete
=> 0,
helem
=> 0,
multideref
=> 0,
);
my
$top
=
'aelem'
;
if
(
$code
=~ /^\s*\
$agg_
...\[0\]$/) {
$top
=
$code
=~ /lex/ ?
'aelemfast_lex'
:
'aelemfast'
;
$c
{
$top
} = 1;
}
else
{
$c
{multideref} = 1;
}
if
(
$body2
ne
''
) {
$top
=
$mod
unless
$mod
eq
''
or
$mod
eq
'local'
;
$c
{
$top
} = 1
}
::test_opcount(0,
$sub
,
$coderef
, \
%c
);
}
}
}
}
}
}
test_opcount(0,
'multideref expressions'
,
sub
{ (
$_
[0] //
$_
)->[0]{2
*$_
[0]} },
{
aelemfast
=> 2,
helem
=> 1,
multideref
=> 1,
},
);
test_opcount(0,
'multideref const index'
,
sub
{
$_
->{1}{1.1} },
{
helem
=> 0,
multideref
=> 1,
},
);
test_opcount(0,
'multideref undef const index'
,
sub
{
$_
->{+my_undef} },
{
helem
=> 1,
multideref
=> 0,
},
);
test_opcount(0,
'multideref op_other etc'
,
sub
{
$_
{foo} =
$_
?
$_
{bar} :
$_
{baz} },
{
helem
=> 0,
multideref
=> 3,
},
);
{
no
strict;
no
warnings;
test_opcount(0,
'multideref no hints'
,
sub
{
$_
{foo}[0] },
{
aelem
=> 0,
helem
=> 0,
multideref
=> 1,
},
);
}
test_opcount(0,
'multideref exists'
,
sub
{
exists
$_
[0] },
{
aelem
=> 0,
aelemfast
=> 0,
multideref
=> 1,
},
);
test_opcount(0,
'barewords can be constant-folded'
,
sub
{
no
strict
'subs'
; FOO . BAR },
{
concat
=> 0,
});
{
my
@a
;
test_opcount(0,
'signature default expressions get optimised'
,
sub
(
$s
=
$a
[0]) {},
{
aelem
=> 0,
aelemfast_lex
=> 1,
});
}
{
local
our
@global
= (3,2,1);
my
@lex
=
qw(a b c)
;
test_opcount(0,
'in-place sort of global'
,
sub
{
@global
=
sort
@global
; 1 },
{
rv2av
=> 1,
aassign
=> 0,
});
test_opcount(0,
'in-place sort of lexical'
,
sub
{
@lex
=
sort
@lex
; 1 },
{
padav
=> 1,
aassign
=> 0,
});
test_opcount(0,
'in-place reversed sort of global'
,
sub
{
@global
=
sort
{
$b
<=>
$a
}
@global
; 1 },
{
rv2av
=> 1,
aassign
=> 0,
});
test_opcount(0,
'in-place custom sort of global'
,
sub
{
@global
=
sort
{
$a
<
$b
?1:
$a
>
$b
?-1:0 }
@global
; 1 },
{
rv2av
=> 1,
aassign
=> 0,
});
sub
mysort {
$b
cmp
$a
};
test_opcount(0,
'in-place sort with function of lexical'
,
sub
{
@lex
=
sort
mysort
@lex
; 1 },
{
padav
=> 1,
aassign
=> 0,
});
}
{
local
our
@pkg
;
my
@lex
;
for
([
'@pkg'
, 0, ],
[
'local @pkg'
, 0, ],
[
'@lex'
, 0, ],
[
'my @a'
, 0, ],
[
'@{[]}'
, 1, ],
){
my
(
$code
,
$partial
) =
@$_
;
test_opcount(0,
"in-place assignment for split: $code"
,
eval
qq{sub { $code = split }
},
{
padav
=> 0,
rv2av
=>
$partial
,
aassign
=> 0,
});
}
}
{
local
our
@pkg
;
my
@lex
;
my
(
$x
,
$y
,
$z
);
for
my
$assign
(0, 1) {
for
my
$index
(
'index($x,$y)'
,
'rindex($x,$y)'
) {
for
my
$fmt
(
"%s <= -1"
,
"%s == -1"
,
"%s != -1"
,
"%s > -1"
,
"%s < 0"
,
"%s >= 0"
,
"-1 < %s"
,
"-1 == %s"
,
"-1 != %s"
,
"-1 >= %s"
,
" 0 <= %s"
,
" 0 > %s"
,
) {
my
$expr
=
sprintf
$fmt
,
$index
;
$expr
=
"\$z = ($expr)"
if
$assign
;
test_opcount(0,
"optimise away compare,const in $expr"
,
eval
qq{sub { $expr }
},
{
lt
=> 0,
le
=> 0,
eq
=> 0,
ne
=> 0,
ge
=> 0,
gt
=> 0,
const
=> 0,
sassign
=> 0,
padsv
=> 2.
});
}
}
}
}
{
my
(
$i
,
$j
,
$s
);
test_opcount(0,
"sprintf pessimised"
,
sub
{
$s
.=
sprintf
"%d%d"
,
$i
,
$j
},
{
const
=> 1,
sprintf
=> 1,
concat
=> 0,
multiconcat
=> 1,
padsv
=> 2,
});
}
test_opcount(0,
"sprintf constant args"
,
sub
{
sprintf
"%s%s"
,
"abc"
,
"def"
},
{
const
=> 1,
sprintf
=> 0,
multiconcat
=> 0.
});
{
my
%seen
;
my
%pessimise
=
map
{
$_
=> 1 }
'$a1.$a2'
,
'"$a1$a2"'
,
'$pkg .= $a1'
,
'$pkg .= "$a1"'
,
'$lex = $a1.$a2'
,
'$lex = "$a1$a2"'
,
'sprintf("-")'
,
'$pkg = sprintf("-")'
,
'$lex = sprintf("-")'
,
'my $l = sprintf("-")'
,
;
for
my
$lhs
(
''
,
'$pkg = '
,
'$pkg .= '
,
'$lex = '
,
'$lex .= '
,
'my $l = '
,
) {
for
my
$nargs
(0..3) {
for
my
$type
(0..2) {
for
my
$const
(0..4) {
my
@args
;
my
@sprintf_args
;
my
$c
=
$type
== 0 ?
'"-"'
:
'-'
;
push
@args
,
$c
if
$const
== 2 ||
$const
== 4;
for
my
$n
(1..
$nargs
) {
if
(
$type
== 2) {
push
@sprintf_args
,
"\$a$n"
;
push
@args
,
'%s'
;
}
else
{
push
@args
,
"\$a$n"
;
}
push
@args
,
$c
if
$const
;
}
pop
@args
if
$const
== 1 ||
$const
== 2;
push
@args
,
$c
if
$nargs
== 0 &&
$const
== 1;
if
(
$type
== 2) {
next
unless
@args
;
}
else
{
next
unless
@args
>= (
$lhs
=~ /\./ ? 1 : 2);
}
my
$rhs
;
if
(
$type
== 0) {
$rhs
=
join
(
'.'
,
@args
);
}
elsif
(
$type
== 1) {
$rhs
=
'"'
.
join
(
''
,
@args
) .
'"'
}
else
{
$rhs
=
'sprintf("'
.
join
(
''
,
@args
)
.
'"'
.
join
(
''
,
map
",$_"
,
@sprintf_args
)
.
')'
;
}
my
$expr
=
$lhs
.
$rhs
;
next
if
exists
$seen
{
$expr
};
$seen
{
$expr
} = 1;
my
(
$a1
,
$a2
,
$a3
);
my
$lex
;
our
$pkg
;
my
$sub
=
eval
qq{sub { $expr }
};
die
"eval(sub { $expr }: $@"
if
$@;
my
$pm
=
$pessimise
{
$expr
};
test_opcount(0, (
$pm
?
"concat "
:
"multiconcat"
)
.
": $expr"
,
$sub
,
$pm
? {
multiconcat
=> 0 }
: {
multiconcat
=> 1,
padsv
=>
$nargs
,
concat
=> 0,
sprintf
=> 0,
const
=> 0,
sassign
=> 0,
stringify
=> 0,
gv
=> 0,
});
}
}
}
}
}
test_opcount(0,
'$lex = "foo"'
,
sub
{
my
$x
;
$x
=
"foo"
; },
{
multiconcat
=> 0,
});
test_opcount(0,
'$lex1 = $lex2 . $lex1'
,
sub
{
my
(
$x
,
$y
);
$x
=
$y
.
$x
},
{
multiconcat
=> 1,
padsv
=> 4,
concat
=> 0,
sassign
=> 0,
stringify
=> 0,
});
test_opcount(0,
'$lex1 = "$lex2$lex1"'
,
sub
{
my
(
$x
,
$y
);
$x
=
"$y$x"
},
{
multiconcat
=> 1,
padsv
=> 4,
concat
=> 0,
sassign
=> 0,
stringify
=> 0,
});
test_opcount(0,
'$lex1 = $lex1 . $lex1'
,
sub
{
my
$x
;
$x
=
$x
.
$x
},
{
multiconcat
=> 0,
});
test_opcount(0,
'my $a .= $b.$c.$d'
,
sub
{
our
(
$b
,
$c
,
$d
);
my
$a
.=
$b
.
$c
.
$d
},
{
padsv
=> 1,
});
test_opcount(0,
"rcatline"
,
sub
{
my
(
$x
,
$y
);
open
FOO,
"xxx"
;
$x
.= <FOO> },
{
rcatline
=> 1,
readline
=> 0,
multiconcat
=> 0,
concat
=> 0,
});
{
my
@a
;
for
my
$i
(60..68) {
my
$c
=
join
'.'
,
map
"\$a[$_]"
, 1..
$i
;
my
$sub
=
eval
qq{sub { $c }
} or
die
$@;
test_opcount(0,
"long chain $i"
,
$sub
,
{
multiconcat
=>
$i
> 65 ? 2 : 1,
concat
=>
$i
== 65 ? 1 : 0,
aelem
=> 0,
aelemfast
=> 0,
});
}
}
test_opcount(0,
"state works with multiconcat"
,
sub
{
use
feature
'state'
;
our
(
$a
,
$b
,
$c
); state
$s
=
$a
.
$b
.
$c
},
{
multiconcat
=> 1,
concat
=> 0,
sassign
=> 0,
once
=> 1,
padsv
=> 2,
});
test_opcount(0,
"multiconcat: 2 adjacent consts"
,
sub
{
my
(
$a
,
$b
);
$a
=
$b
.
"c"
.
"d"
},
{
const
=> 1,
multiconcat
=> 1,
concat
=> 0,
sassign
=> 0,
});
test_opcount(0,
"multiconcat: 3 adjacent consts"
,
sub
{
my
(
$a
,
$b
);
$a
=
$b
.
"c"
.
"d"
.
"e"
},
{
const
=> 1,
multiconcat
=> 1,
concat
=> 0,
sassign
=> 0,
});
test_opcount(0,
"multiconcat: 4 adjacent consts"
,
sub
{
my
(
$a
,
$b
);
$a
=
$b
.
"c"
.
"d"
.
"e"
.
"f"
},
{
const
=> 2,
multiconcat
=> 1,
concat
=> 0,
sassign
=> 0,
});
test_opcount(0,
"multiconcat: local assign"
,
sub
{
our
$global
;
local
$global
=
"$global-X"
},
{
const
=> 0,
gvsv
=> 2,
multiconcat
=> 1,
concat
=> 0,
sassign
=> 1,
});
{
no
warnings
'experimental::try'
;
test_opcount(0,
"try/catch: catch block is optimized"
,
sub
{
my
@a
;
try
{}
catch
(
$e
) {
$a
[0] } },
{
aelemfast_lex
=> 1,
aelem
=> 0,
});
}
{
no
warnings
'experimental::defer'
;
test_opcount(0,
"pushdefer: block is optimized"
,
sub
{
my
@a
; defer {
$a
[0] } },
{
aelemfast_lex
=> 1,
aelem
=> 0,
});
}
no
warnings
'experimental::builtin'
;
test_opcount(0,
"builtin::true/false are replaced with constants"
,
sub
{
my
$x
= builtin::true();
my
$y
= builtin::false() },
{
entersub
=> 0,
const
=> 2,
});
test_opcount(0,
"builtin::is_bool is replaced with direct opcode"
,
sub
{
my
$x
;
my
$y
;
$y
= builtin::is_bool(
$x
); },
{
entersub
=> 0,
is_bool
=> 1,
padsv
=> 3,
sassign
=> 0,
});
test_opcount(0,
"builtin::is_bool gets constant-folded"
,
sub
{ builtin::is_bool(123); },
{
entersub
=> 0,
is_bool
=> 0,
const
=> 1,
});
test_opcount(0,
"builtin::weaken is replaced with direct opcode"
,
sub
{
my
$x
= []; builtin::weaken(
$x
); },
{
entersub
=> 0,
weaken
=> 1,
});
test_opcount(0,
"builtin::unweaken is replaced with direct opcode"
,
sub
{
my
$x
= []; builtin::unweaken(
$x
); },
{
entersub
=> 0,
unweaken
=> 1,
});
test_opcount(0,
"builtin::is_weak is replaced with direct opcode"
,
sub
{ builtin::is_weak([]); },
{
entersub
=> 0,
is_weak
=> 1,
});
test_opcount(0,
"builtin::blessed is replaced with direct opcode"
,
sub
{ builtin::blessed([]); },
{
entersub
=> 0,
blessed
=> 1,
});
test_opcount(0,
"builtin::refaddr is replaced with direct opcode"
,
sub
{ builtin::refaddr([]); },
{
entersub
=> 0,
refaddr
=> 1,
});
test_opcount(0,
"builtin::reftype is replaced with direct opcode"
,
sub
{ builtin::reftype([]); },
{
entersub
=> 0,
reftype
=> 1,
});
my
$one_point_five
= 1.5;
test_opcount(0,
"builtin::ceil is replaced with direct opcode"
,
sub
{ builtin::ceil(
$one_point_five
); },
{
entersub
=> 0,
ceil
=> 1,
});
test_opcount(0,
"builtin::floor is replaced with direct opcode"
,
sub
{ builtin::floor(
$one_point_five
); },
{
entersub
=> 0,
floor
=> 1,
});
done_testing();