aboutsummaryrefslogtreecommitdiffstats
path: root/lib/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'lib/compiler')
-rw-r--r--lib/compiler/doc/src/notes.xml77
-rw-r--r--lib/compiler/src/beam_except.erl7
-rw-r--r--lib/compiler/src/beam_ssa.erl28
-rw-r--r--lib/compiler/src/beam_ssa_bsm.erl6
-rw-r--r--lib/compiler/src/beam_ssa_codegen.erl15
-rw-r--r--lib/compiler/src/beam_ssa_dead.erl30
-rw-r--r--lib/compiler/src/beam_ssa_opt.erl66
-rw-r--r--lib/compiler/src/beam_ssa_pre_codegen.erl11
-rw-r--r--lib/compiler/src/beam_ssa_type.erl89
-rw-r--r--lib/compiler/src/beam_validator.erl12
-rw-r--r--lib/compiler/src/cerl.erl2
-rw-r--r--lib/compiler/src/v3_core.erl3
-rw-r--r--lib/compiler/test/beam_except_SUITE.erl20
-rw-r--r--lib/compiler/test/beam_ssa_SUITE.erl60
-rw-r--r--lib/compiler/test/beam_type_SUITE.erl35
-rw-r--r--lib/compiler/test/bs_match_SUITE.erl19
-rw-r--r--lib/compiler/test/core_SUITE.erl7
-rw-r--r--lib/compiler/test/core_SUITE_data/get_map_element.core18
-rw-r--r--lib/compiler/test/fun_SUITE.erl20
-rw-r--r--lib/compiler/test/guard_SUITE.erl25
-rw-r--r--lib/compiler/test/match_SUITE.erl43
-rw-r--r--lib/compiler/test/receive_SUITE.erl26
-rw-r--r--lib/compiler/vsn.mk2
23 files changed, 496 insertions, 125 deletions
diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml
index 275c6268fa..f0d869381b 100644
--- a/lib/compiler/doc/src/notes.xml
+++ b/lib/compiler/doc/src/notes.xml
@@ -32,6 +32,83 @@
<p>This document describes the changes made to the Compiler
application.</p>
+<section><title>Compiler 7.4.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed an incorrect type determination for constructed
+ binaries, which could cause <c>is_binary</c> checks to
+ succeed when they shouldn't have.</p>
+ <p>
+ Own Id: OTP-15872</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Compiler 7.4.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>The type optimization pass of the compiler could hang
+ or loop for a long time when analyzing a
+ <c>setelement/3</c> call with a varible position.</p>
+ <p>
+ Own Id: OTP-15828 Aux Id: ERL-948 </p>
+ </item>
+ <item>
+ <p>Certain complex receive statements would result in an
+ internal compiler failure.</p>
+ <p>
+ Own Id: OTP-15832 Aux Id: ERL-950 </p>
+ </item>
+ <item>
+ <p>Fixed an unsafe type optimization.</p>
+ <p>
+ Own Id: OTP-15838</p>
+ </item>
+ <item>
+ <p>Fixed a crash when optimizing compiler-generated
+ exceptions (like badmatch) whose offending term was a
+ constructed binary.</p>
+ <p>
+ Own Id: OTP-15839 Aux Id: ERL-954 </p>
+ </item>
+ <item>
+ <p>Fixed a bad optimization related to the <c>++/2</c>
+ operator, where the compiler assumed that it always
+ produced a list (<c>[] ++ RHS</c> returns <c>RHS</c>
+ verbatim, even if it's not a list).</p>
+ <p>
+ Own Id: OTP-15841</p>
+ </item>
+ <item>
+ <p>An <c>is_binary/1</c> test followed by
+ <c>is_bitstring/1</c> (or vice versa) could fail because
+ of an usafe optimization.</p>
+ <p>
+ Own Id: OTP-15845</p>
+ </item>
+ <item>
+ <p>A Core Erlang module where the last clause in a
+ <c>case</c> matched a map would fail to load.</p>
+ <p>
+ Own Id: OTP-15846 Aux Id: ERL-955 </p>
+ </item>
+ <item>
+ <p>Fixed a bug that could cause the compiler to crash
+ when compiling complex nested case expressions.</p>
+ <p>
+ Own Id: OTP-15848 Aux Id: ERL-956 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Compiler 7.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/compiler/src/beam_except.erl b/lib/compiler/src/beam_except.erl
index 28c89782c9..2305502800 100644
--- a/lib/compiler/src/beam_except.erl
+++ b/lib/compiler/src/beam_except.erl
@@ -140,8 +140,11 @@ fix_block_1([{set,[],[],{alloc,Live,{F1,F2,Needed0,F3}}}|Is], Words) ->
[{set,[],[],{alloc,Live,{F1,F2,Needed,F3}}}|Is]
end;
fix_block_1([I|Is], Words) ->
- [I|fix_block_1(Is, Words)].
-
+ [I|fix_block_1(Is, Words)];
+fix_block_1([], _Words) ->
+ %% Rare. The heap allocation was probably done by a binary
+ %% construction instruction.
+ [].
dig_out_fc(Arity, Is0) ->
Regs0 = maps:from_list([{{x,X},{arg,X}} || X <- seq(0, Arity-1)]),
diff --git a/lib/compiler/src/beam_ssa.erl b/lib/compiler/src/beam_ssa.erl
index a9977b0b1d..6492d1e1bf 100644
--- a/lib/compiler/src/beam_ssa.erl
+++ b/lib/compiler/src/beam_ssa.erl
@@ -96,7 +96,8 @@
%% To avoid the collapsing, change the value of SET_LIMIT to 50 in the
%% file erl_types.erl in the hipe application.
--type prim_op() :: 'bs_add' | 'bs_extract' | 'bs_init' | 'bs_init_writable' |
+-type prim_op() :: 'bs_add' | 'bs_extract' | 'bs_get_tail' |
+ 'bs_init' | 'bs_init_writable' |
'bs_match' | 'bs_put' | 'bs_start_match' | 'bs_test_tail' |
'bs_utf16_size' | 'bs_utf8_size' | 'build_stacktrace' |
'call' | 'catch_end' |
@@ -117,11 +118,12 @@
'+' | '-' | '*' | '/'.
%% Primops only used internally during code generation.
--type cg_prim_op() :: 'bs_get' | 'bs_match_string' | 'bs_restore' | 'bs_skip' |
+-type cg_prim_op() :: 'bs_get' | 'bs_get_position' | 'bs_match_string' |
+ 'bs_restore' | 'bs_save' | 'bs_set_position' | 'bs_skip' |
'copy' | 'put_tuple_arity' | 'put_tuple_element' |
- 'set_tuple_element'.
+ 'put_tuple_elements' | 'set_tuple_element'.
--import(lists, [foldl/3,keyfind/3,mapfoldl/3,member/2,reverse/1]).
+-import(lists, [foldl/3,keyfind/3,mapfoldl/3,member/2,reverse/1,umerge/1]).
-spec add_anno(Key, Value, Construct) -> Construct when
Key :: atom(),
@@ -649,12 +651,18 @@ is_commutative('=/=') -> true;
is_commutative('/=') -> true;
is_commutative(_) -> false.
-def_used_1([#b_blk{is=Is,last=Last}|Bs], Preds, Def0, Used0) ->
- {Def,Used1} = def_used_is(Is, Preds, Def0, Used0),
- Used = ordsets:union(used(Last), Used1),
- def_used_1(Bs, Preds, Def, Used);
-def_used_1([], _Preds, Def, Used) ->
- {ordsets:from_list(Def),Used}.
+def_used_1([#b_blk{is=Is,last=Last}|Bs], Preds, Def0, UsedAcc) ->
+ {Def,Used} = def_used_is(Is, Preds, Def0, used(Last)),
+ case Used of
+ [] ->
+ def_used_1(Bs, Preds, Def, UsedAcc);
+ [_|_] ->
+ def_used_1(Bs, Preds, Def, [Used|UsedAcc])
+ end;
+def_used_1([], _Preds, Def0, UsedAcc) ->
+ Def = ordsets:from_list(Def0),
+ Used = umerge(UsedAcc),
+ {Def,Used}.
def_used_is([#b_set{op=phi,dst=Dst,args=Args}|Is],
Preds, Def0, Used0) ->
diff --git a/lib/compiler/src/beam_ssa_bsm.erl b/lib/compiler/src/beam_ssa_bsm.erl
index 382e6f635e..1ac9e6a3bb 100644
--- a/lib/compiler/src/beam_ssa_bsm.erl
+++ b/lib/compiler/src/beam_ssa_bsm.erl
@@ -683,8 +683,12 @@ aca_copy_successors(Lbl0, Blocks0, Counter0) ->
Lbl = maps:get(Lbl0, BRs),
{Lbl, Blocks, Counter}.
+aca_cs_build_brs([?BADARG_BLOCK=Lbl | Path], Counter, Acc) ->
+ %% ?BADARG_BLOCK is a marker and not an actual block, so renaming it will
+ %% break exception handling.
+ aca_cs_build_brs(Path, Counter, Acc#{ Lbl => Lbl });
aca_cs_build_brs([Lbl | Path], Counter0, Acc) ->
- aca_cs_build_brs(Path, Counter0 + 1, maps:put(Lbl, Counter0, Acc));
+ aca_cs_build_brs(Path, Counter0 + 1, Acc#{ Lbl => Counter0 });
aca_cs_build_brs([], Counter, Acc) ->
{Acc, Counter}.
diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl
index c2d5035b19..07f4c8b461 100644
--- a/lib/compiler/src/beam_ssa_codegen.erl
+++ b/lib/compiler/src/beam_ssa_codegen.erl
@@ -1016,6 +1016,14 @@ bif_fail({catch_tag,_}) -> {f,0}.
next_block([]) -> none;
next_block([{Next,_}|_]) -> Next.
+%% Certain instructions (such as get_map_element or is_nonempty_list)
+%% are only used in guards and **must** have a non-zero label;
+%% otherwise, the loader will refuse to load the
+%% module. ensure_label/2 replaces a zero label with the "ultimate
+%% failure" label to make the module loadable. The instruction that
+%% have had the zero label replaced is **not** supposed to ever fail
+%% and actually jump to the label.
+
ensure_label(Fail0, #cg{ultimate_fail=Lbl}) ->
case bif_fail(Fail0) of
{f,0} -> {f,Lbl};
@@ -1160,6 +1168,11 @@ cg_block([#cg_set{op=call}=I,
#cg_set{op=succeeded,dst=Bool}], {Bool,_Fail}, St) ->
%% A call in try/catch block.
cg_block([I], none, St);
+cg_block([#cg_set{op=get_map_element,dst=Dst0,args=Args0},
+ #cg_set{op=succeeded,dst=Bool}], {Bool,Fail0}, St) ->
+ [Dst,Map,Key] = beam_args([Dst0|Args0], St),
+ Fail = ensure_label(Fail0, St),
+ {[{get_map_elements,Fail,Map,{list,[Key,Dst]}}],St};
cg_block([#cg_set{op=Op,dst=Dst0,args=Args0}=I,
#cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) ->
[Dst|Args] = beam_args([Dst0|Args0], St),
@@ -1606,8 +1619,6 @@ cg_test({float,Op0}, Fail, Args, Dst, #cg_set{anno=Anno}) ->
'/' -> fdiv
end,
[line(Anno),{bif,Op,Fail,Args,Dst}];
-cg_test(get_map_element, Fail, [Map,Key], Dst, _I) ->
- [{get_map_elements,Fail,Map,{list,[Key,Dst]}}];
cg_test(peek_message, Fail, [], Dst, _I) ->
[{loop_rec,Fail,{x,0}}|copy({x,0}, Dst)];
cg_test(put_map, Fail, [{atom,exact},SrcMap|Ss], Dst, Set) ->
diff --git a/lib/compiler/src/beam_ssa_dead.erl b/lib/compiler/src/beam_ssa_dead.erl
index bb43a550ae..64b9b3e222 100644
--- a/lib/compiler/src/beam_ssa_dead.erl
+++ b/lib/compiler/src/beam_ssa_dead.erl
@@ -436,8 +436,22 @@ get_phi_arg([{Val,From}|_], From) -> Val;
get_phi_arg([_|As], From) -> get_phi_arg(As, From).
eval_terminator(#b_br{bool=#b_var{}=Bool}=Br, Bs, _St) ->
- Val = get_value(Bool, Bs),
- beam_ssa:normalize(Br#b_br{bool=Val});
+ case get_value(Bool, Bs) of
+ #b_literal{val=Val}=Lit ->
+ case is_boolean(Val) of
+ true ->
+ beam_ssa:normalize(Br#b_br{bool=Lit});
+ false ->
+ %% Non-boolean literal. This means that this `br`
+ %% terminator will never actually be reached with
+ %% these bindings. (There must be a previous two-way
+ %% branch that branches the other way when Bool
+ %% is bound to a non-boolean literal.)
+ none
+ end;
+ #b_var{}=Var ->
+ beam_ssa:normalize(Br#b_br{bool=Var})
+ end;
eval_terminator(#b_br{bool=#b_literal{}}=Br, _Bs, _St) ->
beam_ssa:normalize(Br);
eval_terminator(#b_switch{arg=Arg,fail=Fail,list=List}=Sw, Bs, St) ->
@@ -680,11 +694,8 @@ will_succeed_test(is_list, is_nonempty_list) ->
maybe;
will_succeed_test(is_nonempty_list, is_list) ->
yes;
-will_succeed_test(T1, T2) ->
- case is_numeric_test(T1) andalso is_numeric_test(T2) of
- true -> maybe;
- false -> no
- end.
+will_succeed_test(_T1, _T2) ->
+ maybe.
will_succeed_1('=:=', A, '<', B) ->
if
@@ -769,11 +780,6 @@ will_succeed_vars('==', Val1, '/=', Val2) when Val1 == Val2 -> no;
will_succeed_vars(_, _, _, _) -> maybe.
-is_numeric_test(is_float) -> true;
-is_numeric_test(is_integer) -> true;
-is_numeric_test(is_number) -> true;
-is_numeric_test(_) -> false.
-
eval_type_test(Test, Arg) ->
case eval_type_test_1(Test, Arg) of
true -> yes;
diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl
index 90c0d3cf16..229edc6a1d 100644
--- a/lib/compiler/src/beam_ssa_opt.erl
+++ b/lib/compiler/src/beam_ssa_opt.erl
@@ -904,8 +904,7 @@ cse_suitable(#b_set{}) -> false.
}).
ssa_opt_float({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) ->
- NonGuards0 = float_non_guards(Linear0),
- NonGuards = gb_sets:from_list(NonGuards0),
+ NonGuards = non_guards(Linear0),
Blocks = maps:from_list(Linear0),
Fs = #fs{non_guards=NonGuards,bs=Blocks},
{Linear,Count} = float_opt(Linear0, Count0, Fs),
@@ -916,15 +915,6 @@ float_blk_is_in_guard(#b_blk{last=#b_br{fail=F}}, #fs{non_guards=NonGuards}) ->
float_blk_is_in_guard(#b_blk{}, #fs{}) ->
false.
-float_non_guards([{L,#b_blk{is=Is}}|Bs]) ->
- case Is of
- [#b_set{op=landingpad}|_] ->
- [L|float_non_guards(Bs)];
- _ ->
- float_non_guards(Bs)
- end;
-float_non_guards([]) -> [?BADARG_BLOCK].
-
float_opt([{L,Blk}|Bs0], Count0, Fs) ->
case float_blk_is_in_guard(Blk, Fs) of
true ->
@@ -1774,35 +1764,44 @@ opt_bs_put_split_int_1(Int, L, R) ->
%%%
ssa_opt_tuple_size({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) ->
- {Linear,Count} = opt_tup_size(Linear0, Count0, []),
+ %% This optimization is only safe in guards, as prefixing tuple_size with
+ %% an is_tuple check prevents it from throwing an exception.
+ NonGuards = non_guards(Linear0),
+ {Linear,Count} = opt_tup_size(Linear0, NonGuards, Count0, []),
{St#st{ssa=Linear,cnt=Count}, FuncDb}.
-opt_tup_size([{L,#b_blk{is=Is,last=Last}=Blk}|Bs], Count0, Acc0) ->
+opt_tup_size([{L,#b_blk{is=Is,last=Last}=Blk}|Bs], NonGuards, Count0, Acc0) ->
case {Is,Last} of
{[#b_set{op={bif,'=:='},dst=Bool,args=[#b_var{}=Tup,#b_literal{val=Arity}]}],
#b_br{bool=Bool}} when is_integer(Arity), Arity >= 0 ->
- {Acc,Count} = opt_tup_size_1(Tup, L, Count0, Acc0),
- opt_tup_size(Bs, Count, [{L,Blk}|Acc]);
+ {Acc,Count} = opt_tup_size_1(Tup, L, NonGuards, Count0, Acc0),
+ opt_tup_size(Bs, NonGuards, Count, [{L,Blk}|Acc]);
{_,_} ->
- opt_tup_size(Bs, Count0, [{L,Blk}|Acc0])
+ opt_tup_size(Bs, NonGuards, Count0, [{L,Blk}|Acc0])
end;
-opt_tup_size([], Count, Acc) ->
+opt_tup_size([], _NonGuards, Count, Acc) ->
{reverse(Acc),Count}.
-opt_tup_size_1(Size, EqL, Count0, [{L,Blk0}|Acc]) ->
- case Blk0 of
- #b_blk{is=Is0,last=#b_br{bool=Bool,succ=EqL,fail=Fail}} ->
- case opt_tup_size_is(Is0, Bool, Size, []) of
- none ->
+opt_tup_size_1(Size, EqL, NonGuards, Count0, [{L,Blk0}|Acc]) ->
+ #b_blk{is=Is0,last=Last} = Blk0,
+ case Last of
+ #b_br{bool=Bool,succ=EqL,fail=Fail} ->
+ case gb_sets:is_member(Fail, NonGuards) of
+ true ->
{[{L,Blk0}|Acc],Count0};
- {PreIs,TupleSizeIs,Tuple} ->
- opt_tup_size_2(PreIs, TupleSizeIs, L, EqL,
- Tuple, Fail, Count0, Acc)
+ false ->
+ case opt_tup_size_is(Is0, Bool, Size, []) of
+ none ->
+ {[{L,Blk0}|Acc],Count0};
+ {PreIs,TupleSizeIs,Tuple} ->
+ opt_tup_size_2(PreIs, TupleSizeIs, L, EqL,
+ Tuple, Fail, Count0, Acc)
+ end
end;
- #b_blk{} ->
+ _ ->
{[{L,Blk0}|Acc],Count0}
end;
-opt_tup_size_1(_, _, Count, Acc) ->
+opt_tup_size_1(_, _, _, Count, Acc) ->
{Acc,Count}.
opt_tup_size_2(PreIs, TupleSizeIs, PreL, EqL, Tuple, Fail, Count0, Acc) ->
@@ -2241,6 +2240,19 @@ gcd(A, B) ->
X -> gcd(B, X)
end.
+non_guards(Linear) ->
+ gb_sets:from_list(non_guards_1(Linear)).
+
+non_guards_1([{L,#b_blk{is=Is}}|Bs]) ->
+ case Is of
+ [#b_set{op=landingpad}|_] ->
+ [L | non_guards_1(Bs)];
+ _ ->
+ non_guards_1(Bs)
+ end;
+non_guards_1([]) ->
+ [?BADARG_BLOCK].
+
rel2fam(S0) ->
S1 = sofs:relation(S0),
S = sofs:rel2fam(S1),
diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl
index bf99e8fc26..9af72afca7 100644
--- a/lib/compiler/src/beam_ssa_pre_codegen.erl
+++ b/lib/compiler/src/beam_ssa_pre_codegen.erl
@@ -1415,12 +1415,15 @@ fix_receive([], _Defs, Blocks, Count) ->
find_loop_exit([L1,L2|_Ls], Blocks) ->
Path1 = beam_ssa:rpo([L1], Blocks),
Path2 = beam_ssa:rpo([L2], Blocks),
- find_loop_exit_1(reverse(Path1), reverse(Path2), none);
+ find_loop_exit_1(Path1, cerl_sets:from_list(Path2));
find_loop_exit(_, _) -> none.
-find_loop_exit_1([H|T1], [H|T2], _) ->
- find_loop_exit_1(T1, T2, H);
-find_loop_exit_1(_, _, Exit) -> Exit.
+find_loop_exit_1([H|T], OtherPath) ->
+ case cerl_sets:is_element(H, OtherPath) of
+ true -> H;
+ false -> find_loop_exit_1(T, OtherPath)
+ end;
+find_loop_exit_1([], _) -> none.
%% find_rm_blocks(StartLabel, Blocks) -> [Label].
%% Find all blocks that start with remove_message within the receive
diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl
index 06b42f1928..68920e7dd3 100644
--- a/lib/compiler/src/beam_ssa_type.erl
+++ b/lib/compiler/src/beam_ssa_type.erl
@@ -23,8 +23,8 @@
-include("beam_ssa_opt.hrl").
-import(lists, [all/2,any/2,droplast/1,foldl/3,last/1,member/2,
- keyfind/3,partition/2,reverse/1,reverse/2,
- seq/2,sort/1,split/2]).
+ keyfind/3,reverse/1,reverse/2,
+ sort/1,split/2]).
-define(UNICODE_INT, #t_integer{elements={0,16#10FFFF}}).
@@ -840,15 +840,8 @@ type({bif,Bif}, Args, Ts, _Ds) ->
Type ->
Type
end;
-type(bs_init, [#b_literal{val=Type}|Args], _Ts, _Ds) ->
- case {Type,Args} of
- {new,[_,#b_literal{val=Unit}]} ->
- {binary,Unit};
- {append,[_,_,#b_literal{val=Unit}]} ->
- {binary,Unit};
- {private_append,[_,_,#b_literal{val=Unit}]} ->
- {binary,Unit}
- end;
+type(bs_init, _Args, _Ts, _Ds) ->
+ {binary, 1};
type(bs_extract, [Ctx], Ts, _Ds) ->
#t_bs_match{type=Type} = get_type(Ctx, Ts),
Type;
@@ -874,11 +867,11 @@ type(call, [#b_remote{mod=#b_literal{val=Mod},
true ->
none
end;
- {#t_integer{elements={Min,Max}},
+ {#t_integer{elements={Min,_}}=IntType,
#t_tuple{elements=Es0,size=Size}=T} ->
- %% We know this will land between Min and Max, so kill the
- %% types for those indexes.
- Es = maps:without(seq(Min, Max), Es0),
+ %% Remove type information for all indices that
+ %% falls into the range of the integer.
+ Es = remove_element_info(IntType, Es0),
case T#t_tuple.exact of
false ->
T#t_tuple{elements=Es,size=max(Min, Size)};
@@ -896,11 +889,15 @@ type(call, [#b_remote{mod=#b_literal{val=Mod},
{_,_} ->
#t_tuple{}
end;
- {erlang,'++',[List1,List2]} ->
- case get_type(List1, Ts) =:= cons orelse
- get_type(List2, Ts) =:= cons of
- true -> cons;
- false -> list
+ {erlang,'++',[LHS,RHS]} ->
+ LType = get_type(LHS, Ts),
+ RType = get_type(RHS, Ts),
+ case LType =:= cons orelse RType =:= cons of
+ true ->
+ cons;
+ false ->
+ %% `[] ++ RHS` yields RHS, even if RHS is not a list.
+ join(list, RType)
end;
{erlang,'--',[_,_]} ->
list;
@@ -1388,24 +1385,11 @@ get_type(#b_literal{val=Val}, _Ts) ->
%% type for L. For example, if L was known to be 'list', subtracting
%% 'cons' would give 'nil' as the only possible type. The result of the
%% subtraction for L will be added to FailTypes.
-%%
-%% Here is another example, asking about the variable Bool:
-%%
-%% Head = bif:hd L
-%% Bool = succeeded Head
-%%
-%% 'succeeded Head' will evaluate to 'true' if the instrution that
-%% defined Head succeeded. In this case, it is the 'bif:hd L'
-%% instruction, which will succeed if L is 'cons'. Thus, the meet of
-%% the previous type for L and 'cons' will be added to SuccTypes.
-%%
-%% If 'succeeded Head' evaluates to 'false', it means that 'bif:hd L'
-%% failed and that L is not 'cons'. 'cons' can be subtracted from the
-%% previously known type for L and the result put in FailTypes.
infer_types_br(#b_var{}=V, Ts, #d{ds=Ds}) ->
#{V:=#b_set{op=Op,args=Args}} = Ds,
- Types0 = infer_type(Op, Args, Ds),
+ PosTypes0 = infer_type(Op, Args, Ds),
+ NegTypes0 = infer_type_negative(Op, Args, Ds),
%% We must be careful with types inferred from '=:='.
%%
@@ -1416,13 +1400,17 @@ infer_types_br(#b_var{}=V, Ts, #d{ds=Ds}) ->
%%
%% However, it is safe to subtract a type inferred from '=:=' if
%% it is single-valued, e.g. if it is [] or the atom 'true'.
- EqTypes0 = infer_eq_type(Op, Args, Ts, Ds),
- {Types1,EqTypes} = partition(fun({_,T}) ->
- is_singleton_type(T)
- end, EqTypes0),
- Types = Types1 ++ Types0,
- {meet_types(EqTypes++Types, Ts),subtract_types(Types, Ts)}.
+ EqTypes = infer_eq_type(Op, Args, Ts, Ds),
+ NegTypes1 = [P || {_,T}=P <- EqTypes, is_singleton_type(T)],
+
+ PosTypes = EqTypes ++ PosTypes0,
+ SuccTs = meet_types(PosTypes, Ts),
+
+ NegTypes = NegTypes0 ++ NegTypes1,
+ FailTs = subtract_types(NegTypes, Ts),
+
+ {SuccTs,FailTs}.
infer_types_switch(V, Lit, Ts, #d{ds=Ds}) ->
Types = infer_eq_type({bif,'=:='}, [V, Lit], Ts, Ds),
@@ -1457,6 +1445,19 @@ infer_eq_lit(#b_set{op=get_tuple_element,
[{Tuple,#t_tuple{size=Index,elements=Es}}];
infer_eq_lit(_, _) -> [].
+infer_type_negative(Op, Args, Ds) ->
+ case is_negative_inference_safe(Op, Args) of
+ true ->
+ infer_type(Op, Args, Ds);
+ false ->
+ []
+ end.
+
+%% Conservative list of instructions for which negative
+%% inference is safe.
+is_negative_inference_safe(is_nonempty_list, _Args) -> true;
+is_negative_inference_safe(_, _) -> false.
+
infer_type({bif,element}, [#b_literal{val=Pos},#b_var{}=Tuple], _Ds) ->
if
is_integer(Pos), 1 =< Pos ->
@@ -1649,6 +1650,12 @@ get_literal_from_type(nil) ->
#b_literal{val=[]};
get_literal_from_type(_) -> none.
+remove_element_info(#t_integer{elements={Min,Max}}, Es) ->
+ foldl(fun(El, Acc) when Min =< El, El =< Max ->
+ maps:remove(El, Acc);
+ (_El, Acc) -> Acc
+ end, Es, maps:keys(Es)).
+
t_atom() ->
#t_atom{elements=any}.
diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl
index 09a5a6c104..ebe9631e09 100644
--- a/lib/compiler/src/beam_validator.erl
+++ b/lib/compiler/src/beam_validator.erl
@@ -2844,10 +2844,14 @@ call_return_type_1(erlang, setelement, 3, Vst) ->
setelement(3, TupleType, #{})
end;
call_return_type_1(erlang, '++', 2, Vst) ->
- case get_term_type({x,0}, Vst) =:= cons orelse
- get_term_type({x,1}, Vst) =:= cons of
- true -> cons;
- false -> list
+ LType = get_term_type({x,0}, Vst),
+ RType = get_term_type({x,1}, Vst),
+ case LType =:= cons orelse RType =:= cons of
+ true ->
+ cons;
+ false ->
+ %% `[] ++ RHS` yields RHS, even if RHS is not a list
+ join(list, RType)
end;
call_return_type_1(erlang, '--', 2, _Vst) ->
list;
diff --git a/lib/compiler/src/cerl.erl b/lib/compiler/src/cerl.erl
index 62cd5b5120..bc28f58712 100644
--- a/lib/compiler/src/cerl.erl
+++ b/lib/compiler/src/cerl.erl
@@ -263,7 +263,7 @@
%% @see subtrees/1
%% @see meta/1
--type ctype() :: 'alias' | 'apply' | 'binary' | 'bitrst' | 'call' | 'case'
+-type ctype() :: 'alias' | 'apply' | 'binary' | 'bitstr' | 'call' | 'case'
| 'catch' | 'clause' | 'cons' | 'fun' | 'let' | 'letrec'
| 'literal' | 'map' | 'map_pair' | 'module' | 'primop'
| 'receive' | 'seq' | 'try' | 'tuple' | 'values' | 'var'.
diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl
index 3699c9d22e..007a0247f4 100644
--- a/lib/compiler/src/v3_core.erl
+++ b/lib/compiler/src/v3_core.erl
@@ -1811,7 +1811,8 @@ force_safe(Ce, St0) ->
is_safe(#c_cons{}) -> true;
is_safe(#c_tuple{}) -> true;
-is_safe(#c_var{}) -> true;
+is_safe(#c_var{name={_,_}}) -> false; %Fun. Not safe.
+is_safe(#c_var{name=_}) -> true; %Ordinary variable.
is_safe(#c_literal{}) -> true;
is_safe(_) -> false.
diff --git a/lib/compiler/test/beam_except_SUITE.erl b/lib/compiler/test/beam_except_SUITE.erl
index 8e3b373d29..67947dc292 100644
--- a/lib/compiler/test/beam_except_SUITE.erl
+++ b/lib/compiler/test/beam_except_SUITE.erl
@@ -21,7 +21,8 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
- multiple_allocs/1,bs_get_tail/1,coverage/1]).
+ multiple_allocs/1,bs_get_tail/1,coverage/1,
+ binary_construction_allocation/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -32,7 +33,8 @@ groups() ->
[{p,[parallel],
[multiple_allocs,
bs_get_tail,
- coverage]}].
+ coverage,
+ binary_construction_allocation]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -118,6 +120,20 @@ coverage(_) ->
fake_function_clause(A) -> error(function_clause, [A,42.0]).
+
+binary_construction_allocation(_Config) ->
+ ok = do_binary_construction_allocation("PUT"),
+ ok.
+
+do_binary_construction_allocation(Req) ->
+ %% Allocation for building the error term was done by the
+ %% bs_init2 instruction. beam_except crashed because it expected
+ %% an explicit allocation instruction.
+ ok = case Req of
+ "POST" -> {error, <<"BAD METHOD ", Req/binary>>, Req};
+ _ -> ok
+ end.
+
id(I) -> I.
-file("fake.erl", 1).
diff --git a/lib/compiler/test/beam_ssa_SUITE.erl b/lib/compiler/test/beam_ssa_SUITE.erl
index 15cf9bcbf3..a741ebbdf9 100644
--- a/lib/compiler/test/beam_ssa_SUITE.erl
+++ b/lib/compiler/test/beam_ssa_SUITE.erl
@@ -22,7 +22,8 @@
-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
init_per_group/2,end_per_group/2,
calls/1,tuple_matching/1,recv/1,maps/1,
- cover_ssa_dead/1,combine_sw/1,share_opt/1]).
+ cover_ssa_dead/1,combine_sw/1,share_opt/1,
+ beam_ssa_dead_crash/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -37,7 +38,8 @@ groups() ->
maps,
cover_ssa_dead,
combine_sw,
- share_opt
+ share_opt,
+ beam_ssa_dead_crash
]}].
init_per_suite(Config) ->
@@ -492,6 +494,60 @@ do_share_opt(A) ->
end,
receive after 1 -> ok end.
+beam_ssa_dead_crash(_Config) ->
+ not_A_B = do_beam_ssa_dead_crash(id(false), id(true)),
+ not_A_not_B = do_beam_ssa_dead_crash(false, false),
+ neither = do_beam_ssa_dead_crash(true, false),
+ neither = do_beam_ssa_dead_crash(true, true),
+ ok.
+
+do_beam_ssa_dead_crash(A, B) ->
+ %% beam_ssa_dead attempts to shortcut branches that branch other
+ %% branches. When a two-way branch is encountered, beam_ssa_dead
+ %% will simulate execution along both paths, in the hope that both
+ %% paths happens to end up in the same place.
+ %%
+ %% During the simulated execution of this function, the boolean
+ %% varible for a `br` instruction would be replaced with the
+ %% literal atom `nil`, which is not allowed, and would crash the
+ %% compiler. In practice, during the actual execution, control
+ %% would never be transferred to that `br` instruction when the
+ %% variable in question had the value `nil`.
+ %%
+ %% beam_ssa_dead has been updated to immediately abort the search
+ %% along the current path if there is an attempt to substitute a
+ %% non-boolean value into a `br` instruction.
+
+ case
+ case not A of
+ false ->
+ false;
+ true ->
+ B
+ end
+ of
+ V
+ when
+ V /= nil
+ andalso
+ V /= false ->
+ not_A_B;
+ _ ->
+ case
+ case not A of
+ false ->
+ false;
+ true ->
+ not B
+ end
+ of
+ true ->
+ not_A_not_B;
+ false ->
+ neither
+ end
+ end.
+
%% The identity function.
id(I) -> I.
diff --git a/lib/compiler/test/beam_type_SUITE.erl b/lib/compiler/test/beam_type_SUITE.erl
index 882e281a44..076a604aa4 100644
--- a/lib/compiler/test/beam_type_SUITE.erl
+++ b/lib/compiler/test/beam_type_SUITE.erl
@@ -24,7 +24,7 @@
integers/1,numbers/1,coverage/1,booleans/1,setelement/1,
cons/1,tuple/1,record_float/1,binary_float/1,float_compare/1,
arity_checks/1,elixir_binaries/1,find_best/1,
- test_size/1,cover_lists_functions/1]).
+ test_size/1,cover_lists_functions/1,list_append/1,bad_binary_unit/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -47,7 +47,9 @@ groups() ->
elixir_binaries,
find_best,
test_size,
- cover_lists_functions
+ cover_lists_functions,
+ list_append,
+ bad_binary_unit
]}].
init_per_suite(Config) ->
@@ -271,8 +273,22 @@ setelement(_Config) ->
T0 = id({a,42}),
{a,_} = T0,
{b,_} = setelement(1, T0, b),
+ {z,b} = do_setelement_1(<<(id(1)):32>>, {a,b}, z),
+ {new,two} = do_setelement_2(<<(id(1)):1>>, {one,two}, new),
ok.
+do_setelement_1(<<N:32>>, Tuple, NewValue) ->
+ _ = element(N, Tuple),
+ %% While updating the type for Tuple, beam_ssa_type would do:
+ %% maps:without(lists:seq(0, 4294967295), Elements)
+ setelement(N, Tuple, NewValue).
+
+do_setelement_2(<<N:1>>, Tuple, NewValue) ->
+ %% Cover the second clause in remove_element_info/2. The
+ %% type for the second element will be kept.
+ two = element(2, Tuple),
+ setelement(N, Tuple, NewValue).
+
cons(_Config) ->
[did] = cons(assigned, did),
@@ -487,5 +503,20 @@ cover_lists_functions(Config) ->
true = is_list(Zipped),
ok.
+list_append(_Config) ->
+ %% '++'/2 has a quirk where it returns the right-hand argument as-is when
+ %% the left-hand is [].
+ hello = id([]) ++ id(hello),
+ ok.
+
+%% OTP-15872: The compiler would treat the "Unit" of bs_init instructions as
+%% the unit of the result instead of the required unit of the input, causing
+%% is_binary checks to be wrongly optimized away.
+bad_binary_unit(_Config) ->
+ Bin = id(<<1,2,3>>),
+ Bitstring = <<Bin/binary,1:1>>,
+ false = is_binary(Bitstring),
+ ok.
+
id(I) ->
I.
diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl
index d97f49c56e..145a50f4ad 100644
--- a/lib/compiler/test/bs_match_SUITE.erl
+++ b/lib/compiler/test/bs_match_SUITE.erl
@@ -44,7 +44,8 @@
beam_bsm/1,guard/1,is_ascii/1,non_opt_eq/1,
expression_before_match/1,erl_689/1,restore_on_call/1,
restore_after_catch/1,matches_on_parameter/1,big_positions/1,
- matching_meets_apply/1,bs_start_match2_defs/1]).
+ matching_meets_apply/1,bs_start_match2_defs/1,
+ exceptions_after_match_failure/1]).
-export([coverage_id/1,coverage_external_ignore/2]).
@@ -80,7 +81,8 @@ groups() ->
beam_bsm,guard,is_ascii,non_opt_eq,
expression_before_match,erl_689,restore_on_call,
matches_on_parameter,big_positions,
- matching_meets_apply,bs_start_match2_defs]}].
+ matching_meets_apply,bs_start_match2_defs,
+ exceptions_after_match_failure]}].
init_per_suite(Config) ->
@@ -2005,4 +2007,17 @@ do_matching_meets_apply(_Bin, {Handler, State}) ->
%% Another case of the above.
Handler:abs(State).
+%% Exception handling was broken on the failure path of bs_start_match as
+%% beam_ssa_bsm accidentally cloned and renamed the ?BADARG_BLOCK.
+exceptions_after_match_failure(_Config) ->
+ {'EXIT', {badarith, _}} = (catch do_exceptions_after_match_failure(atom)),
+ ok = do_exceptions_after_match_failure(<<0, 1, "gurka">>),
+ ok = do_exceptions_after_match_failure(2.0).
+
+do_exceptions_after_match_failure(<<_A, _B, "gurka">>) ->
+ ok;
+do_exceptions_after_match_failure(Other) ->
+ Other / 2.0,
+ ok.
+
id(I) -> I.
diff --git a/lib/compiler/test/core_SUITE.erl b/lib/compiler/test/core_SUITE.erl
index e5611e99d1..72016c6d76 100644
--- a/lib/compiler/test/core_SUITE.erl
+++ b/lib/compiler/test/core_SUITE.erl
@@ -29,7 +29,8 @@
bs_shadowed_size_var/1,
cover_v3_kernel_1/1,cover_v3_kernel_2/1,cover_v3_kernel_3/1,
cover_v3_kernel_4/1,cover_v3_kernel_5/1,
- non_variable_apply/1,name_capture/1,fun_letrec_effect/1]).
+ non_variable_apply/1,name_capture/1,fun_letrec_effect/1,
+ get_map_element/1]).
-include_lib("common_test/include/ct.hrl").
@@ -57,7 +58,8 @@ groups() ->
bs_shadowed_size_var,
cover_v3_kernel_1,cover_v3_kernel_2,cover_v3_kernel_3,
cover_v3_kernel_4,cover_v3_kernel_5,
- non_variable_apply,name_capture,fun_letrec_effect
+ non_variable_apply,name_capture,fun_letrec_effect,
+ get_map_element
]}].
@@ -95,6 +97,7 @@ end_per_group(_GroupName, Config) ->
?comp(non_variable_apply).
?comp(name_capture).
?comp(fun_letrec_effect).
+?comp(get_map_element).
try_it(Mod, Conf) ->
Src = filename:join(proplists:get_value(data_dir, Conf),
diff --git a/lib/compiler/test/core_SUITE_data/get_map_element.core b/lib/compiler/test/core_SUITE_data/get_map_element.core
new file mode 100644
index 0000000000..092b5e71eb
--- /dev/null
+++ b/lib/compiler/test/core_SUITE_data/get_map_element.core
@@ -0,0 +1,18 @@
+module 'get_map_element' ['get_map_element'/0]
+attributes []
+
+'get_map_element'/0 =
+ fun () ->
+ apply 'match_map'/1(~{'foo'=>'bar'}~)
+
+'match_map'/1 =
+ fun (_0) ->
+ case _0 of
+ <~{'foo':='bar'}~> when 'true' ->
+ 'ok'
+ %% It will be undefined behaviour at runtime if no
+ %% clause of the case can be selected. That can't
+ %% happen for this module, because match_map/1 is
+ %% always called with a matching map argument.
+ end
+end
diff --git a/lib/compiler/test/fun_SUITE.erl b/lib/compiler/test/fun_SUITE.erl
index 1df0a05275..7fc6195e31 100644
--- a/lib/compiler/test/fun_SUITE.erl
+++ b/lib/compiler/test/fun_SUITE.erl
@@ -22,7 +22,8 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
test1/1,overwritten_fun/1,otp_7202/1,bif_fun/1,
- external/1,eep37/1,eep37_dup/1,badarity/1,badfun/1]).
+ external/1,eep37/1,eep37_dup/1,badarity/1,badfun/1,
+ duplicated_fun/1]).
%% Internal exports.
-export([call_me/1,dup1/0,dup2/0]).
@@ -37,7 +38,7 @@ all() ->
groups() ->
[{p,[parallel],
[test1,overwritten_fun,otp_7202,bif_fun,external,eep37,
- eep37_dup,badarity,badfun]}].
+ eep37_dup,badarity,badfun,duplicated_fun]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -261,5 +262,20 @@ badfun(_Config) ->
expect_badfun(Term, Exit) ->
{'EXIT',{{badfun,Term},_}} = Exit.
+duplicated_fun(_Config) ->
+ try
+ %% The following code used to crash the compiler before
+ %% v3_core:is_safe/1 was corrected to consider fun variables
+ %% unsafe.
+ id([print_result_paths_fun = fun duplicated_fun_helper/1]),
+ ct:error(should_fail)
+ catch
+ error:{badmatch,F} when is_function(F, 1) ->
+ ok
+ end.
+
+duplicated_fun_helper(_) ->
+ ok.
+
id(I) ->
I.
diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl
index ed0a56f064..cea7a374cd 100644
--- a/lib/compiler/test/guard_SUITE.erl
+++ b/lib/compiler/test/guard_SUITE.erl
@@ -35,7 +35,8 @@
basic_andalso_orelse/1,traverse_dcd/1,
check_qlc_hrl/1,andalso_semi/1,t_tuple_size/1,binary_part/1,
bad_constants/1,bad_guards/1,
- guard_in_catch/1,beam_bool_SUITE/1]).
+ guard_in_catch/1,beam_bool_SUITE/1,
+ repeated_type_tests/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -53,7 +54,8 @@ groups() ->
rel_ops,rel_op_combinations,
literal_type_tests,basic_andalso_orelse,traverse_dcd,
check_qlc_hrl,andalso_semi,t_tuple_size,binary_part,
- bad_constants,bad_guards,guard_in_catch,beam_bool_SUITE]}].
+ bad_constants,bad_guards,guard_in_catch,beam_bool_SUITE,
+ repeated_type_tests]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -2261,6 +2263,25 @@ maps() ->
evidence(#{0 := Charge}) when 0; #{[] => Charge} == #{[] => 42} ->
ok.
+repeated_type_tests(_Config) ->
+ binary = repeated_type_test(<<42>>),
+ bitstring = repeated_type_test(<<1:1>>),
+ other = repeated_type_test(atom),
+ ok.
+
+repeated_type_test(T) ->
+ %% Test for a bug in beam_ssa_dead.
+ if is_bitstring(T) ->
+ if is_binary(T) -> %This test would be optimized away.
+ binary;
+ true ->
+ bitstring
+ end;
+ true ->
+ other
+ end.
+
+
%% Call this function to turn off constant propagation.
id(I) -> I.
diff --git a/lib/compiler/test/match_SUITE.erl b/lib/compiler/test/match_SUITE.erl
index 94bfbb0efe..bc74ec4984 100644
--- a/lib/compiler/test/match_SUITE.erl
+++ b/lib/compiler/test/match_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2019. 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.
@@ -25,7 +25,8 @@
match_in_call/1,untuplify/1,shortcut_boolean/1,letify_guard/1,
selectify/1,deselectify/1,underscore/1,match_map/1,map_vars_used/1,
coverage/1,grab_bag/1,literal_binary/1,
- unary_op/1,eq_types/1,match_after_return/1]).
+ unary_op/1,eq_types/1,match_after_return/1,match_right_tuple/1,
+ tuple_size_in_try/1]).
-include_lib("common_test/include/ct.hrl").
@@ -41,7 +42,8 @@ groups() ->
shortcut_boolean,letify_guard,selectify,deselectify,
underscore,match_map,map_vars_used,coverage,
grab_bag,literal_binary,unary_op,eq_types,
- match_after_return]}].
+ match_after_return,match_right_tuple,
+ tuple_size_in_try]}].
init_per_suite(Config) ->
@@ -902,4 +904,39 @@ match_after_return(Config) when is_list(Config) ->
mar_test_tuple(I) -> {gurka, I}.
+match_right_tuple(Config) when is_list(Config) ->
+ %% The loader wrongly coalesced certain get_tuple_element sequences, fusing
+ %% the code below into a single i_get_tuple_element2 operating on {x,0}
+ %% even though the first one overwrites it.
+ %%
+ %% {get_tuple_element,{x,0},0,{x,0}}.
+ %% {get_tuple_element,{x,0},1,{x,1}}.
+
+ Inner = {id(wrong_element), id(ok)},
+ Outer = {Inner, id(wrong_tuple)},
+ ok = match_right_tuple_1(Outer).
+
+match_right_tuple_1(T) ->
+ {A, _} = T,
+ {_, B} = A,
+ %% The call ensures that A is in {x,0} and B is in {x,1}
+ id(force_succ_regs(A, B)).
+
+force_succ_regs(_A, B) -> B.
+
+tuple_size_in_try(Config) when is_list(Config) ->
+ %% The tuple_size optimization was applied outside of guards, causing
+ %% either the emulator or compiler to crash.
+ ok = tsit(gurka),
+ ok = tsit(gaffel).
+
+tsit(A) ->
+ try
+ id(ignored),
+ 1 = tuple_size(A),
+ error
+ catch
+ _:_ -> ok
+ end.
+
id(I) -> I.
diff --git a/lib/compiler/test/receive_SUITE.erl b/lib/compiler/test/receive_SUITE.erl
index 0038eb1a4b..752491f0f8 100644
--- a/lib/compiler/test/receive_SUITE.erl
+++ b/lib/compiler/test/receive_SUITE.erl
@@ -26,7 +26,7 @@
init_per_testcase/2,end_per_testcase/2,
export/1,recv/1,coverage/1,otp_7980/1,ref_opt/1,
wait/1,recv_in_try/1,double_recv/1,receive_var_zero/1,
- match_built_terms/1]).
+ match_built_terms/1,elusive_common_exit/1]).
-include_lib("common_test/include/ct.hrl").
@@ -47,7 +47,7 @@ groups() ->
[{p,test_lib:parallel(),
[recv,coverage,otp_7980,ref_opt,export,wait,
recv_in_try,double_recv,receive_var_zero,
- match_built_terms]}].
+ match_built_terms,elusive_common_exit]}].
init_per_suite(Config) ->
@@ -427,4 +427,26 @@ match_built_terms(Config) when is_list(Config) ->
?MATCH_BUILT_TERM(Ref, <<A, B>>),
?MATCH_BUILT_TERM(Ref, #{ 1 => A, 2 => B}).
+elusive_common_exit(_Config) ->
+ self() ! {1, a},
+ self() ! {2, b},
+ {[z], [{2,b},{1,a}]} = elusive_loop([x,y,z], 2, []),
+ ok.
+
+elusive_loop(List, 0, Results) ->
+ {List, Results};
+elusive_loop(List, ToReceive, Results) ->
+ {Result, RemList} =
+ receive
+ {_Pos, _R} = Res when List =/= [] ->
+ [_H|T] = List,
+ {Res, T};
+ {_Pos, _R} = Res when List =:= [] ->
+ {Res, []}
+ end,
+ %% beam_ssa_pre_codegen:fix_receives() would fail to find
+ %% the common exit block for this receive. That would mean
+ %% that it would not insert all necessary copy instructions.
+ elusive_loop(RemList, ToReceive-1, [Result | Results]).
+
id(I) -> I.
diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk
index 494de072ff..508bbc902c 100644
--- a/lib/compiler/vsn.mk
+++ b/lib/compiler/vsn.mk
@@ -1 +1 @@
-COMPILER_VSN = 7.4
+COMPILER_VSN = 7.4.2