diff options
author | John Högberg <[email protected]> | 2018-05-08 08:52:22 +0200 |
---|---|---|
committer | John Högberg <[email protected]> | 2018-09-28 11:31:57 +0200 |
commit | fd6246c5191d07b80bc7100b470a37a338accecd (patch) | |
tree | 9cd0a6750aa0fa680fc214b6650006f34d2e3818 /lib/compiler/src/beam_bsm.erl | |
parent | b1726b022ad260497a1edc1cceb94935a06804e5 (diff) | |
download | otp-fd6246c5191d07b80bc7100b470a37a338accecd.tar.gz otp-fd6246c5191d07b80bc7100b470a37a338accecd.tar.bz2 otp-fd6246c5191d07b80bc7100b470a37a338accecd.zip |
Rewrite BSM optimizations in the new SSA-based intermediate format
This commit improves the bit-syntax match optimization pass,
leveraging the new SSA intermediate format to perform much more
aggressive optimizations. Some highlights:
* Watch contexts can be reused even after being passed to a
function or being used in a try block.
* Sub-binaries are no longer eagerly extracted, making it far
easier to keep "happy paths" free from binary creation.
* Trivial wrapper functions no longer disable context reuse.
Diffstat (limited to 'lib/compiler/src/beam_bsm.erl')
-rw-r--r-- | lib/compiler/src/beam_bsm.erl | 719 |
1 files changed, 0 insertions, 719 deletions
diff --git a/lib/compiler/src/beam_bsm.erl b/lib/compiler/src/beam_bsm.erl deleted file mode 100644 index abc6e96c85..0000000000 --- a/lib/compiler/src/beam_bsm.erl +++ /dev/null @@ -1,719 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2018. 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. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% - --module(beam_bsm). --export([module/2,format_error/1]). - --import(lists, [member/2,foldl/3,reverse/1,sort/1,all/2]). - -%%% -%%% We optimize bit syntax matching where the tail end of a binary is -%%% matched out and immediately passed on to a bs_start_match2 instruction, -%%% such as in this code sequence: -%%% -%%% func_info ... -%%% L1 test bs_start_match2 {f,...} {x,0} Live SavePositions {x,0} -%%% . . . -%%% test bs_get_binary2 {f,...} {x,0} all 1 Flags {x,0} -%%% . . . -%%% call_only 2 L1 -%%% -%%% The sequence can be optimized simply by removing the bs_get_binary2 -%%% instruction. Another example: -%%% -%%% func_info ... -%%% L1 test bs_start_match2 {f,...} {x,0} Live SavePositions {x,0} -%%% . . . -%%% test bs_get_binary2 {f,...} {x,0} all 8 Flags {x,1} -%%% . . . -%%% move {x,1} {x,0} -%%% call_only 2 L1 -%%% -%%% In this case, the bs_get_binary2 instruction must be replaced by -%%% -%%% test bs_unit {x,1} 8 -%%% -%%% to ensure that the match fail if the length of the binary in bits -%%% is not evenly divisible by 8. -%%% -%%% Note that the bs_start_match2 instruction doesn't need to be in the same -%%% function as the caller. It can be in the beginning of any function, or -%%% follow the bs_get_binary2 instruction in the same function. The important -%%% thing is that the match context register is not copied or built into -%%% data structures or passed to BIFs. -%%% - --type label() :: beam_asm:label(). --type func_info() :: {beam_asm:reg(),boolean()}. - --record(btb, - {f :: gb_trees:tree(label(), func_info()), - index :: beam_utils:code_index(), %{Label,Code} index (for liveness). - ok_br=gb_sets:empty() :: gb_sets:set(label()), %Labels that are OK. - must_not_save=false :: boolean(), %Must not save position when - % optimizing (reaches - % bs_context_to_binary). - must_save=false :: boolean() %Must save position when optimizing. - }). - - --spec module(beam_utils:module_code(), [compile:option()]) -> - {'ok',beam_utils:module_code()}. - -module({Mod,Exp,Attr,Fs0,Lc}, Opts) -> - FIndex = btb_index(Fs0), - Fs = [function(F, FIndex) || F <- Fs0], - Code = {Mod,Exp,Attr,Fs,Lc}, - case proplists:get_bool(bin_opt_info, Opts) of - true -> - {ok,Code,collect_warnings(Fs)}; - false -> - {ok,Code} - end. - --spec format_error('bin_opt' | {'no_bin_opt', term()}) -> nonempty_string(). - -format_error(bin_opt) -> - "OPTIMIZED: creation of sub binary delayed"; -format_error({no_bin_opt,Reason}) -> - lists:flatten(["NOT OPTIMIZED: "|format_error_1(Reason)]). - -%%% -%%% Local functions. -%%% - -function({function,Name,Arity,Entry,Is}, FIndex) -> - try - Index = beam_utils:index_labels(Is), - D = #btb{f=FIndex,index=Index}, - {function,Name,Arity,Entry,btb_opt_1(Is, D, [])} - catch - Class:Error:Stack -> - io:fwrite("Function: ~w/~w\n", [Name,Arity]), - erlang:raise(Class, Error, Stack) - end. - -btb_opt_1([{test,bs_get_binary2,F,_,[Reg,{atom,all},U,Fs],Reg}=I0|Is], D, Acc0) -> - case btb_reaches_match(Is, [Reg], D) of - {error,Reason} -> - Comment = btb_comment_no_opt(Reason, Fs), - btb_opt_1(Is, D, [Comment,I0|Acc0]); - {ok,MustSave} -> - Comment = btb_comment_opt(Fs), - Acc1 = btb_gen_save(MustSave, Reg, [Comment|Acc0]), - Acc = case U of - 1 -> Acc1; - _ -> [{test,bs_test_unit,F,[Reg,U]}|Acc1] - end, - btb_opt_1(Is, D, Acc) - end; -btb_opt_1([{test,bs_get_binary2,F,_,[Ctx,{atom,all},U,Fs],Dst}=I0|Is0], D, Acc0) -> - case btb_reaches_match(Is0, [Ctx,Dst], D) of - {error,Reason} -> - Comment = btb_comment_no_opt(Reason, Fs), - btb_opt_1(Is0, D, [Comment,I0|Acc0]); - {ok,MustSave} when U =:= 1 -> - Comment = btb_comment_opt(Fs), - Acc = btb_gen_save(MustSave, Ctx, [Comment|Acc0]), - Is = prepend_move(Ctx, Dst, Is0), - btb_opt_1(Is, D, Acc); - {ok,MustSave} -> - Comment = btb_comment_opt(Fs), - Acc1 = btb_gen_save(MustSave, Ctx, [Comment|Acc0]), - Acc = [{test,bs_test_unit,F,[Ctx,U]}|Acc1], - Is = prepend_move(Ctx, Dst, Is0), - btb_opt_1(Is, D, Acc) - end; -btb_opt_1([I|Is], D, Acc) -> - %%io:format("~p\n", [I]), - btb_opt_1(Is, D, [I|Acc]); -btb_opt_1([], _, Acc) -> - reverse(Acc). - -btb_gen_save(true, Reg, Acc) -> - [{bs_save2,Reg,{atom,start}}|Acc]; -btb_gen_save(false, _, Acc) -> Acc. - -prepend_move(Ctx, Dst, [{block,Bl0}|Is]) -> - Bl = [{set,[Dst],[Ctx],move}|Bl0], - [{block,Bl}|Is]; -prepend_move(Ctx, Dst, Is) -> - [{move,Ctx,Dst}|Is]. - -%% btb_reaches_match([Instruction], [Register], D) -> -%% {ok,MustSave}|{error,Reason} -%% -%% The list of Registers should be a list of registers referencing a -%% match context. The Register may contain one element if the -%% bs_get_binary2 instruction looks like -%% -%% test bs_get_binary2 {f,...} Ctx all _ _ Ctx -%% -%% or two elements if the instruction looks like -%% -%% test bs_get_binary2 {f,...} Ctx all _ _ Dst -%% -%% This function determines whether the bs_get_binary2 instruction -%% can be omitted (retaining the match context instead of creating -%% a sub binary). -%% -%% The rule is that the match context ultimately must end up at a -%% bs_start_match2 instruction and nowhere else. That it, it must not -%% be passed to BIFs, or copied or put into data structures. There -%% must only be one copy alive when the match context reaches the -%% bs_start_match2 instruction. -%% -%% At a branch, we must follow all branches and make sure that the above -%% rule is followed (or that the branch kills the match context). -%% -%% The MustSave return value will be true if control may end up at -%% bs_context_to_binary instruction. Since that instruction uses the -%% saved start position, we must use "bs_save2 Ctx start" to -%% update the saved start position. An additional complication is that -%% "bs_save2 Ctx start" must not be used if Dst and Ctx are -%% different registers and both registers may be passed to -%% a bs_context_to_binary instruction. -%% - -btb_reaches_match(Is, RegList, D) -> - try - Regs = btb_regs_from_list(RegList), - #btb{must_not_save=MustNotSave,must_save=MustSave} = - btb_reaches_match_1(Is, Regs, D), - case MustNotSave andalso MustSave of - true -> btb_error(must_and_must_not_save); - false -> {ok,MustSave} - end - catch - throw:{error,_}=Error -> Error - end. - -btb_reaches_match_1(Is, Regs, D) -> - case btb_are_registers_empty(Regs) of - false -> - btb_reaches_match_2(Is, Regs, D); - true -> - %% The context was killed, which is OK. - D - end. - -btb_reaches_match_2([{block,Bl}|Is], Regs0, D) -> - Regs = btb_reaches_match_block(Bl, Regs0), - btb_reaches_match_1(Is, Regs, D); -btb_reaches_match_2([{call,Arity,{f,Lbl}}|Is], Regs0, D) -> - case is_tail_call(Is) of - true -> - Regs1 = btb_kill_not_live(Arity, Regs0), - Regs = btb_kill_yregs(Regs1), - btb_tail_call(Lbl, Regs, D); - false -> - btb_call(Arity, Lbl, Regs0, Is, D) - end; -btb_reaches_match_2([{apply,Arity}|Is], Regs, D) -> - btb_call(Arity+2, apply, Regs, Is, D); -btb_reaches_match_2([{call_fun,Live}=I|Is], Regs, D) -> - btb_ensure_not_used([{x,Live}], I, Regs), - btb_call(Live, I, Regs, Is, D); -btb_reaches_match_2([{make_fun2,_,_,_,Live}|Is], Regs, D) -> - btb_call(Live, make_fun2, Regs, Is, D); -btb_reaches_match_2([{call_ext,Arity,Func}=I|Is], Regs0, D) -> - %% Allow us scanning beyond the call in case the match - %% context is saved on the stack. - case beam_jump:is_exit_instruction(I) of - false -> - btb_call(Arity, Func, Regs0, Is, D); - true -> - Regs = btb_kill_not_live(Arity, Regs0), - btb_tail_call(Func, Regs, D) - end; -btb_reaches_match_2([{kill,Y}|Is], Regs, D) -> - btb_reaches_match_1(Is, btb_kill([Y], Regs), D); -btb_reaches_match_2([{deallocate,_}|Is], Regs0, D) -> - Regs = btb_kill_yregs(Regs0), - btb_reaches_match_1(Is, Regs, D); -btb_reaches_match_2([return=I|_], Regs0, D) -> - btb_ensure_not_used([{x,0}], I, Regs0), - D; -btb_reaches_match_2([{gc_bif,_,{f,F},Live,Ss,Dst}=I|Is], Regs0, D0) -> - btb_ensure_not_used(Ss, I, Regs0), - Regs1 = btb_kill_not_live(Live, Regs0), - Regs = btb_kill([Dst], Regs1), - D = btb_follow_branch(F, Regs, D0), - btb_reaches_match_1(Is, Regs, D); -btb_reaches_match_2([{bif,_,{f,F},Ss,Dst}=I|Is], Regs0, D0) -> - btb_ensure_not_used(Ss, I, Regs0), - Regs = btb_kill([Dst], Regs0), - D = btb_follow_branch(F, Regs, D0), - btb_reaches_match_1(Is, Regs, D); -btb_reaches_match_2([{get_map_elements,{f,F},Src,{list,Ls}}=I|Is], Regs0, D0) -> - {Ss,Ds} = beam_utils:split_even(Ls), - btb_ensure_not_used([Src|Ss], I, Regs0), - Regs = btb_kill(Ds, Regs0), - D = btb_follow_branch(F, Regs, D0), - btb_reaches_match_1(Is, Regs, D); -btb_reaches_match_2([{test,bs_start_match2,{f,F},Live,[Ctx,_],Ctx}=I|Is], - Regs0, D0) -> - CtxRegs = btb_context_regs(Regs0), - case member(Ctx, CtxRegs) of - false -> - %% This bs_start_match2 instruction does not use "our" - %% match state. Therefore we can continue the search - %% for another bs_start_match2 instruction. - D = btb_follow_branch(F, Regs0, D0), - Regs = btb_kill_not_live(Live, Regs0), - btb_reaches_match_2(Is, Regs, D); - true -> - %% OK. This instruction will use "our" match state, - %% but we must make sure that all other copies of the - %% match state are killed in the code that follows - %% the instruction. (We know that the fail branch cannot - %% be taken in this case.) - OtherCtxRegs = CtxRegs -- [Ctx], - case btb_are_all_unused(OtherCtxRegs, Is, D0) of - false -> btb_error({OtherCtxRegs,not_all_unused_after,I}); - true -> D0 - end - end; -btb_reaches_match_2([{test,bs_start_match2,{f,F},Live,[Bin,_],Ctx}|Is], - Regs0, D0) -> - CtxRegs = btb_context_regs(Regs0), - case member(Bin, CtxRegs) orelse member(Ctx, CtxRegs) of - false -> - %% This bs_start_match2 does not reference any copy of the - %% match state. Therefore it can safely be passed on the - %% way to another (perhaps more suitable) bs_start_match2 - %% instruction. - D = btb_follow_branch(F, Regs0, D0), - Regs = btb_kill_not_live(Live, Regs0), - btb_reaches_match_2(Is, Regs, D); - true -> - %% This variant of the bs_start_match2 instruction does - %% not accept a match state as source. - btb_error(unsuitable_bs_start_match) - end; -btb_reaches_match_2([{test,_,{f,F},Ss}=I|Is], Regs, D0) -> - btb_ensure_not_used(Ss, I, Regs), - D1 = btb_follow_branch(F, Regs, D0), - D = case Is of - [{bs_context_to_binary,_}|_] -> - %% bs_context_to_binary following a test instruction - %% probably needs the current position to be saved as - %% the new start position, but we can't be sure. - %% Therefore, conservatively disable the optimization - %% (instead of forcing a saving of the position). - D1#btb{must_save=true,must_not_save=true}; - _ -> - D1 - end, - btb_reaches_match_1(Is, Regs, D); -btb_reaches_match_2([{test,_,{f,F},_,Ss,_}=I|Is], Regs, D0) -> - btb_ensure_not_used(Ss, I, Regs), - D = btb_follow_branch(F, Regs, D0), - btb_reaches_match_1(Is, Regs, D); -btb_reaches_match_2([{select,_,Src,{f,F},Conds}=I|Is], Regs, D0) -> - btb_ensure_not_used([Src], I, Regs), - D1 = btb_follow_branch(F, Regs, D0), - D = btb_follow_branches(Conds, Regs, D1), - btb_reaches_match_1(Is, Regs, D); -btb_reaches_match_2([{jump,{f,Lbl}}|_], Regs, #btb{index=Li}=D) -> - Is = fetch_code_at(Lbl, Li), - btb_reaches_match_2(Is, Regs, D); -btb_reaches_match_2([{label,_}|Is], Regs, D) -> - btb_reaches_match_2(Is, Regs, D); -btb_reaches_match_2([{bs_init,{f,0},_,_,Ss,Dst}=I|Is], Regs, D) -> - btb_ensure_not_used(Ss, I, Regs), - btb_reaches_match_1(Is, btb_kill([Dst], Regs), D); -btb_reaches_match_2([{bs_put,{f,0},_,Ss}=I|Is], Regs, D) -> - btb_ensure_not_used(Ss, I, Regs), - btb_reaches_match_1(Is, Regs, D); -btb_reaches_match_2([{bs_restore2,Src,_}=I|Is], Regs0, D) -> - case btb_contains_context(Src, Regs0) of - false -> - btb_reaches_match_1(Is, Regs0, D); - true -> - %% Check that all other copies of the context registers - %% are unused by the following instructions. - Regs = btb_kill([Src], Regs0), - CtxRegs = btb_context_regs(Regs), - case btb_are_all_unused(CtxRegs, Is, D) of - false -> btb_error({CtxRegs,not_all_unused_after,I}); - true -> D#btb{must_not_save=true} - end - end; -btb_reaches_match_2([{bs_context_to_binary,Src}=I|Is], Regs0, D) -> - case btb_contains_context(Src, Regs0) of - false -> - btb_reaches_match_1(Is, Regs0, D); - true -> - %% Check that all other copies of the context registers - %% are unused by the following instructions. - Regs = btb_kill([Src], Regs0), - CtxRegs = btb_context_regs(Regs), - case btb_are_all_unused(CtxRegs, Is, D) of - false -> btb_error({CtxRegs,not_all_unused_after,I}); - true -> D#btb{must_not_save=true} - end - end; -btb_reaches_match_2([{badmatch,Src}=I|_], Regs, D) -> - btb_ensure_not_used([Src], I, Regs), - D; -btb_reaches_match_2([{case_end,Src}=I|_], Regs, D) -> - btb_ensure_not_used([Src], I, Regs), - D; -btb_reaches_match_2([if_end|_], _Regs, D) -> - D; -btb_reaches_match_2([{func_info,_,_,Arity}=I|_], Regs0, D) -> - Regs = btb_kill_yregs(btb_kill_not_live(Arity, Regs0)), - case btb_context_regs(Regs) of - [] -> D; - _ -> {binary_used_in,I} - end; -btb_reaches_match_2([{line,_}|Is], Regs, D) -> - btb_reaches_match_1(Is, Regs, D); -btb_reaches_match_2([I|_], Regs, _) -> - btb_error({btb_context_regs(Regs),I,not_handled}). - -is_tail_call([{deallocate,_}|_]) -> true; -is_tail_call([return|_]) -> true; -is_tail_call(_) -> false. - -btb_call(Arity, Lbl, Regs0, Is, D0) -> - Regs = btb_kill_not_live(Arity, Regs0), - case btb_are_x_registers_empty(Regs) of - false -> - %% There is a match context in one of the x registers. - %% First handle the call as if it were a tail call. - D = btb_tail_call(Lbl, Regs, D0), - - %% No problem so far (the called function can handle a - %% match context). Now we must make sure that we don't - %% have any copies of the match context tucked away in an - %% y register. - RegList = btb_context_regs(Regs), - case [R || {y,_}=R <- RegList] of - [] -> - D; - [_|_] -> - btb_error({multiple_uses,RegList}) - end; - true -> - %% No match context in any x register. It could have been - %% saved to an y register, so continue to scan the code following - %% the call. - btb_reaches_match_1(Is, Regs, D0) - end. - -btb_tail_call(Lbl, Regs, #btb{f=Ftree,must_save=MustSave0}=D) -> - %% Ignore any y registers here. - case [R || {x,_}=R <- btb_context_regs(Regs)] of - [] -> - D; - [{x,_}=Reg] -> - case gb_trees:lookup(Lbl, Ftree) of - {value,{Reg,MustSave}} -> - D#btb{must_save=MustSave0 or MustSave}; - _ when is_integer(Lbl) -> - btb_error({{label,Lbl},no_suitable_bs_start_match}); - _ -> - btb_error({binary_used_in,Lbl}) - end; - [_|_] when not is_integer(Lbl) -> - btb_error({binary_used_in,Lbl}); - [_|_]=RegList -> - btb_error({multiple_uses,RegList}) - end. - -%% btb_follow_branches([Cond], Regs, D) -> D' -%% Recursively follow all the branches. - -btb_follow_branches([{f,Lbl}|T], Regs, D0) -> - D = btb_follow_branch(Lbl, Regs, D0), - btb_follow_branches(T, Regs, D); -btb_follow_branches([_|T], Regs, D) -> - btb_follow_branches(T, Regs, D); -btb_follow_branches([], _, D) -> D. - -%% btb_follow_branch(Lbl, Regs, D) -> D' -%% Recursively follow the branch. - -btb_follow_branch(0, _Regs, D) -> D; -btb_follow_branch(Lbl, Regs, #btb{ok_br=Br0,index=Li}=D) -> - Key = {Lbl,Regs}, - case gb_sets:is_member(Key, Br0) of - true -> - %% We have already followed this branch and it was OK. - D; - false -> - %% New branch. Try it. - Is = fetch_code_at(Lbl, Li), - #btb{ok_br=Br,must_not_save=MustNotSave,must_save=MustSave} = - btb_reaches_match_1(Is, Regs, D), - - %% Since we got back, this branch is OK. - D#btb{ok_br=gb_sets:insert(Key, Br),must_not_save=MustNotSave, - must_save=MustSave} - end. - -btb_reaches_match_block([{set,Ds,Ss,{alloc,Live,_}}=I|Is], Regs0) -> - %% An allocation instruction or a GC bif. We'll kill all registers - %% if any copy of the context is used as the source to the BIF. - btb_ensure_not_used(Ss, I, Regs0), - Regs1 = btb_kill_not_live(Live, Regs0), - Regs = btb_kill(Ds, Regs1), - btb_reaches_match_block(Is, Regs); -btb_reaches_match_block([{set,[Dst]=Ds,[Src],move}|Is], Regs0) -> - Regs1 = btb_kill(Ds, Regs0), - Regs = case btb_contains_context(Src, Regs1) of - false -> Regs1; - true -> btb_set_context(Dst, Regs1) - end, - btb_reaches_match_block(Is, Regs); -btb_reaches_match_block([{set,Ds,Ss,_}=I|Is], Regs0) -> - btb_ensure_not_used(Ss, I, Regs0), - Regs = btb_kill(Ds, Regs0), - btb_reaches_match_block(Is, Regs); -btb_reaches_match_block([], Regs) -> - Regs. - -%% btb_are_all_killed([Register], [Instruction], D) -> true|false -%% Test whether all of the register are unused in the instruction stream. - -btb_are_all_unused(RegList, Is, #btb{index=Li}) -> - all(fun(R) -> - beam_utils:is_not_used(R, Is, Li) - end, RegList). - -%% btp_regs_from_list([Register]) -> RegisterSet. -%% Create a register set from a list of registers. - -btb_regs_from_list(L) -> - foldl(fun(R, Regs) -> - btb_set_context(R, Regs) - end, {0,0}, L). - -%% btb_set_context(Register, RegisterSet) -> RegisterSet' -%% Update RegisterSet to indicate that Register contains the matching context. - -btb_set_context({x,N}, {Xregs,Yregs}) -> - {Xregs bor (1 bsl N),Yregs}; -btb_set_context({y,N}, {Xregs,Yregs}) -> - {Xregs,Yregs bor (1 bsl N)}. - -%% btb_ensure_not_used([Register], Instruction, RegisterSet) -> ok -%% If any register in RegisterSet (the register(s) known to contain -%% the match context) is used in the list of registers, generate an error. - -btb_ensure_not_used(Rs, I, Regs) -> - case lists:any(fun(R) -> btb_contains_context(R, Regs) end, Rs) of - true -> btb_error({binary_used_in,I}); - false -> ok - end. - -%% btb_kill([Register], RegisterSet) -> RegisterSet' -%% Kill all registers mentioned in the list of registers. - -btb_kill([{x,N}|Rs], {Xregs,Yregs}) -> - btb_kill(Rs, {Xregs band (bnot (1 bsl N)),Yregs}); -btb_kill([{y,N}|Rs], {Xregs,Yregs}) -> - btb_kill(Rs, {Xregs,Yregs band (bnot (1 bsl N))}); -btb_kill([{fr,_}|Rs], Regs) -> - btb_kill(Rs, Regs); -btb_kill([], Regs) -> Regs. - -%% btb_kill_not_live(Live, RegisterSet) -> RegisterSet' -%% Kill all registers indicated not live by Live. - -btb_kill_not_live(Live, {Xregs,Yregs}) -> - {Xregs band ((1 bsl Live)-1),Yregs}. - -%% btb_kill(Regs0) -> Regs -%% Kill all y registers. - -btb_kill_yregs({Xregs,_}) -> {Xregs,0}. - -%% btb_are_registers_empty(RegisterSet) -> true|false -%% Test whether the register set is empty. - -btb_are_registers_empty({0,0}) -> true; -btb_are_registers_empty({_,_}) -> false. - -%% btb_are_x_registers_empty(Regs) -> true|false -%% Test whether the x registers are empty. - -btb_are_x_registers_empty({0,_}) -> true; -btb_are_x_registers_empty({_,_}) -> false. - -%% btb_contains_context(Register, RegisterSet) -> true|false -%% Test whether Register contains the context. - -btb_contains_context({x,N}, {Regs,_}) -> Regs band (1 bsl N) =/= 0; -btb_contains_context({y,N}, {_,Regs}) -> Regs band (1 bsl N) =/= 0; -btb_contains_context(_, _) -> false. - -%% btb_context_regs(RegisterSet) -> [Register] -%% Convert the register set to an explicit list of registers. -btb_context_regs({Xregs,Yregs}) -> - btb_context_regs_1(Xregs, 0, x, btb_context_regs_1(Yregs, 0, y, [])). - -btb_context_regs_1(0, _, _, Acc) -> - Acc; -btb_context_regs_1(Regs, N, Tag, Acc) when (Regs band 1) =:= 1 -> - btb_context_regs_1(Regs bsr 1, N+1, Tag, [{Tag,N}|Acc]); -btb_context_regs_1(Regs, N, Tag, Acc) -> - btb_context_regs_1(Regs bsr 1, N+1, Tag, Acc). - -%% btb_index([Function]) -> GbTree({EntryLabel,{Register,MustSave}}) -%% Build an index of functions that accept a match context instead of -%% a binary. MustSave is true if the function may pass the match -%% context to the bs_context_to_binary instruction (in which case -%% the current position in the binary must have saved into the -%% start position using "bs_save_2 Ctx start"). - -btb_index(Fs) -> - btb_index_1(Fs, []). - -btb_index_1([{function,_,_,Entry,Is0}|Fs], Acc0) -> - Is = drop_to_label(Is0, Entry), - Acc = btb_index_2(Is, Entry, false, Acc0), - btb_index_1(Fs, Acc); -btb_index_1([], Acc) -> gb_trees:from_orddict(sort(Acc)). - -btb_index_2([{test,bs_start_match2,{f,_},_,[Reg,_],Reg}|_], - Entry, MustSave, Acc) -> - [{Entry,{Reg,MustSave}}|Acc]; -btb_index_2(Is0, Entry, _, Acc) -> - try btb_index_find_start_match(Is0) of - Is -> btb_index_2(Is, Entry, true, Acc) - catch - throw:none -> Acc - end. - -drop_to_label([{label,L}|Is], L) -> Is; -drop_to_label([_|Is], L) -> drop_to_label(Is, L). - -btb_index_find_start_match([{test,_,{f,F},_},{bs_context_to_binary,_}|Is]) -> - btb_index_find_label(Is, F); -btb_index_find_start_match(_) -> - throw(none). - -btb_index_find_label([{label,L}|Is], L) -> Is; -btb_index_find_label([_|Is], L) -> btb_index_find_label(Is, L). - -btb_error(Error) -> - throw({error,Error}). - -fetch_code_at(Lbl, Li) -> - case beam_utils:code_at(Lbl, Li) of - Is when is_list(Is) -> Is - end. - -%%% -%%% Compilation information warnings. -%%% - -btb_comment_opt({field_flags,[{anno,A}|_]}) -> - {'%',{bin_opt,A}}; -btb_comment_opt(_) -> - {'%',{bin_opt,[]}}. - -btb_comment_no_opt(Reason, {field_flags,[{anno,A}|_]}) -> - {'%',{no_bin_opt,Reason,A}}; -btb_comment_no_opt(Reason, _) -> - {'%',{no_bin_opt,Reason,[]}}. - -collect_warnings(Fs) -> - D = warning_index_functions(Fs), - foldl(fun(F, A) -> collect_warnings_fun(F, D, A) end, [], Fs). - -collect_warnings_fun({function,_,_,_,Is}, D, A) -> - collect_warnings_instr(Is, D, A). - -collect_warnings_instr([{'%',{bin_opt,Where}}|Is], D, Acc0) -> - Acc = add_warning(bin_opt, Where, Acc0), - collect_warnings_instr(Is, D, Acc); -collect_warnings_instr([{'%',{no_bin_opt,Reason0,Where}}|Is], D, Acc0) -> - Reason = warning_translate_label(Reason0, D), - Acc = add_warning({no_bin_opt,Reason}, Where, Acc0), - collect_warnings_instr(Is, D, Acc); -collect_warnings_instr([_|Is], D, Acc) -> - collect_warnings_instr(Is, D, Acc); -collect_warnings_instr([], _, Acc) -> Acc. - -add_warning(Term, Anno, Ws) -> - Line = get_line(Anno), - File = get_file(Anno), - [{File,[{Line,?MODULE,Term}]}|Ws]. - -warning_translate_label(Term, D) when is_tuple(Term) -> - case element(1, Term) of - {label,F} -> - FA = gb_trees:get(F, D), - setelement(1, Term, FA); - _ -> Term - end; -warning_translate_label(Term, _) -> Term. - -get_line([Line|_]) when is_integer(Line) -> Line; -get_line([_|T]) -> get_line(T); -get_line([]) -> none. - -get_file([{file,File}|_]) -> File; -get_file([_|T]) -> get_file(T); -get_file([]) -> "no_file". % should not happen - -warning_index_functions(Fs) -> - D = [{Entry,{F,A}} || {function,F,A,Entry,_} <- Fs], - gb_trees:from_orddict(sort(D)). - -format_error_1({binary_used_in,{extfunc,M,F,A}}) -> - [io_lib:format("sub binary used by ~p:~p/~p", [M,F,A])| - case {M,F,A} of - {erlang,split_binary,2} -> - "; SUGGEST using binary matching instead of split_binary/2"; - _ -> - "" - end]; -format_error_1({binary_used_in,_}) -> - "sub binary is used or returned"; -format_error_1({multiple_uses,_}) -> - "sub binary is matched or used in more than one place"; -format_error_1(unsuitable_bs_start_match) -> - "the binary matching instruction that follows in the same function " - "have problems that prevent delayed sub binary optimization " - "(probably indicated by INFO warnings)"; -format_error_1({{F,A},no_suitable_bs_start_match}) -> - io_lib:format("called function ~p/~p does not begin with a suitable " - "binary matching instruction", [F,A]); -format_error_1(must_and_must_not_save) -> - "different control paths use different positions in the binary"; -format_error_1({_,I,not_handled}) -> - case I of - {'catch',_,_} -> - "the compiler currently does not attempt the delayed sub binary " - "optimization when catch is used"; - {'try',_,_} -> - "the compiler currently does not attempt the delayed sub binary " - "optimization when try/catch is used"; - _ -> - io_lib:format("compiler limitation: instruction ~p prevents " - "delayed sub binary optimization", [I]) - end; -format_error_1(Term) -> - io_lib:format("~w", [Term]). |