Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:24
erlang
2864-beam_ssa_dead-Add-elimination-of-redundant...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 2864-beam_ssa_dead-Add-elimination-of-redundant-tests.patch of Package erlang
From 837c56735c0dc9e5f4022c26c00596ca930d4855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org> Date: Fri, 20 Aug 2021 07:15:11 +0200 Subject: [PATCH 4/7] beam_ssa_dead: Add elimination of redundant tests As a preparation for removing the beam_peep pass, reimplement elimination of redundant tests in beam_ssa_dead. The optimization in beam_ssa_dead is more general and will find many more redundant tests than beam_peep ever did. For example, in Elixir's `Enum` module, it will eliminate many unreachable inlined instances of the `reduce/3` function. --- lib/compiler/src/beam_ssa_dead.erl | 326 ++++++++++++++++++++++++++++- lib/compiler/test/guard_SUITE.erl | 143 ++++++++++++- 2 files changed, 462 insertions(+), 7 deletions(-) diff --git a/lib/compiler/src/beam_ssa_dead.erl b/lib/compiler/src/beam_ssa_dead.erl index 7f00abd709..73626dd08a 100644 --- a/lib/compiler/src/beam_ssa_dead.erl +++ b/lib/compiler/src/beam_ssa_dead.erl @@ -27,8 +27,8 @@ -export([opt/1]). -include("beam_ssa.hrl"). --import(lists, [append/1,keymember/3,last/1,member/2, - reverse/1,takewhile/2]). +-import(lists, [append/1,foldl/3,keymember/3,last/1,member/2, + reverse/1,reverse/2,takewhile/2]). -type used_vars() :: #{beam_ssa:label():=sets:set(beam_ssa:var_name())}. @@ -54,13 +54,13 @@ Label :: beam_ssa:label(), Block :: beam_ssa:b_blk(). -opt(Linear) -> - {Used,Skippable} = used_vars(Linear), - Blocks0 = maps:from_list(Linear), +opt(Linear0) -> + {Used,Skippable} = used_vars(Linear0), + Blocks0 = maps:from_list(Linear0), St0 = #st{bs=Blocks0,us=Used,skippable=Skippable}, St = shortcut_opt(St0), #st{bs=Blocks} = combine_eqs(St#st{us=#{}}), - beam_ssa:linearize(Blocks). + opt_redundant_tests(Blocks). %%% %%% Shortcut br/switch targets. @@ -1050,6 +1050,320 @@ lit_type(Val) -> true -> none end. + +%%% +%%% Remove redundant tests. +%%% +%%% Repeated tests can be introduced by inlining, macros, or +%%% complex guards such as: +%%% +%%% is_head(M, S) when M =:= <<1>>, S =:= <<2>> -> +%%% true; +%%% is_head(M, S) when M =:= <<1>>, S =:= <<3>> -> +%%% false. +%%% +%%% The repeated test is not removed by any of the other optimizing +%%% passes: +%%% +%%% 0: +%%% _2 = bif:'=:=' _0, `<<1>>` +%%% br _2, ^19, ^3 +%%% +%%% 19: +%%% _3 = bif:'=:=' _1, `<<2>>` +%%% br _3, ^7, ^4 +%%% +%%% 7: +%%% ret `true` +%%% +%%% 4: +%%% _4 = bif:'=:=' _0, `<<1>>` +%%% br _4, ^15, ^3 +%%% +%%% 15: +%%% _5 = bif:'=:=' _1, `<<3>>` +%%% br _5, ^11, ^3 +%%% +%%% 11: +%%% ret `false` +%%% +%%% 3: +%%% %% Generate function clause error. +%%% . . . +%%% +%%% This sub pass will keep track of all tests that are known to have +%%% been executed at each block in the SSA code. If a repeated or +%%% inverted test is seen, it can be eliminated. For the example +%%% above, this sub pass will rewrite block 4 like this: +%%% +%%% 4: +%%% _4 = bif:'=:=' `true`, `true` +%%% br ^15 +%%% +%%% This sub pass also removes redundant inverted test such as the +%%% last test in this code: +%%% +%%% if +%%% A < B -> . . . ; +%%% A >= B -> . . . +%%% end +%%% +%%% and this code: +%%% +%%% if +%%% A < B -> . . . ; +%%% A > B -> . . . ; +%%% A == B -> . . . +%%% end +%%% + +opt_redundant_tests(Blocks) -> + All = #{0 => #{}, ?EXCEPTION_BLOCK => #{}}, + RPO = beam_ssa:rpo(Blocks), + Linear = opt_redundant_tests(RPO, Blocks, All), + beam_ssa:trim_unreachable(Linear). + +opt_redundant_tests([L|Ls], Blocks, All0) -> + case All0 of + #{L := Tests} -> + Blk0 = map_get(L, Blocks), + Tests = map_get(L, All0), + Blk1 = opt_switch(Blk0, Tests), + #b_blk{is=Is0} = Blk1, + case opt_redundant_tests_is(Is0, Tests, []) of + none -> + All = update_successors(Blk1, Tests, All0), + [{L,Blk1}|opt_redundant_tests(Ls, Blocks, All)]; + {new_test,Bool,Test,MustInvert} -> + All = update_successors(Blk1, Bool, Test, MustInvert, + Tests, All0), + [{L,Blk1}|opt_redundant_tests(Ls, Blocks, All)]; + {old_test,Is,BoolVar,BoolValue} -> + Blk = case Blk1 of + #b_blk{last=#b_br{bool=BoolVar}=Br0} -> + Br = beam_ssa:normalize(Br0#b_br{bool=BoolValue}), + Blk1#b_blk{is=Is,last=Br}; + #b_blk{}=Blk2 -> + Blk2#b_blk{is=Is} + end, + All = update_successors(Blk, Tests, All0), + [{L,Blk}|opt_redundant_tests(Ls, Blocks, All)] + end; + #{} -> + opt_redundant_tests(Ls, Blocks, All0) + end; +opt_redundant_tests([], _Blocks, _All) -> []. + +opt_switch(#b_blk{last=#b_switch{arg=Arg,list=List0}=Sw}=Blk, Tests) + when map_size(Tests) =/= 0 -> + List = opt_switch_1(List0, Arg, Tests), + Blk#b_blk{last=Sw#b_switch{list=List}}; +opt_switch(Blk, _Tests) -> Blk. + +opt_switch_1([{Lit,_}=H|T], Arg, Tests) -> + case Tests of + #{{'=:=',Arg,Lit} := false} -> + opt_switch_1(T, Arg, Tests); + #{} -> + [H|opt_switch_1(T, Arg, Tests)] + end; +opt_switch_1([], _, _) -> []. + +opt_redundant_tests_is([#b_set{op=Op,args=Args,dst=Bool}=I0], Tests, Acc) -> + case canonical_test(Op, Args) of + none -> + none; + {Test,MustInvert} -> + case old_result(Test, Tests) of + Result0 when is_boolean(Result0) -> + Result = #b_literal{val=Result0 xor MustInvert}, + I = I0#b_set{op={bif,'=:='},args=[Result,#b_literal{val=true}]}, + {old_test,reverse(Acc, [I]),Bool,Result}; + none -> + {new_test,Bool,Test,MustInvert} + end + end; +opt_redundant_tests_is([I|Is], Tests, Acc) -> + opt_redundant_tests_is(Is, Tests, [I|Acc]); +opt_redundant_tests_is([], _Tests, _Acc) -> none. + +old_result(Test, Tests) -> + case Tests of + #{Test := Val} -> Val; + #{} -> old_result_1(Test, Tests) + end. + +%% +%% Remove the last test in a sequence of tests (in any order): +%% +%% if +%% Val1 < Val2 -> . . . +%% Val1 > Val2 -> . . . +%% Val1 == Val2 -> . . . +%% end +%% +%% NOTE: The same optimization is not possible to do with `=:=`, unless +%% we have type information so that we know that `==` and `=:=` produces +%% the same result. +%% + +old_result_1({'==',A,B}, Tests) -> + case Tests of + #{{'<',A,B} := false, {'=<',A,B} := true} -> + %% not A < B, not A > B ==> A == B + true; + #{} -> + none + end; +old_result_1({'=<',A,B}, Tests) -> + case Tests of + #{{'<',A,B} := false, {'==',A,B} := false} -> + %% not A < B, not A == B ==> A > B + false; + #{} -> + none + end; +old_result_1({'<',A,B}, Tests) -> + case Tests of + #{{'=<',A,B} := true, {'==',A,B} := false} -> + %% not A < B, not A == B ==> A < B + true; + #{} -> + none + end; +old_result_1({is_nonempty_list,A}, Tests) -> + case Tests of + #{{is_list,A} := false} -> false; + #{} -> none + end; +old_result_1(_, _) -> none. + +%% canonical_test(Op0, Args0) -> {CanonicalTest, MustInvert} +%% CanonicalTest = {Operator,Variable,Variable|Literal} | +%% {TypeTest,Variable} +%% Operation = '<' | '=<' | '=:=' | '==' +%% TypeTest = is_atom | is_integer ... +%% Variable = #b_var{} +%% Literal = #b_literal{} +%% MustInvert = true | false +%% +%% Canonicalize a test. Always make the register +%% operand the first operand. If there are two registers, +%% order the registers in lexical order. Invert four of +%% the relation operators and indicate with MustInvert +%% whether the operator was inverted. +%% +%% For example, this instruction: +%% +%% #b_set{op={bif,'=:='},args=[#b_literal{}, #b_var{}} +%% +%% will be canonicalized to: +%% +%% {{'=:=',#b_var{},#b_literal{}}, false} +%% +%% while: +%% +%% #b_set{op={bif,'>'},args=[#b_var{}, #b_literal{}}} +%% +%% will be canonicalized to: +%% +%% {{'=<',#b_var{},#b_literal{}}, true} +%% +canonical_test(Op, Args) -> + case normalize_test(Op, Args) of + none -> + none; + Test -> + Inv = case Test of + {'=/=',_,_} -> true; + {'/=',_,_} -> true; + {'>',_,_} -> true; + {'>=',_,_} -> true; + _ -> false + end, + case Inv of + true -> {invert_test(Test),true}; + false -> {Test,false} + end + end. + +update_successors(#b_blk{last=#b_br{bool=Bool,succ=Succ,fail=Fail}}, + Bool, Test, MustInvert, Tests, All0) -> + All1 = update_successor(Succ, Tests#{Test => not MustInvert}, All0), + update_successor(Fail, Tests#{Test => MustInvert}, All1); +update_successors(Blk, _, _, _, TestsA, All) -> + update_successors(Blk, TestsA, All). + +update_successors(#b_blk{last=#b_ret{}}, _Tests, All) -> + All; +update_successors(#b_blk{last=#b_switch{arg=Arg,fail=Fail,list=List}}, + Tests, All0) -> + All1 = update_successors_sw_fail(List, Arg, Fail, Tests, All0), + update_successors_sw(List, Arg, Tests, All1); +update_successors(Blk, Tests, All) -> + foldl(fun(L, A) -> + update_successor(L, Tests, A) + end, All, beam_ssa:successors(Blk)). + +update_successors_sw_fail(List, Arg, Fail, Tests0, All) -> + Tests = foldl(fun({Lit,_}, A) -> + A#{{'=:=',Arg,Lit} => false} + end, Tests0, List), + update_successor(Fail, Tests, All). + +update_successors_sw([{Lit,L}|T], Arg, Tests, All0) -> + All = update_successor(L, Tests#{{'=:=',Arg,Lit} => true}, All0), + update_successors_sw(T, Arg, Tests, All); +update_successors_sw([], _, _, All) -> All. + +update_successor(?EXCEPTION_BLOCK, _Tests, All) -> + All; +update_successor(L, TestsA, All0) -> + case All0 of + #{L := TestsB} -> + All0#{L := maps_intersect_kv(TestsA, TestsB)}; + #{} -> + All0#{L => TestsA} + end. + +maps_intersect_kv(Map, Map) -> + Map; +maps_intersect_kv(Map1, Map2) -> + if + map_size(Map1) < map_size(Map2) -> + map_intersect_kv_1(Map1, Map2); + true -> + map_intersect_kv_1(Map2, Map1) + end. + +map_intersect_kv_1(SmallMap, BigMap) -> + Next = maps:next(maps:iterator(SmallMap)), + case maps_is_subset_kv(Next, BigMap) of + true -> SmallMap; + false -> map_intersect_kv_2(Next, BigMap, []) + end. + +map_intersect_kv_2({K, V, Iterator}, BigMap, Acc) -> + Next = maps:next(Iterator), + case BigMap of + #{K := V} -> + map_intersect_kv_2(Next, BigMap, [{K,V}|Acc]); + #{} -> + map_intersect_kv_2(Next, BigMap, Acc) + end; +map_intersect_kv_2(none, _BigMap, Acc) -> + maps:from_list(Acc). + +maps_is_subset_kv({K, V, Iterator}, BigMap) -> + Next = maps:next(Iterator), + case BigMap of + #{K := V} -> + maps_is_subset_kv(Next, BigMap); + #{} -> + false + end; +maps_is_subset_kv(none, _BigMap) -> true. + %%% %%% Calculate used variables for each block. %%% diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl index af7d8299d5..3d7da5075f 100644 --- a/lib/compiler/test/guard_SUITE.erl +++ b/lib/compiler/test/guard_SUITE.erl @@ -1424,7 +1424,9 @@ rel_op_combinations(Config) when is_list(Config) -> Red = gb_trees:from_orddict(Red0), rel_op_combinations_3(100, Red), - rel_op_combinations_4(). + rel_op_combinations_4(), + + rel_op_combinations_5(). rel_op_combinations_1(0, _) -> ok; @@ -1677,6 +1679,145 @@ rel_op_vars_1(X, N) when X =< N -> le. rel_op_vars_2(X, N) when X =/= N -> ne; rel_op_vars_2(X, N) when X >= N -> ge. +rel_op_combinations_5() -> + lt = lt_gt_eq(a, b), + lt = lt_gt_eq(1.0, 42), + lt = lt_gt_eq(1, 42.0), + + eq = lt_gt_eq(a, a), + eq = lt_gt_eq(42, 42), + eq = lt_gt_eq(42.0, 42), + eq = lt_gt_eq(42, 42.0), + eq = lt_gt_eq(42.0, 42.0), + + gt = lt_gt_eq(b, a), + gt = lt_gt_eq(42.0, 1), + gt = lt_gt_eq(42, 1.0), + + lt = eq_exact_lt_gt(a, b), + lt = eq_exact_lt_gt(1.0, 42), + lt = eq_exact_lt_gt(1, 42.0), + + eq = eq_exact_lt_gt(a, a), + eq = eq_exact_lt_gt(42, 42), + none = eq_exact_lt_gt(42, 42.0), + + gt = eq_exact_lt_gt(b, a), + gt = eq_exact_lt_gt(42.0, 1), + gt = eq_exact_lt_gt(42, 1.0), + + ok. + +lt_gt_eq(A, B) -> + Res = lt_gt_eq_1(A, B), + Res = lt_gt_eq_2(A, B), + Res = lt_gt_eq_3(A, B), + Res = lt_gt_eq_4(A, B), + Res = lt_gt_eq_5(A, B), + lt_gt_eq_6(A, B). + +%% The last test in each 'if' is unnecessary. +lt_gt_eq_1(A, B) -> + if + A < B -> lt; + A == B -> eq; + A > B -> gt + end. + +lt_gt_eq_2(A, B) -> + if + A > B -> gt; + A == B -> eq; + A < B -> lt + end. + +lt_gt_eq_3(A, B) -> + if + A == B -> eq; + A < B -> lt; + A > B -> gt + end. + +lt_gt_eq_4(A, B) -> + if + A == B -> eq; + A > B -> gt; + A < B -> lt + end. + +lt_gt_eq_5(A, B) -> + if + A < B -> lt; + A > B -> gt; + A == B -> eq + end. + +lt_gt_eq_6(A, B) -> + if + A > B -> gt; + A < B -> lt; + A == B -> eq + end. + +eq_exact_lt_gt(A, B) -> + Res = eq_exact_lt_gt_1(A, B), + Res = eq_exact_lt_gt_2(A, B), + Res = eq_exact_lt_gt_3(A, B), + Res = eq_exact_lt_gt_4(A, B), + Res = eq_exact_lt_gt_5(A, B), + Res = eq_exact_lt_gt_6(A, B). + +%% Not possible to optimize (unless we have type information so we +%% know that A == B and A =:= B produces the same result). + +eq_exact_lt_gt_1(A, B) -> + if + A < B -> lt; + A =:= B -> eq; + A > B -> gt; + true -> none + end. + +eq_exact_lt_gt_2(A, B) -> + if + A > B -> gt; + A =:= B -> eq; + A < B -> lt; + true -> none + end. + +eq_exact_lt_gt_3(A, B) -> + if + A =:= B -> eq; + A < B -> lt; + A > B -> gt; + true -> none + end. + +eq_exact_lt_gt_4(A, B) -> + if + A =:= B -> eq; + A > B -> gt; + A < B -> lt; + true -> none + end. + +eq_exact_lt_gt_5(A, B) -> + if + A < B -> lt; + A > B -> gt; + A =:= B -> eq; + true -> none + end. + +eq_exact_lt_gt_6(A, B) -> + if + A > B -> gt; + A < B -> lt; + A =:= B -> eq; + true -> none + end. + %% Exhaustively test all combinations of relational operators %% to ensure the correctness of the optimizations in beam_ssa_dead. -- 2.31.1
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor