summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Högberg <john@erlang.org>2021-01-14 11:19:36 +0100
committerJohn Högberg <john@erlang.org>2021-01-22 10:53:01 +0100
commitc0d65e0d400af75e76bf30001f1c2d85709a12a5 (patch)
tree466e1bc038d4e937d9b032239b3d0dad4ab8fa5d
parent9ffab401c69fe41db473409b04338e24c0980ad0 (diff)
downloaderlang-c0d65e0d400af75e76bf30001f1c2d85709a12a5.tar.gz
compiler: Introduce +recv_opt_info
-rw-r--r--lib/compiler/doc/src/compile.xml10
-rw-r--r--lib/compiler/src/beam_kernel_to_ssa.erl9
-rw-r--r--lib/compiler/src/beam_ssa_recv.erl159
-rw-r--r--lib/compiler/src/v3_core.erl7
-rw-r--r--lib/compiler/test/compilation_SUITE.erl5
-rw-r--r--lib/compiler/test/compile_SUITE.erl3
-rw-r--r--lib/compiler/test/inline_SUITE.erl8
-rw-r--r--lib/compiler/test/test_lib.erl4
-rw-r--r--lib/compiler/test/warnings_SUITE.erl61
-rw-r--r--system/doc/efficiency_guide/efficiency_guide.erl42
-rw-r--r--system/doc/efficiency_guide/processes.xml130
11 files changed, 398 insertions, 40 deletions
diff --git a/lib/compiler/doc/src/compile.xml b/lib/compiler/doc/src/compile.xml
index 0e2f29aa9d..8329ed867c 100644
--- a/lib/compiler/doc/src/compile.xml
+++ b/lib/compiler/doc/src/compile.xml
@@ -314,6 +314,16 @@ module.beam: module.erl \
</p>
</item>
+ <tag><c>recv_opt_info</c></tag>
+ <item>
+ <p>The compiler will emit informational warnings about selective
+ receive optimizations (both successful and unsuccessful). For
+ more information, see the section about
+ <seeguide marker="system/efficiency_guide:processes#receiving-messages">
+ selective receive optimization</seeguide> in the Efficiency
+ Guide.</p>
+ </item>
+
<tag><c>report_errors/report_warnings</c></tag>
<item>
<p>Causes errors/warnings to be printed as they occur.</p>
diff --git a/lib/compiler/src/beam_kernel_to_ssa.erl b/lib/compiler/src/beam_kernel_to_ssa.erl
index 86aaad7f66..2d3e6b730a 100644
--- a/lib/compiler/src/beam_kernel_to_ssa.erl
+++ b/lib/compiler/src/beam_kernel_to_ssa.erl
@@ -724,11 +724,14 @@ internal_cg(_Anno, raise, As, [#k_var{name=Dst0}], St0) ->
Is = [Resume,make_uncond_branch(Fail),#cg_unreachable{}],
{Is,St}
end;
-internal_cg(_Anno, recv_peek_message, [], [#k_var{name=Succeeded0},
- #k_var{name=Dst0}], St0) ->
+internal_cg(Anno, recv_peek_message, [], [#k_var{name=Succeeded0},
+ #k_var{name=Dst0}], St0) ->
{Dst,St1} = new_ssa_var(Dst0, St0),
St = new_succeeded_value(Succeeded0, Dst, St1),
- Set = #b_set{op=peek_message,dst=Dst,args=[#b_literal{val=none}]},
+ Set = #b_set{ anno=Anno,
+ op=peek_message,
+ dst=Dst,
+ args=[#b_literal{val=none}] },
{[Set],St};
internal_cg(_Anno, recv_wait_timeout, As, [#k_var{name=Succeeded0}], St0) ->
%% Note that the `wait_timeout` instruction can potentially branch in three
diff --git a/lib/compiler/src/beam_ssa_recv.erl b/lib/compiler/src/beam_ssa_recv.erl
index f84708435d..3bbdd4683e 100644
--- a/lib/compiler/src/beam_ssa_recv.erl
+++ b/lib/compiler/src/beam_ssa_recv.erl
@@ -19,7 +19,7 @@
%%
-module(beam_ssa_recv).
--export([module/2]).
+-export([format_error/1, module/2]).
%%%
%%% In code such as:
@@ -106,26 +106,40 @@
-import(lists, [foldl/3, search/2]).
+-spec format_error(term()) -> nonempty_string().
+
+format_error(OptInfo) ->
+ format_opt_info(OptInfo).
+
-record(scan, { graph=beam_digraph:new(),
module :: #{ beam_ssa:b_local() => {beam_ssa:block_map(),
[beam_ssa:b_var()]} },
recv_candidates=#{},
ref_candidates=#{} }).
--spec module(beam_ssa:b_module(), [compile:option()]) ->
- {'ok',beam_ssa:b_module()}.
+-spec module(Module, Options) -> Result when
+ Module :: beam_ssa:b_module(),
+ Options :: [compile:option()],
+ Result :: {ok, beam_ssa:b_module(), list()}.
-module(#b_module{}=Mod, _Options) ->
+module(#b_module{}=Mod0, Opts) ->
%% Builds a module-wide graph of all blocks (including calls between
%% functions), and collects all suitable reference creations and receives
%% for later analysis.
- Scan = scan(Mod),
+ Scan = scan(Mod0),
%% Figures out where to place marker creation, usage, and clearing by
%% walking through the module-wide graph.
{Markers, Uses, Clears} = plan(Scan),
- optimize(Mod, Markers, Uses, Clears).
+ Mod = optimize(Mod0, Markers, Uses, Clears),
+
+ Ws = case proplists:get_bool(recv_opt_info, Opts) of
+ true -> collect_opt_info(Mod);
+ false -> []
+ end,
+
+ {ok, Mod, Ws}.
scan(#b_module{body=Fs0}) ->
ModMap = foldl(fun(#b_function{bs=Blocks,args=Args}=F, Acc) ->
@@ -181,17 +195,15 @@ scan_is([#b_set{op=bs_put}], Blk, Lbl, Blocks, FuncId, State) ->
%% This may throw and returns a success variable, so we'll treat it as
%% that.
scan_is([#b_set{op={succeeded,body}}], Blk, Lbl, Blocks, FuncId, State);
-scan_is([#b_set{op=call,dst=Dst,args=[#b_remote{}=Callee | _]} | Is],
+scan_is([#b_set{op=call,args=[#b_remote{} | _]}=Call | Is],
Blk, Lbl, Blocks, FuncId, State0) ->
case {Is, Blk#b_blk.last} of
{[#b_set{op={succeeded,body}}], #b_br{bool=Bool,succ=Succ}} ->
#b_var{} = Bool, %Assertion.
- State = si_remote_call(Dst, Callee, Lbl, Succ,
- Blocks, FuncId, State0),
+ State = si_remote_call(Call, Lbl, Succ, Blocks, FuncId, State0),
scan_is(Is, Blk, Lbl, Blocks, FuncId, State);
_ ->
- State = si_remote_call(Dst, Callee, Lbl, Lbl,
- Blocks, FuncId, State0),
+ State = si_remote_call(Call, Lbl, Lbl, Blocks, FuncId, State0),
scan_is(Is, Blk, Lbl, Blocks, FuncId, State)
end;
scan_is([#b_set{op=call,args=[#b_local{}=Callee | Args]} | Is],
@@ -205,7 +217,7 @@ scan_is([], Blk, Lbl, _Blocks, FuncId, State) ->
scan_add_edge({FuncId, Lbl}, {FuncId, Succ}, Acc)
end, State, beam_ssa:successors(Blk)).
-%% Adds an edge to the callee, with parameter/argument translation to let us
+%% Adds an edge to the callee, with argument/parameter translation to let us
%% follow specific references.
scan_add_call(Args, Callee, Lbl, Caller, #scan{module=ModMap}=State0) ->
#{ Callee := {_Blocks, Params} } = ModMap,
@@ -238,13 +250,14 @@ scan_add_vertex(Vertex, #scan{graph=Graph0}=State) ->
State#scan{graph=Graph}
end.
-si_remote_call(Dst, Callee, CreatedAt, ValidAfter, Blocks, FuncId, State) ->
+si_remote_call(Call, CreatedAt, ValidAfter, Blocks, FuncId, State) ->
+ #b_set{anno=Anno,op=call,dst=Dst,args=[#b_remote{}=Callee | _]}=Call,
case si_makes_ref(Dst, ValidAfter, Callee, Blocks) of
{ExtractedAt, Ref} ->
#scan{ref_candidates=Candidates0} = State,
MakeRefs0 = maps:get(FuncId, Candidates0, []),
- MakeRef = {CreatedAt, Dst, ExtractedAt, Ref},
+ MakeRef = {Anno, CreatedAt, Dst, ExtractedAt, Ref},
Candidates = Candidates0#{ FuncId => [MakeRef | MakeRefs0] },
@@ -334,7 +347,7 @@ plan(Scan) ->
propagate_references(Candidates, G) ->
Roots = maps:fold(fun(FuncId, MakeRefs, Acc) ->
[begin
- {_, _, ExtractedAt, Ref} = MakeRef,
+ {_, _, _, ExtractedAt, Ref} = MakeRef,
Vertex = {FuncId, ExtractedAt},
{Vertex, Ref}
end || MakeRef <- MakeRefs] ++ Acc
@@ -573,7 +586,7 @@ plan_markers(Candidates, UsageMap) ->
end, #{}, Candidates).
plan_markers_1(MakeRefs0, FuncId, UsageMap) ->
- [Marker || {_, _, ExtractedAt, Ref}=Marker <- MakeRefs0,
+ [Marker || {_, _, _, ExtractedAt, Ref}=Marker <- MakeRefs0,
case UsageMap of
#{ {FuncId, ExtractedAt} := Refs } ->
sets:is_element(Ref, Refs);
@@ -613,7 +626,7 @@ plan_clears_1([], _ActiveRefs, _UsageMap) ->
optimize(#b_module{body=Fs0}=Mod, Markers, Uses, Clears) ->
Fs = [optimize_1(F, Markers, Uses, Clears) || F <- Fs0],
- {ok, Mod#b_module{body=Fs}}.
+ Mod#b_module{body=Fs}.
optimize_1(#b_function{bs=Blocks0,cnt=Count0}=F, Markers, Uses, Clears) ->
FuncId = get_func_id(F),
@@ -627,23 +640,23 @@ optimize_1(#b_function{bs=Blocks0,cnt=Count0}=F, Markers, Uses, Clears) ->
F#b_function{bs=Blocks,cnt=Count}.
-insert_markers([{CreatedAt, Dst, ExtractedAt, Ref} | Markers],
+insert_markers([{Anno, CreatedAt, Dst, ExtractedAt, Ref} | Markers],
Blocks0, Count0) ->
{MarkerVar, Blocks1, Count1} =
- insert_reserve(CreatedAt, Dst, Blocks0, Count0),
+ insert_reserve(CreatedAt, Dst, Anno, Blocks0, Count0),
{Blocks, Count} =
insert_bind(ExtractedAt, Ref, MarkerVar, Blocks1, Count1),
insert_markers(Markers, Blocks, Count);
insert_markers([], Blocks, Count) ->
{Blocks, Count}.
-insert_reserve(Lbl, Dst, Blocks0, Count0) ->
+insert_reserve(Lbl, Dst, Anno, Blocks0, Count0) ->
#{ Lbl := #b_blk{is=Is0}=Blk } = Blocks0,
Var = #b_var{name={'@ssa_recv_marker', Count0}},
Count = Count0 + 1,
- Reserve = #b_set{op=recv_marker_reserve,args=[],dst=Var},
+ Reserve = #b_set{anno=Anno,op=recv_marker_reserve,args=[],dst=Var},
Is = insert_reserve_is(Is0, Reserve, Dst),
Blocks = Blocks0#{ Lbl := Blk#b_blk{is=Is} },
@@ -703,3 +716,107 @@ insert_clears_1([{From, To, Ref} | Clears], Count0, Acc) ->
insert_clears_1(Clears, Count, [{From, To, [Clear]} | Acc]);
insert_clears_1([], Count, Acc) ->
{Acc, Count}.
+
+%%%
+%%% +recv_opt_info
+%%%
+
+collect_opt_info(#b_module{body=Fs}) ->
+ coi_1(Fs, []).
+
+coi_1([#b_function{args=Args,bs=Blocks}=F | Fs], Acc0) ->
+ Lbls = beam_ssa:rpo(Blocks),
+ Where = beam_ssa:get_anno(location, F, []),
+ {Defs, _} = foldl(fun(Var, {Defs0, Index0}) ->
+ Defs = Defs0#{ Var => {parameter, Index0}},
+ Index = Index0 + 1,
+ {Defs, Index}
+ end, {#{}, 1}, Args),
+ Acc = coi_bs(Lbls, Blocks, Where, Defs, Acc0),
+ coi_1(Fs, Acc);
+coi_1([], Acc) ->
+ Acc.
+
+coi_bs([Lbl | Lbls], Blocks, Where, Defs0, Ws0) ->
+ #{ Lbl := #b_blk{is=Is,last=Last} } = Blocks,
+ {Defs, Ws} = coi_is(Is, Last, Blocks, Where, Defs0, Ws0),
+ coi_bs(Lbls, Blocks, Where, Defs, Ws);
+coi_bs([], _Blocks, _Where, _Defs, Ws) ->
+ Ws.
+
+coi_is([#b_set{anno=Anno,op=peek_message,args=[#b_var{}]=Args } | Is],
+ Last, Blocks, Where, Defs, Ws) ->
+ [Creation] = coi_creations(Args, Blocks, Defs),
+ Warning = make_warning({used_receive_marker, Creation}, Anno, Where),
+ coi_is(Is, Last, Blocks, Where, Defs, [Warning | Ws]);
+coi_is([#b_set{anno=Anno,op=peek_message,args=[#b_literal{}] } | Is],
+ Last, Blocks, Where, Defs, Ws) ->
+
+ %% Is this a selective receive?
+ #b_br{succ=NextMsg} = Last,
+ #{ NextMsg := #b_blk{is=NextIs} } = Blocks,
+ Info = case NextIs of
+ [#b_set{op=remove_message} | _] -> matches_any_message;
+ _ -> unoptimized_selective_receive
+ end,
+
+ Warning = make_warning(Info, Anno, Where),
+ coi_is(Is, Last, Blocks, Where, Defs, [Warning | Ws]);
+coi_is([#b_set{anno=Anno,op=recv_marker_reserve} | Is],
+ Last, Blocks, Where, Defs, Ws) ->
+ Warning = make_warning(reserved_receive_marker, Anno, Where),
+ coi_is(Is, Last, Blocks, Where, Defs, [Warning | Ws]);
+coi_is([#b_set{anno=Anno,op=call,dst=Dst,args=[#b_local{} | Args] }=I | Is],
+ Last, Blocks, Where, Defs0, Ws0) ->
+ Defs = Defs0#{ Dst => I },
+ Ws = [make_warning({passed_marker, Creation}, Anno, Where)
+ || #b_set{}=Creation <- coi_creations(Args, Blocks, Defs)] ++ Ws0,
+ coi_is(Is, Last, Blocks, Where, Defs, Ws);
+coi_is([#b_set{dst=Dst}=I | Is], Last, Blocks, Where, Defs0, Ws) ->
+ Defs = Defs0#{ Dst => I },
+ coi_is(Is, Last, Blocks, Where, Defs, Ws);
+coi_is([], _Last, _Blocks, _Where, Defs, Ws) ->
+ {Defs, Ws}.
+
+coi_creations([Arg | Args], Blocks, Defs) ->
+ case Defs of
+ #{ Arg := #b_set{op=call,dst=Dst,args=[#b_remote{}=Callee|_]}=Call } ->
+ case si_makes_ref(Dst, 0, Callee, Blocks) of
+ {_, _} ->
+ [Call | coi_creations(Args, Blocks, Defs)];
+ no ->
+ coi_creations(Args, Blocks, Defs)
+ end;
+ #{ Arg := #b_set{op=get_tuple_element,args=[Tuple|_]}} ->
+ coi_creations([Tuple | Args], Blocks, Defs);
+ #{ Arg := {parameter, _}=Parameter } ->
+ [Parameter | coi_creations(Args, Blocks, Defs)];
+ #{} ->
+ coi_creations(Args, Blocks, Defs)
+ end;
+coi_creations([], _Blocks, _Defs) ->
+ [].
+
+make_warning(Term, Anno, Where) ->
+ {File, Line} = maps:get(location, Anno, Where),
+ {File,[{Line,?MODULE,Term}]}.
+
+format_opt_info(matches_any_message) ->
+ "INFO: receive matches any message, this is always fast";
+format_opt_info({passed_marker, Creation}) ->
+ io_lib:format("INFO: passing reference ~ts",
+ [format_ref_creation(Creation)]);
+format_opt_info({used_receive_marker, Creation}) ->
+ io_lib:format("OPTIMIZED: all clauses match reference ~ts",
+ [format_ref_creation(Creation)]);
+format_opt_info(reserved_receive_marker) ->
+ "OPTIMIZED: reference used to mark a message queue position";
+format_opt_info(unoptimized_selective_receive) ->
+ "NOT OPTIMIZED: all clauses do not match a suitable reference".
+
+format_ref_creation({parameter, Index}) ->
+ io_lib:format("in function parameter ~w", [Index]);
+format_ref_creation(#b_set{op=call,anno=Anno,args=[Callee|_]}) ->
+ #b_remote{name=#b_literal{val=F},arity=A} = Callee,
+ {File, Line} = maps:get(location, Anno, {"",1}),
+ io_lib:format("created by ~p/~p at ~ts:~w", [F, A, File, Line]).
diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl
index ae1a2cf36c..d4ee6053ab 100644
--- a/lib/compiler/src/v3_core.erl
+++ b/lib/compiler/src/v3_core.erl
@@ -3001,7 +3001,7 @@ lexpr(#c_receive{anno=RecvAnno,clauses=Cs0,timeout=Timeout0,action=Action}, St0)
body=TimeoutLet}],
PeekCase = #c_case{arg=PeekSucceeded,clauses=PeekCs},
PeekLet = #c_let{vars=[PeekSucceeded,Msg],
- arg=primop(recv_peek_message),
+ arg=primop(recv_peek_message, [], RecvAnno),
body=PeekCase},
Fun = #c_fun{vars=[],body=PeekLet},
@@ -3027,7 +3027,10 @@ primop(Name) ->
primop(Name, []).
primop(Name, Args) ->
- #c_primop{name=#c_literal{val=Name},args=Args}.
+ primop(Name, Args, []).
+
+primop(Name, Args, Anno) ->
+ #c_primop{anno=Anno,name=#c_literal{val=Name},args=Args}.
%%%
%%% Split patterns such as <<Size:32,Tail:Size>> that bind
diff --git a/lib/compiler/test/compilation_SUITE.erl b/lib/compiler/test/compilation_SUITE.erl
index 94c857471b..8b3846bade 100644
--- a/lib/compiler/test/compilation_SUITE.erl
+++ b/lib/compiler/test/compilation_SUITE.erl
@@ -176,7 +176,7 @@ try_it(Module, Conf) ->
Out = proplists:get_value(priv_dir,Conf),
io:format("Compiling: ~s\n", [Src]),
CompRc0 = compile:file(Src, [clint0,clint,ssalint,{outdir,Out},report,
- bin_opt_info|OtherOpts]),
+ bin_opt_info,recv_opt_info|OtherOpts]),
io:format("Result: ~p\n",[CompRc0]),
{ok,_Mod} = CompRc0,
@@ -196,7 +196,7 @@ try_it(Module, Conf) ->
io:format("Compiling (with old inliner): ~s\n", [Src]),
CompRc2 = compile:file(Src, [clint,ssalint,
{outdir,Out},report,bin_opt_info,
- {inline,1000}|OtherOpts]),
+ recv_opt_info,{inline,1000}|OtherOpts]),
io:format("Result: ~p\n",[CompRc2]),
{ok,_Mod} = CompRc2,
load_and_call(Out, Module),
@@ -362,6 +362,7 @@ compile_compiler(Files, OutDir, Version, InlineOpts) ->
Opts = [report,
clint0,clint,ssalint,
bin_opt_info,
+ recv_opt_info,
{outdir,OutDir},
{d,'COMPILER_VSN',"\""++Version++"\""},
nowarn_shadow_vars,
diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl
index 4689bb9e2a..290ed87016 100644
--- a/lib/compiler/test/compile_SUITE.erl
+++ b/lib/compiler/test/compile_SUITE.erl
@@ -1223,7 +1223,7 @@ warnings(_Config) ->
test_lib:p_run(fun do_warnings/1, Files).
do_warnings(F) ->
- {ok,_,_,Ws} = compile:file(F, [binary,bin_opt_info,return]),
+ {ok,_,_,Ws} = compile:file(F, [binary,bin_opt_info,recv_opt_info,return]),
do_warnings_1(Ws, F).
do_warnings_1([{"no_file",Ws}|_], F) ->
@@ -1368,6 +1368,7 @@ compiler_modules() ->
env_compiler_options(_Config) ->
Cases = [
{"bin_opt_info", [bin_opt_info]},
+ {"recv_opt_info", [recv_opt_info]},
{"'S'", ['S']},
{"{source, \"test.erl\"}", [{source, "test.erl"}]},
{"[{d,macro_one,1},{d,macro_two}]", [{d, macro_one, 1}, {d, macro_two}]},
diff --git a/lib/compiler/test/inline_SUITE.erl b/lib/compiler/test/inline_SUITE.erl
index df2d9f472b..1cb30b22da 100644
--- a/lib/compiler/test/inline_SUITE.erl
+++ b/lib/compiler/test/inline_SUITE.erl
@@ -92,7 +92,7 @@ try_inline(Mod, Config) ->
%% Normal compilation.
io:format("Compiling: ~s\n", [Src]),
{ok,Mod} = compile:file(Src, [{outdir,Out},report,
- bin_opt_info,clint,ssalint]),
+ bin_opt_info,recv_opt_info,clint,ssalint]),
ct:timetrap({minutes,10}),
NormalResult = load_and_call(Out, Mod),
@@ -100,7 +100,7 @@ try_inline(Mod, Config) ->
%% Inlining.
io:format("Compiling with old inliner: ~s\n", [Src]),
{ok,Mod} = compile:file(Src, [{outdir,Out},report,bin_opt_info,
- {inline,1000},clint,ssalint]),
+ recv_opt_info,{inline,1000},clint,ssalint]),
%% Run inlined code.
ct:timetrap({minutes,10}),
@@ -112,8 +112,8 @@ try_inline(Mod, Config) ->
%% Inlining.
io:format("Compiling with new inliner: ~s\n", [Src]),
- {ok,Mod} = compile:file(Src, [{outdir,Out},report,
- bin_opt_info,inline,clint,ssalint]),
+ {ok,Mod} = compile:file(Src, [{outdir,Out},report,bin_opt_info,
+ inline,recv_opt_info,clint,ssalint]),
%% Run inlined code.
ct:timetrap({minutes,10}),
diff --git a/lib/compiler/test/test_lib.erl b/lib/compiler/test/test_lib.erl
index 212392fd57..cca81d70f7 100644
--- a/lib/compiler/test/test_lib.erl
+++ b/lib/compiler/test/test_lib.erl
@@ -38,7 +38,7 @@ recompile(Mod) when is_atom(Mod) ->
%% Re-compile the test suite if the cover server is running.
Beam = code:which(Mod),
Src = filename:rootname(Beam, ".beam") ++ ".erl",
- Opts = [bin_opt_info|opt_opts(Mod)],
+ Opts = [bin_opt_info,recv_opt_info|opt_opts(Mod)],
io:format("Recompiling ~p (~p)\n", [Mod,Opts]),
c:c(Src, [{outdir,filename:dirname(Src)}|Opts])
end,
@@ -53,7 +53,7 @@ recompile_core(Mod) when is_atom(Mod) ->
%% Re-compile the test suite if the cover server is running.
Beam = code:which(Mod),
Src = filename:rootname(Beam, ".beam"),
- Opts = [bin_opt_info|opt_opts(Mod)],
+ Opts = [bin_opt_info,recv_opt_info|opt_opts(Mod)],
io:format("Recompiling ~p (~p)\n", [Mod,Opts]),
c:c(Src, [from_core,{outdir,filename:dirname(Src)}|Opts])
end,
diff --git a/lib/compiler/test/warnings_SUITE.erl b/lib/compiler/test/warnings_SUITE.erl
index 1229a13e38..46672c3a88 100644
--- a/lib/compiler/test/warnings_SUITE.erl
+++ b/lib/compiler/test/warnings_SUITE.erl
@@ -42,7 +42,8 @@
comprehensions/1,maps/1,maps_bin_opt_info/1,
redundant_boolean_clauses/1,
latin1_fallback/1,underscore/1,no_warnings/1,
- bit_syntax/1,inlining/1,tuple_calls/1]).
+ bit_syntax/1,inlining/1,tuple_calls/1,
+ recv_opt_info/1]).
init_per_testcase(_Case, Config) ->
Config.
@@ -65,7 +66,7 @@ groups() ->
maps_bin_opt_info,
redundant_boolean_clauses,latin1_fallback,
underscore,no_warnings,bit_syntax,inlining,
- tuple_calls]}].
+ tuple_calls,recv_opt_info]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -992,6 +993,62 @@ tuple_calls(Config) ->
run(Config, Ts),
ok.
+recv_opt_info(Config) when is_list(Config) ->
+ Code = <<"
+ simple_receive() ->
+ receive
+ Message -> handle:msg(Message)
+ end.
+
+ selective_receive(Tag, Message) ->
+ receive
+ {Tag, Message} -> handle:msg(Message)
+ end.
+
+ cross_function_receive() ->
+ cross_function_receive_1(make_ref()).
+
+ cross_function_receive_1(Tag) ->
+ receive
+ {Tag, Message} -> handle:msg(Message)
+ end.
+
+ optimized_receive(Process, Request) ->
+ MRef = monitor(process, Process),
+ Process ! {self(), MRef, Request},
+ receive
+ {MRef, Reply} ->
+ erlang:demonitor(MRef, [flush]),
+ handle:reply(Reply);
+ {'DOWN', MRef, _, _, Reason} ->
+ handle:error(Reason)
+ end.
+ ">>,
+
+ Ws = (catch run_test(Config, Code, [recv_opt_info])),
+
+ %% This is an inexact match since the pass reports exact instructions as
+ %% part of the warnings, which may include annotations that vary from run
+ %% to run.
+ {warnings,
+ [%% simple_receive/0
+ {3,beam_ssa_recv,matches_any_message},
+ %% selective_receive/2
+ {8,beam_ssa_recv,unoptimized_selective_receive},
+ {13,beam_ssa_recv,reserved_receive_marker},
+ %% cross_function_receive/0
+ {13,beam_ssa_recv,{passed_marker,_}},
+ %% cross_function_receive_1/1
+ {16,beam_ssa_recv,{used_receive_marker,{parameter,1}}},
+ %% optimized_receive/2
+ {21,beam_ssa_recv,reserved_receive_marker},
+ {23,beam_ssa_recv,{used_receive_marker,_}}]} = Ws,
+
+ %% For coverage: don't give the recv_opt_info option.
+ [] = (catch run_test(Config, Code, [])),
+
+ ok.
+
%%%
%%% End of test cases.
%%%
diff --git a/system/doc/efficiency_guide/efficiency_guide.erl b/system/doc/efficiency_guide/efficiency_guide.erl
index c57785aaa3..52e5610c4a 100644
--- a/system/doc/efficiency_guide/efficiency_guide.erl
+++ b/system/doc/efficiency_guide/efficiency_guide.erl
@@ -1,5 +1,5 @@
-module(efficiency_guide).
--compile([export_all,nowarn_export_all).
+-compile([export_all,nowarn_export_all]).
%% DO NOT
naive_reverse([H|T]) ->
@@ -188,5 +188,41 @@ explicit_map_pairs(Map, Xs0, Ys0) ->
[] ->
Ys0
end.
-
-
+
+%% DO
+simple_receive() ->
+ receive
+ Message -> handle_msg(Message)
+ end.
+
+%% OK, if Tag is a reference.
+selective_receive(Tag, Message) ->
+ receive
+ {Tag, Message} -> handle_msg(Message)
+ end.
+
+%% DO
+optimized_receive(Process, Request) ->
+ MRef = monitor(process, Process),
+ Process ! {self(), MRef, Request},
+ receive
+ {MRef, Reply} ->
+ erlang:demonitor(MRef, [flush]),
+ handle_reply(Reply);
+ {'DOWN', MRef, _, _, Reason} ->
+ handle_error(Reason)
+ end.
+
+%% DO
+cross_function_receive() ->
+ Ref = make_ref(),
+ cross_function_receive(Ref).
+
+cross_function_receive(Ref) ->
+ receive
+ {Ref, Message} -> handle_msg(Message)
+ end.
+
+handle_reply(_) -> ok.
+handle_error(_) -> ok.
+handle_msg(_) -> ok.
diff --git a/system/doc/efficiency_guide/processes.xml b/system/doc/efficiency_guide/processes.xml
index db05e6a3a8..033e7f109e 100644
--- a/system/doc/efficiency_guide/processes.xml
+++ b/system/doc/efficiency_guide/processes.xml
@@ -144,6 +144,136 @@ loop() ->
the message and distributes it to the correct process.</p>
<section>
+ <marker id="receiving-messages"></marker>
+ <title>Receiving messages</title>
+
+ <p>The cost of receiving messages depends on how complicated the
+ <c>receive</c> expression is. Simple expressions that match any
+ message are very cheap:</p>
+
+ <p><em>DO</em></p>
+ <code type="erl"><![CDATA[
+ receive
+ Message -> handle_msg(Message)
+ end.]]></code>
+
+ <p>This is not always convenient however: we can receive a message that
+ we do not know how to handle at this point, so it's rather common to
+ only match the messages we expect:</p>
+
+ <code type="erl"><![CDATA[
+ receive
+ {Tag, Message} -> handle_msg(Message)
+ end.]]></code>
+
+ <p>While this is convenient it means that the entire message queue must
+ be searched until it finds a matching message. This is very expensive
+ for processes with long message queues, so we have added an
+ optimization for the common case of sending a request and waiting for a
+ response shortly after:</p>
+
+ <p><em>DO</em></p>
+ <code type="erl"><![CDATA[
+ MRef = monitor(process, Process),
+ Process ! {self(), MRef, Request},
+ receive
+ {MRef, Reply} ->
+ erlang:demonitor(MRef, [flush]),
+ handle_reply(Reply);
+ {'DOWN', MRef, _, _, Reason} ->
+ handle_error(Reason)
+ end.]]></code>
+
+ <p>Since the compiler knows that the reference created by <c>monitor/2</c>
+ cannot exist before the call (since it is a globally unique identifier),
+ and that the <c>receive</c> only matches messages that contain said
+ reference, it will tell the emulator to search only the messages that
+ arrived after the call to <c>monitor/2</c>.</p>
+
+ <p>The above is a simple example where one is but guaranteed that the
+ optimization will take, but what about more complicated code?</p>
+
+ <section>
+ <marker id="recv_opt_info"></marker>
+ <title>Option recv_opt_info</title>
+
+ <p>Use the <c>recv_opt_info</c> option to have the compiler print
+ information about receive optimizations. It can be given either to
+ the compiler or <c>erlc</c>:</p>
+
+ <code type="erl"><![CDATA[
+erlc +recv_opt_info Mod.erl]]></code>
+
+ <p>or passed through an environment variable:</p>
+
+ <code type="erl"><![CDATA[
+export ERL_COMPILER_OPTIONS=recv_opt_info]]></code>
+
+ <p>Notice that <c>recv_opt_info</c> is not meant to be a permanent
+ option added to your <c>Makefile</c>s, because all messages that it
+ generates cannot be eliminated. Therefore, passing the option through
+ the environment is in most cases the most practical approach.</p>
+
+ <p>The warnings look as follows:</p>
+
+ <code type="erl"><![CDATA[
+efficiency_guide.erl:194: Warning: INFO: receive matches any message, this is always fast
+efficiency_guide.erl:200: Warning: NOT OPTIMIZED: all clauses do not match a suitable reference
+efficiency_guide.erl:206: Warning: OPTIMIZED: reference used to mark a message queue position
+efficiency_guide.erl:208: Warning: OPTIMIZED: all clauses match reference created by monitor/2 at efficiency_guide.erl:206
+efficiency_guide.erl:219: Warning: INFO: passing reference created by make_ref/0 at efficiency_guide.erl:218
+efficiency_guide.erl:222: Warning: OPTIMIZED: all clauses match reference in function parameter 1]]></code>
+
+ <p>To make it clearer exactly what code the warnings refer to, the
+ warnings in the following examples are inserted as comments
+ after the clause they refer to, for example:</p>
+
+ <code type="erl"><![CDATA[
+%% DO
+simple_receive() ->
+ %% efficiency_guide.erl:194: Warning: INFO: not a selective receive, this is always fast
+ receive
+ Message -> handle_msg(Message)
+ end.
+
+%% DO NOT, unless Tag is known to be a suitable reference: see
+%% cross_function_receive/0 further down.
+selective_receive(Tag, Message) ->
+ %% efficiency_guide.erl:200: Warning: NOT OPTIMIZED: all clauses do not match a suitable reference
+ receive
+ {Tag, Message} -> handle_msg(Message)
+ end.
+
+%% DO
+optimized_receive(Process, Request) ->
+ %% efficiency_guide.erl:206: Warning: OPTIMIZED: reference used to mark a message queue position
+ MRef = monitor(process, Process),
+ Process ! {self(), MRef, Request},
+ %% efficiency_guide.erl:208: Warning: OPTIMIZED: matches reference created by monitor/2 at efficiency_guide.erl:206
+ receive
+ {MRef, Reply} ->
+ erlang:demonitor(MRef, [flush]),
+ handle_reply(Reply);
+ {'DOWN', MRef, _, _, Reason} ->
+ handle_error(Reason)
+ end.
+
+%% DO
+cross_function_receive() ->
+ %% efficiency_guide.erl:218: Warning: OPTIMIZED: reference used to mark a message queue position
+ Ref = make_ref(),
+ %% efficiency_guide.erl:219: Warning: INFO: passing reference created by make_ref/0 at efficiency_guide.erl:218
+ cross_function_receive(Ref).
+
+cross_function_receive(Ref) ->
+ %% efficiency_guide.erl:222: Warning: OPTIMIZED: all clauses match reference in function parameter 1
+ receive
+ {Ref, Message} -> handle_msg(Message)
+ end.]]></code>
+ </section>
+ </section>
+
+ <section>
<title>Constant Pool</title>
<p>Constant Erlang terms (also called <em>literals</em>) are