aboutsummaryrefslogtreecommitdiffstats
path: root/lib/compiler/src/beam_ssa_recv.erl
diff options
context:
space:
mode:
authorBjörn Gustavsson <[email protected]>2018-02-01 08:33:10 +0100
committerBjörn Gustavsson <[email protected]>2018-08-24 09:57:06 +0200
commit6bee2ac7d11668888d93ec4f93730bcae3e5fa79 (patch)
treedf6c6be429ccacfa9c1cf7ea07f890892f73b461 /lib/compiler/src/beam_ssa_recv.erl
parent004257f6fc1ea9efea1c99a93211e2f39b1d14ad (diff)
downloadotp-6bee2ac7d11668888d93ec4f93730bcae3e5fa79.tar.gz
otp-6bee2ac7d11668888d93ec4f93730bcae3e5fa79.tar.bz2
otp-6bee2ac7d11668888d93ec4f93730bcae3e5fa79.zip
Introduce a new SSA-based intermediate format
v3_codegen is replaced by three new passes: * beam_kernel_to_ssa which translates the Kernel Erlang format to a new SSA-based intermediate format. * beam_ssa_pre_codegen which prepares the SSA-based format for code generation, including register allocation. Registers are allocated using the linear scan algorithm. * beam_ssa_codegen which generates BEAM assembly code from the SSA-based format. It easier and more effective to optimize the SSA-based format before X and Y registers have been assigned. The current optimization passes constantly have to make sure no "holes" in the X register assignments are created (that is, that no X register becomes undefined that an allocation instruction depends on). This commit also introduces the following optimizations: * Replacing of tuple matching of records with the is_tagged_tuple instruction. (Replacing beam_record.) * Sinking of get_tuple_element instructions to just before the first use of the extracted values. As well as potentially avoiding extracting tuple elements when they are not actually used on all executions paths, this optimization could also reduce the number values that will need to be stored in Y registers. (Similar to beam_reorder, but more effective.) * Live optimizations, removing the definition of a variable that is not subsequently used (provided that the operation has no side effects), as well strength reduction of binary matching by replacing the extraction of value from a binary with a skip instruction. (Used to be done by beam_block, beam_utils, and v3_codegen.) * Removal of redundant bs_restore2 instructions. (Formerly done by beam_bs.) * Type-based optimizations across branches. More effective than the old beam_type pass that only did type-based optimizations in basic blocks. * Optimization of floating point instructions. (Formerly done by beam_type.) * Optimization of receive statements to introduce recv_mark and recv_set instructions. More effective with far fewer restrictions on what instructions are allowed between creating the reference and entering the receive statement. * Common subexpression elimination. (Formerly done by beam_block.)
Diffstat (limited to 'lib/compiler/src/beam_ssa_recv.erl')
-rw-r--r--lib/compiler/src/beam_ssa_recv.erl281
1 files changed, 281 insertions, 0 deletions
diff --git a/lib/compiler/src/beam_ssa_recv.erl b/lib/compiler/src/beam_ssa_recv.erl
new file mode 100644
index 0000000000..82fe006487
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_recv.erl
@@ -0,0 +1,281 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(beam_ssa_recv).
+-export([module/2]).
+
+%%%
+%%% In code such as:
+%%%
+%%% Ref = make_ref(), %Or erlang:monitor(process, Pid)
+%%% .
+%%% .
+%%% .
+%%% receive
+%%% {Ref,Reply} -> Reply
+%%% end.
+%%%
+%%% we know that none of the messages that exist in the message queue
+%%% before the call to make_ref/0 can be matched out in the receive
+%%% statement. Therefore we can avoid going through the entire message
+%%% queue if we introduce two new instructions (here written as
+%%% BIFs in pseudo-Erlang):
+%%%
+%%% recv_mark(SomeUniqInteger),
+%%% Ref = make_ref(),
+%%% .
+%%% .
+%%% .
+%%% recv_set(SomeUniqInteger),
+%%% receive
+%%% {Ref,Reply} -> Reply
+%%% end.
+%%%
+%%% The recv_mark/1 instruction will save the current position and
+%%% SomeUniqInteger in the process context. The recv_set
+%%% instruction will verify that SomeUniqInteger is still stored
+%%% in the process context. If it is, it will set the current pointer
+%%% for the message queue (the next message to be read out) to the
+%%% position that was saved by recv_mark/1.
+%%%
+%%% The remove_message instruction must be modified to invalidate
+%%% the information stored by the previous recv_mark/1, in case there
+%%% is another receive executed between the calls to recv_mark/1 and
+%%% recv_set/1.
+%%%
+%%% We use a reference to a label (i.e. a position in the loaded code)
+%%% as the SomeUniqInteger.
+%%%
+
+-include("beam_ssa.hrl").
+-import(lists, [all/2,reverse/2]).
+
+-spec module(beam_ssa:b_module(), [compile:option()]) ->
+ {'ok',beam_ssa:b_module()}.
+
+module(#b_module{body=Fs0}=Module, _Opts) ->
+ Fs = [function(F) || F <- Fs0],
+ {ok,Module#b_module{body=Fs}}.
+
+%%%
+%%% Local functions.
+%%%
+
+function(#b_function{anno=Anno,bs=Blocks0}=F) ->
+ try
+ Blocks = opt(Blocks0),
+ F#b_function{bs=Blocks}
+ catch
+ Class:Error:Stack ->
+ #{func_info:={_,Name,Arity}} = Anno,
+ io:fwrite("Function: ~w/~w\n", [Name,Arity]),
+ erlang:raise(Class, Error, Stack)
+ end.
+
+opt(Blocks) ->
+ Linear = beam_ssa:linearize(Blocks),
+ opt(Linear, Blocks, []).
+
+opt([{L,#b_blk{is=[#b_set{op=peek_message}|_]}=Blk0}|Bs], Blocks0, Preds) ->
+ %% Search for a suitable reference creating call in one of the predecessor
+ %% blocks. Whether we find such a call or not, we always clear the
+ %% the list of predecessors to ensure that any nested receive can't
+ %% search above the current receive.
+ case recv_opt(Preds, L, Blocks0) of
+ {yes,Blocks1} ->
+ Blk = beam_ssa:add_anno(recv_set, L, Blk0),
+ Blocks = maps:put(L, Blk, Blocks1),
+ opt(Bs, Blocks, []);
+ no ->
+ opt(Bs, Blocks0, [])
+ end;
+opt([{L,_}|Bs], Blocks, Preds) ->
+ opt(Bs, Blocks, [L|Preds]);
+opt([], Blocks, _) -> Blocks.
+
+recv_opt([L|Ls], RecvLbl, Blocks) ->
+ #b_blk{is=Is0} = Blk0 = maps:get(L, Blocks),
+ case recv_opt_is(Is0, RecvLbl, Blocks, []) of
+ {yes,Is} ->
+ Blk = Blk0#b_blk{is=Is},
+ {yes,maps:put(L, Blk, Blocks)};
+ no ->
+ recv_opt(Ls, RecvLbl, Blocks)
+ end;
+recv_opt([], _, _Blocks) -> no.
+
+recv_opt_is([#b_set{op=call}=I0|Is], RecvLbl, Blocks0, Acc) ->
+ case makes_ref(I0, Blocks0) of
+ no ->
+ recv_opt_is(Is, RecvLbl, Blocks0, [I0|Acc]);
+ {yes,Ref} ->
+ case opt_ref_used(RecvLbl, Ref, Blocks0) of
+ false ->
+ recv_opt_is(Is, RecvLbl, Blocks0, [I0|Acc]);
+ true ->
+ I = beam_ssa:add_anno(recv_mark, RecvLbl, I0),
+ {yes,reverse(Acc, [I|Is])}
+ end
+ end;
+recv_opt_is([I|Is], RecvLbl, Blocks, Acc) ->
+ recv_opt_is(Is, RecvLbl, Blocks, [I|Acc]);
+recv_opt_is([], _, _, _) -> no.
+
+makes_ref(#b_set{dst=#b_var{name=Dst},args=[Func0|_]}, Blocks) ->
+ Func = case Func0 of
+ #b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=Name},arity=A0} ->
+ {Name,A0};
+ _ ->
+ none
+ end,
+ case Func of
+ {make_ref,0} ->
+ {yes,Dst};
+ {monitor,2} ->
+ {yes,Dst};
+ {spawn_monitor,A} when A =:= 1; A =:= 3 ->
+ ref_in_tuple(Dst, Blocks);
+ _ ->
+ no
+ end.
+
+ref_in_tuple(Tuple, Blocks) ->
+ F = fun(#b_set{op=get_tuple_element,dst=#b_var{name=Ref},
+ args=[#b_var{name=Tup},#b_literal{val=1}]}, no)
+ when Tup =:= Tuple -> {yes,Ref};
+ (_, A) -> A
+ end,
+ beam_ssa:fold_instrs_rpo(F, [0], no, Blocks).
+
+opt_ref_used(RecvLbl, Ref, Blocks) ->
+ Vs = #{{var,Ref}=>ref,ref=>Ref,ref_matched=>false},
+ case opt_ref_used_1(RecvLbl, Vs, Blocks) of
+ used -> true;
+ not_used -> false;
+ done -> false
+ end.
+
+opt_ref_used_1(L, Vs0, Blocks) ->
+ #b_blk{is=Is} = Blk = maps:get(L, Blocks),
+ case opt_ref_used_is(Is, Vs0) of
+ #{}=Vs ->
+ opt_ref_used_last(Blk, Vs, Blocks);
+ Result ->
+ Result
+ end.
+
+opt_ref_used_is([#b_set{op=peek_message,dst=#b_var{name=M}}|Is], Vs0) ->
+ Vs = Vs0#{{var,M}=>message},
+ opt_ref_used_is(Is, Vs);
+opt_ref_used_is([#b_set{op={bif,Bif},args=Args,dst=#b_var{name=B}}=I|Is],
+ Vs0) ->
+ S = case Bif of
+ '=:=' -> true;
+ '==' -> true;
+ _ -> none
+ end,
+ case S of
+ none ->
+ Vs = update_vars(I, Vs0),
+ opt_ref_used_is(Is, Vs);
+ Bool when is_boolean(Bool) ->
+ case is_ref_msg_comparison(Args, Vs0) of
+ true ->
+ Vs = Vs0#{B=>{is_ref,Bool}},
+ opt_ref_used_is(Is, Vs);
+ false ->
+ opt_ref_used_is(Is, Vs0)
+ end
+ end;
+opt_ref_used_is([#b_set{op=remove_message}|_], Vs) ->
+ case Vs of
+ #{ref_matched:=true} ->
+ used;
+ #{ref_matched:=false} ->
+ not_used
+ end;
+opt_ref_used_is([#b_set{op=recv_next}|_], _Vs) ->
+ done;
+opt_ref_used_is([#b_set{op=wait_timeout}|_], _Vs) ->
+ done;
+opt_ref_used_is([#b_set{op=wait}|_], _Vs) ->
+ done;
+opt_ref_used_is([#b_set{}=I|Is], Vs0) ->
+ Vs = update_vars(I, Vs0),
+ opt_ref_used_is(Is, Vs);
+opt_ref_used_is([], Vs) -> Vs.
+
+opt_ref_used_last(#b_blk{last=Last}=Blk, Vs, Blocks) ->
+ case Last of
+ #b_br{bool=#b_var{name=Bool},succ=Succ,fail=Fail} ->
+ case Vs of
+ #{Bool:={is_ref,Matched}} ->
+ ref_used_in([{Succ,Vs#{ref_matched:=Matched}},
+ {Fail,Vs#{ref_matched:=not Matched}}],
+ Blocks);
+ #{} ->
+ ref_used_in([{Succ,Vs},{Fail,Vs}], Blocks)
+ end;
+ _ ->
+ SuccVs = [{Succ,Vs} || Succ <- beam_ssa:successors(Blk)],
+ ref_used_in(SuccVs, Blocks)
+ end.
+
+ref_used_in([{L,Vs0}|Ls], Blocks) ->
+ case opt_ref_used_1(L, Vs0, Blocks) of
+ not_used ->
+ not_used;
+ used ->
+ case ref_used_in(Ls, Blocks) of
+ done -> used;
+ Result -> Result
+ end;
+ done -> ref_used_in(Ls, Blocks)
+ end;
+ref_used_in([], _) -> done.
+
+update_vars(#b_set{args=Args,dst=#b_var{name=B}}, Vs) ->
+ Vars = [V || #b_var{name=V} <- Args],
+ All = all(fun(V) ->
+ Var = {var,V},
+ case Vs of
+ #{Var:=message} -> true;
+ #{} -> false
+ end
+ end, Vars),
+ case All of
+ true -> Vs#{{var,B}=>message};
+ false -> Vs
+ end.
+
+%% is_ref_msg_comparison(Args, Variables) -> true|false.
+%% Return 'true' if Args denotes a comparison between the
+%% reference and message or part of the message.
+
+is_ref_msg_comparison([#b_var{name=A1},#b_var{name=A2}], Vs) ->
+ V1 = {var,A1},
+ V2 = {var,A2},
+ case Vs of
+ #{V1:=ref,V2:=message} -> true;
+ #{V1:=message,V2:=ref} -> true;
+ #{} -> false
+ end;
+is_ref_msg_comparison(_, _) -> false.