From 09112806c15a81be86730503af36e304ac11d1ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Wed, 7 Jun 2017 14:27:15 +0200 Subject: Fix unsafe bit syntax matching optimization As part of sys_core_fold, variables involved in bit syntax matching would be annotated when it would be safe for a later pass to do the delayed sub-binary creation optimization. An implicit assumption regarding the annotation was that the code must not be further optimized. That assumption was broken in 05130e48555891, which introduced a fixpoint iteration (applying the optimizations until there were no more changes). That means that a variable could be annotated as safe for reusing the match context in one iteration, but a later iteration could rewrite the code in a way that would make the optimization unsafe. One way to fix this would be to clear all reuse_for_context annotations before each iteration. But that would be wasteful. Instead I chose to fix the problem by moving out the annotation code to a separate pass (sys_core_bsm) that is run later after all major optimizations of Core Erlang has been done. --- lib/compiler/src/Makefile | 1 + lib/compiler/src/compile.erl | 7 +- lib/compiler/src/compiler.app.src | 1 + lib/compiler/src/sys_core_bsm.erl | 355 +++++++++++++++++++++++++++++++++++ lib/compiler/src/sys_core_fold.erl | 297 +---------------------------- lib/compiler/test/bs_match_SUITE.erl | 19 +- lib/compiler/test/compile_SUITE.erl | 1 + lib/compiler/test/misc_SUITE.erl | 3 +- lib/compiler/test/warnings_SUITE.erl | 2 +- 9 files changed, 389 insertions(+), 297 deletions(-) create mode 100644 lib/compiler/src/sys_core_bsm.erl diff --git a/lib/compiler/src/Makefile b/lib/compiler/src/Makefile index 59b80ade5d..f06b8b9ec3 100644 --- a/lib/compiler/src/Makefile +++ b/lib/compiler/src/Makefile @@ -83,6 +83,7 @@ MODULES = \ core_scan \ erl_bifs \ rec_env \ + sys_core_bsm \ sys_core_dsetel \ sys_core_fold \ sys_core_fold_lists \ diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index c6e61d543e..d05b8a9530 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -718,8 +718,10 @@ core_passes() -> | kernel_passes()]. kernel_passes() -> - %% Destructive setelement/3 optimization and core lint. - [{pass,sys_core_dsetel}, + %% Optimizations that must be done after all other optimizations. + [{pass,sys_core_bsm}, + {iff,dcbsm,{listing,"core_bsm"}}, + {pass,sys_core_dsetel}, {iff,dsetel,{listing,"dsetel"}}, {iff,clint,?pass(core_lint_module)}, @@ -1919,6 +1921,7 @@ pre_load() -> erl_lint, erl_parse, erl_scan, + sys_core_bsm, sys_core_dsetel, sys_core_fold, v3_codegen, diff --git a/lib/compiler/src/compiler.app.src b/lib/compiler/src/compiler.app.src index 3961b2af86..d4b4d4da04 100644 --- a/lib/compiler/src/compiler.app.src +++ b/lib/compiler/src/compiler.app.src @@ -58,6 +58,7 @@ core_lib, erl_bifs, rec_env, + sys_core_bsm, sys_core_dsetel, sys_core_fold, sys_core_fold_lists, diff --git a/lib/compiler/src/sys_core_bsm.erl b/lib/compiler/src/sys_core_bsm.erl new file mode 100644 index 0000000000..3e04cc33df --- /dev/null +++ b/lib/compiler/src/sys_core_bsm.erl @@ -0,0 +1,355 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2017. 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% +%% +%% Purpose : Optimize bit syntax matching. + + +-module(sys_core_bsm). +-export([module/2,format_error/1]). + +-include("core_parse.hrl"). +-import(lists, [member/2,nth/2,reverse/1,usort/1]). + +-spec module(cerl:c_module(), [compile:option()]) -> {'ok', cerl:c_module()}. + +module(#c_module{defs=Ds0}=Mod, Opts) -> + {Ds,Ws0} = function(Ds0, [], []), + case member(bin_opt_info, Opts) of + false -> + {ok,Mod#c_module{defs=Ds}}; + true -> + Ws1 = [make_warning(Where, What) || {Where,What} <- Ws0], + Ws = usort(Ws1), + {ok,Mod#c_module{defs=Ds},Ws} + end. + +function([{#c_var{name={F,Arity}}=Name,B0}|Fs], FsAcc, Ws0) -> + try cerl_trees:mapfold(fun bsm_an/2, Ws0, B0) of + {B,Ws} -> + function(Fs, [{Name,B}|FsAcc], Ws) + catch + Class:Error -> + Stack = erlang:get_stacktrace(), + io:fwrite("Function: ~w/~w\n", [F,Arity]), + erlang:raise(Class, Error, Stack) + end; +function([], Fs, Ws) -> + {reverse(Fs),Ws}. + +-type error() :: atom(). +-spec format_error(error()) -> nonempty_string(). + +format_error(bin_opt_alias) -> + "INFO: the '=' operator will prevent delayed sub binary optimization"; +format_error(bin_partition) -> + "INFO: matching non-variables after a previous clause matching a variable " + "will prevent delayed sub binary optimization"; +format_error(bin_left_var_used_in_guard) -> + "INFO: a variable to the left of the binary pattern is used in a guard; " + "will prevent delayed sub binary optimization"; +format_error(bin_argument_order) -> + "INFO: matching anything else but a plain variable to the left of " + "binary pattern will prevent delayed sub binary optimization; " + "SUGGEST changing argument order"; +format_error(bin_var_used) -> + "INFO: using a matched out sub binary will prevent " + "delayed sub binary optimization"; +format_error(orig_bin_var_used_in_guard) -> + "INFO: using the original binary variable in a guard will prevent " + "delayed sub binary optimization"; +format_error(bin_var_used_in_guard) -> + "INFO: using a matched out sub binary in a guard will prevent " + "delayed sub binary optimization". + + +%%% +%%% Annotate bit syntax matching to faciliate optimization in further passes. +%%% + +bsm_an(Core0, Ws0) -> + case bsm_an(Core0) of + {ok,Core} -> + {Core,Ws0}; + {ok,Core,W} -> + {Core,[W|Ws0]} + end. + +bsm_an(#c_case{arg=#c_var{}=V}=Case) -> + bsm_an_1([V], Case); +bsm_an(#c_case{arg=#c_values{es=Es}}=Case) -> + bsm_an_1(Es, Case); +bsm_an(Other) -> + {ok,Other}. + +bsm_an_1(Vs, #c_case{clauses=Cs}=Case) -> + case bsm_leftmost(Cs) of + none -> {ok,Case}; + Pos -> bsm_an_2(Vs, Cs, Case, Pos) + end. + +bsm_an_2(Vs, Cs, Case, Pos) -> + case bsm_nonempty(Cs, Pos) of + true -> bsm_an_3(Vs, Cs, Case, Pos); + false -> {ok,Case} + end. + +bsm_an_3(Vs, Cs, Case, Pos) -> + try + bsm_ensure_no_partition(Cs, Pos), + {ok,bsm_do_an(Vs, Pos, Cs, Case)} + catch + throw:{problem,Where,What} -> + {ok,Case,{Where,What}} + end. + +bsm_do_an(Vs0, Pos, Cs0, Case) -> + case nth(Pos, Vs0) of + #c_var{name=Vname}=V0 -> + Cs = bsm_do_an_var(Vname, Pos, Cs0, []), + V = bsm_annotate_for_reuse(V0), + Bef = lists:sublist(Vs0, Pos-1), + Aft = lists:nthtail(Pos, Vs0), + case Bef ++ [V|Aft] of + [_] -> + Case#c_case{arg=V,clauses=Cs}; + Vs -> + Case#c_case{arg=#c_values{es=Vs},clauses=Cs} + end; + _ -> + Case + end. + +bsm_do_an_var(V, S, [#c_clause{pats=Ps,guard=G,body=B0}=C0|Cs], Acc) -> + case nth(S, Ps) of + #c_var{name=VarName} -> + case core_lib:is_var_used(V, G) of + true -> bsm_problem(C0, orig_bin_var_used_in_guard); + false -> ok + end, + case core_lib:is_var_used(VarName, G) of + true -> bsm_problem(C0, bin_var_used_in_guard); + false -> ok + end, + B1 = bsm_maybe_ctx_to_binary(VarName, B0), + B = bsm_maybe_ctx_to_binary(V, B1), + C = C0#c_clause{body=B}, + bsm_do_an_var(V, S, Cs, [C|Acc]); + #c_alias{}=P -> + case bsm_could_match_binary(P) of + false -> + bsm_do_an_var(V, S, Cs, [C0|Acc]); + true -> + bsm_problem(C0, bin_opt_alias) + end; + P -> + case bsm_could_match_binary(P) andalso bsm_is_var_used(V, G, B0) of + false -> + bsm_do_an_var(V, S, Cs, [C0|Acc]); + true -> + bsm_problem(C0, bin_var_used) + end + end; +bsm_do_an_var(_, _, [], Acc) -> reverse(Acc). + +bsm_annotate_for_reuse(#c_var{anno=Anno}=Var) -> + Var#c_var{anno=[reuse_for_context|Anno]}. + +bsm_is_var_used(V, G, B) -> + core_lib:is_var_used(V, G) orelse core_lib:is_var_used(V, B). + +bsm_maybe_ctx_to_binary(V, B) -> + case core_lib:is_var_used(V, B) andalso not previous_ctx_to_binary(V, B) of + false -> + B; + true -> + #c_seq{arg=#c_primop{name=#c_literal{val=bs_context_to_binary}, + args=[#c_var{name=V}]}, + body=B} + end. + +previous_ctx_to_binary(V, Core) -> + case Core of + #c_seq{arg=#c_primop{name=#c_literal{val=bs_context_to_binary}, + args=[#c_var{name=V}]}} -> + true; + _ -> + false + end. + +%% bsm_leftmost(Cs) -> none | ArgumentNumber +%% Find the leftmost argument that does binary matching. Return +%% the number of the argument (1-N). + +bsm_leftmost(Cs) -> + bsm_leftmost_1(Cs, none). + +bsm_leftmost_1([#c_clause{pats=Ps}|Cs], Pos) -> + bsm_leftmost_2(Ps, Cs, 1, Pos); +bsm_leftmost_1([], Pos) -> Pos. + +bsm_leftmost_2(_, Cs, Pos, Pos) -> + bsm_leftmost_1(Cs, Pos); +bsm_leftmost_2([#c_binary{}|_], Cs, N, _) -> + bsm_leftmost_1(Cs, N); +bsm_leftmost_2([_|Ps], Cs, N, Pos) -> + bsm_leftmost_2(Ps, Cs, N+1, Pos); +bsm_leftmost_2([], Cs, _, Pos) -> + bsm_leftmost_1(Cs, Pos). + +%% bsm_nonempty(Cs, Pos) -> true|false +%% Check if at least one of the clauses matches a non-empty +%% binary in the given argument position. +%% +bsm_nonempty([#c_clause{pats=Ps}|Cs], Pos) -> + case nth(Pos, Ps) of + #c_binary{segments=[_|_]} -> + true; + _ -> + bsm_nonempty(Cs, Pos) + end; +bsm_nonempty([], _ ) -> false. + +%% bsm_ensure_no_partition(Cs, Pos) -> ok (exception if problem) +%% We must make sure that matching is not partitioned between +%% variables like this: +%% foo(<<...>>) -> ... +%% foo() when ... -> ... +%% foo() -> +%% If there is such partition, we are not allowed to reuse the binary variable +%% for the match context. +%% +%% Also, arguments to the left of the argument that is matched +%% against a binary, are only allowed to be simple variables, not +%% used in guards. The reason is that we must know that the binary is +%% only matched in one place (i.e. there must be only one bs_start_match2 +%% instruction emitted). + +bsm_ensure_no_partition(Cs, Pos) -> + bsm_ensure_no_partition_1(Cs, Pos, before). + +%% Loop through each clause. +bsm_ensure_no_partition_1([#c_clause{pats=Ps,guard=G}|Cs], Pos, State0) -> + State = bsm_ensure_no_partition_2(Ps, Pos, G, simple_vars, State0), + case State of + 'after' -> + bsm_ensure_no_partition_after(Cs, Pos); + _ -> + ok + end, + bsm_ensure_no_partition_1(Cs, Pos, State); +bsm_ensure_no_partition_1([], _, _) -> ok. + +%% Loop through each pattern for this clause. +bsm_ensure_no_partition_2([#c_binary{}=Where|_], 1, _, Vstate, State) -> + case State of + before when Vstate =:= simple_vars -> within; + before -> bsm_problem(Where, Vstate); + within when Vstate =:= simple_vars -> within; + within -> bsm_problem(Where, Vstate) + end; +bsm_ensure_no_partition_2([#c_alias{}=Alias|_], 1, N, Vstate, State) -> + %% Retrieve the real pattern that the alias refers to and check that. + P = bsm_real_pattern(Alias), + bsm_ensure_no_partition_2([P], 1, N, Vstate, State); +bsm_ensure_no_partition_2([_|_], 1, _, _Vstate, before=State) -> + %% No binary matching yet - therefore no partition. + State; +bsm_ensure_no_partition_2([P|_], 1, _, Vstate, State) -> + case bsm_could_match_binary(P) of + false -> + %% If clauses can be freely arranged (Vstate =:= simple_vars), + %% a clause that cannot match a binary will not partition the clause. + %% Example: + %% + %% a(Var, <<>>) -> ... + %% a(Var, []) -> ... + %% a(Var, <>) -> ... + %% + %% But if the clauses can't be freely rearranged, as in + %% + %% b(Var, <>) -> ... + %% b(1, 2) -> ... + %% + %% we do have a problem. + %% + case Vstate of + simple_vars -> State; + _ -> bsm_problem(P, Vstate) + end; + true -> + %% The pattern P *may* match a binary, so we must update the state. + %% (P must be a variable.) + case State of + within -> 'after'; + 'after' -> 'after' + end + end; +bsm_ensure_no_partition_2([#c_var{name=V}|Ps], N, G, Vstate, S) -> + case core_lib:is_var_used(V, G) of + false -> + bsm_ensure_no_partition_2(Ps, N-1, G, Vstate, S); + true -> + bsm_ensure_no_partition_2(Ps, N-1, G, bin_left_var_used_in_guard, S) + end; +bsm_ensure_no_partition_2([_|Ps], N, G, _, S) -> + bsm_ensure_no_partition_2(Ps, N-1, G, bin_argument_order, S). + +bsm_ensure_no_partition_after([#c_clause{pats=Ps}=C|Cs], Pos) -> + case nth(Pos, Ps) of + #c_var{} -> + bsm_ensure_no_partition_after(Cs, Pos); + _ -> + bsm_problem(C, bin_partition) + end; +bsm_ensure_no_partition_after([], _) -> ok. + +bsm_could_match_binary(#c_alias{pat=P}) -> bsm_could_match_binary(P); +bsm_could_match_binary(#c_cons{}) -> false; +bsm_could_match_binary(#c_tuple{}) -> false; +bsm_could_match_binary(#c_literal{val=Lit}) -> is_bitstring(Lit); +bsm_could_match_binary(_) -> true. + +bsm_real_pattern(#c_alias{pat=P}) -> bsm_real_pattern(P); +bsm_real_pattern(P) -> P. + +bsm_problem(Where, What) -> + throw({problem,Where,What}). + +make_warning(Core, Term) -> + case should_suppress_warning(Core) of + true -> + ok; + false -> + Anno = cerl:get_ann(Core), + Line = get_line(Anno), + File = get_file(Anno), + {File,[{Line,?MODULE,Term}]} + end. + +should_suppress_warning(Core) -> + Ann = cerl:get_ann(Core), + member(compiler_generated, Ann). + +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 diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl index 3673a339f6..cbf6e256f7 100644 --- a/lib/compiler/src/sys_core_fold.erl +++ b/lib/compiler/src/sys_core_fold.erl @@ -71,7 +71,7 @@ -export([module/2,format_error/1]). -import(lists, [map/2,foldl/3,foldr/3,mapfoldl/3,all/2,any/2, - reverse/1,reverse/2,member/2,nth/2,flatten/1, + reverse/1,reverse/2,member/2,flatten/1, unzip/1,keyfind/3]). -import(cerl, [ann_c_cons/3,ann_c_map/3,ann_c_tuple/2]). @@ -107,7 +107,6 @@ {'ok', cerl:c_module(), [_]}. module(#c_module{defs=Ds0}=Mod, Opts) -> - put(bin_opt_info, member(bin_opt_info, Opts)), put(no_inline_list_funcs, not member(inline_list_funcs, Opts)), case get(new_var_num) of undefined -> put(new_var_num, 0); @@ -116,7 +115,6 @@ module(#c_module{defs=Ds0}=Mod, Opts) -> init_warnings(), Ds1 = [function_1(D) || D <- Ds0], erase(no_inline_list_funcs), - erase(bin_opt_info), {ok,Mod#c_module{defs=Ds1},get_warnings()}. function_1({#c_var{name={F,Arity}}=Name,B0}) -> @@ -383,10 +381,8 @@ expr(#c_case{}=Case0, Ctxt, Sub) -> warn_no_clause_match(Case1, Case), Expr = eval_case(Case, Sub), case move_case_into_arg(Case, Sub) of - impossible -> - bsm_an(Expr); - Other -> - Other + impossible -> Expr; + Other -> Other end; Other -> expr(Other, Ctxt, Sub) @@ -2943,15 +2939,8 @@ update_types(Expr, Pat, #sub{t=Tdb0}=Sub) -> Tdb = update_types_1(Expr, Pat, Tdb0), Sub#sub{t=Tdb}. -update_types_1(#c_var{name=V,anno=Anno}, Pat, Types) -> - case member(reuse_for_context, Anno) of - true -> - %% If a variable has been marked for reuse of binary context, - %% optimizations based on type information are unsafe. - kill_types(V, Types); - false -> - update_types_2(V, Pat, Types) - end; +update_types_1(#c_var{name=V}, Pat, Types) -> + update_types_2(V, Pat, Types); update_types_1(_, _, Types) -> Types. update_types_2(V, [#c_tuple{}=P], Types) -> @@ -2994,253 +2983,6 @@ copy_type(_, _, Tdb) -> Tdb. void() -> #c_literal{val=ok}. -%%% -%%% Annotate bit syntax matching to faciliate optimization in further passes. -%%% - -bsm_an(#c_case{arg=#c_var{}=V}=Case) -> - bsm_an_1([V], Case); -bsm_an(#c_case{arg=#c_values{es=Es}}=Case) -> - bsm_an_1(Es, Case); -bsm_an(Other) -> Other. - -bsm_an_1(Vs, #c_case{clauses=Cs}=Case) -> - case bsm_leftmost(Cs) of - none -> Case; - Pos -> bsm_an_2(Vs, Cs, Case, Pos) - end. - -bsm_an_2(Vs, Cs, Case, Pos) -> - case bsm_nonempty(Cs, Pos) of - true -> bsm_an_3(Vs, Cs, Case, Pos); - false -> Case - end. - -bsm_an_3(Vs, Cs, Case, Pos) -> - try - bsm_ensure_no_partition(Cs, Pos), - bsm_do_an(Vs, Pos, Cs, Case) - catch - throw:{problem,Where,What} -> - add_bin_opt_info(Where, What), - Case - end. - -bsm_do_an(Vs0, Pos, Cs0, Case) -> - case nth(Pos, Vs0) of - #c_var{name=Vname}=V0 -> - Cs = bsm_do_an_var(Vname, Pos, Cs0, []), - V = bsm_annotate_for_reuse(V0), - Bef = lists:sublist(Vs0, Pos-1), - Aft = lists:nthtail(Pos, Vs0), - case Bef ++ [V|Aft] of - [_] -> - Case#c_case{arg=V,clauses=Cs}; - Vs -> - Case#c_case{arg=#c_values{es=Vs},clauses=Cs} - end; - _ -> - Case - end. - -bsm_do_an_var(V, S, [#c_clause{pats=Ps,guard=G,body=B0}=C0|Cs], Acc) -> - case nth(S, Ps) of - #c_var{name=VarName} -> - case core_lib:is_var_used(V, G) of - true -> bsm_problem(C0, orig_bin_var_used_in_guard); - false -> ok - end, - case core_lib:is_var_used(VarName, G) of - true -> bsm_problem(C0, bin_var_used_in_guard); - false -> ok - end, - B1 = bsm_maybe_ctx_to_binary(VarName, B0), - B = bsm_maybe_ctx_to_binary(V, B1), - C = C0#c_clause{body=B}, - bsm_do_an_var(V, S, Cs, [C|Acc]); - #c_alias{}=P -> - case bsm_could_match_binary(P) of - false -> - bsm_do_an_var(V, S, Cs, [C0|Acc]); - true -> - bsm_problem(C0, bin_opt_alias) - end; - P -> - case bsm_could_match_binary(P) andalso bsm_is_var_used(V, G, B0) of - false -> - bsm_do_an_var(V, S, Cs, [C0|Acc]); - true -> - bsm_problem(C0, bin_var_used) - end - end; -bsm_do_an_var(_, _, [], Acc) -> reverse(Acc). - -bsm_annotate_for_reuse(#c_var{anno=Anno}=Var) -> - case member(reuse_for_context, Anno) of - false -> Var#c_var{anno=[reuse_for_context|Anno]}; - true -> Var - end. - -bsm_is_var_used(V, G, B) -> - core_lib:is_var_used(V, G) orelse core_lib:is_var_used(V, B). - -bsm_maybe_ctx_to_binary(V, B) -> - case core_lib:is_var_used(V, B) andalso not previous_ctx_to_binary(V, B) of - false -> - B; - true -> - #c_seq{arg=#c_primop{name=#c_literal{val=bs_context_to_binary}, - args=[#c_var{name=V}]}, - body=B} - end. - -previous_ctx_to_binary(V, Core) -> - case Core of - #c_seq{arg=#c_primop{name=#c_literal{val=bs_context_to_binary}, - args=[#c_var{name=V}]}} -> - true; - _ -> - false - end. - -%% bsm_leftmost(Cs) -> none | ArgumentNumber -%% Find the leftmost argument that does binary matching. Return -%% the number of the argument (1-N). - -bsm_leftmost(Cs) -> - bsm_leftmost_1(Cs, none). - -bsm_leftmost_1([#c_clause{pats=Ps}|Cs], Pos) -> - bsm_leftmost_2(Ps, Cs, 1, Pos); -bsm_leftmost_1([], Pos) -> Pos. - -bsm_leftmost_2(_, Cs, Pos, Pos) -> - bsm_leftmost_1(Cs, Pos); -bsm_leftmost_2([#c_binary{}|_], Cs, N, _) -> - bsm_leftmost_1(Cs, N); -bsm_leftmost_2([_|Ps], Cs, N, Pos) -> - bsm_leftmost_2(Ps, Cs, N+1, Pos); -bsm_leftmost_2([], Cs, _, Pos) -> - bsm_leftmost_1(Cs, Pos). - -%% bsm_nonempty(Cs, Pos) -> true|false -%% Check if at least one of the clauses matches a non-empty -%% binary in the given argument position. -%% -bsm_nonempty([#c_clause{pats=Ps}|Cs], Pos) -> - case nth(Pos, Ps) of - #c_binary{segments=[_|_]} -> - true; - _ -> - bsm_nonempty(Cs, Pos) - end; -bsm_nonempty([], _ ) -> false. - -%% bsm_ensure_no_partition(Cs, Pos) -> ok (exception if problem) -%% We must make sure that matching is not partitioned between -%% variables like this: -%% foo(<<...>>) -> ... -%% foo() when ... -> ... -%% foo() -> -%% If there is such partition, we are not allowed to reuse the binary variable -%% for the match context. -%% -%% Also, arguments to the left of the argument that is matched -%% against a binary, are only allowed to be simple variables, not -%% used in guards. The reason is that we must know that the binary is -%% only matched in one place (i.e. there must be only one bs_start_match2 -%% instruction emitted). - -bsm_ensure_no_partition(Cs, Pos) -> - bsm_ensure_no_partition_1(Cs, Pos, before). - -%% Loop through each clause. -bsm_ensure_no_partition_1([#c_clause{pats=Ps,guard=G}|Cs], Pos, State0) -> - State = bsm_ensure_no_partition_2(Ps, Pos, G, simple_vars, State0), - case State of - 'after' -> - bsm_ensure_no_partition_after(Cs, Pos); - _ -> - ok - end, - bsm_ensure_no_partition_1(Cs, Pos, State); -bsm_ensure_no_partition_1([], _, _) -> ok. - -%% Loop through each pattern for this clause. -bsm_ensure_no_partition_2([#c_binary{}=Where|_], 1, _, Vstate, State) -> - case State of - before when Vstate =:= simple_vars -> within; - before -> bsm_problem(Where, Vstate); - within when Vstate =:= simple_vars -> within; - within -> bsm_problem(Where, Vstate) - end; -bsm_ensure_no_partition_2([#c_alias{}=Alias|_], 1, N, Vstate, State) -> - %% Retrieve the real pattern that the alias refers to and check that. - P = bsm_real_pattern(Alias), - bsm_ensure_no_partition_2([P], 1, N, Vstate, State); -bsm_ensure_no_partition_2([_|_], 1, _, _Vstate, before=State) -> - %% No binary matching yet - therefore no partition. - State; -bsm_ensure_no_partition_2([P|_], 1, _, Vstate, State) -> - case bsm_could_match_binary(P) of - false -> - %% If clauses can be freely arranged (Vstate =:= simple_vars), - %% a clause that cannot match a binary will not partition the clause. - %% Example: - %% - %% a(Var, <<>>) -> ... - %% a(Var, []) -> ... - %% a(Var, <>) -> ... - %% - %% But if the clauses can't be freely rearranged, as in - %% - %% b(Var, <>) -> ... - %% b(1, 2) -> ... - %% - %% we do have a problem. - %% - case Vstate of - simple_vars -> State; - _ -> bsm_problem(P, Vstate) - end; - true -> - %% The pattern P *may* match a binary, so we must update the state. - %% (P must be a variable.) - case State of - within -> 'after'; - 'after' -> 'after' - end - end; -bsm_ensure_no_partition_2([#c_var{name=V}|Ps], N, G, Vstate, S) -> - case core_lib:is_var_used(V, G) of - false -> - bsm_ensure_no_partition_2(Ps, N-1, G, Vstate, S); - true -> - bsm_ensure_no_partition_2(Ps, N-1, G, bin_left_var_used_in_guard, S) - end; -bsm_ensure_no_partition_2([_|Ps], N, G, _, S) -> - bsm_ensure_no_partition_2(Ps, N-1, G, bin_argument_order, S). - -bsm_ensure_no_partition_after([#c_clause{pats=Ps}=C|Cs], Pos) -> - case nth(Pos, Ps) of - #c_var{} -> - bsm_ensure_no_partition_after(Cs, Pos); - _ -> - bsm_problem(C, bin_partition) - end; -bsm_ensure_no_partition_after([], _) -> ok. - -bsm_could_match_binary(#c_alias{pat=P}) -> bsm_could_match_binary(P); -bsm_could_match_binary(#c_cons{}) -> false; -bsm_could_match_binary(#c_tuple{}) -> false; -bsm_could_match_binary(#c_literal{val=Lit}) -> is_bitstring(Lit); -bsm_could_match_binary(_) -> true. - -bsm_real_pattern(#c_alias{pat=P}) -> bsm_real_pattern(P); -bsm_real_pattern(P) -> P. - -bsm_problem(Where, What) -> - throw({problem,Where,What}). %%% %%% Handling of warnings. @@ -3249,12 +2991,6 @@ bsm_problem(Where, What) -> init_warnings() -> put({?MODULE,warnings}, []). -add_bin_opt_info(Core, Term) -> - case get(bin_opt_info) of - true -> add_warning(Core, Term); - false -> ok - end. - add_warning(Core, Term) -> case should_suppress_warning(Core) of true -> @@ -3376,28 +3112,7 @@ format_error(result_ignored) -> format_error(invalid_call) -> "invalid function call"; format_error(useless_building) -> - "a term is constructed, but never used"; -format_error(bin_opt_alias) -> - "INFO: the '=' operator will prevent delayed sub binary optimization"; -format_error(bin_partition) -> - "INFO: matching non-variables after a previous clause matching a variable " - "will prevent delayed sub binary optimization"; -format_error(bin_left_var_used_in_guard) -> - "INFO: a variable to the left of the binary pattern is used in a guard; " - "will prevent delayed sub binary optimization"; -format_error(bin_argument_order) -> - "INFO: matching anything else but a plain variable to the left of " - "binary pattern will prevent delayed sub binary optimization; " - "SUGGEST changing argument order"; -format_error(bin_var_used) -> - "INFO: using a matched out sub binary will prevent " - "delayed sub binary optimization"; -format_error(orig_bin_var_used_in_guard) -> - "INFO: using the original binary variable in a guard will prevent " - "delayed sub binary optimization"; -format_error(bin_var_used_in_guard) -> - "INFO: using a matched out sub binary in a guard will prevent " - "delayed sub binary optimization". + "a term is constructed, but never used". -ifdef(DEBUG). %% In order for simplify_let/2 to work correctly, the list of diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl index 89f851ac3b..106d8eb45a 100644 --- a/lib/compiler/test/bs_match_SUITE.erl +++ b/lib/compiler/test/bs_match_SUITE.erl @@ -39,7 +39,7 @@ match_string_opt/1,select_on_integer/1, map_and_binary/1,unsafe_branch_caching/1, bad_literals/1,good_literals/1,constant_propagation/1, - parse_xml/1]). + parse_xml/1,get_payload/1]). -export([coverage_id/1,coverage_external_ignore/2]). @@ -70,7 +70,8 @@ groups() -> no_partition,calling_a_binary,binary_in_map, match_string_opt,select_on_integer, map_and_binary,unsafe_branch_caching, - bad_literals,good_literals,constant_propagation,parse_xml]}]. + bad_literals,good_literals,constant_propagation,parse_xml, + get_payload]}]. init_per_suite(Config) -> @@ -1508,6 +1509,20 @@ do_parse_xml(<<"> = Bytes) -> is_next_char_whitespace(<>) -> C =:= $\s. +-record(ext_header, + {this_hdr = 17, + ext_hdr_opts}). + +get_payload(Config) -> + <<3445:48>> = do_get_payload(#ext_header{ext_hdr_opts = <<3445:48>>}), + {'EXIT',_} = (catch do_get_payload(#ext_header{})), + ok. + +do_get_payload(ExtHdr) -> + _ = ExtHdr#ext_header.this_hdr, + ExtHdrOptions = ExtHdr#ext_header.ext_hdr_opts, + <<_:13,_:35>> = ExtHdr#ext_header.ext_hdr_opts, + ExtHdrOptions. check(F, R) -> R = F(). diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl index 4e2753ba5f..f647a4030d 100644 --- a/lib/compiler/test/compile_SUITE.erl +++ b/lib/compiler/test/compile_SUITE.erl @@ -371,6 +371,7 @@ do_file_listings(DataDir, PrivDir, [File|Files]) -> do_listing(Simple, TargetDir, dinline, ".inline"), do_listing(Simple, TargetDir, dcore, ".core"), do_listing(Simple, TargetDir, dcopt, ".copt"), + do_listing(Simple, TargetDir, dcbsm, ".core_bsm"), do_listing(Simple, TargetDir, dsetel, ".dsetel"), do_listing(Simple, TargetDir, dkern, ".kernel"), do_listing(Simple, TargetDir, dlife, ".life"), diff --git a/lib/compiler/test/misc_SUITE.erl b/lib/compiler/test/misc_SUITE.erl index 01b064cc10..4bd884d86b 100644 --- a/lib/compiler/test/misc_SUITE.erl +++ b/lib/compiler/test/misc_SUITE.erl @@ -161,11 +161,12 @@ md5_1(Beam) -> %% Cover some code that handles internal errors. silly_coverage(Config) when is_list(Config) -> - %% sys_core_fold, sys_core_setel, v3_kernel + %% sys_core_fold, sys_core_bsm, sys_core_setel, v3_kernel BadCoreErlang = {c_module,[], name,[],[], [{{c_var,[],{foo,2}},seriously_bad_body}]}, expect_error(fun() -> sys_core_fold:module(BadCoreErlang, []) end), + expect_error(fun() -> sys_core_bsm:module(BadCoreErlang, []) end), expect_error(fun() -> sys_core_dsetel:module(BadCoreErlang, []) end), expect_error(fun() -> v3_kernel:module(BadCoreErlang, []) end), diff --git a/lib/compiler/test/warnings_SUITE.erl b/lib/compiler/test/warnings_SUITE.erl index 7c27750556..77e4234c70 100644 --- a/lib/compiler/test/warnings_SUITE.erl +++ b/lib/compiler/test/warnings_SUITE.erl @@ -529,7 +529,7 @@ bin_opt_info(Config) when is_list(Config) -> Code, [bin_opt_info], {warnings, - [{4,sys_core_fold,orig_bin_var_used_in_guard}, + [{4,sys_core_bsm,orig_bin_var_used_in_guard}, {5,beam_bsm,{no_bin_opt,{{t1,1},no_suitable_bs_start_match}}}, {9,beam_bsm,{no_bin_opt, {binary_used_in,{extfunc,erlang,split_binary,2}}}} ]}}], -- cgit v1.2.3