diff options
Diffstat (limited to 'lib/compiler')
-rw-r--r-- | lib/compiler/doc/src/notes.xml | 44 | ||||
-rw-r--r-- | lib/compiler/src/beam_ssa_dead.erl | 87 | ||||
-rw-r--r-- | lib/compiler/src/beam_ssa_opt.erl | 18 | ||||
-rw-r--r-- | lib/compiler/src/beam_ssa_pre_codegen.erl | 6 | ||||
-rw-r--r-- | lib/compiler/src/beam_ssa_share.erl | 8 | ||||
-rw-r--r-- | lib/compiler/src/beam_validator.erl | 62 | ||||
-rw-r--r-- | lib/compiler/src/compile.erl | 7 | ||||
-rw-r--r-- | lib/compiler/test/Makefile | 15 | ||||
-rw-r--r-- | lib/compiler/test/beam_ssa_SUITE.erl | 41 | ||||
-rw-r--r-- | lib/compiler/test/beam_type_SUITE.erl | 25 | ||||
-rw-r--r-- | lib/compiler/test/beam_validator_SUITE.erl | 23 | ||||
-rw-r--r-- | lib/compiler/test/test_lib.erl | 3 | ||||
-rw-r--r-- | lib/compiler/vsn.mk | 2 |
13 files changed, 264 insertions, 77 deletions
diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml index f0d869381b..f11444137d 100644 --- a/lib/compiler/doc/src/notes.xml +++ b/lib/compiler/doc/src/notes.xml @@ -32,6 +32,50 @@ <p>This document describes the changes made to the Compiler application.</p> +<section><title>Compiler 7.4.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>Fixed a compiler crash introduced in <c>22.0.6</c> + (OTP-15952).</p> + <p> + Own Id: OTP-15953 Aux Id: ERL-999 </p> + </item> + </list> + </section> + +</section> + +<section><title>Compiler 7.4.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>Fixed an unsafe optimization when matching + <c>tuple_size/1</c> outside of guards, which could crash + the emulator if the argument was not a tuple.</p> + <p> + Own Id: OTP-15945</p> + </item> + <item> + <p>Fixed a rare bug that could cause the wrong kind of + exception to be thrown when a BIF failed in a function + that matched bitstrings.</p> + <p> + Own Id: OTP-15946</p> + </item> + <item> + <p>Fixed a bug where receive statements inside try/catch + blocks could return incorrect results.</p> + <p> + Own Id: OTP-15952</p> + </item> + </list> + </section> + +</section> + <section><title>Compiler 7.4.2</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/compiler/src/beam_ssa_dead.erl b/lib/compiler/src/beam_ssa_dead.erl index 88767456a3..55ded77d43 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 cce539f582..580abf4ed9 100644 --- a/lib/compiler/src/beam_ssa_opt.erl +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -1960,12 +1960,24 @@ is_merge_allowed(_, #b_blk{}, #b_blk{is=[#b_set{op=exception_trampoline}|_]}) -> false; is_merge_allowed(_, #b_blk{is=[#b_set{op=exception_trampoline}|_]}, #b_blk{}) -> 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_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl index 28b2e55c43..89053c7b9f 100644 --- a/lib/compiler/src/beam_ssa_pre_codegen.erl +++ b/lib/compiler/src/beam_ssa_pre_codegen.erl @@ -1623,10 +1623,14 @@ find_loop_exit([L1,L2|_Ls], Blocks) -> find_loop_exit_1(Path1, cerl_sets:from_list(Path2)); find_loop_exit(_, _) -> none. +find_loop_exit_1([?EXCEPTION_BLOCK | T], OtherPath) -> + %% ?EXCEPTION_BLOCK is a marker and not an actual block, so we can't + %% consider it to be a common block even if both paths cross it. + find_loop_exit_1(T, OtherPath); find_loop_exit_1([H|T], OtherPath) -> case cerl_sets:is_element(H, OtherPath) of true -> H; - false -> find_loop_exit_1(T, OtherPath) + false -> find_loop_exit_1(T, OtherPath) end; find_loop_exit_1([], _) -> none. diff --git a/lib/compiler/src/beam_ssa_share.erl b/lib/compiler/src/beam_ssa_share.erl index 85ab088d14..fa728992f8 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_validator.erl b/lib/compiler/src/beam_validator.erl index afede2b54d..abc3aa3875 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -1531,21 +1531,8 @@ validate_select_val(Fail, [Val,{f,L}|T], Src, Vst0) -> update_ne_types(Src, Val, FailVst) end), validate_select_val(Fail, T, Src, Vst); -validate_select_val(Fail, [], Src, Vst) -> +validate_select_val(Fail, [], _Src, Vst) -> branch(Fail, Vst, - fun(FailVst) -> - FailType = get_term_type(Src, FailVst), - case beam_types:get_singleton_value(FailType) of - {ok, Value} -> - %% This is the only possible value at the fail - %% label, so we can infer types as if we matched it - %% directly. - Lit = value_to_literal(Value), - update_eq_types(Src, Lit, FailVst); - error -> - FailVst - end - end, fun(SuccVst) -> %% The next instruction is never executed. kill_state(SuccVst) @@ -1595,27 +1582,23 @@ infer_types(CompareOp, LHS, RHS, #vst{current=#st{vs=Vs}}=Vst0) -> Vst0 end. -infer_types_1(#value{op={bif,'=:='},args=[LHS,RHS]}, Val, Op, Vst0) -> +infer_types_1(#value{op={bif,'=:='},args=[LHS,RHS]}, Val, Op, Vst) -> case Val of {atom, Bool} when Op =:= eq_exact, Bool; Op =:= ne_exact, not Bool -> - Vst = infer_types(eq_exact, RHS, LHS, Vst0), infer_types(eq_exact, LHS, RHS, Vst); {atom, Bool} when Op =:= ne_exact, Bool; Op =:= eq_exact, not Bool -> - Vst = infer_types(ne_exact, RHS, LHS, Vst0), infer_types(ne_exact, LHS, RHS, Vst); _ -> - Vst0 + Vst end; -infer_types_1(#value{op={bif,'=/='},args=[LHS,RHS]}, Val, Op, Vst0) -> +infer_types_1(#value{op={bif,'=/='},args=[LHS,RHS]}, Val, Op, Vst) -> case Val of {atom, Bool} when Op =:= ne_exact, Bool; Op =:= eq_exact, not Bool -> - Vst = infer_types(ne_exact, RHS, LHS, Vst0), infer_types(ne_exact, LHS, RHS, Vst); {atom, Bool} when Op =:= eq_exact, Bool; Op =:= ne_exact, not Bool -> - Vst = infer_types(eq_exact, RHS, LHS, Vst0), infer_types(eq_exact, LHS, RHS, Vst); _ -> - Vst0 + Vst end; infer_types_1(#value{op={bif,element},args=[{integer,Index},Tuple]}, Val, Op, Vst) when Index >= 1 -> @@ -1797,17 +1780,21 @@ update_type(Merge, With, Literal, Vst) -> end. update_eq_types(LHS, RHS, Vst0) -> - Vst1 = infer_types(eq_exact, LHS, RHS, Vst0), + LType = get_term_type(LHS, Vst0), + RType = get_term_type(RHS, Vst0), - T1 = get_term_type(LHS, Vst1), - T2 = get_term_type(RHS, Vst1), + Vst1 = update_type(fun meet/2, RType, LHS, Vst0), + Vst = update_type(fun meet/2, LType, RHS, Vst1), - Vst = update_type(fun meet/2, T2, LHS, Vst1), - update_type(fun meet/2, T1, RHS, Vst). + infer_types(eq_exact, LHS, RHS, Vst). update_ne_types(LHS, RHS, Vst0) -> - Vst = infer_types(ne_exact, LHS, RHS, Vst0), + Vst1 = update_ne_types_1(LHS, RHS, Vst0), + Vst = update_ne_types_1(RHS, LHS, Vst1), + + infer_types(ne_exact, LHS, RHS, Vst). +update_ne_types_1(LHS, RHS, Vst0) -> %% While updating types on equality is fairly straightforward, inequality %% is a bit trickier since all we know is that the *value* of LHS differs %% from RHS, so we can't blindly subtract their types. @@ -1817,10 +1804,23 @@ update_ne_types(LHS, RHS, Vst0) -> %% #t_integer{} we would erroneously infer that the new type is float. %% %% Therefore, we only subtract when we know that RHS has a specific value. - RType = get_term_type(RHS, Vst), + RType = get_term_type(RHS, Vst0), case beam_types:is_singleton_type(RType) of - true -> update_type(fun subtract/2, RType, LHS, Vst); - false -> Vst + true -> + Vst = update_type(fun subtract/2, RType, LHS, Vst0), + + %% If LHS has a specific value after subtraction we can infer types + %% as if we've made an exact match, which is much stronger than + %% ne_exact. + LType = get_term_type(LHS, Vst), + case beam_types:get_singleton_value(LType) of + {ok, Value} -> + infer_types(eq_exact, LHS, value_to_literal(Value), Vst); + error -> + Vst + end; + false -> + Vst0 end. %% Helper functions for the above. diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index 42f9e8b902..21d67f5649 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -271,8 +271,11 @@ expand_opt(r22, Os) -> [no_shared_fun_wrappers, no_swap | 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/test/Makefile b/lib/compiler/test/Makefile index 2c0767b17f..44cff40128 100644 --- a/lib/compiler/test/Makefile +++ b/lib/compiler/test/Makefile @@ -110,6 +110,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) @@ -122,6 +124,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) @@ -151,7 +155,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 \ @@ -170,6 +174,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 @@ -203,6 +209,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 # ---------------------------------------------------- @@ -217,7 +227,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 11bd5cf07d..0a0f35ee7f 100644 --- a/lib/compiler/test/beam_ssa_SUITE.erl +++ b/lib/compiler/test/beam_ssa_SUITE.erl @@ -196,6 +196,9 @@ recv(_Config) -> self() ! 2, b = tricky_recv_5(), + %% tricky_recv_6/0 is a compile-time error. + tricky_recv_6(), + ok. sync_wait_mon({Pid, Ref}, Timeout) -> @@ -321,6 +324,18 @@ tricky_recv_5() -> _:_ -> c end. +%% When fixing tricky_recv_5, we introduced a compiler crash when the common +%% exit block was ?EXCEPTION_BLOCK and floats were in the picture. +tricky_recv_6() -> + RefA = make_ref(), + RefB = make_ref(), + receive + {RefA, Number} -> Number + 1.0; + {RefB, Number} -> Number + 2.0 + after 0 -> + ok + end. + maps(_Config) -> {'EXIT',{{badmatch,#{}},_}} = (catch maps_1(any)), ok. @@ -469,9 +484,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; @@ -480,6 +497,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 d49d5af9c3..326ad9042f 100644 --- a/lib/compiler/test/beam_validator_SUITE.erl +++ b/lib/compiler/test/beam_validator_SUITE.erl @@ -35,7 +35,7 @@ map_field_lists/1,cover_bin_opt/1, val_dsetel/1,bad_tuples/1,bad_try_catch_nesting/1, receive_stacked/1,aliased_types/1,type_conflict/1, - infer_on_eq/1,infer_dead_value/1]). + infer_on_eq/1,infer_dead_value/1,infer_on_ne/1]). -include_lib("common_test/include/ct.hrl"). @@ -65,7 +65,7 @@ groups() -> map_field_lists,cover_bin_opt,val_dsetel, bad_tuples,bad_try_catch_nesting, receive_stacked,aliased_types,type_conflict, - infer_on_eq,infer_dead_value]}]. + infer_on_eq,infer_dead_value,infer_on_ne]}]. init_per_suite(Config) -> test_lib:recompile(?MODULE), @@ -700,6 +700,25 @@ idv_1({_A, _B, _C, _D, _E, F, G}, idv_1(_A, _B) -> error. +%% ERL-998; type inference for select_val (#b_switch{}) was more clever than +%% that for is_ne_exact (#b_br{}), sometimes failing validation when the type +%% optimization pass acted on the former and the validator got the latter. + +-record(ion, {state}). + +infer_on_ne(Config) when is_list(Config) -> + #ion{state = closing} = ion_1(#ion{ state = id(open) }), + #ion{state = closing} = ion_close(#ion{ state = open }), + ok. + +ion_1(State = #ion{state = open}) -> ion_2(State); +ion_1(State = #ion{state = closing}) -> ion_2(State). + +ion_2(State = #ion{state = open}) -> ion_close(State); +ion_2(#ion{state = closing}) -> ok. + +ion_close(State = #ion{}) -> State#ion{state = closing}. + %%%------------------------------------------------------------------------- transform_remove(Remove, Module) -> diff --git a/lib/compiler/test/test_lib.erl b/lib/compiler/test/test_lib.erl index a468482acb..4b68e663cd 100644 --- a/lib/compiler/test/test_lib.erl +++ b/lib/compiler/test/test_lib.erl @@ -99,7 +99,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) -> diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk index 508bbc902c..7192ddca15 100644 --- a/lib/compiler/vsn.mk +++ b/lib/compiler/vsn.mk @@ -1 +1 @@ -COMPILER_VSN = 7.4.2 +COMPILER_VSN = 7.4.4 |