aboutsummaryrefslogtreecommitdiffstats
path: root/lib/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'lib/compiler')
-rw-r--r--lib/compiler/src/beam_ssa.erl28
-rw-r--r--lib/compiler/src/beam_ssa_dead.erl87
-rw-r--r--lib/compiler/src/beam_ssa_opt.erl18
-rw-r--r--lib/compiler/src/beam_ssa_share.erl8
-rw-r--r--lib/compiler/src/beam_ssa_type.erl4
-rw-r--r--lib/compiler/src/beam_validator.erl53
-rw-r--r--lib/compiler/src/cerl.erl2
-rw-r--r--lib/compiler/src/compile.erl7
-rw-r--r--lib/compiler/src/v3_core.erl3
-rw-r--r--lib/compiler/test/Makefile15
-rw-r--r--lib/compiler/test/beam_ssa_SUITE.erl26
-rw-r--r--lib/compiler/test/beam_type_SUITE.erl25
-rw-r--r--lib/compiler/test/beam_validator_SUITE.erl22
-rw-r--r--lib/compiler/test/fun_SUITE.erl20
-rw-r--r--lib/compiler/test/test_lib.erl3
15 files changed, 241 insertions, 80 deletions
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_dead.erl b/lib/compiler/src/beam_ssa_dead.erl
index 64b9b3e222..e78e4647a8 100644
--- a/lib/compiler/src/beam_ssa_dead.erl
+++ b/lib/compiler/src/beam_ssa_dead.erl
@@ -30,7 +30,7 @@
-import(lists, [append/1,keymember/3,last/1,member/2,
takewhile/2,reverse/1]).
--type used_vars() :: #{beam_ssa:label():=ordsets:ordset(beam_ssa:var_name())}.
+-type used_vars() :: #{beam_ssa:label():=cerl_sets:set(beam_ssa:var_name())}.
-type basic_type_test() :: atom() | {'is_tagged_tuple',pos_integer(),atom()}.
-type type_test() :: basic_type_test() | {'not',basic_type_test()}.
@@ -90,13 +90,11 @@ shortcut_opt(#st{bs=Blocks}=St) ->
%% the diff.)
%%
%% Unfortunately, processing the blocks in reverse post order
- %% potentially makes the time complexity quadratic or even cubic if
- %% the ordset of unset variables grows large, instead of
- %% linear for post order processing. We try to still get reasonable
- %% compilation times by optimizations that will keep the constant
- %% factor as low as possible, and we try to avoid the cubic time
- %% complexity by trying to keep the set of unset variables as small
- %% as possible.
+ %% potentially makes the time complexity quadratic, instead of
+ %% linear for post order processing. We avoid drastic slowdowns by
+ %% limiting how far we search forward to a common block that
+ %% both the success and failure label will reach (see the comment
+ %% in the first clause of shortcut_2/5).
Ls = beam_ssa:rpo(Blocks),
shortcut_opt(Ls, #{}, St).
@@ -124,10 +122,15 @@ shortcut_terminator(#b_br{bool=#b_var{}=Bool,succ=Succ0,fail=Fail0}=Br,
Is, From, Bs, St0) ->
St = St0#st{target=one_way},
RelOp = get_rel_op(Bool, Is),
- SuccBs = bind_var(Bool, #b_literal{val=true}, Bs),
+
+ %% The boolean in a `br` is seldom used by the successors. By
+ %% not binding its value unless it is actually used we might be able
+ %% to skip some work in shortcut/4 and sub/2.
+ SuccBs = bind_var_if_used(Succ0, Bool, #b_literal{val=true}, Bs, St),
BrSucc = shortcut(Succ0, From, SuccBs, St#st{rel_op=RelOp}),
- FailBs = bind_var(Bool, #b_literal{val=false}, Bs),
+ FailBs = bind_var_if_used(Fail0, Bool, #b_literal{val=false}, Bs, St),
BrFail = shortcut(Fail0, From, FailBs, St#st{rel_op=invert_op(RelOp)}),
+
case {BrSucc,BrFail} of
{#b_br{bool=#b_literal{val=true},succ=Succ},
#b_br{bool=#b_literal{val=true},succ=Fail}}
@@ -152,8 +155,14 @@ shortcut_switch([{Lit,L0}|T], Bool, From, Bs, St0) ->
[{Lit,L}|shortcut_switch(T, Bool, From, Bs, St0)];
shortcut_switch([], _, _, _, _) -> [].
+shortcut(L, _From, Bs, #st{rel_op=none,target=one_way}) when map_size(Bs) =:= 0 ->
+ %% There is no way that we can find a suitable branch, because there is no
+ %% relational operator stored, there are no bindings, and the block L can't
+ %% have any phi nodes from which we could pick bindings because when the target
+ %% is `one_way`, it implies the From block has a two-way `br` terminator.
+ #b_br{bool=#b_literal{val=true},succ=L,fail=L};
shortcut(L, From, Bs, St) ->
- shortcut_1(L, From, Bs, ordsets:new(), St).
+ shortcut_1(L, From, Bs, cerl_sets:new(), St).
shortcut_1(L, From, Bs0, UnsetVars0, St) ->
case shortcut_2(L, From, Bs0, UnsetVars0, St) of
@@ -170,7 +179,19 @@ shortcut_1(L, From, Bs0, UnsetVars0, St) ->
end.
%% Try to shortcut this block, branching to a successor.
-shortcut_2(L, From, Bs0, UnsetVars0, St) ->
+shortcut_2(L, From, Bs, UnsetVars, St) ->
+ case cerl_sets:size(UnsetVars) of
+ SetSize when SetSize > 128 ->
+ %% This is an heuristic to limit the search for a forced label
+ %% before it drastically slows down the compiler. Experiments
+ %% with scripts/diffable showed that limits larger than 31 did not
+ %% find any more opportunities for optimization.
+ none;
+ _SetSize ->
+ shortcut_3(L, From, Bs, UnsetVars, St)
+ end.
+
+shortcut_3(L, From, Bs0, UnsetVars0, St) ->
#b_blk{is=Is,last=Last} = get_block(L, St),
case eval_is(Is, From, Bs0, St) of
none ->
@@ -347,7 +368,7 @@ update_unset_vars(L, Is, Br, UnsetVars, #st{skippable=Skippable}) ->
%% Some variables defined in this block are used by
%% successors. We must update the set of unset variables.
SetInThisBlock = [V || #b_set{dst=V} <- Is],
- ordsets:union(UnsetVars, ordsets:from_list(SetInThisBlock))
+ cerl_sets:union(UnsetVars, cerl_sets:from_list(SetInThisBlock))
end.
shortcut_two_way(#b_br{succ=Succ,fail=Fail}, From, Bs0, UnsetVars0, St0) ->
@@ -376,14 +397,14 @@ is_br_safe(UnsetVars, Br, #st{us=Us}=St) ->
%% A two-way branch never branches to a phi node, so there
%% is no need to check for phi nodes here.
- not member(V, UnsetVars) andalso
- ordsets:is_disjoint(Used0, UnsetVars) andalso
- ordsets:is_disjoint(Used1, UnsetVars);
+ not cerl_sets:is_element(V, UnsetVars) andalso
+ cerl_sets:is_disjoint(Used0, UnsetVars) andalso
+ cerl_sets:is_disjoint(Used1, UnsetVars);
#b_br{succ=Same,fail=Same} ->
%% An unconditional branch must not jump to
%% a phi node.
not is_forbidden(Same, St) andalso
- ordsets:is_disjoint(map_get(Same, Us), UnsetVars)
+ cerl_sets:is_disjoint(map_get(Same, Us), UnsetVars)
end.
is_forbidden(L, St) ->
@@ -500,6 +521,15 @@ eval_switch_1([], _Arg, _PrevOp, Fail) ->
%% Fail is now either the failure label or 'none'.
Fail.
+bind_var_if_used(L, Var, Val0, Bs, #st{us=Us}) ->
+ case cerl_sets:is_element(Var, map_get(L, Us)) of
+ true ->
+ Val = get_value(Val0, Bs),
+ Bs#{Var=>Val};
+ false ->
+ Bs
+ end.
+
bind_var(Var, Val0, Bs) ->
Val = get_value(Val0, Bs),
Bs#{Var=>Val}.
@@ -989,7 +1019,7 @@ used_vars([{L,#b_blk{is=Is}=Blk}|Bs], UsedVars0, Skip0) ->
%% shortcut_opt/1.
Successors = beam_ssa:successors(Blk),
- Used0 = used_vars_succ(Successors, L, UsedVars0, []),
+ Used0 = used_vars_succ(Successors, L, UsedVars0, cerl_sets:new()),
Used = used_vars_blk(Blk, Used0),
UsedVars = used_vars_phis(Is, L, Used, UsedVars0),
@@ -1000,8 +1030,8 @@ used_vars([{L,#b_blk{is=Is}=Blk}|Bs], UsedVars0, Skip0) ->
%% shortcut_opt/1.
Defined0 = [Def || #b_set{dst=Def} <- Is],
- Defined = ordsets:from_list(Defined0),
- MaySkip = ordsets:is_disjoint(Defined, Used0),
+ Defined = cerl_sets:from_list(Defined0),
+ MaySkip = cerl_sets:is_disjoint(Defined, Used0),
case MaySkip of
true ->
Skip = Skip0#{L=>true},
@@ -1018,11 +1048,11 @@ used_vars_succ([S|Ss], L, LiveMap, Live0) ->
#{Key:=Live} ->
%% The successor has a phi node, and the value for
%% this block in the phi node is a variable.
- used_vars_succ(Ss, L, LiveMap, ordsets:union(Live, Live0));
+ used_vars_succ(Ss, L, LiveMap, cerl_sets:union(Live, Live0));
#{S:=Live} ->
%% No phi node in the successor, or the value for
%% this block in the phi node is a literal.
- used_vars_succ(Ss, L, LiveMap, ordsets:union(Live, Live0));
+ used_vars_succ(Ss, L, LiveMap, cerl_sets:union(Live, Live0));
#{} ->
%% A peek_message block which has not been processed yet.
used_vars_succ(Ss, L, LiveMap, Live0)
@@ -1040,7 +1070,7 @@ used_vars_phis(Is, L, Live0, UsedVars0) ->
case [{P,V} || {#b_var{}=V,P} <- PhiArgs] of
[_|_]=PhiVars ->
PhiLive0 = rel2fam(PhiVars),
- PhiLive = [{{L,P},ordsets:union(ordsets:from_list(Vs), Live0)} ||
+ PhiLive = [{{L,P},cerl_sets:union(cerl_sets:from_list(Vs), Live0)} ||
{P,Vs} <- PhiLive0],
maps:merge(UsedVars, maps:from_list(PhiLive));
[] ->
@@ -1050,14 +1080,14 @@ used_vars_phis(Is, L, Live0, UsedVars0) ->
end.
used_vars_blk(#b_blk{is=Is,last=Last}, Used0) ->
- Used = ordsets:union(Used0, beam_ssa:used(Last)),
+ Used = cerl_sets:union(Used0, cerl_sets:from_list(beam_ssa:used(Last))),
used_vars_is(reverse(Is), Used).
used_vars_is([#b_set{op=phi}|Is], Used) ->
used_vars_is(Is, Used);
used_vars_is([#b_set{dst=Dst}=I|Is], Used0) ->
- Used1 = ordsets:union(Used0, beam_ssa:used(I)),
- Used = ordsets:del_element(Dst, Used1),
+ Used1 = cerl_sets:union(Used0, cerl_sets:from_list(beam_ssa:used(I))),
+ Used = cerl_sets:del_element(Dst, Used1),
used_vars_is(Is, Used);
used_vars_is([], Used) ->
Used.
@@ -1066,8 +1096,9 @@ used_vars_is([], Used) ->
%%% Common utilities.
%%%
-sub(#b_set{args=Args}=I, Sub) ->
- I#b_set{args=[sub_arg(A, Sub) || A <- Args]}.
+sub(#b_set{args=Args}=I, Sub) when map_size(Sub) =/= 0 ->
+ I#b_set{args=[sub_arg(A, Sub) || A <- Args]};
+sub(I, _Sub) -> I.
sub_arg(#b_var{}=Old, Sub) ->
case Sub of
diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl
index 229edc6a1d..d87c66c272 100644
--- a/lib/compiler/src/beam_ssa_opt.erl
+++ b/lib/compiler/src/beam_ssa_opt.erl
@@ -1939,12 +1939,24 @@ verify_merge_is(_) ->
is_merge_allowed(_, #b_blk{}, #b_blk{is=[#b_set{op=peek_message}|_]}) ->
false;
-is_merge_allowed(L, #b_blk{last=#b_br{}}=Blk, #b_blk{}) ->
+is_merge_allowed(L, #b_blk{last=#b_br{}}=Blk, #b_blk{is=Is}) ->
%% The predecessor block must have exactly one successor (L) for
%% the merge to be safe.
case beam_ssa:successors(Blk) of
- [L] -> true;
- [_|_] -> false
+ [L] ->
+ case Is of
+ [#b_set{op=phi,args=[_]}|_] ->
+ %% The type optimizer pass must have been
+ %% turned off, since it would have removed this
+ %% redundant phi node. Refuse to merge the blocks
+ %% to ensure that this phi node remains at the
+ %% beginning of a block.
+ false;
+ _ ->
+ true
+ end;
+ [_|_] ->
+ false
end;
is_merge_allowed(_, #b_blk{last=#b_switch{}}, #b_blk{}) ->
false.
diff --git a/lib/compiler/src/beam_ssa_share.erl b/lib/compiler/src/beam_ssa_share.erl
index 426efa2cc9..73983bd34a 100644
--- a/lib/compiler/src/beam_ssa_share.erl
+++ b/lib/compiler/src/beam_ssa_share.erl
@@ -303,8 +303,12 @@ canonical_is([#b_ret{arg=Arg}], VarMap, Acc0) ->
Acc0
end,
{{ret,canonical_arg(Arg, VarMap),Acc1},VarMap};
-canonical_is([#b_br{bool=#b_var{},fail=Fail}], VarMap, Acc) ->
- {{br,succ,Fail,Acc},VarMap};
+canonical_is([#b_br{bool=#b_var{}=Arg,fail=Fail}], VarMap, Acc) ->
+ %% A previous buggy version of this code omitted the canonicalized
+ %% argument in the return value. Unfortunately, that worked most
+ %% of the time, except when `br` terminator referenced a variable
+ %% defined in a previous block instead of in the same block.
+ {{br,canonical_arg(Arg, VarMap),succ,Fail,Acc},VarMap};
canonical_is([#b_br{succ=Succ}], VarMap, Acc) ->
{{br,Succ,Acc},VarMap};
canonical_is([], VarMap, Acc) ->
diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl
index 68920e7dd3..3c06c83e2e 100644
--- a/lib/compiler/src/beam_ssa_type.erl
+++ b/lib/compiler/src/beam_ssa_type.erl
@@ -160,6 +160,10 @@ opt_finish_1([Arg | Args], [TypeMap | TypeMaps], ParamInfo0) ->
case join(maps:values(TypeMap)) of
any ->
opt_finish_1(Args, TypeMaps, ParamInfo0);
+ none ->
+ %% This function will never be called. Pretend that we don't
+ %% know the type for this argument.
+ opt_finish_1(Args, TypeMaps, ParamInfo0);
JoinedType ->
JoinedType = verified_type(JoinedType),
ParamInfo = ParamInfo0#{ Arg => validator_anno(JoinedType) },
diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl
index 370481d08a..349d74eb58 100644
--- a/lib/compiler/src/beam_validator.erl
+++ b/lib/compiler/src/beam_validator.erl
@@ -1618,13 +1618,8 @@ infer_types_1(#value{op={bif,'=:='},args=[LHS,RHS]}) ->
end;
infer_types_1(#value{op={bif,element},args=[{integer,Index}=Key,Tuple]}) ->
fun(Val, S) ->
- case is_value_alive(Tuple, S) of
- true ->
- Type = {tuple,[Index], #{ Key => get_term_type(Val, S) }},
- update_type(fun meet/2, Type, Tuple, S);
- false ->
- S
- end
+ Type = {tuple,[Index], #{ Key => get_term_type(Val, S) }},
+ update_type(fun meet/2, Type, Tuple, S)
end;
infer_types_1(#value{op={bif,is_atom},args=[Src]}) ->
infer_type_test_bif({atom,[]}, Src);
@@ -1648,10 +1643,7 @@ infer_types_1(#value{op={bif,is_tuple},args=[Src]}) ->
infer_type_test_bif({tuple,[0],#{}}, Src);
infer_types_1(#value{op={bif,tuple_size}, args=[Tuple]}) ->
fun({integer,Arity}, S) ->
- case is_value_alive(Tuple, S) of
- true -> update_type(fun meet/2, {tuple,Arity,#{}}, Tuple, S);
- false -> S
- end;
+ update_type(fun meet/2, {tuple,Arity,#{}}, Tuple, S);
(_, S) -> S
end;
infer_types_1(_) ->
@@ -1659,10 +1651,7 @@ infer_types_1(_) ->
infer_type_test_bif(Type, Src) ->
fun({atom,true}, S) ->
- case is_value_alive(Src, S) of
- true -> update_type(fun meet/2, Type, Src, S);
- false -> S
- end;
+ update_type(fun meet/2, Type, Src, S);
(_, S) ->
S
end.
@@ -2295,9 +2284,6 @@ get_raw_type(#value_ref{}=Ref, #vst{current=#st{vs=Vs}}) ->
get_raw_type(Src, #vst{}) ->
get_literal_type(Src).
-is_value_alive(#value_ref{}=Ref, #vst{current=#st{vs=Vs}}) ->
- is_map_key(Ref, Vs).
-
get_literal_type(nil=T) -> T;
get_literal_type({atom,A}=T) when is_atom(A) -> T;
get_literal_type({float,F}=T) when is_float(F) -> T;
@@ -2479,25 +2465,44 @@ merge_vrefs(RefA, RefB, Merge, Counter) ->
merge_values(Merge, VsA, VsB) ->
maps:fold(fun(Spec, New, Acc) ->
- merge_values_1(Spec, New, VsA, VsB, Acc)
+ mv_1(Spec, New, VsA, VsB, Acc)
end, #{}, Merge).
-merge_values_1(Same, Same, VsA, VsB, Acc) ->
+mv_1(Same, Same, VsA, VsB, Acc0) ->
%% We're merging different versions of the same value, so it's safe to
%% reuse old entries if the type's unchanged.
- #value{type=TypeA}=EntryA = map_get(Same, VsA),
- #value{type=TypeB}=EntryB = map_get(Same, VsB),
+ #value{type=TypeA,args=Args}=EntryA = map_get(Same, VsA),
+ #value{type=TypeB,args=Args}=EntryB = map_get(Same, VsB),
+
Entry = case join(TypeA, TypeB) of
TypeA -> EntryA;
TypeB -> EntryB;
JoinedType -> EntryA#value{type=JoinedType}
end,
- Acc#{ Same => Entry };
-merge_values_1({RefA, RefB}, New, VsA, VsB, Acc) ->
+
+ Acc = Acc0#{ Same => Entry },
+
+ %% Type inference may depend on values that are no longer reachable from a
+ %% register, so all arguments must be merged into the new state.
+ mv_args(Args, VsA, VsB, Acc);
+mv_1({RefA, RefB}, New, VsA, VsB, Acc) ->
#value{type=TypeA} = map_get(RefA, VsA),
#value{type=TypeB} = map_get(RefB, VsB),
Acc#{ New => #value{op=join,args=[],type=join(TypeA, TypeB)} }.
+mv_args([#value_ref{}=Arg | Args], VsA, VsB, Acc0) ->
+ case Acc0 of
+ #{ Arg := _ } ->
+ mv_args(Args, VsA, VsB, Acc0);
+ #{} ->
+ Acc = mv_1(Arg, Arg, VsA, VsB, Acc0),
+ mv_args(Args, VsA, VsB, Acc)
+ end;
+mv_args([_ | Args], VsA, VsB, Acc) ->
+ mv_args(Args, VsA, VsB, Acc);
+mv_args([], _VsA, _VsB, Acc) ->
+ Acc.
+
merge_fragility(FragileA, FragileB) ->
cerl_sets:union(FragileA, FragileB).
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/compile.erl b/lib/compiler/src/compile.erl
index 28db8986ff..0325c714d0 100644
--- a/lib/compiler/src/compile.erl
+++ b/lib/compiler/src/compile.erl
@@ -268,8 +268,11 @@ expand_opt(r21, Os) ->
[no_put_tuple2 | expand_opt(no_bsm3, Os)];
expand_opt({debug_info_key,_}=O, Os) ->
[encrypt_debug_info,O|Os];
-expand_opt(no_type_opt, Os) ->
- [no_ssa_opt_type_start,
+expand_opt(no_type_opt=O, Os) ->
+ %% Be sure to keep the no_type_opt option so that it will
+ %% be recorded in the BEAM file, allowing the test suites
+ %% to recompile the file with this option.
+ [O,no_ssa_opt_type_start,
no_ssa_opt_type_continue,
no_ssa_opt_type_finish | Os];
expand_opt(O, Os) -> [O|Os].
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/Makefile b/lib/compiler/test/Makefile
index db8eb7e2e1..7be23fbb93 100644
--- a/lib/compiler/test/Makefile
+++ b/lib/compiler/test/Makefile
@@ -109,6 +109,8 @@ NO_MOD_OPT = $(NO_OPT)
NO_SSA_OPT = $(NO_OPT)
+NO_TYPE_OPT = $(NO_OPT)
+
NO_OPT_MODULES= $(NO_OPT:%=%_no_opt_SUITE)
NO_OPT_ERL_FILES= $(NO_OPT_MODULES:%=%.erl)
POST_OPT_MODULES= $(NO_OPT:%=%_post_opt_SUITE)
@@ -121,6 +123,8 @@ NO_MOD_OPT_MODULES= $(NO_MOD_OPT:%=%_no_module_opt_SUITE)
NO_MOD_OPT_ERL_FILES= $(NO_MOD_OPT_MODULES:%=%.erl)
NO_SSA_OPT_MODULES= $(NO_SSA_OPT:%=%_no_ssa_opt_SUITE)
NO_SSA_OPT_ERL_FILES= $(NO_SSA_OPT_MODULES:%=%.erl)
+NO_TYPE_OPT_MODULES= $(NO_TYPE_OPT:%=%_no_type_opt_SUITE)
+NO_TYPE_OPT_ERL_FILES= $(NO_TYPE_OPT_MODULES:%=%.erl)
ERL_FILES= $(MODULES:%=%.erl)
CORE_FILES= $(CORE_MODULES:%=%.core)
@@ -150,7 +154,7 @@ EBIN = .
# ----------------------------------------------------
make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) $(NO_SSA_OPT_ERL_FILES) \
- $(INLINE_ERL_FILES) $(R21_ERL_FILES) $(NO_MOD_OPT_ERL_FILES)
+ $(INLINE_ERL_FILES) $(R21_ERL_FILES) $(NO_MOD_OPT_ERL_FILES) $(NO_TYPE_OPT_ERL_FILES)
$(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) \
> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +no_copt +no_postopt \
@@ -169,6 +173,8 @@ make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) $(NO_SSA_OPT_ERL_FILES
-o$(EBIN) $(NO_MOD_OPT_MODULES) >> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +from_core $(ERL_COMPILE_FLAGS) \
-o$(EBIN) $(CORE_MODULES) >> $(EMAKEFILE)
+ $(ERL_TOP)/make/make_emakefile +no_type_opt $(ERL_COMPILE_FLAGS) \
+ -o$(EBIN) $(NO_TYPE_OPT_MODULES) >> $(EMAKEFILE)
tests debug opt: make_emakefile
erl $(ERL_MAKE_FLAGS) -make
@@ -202,6 +208,10 @@ docs:
%_no_module_opt_SUITE.erl: %_SUITE.erl
sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
+%_no_type_opt_SUITE.erl: %_SUITE.erl
+ sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
+
+
# ----------------------------------------------------
# Release Target
# ----------------------------------------------------
@@ -216,7 +226,8 @@ release_tests_spec: make_emakefile
$(INSTALL_DATA) $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) \
$(INLINE_ERL_FILES) $(R21_ERL_FILES) \
$(NO_MOD_OPT_ERL_FILES) \
- $(NO_SSA_OPT_ERL_FILES) "$(RELSYSDIR)"
+ $(NO_SSA_OPT_ERL_FILES) \
+ $(NO_TYPE_OPT_ERL_FILES) "$(RELSYSDIR)"
$(INSTALL_DATA) $(CORE_FILES) "$(RELSYSDIR)"
for file in $(ERL_DUMMY_FILES); do \
module=`basename $$file .erl`; \
diff --git a/lib/compiler/test/beam_ssa_SUITE.erl b/lib/compiler/test/beam_ssa_SUITE.erl
index 6f99b0e28e..3b510f3528 100644
--- a/lib/compiler/test/beam_ssa_SUITE.erl
+++ b/lib/compiler/test/beam_ssa_SUITE.erl
@@ -525,9 +525,11 @@ do_comb_sw_2(X) ->
erase(?MODULE).
share_opt(_Config) ->
- ok = do_share_opt(0).
+ ok = do_share_opt_1(0),
+ ok = do_share_opt_2(),
+ ok.
-do_share_opt(A) ->
+do_share_opt_1(A) ->
%% The compiler would be stuck in an infinite loop in beam_ssa_share.
case A of
0 -> a;
@@ -536,6 +538,26 @@ do_share_opt(A) ->
end,
receive after 1 -> ok end.
+do_share_opt_2() ->
+ ok = sopt_2({[pointtopoint], [{dstaddr,any}]}, ok),
+ ok = sopt_2({[broadcast], [{broadaddr,any}]}, ok),
+ ok = sopt_2({[], []}, ok),
+ ok.
+
+sopt_2({Flags, Opts}, ok) ->
+ Broadcast = lists:member(broadcast, Flags),
+ P2P = lists:member(pointtopoint, Flags),
+ case Opts of
+ %% The following two clauses would be combined to one, silently
+ %% discarding the guard test of the P2P variable.
+ [{broadaddr,_}|Os] when Broadcast ->
+ sopt_2({Flags, Os}, ok);
+ [{dstaddr,_}|Os] when P2P ->
+ sopt_2({Flags, Os}, ok);
+ [] ->
+ 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),
diff --git a/lib/compiler/test/beam_type_SUITE.erl b/lib/compiler/test/beam_type_SUITE.erl
index 076a604aa4..a99dee48aa 100644
--- a/lib/compiler/test/beam_type_SUITE.erl
+++ b/lib/compiler/test/beam_type_SUITE.erl
@@ -24,7 +24,8 @@
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,list_append/1,bad_binary_unit/1]).
+ test_size/1,cover_lists_functions/1,list_append/1,bad_binary_unit/1,
+ none_argument/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -49,7 +50,8 @@ groups() ->
test_size,
cover_lists_functions,
list_append,
- bad_binary_unit
+ bad_binary_unit,
+ none_argument
]}].
init_per_suite(Config) ->
@@ -518,5 +520,24 @@ bad_binary_unit(_Config) ->
false = is_binary(Bitstring),
ok.
+%% ERL-1013: The compiler would crash during the type optimization pass.
+none_argument(_Config) ->
+ Binary = id(<<3:16, 42>>),
+ error = id(case Binary of
+ <<Len:16, Body/binary>> when length(Body) == Len - 2 ->
+ %% The type for Body will be none. It means
+ %% that this clause will never match and that
+ %% uncompress/1 will never be called.
+ uncompress(Body);
+ _ ->
+ error
+ end),
+ ok.
+
+uncompress(CompressedBinary) ->
+ %% The type for CompressedBinary is none, which beam_ssa_type
+ %% did not handle properly.
+ zlib:uncompress(CompressedBinary).
+
id(I) ->
I.
diff --git a/lib/compiler/test/beam_validator_SUITE.erl b/lib/compiler/test/beam_validator_SUITE.erl
index 6b1438abdd..20f6cb2691 100644
--- a/lib/compiler/test/beam_validator_SUITE.erl
+++ b/lib/compiler/test/beam_validator_SUITE.erl
@@ -681,11 +681,16 @@ infer_on_eq_4(T) ->
%% ERIERL-348; types were inferred for dead values, causing validation to fail.
+-record(idv, {key}).
+
infer_dead_value(Config) when is_list(Config) ->
a = idv_1({a, b, c, d, e, f, g}, {0, 0, 0, 0, 0, 0, 0}),
b = idv_1({a, b, c, d, 0, 0, 0}, {a, b, c, d, 0, 0, 0}),
c = idv_1({0, 0, 0, 0, 0, f, g}, {0, 0, 0, 0, 0, f, g}),
error = idv_1(gurka, gaffel),
+
+ ok = idv_2(id(#idv{})),
+
ok.
idv_1({_A, _B, _C, _D, _E, _F, _G},
@@ -700,6 +705,23 @@ idv_1({_A, _B, _C, _D, _E, F, G},
idv_1(_A, _B) ->
error.
+%% ERL-995: The first solution to ERIERL-348 was incomplete and caused
+%% validation to fail when living values depended on delayed type inference on
+%% "dead" values.
+
+idv_2(State) ->
+ Flag = (State#idv.key == undefined),
+ case id(gurka) of
+ {_} -> id([Flag]);
+ _ -> ok
+ end,
+ if
+ Flag -> idv_called_once(State);
+ true -> ok
+ end.
+
+idv_called_once(_State) -> ok.
+
%%%-------------------------------------------------------------------------
transform_remove(Remove, Module) ->
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/test_lib.erl b/lib/compiler/test/test_lib.erl
index 3348c6e9ea..34410e4b2a 100644
--- a/lib/compiler/test/test_lib.erl
+++ b/lib/compiler/test/test_lib.erl
@@ -97,7 +97,8 @@ get_data_dir(Config) ->
Data2 = re:replace(Data1, "_post_opt_SUITE", "_SUITE", Opts),
Data3 = re:replace(Data2, "_inline_SUITE", "_SUITE", Opts),
Data4 = re:replace(Data3, "_r21_SUITE", "_SUITE", Opts),
- Data = re:replace(Data4, "_no_module_opt_SUITE", "_SUITE", Opts),
+ Data5 = re:replace(Data4, "_no_module_opt_SUITE", "_SUITE", Opts),
+ Data = re:replace(Data5, "_no_type_opt_SUITE", "_SUITE", Opts),
re:replace(Data, "_no_ssa_opt_SUITE", "_SUITE", Opts).
is_cloned_mod(Mod) ->