File 1023-Undo-instruction-combination-optimizations.patch of Package erlang
From f302db9ba2d1e83f8349a1f376cd5c101310ecaa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org>
Date: Wed, 10 May 2023 08:18:15 +0200
Subject: [PATCH 3/5] Undo instruction combination optimizations
As a refactoring preparing to generate C code, undo some of
optimizations that combine instructions in the transform
engine.
---
erts/emulator/beam/beam_transform_engine.c | 93 ++-------------
erts/emulator/utils/beam_makeops | 128 +++++----------------
2 files changed, 38 insertions(+), 183 deletions(-)
diff --git a/erts/emulator/beam/beam_transform_engine.c b/erts/emulator/beam/beam_transform_engine.c
index 3ce934a48c..7891c901df 100644
--- a/erts/emulator/beam/beam_transform_engine.c
+++ b/erts/emulator/beam/beam_transform_engine.c
@@ -83,51 +83,17 @@ erts_transform_engine(LoaderState* st)
if (((1 << instr->a[ap].type) & mask) == 0)
goto restart;
break;
-#if defined(TOP_is_type_next_arg)
- case TOP_is_type_next_arg:
- mask = *pc++;
- ASSERT(ap < instr->arity);
- ASSERT(instr->a[ap].type < BEAM_NUM_TAGS);
- if (((1 << instr->a[ap].type) & mask) == 0)
- goto restart;
- ap++;
- break;
-#endif
case TOP_pred:
i = *pc++;
i = erts_beam_eval_predicate((unsigned) i, st, var, rest_args);
if (i == 0)
goto restart;
break;
-#if defined(TOP_is_eq)
case TOP_is_eq:
ASSERT(ap < instr->arity);
if (*pc++ != instr->a[ap].val)
goto restart;
break;
-#endif
- case TOP_is_type_eq:
- mask = *pc++;
-
- ASSERT(ap < instr->arity);
- ASSERT(instr->a[ap].type < BEAM_NUM_TAGS);
- if (((1 << instr->a[ap].type) & mask) == 0)
- goto restart;
- if (*pc++ != instr->a[ap].val)
- goto restart;
- break;
-#if defined(TOP_is_type_eq_next_arg)
- case TOP_is_type_eq_next_arg:
- mask = *pc++;
- ASSERT(ap < instr->arity);
- ASSERT(instr->a[ap].type < BEAM_NUM_TAGS);
- if (((1 << instr->a[ap].type) & mask) == 0)
- goto restart;
- if (*pc++ != instr->a[ap].val)
- goto restart;
- ap++;
- break;
-#endif
#if defined(TOP_is_bif)
case TOP_is_bif:
{
@@ -249,50 +215,12 @@ erts_transform_engine(LoaderState* st)
}
break;
#endif
- case TOP_set_var_next_arg:
+ case TOP_set_var:
ASSERT(ap < instr->arity);
i = *pc++;
ASSERT(i < TE_MAX_VARS);
- var[i].type = instr->a[ap].type;
- var[i].val = instr->a[ap].val;
- ap++;
- break;
-#if defined(TOP_is_type_set_var_next_arg)
- case TOP_is_type_set_var_next_arg:
- mask = pc[0];
- i = pc[1];
- ASSERT(i < TE_MAX_VARS);
- ASSERT(ap < instr->arity);
- ASSERT(instr->a[ap].type < BEAM_NUM_TAGS);
- if (((1 << instr->a[ap].type) & mask) == 0)
- goto restart;
- ASSERT(i < TE_MAX_VARS);
var[i] = instr->a[ap];
- ap++;
- pc += 2;
- break;
-#endif
-#if defined(TOP_is_type_eq_set_var_next_arg)
- case TOP_is_type_eq_set_var_next_arg:
- {
- Eterm val;
- mask = pc[0];
- val = pc[1];
- i = pc[2];
- ASSERT(i < TE_MAX_VARS);
- ASSERT(ap < instr->arity);
- ASSERT(instr->a[ap].type < BEAM_NUM_TAGS);
- if (((1 << instr->a[ap].type) & mask) == 0)
- goto restart;
- if (val != instr->a[ap].val)
- goto restart;
- ASSERT(i < TE_MAX_VARS);
- var[i] = instr->a[ap];
- ap++;
- pc += 3;
- }
break;
-#endif
#if defined(TOP_rest_args)
case TOP_rest_args:
{
@@ -331,8 +259,8 @@ erts_transform_engine(LoaderState* st)
keep = instr;
break;
#endif
-#if defined(TOP_call_end)
- case TOP_call_end:
+#if defined(TOP_call)
+ case TOP_call:
{
BeamOp** lastp;
BeamOp* new_instr;
@@ -352,7 +280,7 @@ erts_transform_engine(LoaderState* st)
*lastp = keep;
instr = new_instr;
}
- /* FALLTHROUGH */
+ break;
#endif
case TOP_end:
st->genop = instr;
@@ -382,18 +310,15 @@ erts_transform_engine(LoaderState* st)
instr->arity = gen_opc[op].arity;
return TE_OK;
#endif
- case TOP_store_val_next_arg:
+ case TOP_store_val:
instr->a[ap].type = pc[0];
instr->a[ap].val = pc[1];
- ap++;
pc += 2;
break;
- case TOP_store_var_next_arg:
+ case TOP_store_var:
i = *pc++;
ASSERT(i < TE_MAX_VARS);
- instr->a[ap].type = var[i].type;
- instr->a[ap].val = var[i].val;
- ap++;
+ instr->a[ap] = var[i];
break;
#if defined(TOP_store_rest_args)
case TOP_store_rest_args:
@@ -416,6 +341,10 @@ erts_transform_engine(LoaderState* st)
case TOP_try_me_else_fail:
restart = restart_fail;
break;
+#if defined(TOP_nop)
+ case TOP_nop:
+ break;
+#endif
case TOP_fail:
return TE_FAIL;
#if defined(TOP_skip_unless)
diff --git a/erts/emulator/utils/beam_makeops b/erts/emulator/utils/beam_makeops
index c7ea032026..57f9d0fe49 100755
--- a/erts/emulator/utils/beam_makeops
+++ b/erts/emulator/utils/beam_makeops
@@ -2835,13 +2835,7 @@ sub tr_gen_from {
$types .= "$_ ";
$type_mask |= $type_bit{$_};
}
- if ($cond ne 'is_eq') {
- push(@code, make_op($types, 'is_type', $type_mask));
- } else {
- $cond = '';
- push(@code, make_op("$types== $val", 'is_type_eq',
- $type_mask, $val));
- }
+ push(@code, make_op($types, 'is_type', $type_mask));
}
}
@@ -2883,14 +2877,7 @@ sub tr_gen_from {
push(@code, make_op($var, 'set_var', $var{$var}));
}
}
- if (is_instr($code[$#code], 'set_var')) {
- my $ref = pop @code;
- my $comment = $ref->[2];
- my $var = $ref->[1][1];
- push(@code, make_op($comment, 'set_var_next_arg', $var));
- } else {
- push(@code, make_op($ignored_var, 'next_arg'));
- }
+ push(@code, make_op($ignored_var, 'next_arg'));
}
# Remove redundant 'next_arg' instructions before the end
@@ -2903,39 +2890,6 @@ sub tr_gen_from {
#
push(@code, make_op($may_fail ? '' : 'always reached', 'commit'));
- #
- # Peephole optimization: combine instructions.
- #
- for (my $i = 0; $i < @code; $i++) {
- if (is_instr($code[$i], 'is_type')) {
- my(undef, $is_type_ref, $type_comment) = @{$code[$i]};
- if (is_instr($code[$i+1], 'set_var_next_arg')) {
- my(undef, $next_ref, $next_comment) = @{$code[$i+1]};
- my $comment = "$type_comment $next_comment";
- my $op = make_op($comment, 'is_type_set_var_next_arg',
- $is_type_ref->[1], $next_ref->[1]);
- splice @code, $i, 2, ($op);
- } elsif (is_instr($code[$i+1], 'next_arg')) {
- my $op = make_op($type_comment, 'is_type_next_arg', $is_type_ref->[1]);
- splice @code, $i, 2, ($op);
- }
- } elsif (is_instr($code[$i], 'is_type_eq')) {
- my(undef, $is_type_ref, $type_comment) = @{$code[$i]};
- if (is_instr($code[$i+1], 'set_var_next_arg')) {
- my(undef, $next_ref, $next_comment) = @{$code[$i+1]};
- my $comment = "$type_comment $next_comment";
- my $op = make_op($comment, 'is_type_eq_set_var_next_arg',
- $is_type_ref->[1], $is_type_ref->[2],
- $next_ref->[1]);
- splice @code, $i, 2, ($op);
- } elsif (is_instr($code[$i+1], 'next_arg')) {
- my $op = make_op($type_comment, 'is_type_eq_next_arg',
- $is_type_ref->[1], $is_type_ref->[2]);
- splice @code, $i, 2, ($op);
- }
- }
- }
-
$te_max_vars = $var_num
if $te_max_vars < $var_num;
[\%var, \%var_type, \@instrs, \@code];
@@ -2984,7 +2938,7 @@ sub tr_gen_to {
pop(@code); # Get rid of 'commit' instruction
my $index = next_tr_index(\@call_table, \%call_table,
$name, @args);
- my $op = make_op("$name()", 'call_end', $index);
+ my $op = make_op("$name()", 'call', $index);
my @slots = grep(/^\d+/, map { $var{$_} } @ops);
op_slot_usage($op, @slots);
push(@code, $op);
@@ -3013,21 +2967,22 @@ sub tr_gen_to {
} elsif ($var ne '') {
error($where, "variable '$var' unbound")
unless defined $var{$var};
- my $op = make_op($var, 'store_var_next_arg', $var{$var});
+ my $op = make_op($var, 'store_var', $var{$var});
op_slot_usage($op, $var{$var});
- push(@code, $op);
+ my $next_arg = make_op('', 'next_arg');
+ push @code, $op, $next_arg;
} elsif ($type ne '') {
my $val = $type_val || 0;
my $comment = "$type=$val";
- my $op = make_op($comment, 'store_val_next_arg', "TAG_$type", $val);
- push @code, $op;
+ my $store_val = make_op($comment, 'store_val', "TAG_$type", $val);
+ my $next_arg = make_op('', 'next_arg');
+ push @code, $store_val, $next_arg;
}
}
pop(@code) if is_instr($code[$#code], 'next_arg');
}
- push(@code, make_op('', 'end'))
- unless is_instr($code[$#code], 'call_end');
+ push(@code, make_op('', 'end'));
tr_maybe_keep(\@code);
tr_maybe_rename(\@code);
@@ -3162,7 +3117,8 @@ sub group_tr {
sub tr_maybe_keep {
my($ref) = @_;
- my @last_instr;
+ my $ap = 1;
+ my @last_instr = ('unknown','ignored');
my $pos;
my $reused_instr;
@@ -3171,21 +3127,21 @@ sub tr_maybe_keep {
my($size, $instr_ref, $comment) = @$instr;
my($op, @args) = @$instr_ref;
if ($op eq 'next_instr') {
- @last_instr = ($args[0]);
- } elsif ($op eq 'set_var_next_arg') {
- push @last_instr, $args[0];
- } elsif ($op eq 'is_type_set_var_next_arg') {
- push @last_instr, $args[1];
- } elsif ($op eq 'is_type_eq_set_var_next_arg') {
- push @last_instr, $args[2];
+ @last_instr = ($args[0],'ignored');
+ $ap = 1;
} elsif ($op eq 'next_arg') {
- push @last_instr, 'ignored';
+ $ap++;
+ $last_instr[$ap] = 'ignored'
+ unless defined $pos;
+ } elsif ($op eq 'set_var') {
+ $last_instr[$ap] = $args[0];
} elsif ($op eq 'new_instr') {
unless (defined $pos) {
# 'new_instr' immediately after 'commit'.
$reused_instr = $args[0];
- return unless shift(@last_instr) == $reused_instr;
+ return unless $last_instr[0] == $reused_instr;
$pos = $i - 1;
+ $ap = 1;
} else {
# Second 'new_instr' after 'commit'. The instructions
# from $pos up to and including $i - 1 rebuilds the
@@ -3196,8 +3152,8 @@ sub tr_maybe_keep {
splice @$ref, $pos, $i-$pos, ($reuse);
return;
}
- } elsif ($op eq 'store_var_next_arg') {
- return unless @last_instr and shift(@last_instr) eq $args[0];
+ } elsif ($op eq 'store_var') {
+ return unless $last_instr[$ap] eq $args[0];
} elsif (defined $pos) {
return;
}
@@ -3235,26 +3191,7 @@ sub tr_maybe_rename {
my($op, @args) = @$instr_ref;
if ($s eq 'left') {
- if ($op eq 'set_var_next_arg') {
- if ($num_args == $a and $args[0] == $a) {
- $num_args++;
- }
- $a++;
- } elsif ($op eq 'is_type_set_var_next_arg') {
- if ($num_args == $a and $args[1] == $a) {
- $num_args++;
- }
- $a++;
- } elsif ($op eq 'is_type_eq_set_var_next_arg') {
- if ($num_args == $a and $args[2] == $a) {
- $num_args++;
- }
- $a++;
- } elsif ($op eq 'next_arg') {
- $a++;
- } elsif ($op eq 'is_type_next_arg') {
- $a++;
- } elsif ($op eq 'is_type_eq_next_arg') {
+ if ($op eq 'next_arg') {
$a++;
} elsif ($op eq 'commit') {
$a = 0;
@@ -3272,9 +3209,7 @@ sub tr_maybe_rename {
return;
}
} elsif ($s eq 'right') {
- if ($op eq 'store_var_next_arg' && $args[0] == $a) {
- $a++;
- } elsif ($op eq 'end' && $a <= $num_args) {
+ if ($op eq 'end' && $a <= $num_args) {
my $name = $gen_opname[$new_instr];
my $arity = $gen_arity[$new_instr];
my $new_op = make_op("$name/$arity", 'rename', $new_instr);
@@ -3303,19 +3238,10 @@ sub tr_remove_unused {
for my $instr (@$ref) {
my($size, $instr_ref, $comment) = @$instr;
my($op, @args) = @$instr_ref;
- if ($op eq 'set_var_next_arg') {
+ if ($op eq 'set_var') {
my $var = $args[0];
next if $used{$var};
- $instr = make_op("$comment (ignored)", 'next_arg');
- } elsif ($op eq 'is_type_set_var_next_arg') {
- my($type,$var) = @args;
- next if $used{$var};
- $instr = make_op("$comment (ignored)", 'is_type_next_arg', $type);
- } elsif ($op eq 'is_type_eq_set_var_next_arg') {
- my($type,$val,$var) = @args;
- next if $used{$var};
- $instr = make_op("$comment (ignored)", 'is_type_eq_next_arg',
- $type, $val);
+ $instr = make_op("(ignoring unused variable $comment)", 'nop');
}
}
--
2.35.3