Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:26
erlang
1901-compiler-Eliminate-the-Kernel-Erlang-inter...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 1901-compiler-Eliminate-the-Kernel-Erlang-intermediate-re.patch of Package erlang
From 83d41bbebc7281f72d91eb78c24831c631a92a88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org> Date: Tue, 7 Mar 2023 08:42:58 +0100 Subject: [PATCH] compiler: Eliminate the Kernel Erlang intermediate representation The undocumented Kernel Erlang intermediate format has been present in the compiler in some form since OTP R6B. In the current compiler, the `v3_kernel` pass translates Core Erlang to Kernel Erlang, and the `beam_kernel_to_ssa` pass then translates Kernel Erlang to SSA code. Having to maintain the Kernel Erlang representation makes compiler maintenance harder. Therefore, this commit eliminates the Kernel Erlang representation by replacing the `v3_kernel` and `beam_kernel_to_ssa` passes with the `beam_core_to_ssa` pass. While we are at it, we will guarantee that the SSA counter value in each function will never clash with an existing variable name. That will simplify generation of new variable names in the SSA passes. For the history of Kernel Erlang and Core Erlang, see the following blog post: https://www.erlang.org/blog/beam-compiler-history --- lib/compiler/internal_doc/beam_ssa.md | 34 +- lib/compiler/src/Makefile | 17 +- lib/compiler/src/beam_core_to_ssa.erl | 3249 +++++++++++++++++ lib/compiler/src/beam_kernel_to_ssa.erl | 1343 ------- lib/compiler/src/beam_listing.erl | 10 +- lib/compiler/src/beam_ssa.erl | 15 +- lib/compiler/src/beam_ssa_bc_size.erl | 16 +- lib/compiler/src/beam_ssa_bool.erl | 2 +- lib/compiler/src/beam_ssa_bsm.erl | 4 +- lib/compiler/src/beam_ssa_codegen.erl | 10 +- lib/compiler/src/beam_ssa_dead.erl | 2 +- lib/compiler/src/beam_ssa_opt.erl | 44 +- lib/compiler/src/beam_ssa_pp.erl | 7 - lib/compiler/src/beam_ssa_pre_codegen.erl | 51 +- lib/compiler/src/beam_ssa_private_append.erl | 3 +- lib/compiler/src/beam_ssa_recv.erl | 6 +- lib/compiler/src/beam_ssa_type.erl | 2 +- lib/compiler/src/compile.erl | 16 +- lib/compiler/src/compiler.app.src | 6 +- lib/compiler/src/v3_kernel.erl | 2316 ------------ lib/compiler/src/v3_kernel.hrl | 76 - lib/compiler/src/v3_kernel_pp.erl | 509 --- .../sanity_checks.erl | 2 +- lib/compiler/test/compile_SUITE.erl | 40 +- lib/compiler/test/misc_SUITE.erl | 21 +- lib/compiler/test/record_SUITE.erl | 6 + lib/compiler/test/warnings_SUITE.erl | 32 +- lib/stdlib/test/qlc_SUITE.erl | 2 +- 28 files changed, 3382 insertions(+), 4459 deletions(-) create mode 100644 lib/compiler/src/beam_core_to_ssa.erl delete mode 100644 lib/compiler/src/beam_kernel_to_ssa.erl delete mode 100644 lib/compiler/src/v3_kernel.erl delete mode 100644 lib/compiler/src/v3_kernel.hrl delete mode 100644 lib/compiler/src/v3_kernel_pp.erl diff --git a/lib/compiler/internal_doc/beam_ssa.md b/lib/compiler/internal_doc/beam_ssa.md index d32431ecff..199e67384a 100644 --- a/lib/compiler/internal_doc/beam_ssa.md +++ b/lib/compiler/internal_doc/beam_ssa.md @@ -123,28 +123,18 @@ apply: Variable Naming --------------- -A variable name in BEAM SSA is either an atom, a non-negative integer -or a tuple: `atom() | non_neg_integer() | {atom() | non_neg_integer(), -non_neg_integer()}`. In order to generate fresh unused variable names, -all compiler transforms maintain a counter, the `cnt`-field in the -`opt_st`-record, which is incremented each time a new variable or -label is created. In the following description the value of the -`cnt`-field is called `Cnt`. - -Due to peculiarities in the BEAM SSA code generator, a compiler -transformation unfortunately cannot just use the `cnt`-value directly -as a fresh name. There are three basic strategies for creating fresh -variable names which can by used by a compiler pass: +A variable name in BEAM SSA is either an atom or a non-negative +integer: -1) A name can be derived from an existing name of the form `V :: - atom() | non_neg_integer()` by selecting an atom, which is unique to - the compiler pass, to form a new name `{A, V}`. The same `A` cannot - be used by strategy 3) below. + atom() | non_neg_integer() -2) A name can be derived from an existing name of the form `V :: - non_neg_integer()` by combining it with the `cnt`-field into `{V, - Cnt}`. +In order to generate fresh unused variable names, all compiler +transforms maintain a counter, the `cnt`-field in the `b_function` and +`opt_st` records, which is incremented each time a new variable or +label is created. In the following description the value of the +`cnt`-field is called `Cnt`. The `Cnt` value is guaranteed to never +clash with a previously defined variable name. Therefore, value of +`Cnt` can directly be used as a variable name in the SSA passes. -3) A fresh name can be created by selecting an atom `A`, which is - unique to the compiler pass, to form the new name `{A, Cnt}`. The - same `A` cannot be used by strategy 1) above. +Note that the rules were more complicated before Erlang/OTP 27, because +the `Cnt` value could clash with other variables. diff --git a/lib/compiler/src/Makefile b/lib/compiler/src/Makefile index e0625337b5..7e59241429 100644 --- a/lib/compiler/src/Makefile +++ b/lib/compiler/src/Makefile @@ -51,6 +51,7 @@ MODULES = \ beam_bounds \ beam_block \ beam_call_types \ + beam_core_to_ssa \ beam_clean \ beam_dict \ beam_digraph \ @@ -76,7 +77,6 @@ MODULES = \ beam_ssa_share \ beam_ssa_throw \ beam_ssa_type \ - beam_kernel_to_ssa \ beam_trim \ beam_types \ beam_utils \ @@ -102,9 +102,7 @@ MODULES = \ sys_core_prepare \ sys_messages \ sys_pre_attributes \ - v3_core \ - v3_kernel \ - v3_kernel_pp + v3_core BEAM_H = $(wildcard ../priv/beam_h/*.h) @@ -114,8 +112,7 @@ HRL_FILES= \ beam_ssa_opt.hrl \ beam_ssa.hrl \ beam_types.hrl \ - core_parse.hrl \ - v3_kernel.hrl + core_parse.hrl YRL_FILE = core_parse.yrl @@ -206,14 +203,14 @@ release_docs_spec: $(EBIN)/beam_a.beam: beam_asm.hrl beam_types.hrl $(EBIN)/beam_asm.beam: beam_asm.hrl $(EGEN)/beam_opcodes.hrl beam_types.hrl -$(EBIN)/beam_call_types.beam: beam_types.hrl $(EBIN)/beam_block.beam: beam_asm.hrl +$(EBIN)/beam_call_types.beam: beam_types.hrl +$(EBIN)/beam_core_to_ssa.beam: core_parse.hrl beam_ssa.hrl $(EBIN)/beam_dict.beam: beam_types.hrl $(EBIN)/beam_disasm.beam: $(EGEN)/beam_opcodes.hrl beam_disasm.hrl \ beam_asm.hrl beam_types.hrl $(EBIN)/beam_jump.beam: beam_asm.hrl -$(EBIN)/beam_kernel_to_ssa.beam: v3_kernel.hrl beam_ssa.hrl -$(EBIN)/beam_listing.beam: core_parse.hrl v3_kernel.hrl beam_ssa.hrl \ +$(EBIN)/beam_listing.beam: core_parse.hrl beam_ssa.hrl \ beam_asm.hrl beam_types.hrl $(EBIN)/beam_ssa.beam: beam_ssa.hrl $(EBIN)/beam_ssa_alias_opt.beam: beam_ssa_opt.hrl beam_types.hrl @@ -245,5 +242,3 @@ $(EBIN)/sys_core_fold.beam: core_parse.hrl $(EBIN)/sys_core_fold_lists.beam: core_parse.hrl $(EBIN)/sys_core_inline.beam: core_parse.hrl $(EBIN)/v3_core.beam: core_parse.hrl -$(EBIN)/v3_kernel.beam: core_parse.hrl v3_kernel.hrl -$(EBIN)/v3_kernel_pp.beam: v3_kernel.hrl diff --git a/lib/compiler/src/beam_core_to_ssa.erl b/lib/compiler/src/beam_core_to_ssa.erl new file mode 100644 index 0000000000..bde071b4d6 --- /dev/null +++ b/lib/compiler/src/beam_core_to_ssa.erl @@ -0,0 +1,3249 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2023. 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: Transform Core Erlang to SSA code. + +%% +%% The translation from Core Erlang to SSA code is done in three +%% passes: +%% +%% 1. Basic translation, translate variable/function names, flatten +%% completely, pattern matching compilation. To ensure unique +%% variable names we use a variable substitution table and keep the +%% set of all defined variables. The nested scoping of Core Erlang +%% means that we must also nest the substitution tables, but the +%% defined set must be passed through to match the flat structure of +%% SSA code and to make sure variables with the same name from +%% different scopes get different substitutions. +%% +%% We also use these substitutions to handle the variable renaming +%% necessary in pattern matching compilation. +%% +%% 2. Fun lifting (lambda lifting), variable usage annotation and +%% last-call handling. +%% +%% 3. Translation to SSA code. +%% +%% Historical note +%% +%% The translation from Core Erlang to SSA code used to be done +%% by two separate compiler passes: +%% +%% 1. Core Erlang was translated to Kernel Erlang by the `v3_kernel` +%% pass in two sub passes. +%% +%% 2. Kernel Erlang was translated to SSA code by the `beam_kernel_to_ssa` +%% pass. +%% +%% For the history of Kernel Erlang and Core Erlang, see the following +%% blog post: +%% +%% https://www.erlang.org/blog/beam-compiler-history +%% + +-module(beam_core_to_ssa). + +-export([module/2,format_error/1]). + +-import(lists, [all/2,append/1,droplast/1, + flatten/1,foldl/3,foldr/3, + map/2,mapfoldl/3,member/2, + keyfind/3,keysort/2,last/1, + partition/2,reverse/1,reverse/2, + seq/2,sort/1,sort/2,splitwith/2, + zip/2]). +-import(ordsets, [add_element/2,del_element/2,intersection/2, + subtract/2,union/2,union/1]). + +-include("core_parse.hrl"). +-include("beam_ssa.hrl"). + +%% Matches collapse max segment in v3_core. +-define(EXPAND_MAX_SIZE_SEGMENT, 1024). + +%% Internal records created by the first pass and eliminated in the +%% second pass. + +-record(ivalues, {args}). +-record(iset, {vars,arg}). +-record(ilet, {vars,arg,body}). +-record(iletrec, {defs}). +-record(ialias, {vars,pat}). + +-record(ifun, {anno=[],vars,body}). +-record(iclause, {anno=[],sub,pats,guard,body}). + +%% The following records are used to represent complex terms used for +%% matching. (Construction of those term types is translated directly +%% to SSA instructions.) + +-record(cg_tuple, {es}). +-record(cg_map, {var=#b_literal{val=#{}},op,es}). +-record(cg_map_pair, {key,val}). +-record(cg_cons, {hd,tl}). +-record(cg_binary, {segs}). +-record(cg_bin_seg, {size,unit,type,flags,seg,next}). +-record(cg_bin_int, {size,unit,flags,val,next}). +-record(cg_bin_end, {}). + +%% Other internal records. + +-record(cg_seq, {arg,body}). +-record(cg_call, {anno=[],op,args,ret=[]}). +-record(cg_internal, {anno=[],op,args,ret=[]}). + +-record(cg_try, {arg,vars,body,evars,handler,ret=[]}). +-record(cg_catch, {body,ret=[]}). + +-record(cg_letrec_goto, {label,vars=[],first,then,ret=[]}). +-record(cg_goto, {label,args=[]}). + +-record(cg_opaque, {val}). + +-record(cg_match, {body,ret=[]}). +-record(cg_alt, {anno=[],first,then}). +-record(cg_select, {anno=[],var,types}). +-record(cg_type_clause, {anno=[],type,values}). +-record(cg_val_clause, {anno=[],val,body}). +-record(cg_guard, {anno=[],clauses}). +-record(cg_guard_clause, {guard,body}). +-record(cg_test, {op,args}). + +-record(cg_break, {args=[] :: [beam_ssa:value()], + phi :: label() | 'undefined'}). +-record(cg_phi, {vars :: [beam_ssa:b_var()]}). +-record(cg_unreachable, {}). +-record(cg_succeeded, {set :: beam_ssa:b_set()}). + +get_anno(#iclause{anno=Anno}) -> Anno; +get_anno(#cg_alt{anno=Anno}) -> Anno; +get_anno(#cg_guard{anno=Anno}) -> Anno; +get_anno(#cg_select{anno=Anno}) -> Anno. + +-type warning() :: {'failed' | 'nomatch', term()}. + +%% State record for the first two passes (formerly `v3_kernel`). +-record(kern, {module :: atom(), %Current module + func, %Current host function + fargs=[] :: [#b_var{}], %Arguments for current function + vcount=0, %Variable counter + fcount=0, %Fun counter + ds=sets:new([{version, 2}]) :: sets:set(), %Defined variables + funs=[], %Fun functions + free=#{}, %Free variables + ws=[] :: [warning()], %Warnings. + no_make_fun3 :: boolean(), + no_shared_fun_wrappers=false :: boolean(), + no_min_max_bifs=false :: boolean() + }). + +-spec module(cerl:c_module(), [compile:option()]) -> + {'ok', #b_module{}, [warning()]}. + +module(#c_module{name=#c_literal{val=Mod},exports=Es,attrs=As,defs=Fs}, Options) -> + Kas = attributes(As), + Kes = map(fun (#c_var{name={_,_}=Fname}) -> Fname end, Es), + NoSharedFunWrappers = proplists:get_bool(no_shared_fun_wrappers, + Options), + NoMinMaxBifs = proplists:get_bool(no_min_max_bifs, Options), + NoMakeFun3 = proplists:get_bool(no_make_fun3, Options), + St0 = #kern{module=Mod, + no_shared_fun_wrappers=NoSharedFunWrappers, + no_min_max_bifs=NoMinMaxBifs, + no_make_fun3=NoMakeFun3}, + {Kfs,St} = mapfoldl(fun function/2, St0, Fs), + Body = Kfs ++ St#kern.funs, + Code = #b_module{name=Mod,exports=Kes,attributes=Kas,body=Body}, + {ok,Code,sort(St#kern.ws)}. + +-spec format_error(warning()) -> string() | binary(). + +format_error({nomatch,{shadow,Line}}) -> + M = io_lib:format(<<"this clause cannot match because a previous clause at line ~p " + "always matches">>, [Line]), + flatten(M); +format_error({nomatch,shadow}) -> + <<"this clause cannot match because a previous clause always matches">>; +format_error({failed,bad_call}) -> + <<"invalid module and/or function name; this call will always fail">>; +format_error({failed,bad_segment_size}) -> + <<"binary construction will fail because the size of a segment is invalid">>. + +attributes([{#c_literal{val=Name},#c_literal{val=Val}}|As]) -> + case include_attribute(Name) of + false -> + attributes(As); + true -> + [{Name,Val}|attributes(As)] + end; +attributes([]) -> []. + +include_attribute(type) -> false; +include_attribute(spec) -> false; +include_attribute(callback) -> false; +include_attribute(opaque) -> false; +include_attribute(export_type) -> false; +include_attribute(record) -> false; +include_attribute(optional_callbacks) -> false; +include_attribute(file) -> false; +include_attribute(compile) -> false; +include_attribute(_) -> true. + +function({#c_var{name={F,Arity}=FA},Body}, St0) -> + try + %% Find a suitable starting value for the counter + %% used for generating labels and variable names. + Count0 = cerl_trees:next_free_variable_name(Body), + Count = max(?EXCEPTION_BLOCK + 1, Count0), + + %% First pass: Basic translation. + St1 = St0#kern{func=FA,vcount=Count,fcount=0}, + {#ifun{anno=Ab,vars=Kvs,body=B0},[],St2} = expr(Body, new_sub(), St1), + St3 = St2#kern{ds=sets:new([{version,2}])}, + + %% Second pass: Variable usage and lambda lifting. + {B1,_,St4} = ubody(B0, return, St3), + St5 = St4#kern{free=#{}}, + + %% Third pass: Translation to SSA code. + FDef = make_ssa_function(Ab, F, Kvs, B1, St5), + {FDef,St5} + catch + Class:Error:Stack -> + io:fwrite("Function: ~w/~w\n", [F,Arity]), + erlang:raise(Class, Error, Stack) + end. + +%%% +%%% First pass: Basic translation. +%%% + +%% body(Cexpr, Sub, State) -> {Kexpr,[PreKepxr],State}. +%% Do the main sequence of a body. A body ends in an atomic value or +%% values. Must check if vector first so do expr. + +body(#c_values{es=Ces}, Sub, St0) -> + %% Do this here even if only in bodies. + {Kes,Pe,St1} = atomic_list(Ces, Sub, St0), + {#ivalues{args=Kes},Pe,St1}; +body(Ce, Sub, St0) -> + expr(Ce, Sub, St0). + +%% guard(Cexpr, Sub, State) -> {Kexpr,State}. +%% We handle guards almost as bodies. The only special thing we +%% must do is to make the final Kexpr a #cg_test{}. + +guard(G0, Sub, St0) -> + {Ge0,Pre,St1} = expr(G0, Sub, St0), + {Ge,St} = gexpr_test(Ge0, St1), + {pre_seq(Pre, Ge),St}. + +%% gexpr_test(Kexpr, State) -> {Kexpr,State}. +%% Builds the final boolean test from the last Kexpr in a guard test. +%% Must enter try blocks and isets and find the last Kexpr in them. +%% This must end in a recognised BEAM test! + +gexpr_test(#b_set{op={bif,F},args=Args}, St) -> + Ar = length(Args), + true = erl_internal:new_type_test(F, Ar) orelse + erl_internal:comp_op(F, Ar), %Assertion + {#cg_test{op=F,args=Args},St}; +gexpr_test(#cg_try{arg=B0,vars=[#b_var{name=X}],body=#b_var{name=X}, + handler=#b_literal{val=false}}=Try, St0) -> + {B,St} = gexpr_test(B0, St0), + {Try#cg_try{vars=[],arg=B,body=#cg_break{}, + evars=[],handler=#cg_break{}},St}; +gexpr_test(#ilet{body=B0}=Iset, St0) -> + {B1,St1} = gexpr_test(B0, St0), + {Iset#ilet{body=B1},St1}; +gexpr_test(Ke, St) -> gexpr_test_add(Ke, St). %Add equality test + +gexpr_test_add(Ke, St0) -> + {Ae,Ap,St1} = force_atomic(Ke, St0), + {pre_seq(Ap, #cg_test{op='=:=',args=[Ae,#b_literal{val='true'}]}),St1}. + +%% expr(Cexpr, Sub, State) -> {Kexpr,[PreKexpr],State}. +%% Convert a Core expression, flattening it at the same time. + +expr(#c_var{anno=A,name={Name0,Arity}}=Fname, Sub, St) -> + case St#kern.no_shared_fun_wrappers of + false -> + Name = get_fsub(Name0, Arity, Sub), + {#b_local{name=#b_literal{val=Name},arity=Arity},[],St}; + true -> + %% For backward compatibility with OTP 22 and earlier, + %% use the pre-generated name for the fun wrapper. + %% There will be one wrapper function for each occurrence + %% of `fun F/A`. + Vs = [#c_var{name=list_to_atom("V" ++ integer_to_list(V))} || + V <- seq(1, Arity)], + Fun = #c_fun{anno=A,vars=Vs,body=#c_apply{anno=A,op=Fname,args=Vs}}, + expr(Fun, Sub, St) + end; +expr(#c_var{name=V}, Sub, St) -> + {#b_var{name=get_vsub(V, Sub)},[],St}; +expr(#c_literal{val=V}, _Sub, St) -> + {#b_literal{val=V},[],St}; +expr(#c_cons{hd=Ch,tl=Ct}, Sub, St0) -> + %% Do cons in two steps, first the expressions left to right, then + %% any remaining literals right to left. + {Kh0,Hp0,St1} = expr(Ch, Sub, St0), + {Kt0,Tp0,St2} = expr(Ct, Sub, St1), + {Kt1,Tp1,St3} = force_atomic(Kt0, St2), + {Kh1,Hp1,St4} = force_atomic(Kh0, St3), + {#b_set{op=put_list,args=[Kh1,Kt1]},Hp0 ++ Tp0 ++ Tp1 ++ Hp1,St4}; +expr(#c_tuple{es=Ces}, Sub, St0) -> + {Kes,Ep,St1} = atomic_list(Ces, Sub, St0), + {#b_set{op=put_tuple,args=Kes},Ep,St1}; +expr(#c_map{anno=A,arg=Var,es=Ces}, Sub, St0) -> + expr_map(A, Var, Ces, Sub, St0); +expr(#c_binary{anno=A,segments=Cv}, Sub, St0) -> + try + expr_binary(A, Cv, Sub, St0) + catch + throw:{bad_segment_size,Anno} -> + St1 = add_warning(Anno, {failed,bad_segment_size}, St0), + Erl = #c_literal{val=erlang}, + Name = #c_literal{val=error}, + Args = [#c_literal{val=badarg}], + Error = #c_call{anno=A,module=Erl,name=Name,args=Args}, + expr(Error, Sub, St1) + end; +expr(#c_fun{anno=A,vars=Cvs,body=Cb}, Sub0, #kern{fargs=OldFargs}=St0) -> + {Kvs,Sub1,St1} = pattern_list(Cvs, Sub0, St0), + {Kb,Pb,St2} = body(Cb, Sub1, St1#kern{fargs=Kvs}), + {#ifun{anno=A,vars=Kvs,body=pre_seq(Pb, Kb)},[],St2#kern{fargs=OldFargs}}; +expr(#c_seq{arg=Ca,body=Cb}, Sub, St0) -> + {Ka,Pa,St1} = body(Ca, Sub, St0), + {Kb,Pb,St2} = body(Cb, Sub, St1), + {Kb,Pa ++ [Ka] ++ Pb,St2}; +expr(#c_let{vars=Cvs,arg=Ca,body=Cb}, Sub0, St0) -> + {Ka,Pa,St1} = body(Ca, Sub0, St0), + {Kps,Sub1,St2} = pattern_list(Cvs, Sub0, St1), + %% Break known multiple values into separate sets. + Sets = case Ka of + #ivalues{args=Kas} -> + [#iset{vars=[V],arg=Val} || {V,Val} <- zip(Kps, Kas)]; + _Other -> + [#iset{vars=Kps,arg=Ka}] + end, + {Kb,Pb,St3} = body(Cb, Sub1, St2), + {Kb,Pa ++ Sets ++ Pb,St3}; +expr(#c_letrec{anno=A,defs=Cfs,body=Cb}, Sub, St) -> + case member(letrec_goto, A) of + true -> + letrec_goto(Cfs, Cb, Sub, St); + false -> + letrec_local_function(A, Cfs, Cb, Sub, St) + end; +expr(#c_case{arg=Ca,clauses=Ccs}, Sub, St0) -> + {Ka,Pa,St1} = body(Ca, Sub, St0), %This is a body! + {Kvs,Pv,St2} = match_vars(Ka, St1), %Must have variables here! + {Km,St3} = kmatch(Kvs, Ccs, Sub, St2), + Match = flatten_seq(build_match(Km)), + {last(Match),Pa ++ Pv ++ droplast(Match),St3}; +expr(#c_apply{anno=A,op=Cop,args=Cargs}, Sub, St) -> + c_apply(A, Cop, Cargs, Sub, St); +expr(#c_call{anno=A,module=M0,name=F0,args=Cargs}, Sub, St0) -> + case call_type(M0, F0, Cargs, St0) of + bif -> + #c_literal{val=Name} = F0, + {Args,Ap,St} = atomic_list(Cargs, Sub, St0), + Set = #b_set{anno=line_anno(A),op={bif,Name},args=Args}, + case erl_bifs:is_safe(erlang, Name, length(Args)) of + true -> {Set,Ap,St}; + false -> {#cg_succeeded{set=Set},Ap,St} + end; + call -> + {[M,F|Args],Ap,St} = atomic_list([M0,F0|Cargs], Sub, St0), + Remote = #b_remote{mod=M,name=F,arity=length(Args)}, + {#cg_call{anno=A,op=Remote,args=Args},Ap,St}; + is_record -> + [TupleVar,#c_literal{}=Tag,#c_literal{val=Arity}] = Cargs, + {[Any|NotUsed],St} = new_core_vars(Arity, St0), + TuplePat = #c_tuple{es=[Tag|NotUsed]}, + False = #c_literal{val=false}, + True = #c_literal{val=true}, + Cs = [#c_clause{pats=[TuplePat],guard=True,body=True}, + #c_clause{pats=[Any],guard=True,body=False}], + Case = #c_case{arg=TupleVar,clauses=Cs}, + expr(Case, Sub, St); + error -> + %% Invalid call (e.g. M:42/3). Issue a warning, and let + %% the generated code call apply/3. + St = add_warning(A, {failed,bad_call}, St0), + Call = #c_call{anno=A, + module=#c_literal{val=erlang}, + name=#c_literal{val=apply}, + args=[M0,F0,cerl:make_list(Cargs)]}, + expr(Call, Sub, St) + end; +expr(#c_primop{anno=A,name=#c_literal{val=match_fail},args=[Arg]}, Sub, St) -> + translate_match_fail(Arg, Sub, A, St); +expr(#c_primop{anno=A,name=#c_literal{val=Op},args=Cargs}, Sub, St0) -> + {Args,Ap,St1} = atomic_list(Cargs, Sub, St0), + {primop(Op, A, Args),Ap,St1}; +expr(#c_try{arg=Ca,vars=Cvs,body=Cb,evars=Evs,handler=Ch}, Sub0, St0) -> + {Ka,Pa,St1} = body(Ca, Sub0, St0), + {Kcvs,Sub1,St2} = pattern_list(Cvs, Sub0, St1), + {Kb,Pb,St3} = body(Cb, Sub1, St2), + {Kevs,Sub2,St4} = pattern_list(Evs, Sub0, St3), + {Kh,Ph,St5} = body(Ch, Sub2, St4), + {#cg_try{arg=pre_seq(Pa, Ka), + vars=Kcvs,body=pre_seq(Pb, Kb), + evars=Kevs,handler=pre_seq(Ph, Kh)},[],St5}; +expr(#c_catch{body=Cb}, Sub, St0) -> + {Kb,Pb,St1} = body(Cb, Sub, St0), + {#cg_catch{body=pre_seq(Pb, Kb)},[],St1}; +expr(#c_opaque{val=Check}, _Sub, St) -> + {#cg_opaque{val=Check},[],St}. + +primop(raise, Anno, Args) -> + primop_succeeded(resume, Anno, Args); +primop(raw_raise, Anno, Args) -> + primop_succeeded(raw_raise, Anno, Args); +primop(Op, Anno, Args) when Op =:= recv_peek_message; + Op =:= recv_wait_timeout -> + #cg_internal{anno=internal_anno(Anno),op=Op,args=Args}; +primop(Op, Anno, Args) -> + #b_set{anno=internal_anno(Anno),op=Op,args=Args}. + +primop_succeeded(Op, Anno0, Args) -> + Anno = internal_anno(Anno0), + Set = #b_set{anno=Anno,op=Op,args=Args}, + #cg_succeeded{set=Set}. + +%% Implement letrec in the traditional way as a local +%% function for each definition in the letrec. + +letrec_local_function(A, Cfs, Cb, Sub0, St0) -> + %% Make new function names and store substitution. + {Fs0,{Sub1,St1}} = + mapfoldl(fun ({#c_var{name={F,Ar}},#c_fun{}=B0}, {Sub,S0}) -> + {N,St1} = new_fun_name(atom_to_list(F) + ++ "/" ++ + integer_to_list(Ar), + S0), + B = B0#c_fun{anno=[{letrec_name,N}]}, + {{N,B},{set_fsub(F, Ar, N, Sub),St1}} + end, {Sub0,St0}, Cfs), + %% Run translation on functions and body. + {Fs1,St2} = mapfoldl(fun ({N,Fd0}, S1) -> + {Fd1,[],St2} = expr(Fd0, Sub1, S1), + Fd = Fd1#ifun{anno=A}, + {{N,Fd},St2} + end, St1, Fs0), + {Kb,Pb,St3} = body(Cb, Sub1, St2), + {Kb,[#iletrec{defs=Fs1}|Pb],St3}. + +%% Implement letrec with the single definition as a label and each +%% apply of it as a goto. + +letrec_goto([{#c_var{name={Name,Arity}},Cfail}], Cb, Sub0, St0) -> + {Label,St1} = new_var_name(St0), + #c_fun{vars=FunVars,body=FunBody} = Cfail, + Sub1 = set_fsub(Name, Arity, {letrec_goto,Label}, Sub0), + {Kvars,{FunSub,St2}} = + mapfoldl(fun(#c_var{name=V}, {SubInt,StInt0}) -> + {New,StInt1} = new_var_name(StInt0), + {#b_var{name=New}, + {set_vsub(V, New, SubInt), + StInt1#kern{ds=sets:add_element(New, StInt1#kern.ds)}}} + end, {Sub1,St1}, FunVars), + {Kb,Pb,St3} = body(Cb, Sub1, St2), + {Kfail,Fb,St4} = body(FunBody, FunSub, St3), + case {Kb,Kfail,Fb} of + {#cg_goto{label=Label},#cg_goto{}=InnerGoto,[]} -> + {InnerGoto,Pb,St4}; + {_,_,_} -> + Alt = #cg_letrec_goto{label=Label,vars=Kvars, + first=Kb,then=pre_seq(Fb, Kfail)}, + {Alt,Pb,St4} + end. + +%% translate_match_fail(Arg, Sub, Anno, St) -> {Kexpr,[PreKexpr],State}. +%% Translate match_fail primops, paying extra attention to `function_clause` +%% errors that may have been inlined from other functions. + +translate_match_fail(Arg, Sub, Anno, St0) -> + {Cargs,ExtraAnno,St1} = + case {cerl:data_type(Arg),cerl:data_es(Arg)} of + {tuple,[#c_literal{val=function_clause}|_]=As} -> + translate_fc_args(As, Sub, Anno, St0); + {tuple,[#c_literal{}|_]=As} -> + {As,[],St0}; + {{atomic,Reason}, []} -> + {[#c_literal{val=Reason}],[],St0} + end, + {Args,Ap,St} = atomic_list(Cargs, Sub, St1), + SsaAnno = internal_anno(ExtraAnno ++ Anno), + Set = #b_set{anno=SsaAnno,op=match_fail,args=Args}, + {#cg_succeeded{set=Set},Ap,St}. + +translate_fc_args(As, Sub, Anno, #kern{fargs=Fargs}=St0) -> + {ExtraAnno, St} = + case same_args(As, Fargs, Sub) of + true -> + %% The arguments for the `function_clause` exception are + %% the arguments for the current function in the correct + %% order. + {[], St0}; + false -> + %% The arguments in the `function_clause` exception don't + %% match the arguments for the current function because of + %% inlining. + case keyfind(function, 1, Anno) of + false -> + {Name, St1} = new_fun_name("inlined", St0), + {[{inlined,{Name,length(As) - 1}}], St1}; + {_,{Name0,Arity}} -> + %% This is function that has been inlined. + Name1 = ["-inlined-",Name0,"/",Arity,"-"], + Name = list_to_atom(lists:concat(Name1)), + {[{inlined,{Name,Arity}}], St0} + end + end, + {As, ExtraAnno, St}. + +same_args([#c_var{name=Cv}|Vs], [#b_var{name=Kv}|As], Sub) -> + get_vsub(Cv, Sub) =:= Kv andalso same_args(Vs, As, Sub); +same_args([], [], _Sub) -> true; +same_args(_, _, _) -> false. + +expr_map(A, Var0, Ces, Sub, St0) -> + {Var,Mps,St1} = expr(Var0, Sub, St0), + {Km,Eps,St2} = map_split_pairs(A, Var, Ces, Sub, St1), + {Km,Eps++Mps,St2}. + +map_split_pairs(A, Var, Ces, Sub, St0) -> + %% 1. Force variables. + %% 2. Group adjacent pairs with literal keys. + %% 3. Within each such group, remove multiple assignments to + %% the same key. + %% 4. Partition each group according to operator ('=>' and ':='). + {Pairs,Esp,St1} = + foldr(fun(#c_map_pair{op=#c_literal{val=Op},key=K0,val=V0}, + {Ops,Espi,Sti0}) when Op =:= assoc; Op =:= exact -> + {K,Eps1,Sti1} = atomic(K0, Sub, Sti0), + {V,Eps2,Sti2} = atomic(V0, Sub, Sti1), + {[{Op,K,V}|Ops],Eps1 ++ Eps2 ++ Espi,Sti2} + end, {[],[],St0}, Ces), + map_split_pairs_1(A, Var, Pairs, Esp, St1). + +map_split_pairs_1(A, Map0, [{Op,Key,Val}|Pairs1]=Pairs0, Esp0, St0) -> + {Map1,Em,St1} = force_atomic(Map0, St0), + case Key of + #b_var{} -> + %% Don't combine variable keys with other keys. + Kes = [[Key,Val]], + Map = ssa_map(A, Op, Map1, Kes), + map_split_pairs_1(A, Map, Pairs1, Esp0 ++ Em, St1); + #b_literal{} -> + %% Literal key. Split off all literal keys. + {L,Pairs} = splitwith(fun({_,#b_var{},_}) -> false; + ({_,_,_}) -> true + end, Pairs0), + {Map,Esp,St2} = map_group_pairs(A, Map1, L, Esp0 ++ Em, St1), + map_split_pairs_1(A, Map, Pairs, Esp, St2) + end; +map_split_pairs_1(_, Map, [], Esp, St0) -> + {Map,Esp,St0}. + +map_group_pairs(A, Var, Pairs0, Esp, St0) -> + Pairs = map_remove_dup_keys(Pairs0), + Assoc = [[K,V] || {_,{assoc,K,V}} <- Pairs], + Exact = [[K,V] || {_,{exact,K,V}} <- Pairs], + case {Assoc,Exact} of + {[_|_],[]} -> + {ssa_map(A, assoc, Var, Assoc),Esp,St0}; + {[],[_|_]} -> + {ssa_map(A, exact, Var, Exact),Esp,St0}; + {[_|_],[_|_]} -> + Map = ssa_map(A, assoc, Var, Assoc), + {Mvar,Em,St1} = force_atomic(Map, St0), + {ssa_map(A, exact, Mvar, Exact),Esp ++ Em,St1} + end. + +ssa_map(A, Op, SrcMap, Pairs) -> + FlatList = append(Pairs), + Args = [#b_literal{val=Op},SrcMap|FlatList], + LineAnno = line_anno(A), + Set = #b_set{anno=LineAnno,op=put_map,args=Args}, + case Op of + assoc -> Set; + exact -> #cg_succeeded{set=Set} + end. + +map_remove_dup_keys(Es) -> + map_remove_dup_keys(Es, #{}). + +map_remove_dup_keys([{assoc,K,V}|Es0], Used0) -> + Op = case Used0 of + #{K := {exact,_,_}} -> exact; + #{} -> assoc + end, + Used1 = Used0#{K => {Op,K,V}}, + map_remove_dup_keys(Es0, Used1); +map_remove_dup_keys([{exact,K,V}|Es0], Used0) -> + Op = case Used0 of + #{K := {assoc,_,_}} -> assoc; + #{} -> exact + end, + Used1 = Used0#{K => {Op,K,V}}, + map_remove_dup_keys(Es0, Used1); +map_remove_dup_keys([], Used) -> + %% We must sort the map entries to ensure consistent + %% order from compilation to compilation. + sort(maps:to_list(Used)). + +%% match_vars(Kexpr, State) -> {[Kvar],[PreKexpr],State}. +%% Force return from body into a list of variables. + +match_vars(#ivalues{args=As}, St) -> + foldr(fun (Ka, {Vs,Vsp,St0}) -> + {V,Vp,St1} = force_variable(Ka, St0), + {[V|Vs],Vp ++ Vsp,St1} + end, {[],[],St}, As); +match_vars(Ka, St0) -> + {V,Vp,St1} = force_variable(Ka, St0), + {[V],Vp,St1}. + +%% c_apply(A, Op, [Carg], Sub, State) -> {Kexpr,[PreKexpr],State}. +%% Transform application. + +c_apply(A, #c_var{name={F0,Ar}}, Cargs, Sub, St0) -> + {Args,Ap,St1} = atomic_list(Cargs, Sub, St0), + case get_fsub(F0, Ar, Sub) of + {letrec_goto,Label} -> + {#cg_goto{label=Label,args=Args},Ap,St1}; + F1 -> + {#cg_call{anno=A,op=#b_local{name=#b_literal{val=F1},arity=Ar},args=Args}, + Ap,St1} + end; +c_apply(A, Cop, Cargs, Sub, St0) -> + {Kop,Op,St1} = variable(Cop, Sub, St0), + {Args,Ap,St2} = atomic_list(Cargs, Sub, St1), + {#cg_call{anno=A,op=Kop,args=Args},Op ++ Ap,St2}. + +flatten_seq(#ilet{vars=Vs,arg=Arg,body=B}) -> + [#iset{vars=Vs,arg=Arg}|flatten_seq(B)]; +flatten_seq(Ke) -> [Ke]. + +pre_seq([#iset{vars=Vs,arg=Arg}|Ps], K) -> + #ilet{vars=Vs,arg=Arg,body=pre_seq(Ps, K)}; +pre_seq([P|Ps], K) -> + #ilet{vars=[],arg=P,body=pre_seq(Ps, K)}; +pre_seq([], K) -> K. + +%% atomic(Cexpr, Sub, State) -> {Katomic,[PreKexpr],State}. +%% Convert a Core expression making sure the result is atomic +%% (variable or literal). + +atomic(Ce, Sub, St0) -> + {Ke,Kp,St1} = expr(Ce, Sub, St0), + {Ka,Ap,St2} = force_atomic(Ke, St1), + {Ka,Kp ++ Ap,St2}. + +force_atomic(#b_literal{}=Ke, St) -> + {Ke,[],St}; +force_atomic(Ke, St) -> + force_variable(Ke, St). + +%% atomic_list([Cexpr], Sub, State) -> {[Kexpr],[PreKexpr],State}. + +atomic_list(Ces, Sub, St) -> + foldr(fun (Ce, {Kes,Esp,St0}) -> + {Ke,Ep,St1} = atomic(Ce, Sub, St0), + {[Ke|Kes],Ep ++ Esp,St1} + end, {[],[],St}, Ces). + +%%% +%%% Construction of binaries. +%%% + +expr_binary(Anno, Segs0, Sub, St0) -> + {Segs1,Ep,St1} = atomic_bin(Segs0, Sub, St0), + Segs = case Segs1 of + [#b_literal{val=binary},UnitFlags,Val,#b_literal{val=all}|Segs2] -> + Op = case member(single_use, Anno) of + true -> private_append; + false -> append + end, + [#b_literal{val=Op},UnitFlags,Val,#b_literal{val=all}|Segs2]; + _ -> + Segs1 + end, + LineAnno = line_anno(Anno), + Build = #cg_succeeded{set=#b_set{anno=LineAnno,op=bs_create_bin,args=Segs}}, + {Build,Ep,St1}. + +atomic_bin([#c_bitstr{anno=A,val=E0,size=S0,unit=U0,type=T,flags=Fs0}|Es0], + Sub, St0) -> + {E,Ap1,St1} = atomic(E0, Sub, St0), + {S1,Ap2,St2} = atomic(S0, Sub, St1), + validate_bin_element_size(S1, A), + U1 = cerl:concrete(U0), + Fs1 = cerl:concrete(Fs0), + {Es,Ap3,St3} = atomic_bin(Es0, Sub, St2), + {ssa_bin_segment(A, T, E, S1, U1, Fs1) ++ Es, + Ap1++Ap2++Ap3,St3}; +atomic_bin([], _Sub, St) -> {[],[],St}. + +ssa_bin_segment(Anno, Type, Src, Size, U, Flags0) -> + Seg = case lists:keyfind(segment, 1, Anno) of + false -> []; + {segment,_}=Seg0 -> [Seg0] + end, + TypeArg = #b_literal{val=cerl:concrete(Type)}, + Unit = case U of + undefined -> 0; + _ -> U + end, + Flags = strip_bs_construct_flags(Flags0), + UnitFlags = #b_literal{val=[Unit|Flags++Seg]}, + [TypeArg,UnitFlags,Src,Size]. + +validate_bin_element_size(#b_var{}, _Anno) -> ok; +validate_bin_element_size(#b_literal{val=Val}, Anno) -> + case Val of + all -> ok; + undefined -> ok; + _ when is_integer(Val), Val >= 0 -> ok; + _ -> throw({bad_segment_size,Anno}) + end. + +%% Only keep the flags that have a meaning for binary construction and +%% are distinct from the default value. +strip_bs_construct_flags(Flags) -> + [Flag || Flag <- Flags, + case Flag of + little -> true; + native -> true; + big -> false; + signed -> false; + unsigned -> false + end]. + +%% variable(Cexpr, Sub, State) -> {Kvar,[PreKexpr],State}. +%% Convert a Core expression making sure the result is a variable. + +variable(Ce, Sub, St0) -> + {Ke,Kp,St1} = expr(Ce, Sub, St0), + {Kv,Vp,St2} = force_variable(Ke, St1), + {Kv,Kp ++ Vp,St2}. + +force_variable(#b_var{}=Ke, St) -> + {Ke,[],St}; +force_variable(Ke, St0) -> + {V,St1} = new_var(St0), + {V,[#iset{vars=[V],arg=Ke}],St1}. + +%% pattern(Cpat, Sub, State) -> {Kpat,Sub,State}. +%% Convert patterns. Variables shadow so rename variables that are +%% already defined. + +pattern(#c_var{name=V}, Sub, St0) -> + case sets:is_element(V, St0#kern.ds) of + true -> + {New,St1} = new_var_name(St0), + {#b_var{name=New}, + set_vsub(V, New, Sub), + St1#kern{ds=sets:add_element(New, St1#kern.ds)}}; + false -> + {#b_var{name=V},Sub, + St0#kern{ds=sets:add_element(V, St0#kern.ds)}} + end; +pattern(#c_literal{val=Val}, Sub, St) -> + {#b_literal{val=Val},Sub,St}; +pattern(#c_cons{hd=Ch,tl=Ct}, Sub0, St0) -> + {Kh,Sub1,St1} = pattern(Ch, Sub0, St0), + {Kt,Sub2,St2} = pattern(Ct, Sub1, St1), + {#cg_cons{hd=Kh,tl=Kt},Sub2,St2}; +pattern(#c_tuple{es=Ces}, Sub0, St0) -> + {Kes,Sub1,St1} = pattern_list(Ces, Sub0, St0), + {#cg_tuple{es=Kes},Sub1,St1}; +pattern(#c_map{es=Ces}, Sub0, St0) -> + {Kes,Sub1,St1} = pattern_map_pairs(Ces, Sub0, St0), + {#cg_map{op=exact,es=Kes},Sub1,St1}; +pattern(#c_binary{segments=Cv}, Sub0, St0) -> + {Kv,Sub1,St1} = pattern_bin(Cv, Sub0, St0), + {#cg_binary{segs=Kv},Sub1,St1}; +pattern(#c_alias{var=Cv,pat=Cp}, Sub0, St0) -> + {Cvs,Cpat} = flatten_alias(Cp), + {Kvs,Sub1,St1} = pattern_list([Cv|Cvs], Sub0, St0), + {Kpat,Sub2,St2} = pattern(Cpat, Sub1, St1), + {#ialias{vars=Kvs,pat=Kpat},Sub2,St2}. + +flatten_alias(#c_alias{var=V,pat=P}) -> + {Vs,Pat} = flatten_alias(P), + {[V|Vs],Pat}; +flatten_alias(Pat) -> {[],Pat}. + +pattern_map_pairs(Ces0, Sub0, St0) -> + {Kes,{Sub1,St1}} = + mapfoldl(fun(#c_map_pair{key=Ck,val=Cv},{Subi0,Sti0}) -> + {Kk,[],Sti1} = expr(Ck, Subi0, Sti0), + {Kv,Subi2,Sti2} = pattern(Cv, Subi0, Sti1), + {#cg_map_pair{key=Kk,val=Kv},{Subi2,Sti2}} + end, {Sub0, St0}, Ces0), + %% It is later assumed that these keys are sorted according + %% to the internal term order, so we'll need to sort them + %% here. + Kes1 = sort(fun(#cg_map_pair{key=A}, #cg_map_pair{key=B}) -> + erts_internal:cmp_term(A, B) < 0 + end, Kes), + {Kes1,Sub1,St1}. + +pattern_bin([#c_bitstr{val=E0,size=S0,unit=U0,type=T,flags=Fs0}|Es0], + Sub0, St0) -> + {S1,[],St1} = expr(S0, Sub0, St0), + S = case S1 of + #b_var{} -> S1; + #b_literal{val=Val} when is_integer(Val); is_atom(Val) -> S1; + _ -> + %% Bad size (coming from an optimization or Core Erlang + %% source code) - replace it with a known atom because + %% a literal or bit syntax construction can cause further + %% problems. + #b_literal{val=bad_size} + end, + U = cerl:concrete(U0), + Fs = cerl:concrete(Fs0), + {E,Sub1,St2} = pattern(E0, Sub0, St1), + {Es,Sub,St3} = pattern_bin(Es0, Sub1, St2), + {build_bin_seg(S, U, cerl:concrete(T), Fs, E, Es),Sub,St3}; +pattern_bin([], Sub, St) -> + {#cg_bin_end{},Sub,St}. + +%% build_bin_seg(Size, Unit, Type, Flags, Seg, Next) -> #cg_bin_seg{}. +%% This function normalizes literal integers with size > 8 and literal +%% utf8 segments into integers with size = 8 (and potentially an integer +%% with size less than 8 at the end). This is so further optimizations +%% have a normalized view of literal integers, allowing us to generate +%% more literals and group more clauses. Those integers may be "squeezed" +%% later into the largest integer possible. +%% +build_bin_seg(#b_literal{val=Bits}=Sz, U, integer=Type, + [unsigned,big]=Flags, #b_literal{val=Int}=Seg, Next) + when is_integer(Bits) -> + Size = Bits * U, + case integer_fits_and_is_expandable(Int, Size) of + true -> + build_bin_seg_integer_recur(Size, Int, Next); + false -> + #cg_bin_seg{size=Sz,unit=U,type=Type, + flags=Flags,seg=Seg,next=Next} + end; +build_bin_seg(Sz, U, utf8=Type, [unsigned,big]=Flags, + #b_literal{val=Utf8}=Seg, Next) -> + case utf8_fits(Utf8) of + {Int,Bits} -> + build_bin_seg_integer_recur(Bits, Int, Next); + error -> + #cg_bin_seg{size=Sz,unit=U,type=Type,flags=Flags,seg=Seg,next=Next} + end; +build_bin_seg(Sz, U, Type, Flags, Seg, Next) -> + #cg_bin_seg{size=Sz,unit=U,type=Type,flags=Flags,seg=Seg,next=Next}. + +build_bin_seg_integer_recur(Bits, Val, Next) -> + Chunks = bitstring_to_list(<<Val:Bits>>), + build_bin_seg_integer_recur_1(Chunks, Next). + +build_bin_seg_integer_recur_1([Val0], Next) when is_bitstring(Val0) -> + Bits = bit_size(Val0), + <<Val:Bits>> = Val0, + build_bin_seg_integer(Bits, Val, Next); +build_bin_seg_integer_recur_1([Val], Next) when is_integer(Val) -> + build_bin_seg_integer(8, Val, Next); +build_bin_seg_integer_recur_1([Val|Values], Next0) when is_integer(Val) -> + Next = build_bin_seg_integer_recur_1(Values, Next0), + build_bin_seg_integer(8, Val, Next). + +build_bin_seg_integer(Bits, Val, Next) -> + Sz = #b_literal{val=Bits}, + Seg = #b_literal{val=Val}, + #cg_bin_seg{size=Sz,unit=1,type=integer,flags=[unsigned,big], + seg=Seg,next=Next}. + +integer_fits_and_is_expandable(Int, Size) + when is_integer(Int), is_integer(Size), + 0 < Size, Size =< ?EXPAND_MAX_SIZE_SEGMENT -> + case <<Int:Size>> of + <<Int:Size>> -> true; + _ -> false + end; +integer_fits_and_is_expandable(_Int, _Size) -> false. + +utf8_fits(Utf8) -> + try <<Utf8/utf8>> of + Bin -> + Bits = bit_size(Bin), + <<Int:Bits>> = Bin, + {Int,Bits} + catch + _:_ -> error + end. + +%% pattern_list([Cexpr], Sub, State) -> {[Kexpr],Sub,State}. + +pattern_list(Ces, Sub, St) -> + foldr(fun (Ce, {Kes,Sub0,St0}) -> + {Ke,Sub1,St1} = pattern(Ce, Sub0, St0), + {[Ke|Kes],Sub1,St1} + end, {[],Sub,St}, Ces). + +%% new_sub() -> Subs. +%% set_vsub(Name, Sub, Subs) -> Subs. +%% subst_vsub(Name, Sub, Subs) -> Subs. +%% get_vsub(Name, Subs) -> SubName. +%% Add/get substitute Sub for Name to VarSub. +%% +%% We're using a many-to-one bimap so we can rename all references to a +%% variable without having to scan through all of them, which can cause +%% compile times to explode (see record_SUITE:slow_compilation/1). + +new_sub() -> {#{}, #{}}. + +get_vsub(Key, Subs) -> + bimap_get(Key, Subs, Key). + +get_fsub(Name, Arity, Subs) -> + bimap_get({Name, Arity}, Subs, Name). + +set_vsub(Key, Val, Subs) -> + true = Key =/= Val, %Assertion. + bimap_set(Key, Val, Subs). + +set_fsub(Name, Arity, Val, Subs) -> + set_vsub({Name, Arity}, Val, Subs). + +subst_vsub(Key, Val, Subs) -> + bimap_rename(Key, Val, Subs). + +bimap_get(Key, {Map, _InvMap}, Default) -> + case Map of + #{Key := Val} -> Val; + #{} -> Default + end. + +%% Maps Key to Val without touching existing references to Key. +bimap_set(Key, Val, {Map0, InvMap0}) when is_map(Map0), is_map(InvMap0) -> + InvMap = bm_update_inv_lookup(Key, Val, Map0, InvMap0), + Map = Map0#{Key => Val}, + {Map,InvMap}. + +bm_update_inv_lookup(Key, Val, Map, InvMap0) -> + InvMap = bm_cleanup_inv_lookup(Key, Map, InvMap0), + case InvMap of + #{Val := Keys} -> + %% Other keys map to the same value, add ours to the set. + InvMap#{Val := add_element(Key, Keys)}; + #{} -> + InvMap#{Val => [Key]} + end. + +bm_cleanup_inv_lookup(Key, Map, InvMap) when is_map_key(Key, Map) -> + #{Key := Old} = Map, + #{Old := Keys0} = InvMap, + case del_element(Key, Keys0) of + [] -> + maps:remove(Old, InvMap); + Keys -> + InvMap#{Old := Keys} + end; +bm_cleanup_inv_lookup(_Key, _Map, InvMap) -> + InvMap. + +%% Map Key to Val, and replace all existing references to Key with Val. +bimap_rename(Key, Val, {Map0, InvMap0}) when is_map_key(Key, InvMap0) -> + {Keys,InvMap1} = maps:take(Key, InvMap0), + InvMap = InvMap1#{Val => add_element(Key, Keys)}, + + Map1 = Map0#{Key => Val}, + Map = bimap_update_lookup(Keys, Val, Map1), + + {Map,InvMap}; +bimap_rename(Key, Val, Subs) -> + bimap_set(Key, Val, Subs). + +bimap_update_lookup([Key|Keys], Val, Map) -> + bimap_update_lookup(Keys, Val, Map#{Key := Val}); +bimap_update_lookup([], _Val, Map) -> + Map. + +new_fun_name(St) -> + new_fun_name("anonymous", St). + +%% new_fun_name(Type, State) -> {FunName,State}. + +new_fun_name(Type, #kern{func={F,Arity},fcount=C}=St) -> + Name = "-" ++ atom_to_list(F) ++ "/" ++ integer_to_list(Arity) ++ + "-" ++ Type ++ "-" ++ integer_to_list(C) ++ "-", + {list_to_atom(Name),St#kern{fcount=C+1}}. + +%% new_var_name(State) -> {VarName,State}. + +new_var_name(#kern{vcount=C}=St) -> + {C,St#kern{vcount=C+1}}. + +%% new_var(State) -> {#b_var{},State}. + +new_var(St0) -> + {New,St1} = new_var_name(St0), + {#b_var{name=New},St1}. + +%% new_vars(Count, State) -> {[#b_var{}],State}. + +new_vars(N, St) when is_integer(N) -> + new_vars(N, St, []). + +new_vars(N, St0, Vs) when N > 0 -> + {V,St1} = new_var(St0), + new_vars(N-1, St1, [V|Vs]); +new_vars(0, St, Vs) -> {Vs,St}. + +make_vars(Vs) -> [#b_var{name=V} || V <- Vs]. + +%% new_core_vars(Count, State) -> {[#c_var{}],State}. + +new_core_vars(N, St) when is_integer(N) -> + new_core_vars(N, St, []). + +new_core_vars(N, St0, Vs) when N > 0 -> + {V,St1} = new_var_name(St0), + new_core_vars(N-1, St1, [#c_var{name=V}|Vs]); +new_core_vars(0, St, Vs) -> {Vs,St}. + +%% call_type(Mod, Name, [Arg], State) -> bif | call | is_record | error. + +call_type(#c_literal{val=M}, #c_literal{val=F}, As, St) when is_atom(M), is_atom(F) -> + case is_guard_bif(M, F, As) of + false -> + call; + true -> + %% The guard BIFs min/2 and max/2 were introduced in + %% Erlang/OTP 26. If we are compiling for an earlier + %% version, we must translate them as call instructions. + case {M,F,St#kern.no_min_max_bifs} of + {erlang,min,true} -> call; + {erlang,max,true} -> call; + {erlang,is_record,_} when length(As) =:= 3 -> is_record; + {erlang,_,_} -> bif + end + end; +call_type(#c_var{}, #c_literal{val=A}, _, _) when is_atom(A) -> call; +call_type(#c_literal{val=A}, #c_var{}, _, _) when is_atom(A) -> call; +call_type(#c_var{}, #c_var{}, _, _) -> call; +call_type(_, _, _, _) -> error. + +%% is_guard_bif(Mod, Name, Args) -> true | false. +%% Test whether this function is a guard BIF. + +is_guard_bif(erlang, get, [_]) -> true; +is_guard_bif(erlang, is_record, [_,Tag,Sz]) -> + case {Tag,Sz} of + {#c_literal{val=Atom},#c_literal{val=Arity}} + when is_atom(Atom), is_integer(Arity), Arity >= 1 -> + true; + {_,_} -> + false + end; +is_guard_bif(erlang, N, As) -> + Arity = length(As), + case erl_internal:guard_bif(N, Arity) of + true -> true; + false -> + try erl_internal:op_type(N, Arity) of + arith -> true; + bool -> true; + comp -> true; + list -> false; + send -> false + catch + _:_ -> false % not an op + end + end; +is_guard_bif(_, _, _) -> false. + +%% This code implements the algorithm for an optimizing compiler for +%% pattern matching given "The Implementation of Functional +%% Programming Languages" by Simon Peyton Jones. The code is much +%% longer as the meaning of constructors is different from the book. +%% +%% In Erlang many constructors can have different values, e.g. 'atom' +%% or 'integer', whereas in the original algorithm these would be +%% different constructors. Our view makes it easier in later passes to +%% handle indexing over each type. +%% +%% Patterns are complicated by having alias variables. The form of a +%% pattern is Pat | {alias,Pat,[AliasVar]}. This is hidden by access +%% functions to pattern arguments but the code must be aware of it. +%% +%% The compilation proceeds in two steps: +%% +%% 1. The patterns in the clauses to converted to lists of Kernel +%% patterns. The Core clause is now hybrid, this is easier to work +%% with. Remove clauses with trivially false guards, this simplifies +%% later passes. Add locally defined vars and variable subs to each +%% clause for later use. +%% +%% 2. The pattern matching is optimised. Variable substitutions are +%% added to the VarSub structure and new variables are made visible. +%% The guard and body are then converted to Kernel form. + +%% kmatch([Var], [Clause], Sub, State) -> {Kexpr,State}. + +kmatch(Us, Ccs, Sub, St0) -> + {Cs,St1} = match_pre(Ccs, Sub, St0), %Convert clauses + Def = fail, + match(Us, Cs, Def, St1). %Do the match. + +%% match_pre([Cclause], Sub, State) -> {[Clause],State}. +%% Must be careful not to generate new substitutions here now! + +match_pre(Cs, Sub0, St) -> + foldr(fun (#c_clause{anno=A,pats=Ps,guard=G,body=B}, {Cs0,St0}) -> + {Kps,Sub1,St1} = pattern_list(Ps, Sub0, St0), + {[#iclause{anno=A,sub=Sub1, + pats=Kps,guard=G,body=B}|Cs0],St1} + end, {[],St}, Cs). + +%% match([Var], [Clause], Default, State) -> {MatchExpr,State}. + +match([_|_]=Vars, Cs, Def, St0) -> + Pcss = partition(Cs), + foldr(fun (Pcs, {D,St}) -> + match_varcon(Vars, Pcs, D, St) + end, {Def,St0}, Pcss); +match([], Cs, Def, St) -> + match_guard(Cs, Def, St). + +%% match_guard([Clause], Default, State) -> {IfExpr,State}. +%% Build a guard to handle guards. A guard *ALWAYS* fails if no +%% clause matches, there will be a surrounding 'alt' to catch the +%% failure. Drop redundant cases, i.e. those after a true guard. + +match_guard(Cs0, Def0, St0) -> + {Cs1,Def1,St1} = match_guard_1(Cs0, Def0, St0), + {build_alt(build_guard(Cs1), Def1),St1}. + +match_guard_1([#iclause{anno=A,sub=Sub,guard=G,body=B}|Cs0], Def0, St0) -> + case is_true_guard(G) of + true -> + %% The true clause body becomes the default. + {Kb,Pb,St1} = body(B, Sub, St0), + St2 = maybe_add_warning(Cs0, A, St1), + St = maybe_add_warning(Def0, A, St2), + {[],pre_seq(Pb, Kb),St}; + false -> + {Kg,St1} = guard(G, Sub, St0), + {Kb,Pb,St2} = body(B, Sub, St1), + {Cs1,Def1,St3} = match_guard_1(Cs0, Def0, St2), + {[#cg_guard_clause{guard=Kg,body=pre_seq(Pb, Kb)}|Cs1], + Def1,St3} + end; +match_guard_1([], Def, St) -> {[],Def,St}. + +%% is_true_guard(Guard) -> boolean(). +%% Test if a guard is trivially true. + +is_true_guard(#c_literal{val=true}) -> true; +is_true_guard(_) -> false. + +%% partition([Clause]) -> [[Clause]]. +%% Partition a list of clauses into groups which either contain +%% clauses with a variable first argument, or with a "constructor". + +partition([C1|Cs]) -> + V1 = is_var_clause(C1), + {More,Rest} = splitwith(fun (C) -> is_var_clause(C) =:= V1 end, Cs), + [[C1|More]|partition(Rest)]; +partition([]) -> []. + +%% match_varcon([Var], [Clause], Def, [Var], Sub, State) -> +%% {MatchExpr,State}. + +match_varcon(Us, [C|_]=Cs, Def, St) -> + case is_var_clause(C) of + true -> match_var(Us, Cs, Def, St); + false -> match_con(Us, Cs, Def, St) + end. + +%% match_var([Var], [Clause], Def, State) -> {MatchExpr,State}. +%% Build a call to "select" from a list of clauses all containing a +%% variable as the first argument. We must rename the variable in +%% each clause to be the match variable as these clause will share +%% this variable and may have different names for it. Rename aliases +%% as well. + +match_var([U|Us], Cs0, Def, St) -> + Cs1 = map(fun (#iclause{sub=Sub0,pats=[Arg|As]}=C) -> + Vs = [arg_arg(Arg)|arg_alias(Arg)], + Sub1 = foldl(fun (#b_var{name=V}, Acc) -> + subst_vsub(V, U#b_var.name, Acc) + end, Sub0, Vs), + C#iclause{sub=Sub1,pats=As} + end, Cs0), + match(Us, Cs1, Def, St). + +%% match_con(Variables, [Clause], Default, State) -> {SelectExpr,State}. +%% Build call to "select" from a list of clauses all containing a +%% constructor/constant as first argument. Group the constructors +%% according to type, the order is really irrelevant but tries to be +%% smart. +match_con([U|_Us]=L, Cs, Def, St0) -> + %% Extract clauses for different constructors (types). + Ttcs0 = select_types(Cs, [], [], [], [], [], [], [], [], []), + Ttcs1 = [{T, Types} || {T, [_ | _] = Types} <- Ttcs0], + Ttcs = opt_single_valued(Ttcs1), + {Scs,St1} = + mapfoldl(fun ({T,Tcs}, St) -> + {[S|_]=Sc,S1} = match_value(L, T, Tcs, fail, St), + #cg_val_clause{anno=Anno} = S, + {#cg_type_clause{anno=Anno,type=T,values=Sc},S1} end, + St0, Ttcs), + {build_alt(build_select(U, Scs), Def),St1}. + +select_types([NoExpC|Cs], Bin, BinCon, Cons, Tuple, Map, Atom, Float, Int, Nil) -> + C = expand_pat_lit_clause(NoExpC), + case clause_con(C) of + cg_binary -> + select_types(Cs, [C|Bin], BinCon, Cons, Tuple, Map, Atom, Float, Int, Nil); + cg_bin_seg -> + select_types(Cs, Bin, [C|BinCon], Cons, Tuple, Map, Atom, Float, Int, Nil); + cg_bin_end -> + select_types(Cs, Bin, [C|BinCon], Cons, Tuple, Map, Atom, Float, Int, Nil); + cg_cons -> + select_types(Cs, Bin, BinCon, [C|Cons], Tuple, Map, Atom, Float, Int, Nil); + cg_tuple -> + select_types(Cs, Bin, BinCon, Cons, [C|Tuple], Map, Atom, Float, Int, Nil); + cg_map -> + select_types(Cs, Bin, BinCon, Cons, Tuple, [C|Map], Atom, Float, Int, Nil); + cg_nil -> + select_types(Cs, Bin, BinCon, Cons, Tuple, Map, Atom, Float, Int, [C|Nil]); + cg_atom -> + select_types(Cs, Bin, BinCon, Cons, Tuple, Map, [C|Atom], Float, Int, Nil); + cg_float -> + select_types(Cs, Bin, BinCon, Cons, Tuple, Map, Atom, [C|Float], Int, Nil); + cg_int -> + select_types(Cs, Bin, BinCon, Cons, Tuple, Map, Atom, Float, [C|Int], Nil) + end; +select_types([], Bin, BinCon, Cons, Tuple, Map, Atom, Float, Int, Nil) -> + [{cg_binary, reverse(Bin)}] ++ handle_bin_con(reverse(BinCon)) ++ + [ + {cg_cons, reverse(Cons)}, + {cg_tuple, reverse(Tuple)}, + {cg_map, reverse(Map)}, + {{bif,is_atom}, reverse(Atom)}, + {{bif,is_float}, reverse(Float)}, + {{bif,is_integer}, reverse(Int)}, + {cg_nil, reverse(Nil)} + ]. + +expand_pat_lit_clause(#iclause{pats=[#ialias{pat=#b_literal{val=Val}}=Alias|Ps]}=C) -> + P = expand_pat_lit(Val), + C#iclause{pats=[Alias#ialias{pat=P}|Ps]}; +expand_pat_lit_clause(#iclause{pats=[#b_literal{val=Val}|Ps]}=C) -> + P = expand_pat_lit(Val), + C#iclause{pats=[P|Ps]}; +expand_pat_lit_clause(C) -> C. + +expand_pat_lit([H|T]) -> + #cg_cons{hd=#b_literal{val=H},tl=#b_literal{val=T}}; +expand_pat_lit(Tuple) when is_tuple(Tuple) -> + #cg_tuple{es=[#b_literal{val=E} || E <- tuple_to_list(Tuple)]}; +expand_pat_lit(Lit) -> + #b_literal{val=Lit}. + +%% opt_singled_valued([{Type,Clauses}]) -> [{Type,Clauses}]. +%% If a type only has one clause and if the pattern is a complex +%% literal, the matching can be done more efficiently by directly +%% comparing with the literal (that is especially true for binaries). +%% +%% It is important not to do this transformation for atomic literals +%% (such as `[]`), since that would cause the test for an empty list +%% to be executed before the test for a nonempty list. + +opt_single_valued(Ttcs) -> + opt_single_valued(Ttcs, [], []). + +opt_single_valued([{_,[#iclause{pats=[#b_literal{}|_]}]}=Ttc|Ttcs], TtcAcc, LitAcc) -> + %% This is an atomic literal. + opt_single_valued(Ttcs, [Ttc|TtcAcc], LitAcc); +opt_single_valued([{_,[#iclause{pats=[P0|Ps]}=Tc]}=Ttc|Ttcs], TtcAcc, LitAcc) -> + try combine_lit_pat(P0) of + P -> + LitTtc = Tc#iclause{pats=[P|Ps]}, + opt_single_valued(Ttcs, TtcAcc, [LitTtc|LitAcc]) + catch + not_possible -> + opt_single_valued(Ttcs, [Ttc|TtcAcc], LitAcc) + end; +opt_single_valued([Ttc|Ttcs], TtcAcc, LitAcc) -> + opt_single_valued(Ttcs, [Ttc|TtcAcc], LitAcc); +opt_single_valued([], TtcAcc, []) -> + reverse(TtcAcc); +opt_single_valued([], TtcAcc, LitAcc) -> + Literals = {b_literal,reverse(LitAcc)}, + %% Test the literals as early as possible. + case reverse(TtcAcc) of + [{cg_binary,_}=Bin|Ttcs] -> + %% The delayed creation of sub binaries requires + %% bs_start_match2 to be the first instruction in the + %% function. + [Bin,Literals|Ttcs]; + Ttcs -> + [Literals|Ttcs] + end. + +combine_lit_pat(#ialias{pat=Pat0}=Alias) -> + Pat = combine_lit_pat(Pat0), + Alias#ialias{pat=Pat}; +combine_lit_pat(#b_literal{}) -> + %% This is an atomic literal. Rewriting would be a pessimization, + %% especially for `[]`. + throw(not_possible); +combine_lit_pat(Pat) -> + do_combine_lit_pat(Pat). + +do_combine_lit_pat(#cg_binary{segs=Segs}) -> + Bin = combine_bin_segs(Segs), + #b_literal{val=Bin}; +do_combine_lit_pat(#cg_cons{hd=Hd0,tl=Tl0}) -> + #b_literal{val=Hd} = do_combine_lit_pat(Hd0), + #b_literal{val=Tl} = do_combine_lit_pat(Tl0), + #b_literal{val=[Hd|Tl]}; +do_combine_lit_pat(#b_literal{}=Lit) -> + Lit; +do_combine_lit_pat(#cg_tuple{es=Es0}) -> + Es = [begin + #b_literal{val=Lit} = do_combine_lit_pat(El), + Lit + end || El <- Es0], + #b_literal{val=list_to_tuple(Es)}; +do_combine_lit_pat(_) -> + throw(not_possible). + +combine_bin_segs(#cg_bin_seg{size=#b_literal{val=8},unit=1,type=integer, + flags=[unsigned,big],seg=#b_literal{val=Int},next=Next}) + when is_integer(Int), 0 =< Int, Int =< 255 -> + <<Int,(combine_bin_segs(Next))/bits>>; +combine_bin_segs(#cg_bin_end{}) -> + <<>>; +combine_bin_segs(_) -> + throw(not_possible). + +%% handle_bin_con([Clause]) -> [{Type,[Clause]}]. +%% Handle clauses for the cg_bin_seg constructor. As cg_bin_seg +%% matching can overlap, the cg_bin_seg constructors cannot be +%% reordered, only grouped. + +handle_bin_con(Cs) -> + %% The usual way to match literals is to first extract the + %% value to a register, and then compare the register to the + %% literal value. Extracting the value is good if we need + %% compare it more than once. + %% + %% But we would like to combine the extracting and the + %% comparing into a single instruction if we know that + %% a binary segment must contain specific integer value + %% or the matching will fail, like in this example: + %% + %% <<42:8,...>> -> + %% <<42:8,...>> -> + %% . + %% . + %% . + %% <<42:8,...>> -> + %% <<>> -> + %% + %% The first segment must either contain the integer 42 + %% or the binary must end for the match to succeed. + %% + %% The way we do is to replace the generic #cg_bin_seg{} + %% record with a #cg_bin_int{} record if all clauses will + %% select the same literal integer (except for one or more + %% clauses that will end the binary). + try + {BinSegs0,BinEnd} = + partition(fun (C) -> + clause_con(C) =:= cg_bin_seg + end, Cs), + BinSegs = select_bin_int(BinSegs0), + case BinEnd of + [] -> BinSegs; + [_|_] -> BinSegs ++ [{cg_bin_end,BinEnd}] + end + catch + throw:not_possible -> + handle_bin_con_not_possible(Cs) + end. + +handle_bin_con_not_possible([C1|Cs]) -> + Con = clause_con(C1), + {More,Rest} = splitwith(fun (C) -> clause_con(C) =:= Con end, Cs), + [{Con,[C1|More]}|handle_bin_con_not_possible(Rest)]; +handle_bin_con_not_possible([]) -> []. + +%% select_bin_int([Clause]) -> {cg_bin_int,[Clause]} +%% If the first pattern in each clause selects the same integer, +%% rewrite all clauses to use #cg_bin_int{} (which will later be +%% translated to a bs_match_string/4 instruction). +%% +%% If it is not possible to do this rewrite, a 'not_possible' +%% exception is thrown. + +select_bin_int([#iclause{pats=[#cg_bin_seg{type=integer, + size=#b_literal{val=Bits0}=Sz,unit=U, + flags=Fl,seg=#b_literal{val=Val}, + next=N}|Ps]}=C|Cs0]) + when is_integer(Bits0), is_integer(U) -> + Bits = U * Bits0, + if + Bits > ?EXPAND_MAX_SIZE_SEGMENT -> + throw(not_possible); %Expands the code too much. + true -> + ok + end, + select_assert_match_possible(Bits, Val, Fl), + P = #cg_bin_int{size=Sz,unit=U,flags=Fl,val=Val,next=N}, + case member(native, Fl) of + true -> throw(not_possible); + false -> ok + end, + Cs1 = [C#iclause{pats=[P|Ps]}|select_bin_int_1(Cs0, Bits, Fl, Val)], + Cs = reorder_bin_ints(Cs1), + [{cg_bin_int,Cs}]; +select_bin_int(_) -> throw(not_possible). + +select_bin_int_1([#iclause{pats=[#cg_bin_seg{type=integer, + size=#b_literal{val=Bits0}=Sz, + unit=U, + flags=Fl,seg=#b_literal{val=Val}, + next=N}|Ps]}=C|Cs], + Bits, Fl, Val) when is_integer(Val) -> + if + Bits0*U =:= Bits -> ok; + true -> throw(not_possible) + end, + P = #cg_bin_int{size=Sz,unit=U,flags=Fl,val=Val,next=N}, + [C#iclause{pats=[P|Ps]}|select_bin_int_1(Cs, Bits, Fl, Val)]; +select_bin_int_1([], _, _, _) -> []; +select_bin_int_1(_, _, _, _) -> throw(not_possible). + +select_assert_match_possible(Sz, Val, Fs) + when is_integer(Sz), Sz >= 0, is_integer(Val) -> + EmptyBindings = erl_eval:new_bindings(), + MatchFun = match_fun(Val), + EvalFun = fun({integer,_,S}, B) -> {value,S,B} end, + Expr = [{bin_element,0,{integer,0,Val},{integer,0,Sz},[{unit,1}|Fs]}], + {value,Bin,EmptyBindings} = eval_bits:expr_grp(Expr, EmptyBindings, EvalFun), + try + {match,_} = eval_bits:match_bits(Expr, Bin, + EmptyBindings, + EmptyBindings, + MatchFun, EvalFun), + ok %this is just an assertion (i.e., no return value) + catch + throw:nomatch -> + throw(not_possible) + end; +select_assert_match_possible(_, _, _) -> + throw(not_possible). + +match_fun(Val) -> + fun(match, {{integer,_,_},NewV,Bs}) when NewV =:= Val -> + {match,Bs} + end. + +reorder_bin_ints([_]=Cs) -> + Cs; +reorder_bin_ints(Cs0) -> + %% It is safe to reorder clauses that match binaries if all + %% of the followings conditions are true: + %% + %% * The first segments for all of them match the same number of + %% bits (guaranteed by caller). + %% + %% * All segments have fixed sizes. + %% + %% * The patterns that follow are also safe to re-order. + try + Cs = sort([{reorder_bin_int_sort_key(C),C} || C <- Cs0]), + [C || {_,C} <- Cs] + catch + throw:not_possible -> + Cs0 + end. + +reorder_bin_int_sort_key(#iclause{pats=[Pat|More],guard=#c_literal{val=true}}) -> + case all(fun(#b_var{}) -> true; + (_) -> false + end, More) of + true -> + %% Only variables. Safe to re-order. + ok; + false -> + %% Not safe to re-order. For example: + %% f([<<"prefix">>, <<"action">>]) -> ... + %% f([<<"prefix">>, Variable]) -> ... + throw(not_possible) + end, + + %% Ensure that the remaining segments have fixed sizes. For example, the following + %% clauses are not safe to re-order: + %% f(<<"dd",_/binary>>) -> dd; + %% f(<<"d",_/binary>>) -> d. + ensure_fixed_size(Pat#cg_bin_int.next), + + case Pat of + #cg_bin_int{val=Val,next=#cg_bin_end{}} -> + %% Sort before clauses with additional segments. This + %% usually results in better code. + [Val]; + #cg_bin_int{val=Val} -> + [Val,more] + end; +reorder_bin_int_sort_key(#iclause{}) -> + throw(not_possible). + +ensure_fixed_size(#cg_bin_seg{size=Size,next=Next}) -> + case Size of + #b_literal{val=Sz} when is_integer(Sz) -> + ensure_fixed_size(Next); + _ -> + throw(not_possible) + end; +ensure_fixed_size(#cg_bin_end{}) -> + ok. + +%% match_value([Var], Con, [Clause], Default, State) -> {SelectExpr,State}. +%% At this point all the clauses have the same constructor; we must +%% now separate them according to value. + +match_value(Us0, T, Cs0, Def, St0) -> + {Us1,Cs1,St1} = partition_intersection(T, Us0, Cs0, St0), + UCss = group_value(T, Us1, Cs1), + mapfoldl(fun ({Us,Cs}, St) -> match_clause(Us, Cs, Def, St) end, St1, UCss). + +%% partition_intersection(Type, Us, [Clause], State) -> {Us,Cs,State}. +%% Partition a map into two maps with the most common keys to the +%% first map. +%% +%% case <M> of +%% <#{a,b}> +%% <#{a,c}> +%% <#{a}> +%% end +%% +%% becomes +%% +%% case <M,M> of +%% <#{a}, #{b}> +%% <#{a}, #{c}> +%% <#{a}, #{ }> +%% end +%% +%% The intention is to group as many keys together as possible and +%% thus reduce the number of lookups to that key. + +partition_intersection(cg_map, [U|_]=Us, [_,_|_]=Cs0, St0) -> + Ps = [clause_val(C) || C <- Cs0], + case find_key_intersection(Ps) of + none -> + {Us,Cs0,St0}; + Ks -> + Cs1 = map(fun(#iclause{pats=[Arg|Args]}=C) -> + {Arg1,Arg2} = partition_keys(Arg, Ks), + C#iclause{pats=[Arg1,Arg2|Args]} + end, Cs0), + {[U|Us],Cs1,St0} + end; +partition_intersection(_, Us, Cs, St) -> + {Us,Cs,St}. + +partition_keys(#cg_map{es=Pairs}=Map, Ks) -> + F = fun(#cg_map_pair{key=Key}) -> + sets:is_element(Key, Ks) + end, + {Ps1,Ps2} = partition(F, Pairs), + {Map#cg_map{es=Ps1},Map#cg_map{es=Ps2}}; +partition_keys(#ialias{pat=Map}=Alias, Ks) -> + %% Only alias one of them. + {Map1,Map2} = partition_keys(Map, Ks), + {Map1,Alias#ialias{pat=Map2}}. + +find_key_intersection(Ps) -> + Sets = [sets:from_list(Ks, [{version, 2}]) || Ks <- Ps], + Intersection = sets:intersection(Sets), + case sets:is_empty(Intersection) of + true -> + none; + false -> + All = all(fun (Kset) -> Kset =:= Intersection end, Sets), + case All of + true -> + %% All clauses test the same keys. Partitioning + %% the keys could only make the code worse. + none; + false -> + Intersection + end + end. + +%% group_value([Clause]) -> [[Clause]]. +%% Group clauses according to value. Here we know that: +%% 1. Some types are singled valued +%% 2. The clauses in maps and bin_segs cannot be reordered, +%% only grouped +%% 3. Other types are disjoint and can be reordered + +group_value(cg_cons, Us, Cs) -> [{Us,Cs}]; %These are single valued +group_value(cg_nil, Us, Cs) -> [{Us,Cs}]; +group_value(cg_binary, Us, Cs) -> [{Us,Cs}]; +group_value(cg_bin_end, Us, Cs) -> [{Us,Cs}]; +group_value(cg_bin_seg, Us, Cs) -> group_keeping_order(Us, Cs); +group_value(cg_bin_int, Us, Cs) -> [{Us,Cs}]; +group_value(cg_map, Us, Cs) -> group_keeping_order(Us, Cs); +group_value(_, Us, Cs) -> + Map = group_values(Cs), + + %% We must sort the grouped values to ensure consistent + %% order from compilation to compilation. + sort([{Us,Vcs} || _ := Vcs <- Map]). + +group_values(Cs) -> + F = fun(C) -> clause_val(C) end, + maps:groups_from_list(F, Cs). + +group_keeping_order(Us, [C1|Cs]) -> + V1 = clause_val(C1), + {More,Rest} = splitwith(fun (C) -> clause_val(C) =:= V1 end, Cs), + [{Us,[C1|More]}|group_keeping_order(Us, Rest)]; +group_keeping_order(_, []) -> []. + +%% match_clause([Var], [Clause], Default, State) -> {Clause,State}. +%% At this point all the clauses have the same "value". Build one +%% select clause for this value and continue matching. Rename +%% aliases as well. + +match_clause([U|Us], [#iclause{anno=Anno}|_]=Cs0, Def, St0) -> + {Match,Vs,St1} = get_match(get_con(Cs0), St0), + Cs1 = new_clauses(Cs0, U), + Cs2 = squeeze_clauses(Cs1, []), + {B,St2} = match(Vs ++ Us, Cs2, Def, St1), + {#cg_val_clause{anno=Anno,val=Match,body=B},St2}. + +get_con([C|_]) -> arg_arg(clause_arg(C)). %Get the constructor + +get_match(#cg_cons{}, St0) -> + {[H,T]=L,St1} = new_vars(2, St0), + {#cg_cons{hd=H,tl=T},L,St1}; +get_match(#cg_binary{}, St0) -> + {V,St1} = new_var(St0), + {#cg_binary{segs=V},[V],St1}; +get_match(#cg_bin_seg{size=#b_literal{val=all},next=#cg_bin_end{}}=Seg, St0) -> + {[S,N],St1} = new_vars(2, St0), + {Seg#cg_bin_seg{seg=S,next=N},[S],St1}; +get_match(#cg_bin_seg{}=Seg, St0) -> + {[S,N],St1} = new_vars(2, St0), + {Seg#cg_bin_seg{seg=S,next=N},[S,N],St1}; +get_match(#cg_bin_int{}=BinInt, St0) -> + {N,St1} = new_var(St0), + {BinInt#cg_bin_int{next=N},[N],St1}; +get_match(#cg_tuple{es=Es}, St0) -> + {Mes,St1} = new_vars(length(Es), St0), + {#cg_tuple{es=Mes},Mes,St1}; +get_match(#cg_map{op=exact,es=Es0}, St0) -> + {Mes,St1} = new_vars(length(Es0), St0), + {Es,_} = mapfoldl(fun(#cg_map_pair{}=Pair, [V|Vs]) -> + {Pair#cg_map_pair{val=V},Vs} + end, Mes, Es0), + {#cg_map{op=exact,es=Es},Mes,St1}; +get_match(M, St) -> + {M,[],St}. + +new_clauses(Cs, #b_var{name=U}) -> + map(fun(#iclause{sub=Sub0,pats=[Arg|As]}=C) -> + Head = case arg_arg(Arg) of + #cg_cons{hd=H,tl=T} -> [H,T|As]; + #cg_tuple{es=Es} -> Es ++ As; + #cg_binary{segs=E} -> [E|As]; + #cg_bin_seg{size=#b_literal{val=all}, + seg=S,next=#cg_bin_end{}} -> + [S|As]; + #cg_bin_seg{seg=S,next=N} -> + [S,N|As]; + #cg_bin_int{next=N} -> + [N|As]; + #cg_map{op=exact,es=Es} -> + Vals = [V || #cg_map_pair{val=V} <- Es], + Vals ++ As; + _Other -> + As + end, + Vs = arg_alias(Arg), + Sub1 = foldl(fun (#b_var{name=V}, Acc) -> + subst_vsub(V, U, Acc) + end, Sub0, Vs), + C#iclause{sub=Sub1,pats=Head} + end, Cs). + +%%% +%%% Group and squeeze +%%% +%%% The goal of those functions is to group subsequent integer +%%% cg_bin_seg literals by count so we can leverage bs_get_integer_16 +%%% whenever possible. +%%% +%%% The priority is to create large groups. So if we have three +%%% clauses matching on 16-bits/16-bits/8-bits, we will first have a +%%% single 8-bits match for all three clauses instead of clauses (one +%%% with 16 and another with 8). But note the algorithm is recursive, +%%% so the remaining 8-bits for the first two clauses will be grouped +%%% next. +%%% +%%% We also try to avoid creating too large groups. If we have too +%%% many clauses, it is preferable to match on 8 bits, select a +%%% branch, then match on the next 8 bits, rather than match on +%%% 16 bits which would force us to have to select too many values at +%%% the same time, which would not be efficient. +%%% +%%% Another restriction is that we create groups only if the end of +%%% the group is a variadic clause or the end of the binary. That's +%%% because if we have 16-bits/16-bits/catch-all, breaking it into a +%%% 16-bits lookup will make the catch-all more expensive. +%%% +%%% Clauses are grouped in reverse when squeezing and then flattened and +%%% re-reversed at the end. +%%% + +squeeze_clauses([C|Cs], Acc) -> + case clause_count_segments(C) of + {literal,N} -> + squeeze_clauses(Cs, N, 1, [C], Acc); + _ -> + squeeze_clauses(Cs, [[C]|Acc]) + end; +squeeze_clauses(_, Acc) -> + flat_reverse(Acc). + +squeeze_clauses([C|Cs], N0, Count, GroupAcc, Acc) -> + case clause_count_segments(C) of + {literal,N} -> + squeeze_clauses(Cs, min(N0, N), Count + 1, + [C|GroupAcc], Acc); + {variadic,N} when N =< N0 -> + Squeezed = do_squeeze_clauses(GroupAcc, N, Count), + squeeze_clauses(Cs, [[C|Squeezed] | Acc]); + bin_end when Cs =:= [] -> + Squeezed = do_squeeze_clauses(GroupAcc, fix_count(N0), Count), + flat_reverse([[C|Squeezed] | Acc]); + _ -> + squeeze_clauses(Cs, [[C|GroupAcc] | Acc]) + end; +squeeze_clauses([], N, Count, GroupAcc, Acc) -> + Squeezed = do_squeeze_clauses(GroupAcc, fix_count(N), Count), + flat_reverse([Squeezed|Acc]). + +clause_count_segments(#iclause{pats=[P|_]}) -> + case P of + #cg_bin_seg{seg=#b_literal{}} -> + count_segments(P, 0); + #cg_bin_seg{size=#b_literal{val=Size}, + unit=Unit, + type=integer, + flags=[unsigned,big], + seg=#b_var{}} when ((Size * Unit) rem 8) =:= 0 -> + {variadic, (Size * Unit) div 8}; + #cg_bin_end{} -> + bin_end; + _ -> + error + end; +clause_count_segments(_) -> error. + +count_segments(#cg_bin_seg{size=#b_literal{val=8}, + unit=1,type=integer,flags=[unsigned,big], + seg=#b_literal{val=Int},next=Next}, Count) + when is_integer(Int), 0 =< Int, Int =< 255 -> + count_segments(Next, Count + 1); +count_segments(_, Count) when Count > 0 -> + {literal,Count}; +count_segments(_, _Count) -> + error. + +%% Since 4 bytes in on 32-bits systems are bignums, we convert +%% anything more than 3 bytes into a 2-byte lookup. The goal is to +%% convert any multi-clause segment into 2-byte lookups with a +%% potential 3-byte lookup at the end. +fix_count(N) when N > 3 -> 2; +fix_count(N) -> N. + +do_squeeze_clauses(Cs, Size, Count) when Count >= 16; Size =< 1 -> + %% If we have more than 16 clauses it is better to branch multiple + %% times than getting a large integer. We also give up if we have + %% nothing to squeeze. + Cs; +do_squeeze_clauses(Cs, Size, _Count) -> + [C#iclause{pats=[squeeze_segments(P, Size)|Pats]} || + #iclause{pats=[P|Pats]}=C <- Cs]. + +squeeze_segments(BinSeg, Size) -> + squeeze_segments(BinSeg, 0, 0, Size). + +squeeze_segments(#cg_bin_seg{seg=#b_literal{val=Val},next=Next}=BinSeg, + Acc0, Size0, Count) -> + Acc = (Acc0 bsl 8) bor Val, + Size = Size0 + 8, + case Count of + 1 -> + BinSeg#cg_bin_seg{size=#b_literal{val=Size}, + seg=#b_literal{val=Acc}}; + _ -> + squeeze_segments(Next, Acc, Size, Count - 1) + end. + +flat_reverse(L) -> + flat_reverse(L, []). + +flat_reverse([H|T], Acc) -> + flat_reverse(T, reverse(H, Acc)); +flat_reverse([], Acc) -> Acc. + +%%% +%%% End of group and squeeze +%%% + +%% build_guard([GuardClause]) -> GuardExpr. + +build_guard([]) -> fail; +build_guard(Cs) -> #cg_guard{clauses=Cs}. + +%% build_select(Var, [ConClause]) -> SelectExpr. + +build_select(V, [#cg_type_clause{anno=Anno}|_]=Tcs) -> + #cg_select{anno=Anno,var=V,types=Tcs}. + +%% build_alt(First, Then) -> AltExpr. +%% Build an alt. + +build_alt(fail, Then) -> Then; +build_alt(First, fail) -> First; +build_alt(First, Then) -> + Anno = get_anno(First), + #cg_alt{anno=Anno,first=First,then=Then}. + +%% build_match(MatchExpr) -> Kexpr. +%% Build a match expr if there is a match. + +build_match(#cg_alt{}=Km) -> #cg_match{body=Km}; +build_match(#cg_select{}=Km) -> #cg_match{body=Km}; +build_match(#cg_guard{}=Km) -> #cg_match{body=Km}; +build_match(Km) -> Km. + +%% clause_arg(Clause) -> FirstArg. +%% clause_con(Clause) -> Constructor. +%% clause_val(Clause) -> Value. +%% is_var_clause(Clause) -> boolean(). + +clause_arg(#iclause{pats=[Arg|_]}) -> Arg. + +clause_con(C) -> arg_con(clause_arg(C)). + +clause_val(C) -> arg_val(clause_arg(C), C). + +is_var_clause(C) -> clause_con(C) =:= b_var. + +%% arg_arg(Arg) -> Arg. +%% arg_alias(Arg) -> Aliases. +%% arg_con(Arg) -> Constructor. +%% arg_val(Arg) -> Value. +%% These are the basic functions for obtaining fields in an argument. + +arg_arg(#ialias{pat=Con}) -> Con; +arg_arg(Con) -> Con. + +arg_alias(#ialias{vars=As}) -> As; +arg_alias(_Con) -> []. + +arg_con(Arg) -> + case arg_arg(Arg) of + #cg_cons{} -> cg_cons; + #cg_tuple{} -> cg_tuple; + #cg_map{} -> cg_map; + #cg_binary{} -> cg_binary; + #cg_bin_end{} -> cg_bin_end; + #cg_bin_seg{} -> cg_bin_seg; + #b_var{} -> b_var; + #b_literal{val=Val} -> + if + is_atom(Val) -> cg_atom; + is_integer(Val) -> cg_int; + is_float(Val) -> cg_float; + Val =:= [] -> cg_nil; + true -> b_literal + end + end. + +arg_val(Arg, C) -> + case arg_arg(Arg) of + #b_literal{val=Lit} -> Lit; + #cg_tuple{es=Es} -> length(Es); + #cg_bin_seg{size=S,unit=U,type=T,flags=Fs} -> + case S of + #b_var{name=V} -> + #iclause{sub=Sub} = C, + {#b_var{name=get_vsub(V, Sub)},U,T,Fs}; + #b_literal{} -> + {S,U,T,Fs} + end; + #cg_map{op=exact,es=Es} -> + sort(fun(A, B) -> + %% Keys are #b_var{} | #b_literal{}. + %% Literals will sort before variables + %% as intended. + erts_internal:cmp_term(A, B) < 0 + end, [Key || #cg_map_pair{key=Key} <- Es]) + end. + +%%% +%%% Handling of errors and warnings (generated by the first pass). +%%% + +maybe_add_warning([C|_], MatchAnno, St) -> + maybe_add_warning(C, MatchAnno, St); +maybe_add_warning([], _MatchAnno, St) -> St; +maybe_add_warning(fail, _MatchAnno, St) -> St; +maybe_add_warning(Ke, MatchAnno, St) -> + Anno = get_anno(Ke), + case member(compiler_generated, Anno) of + true -> + St; + false -> + Warn = case get_location(MatchAnno) of + none -> + {nomatch,shadow}; + {MatchLine,_} when is_integer(MatchLine) -> + {nomatch,{shadow,MatchLine}}; + MatchLine when is_integer(MatchLine) -> + {nomatch,{shadow,MatchLine}} + end, + add_warning(Anno, Warn, St) + end. + +add_warning(Anno, Term, #kern{ws=Ws}=St) -> + Location = get_location(Anno), + File = get_file(Anno), + St#kern{ws=[{File,[{Location,?MODULE,Term}]}|Ws]}. + +get_location([Line|_]) when is_integer(Line) -> + Line; +get_location([{Line,Column}|_]) when is_integer(Line), is_integer(Column) -> + {Line,Column}; +get_location([_|T]) -> + get_location(T); +get_location([]) -> + none. + +get_file([{file,File}|_]) -> File; +get_file([_|T]) -> get_file(T); +get_file([]) -> "no_file". %Should not happen + +%%% +%%% Second pass: Variable usage and lambda lifting. +%%% + +%% ubody_used_vars(Expr, State) -> [UsedVar] +%% Return all used variables for the body sequence. Much more +%% efficient than using ubody/3 if the body contains nested letrecs. +ubody_used_vars(Expr, St) -> + {_,Used,_} = ubody(Expr, return, St#kern{funs=ignore}), + Used. + +%% ubody(Expr, Break, State) -> {Expr,[UsedVar],State}. +%% Tag the body sequence with its used variables. These bodies +%% either end with a #cg_break{}, #b_ret{} or, an expression +%% which itself can return, such as #cg_match{}. + +ubody(#ilet{vars=[],arg=#iletrec{}=Let,body=B0}, Br, St0) -> + %% An iletrec{} should never be last. + St = iletrec_funs(Let, St0), + ubody(B0, Br, St); +ubody(#ilet{vars=[],arg=#b_literal{},body=B0}, Br, St0) -> + ubody(B0, Br, St0); +ubody(#ilet{vars=[],arg=#b_var{},body=B0}, Br, St0) -> + ubody(B0, Br, St0); +ubody(#ilet{vars=Vs,arg=E0,body=B0}, Br, St0) -> + {E1,Eu,St1} = uexpr(E0, {break,Vs}, St0), + {B1,Bu,St2} = ubody(B0, Br, St1), + Ns = atomic_list_vars(Vs), + Used = union(Eu, subtract(Bu, Ns)), %Used external vars + {#cg_seq{arg=E1,body=B1},Used,St2}; +ubody(#ivalues{args=As}, {break,_Vbs}, St) -> + Au = atomic_list_vars(As), + {#cg_break{args=As},Au,St}; +ubody(#cg_break{args=As}=Break, {break,_Vbs}, St) -> + Au = atomic_list_vars(As), + {Break,Au,St}; +ubody(#b_ret{arg=Arg}=Ret, return, St) -> + Used = atomic_vars(Arg), + {Ret,Used,St}; +ubody(#cg_goto{args=As}=Goto, _Br, St) -> + Au = atomic_list_vars(As), + {Goto,Au,St}; +ubody(#cg_letrec_goto{}=E, return, St) -> + uexpr(E, return, St); +ubody(#cg_match{}=E, return, St) -> + uexpr(E, return, St); +ubody(#cg_try{}=E, return, St) -> + uexpr(E, return, St); +ubody(E, return, St0) -> + {Ea,Pa,St1} = force_atomic(E, St0), + ubody(pre_seq(Pa, #b_ret{arg=Ea}), return, St1); +ubody(E, {break,[_]}=Break, St0) -> + {Ea,Pa,St1} = force_atomic(E, St0), + ubody(pre_seq(Pa, #cg_break{args=[Ea]}), Break, St1); +ubody(E, {break,Rs}=Break, St0) -> + {Vs,St1} = new_vars(length(Rs), St0), + PreSeq = #ilet{vars=Vs,arg=E,body=#cg_break{args=Vs}}, + ubody(PreSeq, Break, St1). + +iletrec_funs(#iletrec{defs=Fs}, St0) -> + %% Use union of all free variables. + %% First just work out free variables for all functions. + Free = foldl(fun ({_,#ifun{vars=Vs,body=Fb0}}, Free0) -> + Fbu = ubody_used_vars(Fb0, St0), + Ns = atomic_list_vars(Vs), + Free1 = subtract(Fbu, Ns), + union(Free1, Free0) + end, [], Fs), + FreeVs = make_vars(Free), + %% Add this free info to State. + St1 = foldl(fun ({N,#ifun{vars=Vs}}, Lst) -> + store_free(N, length(Vs), FreeVs, Lst) + end, St0, Fs), + iletrec_funs_gen(Fs, FreeVs, St1). + +%% Now regenerate local functions to use free variable information. +iletrec_funs_gen(_, _, #kern{funs=ignore}=St) -> + %% Optimization: The ultimate caller is only interested in the used variables, + %% not the updated state. Makes a difference if there are nested letrecs. + St; +iletrec_funs_gen(Fs, FreeVs, St0) -> + foldl(fun ({N,#ifun{anno=Fa,vars=Vs,body=Fb0}}, Lst0) -> + {Fb1,_,Lst1} = ubody(Fb0, return, Lst0), + Fun = make_ssa_function(Fa, N, Vs++FreeVs, Fb1, Lst1), + Lst1#kern{funs=[Fun|Lst1#kern.funs]} + end, St0, Fs). + +%% uexpr(Expr, Break, State) -> {Expr,[UsedVar],State}. +%% Calculate the used variables for an expression. +%% Break = return | {break,[RetVar]}. + +uexpr(#cg_test{args=As}=Test, {break,Rs}, St) -> + [] = Rs, %Sanity check + Used = atomic_list_vars(As), + {Test,Used,St}; +uexpr(#ilet{vars=Vs,arg=E0,body=B0}, {break,_}=Br, St0) -> + Ns = atomic_list_vars(Vs), + {E1,Eu,St1} = uexpr(E0, {break,Vs}, St0), + {B1,Bu,St2} = uexpr(B0, Br, St1), + Used = union(Eu, subtract(Bu, Ns)), + {#cg_seq{arg=E1,body=B1},Used,St2}; +uexpr(#cg_call{op=#b_local{name=#b_literal{val=F},arity=Ar}=Op,args=As0}=Call, + {break,Rs0}, St0) -> + {Rs,St} = ensure_return_vars(Rs0, St0), + Free = get_free(F, Ar, St), + As1 = As0 ++ Free, %Add free variables LAST! + Used = atomic_list_vars(As1), + {Call#cg_call{op=Op#b_local{arity=Ar + length(Free)}, + args=As1,ret=Rs},Used,St}; +uexpr(#cg_call{anno=A,op=Op,args=As}=Call0, {break,Rs0}, St0) -> + {[R|Rs],St} = ensure_return_vars(Rs0, St0), + Used = union(op_vars(Op), atomic_list_vars(As)), + Call = Call0#cg_call{anno=A,ret=[R]}, + Seq = set_unused(Rs, Call), + {Seq,Used,St}; +uexpr(#cg_internal{args=As}=Internal, {break,Rs}, St0) -> + Used = atomic_list_vars(As), + {Brs,St1} = internal_returns(Internal, Rs, St0), + {Internal#cg_internal{ret=Brs},Used,St1}; +uexpr(#cg_match{body=B0}=Match, Br, St0) -> + Rs = break_rets(Br), + {B1,Bu,St1} = umatch(B0, Br, St0), + {Match#cg_match{body=B1,ret=Rs},Bu,St1}; +uexpr(#cg_try{arg=A0,vars=Vs,body=B0,evars=Evs,handler=H0}=Try, Br, St0) -> + Rs = break_rets(Br), + {Avs,St1} = new_vars(length(Vs), St0), + {A1,Au,St2} = ubody(A0, {break,Avs}, St1), + {B1,Bu,St3} = ubody(B0, Br, St2), + {H1,Hu,St4} = ubody(H0, Br, St3), + Used = union([Au,subtract(Bu, atomic_list_vars(Vs)), + subtract(Hu, atomic_list_vars(Evs))]), + {Try#cg_try{arg=A1,vars=Vs,body=B1,evars=Evs,handler=H1,ret=Rs}, + Used,St4}; +uexpr(#cg_catch{body=B0}=Catch, {break,Rs0}, St0) -> + {Rb,St1} = new_var(St0), + {B1,Bu,St2} = ubody(B0, {break,[Rb]}, St1), + %% Guarantee ONE return variable. + {Ns,St3} = new_vars(1 - length(Rs0), St2), + Rs1 = Rs0 ++ Ns, + {Catch#cg_catch{body=B1,ret=Rs1},Bu,St3}; +uexpr(#ifun{anno=A,vars=Vs,body=B0}, {break,Rs}, St0) -> + {B1,Bu,St1} = ubody(B0, return, St0), %Return out of new function + Ns = atomic_list_vars(Vs), + Free = subtract(Bu, Ns), %Free variables in fun + Fvs = make_vars(Free), + Arity = length(Vs) + length(Free), + {Fname,St2} = + case keyfind(id, 1, A) of + {id,{_,_,Fname0}} -> + {Fname0,St1}; + false -> + %% No id annotation. Must invent a fun name. + new_fun_name(St1) + end, + Fun = make_ssa_function(A, Fname, Vs++Fvs, B1, St2), + Local = #b_local{name=#b_literal{val=Fname},arity=Arity}, + {MakeFun,St3} = make_fun(Rs, Local, Fvs, St2), + {MakeFun,Free,add_local_function(Fun, St3)}; +uexpr(#b_local{name=#b_literal{val=Name},arity=Arity}=Local0, {break,Rs}, St0) -> + Free = atomic_list_vars(get_free(Name, Arity, St0)), + Fvs = make_vars(Free), + FreeCount = length(Fvs), + Local = Local0#b_local{arity=Arity+FreeCount}, + {MakeFun,St1} = make_fun(Rs, Local, Fvs, St0), + {MakeFun,Free,St1}; +uexpr(#cg_letrec_goto{vars=Vs,first=F0,then=T0}=LetrecGoto, Br, St0) -> + Rs = break_rets(Br), + Ns = atomic_list_vars(Vs), + {F1,Fu,St1} = ubody(F0, Br, St0), + {T1,Tu,St2} = ubody(T0, Br, St1), + Used = subtract(union(Fu, Tu), Ns), + {LetrecGoto#cg_letrec_goto{first=F1,then=T1,ret=Rs},Used,St2}; +uexpr(#b_set{dst=none,args=Args}=Set, {break,[Dst]}, St) -> + Used = atomic_list_vars(Args), + {Set#b_set{dst=Dst},Used,St}; +uexpr(#b_set{dst=none,args=Args}=Set0, {break,Rs0}, St0) -> + Used = atomic_list_vars(Args), + {[Dst|Ds],St1} = ensure_return_vars(Rs0, St0), + Seq = set_unused(Ds, Set0#b_set{dst=Dst}), + {Seq,Used,St1}; +uexpr(#cg_succeeded{set=Set0}, {break,_}=Br, St0) -> + {Set,Used,St1} = uexpr(Set0, Br, St0), + {#cg_succeeded{set=Set},Used,St1}; +uexpr(#cg_opaque{}=Opaque, _, St) -> + {Opaque,[],St}; +uexpr(Atomic, {break,[Dst]}, St0) -> + Used = atomic_vars(Atomic), + {#b_set{op=copy,dst=Dst,args=[Atomic]},Used,St0}. + +make_fun(Rs, Local, FreeVars, St0) -> + {[Dst],St1} = ensure_return_vars(Rs, St0), + Op = case St1 of + #kern{no_make_fun3=false} -> make_fun; + #kern{no_make_fun3=true} -> old_make_fun + end, + {#b_set{op=Op,dst=Dst,args=[Local|FreeVars]},St1}. + +add_local_function(_, #kern{funs=ignore}=St) -> + St; +add_local_function(#b_function{anno=Anno}=F, + #kern{funs=Funs}=St) -> + FuncInfo = map_get(func_info, Anno), + case is_defined(FuncInfo, Funs) of + false -> + St#kern{funs=[F|Funs]}; + true -> + St + end. + +is_defined(FuncInfo, [#b_function{anno=Anno}|Fs]) -> + case Anno of + #{func_info := FuncInfo} -> true; + #{} -> is_defined(FuncInfo, Fs) + end; +is_defined(_, []) -> false. + +set_unused([D|Ds], Seq) -> + Copy = #b_set{op=copy,dst=D,args=[#b_literal{val=unused}]}, + set_unused(Ds, #cg_seq{arg=Copy,body=Seq}); +set_unused([], Seq) -> Seq. + +%% get_free(Name, Arity, State) -> [Free]. +%% store_free(Name, Arity, [Free], State) -> State. + +get_free(F, A, #kern{free=FreeMap}) -> + case FreeMap of + #{{F,A} := Val} -> Val; + #{} -> [] + end. + +store_free(F, A, Free, #kern{free=FreeMap0}=St) -> + FreeMap = FreeMap0#{{F,A} => Free}, + St#kern{free=FreeMap}. + +break_rets({break,Rs}) -> Rs; +break_rets(return) -> []. + +%% internal_returns(Op, [Ret], State) -> {[Ret],State}. +%% Fix return values for #cg_internal{}. + +internal_returns(#cg_internal{op=Op,args=Args}, Rs, St0) -> + Ar = length(Args), + NumReturns = case {Op,Ar} of + {recv_peek_message,0} -> 2; + {_,_} -> 1 + end, + {Ns,St1} = new_vars(NumReturns - length(Rs), St0), + {Rs ++ Ns,St1}. + +%% ensure_return_vars([Ret], State) -> {[Ret],State}. + +ensure_return_vars([], St) -> new_vars(1, St); +ensure_return_vars([_|_]=Rs, St) -> {Rs,St}. + +%% umatch(Match, Break, State) -> {Match,[UsedVar],State}. +%% Calculate the used variables for a match expression. + +umatch(#cg_alt{first=F0,then=T0}=Alt, Br, St0) -> + {F1,Fu,St1} = umatch(F0, Br, St0), + {T1,Tu,St2} = umatch(T0, Br, St1), + Used = union(Fu, Tu), + {Alt#cg_alt{first=F1,then=T1},Used,St2}; +umatch(#cg_select{var=#b_var{name=Var},types=Ts0}=Select, Br, St0) -> + {Ts1,Tus,St1} = umatch_list(Ts0, Br, St0), + Used = add_element(Var, Tus), + {Select#cg_select{types=Ts1},Used,St1}; +umatch(#cg_type_clause{values=Vs0}=TypeClause, Br, St0) -> + {Vs1,Vus,St1} = umatch_list(Vs0, Br, St0), + {TypeClause#cg_type_clause{values=Vs1},Vus,St1}; +umatch(#cg_val_clause{val=P0,body=B0}=ValClause, Br, St0) -> + {U0,Ps} = pat_vars(P0), + {B1,Bu,St1} = umatch(B0, Br, St0), + P = pat_mark_unused(P0, Bu, Ps), + Used = union(U0, subtract(Bu, Ps)), + {ValClause#cg_val_clause{val=P,body=B1},Used,St1}; +umatch(#cg_guard{clauses=Gs0}=Guard, Br, St0) -> + {Gs1,Gus,St1} = umatch_list(Gs0, Br, St0), + {Guard#cg_guard{clauses=Gs1},Gus,St1}; +umatch(#cg_guard_clause{guard=G0,body=B0}=GuardClause, Br, St0) -> + {G1,Gu,St1} = uexpr(G0, {break,[]}, St0), + {B1,Bu,St2} = umatch(B0, Br, St1), + Used = union(Gu, Bu), + {GuardClause#cg_guard_clause{guard=G1,body=B1},Used,St2}; +umatch(B0, Br, St0) -> ubody(B0, Br, St0). + +umatch_list(Ms0, Br, St) -> + foldr(fun (M0, {Ms1,Us,Sta}) -> + {M1,Mu,Stb} = umatch(M0, Br, Sta), + {[M1|Ms1],union(Mu, Us),Stb} + end, {[],[],St}, Ms0). + +pat_mark_unused(#cg_tuple{es=Es0}=P, Used0, Ps) -> + %% Not extracting unused tuple elements is an optimization for + %% compile time and memory use during compilation. It is probably + %% worthwhile because it is common to extract only a few elements + %% from a huge record. + Used = intersection(Used0, Ps), + Es = [case member(V, Used) of + true -> Var; + false -> #b_literal{val=unused} + end || #b_var{name=V}=Var <- Es0], + P#cg_tuple{es=Es}; +pat_mark_unused(P, _Used, _Ps) -> P. + +%% op_vars(Op) -> [VarName]. + +op_vars(#b_remote{mod=Mod,name=Name}) -> + atomic_list_vars([Mod,Name]); +op_vars(Atomic) -> atomic_vars(Atomic). + +%% atomic_vars(Literal) -> [VarName]. +%% Return the variables in an atomic (variable or literal). + +atomic_vars(#b_var{name=N}) -> [N]; +atomic_vars(#b_literal{}) -> []. + +atomic_list_vars(Ps) -> + foldl(fun (P, Vs) -> union(atomic_vars(P), Vs) end, [], Ps). + +%% pat_vars(Pattern) -> {[UsedVarName],[NewVarName]}. +%% Return variables in a pattern. All variables are new variables +%% except those in the size field of binary segments and the key +%% field in map_pairs. + +pat_vars(#b_var{name=N}) -> {[],[N]}; +pat_vars(#b_literal{}) -> {[],[]}; +pat_vars(#cg_cons{hd=H,tl=T}) -> + pat_list_vars([H,T]); +pat_vars(#cg_binary{segs=V}) -> + pat_vars(V); +pat_vars(#cg_bin_seg{size=Size,seg=S,next=N}) -> + {U1,New} = pat_list_vars([S,N]), + {[],U2} = pat_vars(Size), + {union(U1, U2),New}; +pat_vars(#cg_bin_int{size=Size,next=N}) -> + {[],New} = pat_vars(N), + {[],U} = pat_vars(Size), + {U,New}; +pat_vars(#cg_bin_end{}) -> {[],[]}; +pat_vars(#cg_tuple{es=Es}) -> + pat_list_vars(Es); +pat_vars(#cg_map{es=Es}) -> + pat_list_vars(Es); +pat_vars(#cg_map_pair{key=K,val=V}) -> + {U1,New} = pat_vars(V), + {[],U2} = pat_vars(K), + {union(U1, U2),New}. + +pat_list_vars(Ps) -> + foldl(fun (P, {Used0,New0}) -> + {Used,New} = pat_vars(P), + {union(Used0, Used),union(New0, New)} end, + {[],[]}, Ps). + +%%% +%%% Third pass: Translation to SSA code. +%%% + +-type label() :: beam_ssa:label(). + +%% Main codegen structure for the SSA pass (formerly `beam_kernel_to_ssa`). +-record(cg, {lcount=1 :: label(), %Label counter + bfail=1 :: label(), + catch_label=none :: 'none' | label(), + vars=#{} :: map(), %Defined variables. + break=0 :: label(), %Break label + checks=[] :: [term()] + }). + +make_ssa_function(Anno0, Name, As, #cg_match{}=Body, + #kern{module=Mod,vcount=Count0}) -> + Anno1 = line_anno(Anno0), + Anno2 = Anno1#{func_info => {Mod,Name,length(As)}}, + St0 = #cg{lcount=Count0}, + {Asm,St} = cg_fun(Body, St0), + #cg{checks=Checks,lcount=Count} = St, + Anno = case Checks of + [] -> + Anno2; + [_|_] -> + Anno2#{ssa_checks => Checks} + end, + #b_function{anno=Anno,args=As,bs=Asm,cnt=Count}; +make_ssa_function(Anno, Name, As, Body, St) -> + Match = #cg_match{body=Body,ret=[]}, + make_ssa_function(Anno, Name, As, Match, St). + +cg_fun(Ke, St0) -> + {FailIs,St1} = make_exception_block(St0), + {B,St} = cg(Ke, St1), + Asm0 = [{label,0}|B++FailIs], + Asm = fix_phis(Asm0), + {build_map(Asm),St}. + +make_exception_block(St0) -> + {Dst,St} = new_ssa_var(St0), + Is = [{label,?EXCEPTION_BLOCK}, + #b_set{op=call,dst=Dst, + args=[#b_remote{mod=#b_literal{val=erlang}, + name=#b_literal{val=error}, + arity=1}, + #b_literal{val=badarg}]}, + #b_ret{arg=Dst}], + {Is,St#cg{bfail=?EXCEPTION_BLOCK}}. + +%% cg(Lkexpr, State) -> {[Ainstr],State}. +%% Generate SSA code. + +cg(#b_set{op=copy,dst=#b_var{name=Dst},args=[Arg0]}, St0) -> + %% Create an alias for a variable or literal. + Arg = ssa_arg(Arg0, St0), + St = set_ssa_var(Dst, Arg, St0), + {[],St}; +cg(#b_set{args=Args0}=Set0, St) -> + Args = ssa_args(Args0, St), + Set = Set0#b_set{args=Args}, + {[Set],St}; +cg(#b_ret{arg=Ret0}, St) -> + Ret = ssa_arg(Ret0, St), + {[#b_ret{arg=Ret}],St}; +cg(#cg_succeeded{set=Set0}, St0) -> + {[#b_set{dst=Dst}=Set],St1} = cg(Set0, St0), + FailCtx = fail_context(St1), + {Is,St} = make_succeeded(Dst, FailCtx, St1), + {[Set|Is],St}; +cg(#cg_match{body=M,ret=Rs}, #cg{bfail=Bfail,break=OldBreak}=St0) -> + {B,St1} = new_label(St0), + {Mis,St2} = match_cg(M, Bfail, St1#cg{break=B}), + St = St2#cg{break=OldBreak}, + {Mis ++ [{label,B},#cg_phi{vars=Rs}],St}; +cg(#cg_seq{arg=Arg,body=Body}, St0) -> + {ArgIs,St1} = cg(Arg, St0), + {BodyIs,St} = cg(Body, St1), + {ArgIs ++ BodyIs,St}; +cg(#cg_call{anno=Anno,op=Func,args=As,ret=Rs}, St) -> + call_cg(Func, As, Rs, Anno, St); +cg(#cg_internal{anno=Anno,op=Op,args=As,ret=Rs}, St) -> + internal_cg(Anno, Op, As, Rs, St); +cg(#cg_try{arg=Ta,vars=Vs,body=Tb,evars=Evs,handler=Th,ret=Rs}, St) -> + try_cg(Ta, Vs, Tb, Evs, Th, Rs, St); +cg(#cg_catch{body=Cb,ret=[R]}, St) -> + catch_cg(Cb, R, St); +cg(#cg_break{args=Bs}, #cg{break=Br}=St) -> + Args = ssa_args(Bs, St), + {[#cg_break{args=Args,phi=Br}],St}; +cg(#cg_letrec_goto{label=Tf,vars=Vs,first=First,then=Then,ret=BreakVars}, + #cg{break=OldBreak}=St0) -> + {B,St1} = new_label(St0), + {Fis,St2} = cg(First, St1#cg{break=B}), + {Sis,St} = cg(Then, St2), + PostPhi = #cg_phi{vars=BreakVars}, + FailPhi = case Vs of + [] -> []; + [_|_] -> [#cg_phi{vars=Vs}] + end, + {Fis ++ [{label,Tf}] ++ FailPhi ++ Sis ++ [{label,B},PostPhi], + St#cg{break=OldBreak}}; +cg(#cg_goto{label=Label,args=[]}, St) -> + {[make_uncond_branch(Label)],St}; +cg(#cg_goto{label=Label,args=As0}, St) -> + As = ssa_args(As0, St), + Break = #cg_break{args=As,phi=Label}, + {[Break],St}; +cg(#cg_opaque{val=Check}, St) -> + {ssa_check_when,_,_,_,_} = Check, %Assertion. + {[],St#cg{checks=[Check|St#cg.checks]}}. + +%% match_cg(Match, Fail, State) -> {[Ainstr],State}. +%% Generate code for a match tree. + +match_cg(#cg_alt{first=F,then=S}, Fail, St0) -> + {Tf,St1} = new_label(St0), + {Fis,St2} = match_cg(F, Tf, St1), + {Sis,St3} = match_cg(S, Fail, St2), + {Fis ++ [{label,Tf}] ++ Sis,St3}; +match_cg(#cg_select{var=#b_var{}=Src0,types=Scs}, Fail, St) -> + Src = ssa_arg(Src0, St), + match_fmf(fun (#cg_type_clause{type=Type,values=Vs}, F, Sta) -> + select_cg(Type, Vs, Src, F, Fail, Sta) + end, Fail, St, Scs); +match_cg(#cg_guard{clauses=Gcs}, Fail, St) -> + match_fmf(fun (G, F, Sta) -> + guard_clause_cg(G, F, Sta) + end, Fail, St, Gcs); +match_cg(Ke, _Fail, St0) -> + cg(Ke, St0). + +%% select_cg(Type, [ValueClause], Src, TypeFail, ValueFail, State) -> +%% {Is,State}. +%% Selecting type and value needs two failure labels, TypeFail is the +%% label to jump to of the next type test when this type fails, and +%% ValueFail is the label when this type is correct but the value is +%% wrong. These are different as in the second case there is no need +%% to try the next type, as it will always fail. + +select_cg(cg_binary, [S], Var, Tf, Vf, St) -> + select_binary(S, Var, Tf, Vf, St); +select_cg(cg_bin_seg, Vs, Var, Tf, _Vf, St) -> + select_bin_segs(Vs, Var, Tf, St); +select_cg(cg_bin_int, Vs, Var, Tf, _Vf, St) -> + select_bin_segs(Vs, Var, Tf, St); +select_cg(cg_bin_end, [S], Var, Tf, _Vf, St) -> + select_bin_end(S, Var, Tf, St); +select_cg(cg_map, Vs, Var, Tf, Vf, St) -> + select_map(Vs, Var, Tf, Vf, St); +select_cg(cg_cons, [S], Var, Tf, Vf, St) -> + select_cons(S, Var, Tf, Vf, St); +select_cg(cg_nil, [_]=Vs, Var, Tf, Vf, St) -> + select_literal(Vs, Var, Tf, Vf, St); +select_cg(b_literal, Vs, Var, Tf, Vf, St) -> + select_literal(Vs, Var, Tf, Vf, St); +select_cg(Type, Scs, Var, Tf, Vf, St0) -> + {Vis,St1} = + mapfoldl(fun (S, Sta) -> + {Val,Is,Stb} = select_val(S, Var, Vf, Sta), + {{Is,[Val]},Stb} + end, St0, Scs), + OptVls = combine(lists:sort(combine(Vis))), + {Vls,Sis,St2} = select_labels(OptVls, St1, [], []), + select_val_cg(Type, Var, Vls, Tf, Vf, Sis, St2). + +select_val_cg({bif,is_atom}, {bool,Dst}, Vls, _Tf, _Vf, Sis, St) -> + %% Generate a br instruction for a known boolean value from + %% the `wait_timeout` instruction. + #b_var{} = Dst, %Assertion. + [{#b_literal{val=false},Fail},{#b_literal{val=true},Succ}] = sort(Vls), + Br = #b_br{bool=Dst,succ=Succ,fail=Fail}, + {[Br|Sis],St}; +select_val_cg({bif,is_atom}, {{succeeded,_}=SuccOp,Dst}, Vls, _Tf, _Vf, Sis, St0) -> + [{#b_literal{val=false},Fail},{#b_literal{val=true},Succ}] = sort(Vls), + #b_var{} = Dst, %Assertion. + %% Generate a `{succeeded,guard}` instruction and two-way branch + %% following the `peek_message` instruction. + {Cond,St} = make_cond(SuccOp, [Dst], Fail, Succ, St0), + {Cond ++ Sis,St}; +select_val_cg(cg_tuple, Tuple, Vls, Tf, Vf, Sis, St0) -> + {Is0,St1} = make_cond_branch({bif,is_tuple}, [Tuple], Tf, St0), + {Arity,St2} = new_ssa_var(St1), + GetArity = #b_set{op={bif,tuple_size},dst=Arity,args=[Tuple]}, + {Is,St} = select_val_cg({bif,is_integer}, Arity, Vls, Vf, Vf, Sis, St2), + {Is0 ++ [GetArity|Is],St}; +select_val_cg(Type, R, Vls, Tf, Vf, Sis, St0) -> + {TypeIs,St1} = + if + Tf =:= Vf -> + %% The type and value failure labels are the same; we + %% don't need a type test. + {[],St0}; + true -> + %% Different labels for type failure and value + %% failure; we need a type test. + make_cond_branch(Type, [R], Tf, St0) + end, + case Vls of + [{Val,Succ}] -> + {Is,St} = make_cond({bif,'=:='}, [R,Val], Vf, Succ, St1), + {TypeIs++Is++Sis,St}; + [_|_] -> + {TypeIs++[#b_switch{arg=R,fail=Vf,list=Vls}|Sis],St1} + end. + +combine([{Is,Vs1},{Is,Vs2}|Vis]) -> combine([{Is,Vs1 ++ Vs2}|Vis]); +combine([V|Vis]) -> [V|combine(Vis)]; +combine([]) -> []. + +select_labels([{Is,Vs}|Vis], St0, Vls, Sis) -> + {Lbl,St1} = new_label(St0), + select_labels(Vis, St1, add_vls(Vs, Lbl, Vls), [{label,Lbl}|Is] ++ Sis); +select_labels([], St, Vls, Sis) -> + {Vls,Sis,St}. + +add_vls([V|Vs], Lbl, Acc) -> + add_vls(Vs, Lbl, [{V,Lbl}|Acc]); +add_vls([], _, Acc) -> Acc. + +select_literal(S, Src, Tf, Vf, St) -> + F = fun(ValClause, Fail, St0) -> + {Val,ValIs,St1} = select_val(ValClause, Src, Vf, St0), + Args = [Src,Val], + {Is,St2} = make_cond_branch({bif,'=:='}, Args, Fail, St1), + {Is++ValIs,St2} + end, + match_fmf(F, Tf, St, S). + +select_cons(#cg_val_clause{val=#cg_cons{hd=Hd,tl=Tl},body=B}, + Src, Tf, Vf, St0) -> + {Bis,St1} = match_cg(B, Vf, St0), + Args = [Src], + {Is,St} = make_cond_branch(is_nonempty_list, Args, Tf, St1), + GetHd = #b_set{op=get_hd,dst=Hd,args=Args}, + GetTl = #b_set{op=get_tl,dst=Tl,args=Args}, + {Is ++ [GetHd,GetTl|Bis],St}. + +select_binary(#cg_val_clause{val=#cg_binary{segs=#b_var{}=Ctx},body=B}, + Src, Tf, Vf, St0) -> + {Bis0,St1} = match_cg(B, Vf, St0), + Bis1 = finish_bs_matching(Bis0), + {TestIs,St} = make_succeeded(Ctx, {guard,Tf}, St1), + Bis = [#b_set{op=bs_start_match,dst=Ctx, + args=[#b_literal{val=new},Src]}] ++ TestIs ++ Bis1, + {Bis,St}. + +finish_bs_matching([#b_set{op=bs_match, + args=[#b_literal{val=string},Ctx, + #b_literal{val=BinList}]}=Set|Is]) + when is_list(BinList) -> + I = Set#b_set{args=[#b_literal{val=string},Ctx, + #b_literal{val=list_to_bitstring(BinList)}]}, + finish_bs_matching([I|Is]); +finish_bs_matching([I|Is]) -> + [I|finish_bs_matching(Is)]; +finish_bs_matching([]) -> []. + +%% Instructions for selection of binary segments. + +select_bin_segs(Scs, #b_var{}=Ctx, Tf, St) -> + match_fmf(fun(S, Fail, Sta) -> + select_bin_seg(S, Ctx, Fail, Sta) + end, Tf, St, Scs). + +select_bin_seg(#cg_val_clause{val=#cg_bin_seg{seg=Dst,next=Next}=Seg, + body=B,anno=Anno}, + Ctx, Fail, St0) -> + LineAnno = line_anno(Anno), + {Mis,St1} = select_extract_bin(Seg, Ctx, Fail, LineAnno, St0), + {Bis,St} = match_cg(B, Fail, St1), + BsGet = #b_set{op=bs_extract,dst=Dst,args=[ssa_arg(Next, St)]}, + Is = Mis ++ [BsGet] ++ Bis, + {Is,St}; +select_bin_seg(#cg_val_clause{val=#cg_bin_int{}=Seg,body=B}, + Ctx, Fail, St0) -> + {Mis,St1} = select_extract_int(Seg, Fail, Ctx, St0), + {Bis,St} = match_cg(B, Fail, St1), + case Mis ++ Bis of + [#b_set{op=bs_match, + args=[#b_literal{val=string},OtherCtx1,Bin1]}, + #b_set{op={succeeded,guard},dst=Bool1}, + #b_br{bool=Bool1,succ=Succ,fail=Fail}, + {label,Succ}, + #b_set{op=bs_match,dst=Dst, + args=[#b_literal{val=string},_OtherCtx2,Bin2]} | + [#b_set{op={succeeded,guard},dst=Bool2}, + #b_br{bool=Bool2,fail=Fail}|_]=Is] -> + %% We used to do this optimization later, but it turns out + %% that in huge functions with many string matching + %% instructions, it's a huge win to do the combination + %% now. To avoid copying the binary data again and again, + %% we'll combine bitstrings in a list and convert all of + %% it to a bitstring later. + {#b_literal{val=B1},#b_literal{val=B2}} = {Bin1,Bin2}, + Bin = #b_literal{val=[B1,B2]}, + Set = #b_set{op=bs_match,dst=Dst, + args=[#b_literal{val=string},OtherCtx1,Bin]}, + {[Set|Is],St}; + Is0 -> + {Is0,St} + end. + +select_bin_end(#cg_val_clause{val=#cg_bin_end{},body=B}, #b_var{}=Ctx, Tf, St0) -> + {Bis,St1} = match_cg(B, Tf, St0), + {TestIs,St} = make_cond_branch(bs_test_tail, [Ctx,#b_literal{val=0}], Tf, St1), + {TestIs ++ Bis,St}. + +select_extract_bin(#cg_bin_seg{type=Type,size=Size0,unit=Unit0, + flags=Flags0,next=Dst}, + Ctx, Fail, Anno, St0) -> + Size = case {Size0,ssa_arg(Size0, St0)} of + {#b_var{},#b_literal{val=all}} -> + %% The size `all` is used for the size of the final + %% binary segment in a pattern. Using `all` + %% explicitly is not allowed, so we convert it to + %% an obvious invalid size. Example: + %% + %% All = all, + %% <<Val:All/binary>> = Bin + %% + #b_literal{val=bad_size}; + {_,Size1} -> + Size1 + end, + Unit = #b_literal{val=Unit0}, + Flags = #b_literal{val=Flags0}, + TypeArg = #b_literal{val=Type}, + Args = [TypeArg,Ctx,Flags| + case bs_need_size(Type) of + true -> [Size,Unit]; + false -> [] + end], + BsMatch = #b_set{anno=Anno,op=bs_match,dst=Dst,args=Args}, + {Is,St} = make_succeeded(Dst, {guard,Fail}, St0), + {[BsMatch|Is],St}. + +bs_need_size(utf8) -> false; +bs_need_size(utf16) -> false; +bs_need_size(utf32) -> false; +bs_need_size(_) -> true. + +select_extract_int(#cg_bin_int{val=0,size=#b_literal{val=0}, + next=#b_var{name=Tl}}, + _Fail, Ctx, St0) -> + %% Example: + %% <<..., 0:0, ...>> = Bin, + St = set_ssa_var(Tl, Ctx, St0), + {[],St}; +select_extract_int(#cg_bin_int{val=Val,size=#b_literal{val=Sz}, + unit=U,flags=Fs,next=#b_var{}=Dst}, + Fail, Ctx, St0) when is_integer(Sz), is_integer(U) -> + Bits = U * Sz, + Bin = case member(big, Fs) of + true -> + <<Val:Bits>>; + false -> + true = member(little, Fs), %Assertion. + <<Val:Bits/little>> + end, + Bits = bit_size(Bin), %Assertion. + {TestIs,St} = make_succeeded(Dst, {guard,Fail}, St0), + Set = #b_set{op=bs_match,dst=Dst, + args=[#b_literal{val=string},Ctx,#b_literal{val=Bin}]}, + {[Set|TestIs],St}. + +select_val(#cg_val_clause{val=#cg_tuple{es=Es},body=B}, V, Vf, St0) -> + Eis = select_extract_tuple(Es, 0, V), + {Bis,St1} = match_cg(B, Vf, St0), + {#b_literal{val=length(Es)},Eis ++ Bis,St1}; +select_val(#cg_val_clause{val=#b_literal{}=Val,body=B}, _V, Vf, St0) -> + {Bis,St1} = match_cg(B, Vf, St0), + {Val,Bis,St1}. + +select_extract_tuple([E|Es], Index, Tuple) -> + case E of + #b_var{} -> + Args = [Tuple,#b_literal{val=Index}], + Get = #b_set{op=get_tuple_element,dst=E,args=Args}, + [Get|select_extract_tuple(Es, Index+1, Tuple)]; + #b_literal{val=unused} -> + %% Not extracting tuple elements that are not used is an + %% optimization for compile time and memory use during + %% compilation, which is probably worthwhile because it is + %% common to extract only a few elements from a huge + %% record. + select_extract_tuple(Es, Index + 1, Tuple) + end; +select_extract_tuple([], _, _) -> []. + +select_map(Scs, MapSrc, Tf, Vf, St0) -> + {Is,St1} = + match_fmf(fun(#cg_val_clause{val=#cg_map{op=exact,es=Es}, + body=B}, Fail, St1) -> + select_map_val(MapSrc, Es, B, Fail, St1) + end, Vf, St0, Scs), + {TestIs,St} = make_cond_branch({bif,is_map}, [MapSrc], Tf, St1), + {TestIs++Is,St}. + +select_map_val(MapSrc, Es, B, Fail, St0) -> + {Eis,St1} = select_extract_map(Es, MapSrc, Fail, St0), + {Bis,St2} = match_cg(B, Fail, St1), + {Eis++Bis,St2}. + +select_extract_map([P|Ps], MapSrc, Fail, St0) -> + #cg_map_pair{key=Key0,val=Dst} = P, + Key = ssa_arg(Key0, St0), + Set = #b_set{op=get_map_element,dst=Dst,args=[MapSrc,Key]}, + {TestIs,St1} = make_succeeded(Dst, {guard,Fail}, St0), + {Is,St} = select_extract_map(Ps, MapSrc, Fail, St1), + {[Set|TestIs]++Is,St}; +select_extract_map([], _, _, St) -> + {[],St}. + +guard_clause_cg(#cg_guard_clause{guard=G,body=B}, Fail, St0) -> + {Gis,St1} = guard_cg(G, Fail, St0), + {Bis,St} = match_cg(B, Fail, St1), + {Gis ++ Bis,St}. + +%% guard_cg(Guard, Fail, State) -> {[Ainstr],State}. +%% A guard is a boolean expression of tests. Tests return true or +%% false. A fault in a test causes the test to return false. Tests +%% never return the boolean, instead we generate jump code to go to +%% the correct exit point. Primops and tests all go to the next +%% instruction on success or jump to a failure label. + +guard_cg(#cg_try{arg=Ts,vars=[],body=#cg_break{args=[]}, + evars=[],handler=#cg_break{args=[]}}, + Fail, + #cg{bfail=OldBfail,break=OldBreak}=St0) -> + %% Do a try/catch without return value for effect. The return + %% value is not checked; success passes on to the next instruction + %% and failure jumps to Fail. + {Next,St1} = new_label(St0), + {Tis,St2} = guard_cg(Ts, Fail, St1#cg{bfail=Fail,break=Next}), + Is = Tis ++ [{label,Next},#cg_phi{vars=[]}], + {Is,St2#cg{bfail=OldBfail,break=OldBreak}}; +guard_cg(#cg_test{op=Test,args=As}, Fail, St0) when is_atom(Test) -> + test_cg(Test, false, As, Fail, St0); +guard_cg(#cg_seq{arg=Arg,body=Body}, Fail, St0) -> + {ArgIs,St1} = guard_cg(Arg, Fail, St0), + {BodyIs,St} = guard_cg(Body, Fail, St1), + {ArgIs++BodyIs,St}; +guard_cg(G, _Fail, St) -> + cg(G, St). + +test_cg('=/=', Inverted, As, Fail, St) -> + test_cg('=:=', not Inverted, As, Fail, St); +test_cg('/=', Inverted, As, Fail, St) -> + test_cg('==', not Inverted, As, Fail, St); +test_cg(Test, Inverted, As0, Fail, St0) -> + As = ssa_args(As0, St0), + {Succ,St} = new_label(St0), + Bool = #b_var{name=Succ}, + Bif = #b_set{op={bif,Test},dst=Bool,args=As}, + Br = case Inverted of + false -> + #b_br{bool=Bool,succ=Succ,fail=Fail}; + true -> + #b_br{bool=Bool,succ=Fail,fail=Succ} + end, + {[Bif,Br,{label,Succ}],St}. + +%% match_fmf(Fun, LastFail, State, [Clause]) -> {Is,State}. +%% This is a special flatmapfoldl for match code gen where we +%% generate a "failure" label for each clause. The last clause uses +%% an externally generated failure label, LastFail. N.B. We do not +%% know or care how the failure labels are used. + +match_fmf(F, LastFail, St, [H]) -> + F(H, LastFail, St); +match_fmf(F, LastFail, St0, [H|T]) -> + {Fail,St1} = new_label(St0), + {R,St2} = F(H, Fail, St1), + {Rs,St3} = match_fmf(F, LastFail, St2, T), + {R ++ [{label,Fail}] ++ Rs,St3}. + +%% fail_context(State) -> {body | guard, FailureLabel}. +%% Return an indication of which part of a function code is +%% being generated for and the appropriate failure label to +%% use. + +fail_context(#cg{catch_label=Catch,bfail=Fail}) -> + if + Fail =/= ?EXCEPTION_BLOCK -> + {guard,Fail}; + Catch =:= none -> + {body,Fail}; + is_integer(Catch) -> + {body,Catch} + end. + +%% call_cg(Func, [Arg], [Ret], Le, State) -> +%% {[Ainstr],State}. +%% Generate code for call. + +call_cg(Func, As, [Dst], Le, St0) -> + case fail_context(St0) of + {guard,Fail} -> + %% Inside a guard. The only allowed function call is to + %% erlang:error/1,2. We will generate a branch to the + %% failure branch. + #b_remote{mod=#b_literal{val=erlang}, + name=#b_literal{val=error}} = Func, %Assertion. + {[make_uncond_branch(Fail),#cg_unreachable{}],St0}; + FailCtx -> + %% Ordinary function call in a function body. + Args = ssa_args([Func|As], St0), + Call = #b_set{anno=line_anno(Le),op=call,dst=Dst,args=Args}, + {TestIs,St} = make_succeeded(Dst, FailCtx, St0), + {[Call|TestIs],St} + end. + +internal_anno(Le) -> + Anno = line_anno(Le), + case keyfind(inlined, 1, Le) of + false -> Anno; + {inlined, NameArity} -> Anno#{inlined => NameArity} + end. + +%% internal_cg(Anno, Op, [Arg], [Ret], State) -> +%% {[Ainstr],State}. +internal_cg(Anno, recv_peek_message, [], [#b_var{name=Succeeded0}, + #b_var{}=Dst], St0) -> + St = new_succeeded_value(Succeeded0, Dst, St0), + Set = #b_set{anno=Anno, + op=peek_message, + dst=Dst, + args=[#b_literal{val=none}]}, + {[Set],St}; +internal_cg(_Anno, recv_wait_timeout, As, [#b_var{name=Succeeded0}], St0) -> + %% Note that the `wait_timeout` instruction can potentially branch in three + %% different directions: + %% + %% * A new message is available in the message queue. `wait_timeout` + %% branches to the given label. + %% + %% * The timeout expired. `wait_timeout` transfers control to the next + %% instruction. + %% + %% * The value for timeout duration is invalid (either not an integer or + %% negative or too large). A `timeout_value` exception will be raised. + %% + %% `wait_timeout` will be represented like this in SSA code: + %% + %% WaitBool = wait_timeout TimeoutValue + %% Succeeded = succeeded:body WaitBool + %% br Succeeded, ^good_timeout_value, ^bad_timeout_value + %% + %% good_timeout_value: + %% br WaitBool, ^timeout_expired, ^new_message_received + %% + Args = ssa_args(As, St0), + {Wait,St1} = new_ssa_var(St0), + {Succ,St2} = make_succeeded(Wait, fail_context(St1), St1), + St = new_bool_value(Succeeded0, Wait, St2), + Set = #b_set{op=wait_timeout,dst=Wait,args=Args}, + {[Set|Succ],St}. + +%% try_cg(TryBlock, [BodyVar], TryBody, [ExcpVar], TryHandler, [Ret], St) -> +%% {[Ainstr],St}. + +try_cg(Ta, SsaVs, Tb, SsaEvs, Th, BreakVars, St0) -> + case try_cg_guard(Ta, SsaVs, Tb, Th, BreakVars, St0) of + not_possible -> + %% The general try/catch (not in a guard). + {B,St1} = new_label(St0), %Body label + {H,St2} = new_label(St1), %Handler label + {E,St3} = new_label(St2), %End label + {Next,St4} = new_label(St3), + {TryTag,St5} = new_ssa_var(St4), + {Ais,St6} = cg(Ta, St5#cg{break=B,catch_label=H}), + St7 = St6#cg{break=E,catch_label=St5#cg.catch_label}, + {Bis,St8} = cg(Tb, St7), + {His,St9} = cg(Th, St8), + {Handler,St10} = try_handler(H, TryTag, SsaEvs, St9), + {KillTryTag,St} = kill_try_tag(TryTag, St10), + {[#b_set{op=new_try_tag,dst=TryTag,args=[#b_literal{val='try'}]}, + #b_br{bool=TryTag,succ=Next,fail=H}, + {label,Next}] ++ Ais ++ + [{label,B},#cg_phi{vars=SsaVs},KillTryTag] ++ Bis ++ + Handler ++ His ++ + [{label,E},#cg_phi{vars=BreakVars}], + St#cg{break=St0#cg.break}}; + Result -> + Result + end. + +try_cg_guard(Ta, SsaVs, Tb, Th, BreakVars, St0) -> + {B,St1} = new_label(St0), %Body label + {H,St2} = new_label(St1), %Handler label + {Ais,_} = cg(Ta, St2#cg{break=B,catch_label=H}), + + %% We try to avoid constructing a try/catch if the expression to + %% be evaluated don't have any side effects and if the error + %% reason is not explicitly matched. + %% + %% Starting in OTP 23, segment sizes in binary matching and keys + %% in map matching are allowed to be arbitrary guard + %% expressions. Those expressions are evaluated in a try/catch + %% so that matching can continue with the next clause if the evaluation + %% of such expression fails. + %% + %% It is not allowed to use try/catch during matching in a receive + %% (the try/catch would force the saving of fragile message references + %% to the stack frame). Therefore, avoiding creating try/catch is + %% not merely an optimization but necessary for correctness. + + case is_guard_cg_safe_list(Ais) of + true -> + %% All instructions are suitable in a guard. Check + %% whether the exception is matched. + {ProtIs,St3} = guard_cg(Ta, H, St2#cg{break=B,bfail=H}), + case {SsaVs,Tb,Th} of + {[#b_var{name=X}],#cg_break{args=[#b_var{name=X}]}, + #cg_break{args=[#b_literal{}]}} -> + {His,St} = cg(Th, St3), + Is = ProtIs ++ [{label,H}] ++ His ++ + [{label,B},#cg_phi{vars=BreakVars}], + {Is,St#cg{break=St0#cg.break,bfail=St0#cg.bfail}}; + {[#b_var{name=X}],#cg_break{args=[#b_literal{}=SuccLit,#b_var{name=X}]}, + #cg_break{args=[#b_literal{val=false},#b_literal{}]}} -> + %% This code probably evaluates a key expression + %% in map matching. + {FinalLabel,St4} = new_label(St3), + {His,St5} = cg(Th, St4#cg{break=FinalLabel}), + {Result,St} = new_ssa_var(St5), + Is = ProtIs ++ [{label,H}] ++ His ++ + [{label,B}, + #cg_phi{vars=[Result]}, + #cg_break{args=[SuccLit,Result],phi=FinalLabel}, + {label,FinalLabel}, + #cg_phi{vars=BreakVars}], + {Is,St#cg{break=St0#cg.break,bfail=St0#cg.bfail}}; + {_,#cg_break{args=[]},#cg_break{args=[]}} -> + %% This code probably does the size calculation + %% for a segment in binary matching. + {His,St} = cg(Th, St3), + Is = ProtIs ++ [{label,H}] ++ His ++ + [{label,B},#cg_phi{vars=BreakVars}], + {Is,St#cg{break=St0#cg.break,bfail=St0#cg.bfail}}; + {_,_,_} -> + not_possible + end; + false -> + not_possible + end. + +is_guard_cg_safe_list(Is) -> + all(fun is_guard_cg_safe/1, Is). + +is_guard_cg_safe(#b_set{op=call,args=Args}) -> + case Args of + [#b_remote{mod=#b_literal{val=erlang}, + name=#b_literal{val=error}, + arity=1}|_] -> + true; + _ -> + false + end; +is_guard_cg_safe(#b_set{}=I) -> not beam_ssa:clobbers_xregs(I); +is_guard_cg_safe(#b_br{}) -> true; +is_guard_cg_safe(#b_switch{}) -> true; +is_guard_cg_safe(#cg_break{}) -> true; +is_guard_cg_safe(#cg_phi{}) -> true; +is_guard_cg_safe({label,_}) -> true; +is_guard_cg_safe(#cg_unreachable{}) -> false. + +try_handler(H, TryTag, SsaEvs, St0) -> + {CatchedAgg,St1} = new_ssa_var(St0), + {KillTryTag,St} = kill_try_tag(TryTag, St1), + ExtractVs = extract_vars(SsaEvs, CatchedAgg, 0), + Args = [#b_literal{val='try'},TryTag], + Handler = [{label,H}, + #b_set{op=landingpad,dst=CatchedAgg,args=Args}] ++ + ExtractVs ++ [KillTryTag], + {Handler,St}. + +kill_try_tag(TryTag, St0) -> + {Ignored,St} = new_ssa_var(St0), + KillTryTag = #b_set{op=kill_try_tag,dst=Ignored,args=[TryTag]}, + {KillTryTag,St}. + +extract_vars([V|Vs], Agg, N) -> + I = #b_set{op=extract,dst=V,args=[Agg,#b_literal{val=N}]}, + [I|extract_vars(Vs, Agg, N+1)]; +extract_vars([], _, _) -> []. + +%% catch_cg(CatchBlock, Ret, St) -> {[Ainstr],St}. + +'catch_cg'(Block, #b_var{}=Dst, St0) -> + {B,St1} = new_label(St0), + {Next,St2} = new_label(St1), + {H,St3} = new_label(St2), + {CatchReg,St4} = new_ssa_var(St3), + {Succ,St5} = new_label(St4), + {Cis,St6} = cg(Block, St5#cg{break=Succ,catch_label=H}), + {CatchedVal,St7} = new_ssa_var(St6), + {SuccVal,St8} = new_ssa_var(St7), + {CatchedAgg,St9} = new_ssa_var(St8), + {CatchEndVal,St} = new_ssa_var(St9), + Args = [#b_literal{val='catch'},CatchReg], + {[#b_set{op=new_try_tag,dst=CatchReg,args=[#b_literal{val='catch'}]}, + #b_br{bool=CatchReg,succ=Next,fail=H}, + {label,Next}] ++ Cis ++ + [{label,H}, + #b_set{op=landingpad,dst=CatchedAgg,args=Args}, + #b_set{op=extract,dst=CatchedVal, + args=[CatchedAgg,#b_literal{val=0}]}, + #cg_break{args=[CatchedVal],phi=B}, + {label,Succ}, + #cg_phi{vars=[SuccVal]}, + #cg_break{args=[SuccVal],phi=B}, + {label,B},#cg_phi{vars=[CatchEndVal]}, + #b_set{op=catch_end,dst=Dst,args=[CatchReg,CatchEndVal]}], + St#cg{break=St1#cg.break,catch_label=St1#cg.catch_label}}. + +%%% +%%% SSA utilities. +%%% + +make_cond(Cond, Args, Fail, Succ, St0) -> + {Bool,St} = new_ssa_var(St0), + Bif = #b_set{op=Cond,dst=Bool,args=Args}, + Br = #b_br{bool=Bool,succ=Succ,fail=Fail}, + {[Bif,Br],St}. + +make_cond_branch(Cond, Args, Fail, St0) -> + {Succ,St} = new_label(St0), + Bool = #b_var{name=Succ}, + Bif = #b_set{op=Cond,dst=Bool,args=Args}, + Br = #b_br{bool=Bool,succ=Succ,fail=Fail}, + {[Bif,Br,{label,Succ}],St}. + +make_uncond_branch(Fail) -> + #b_br{bool=#b_literal{val=true},succ=Fail,fail=Fail}. + +%% +%% Success checks need to be treated differently in bodies and guards; +%% a check in a guard can be safely removed when we know it fails +%% because we know there's never any side effects, but in bodies the +%% checked instruction may throw an exception and we need to ensure it +%% isn't optimized away. +%% +%% Checks are expressed as {succeeded,guard} and {succeeded,body}, +%% respectively, where the latter has a side effect (see +%% beam_ssa:no_side_effect/1) and the former does not. This ensures +%% that passes like ssa_opt_dead and ssa_opt_live won't optimize away +%% pure operations that may throw an exception, since their result is +%% used in {succeeded,body}. +%% +%% Other than the above details, the two variants are equivalent and +%% most passes that care about them can simply match {succeeded,_}. +%% + +make_succeeded(Var, {Where,Fail}, St) when Where =:= body; Where =:= guard -> + make_cond_branch({succeeded,Where}, [Var], Fail, St). + +ssa_args(As, St) -> + [ssa_arg(A, St) || A <- As]. + +ssa_arg(#b_var{name=V}=Var0, #cg{vars=Vars}) -> + case Vars of + #{V := Var} -> Var; + #{} -> Var0 + end; +ssa_arg(#b_literal{}=Lit, _) -> + Lit; +ssa_arg(#b_remote{mod=Mod0,name=Name0,arity=Arity}, St) -> + Mod = ssa_arg(Mod0, St), + Name = ssa_arg(Name0, St), + #b_remote{mod=Mod,name=Name,arity=Arity}; +ssa_arg(#b_local{name=#b_literal{}}=Local, _) -> + Local. + +new_succeeded_value(VarBase, Var, #cg{vars=Vars0}=St) -> + Vars = Vars0#{VarBase => {{succeeded,guard},Var}}, + St#cg{vars=Vars}. + +new_bool_value(VarBase, Var, #cg{vars=Vars0}=St) -> + Vars = Vars0#{VarBase => {bool,Var}}, + St#cg{vars=Vars}. + +new_ssa_var(#cg{lcount=Uniq}=St) -> + {#b_var{name=Uniq},St#cg{lcount=Uniq+1}}. + +set_ssa_var(VarBase, Val, #cg{vars=Vars}=St) + when is_atom(VarBase); is_integer(VarBase) -> + St#cg{vars=Vars#{VarBase => Val}}. + +new_label(#cg{lcount=Next}=St) -> + {Next,St#cg{lcount=Next+1}}. + +%% line_anno(Le) -> #{} | #{location:={File,Line}}. +%% Create a location annotation, containing information about the +%% current filename and line number. The annotation should be +%% included in any operation that could cause an exception. + +line_anno([Line,{file,Name}]) when is_integer(Line) -> + line_anno_1(Name, Line); +line_anno([{Line,Column},{file,Name}]) when is_integer(Line), + is_integer(Column) -> + line_anno_1(Name, Line); +line_anno([_|_]=A) -> + {Name,Line} = find_loc(A, no_file, 0), + line_anno_1(Name, Line); +line_anno([]) -> + #{}. + +line_anno_1(no_file, _) -> + #{}; +line_anno_1(_, 0) -> + %% Missing line number or line number 0. + #{}; +line_anno_1(Name, Line) -> + #{location => {Name,Line}}. + +find_loc([Line|T], File, _) when is_integer(Line) -> + find_loc(T, File, Line); +find_loc([{Line, Column}|T], File, _) when is_integer(Line), + is_integer(Column) -> + find_loc(T, File, Line); +find_loc([{file,File}|T], _, Line) -> + find_loc(T, File, Line); +find_loc([_|T], File, Line) -> + find_loc(T, File, Line); +find_loc([], File, Line) -> {File,Line}. + +%% fix_phis(Is0) -> Is. +%% Rewrite #cg_break{} and #cg_phi{} records to #b_set{} records. +%% A #cg_break{} is rewritten to an unconditional branch, and +%% and a #cg_phi{} is rewritten to one or more phi nodes. + +fix_phis(Is) -> + fix_phis_1(Is, none, #{}). + +fix_phis_1([{label,Lbl},#cg_phi{vars=Vars}|Is0], _Lbl, Map0) -> + case Map0 of + #{Lbl := Pairs} -> + %% This phi node was referenced by at least one #cg_break{}. + %% Create the phi nodes. + Phis = gen_phis(Vars, Pairs), + Map = maps:remove(Lbl, Map0), + [{label,Lbl}] ++ Phis ++ fix_phis_1(Is0, Lbl, Map); + #{} -> + %% No #cg_break{} instructions reference this label. + %% #cg_break{} instructions must reference the labels for + %% #cg_phi{} instructions; therefore this label is + %% unreachable and can be dropped. + Is = drop_upto_label(Is0), + fix_phis_1(Is, none, Map0) + end; +fix_phis_1([{label,L}=I|Is], _Lbl, Map) -> + [I|fix_phis_1(Is, L, Map)]; +fix_phis_1([#cg_unreachable{}|Is0], _Lbl, Map) -> + Is = drop_upto_label(Is0), + fix_phis_1(Is, none, Map); +fix_phis_1([#cg_break{args=Args,phi=Target}|Is], Lbl, Map) when is_integer(Lbl) -> + %% Pair each argument with the label for this block and save in the map. + Pairs1 = case Map of + #{Target := Pairs0} -> Pairs0; + #{} -> [] + end, + Pairs = [[{Arg,Lbl} || Arg <- Args]|Pairs1], + I = make_uncond_branch(Target), + [I|fix_phis_1(Is, none, Map#{Target => Pairs})]; +fix_phis_1([#b_set{op=remove_message,dst=Dst}=Set, + #b_ret{arg=Dst}=Ret0|Is], Lbl, Map) -> + %% The remove_message instruction, which is an instruction without + %% value, was used in effect context in an `after` block. Example: + %% + %% try + %% . . . + %% after + %% . + %% . + %% . + %% receive _ -> ignored end + %% end, + %% ok. + %% + Ret = Ret0#b_ret{arg=#b_literal{val=ok}}, + [Set,Ret|fix_phis_1(Is, Lbl, Map)]; +fix_phis_1([I|Is], Lbl, Map) -> + [I|fix_phis_1(Is, Lbl, Map)]; +fix_phis_1([], _, Map) -> + 0 = map_size(Map), %Assertion. + []. + +gen_phis([V|Vs], Preds0) -> + {Pairs,Preds} = collect_predecessors(Preds0, [], []), + [_|_] = Pairs, %Assertion. + [#b_set{op=phi,dst=V,args=Pairs}|gen_phis(Vs, Preds)]; +gen_phis([], _) -> []. + +collect_predecessors([[First|Rest]|T], ColAcc, RestAcc) -> + collect_predecessors(T, [First|ColAcc], [Rest|RestAcc]); +collect_predecessors([], ColAcc, RestAcc) -> + {keysort(2, ColAcc),RestAcc}. + +drop_upto_label([{label,_}|_]=Is) -> Is; +drop_upto_label([_|Is]) -> drop_upto_label(Is). + +%% build_map(Is) -> #{}. +%% Split up the sequential instruction stream into blocks and +%% store them in a map. + +build_map(Is) -> + Linear0 = build_graph_1(Is, [], []), + Linear = beam_ssa:trim_unreachable(Linear0), + maps:from_list(Linear). + +build_graph_1([{label,L}|Is], Lbls, []) -> + build_graph_1(Is, [L|Lbls], []); +build_graph_1([{label,L}|Is], Lbls, [_|_]=BlockAcc) -> + make_blocks(Lbls, BlockAcc) ++ build_graph_1(Is, [L], []); +build_graph_1([I|Is], Lbls, BlockAcc) -> + build_graph_1(Is, Lbls, [I|BlockAcc]); +build_graph_1([], Lbls, BlockAcc) -> + make_blocks(Lbls, BlockAcc). + +make_blocks(Lbls, [Last0|Is0]) -> + Is = reverse(Is0), + Last = beam_ssa:normalize(Last0), + Block = #b_blk{is=Is,last=Last}, + [{L,Block} || L <- Lbls]. diff --git a/lib/compiler/src/beam_kernel_to_ssa.erl b/lib/compiler/src/beam_kernel_to_ssa.erl deleted file mode 100644 index f94ab4ed15..0000000000 --- a/lib/compiler/src/beam_kernel_to_ssa.erl +++ /dev/null @@ -1,1343 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2018-2023. 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: Convert the Kernel Erlang format to the SSA format. - --module(beam_kernel_to_ssa). - -%% The main interface. --export([module/2]). - --import(lists, [all/2,append/1,flatmap/2,foldl/3, - keyfind/3,keysort/2,mapfoldl/3,member/2, - reverse/1,sort/1]). - --include("v3_kernel.hrl"). --include("beam_ssa.hrl"). - --type label() :: beam_ssa:label(). - -%% Main codegen structure. --record(cg, {lcount=1 :: label(), %Label counter - bfail=1 :: label(), - catch_label=none :: 'none' | label(), - vars=#{} :: map(), %Defined variables. - break=0 :: label(), %Break label - recv=0 :: label(), %Receive label - ultimate_failure=0 :: label(), %Label for ultimate match failure. - labels=#{} :: #{atom() => label()}, - no_make_fun3=false :: boolean(), - checks=[] :: [term()] - }). - -%% Internal records. --record(cg_break, {args :: [beam_ssa:value()], - phi :: label() - }). --record(cg_phi, {vars :: [beam_ssa:b_var()] - }). --record(cg_unreachable, {}). - --spec module(#k_mdef{}, [compile:option()]) -> {'ok',#b_module{}}. - -module(#k_mdef{name=Mod,exports=Es,attributes=Attr,body=Forms}, Opts) -> - NoMakeFun3 = proplists:get_bool(no_make_fun3, Opts), - Body = functions(Forms, Mod, NoMakeFun3), - Module = #b_module{name=Mod,exports=Es,attributes=Attr,body=Body}, - {ok,Module}. - -functions(Forms, Mod, NoMakeFun3) -> - [function(F, Mod, NoMakeFun3) || F <- Forms]. - -function(#k_fdef{anno=Anno0,func=Name,arity=Arity, - vars=As0,body=Kb}, Mod, NoMakeFun3) -> - try - #k_match{} = Kb, %Assertion. - - %% Generate the SSA form immediate format. - St0 = #cg{no_make_fun3=NoMakeFun3}, - {As,St1} = new_ssa_vars(As0, St0), - {Asm,St} = cg_fun(Kb, St1), - Anno1 = line_anno(Anno0), - Anno2 = Anno1#{func_info=>{Mod,Name,Arity}}, - Anno = case St#cg.checks of - [] -> Anno2; - Checks -> - Anno2#{ssa_checks=>Checks} - end, - #b_function{anno=Anno,args=As,bs=Asm,cnt=St#cg.lcount} - catch - Class:Error:Stack -> - io:fwrite("Function: ~w/~w\n", [Name,Arity]), - erlang:raise(Class, Error, Stack) - end. - -%% cg_fun([Lkexpr], [HeadVar], State) -> {[Ainstr],State} - -cg_fun(Ke, St0) -> - {UltimateFail,FailIs,St1} = make_failure(badarg, St0), - ?EXCEPTION_BLOCK = UltimateFail, %Assertion. - St2 = St1#cg{bfail=UltimateFail,ultimate_failure=UltimateFail}, - {B,St} = cg(Ke, St2), - Asm = [{label,0}|B++FailIs], - finalize(Asm, St). - -make_failure(Reason, St0) -> - {Lbl,St1} = new_label(St0), - {Dst,St} = new_ssa_var('@ssa_ret', St1), - Is = [{label,Lbl}, - #b_set{op=call,dst=Dst, - args=[#b_remote{mod=#b_literal{val=erlang}, - name=#b_literal{val=error}, - arity=1}, - #b_literal{val=Reason}]}, - #b_ret{arg=Dst}], - {Lbl,Is,St}. - -%% cg(Lkexpr, State) -> {[Ainstr],State}. -%% Generate code for a kexpr. - -cg(#k_match{body=M,ret=Rs}, St) -> - do_match_cg(M, Rs, St); -cg(#k_seq{arg=Arg,body=Body}, St0) -> - {ArgIs,St1} = cg(Arg, St0), - {BodyIs,St} = cg(Body, St1), - {ArgIs++BodyIs,St}; -cg(#k_call{anno=Le,op=Func,args=As,ret=Rs}, St) -> - call_cg(Func, As, Rs, Le, St); -cg(#k_enter{anno=Le,op=Func,args=As}, St) -> - enter_cg(Func, As, Le, St); -cg(#k_bif{anno=Le}=Bif, St) -> - bif_cg(Bif, Le, St); -cg(#k_try{arg=Ta,vars=Vs,body=Tb,evars=Evs,handler=Th,ret=Rs}, St) -> - try_cg(Ta, Vs, Tb, Evs, Th, Rs, St); -cg(#k_try_enter{arg=Ta,vars=Vs,body=Tb,evars=Evs,handler=Th}, St) -> - try_enter_cg(Ta, Vs, Tb, Evs, Th, St); -cg(#k_catch{body=Cb,ret=[R]}, St) -> - do_catch_cg(Cb, R, St); -cg(#k_put{anno=Le,arg=Con,ret=Var}, St) -> - put_cg(Var, Con, Le, St); -cg(#k_return{args=[Ret0]}, St) -> - Ret = ssa_arg(Ret0, St), - {[#b_ret{arg=Ret}],St}; -cg(#k_break{args=Bs}, #cg{break=Br}=St) -> - Args = ssa_args(Bs, St), - {[#cg_break{args=Args,phi=Br}],St}; -cg(#k_letrec_goto{label=Label,vars=Vs0,first=First,then=Then,ret=Rs}, - #cg{break=OldBreak,labels=Labels0}=St0) -> - {Tf,St1} = new_label(St0), - {B,St2} = new_label(St1), - Labels = Labels0#{Label=>Tf}, - {Vs,St3} = new_ssa_vars(Vs0, St2), - {Fis,St4} = cg(First, St3#cg{labels=Labels,break=B}), - {Sis,St5} = cg(Then, St4), - St6 = St5#cg{labels=Labels0}, - {BreakVars,St} = new_ssa_vars(Rs, St6), - PostPhi = #cg_phi{vars=BreakVars}, - FailPhi = case Vs of - [] -> []; - [_|_] -> [#cg_phi{vars=Vs}] - end, - {Fis ++ [{label,Tf}] ++ FailPhi ++ Sis ++ [{label,B},PostPhi], - St#cg{break=OldBreak}}; -cg(#k_goto{label=Label,args=[]}, #cg{labels=Labels}=St) -> - Branch = map_get(Label, Labels), - {[make_uncond_branch(Branch)],St}; -cg(#k_goto{label=Label,args=As0}, #cg{labels=Labels}=St) -> - As = ssa_args(As0, St), - Branch = map_get(Label, Labels), - Break = #cg_break{args=As,phi=Branch}, - {[Break],St}; -cg(#k_opaque{val={ssa_check_when,_,_,_,_}=Check},St) -> %% Extract here - {[],St#cg{checks=[Check|St#cg.checks]}}. - -%% match_cg(Matc, [Ret], State) -> {[Ainstr],State}. -%% Generate code for a match. - -do_match_cg(M, Rs, #cg{bfail=Bfail,break=OldBreak}=St0) -> - {B,St1} = new_label(St0), - {Mis,St2} = match_cg(M, Bfail, St1#cg{break=B}), - St3 = St2#cg{break=OldBreak}, - {BreakVars,St} = new_ssa_vars(Rs, St3), - {Mis ++ [{label,B},#cg_phi{vars=BreakVars}],St}. - -%% match_cg(Match, Fail, State) -> {[Ainstr],State}. -%% Generate code for a match tree. - -match_cg(#k_alt{first=F,then=S}, Fail, St0) -> - {Tf,St1} = new_label(St0), - {Fis,St2} = match_cg(F, Tf, St1), - {Sis,St3} = match_cg(S, Fail, St2), - {Fis ++ [{label,Tf}] ++ Sis,St3}; -match_cg(#k_select{var=#k_var{}=V,types=Scs}, Fail, St) -> - match_fmf(fun (S, F, Sta) -> - select_cg(S, V, F, Fail, Sta) - end, Fail, St, Scs); -match_cg(#k_guard{clauses=Gcs}, Fail, St) -> - match_fmf(fun (G, F, Sta) -> - guard_clause_cg(G, F, Sta) - end, Fail, St, Gcs); -match_cg(Ke, _Fail, St0) -> - cg(Ke, St0). - -%% select_cg(Sclause, V, TypeFail, ValueFail, State) -> {Is,State}. -%% Selecting type and value needs two failure labels, TypeFail is the -%% label to jump to of the next type test when this type fails, and -%% ValueFail is the label when this type is correct but the value is -%% wrong. These are different as in the second case there is no need -%% to try the next type, it will always fail. - -select_cg(#k_type_clause{type=k_binary,values=[S]}, Var, Tf, Vf, St) -> - select_binary(S, Var, Tf, Vf, St); -select_cg(#k_type_clause{type=k_bin_seg,values=Vs}, Var, Tf, _Vf, St) -> - select_bin_segs(Vs, Var, Tf, St); -select_cg(#k_type_clause{type=k_bin_int,values=Vs}, Var, Tf, _Vf, St) -> - select_bin_segs(Vs, Var, Tf, St); -select_cg(#k_type_clause{type=k_bin_end,values=[S]}, Var, Tf, _Vf, St) -> - select_bin_end(S, Var, Tf, St); -select_cg(#k_type_clause{type=k_map,values=Vs}, Var, Tf, Vf, St) -> - select_map(Vs, Var, Tf, Vf, St); -select_cg(#k_type_clause{type=k_cons,values=[S]}, Var, Tf, Vf, St) -> - select_cons(S, Var, Tf, Vf, St); -select_cg(#k_type_clause{type=k_nil,values=[S]}, Var, Tf, Vf, St) -> - select_nil(S, Var, Tf, Vf, St); -select_cg(#k_type_clause{type=k_literal,values=Vs}, Var, Tf, Vf, St) -> - select_literal(Vs, Var, Tf, Vf, St); -select_cg(#k_type_clause{type=Type,values=Scs}, Var, Tf, Vf, St0) -> - {Vis,St1} = - mapfoldl(fun (S, Sta) -> - {Val,Is,Stb} = select_val(S, Var, Vf, Sta), - {{Is,[Val]},Stb} - end, St0, Scs), - OptVls = combine(lists:sort(combine(Vis))), - {Vls,Sis,St2} = select_labels(OptVls, St1, [], []), - Arg = ssa_arg(Var, St2), - {Is,St} = select_val_cg(Type, Arg, Vls, Tf, Vf, Sis, St2), - {Is,St}. - -select_val_cg(k_atom, {bool,Dst}, Vls, _Tf, _Vf, Sis, St) -> - %% Generate a br instruction for a known boolean value from - %% the `wait_timeout` instruction. - #b_var{} = Dst, %Assertion. - [{#b_literal{val=false},Fail},{#b_literal{val=true},Succ}] = sort(Vls), - Br = #b_br{bool=Dst,succ=Succ,fail=Fail}, - {[Br|Sis],St}; -select_val_cg(k_atom, {succeeded,Dst}, Vls, _Tf, _Vf, Sis, St0) -> - [{#b_literal{val=false},Fail},{#b_literal{val=true},Succ}] = sort(Vls), - #b_var{} = Dst, %Assertion. - %% Generate a `succeeded` instruction and two-way branch - %% following the `peek_message` instruction. - {Bool,St} = new_ssa_var('@ssa_bool', St0), - Succeeded = #b_set{op={succeeded,guard},dst=Bool,args=[Dst]}, - Br = #b_br{bool=Bool,succ=Succ,fail=Fail}, - {[Succeeded,Br|Sis],St}; -select_val_cg(k_tuple, Tuple, Vls, Tf, Vf, Sis, St0) -> - {Is0,St1} = make_cond_branch({bif,is_tuple}, [Tuple], Tf, St0), - {Arity,St2} = new_ssa_var('@ssa_arity', St1), - GetArity = #b_set{op={bif,tuple_size},dst=Arity,args=[Tuple]}, - {Is,St} = select_val_cg(k_int, Arity, Vls, Vf, Vf, Sis, St2), - {Is0++[GetArity]++Is,St}; -select_val_cg(Type, R, Vls, Tf, Vf, Sis, St0) -> - {TypeIs,St1} = if - Tf =:= Vf -> - %% The type and value failure labels are the same; - %% we don't need a type test. - {[],St0}; - true -> - %% Different labels for type failure and - %% label failure; we need a type test. - Test = select_type_test(Type), - make_cond_branch(Test, [R], Tf, St0) - end, - case Vls of - [{Val,Succ}] -> - {Is,St} = make_cond({bif,'=:='}, [R,Val], Vf, Succ, St1), - {TypeIs++Is++Sis,St}; - [_|_] -> - {TypeIs++[#b_switch{arg=R,fail=Vf,list=Vls}|Sis],St1} - end. - -select_type_test(k_int) -> {bif,is_integer}; -select_type_test(k_atom) -> {bif,is_atom}; -select_type_test(k_float) -> {bif,is_float}. - -combine([{Is,Vs1},{Is,Vs2}|Vis]) -> combine([{Is,Vs1 ++ Vs2}|Vis]); -combine([V|Vis]) -> [V|combine(Vis)]; -combine([]) -> []. - -select_labels([{Is,Vs}|Vis], St0, Vls, Sis) -> - {Lbl,St1} = new_label(St0), - select_labels(Vis, St1, add_vls(Vs, Lbl, Vls), [[{label,Lbl}|Is]|Sis]); -select_labels([], St, Vls, Sis) -> - {Vls,append(Sis),St}. - -add_vls([V|Vs], Lbl, Acc) -> - add_vls(Vs, Lbl, [{#b_literal{val=V},Lbl}|Acc]); -add_vls([], _, Acc) -> Acc. - -select_literal(S, V, Tf, Vf, St) -> - Src = ssa_arg(V, St), - F = fun(ValClause, Fail, St0) -> - {Val,ValIs,St1} = select_val(ValClause, V, Vf, St0), - Args = [Src,#b_literal{val=Val}], - {Is,St2} = make_cond_branch({bif,'=:='}, Args, Fail, St1), - {Is++ValIs,St2} - end, - match_fmf(F, Tf, St, S). - -select_cons(#k_val_clause{val=#k_cons{hd=Hd,tl=Tl},body=B}, - V, Tf, Vf, St0) -> - Es = [Hd,Tl], - {Eis,St1} = select_extract_cons(V, Es, St0), - {Bis,St2} = match_cg(B, Vf, St1), - Src = ssa_arg(V, St2), - {Is,St} = make_cond_branch(is_nonempty_list, [Src], Tf, St2), - {Is ++ Eis ++ Bis,St}. - -select_nil(#k_val_clause{val=#k_literal{val=[]},body=B}, V, Tf, Vf, St0) -> - {Bis,St1} = match_cg(B, Vf, St0), - Src = ssa_arg(V, St1), - {Is,St} = make_cond_branch({bif,'=:='}, [Src,#b_literal{val=[]}], Tf, St1), - {Is ++ Bis,St}. - -select_binary(#k_val_clause{val=#k_binary{segs=#k_var{name=Ctx0}},body=B}, - #k_var{}=Src, Tf, Vf, St0) -> - {Ctx,St1} = new_ssa_var(Ctx0, St0), - {Bis0,St2} = match_cg(B, Vf, St1), - {TestIs,St} = make_succeeded(Ctx, {guard, Tf}, St2), - Bis1 = [#b_set{op=bs_start_match,dst=Ctx, - args=[#b_literal{val=new}, - ssa_arg(Src, St)]}] ++ TestIs ++ Bis0, - Bis = finish_bs_matching(Bis1), - {Bis,St}. - -finish_bs_matching([#b_set{op=bs_match, - args=[#b_literal{val=string},Ctx,#b_literal{val=BinList}]}=Set|Is]) - when is_list(BinList) -> - I = Set#b_set{args=[#b_literal{val=string},Ctx, - #b_literal{val=list_to_bitstring(BinList)}]}, - finish_bs_matching([I|Is]); -finish_bs_matching([I|Is]) -> - [I|finish_bs_matching(Is)]; -finish_bs_matching([]) -> []. - -make_cond(Cond, Args, Fail, Succ, St0) -> - {Bool,St} = new_ssa_var('@ssa_bool', St0), - Bif = #b_set{op=Cond,dst=Bool,args=Args}, - Br = #b_br{bool=Bool,succ=Succ,fail=Fail}, - {[Bif,Br],St}. - -make_cond_branch(Cond, Args, Fail, St0) -> - {Bool,St1} = new_ssa_var('@ssa_bool', St0), - {Succ,St} = new_label(St1), - Bif = #b_set{op=Cond,dst=Bool,args=Args}, - Br = #b_br{bool=Bool,succ=Succ,fail=Fail}, - {[Bif,Br,{label,Succ}],St}. - -make_uncond_branch(Fail) -> - #b_br{bool=#b_literal{val=true},succ=Fail,fail=Fail}. - -%% -%% Success checks need to be treated differently in bodies and guards; a check -%% in a guard can be safely removed when we know it fails because we know -%% there's never any side-effects, but in bodies the checked instruction may -%% throw an exception and we need to ensure it isn't optimized away. -%% -%% Checks are expressed as {succeeded,guard} and {succeeded,body} respectively, -%% where the latter has a side-effect (see beam_ssa:no_side_effect/1) and the -%% former does not. This ensures that passes like ssa_opt_dead and ssa_opt_live -%% won't optimize away pure operations that may throw an exception, since their -%% result is used in {succeeded,body}. -%% -%% Other than the above details, the two variants are equivalent and most -%% passes that care about them can simply match {succeeded,_}. -%% - -make_succeeded(Var, {guard, Fail}, St) -> - make_succeeded_1(Var, guard, Fail, St); -make_succeeded(Var, {in_catch, CatchLbl}, St) -> - make_succeeded_1(Var, body, CatchLbl, St); -make_succeeded(Var, {no_catch, Fail}, St) -> - #cg{ultimate_failure=Fail} = St, %Assertion - make_succeeded_1(Var, body, Fail, St). - -make_succeeded_1(Var, Kind, Fail, St0) -> - {Bool,St1} = new_ssa_var('@ssa_bool', St0), - {Succ,St} = new_label(St1), - - Check = [#b_set{op={succeeded,Kind},dst=Bool,args=[Var]}, - #b_br{bool=Bool,succ=Succ,fail=Fail}], - - {Check ++ [{label,Succ}], St}. - -%% Instructions for selection of binary segments. - -select_bin_segs(Scs, Ivar, Tf, St) -> - match_fmf(fun(S, Fail, Sta) -> - select_bin_seg(S, Ivar, Fail, Sta) - end, Tf, St, Scs). - -select_bin_seg(#k_val_clause{val=#k_bin_seg{size=Size,unit=U,type=T, - seg=Seg,flags=Fs,next=Next}, - body=B,anno=Anno}, - #k_var{}=Src, Fail, St0) -> - LineAnno = line_anno(Anno), - Ctx = get_context(Src, St0), - {Mis,St1} = select_extract_bin(Next, Size, U, T, Fs, Fail, - Ctx, LineAnno, St0), - {Extracted,St2} = new_ssa_var(Seg#k_var.name, St1), - {Bis,St} = match_cg(B, Fail, St2), - BsGet = #b_set{op=bs_extract,dst=Extracted,args=[ssa_arg(Next, St)]}, - Is = Mis ++ [BsGet] ++ Bis, - {Is,St}; -select_bin_seg(#k_val_clause{val=#k_bin_int{size=Sz,unit=U,flags=Fs, - val=Val,next=Next}, - body=B}, - #k_var{}=Src, Fail, St0) -> - Ctx = get_context(Src, St0), - {Mis,St1} = select_extract_int(Next, Val, Sz, U, Fs, Fail, - Ctx, St0), - {Bis,St} = match_cg(B, Fail, St1), - Is = case Mis ++ Bis of - [#b_set{op=bs_match,args=[#b_literal{val=string},OtherCtx1,Bin1]}, - #b_set{op={succeeded,guard},dst=Bool1}, - #b_br{bool=Bool1,succ=Succ,fail=Fail}, - {label,Succ}, - #b_set{op=bs_match,dst=Dst,args=[#b_literal{val=string},_OtherCtx2,Bin2]}| - [#b_set{op={succeeded,guard},dst=Bool2}, - #b_br{bool=Bool2,fail=Fail}|_]=Is0] -> - %% We used to do this optimization later, but it - %% turns out that in huge functions with many - %% string matching instructions, it's a huge win - %% to do the combination now. To avoid copying the - %% binary data again and again, we'll combine bitstrings - %% in a list and convert all of it to a bitstring later. - {#b_literal{val=B1},#b_literal{val=B2}} = {Bin1,Bin2}, - Bin = #b_literal{val=[B1,B2]}, - Set = #b_set{op=bs_match,dst=Dst,args=[#b_literal{val=string},OtherCtx1,Bin]}, - [Set|Is0]; - Is0 -> - Is0 - end, - {Is,St}. - -get_context(#k_var{}=Var, St) -> - ssa_arg(Var, St). - -select_bin_end(#k_val_clause{val=#k_bin_end{},body=B}, Src, Tf, St0) -> - Ctx = get_context(Src, St0), - {Bis,St1} = match_cg(B, Tf, St0), - {TestIs,St} = make_cond_branch(bs_test_tail, [Ctx,#b_literal{val=0}], Tf, St1), - Is = TestIs++Bis, - {Is,St}. - -select_extract_bin(#k_var{name=Hd}, Size0, Unit, Type, Flags, Vf, - Ctx, Anno, St0) -> - {Dst,St1} = new_ssa_var(Hd, St0), - Size = case {Size0,ssa_arg(Size0, St0)} of - {#k_var{},#b_literal{val=all}} -> - %% The size `all` is used for the size of the final binary - %% segment in a pattern. Using `all` explicitly is not allowed, - %% so we convert it to an obvious invalid size. - #b_literal{val=bad_size}; - {_,Size1} -> - Size1 - end, - build_bs_instr(Anno, Type, Vf, Ctx, Size, Unit, Flags, Dst, St1). - -select_extract_int(#k_var{name=Tl}, 0, #k_literal{val=0}, _U, _Fs, _Vf, - Ctx, St0) -> - St = set_ssa_var(Tl, Ctx, St0), - {[],St}; -select_extract_int(#k_var{name=Tl}, Val, #k_literal{val=Sz}, U, Fs, Vf, - Ctx, St0) when is_integer(Sz) -> - {Dst,St1} = new_ssa_var(Tl, St0), - Bits = U*Sz, - Bin = case member(big, Fs) of - true -> - <<Val:Bits>>; - false -> - true = member(little, Fs), %Assertion. - <<Val:Bits/little>> - end, - Bits = bit_size(Bin), %Assertion. - {TestIs,St} = make_succeeded(Dst, {guard, Vf}, St1), - Set = #b_set{op=bs_match,dst=Dst, - args=[#b_literal{val=string},Ctx,#b_literal{val=Bin}]}, - {[Set|TestIs],St}. - -build_bs_instr(Anno, Type, Fail, Ctx, Size, Unit0, Flags0, Dst, St0) -> - Unit = #b_literal{val=Unit0}, - Flags = #b_literal{val=Flags0}, - NeedSize = bs_need_size(Type), - TypeArg = #b_literal{val=Type}, - Get = case NeedSize of - true -> - #b_set{anno=Anno,op=bs_match,dst=Dst, - args=[TypeArg,Ctx,Flags,Size,Unit]}; - false -> - #b_set{anno=Anno,op=bs_match,dst=Dst, - args=[TypeArg,Ctx,Flags]} - end, - {Is,St} = make_succeeded(Dst, {guard, Fail}, St0), - {[Get|Is],St}. - -select_val(#k_val_clause{val=#k_tuple{es=Es},body=B}, V, Vf, St0) -> - {Eis,St1} = select_extract_tuple(V, Es, St0), - {Bis,St2} = match_cg(B, Vf, St1), - {length(Es),Eis ++ Bis,St2}; -select_val(#k_val_clause{val=#k_literal{val=Val},body=B}, _V, Vf, St0) -> - {Bis,St1} = match_cg(B, Vf, St0), - {Val,Bis,St1}. - -%% select_extract_tuple(Src, [V], State) -> {[E],State}. -%% Extract tuple elements, but only if they are actually used. -%% -%% Not extracting tuple elements that are not used is an -%% optimization for compile time and memory use during compilation. -%% It is probably worthwhile because it is common to extract only a -%% few elements from a huge record. - -select_extract_tuple(Src, Vs, St0) -> - Tuple = ssa_arg(Src, St0), - F = fun (#k_var{anno=Anno,name=V}, {Elem,S0}) -> - case member(unused, Anno) of - true -> - {[],{Elem+1,S0}}; - false -> - Args = [Tuple,#b_literal{val=Elem}], - {Dst,S} = new_ssa_var(V, S0), - Get = #b_set{op=get_tuple_element, - dst=Dst,args=Args}, - {[Get],{Elem+1,S}} - end - end, - {Es,{_,St}} = flatmapfoldl(F, {0,St0}, Vs), - {Es,St}. - -select_map(Scs, V, Tf, Vf, St0) -> - MapSrc = ssa_arg(V, St0), - {Is,St1} = - match_fmf(fun(#k_val_clause{val=#k_map{op=exact,es=Es}, - body=B}, Fail, St1) -> - select_map_val(V, Es, B, Fail, St1) - end, Vf, St0, Scs), - {TestIs,St} = make_cond_branch({bif,is_map}, [MapSrc], Tf, St1), - {TestIs++Is,St}. - -select_map_val(V, Es, B, Fail, St0) -> - {Eis,St1} = select_extract_map(Es, V, Fail, St0), - {Bis,St2} = match_cg(B, Fail, St1), - {Eis++Bis,St2}. - -select_extract_map([P|Ps], Src, Fail, St0) -> - MapSrc = ssa_arg(Src, St0), - #k_map_pair{key=Key0,val=#k_var{name=Dst0}} = P, - Key = ssa_arg(Key0, St0), - {Dst,St1} = new_ssa_var(Dst0, St0), - Set = #b_set{op=get_map_element,dst=Dst,args=[MapSrc,Key]}, - {TestIs,St2} = make_succeeded(Dst, {guard, Fail}, St1), - {Is,St} = select_extract_map(Ps, Src, Fail, St2), - {[Set|TestIs]++Is,St}; -select_extract_map([], _, _, St) -> - {[],St}. - -select_extract_cons(Src0, [#k_var{name=Hd},#k_var{name=Tl}], St0) -> - Src = ssa_arg(Src0, St0), - {HdDst,St1} = new_ssa_var(Hd, St0), - {TlDst,St2} = new_ssa_var(Tl, St1), - GetHd = #b_set{op=get_hd,dst=HdDst,args=[Src]}, - GetTl = #b_set{op=get_tl,dst=TlDst,args=[Src]}, - {[GetHd,GetTl],St2}. - -guard_clause_cg(#k_guard_clause{guard=G,body=B}, Fail, St0) -> - {Gis,St1} = guard_cg(G, Fail, St0), - {Bis,St} = match_cg(B, Fail, St1), - {Gis ++ Bis,St}. - -%% guard_cg(Guard, Fail, State) -> {[Ainstr],State}. -%% A guard is a boolean expression of tests. Tests return true or -%% false. A fault in a test causes the test to return false. Tests -%% never return the boolean, instead we generate jump code to go to -%% the correct exit point. Primops and tests all go to the next -%% instruction on success or jump to a failure label. - -guard_cg(#k_try{arg=Ts,vars=[],body=#k_break{args=[]}, - evars=[],handler=#k_break{args=[]}}, - Fail, - #cg{bfail=OldBfail,break=OldBreak}=St0) -> - %% Do a try/catch without return value for effect. The return - %% value is not checked; success passes on to the next instruction - %% and failure jumps to Fail. - {Next,St1} = new_label(St0), - {Tis,St2} = guard_cg(Ts, Fail, St1#cg{bfail=Fail,break=Next}), - Is = Tis ++ [{label,Next},#cg_phi{vars=[]}], - {Is,St2#cg{bfail=OldBfail,break=OldBreak}}; -guard_cg(#k_test{op=Test0,args=As}, Fail, St0) -> - #k_remote{mod=#k_literal{val=erlang},name=#k_literal{val=Test}} = Test0, - test_cg(Test, false, As, Fail, St0); -guard_cg(#k_seq{arg=Arg,body=Body}, Fail, St0) -> - {ArgIs,St1} = guard_cg(Arg, Fail, St0), - {BodyIs,St} = guard_cg(Body, Fail, St1), - {ArgIs++BodyIs,St}; -guard_cg(G, _Fail, St) -> - cg(G, St). - -test_cg('=/=', Inverted, As, Fail, St) -> - test_cg('=:=', not Inverted, As, Fail, St); -test_cg('/=', Inverted, As, Fail, St) -> - test_cg('==', not Inverted, As, Fail, St); -test_cg(Test, Inverted, As0, Fail, St0) -> - As = ssa_args(As0, St0), - case {Test,ssa_args(As0, St0)} of - {is_record,[Tuple,#b_literal{val=Atom}=Tag,#b_literal{val=Int}=Arity]} - when is_atom(Atom), is_integer(Int) -> - false = Inverted, %Assertion. - test_is_record_cg(Fail, Tuple, Tag, Arity, St0); - {_,As} -> - {Bool,St1} = new_ssa_var('@ssa_bool', St0), - {Succ,St} = new_label(St1), - Bif = #b_set{op={bif,Test},dst=Bool,args=As}, - Br = case Inverted of - false -> #b_br{bool=Bool,succ=Succ,fail=Fail}; - true -> #b_br{bool=Bool,succ=Fail,fail=Succ} - end, - {[Bif,Br,{label,Succ}],St} - end. - -test_is_record_cg(Fail, Tuple, TagVal, ArityVal, St0) -> - {Arity,St1} = new_ssa_var('@ssa_arity', St0), - {Tag,St2} = new_ssa_var('@ssa_tag', St1), - {Is0,St3} = make_cond_branch({bif,is_tuple}, [Tuple], Fail, St2), - GetArity = #b_set{op={bif,tuple_size},dst=Arity,args=[Tuple]}, - {Is1,St4} = make_cond_branch({bif,'=:='}, [Arity,ArityVal], Fail, St3), - GetTag = #b_set{op=get_tuple_element,dst=Tag, - args=[Tuple,#b_literal{val=0}]}, - {Is2,St} = make_cond_branch({bif,'=:='}, [Tag,TagVal], Fail, St4), - Is = Is0 ++ [GetArity] ++ Is1 ++ [GetTag] ++ Is2, - {Is,St}. - -%% match_fmf(Fun, LastFail, State, [Clause]) -> {Is,State}. -%% This is a special flatmapfoldl for match code gen where we -%% generate a "failure" label for each clause. The last clause uses -%% an externally generated failure label, LastFail. N.B. We do not -%% know or care how the failure labels are used. - -match_fmf(F, LastFail, St, [H]) -> - F(H, LastFail, St); -match_fmf(F, LastFail, St0, [H|T]) -> - {Fail,St1} = new_label(St0), - {R,St2} = F(H, Fail, St1), - {Rs,St3} = match_fmf(F, LastFail, St2, T), - {R ++ [{label,Fail}] ++ Rs,St3}. - -%% fail_context(State) -> {Where,FailureLabel}. -%% Where = guard | no_catch | in_catch -%% Return an indication of which part of a function code is -%% being generated for and the appropriate failure label to -%% use. -%% -%% Where has the following meaning: -%% -%% guard - Inside a guard. -%% no_catch - In a function body, not in the scope of -%% a try/catch or catch. -%% in_catch - In the scope of a try/catch or catch. - -fail_context(#cg{catch_label=Catch,bfail=Fail,ultimate_failure=Ult}) -> - if - Fail =/= Ult -> - {guard,Fail}; - Catch =:= none -> - {no_catch,Fail}; - is_integer(Catch) -> - {in_catch,Catch} - end. - -%% call_cg(Func, [Arg], [Ret], Le, State) -> -%% {[Ainstr],State}. -%% enter_cg(Func, [Arg], Le, St) -> {[Ainstr],St}. -%% Generate code for call and enter. - -call_cg(Func, As, [], Le, St) -> - call_cg(Func, As, [#k_var{name='@ssa_ignored'}], Le, St); -call_cg(Func, As, [#k_var{name=R}|MoreRs]=Rs, Le, St0) -> - case fail_context(St0) of - {guard,Fail} -> - %% Inside a guard. The only allowed function call is to - %% erlang:error/1,2. We will generate a branch to the - %% failure branch. - #k_remote{mod=#k_literal{val=erlang}, - name=#k_literal{val=error}} = Func, %Assertion. - St = set_unused_ssa_vars(Rs, St0), - {[make_uncond_branch(Fail),#cg_unreachable{}],St}; - FailCtx -> - %% Ordinary function call in a function body. - Args = ssa_args([Func|As], St0), - {Ret,St1} = new_ssa_var(R, St0), - Call = #b_set{anno=line_anno(Le),op=call,dst=Ret,args=Args}, - - %% If this is a call to erlang:error(), MoreRs could be a - %% nonempty list of variables that each need a value. - St2 = set_unused_ssa_vars(MoreRs, St1), - - {TestIs,St} = make_succeeded(Ret, FailCtx, St2), - {[Call|TestIs],St} - end. - -enter_cg(Func, As0, Le, St0) -> - {no_catch,_} = FailCtx = fail_context(St0), %Assertion. - - As = ssa_args([Func|As0], St0), - {Ret,St2} = new_ssa_var('@ssa_ret', St0), - Call = #b_set{anno=line_anno(Le),op=call,dst=Ret,args=As}, - - {TestIs,St} = make_succeeded(Ret, FailCtx, St2), - {[Call | TestIs] ++ [#b_ret{arg=Ret}],St}. - -%% bif_cg(#k_bif{}, Le,State) -> {[Ainstr],State}. -%% Generate code for a guard BIF or primop. - -bif_cg(#k_bif{anno=A,op=#k_internal{name=Name},args=As,ret=Rs}, _Le, St) -> - internal_cg(internal_anno(A), Name, As, Rs, St); -bif_cg(#k_bif{op=#k_remote{mod=#k_literal{val=erlang},name=#k_literal{val=Name}}, - args=As,ret=Rs}, Le, St) -> - bif_cg(Name, As, Rs, Le, St). - -internal_anno(Le) -> - Anno = line_anno(Le), - case keyfind(inlined, 1, Le) of - false -> Anno; - {inlined, NameArity} -> Anno#{ inlined => NameArity } - end. - -%% internal_cg(Bif, [Arg], [Ret], Le, State) -> -%% {[Ainstr],State}. -internal_cg(Anno, Op, As, Rs, St0) - when Op =:= match_fail; Op =:= raise; Op =:= raw_raise -> - {Dst, St1} = case Rs of - [#k_var{name=Dst0} | Rest] -> - {Var, StV} = new_ssa_var(Dst0, St0), - {Var, set_unused_ssa_vars(Rest, StV)}; - [] -> - new_ssa_var('@exception', St0) - end, - - {Kind, _Fail} = Context = fail_context(St1), - true = (Kind =/= guard) orelse (Op =:= match_fail), %Assertion. - - Args = ssa_args(As, St1), - - Set = #b_set{anno=Anno,op=fix_op(Op, St1),dst=Dst,args=Args}, - - {TestIs, St} = make_succeeded(Dst, Context, St1), - {[Set | TestIs], St}; -internal_cg(Anno, recv_peek_message, [], [#k_var{name=Succeeded0}, - #k_var{name=Dst0}], St0) -> - {Dst,St1} = new_ssa_var(Dst0, St0), - St = new_succeeded_value(Succeeded0, Dst, St1), - Set = #b_set{ anno=Anno, - op=peek_message, - dst=Dst, - args=[#b_literal{val=none}] }, - {[Set],St}; -internal_cg(_Anno, recv_wait_timeout, As, [#k_var{name=Succeeded0}], St0) -> - %% Note that the `wait_timeout` instruction can potentially branch in three - %% different directions: - %% - %% * A new message is available in the message queue. `wait_timeout` - %% branches to the given label. - %% - %% * The timeout expired. `wait_timeout` transfers control to the next - %% instruction. - %% - %% * The value for timeout duration is invalid (either not an integer or - %% negative or too large). A `timeout_value` exception will be raised. - %% - %% `wait_timeout` will be represented like this in SSA code: - %% - %% WaitBool = wait_timeout TimeoutValue - %% Succeeded = succeeded:body WaitBool - %% br Succeeded, ^good_timeout_value, ^bad_timeout_value - %% - %% good_timeout_value: - %% br WaitBool, ^timeout_expired, ^new_message_received - %% - Args = ssa_args(As, St0), - {Wait,St1} = new_ssa_var('@ssa_wait', St0), - {Succ,St2} = make_succeeded(Wait, fail_context(St1), St1), - St = new_bool_value(Succeeded0, Wait, St2), - Set = #b_set{op=wait_timeout,dst=Wait,args=Args}, - {[Set|Succ],St}; -internal_cg(Anno, Op0, As, [#k_var{name=Dst0}], St0) when is_atom(Op0) -> - %% This behaves like a function call. - {Dst,St} = new_ssa_var(Dst0, St0), - Args = ssa_args(As, St), - Op = fix_op(Op0, St), - Set = #b_set{anno=Anno,op=Op,dst=Dst,args=Args}, - {[Set],St}; -internal_cg(Anno, Op0, As, [], St0) when is_atom(Op0) -> - %% This behaves like a function call. - {Dst,St} = new_ssa_var('@ssa_ignored', St0), - Args = ssa_args(As, St), - Op = fix_op(Op0, St), - Set = #b_set{anno=Anno,op=Op,dst=Dst,args=Args}, - {[Set],St}. - -fix_op(make_fun, #cg{no_make_fun3=true}) -> old_make_fun; -fix_op(raise, _) -> resume; -fix_op(Op, _) -> Op. - -bif_cg(Bif, As0, [#k_var{name=Dst0}], Le, St0) -> - {Dst,St1} = new_ssa_var(Dst0, St0), - case {Bif,ssa_args(As0, St0)} of - {is_record,[Tuple,#b_literal{val=Atom}=Tag, - #b_literal{val=Int}=Arity]} - when is_atom(Atom), is_integer(Int) -> - bif_is_record_cg(Dst, Tuple, Tag, Arity, St1); - {_,As} -> - I = #b_set{anno=line_anno(Le),op={bif,Bif},dst=Dst,args=As}, - case erl_bifs:is_safe(erlang, Bif, length(As)) of - false -> - FailCtx = fail_context(St1), - {Is,St} = make_succeeded(Dst, FailCtx, St1), - {[I|Is],St}; - true-> - {[I],St1} - end - end. - -bif_is_record_cg(Dst, Tuple, TagVal, ArityVal, St0) -> - {Arity,St1} = new_ssa_var('@ssa_arity', St0), - {Tag,St2} = new_ssa_var('@ssa_tag', St1), - {Phi,St3} = new_label(St2), - {False,St4} = new_label(St3), - {Is0,St5} = make_cond_branch({bif,is_tuple}, [Tuple], False, St4), - GetArity = #b_set{op={bif,tuple_size},dst=Arity,args=[Tuple]}, - {Is1,St6} = make_cond_branch({bif,'=:='}, [Arity,ArityVal], False, St5), - GetTag = #b_set{op=get_tuple_element,dst=Tag, - args=[Tuple,#b_literal{val=0}]}, - {Is2,St} = make_cond_branch({bif,'=:='}, [Tag,TagVal], False, St6), - Is3 = [#cg_break{args=[#b_literal{val=true}],phi=Phi}, - {label,False}, - #cg_break{args=[#b_literal{val=false}],phi=Phi}, - {label,Phi}, - #cg_phi{vars=[Dst]}], - Is = Is0 ++ [GetArity] ++ Is1 ++ [GetTag] ++ Is2 ++ Is3, - {Is,St}. - -%% try_cg(TryBlock, [BodyVar], TryBody, [ExcpVar], TryHandler, [Ret], St) -> -%% {[Ainstr],St}. - -try_cg(Ta, Vs, Tb, Evs, Th, Rs, St0) -> - {B,St1} = new_label(St0), %Body label - {H,St2} = new_label(St1), %Handler label - {E,St3} = new_label(St2), %End label - {Next,St4} = new_label(St3), - {TryTag,St5} = new_ssa_var('@ssa_catch_tag', St4), - {SsaVs,St6} = new_ssa_vars(Vs, St5), - {SsaEvs,St7} = new_ssa_vars(Evs, St6), - {Ais,St8} = cg(Ta, St7#cg{break=B,catch_label=H}), - - %% We try to avoid constructing a try/catch if the expression to - %% be evaluated don't have any side effects and if the error - %% reason is not explicitly matched. - %% - %% Starting in OTP 23, segment sizes in binary matching and keys - %% in map matching are allowed to be arbitrary guard - %% expressions. Those expressions are evaluated in a try/catch - %% so that matching can continue with the next clause if the evaluation - %% of such expression fails. - %% - %% It is not allowed to use try/catch during matching in a receive - %% (the try/catch would force the saving of fragile message references - %% to the stack frame). Therefore, avoiding creating try/catch is - %% not merely an optimization but necessary for correctness. - - case {Vs,Tb,Th,is_guard_cg_safe_list(Ais)} of - {[#k_var{name=X}],#k_break{args=[#k_var{name=X}]}, - #k_break{args=[#k_literal{}]},true} -> - %% There are no instructions that will clobber X registers - %% and the exception is not matched. Therefore, a - %% try/catch is not needed. This code is probably located - %% in a guard. - {ProtIs,St9} = guard_cg(Ta, H, St7#cg{break=B,bfail=H}), - {His,St10} = cg(Th, St9), - {RetVars,St} = new_ssa_vars(Rs, St10), - Is = ProtIs ++ [{label,H}] ++ His ++ - [{label,B},#cg_phi{vars=RetVars}], - {Is,St#cg{break=St0#cg.break,bfail=St7#cg.bfail}}; - {[#k_var{name=X}],#k_break{args=[#k_literal{}=SuccLit0,#k_var{name=X}]}, - #k_break{args=[#k_literal{val=false},#k_literal{}]},true} -> - %% There are no instructions that will clobber X registers - %% and the exception is not matched. Therefore, a - %% try/catch is not needed. This code probably evaluates - %% a key expression in map matching. - {FinalLabel,St9} = new_label(St7), - {ProtIs,St10} = guard_cg(Ta, H, St9#cg{break=B,bfail=H}), - {His,St11} = cg(Th, St10#cg{break=FinalLabel}), - {RetVars,St12} = new_ssa_vars(Rs, St11), - {Result,St} = new_ssa_var('@ssa_result', St12), - SuccLit = ssa_arg(SuccLit0, St), - Is = ProtIs ++ [{label,H}] ++ His ++ - [{label,B}, - #cg_phi{vars=[Result]}, - #cg_break{args=[SuccLit,Result],phi=FinalLabel}, - {label,FinalLabel}, - #cg_phi{vars=RetVars}], - {Is,St#cg{break=St0#cg.break,bfail=St7#cg.bfail}}; - {_,#k_break{args=[]},#k_break{args=[]},true} -> - %% There are no instructions that will clobber X registers - %% and the exception is not matched. Therefore, a - %% try/catch is not needed. This code probably does the - %% size calculation for a segment in binary matching. - {ProtIs,St9} = guard_cg(Ta, H, St7#cg{break=B,bfail=H}), - {His,St10} = cg(Th, St9), - {RetVars,St} = new_ssa_vars(Rs, St10), - Is = ProtIs ++ [{label,H}] ++ His ++ - [{label,B},#cg_phi{vars=RetVars}], - {Is,St#cg{break=St0#cg.break,bfail=St7#cg.bfail}}; - {_,_,_,_} -> - %% The general try/catch (not in a guard). - St9 = St8#cg{break=E,catch_label=St7#cg.catch_label}, - {Bis,St10} = cg(Tb, St9), - {His,St11} = cg(Th, St10), - {BreakVars,St12} = new_ssa_vars(Rs, St11), - {CatchedAgg,St13} = new_ssa_var('@ssa_agg', St12), - ExtractVs = extract_vars(SsaEvs, CatchedAgg, 0), - KillTryTag = #b_set{op=kill_try_tag,args=[TryTag]}, - Args = [#b_literal{val='try'},TryTag], - Handler = [{label,H}, - #b_set{op=landingpad,dst=CatchedAgg,args=Args}] ++ - ExtractVs ++ [KillTryTag], - {[#b_set{op=new_try_tag,dst=TryTag,args=[#b_literal{val='try'}]}, - #b_br{bool=TryTag,succ=Next,fail=H}, - {label,Next}] ++ Ais ++ - [{label,B},#cg_phi{vars=SsaVs},KillTryTag] ++ Bis ++ - Handler ++ His ++ - [{label,E},#cg_phi{vars=BreakVars}], - St13#cg{break=St0#cg.break}} - end. - -is_guard_cg_safe_list(Is) -> - all(fun is_guard_cg_safe/1, Is). - -is_guard_cg_safe(#b_set{op=call,args=Args}) -> - case Args of - [#b_remote{mod=#b_literal{val=erlang}, - name=#b_literal{val=error}, - arity=1}|_] -> - true; - _ -> - false - end; -is_guard_cg_safe(#b_set{}=I) -> not beam_ssa:clobbers_xregs(I); -is_guard_cg_safe(#b_br{}) -> true; -is_guard_cg_safe(#b_switch{}) -> true; -is_guard_cg_safe(#cg_break{}) -> true; -is_guard_cg_safe(#cg_phi{}) -> true; -is_guard_cg_safe({label,_}) -> true; -is_guard_cg_safe(#cg_unreachable{}) -> false. - -try_enter_cg(Ta, Vs, Tb, Evs, Th, St0) -> - {B,St1} = new_label(St0), %Body label - {H,St2} = new_label(St1), %Handler label - {Next,St3} = new_label(St2), - {TryTag,St4} = new_ssa_var('@ssa_catch_tag', St3), - {SsaVs,St5} = new_ssa_vars(Vs, St4), - {SsaEvs,St6} = new_ssa_vars(Evs, St5), - {Ais,St7} = cg(Ta, St6#cg{break=B,catch_label=H}), - St8 = St7#cg{catch_label=St6#cg.catch_label}, - {Bis,St9} = cg(Tb, St8), - {His,St10} = cg(Th, St9), - {CatchedAgg,St} = new_ssa_var('@ssa_agg', St10), - ExtractVs = extract_vars(SsaEvs, CatchedAgg, 0), - KillTryTag = #b_set{op=kill_try_tag,args=[TryTag]}, - Args = [#b_literal{val='try'},TryTag], - Handler = [{label,H}, - #b_set{op=landingpad,dst=CatchedAgg,args=Args}] ++ - ExtractVs ++ [KillTryTag], - {[#b_set{op=new_try_tag,dst=TryTag,args=[#b_literal{val='try'}]}, - #b_br{bool=TryTag,succ=Next,fail=H}, - {label,Next}] ++ Ais ++ - [{label,B},#cg_phi{vars=SsaVs},KillTryTag] ++ Bis ++ - Handler ++ His, - St#cg{break=St0#cg.break}}. - -extract_vars([V|Vs], Agg, N) -> - I = #b_set{op=extract,dst=V,args=[Agg,#b_literal{val=N}]}, - [I|extract_vars(Vs, Agg, N+1)]; -extract_vars([], _, _) -> []. - -%% do_catch_cg(CatchBlock, Ret, St) -> {[Ainstr],St}. - -do_catch_cg(Block, #k_var{name=R}, St0) -> - {B,St1} = new_label(St0), - {Next,St2} = new_label(St1), - {H,St3} = new_label(St2), - {CatchReg,St4} = new_ssa_var('@ssa_catch_tag', St3), - {Dst,St5} = new_ssa_var(R, St4), - {Succ,St6} = new_label(St5), - {Cis,St7} = cg(Block, St6#cg{break=Succ,catch_label=H}), - {CatchedVal,St8} = new_ssa_var('@catched_val', St7), - {SuccVal,St9} = new_ssa_var('@success_val', St8), - {CatchedAgg,St10} = new_ssa_var('@ssa_agg', St9), - {CatchEndVal,St} = new_ssa_var('@catch_end_val', St10), - Args = [#b_literal{val='catch'},CatchReg], - {[#b_set{op=new_try_tag,dst=CatchReg,args=[#b_literal{val='catch'}]}, - #b_br{bool=CatchReg,succ=Next,fail=H}, - {label,Next}] ++ Cis ++ - [{label,H}, - #b_set{op=landingpad,dst=CatchedAgg,args=Args}, - #b_set{op=extract,dst=CatchedVal, - args=[CatchedAgg,#b_literal{val=0}]}, - #cg_break{args=[CatchedVal],phi=B}, - {label,Succ}, - #cg_phi{vars=[SuccVal]}, - #cg_break{args=[SuccVal],phi=B}, - {label,B},#cg_phi{vars=[CatchEndVal]}, - #b_set{op=catch_end,dst=Dst,args=[CatchReg,CatchEndVal]}], - St#cg{break=St1#cg.break,catch_label=St1#cg.catch_label}}. - -%% put_cg([Var], Constr, Le, Vdb, Bef, St) -> {[Ainstr],St}. -%% Generate code for constructing terms. - -put_cg([#k_var{name=R}], #k_cons{hd=Hd,tl=Tl}, _Le, St0) -> - Args = ssa_args([Hd,Tl], St0), - {Dst,St} = new_ssa_var(R, St0), - PutList = #b_set{op=put_list,dst=Dst,args=Args}, - {[PutList],St}; -put_cg([#k_var{name=R}], #k_tuple{es=Es}, _Le, St0) -> - {Ret,St} = new_ssa_var(R, St0), - Args = ssa_args(Es, St), - PutTuple = #b_set{op=put_tuple,dst=Ret,args=Args}, - {[PutTuple],St}; -put_cg([#k_var{name=R}], #k_binary{segs=Segs}, Le, St0) -> - FailCtx = fail_context(St0), - {Dst,St1} = new_ssa_var(R, St0), - cg_binary(Dst, Segs, FailCtx, Le, St1); -put_cg([#k_var{name=R}], #k_map{op=Op,var=Map, - es=[#k_map_pair{key=#k_var{}=K,val=V}]}, - Le, St0) -> - %% Map: single variable key. - SrcMap = ssa_arg(Map, St0), - LineAnno = line_anno(Le), - List = [ssa_arg(K, St0),ssa_arg(V, St0)], - {Dst,St1} = new_ssa_var(R, St0), - {Is,St} = put_cg_map(LineAnno, Op, SrcMap, Dst, List, St1), - {Is,St}; -put_cg([#k_var{name=R}], #k_map{op=Op,var=Map,es=Es}, Le, St0) -> - %% Map: one or more literal keys. - [] = [Var || #k_map_pair{key=#k_var{}=Var} <- Es], %Assertion - SrcMap = ssa_arg(Map, St0), - LineAnno = line_anno(Le), - List = flatmap(fun(#k_map_pair{key=K,val=V}) -> - [ssa_arg(K, St0),ssa_arg(V, St0)] - end, Es), - {Dst,St1} = new_ssa_var(R, St0), - {Is,St} = put_cg_map(LineAnno, Op, SrcMap, Dst, List, St1), - {Is,St}; -put_cg([#k_var{name=R}], Con0, _Le, St0) -> - %% Create an alias for a variable or literal. - Con = ssa_arg(Con0, St0), - St = set_ssa_var(R, Con, St0), - {[],St}. - -put_cg_map(LineAnno, Op, SrcMap, Dst, List, St0) -> - Args = [#b_literal{val=Op},SrcMap|List], - PutMap = #b_set{anno=LineAnno,op=put_map,dst=Dst,args=Args}, - if - Op =:= assoc -> - {[PutMap],St0}; - true -> - FailCtx = fail_context(St0), - {Is,St} = make_succeeded(Dst, FailCtx, St0), - {[PutMap|Is],St} - end. - -%%% -%%% Code generation for constructing binaries. -%%% - -cg_binary(Dst, Segs0, FailCtx, Le, St0) -> - Segs1 = cg_bin_segments(Segs0, St0), - Segs = case Segs1 of - [#b_literal{val=binary},UnitFlags,Val,#b_literal{val=all}|Segs2] -> - Op = case member(single_use, Le) of - true -> private_append; - false -> append - end, - [#b_literal{val=Op},UnitFlags,Val,#b_literal{val=all}|Segs2]; - _ -> - Segs1 - end, - LineAnno = line_anno(Le), - Build = #b_set{anno=LineAnno,op=bs_create_bin,args=Segs,dst=Dst}, - {TestIs,St} = make_succeeded(Dst, FailCtx, St0), - {[Build|TestIs],St}. - -cg_bin_segments(#k_bin_seg{anno=Anno,type=Type,flags=Flags0,seg=Src0,size=Size0,unit=U,next=Next}, St) -> - Seg = case lists:keyfind(segment, 1,Anno) of - false -> []; - {segment,_}=Seg0 -> [Seg0] - end, - [Src,Size] = ssa_args([Src0,Size0], St), - TypeArg = #b_literal{val=Type}, - Unit = case U of - undefined -> 0; - _ -> U - end, - Flags = strip_bs_construct_flags(Flags0), - UnitFlags = #b_literal{val=[Unit|Flags++Seg]}, - [TypeArg,UnitFlags,Src,Size|cg_bin_segments(Next, St)]; -cg_bin_segments(#k_bin_end{}, _St) -> []. - -bs_need_size(utf8) -> false; -bs_need_size(utf16) -> false; -bs_need_size(utf32) -> false; -bs_need_size(_) -> true. - -%% Only keep the flags that have a meaning for binary construction and -%% are distinct from the default value. -strip_bs_construct_flags(Flags) -> - [Flag || Flag <- Flags, - case Flag of - little -> true; - native -> true; - big -> false; - signed -> false; - unsigned -> false - end]. - -%%% -%%% Utilities for creating the SSA types. -%%% - -ssa_args(As, St) -> - [ssa_arg(A, St) || A <- As]. - -ssa_arg(#k_var{name=V}, #cg{vars=Vars}) -> map_get(V, Vars); -ssa_arg(#k_literal{val=V}, _) -> #b_literal{val=V}; -ssa_arg(#k_remote{mod=Mod0,name=Name0,arity=Arity}, St) -> - Mod = ssa_arg(Mod0, St), - Name = ssa_arg(Name0, St), - #b_remote{mod=Mod,name=Name,arity=Arity}; -ssa_arg(#k_local{name=Name,arity=Arity}, _) when is_atom(Name) -> - #b_local{name=#b_literal{val=Name},arity=Arity}. - -new_succeeded_value(VarBase, Var, #cg{vars=Vars0}=St) -> - Vars = Vars0#{VarBase=>{succeeded,Var}}, - St#cg{vars=Vars}. - -new_bool_value(VarBase, Var, #cg{vars=Vars0}=St) -> - Vars = Vars0#{VarBase=>{bool,Var}}, - St#cg{vars=Vars}. - -new_ssa_vars(Vs, St) -> - mapfoldl(fun(#k_var{name=V}, S) -> - new_ssa_var(V, S) - end, St, Vs). - -new_ssa_var(VarBase, #cg{lcount=Uniq,vars=Vars}=St0) - when is_atom(VarBase); is_integer(VarBase) -> - case Vars of - #{VarBase:=_} -> - Var = #b_var{name={VarBase,Uniq}}, - St = St0#cg{lcount=Uniq+1,vars=Vars#{VarBase=>Var}}, - {Var,St}; - #{} -> - Var = #b_var{name=VarBase}, - St = St0#cg{vars=Vars#{VarBase=>Var}}, - {Var,St} - end. - -set_unused_ssa_vars(Vars, St) -> - foldl(fun(#k_var{name=V}, S) -> - set_ssa_var(V, #b_literal{val=unused}, S) - end, St, Vars). - -set_ssa_var(VarBase, Val, #cg{vars=Vars}=St) - when is_atom(VarBase); is_integer(VarBase) -> - St#cg{vars=Vars#{VarBase=>Val}}. - -%% new_label(St) -> {L,St}. - -new_label(#cg{lcount=Next}=St) -> - {Next,St#cg{lcount=Next+1}}. - -%% line_anno(Le) -> #{} | #{location:={File,Line}}. -%% Create a location annotation, containing information about the -%% current filename and line number. The annotation should be -%% included in any operation that could cause an exception. - -line_anno([Line,{file,Name}]) when is_integer(Line) -> - line_anno_1(Name, Line); -line_anno([{Line,Column},{file,Name}]) when is_integer(Line), - is_integer(Column) -> - line_anno_1(Name, Line); -line_anno([_|_]=A) -> - {Name,Line} = find_loc(A, no_file, 0), - line_anno_1(Name, Line); -line_anno([]) -> - #{}. - -line_anno_1(no_file, _) -> - #{}; -line_anno_1(_, 0) -> - %% Missing line number or line number 0. - #{}; -line_anno_1(Name, Line) -> - #{location=>{Name,Line}}. - -find_loc([Line|T], File, _) when is_integer(Line) -> - find_loc(T, File, Line); -find_loc([{Line, Column}|T], File, _) when is_integer(Line), - is_integer(Column) -> - find_loc(T, File, Line); -find_loc([{file,File}|T], _, Line) -> - find_loc(T, File, Line); -find_loc([_|T], File, Line) -> - find_loc(T, File, Line); -find_loc([], File, Line) -> {File,Line}. - -flatmapfoldl(F, Accu0, [Hd|Tail]) -> - {R,Accu1} = F(Hd, Accu0), - {Rs,Accu2} = flatmapfoldl(F, Accu1, Tail), - {R++Rs,Accu2}; -flatmapfoldl(_, Accu, []) -> {[],Accu}. - -%%% -%%% Finalize the code. -%%% - -finalize(Asm0, St0) -> - Asm1 = fix_phis(Asm0), - {Asm,St} = fix_sets(Asm1, [], St0), - {build_map(Asm),St}. - -%% fix_phis(Is0) -> Is. -%% Rewrite #cg_break{} and #cg_phi{} records to #b_set{} records. -%% A #cg_break{} is rewritten to an unconditional branch, and -%% and a #cg_phi{} is rewritten to one or more phi nodes. - -fix_phis(Is) -> - fix_phis_1(Is, none, #{}). - -fix_phis_1([{label,Lbl},#cg_phi{vars=Vars}|Is0], _Lbl, Map0) -> - case Map0 of - #{Lbl:=Pairs} -> - %% This phi node was referenced by at least one #cg_break{}. - %% Create the phi nodes. - Phis = gen_phis(Vars, Pairs), - Map = maps:remove(Lbl, Map0), - [{label,Lbl}] ++ Phis ++ fix_phis_1(Is0, Lbl, Map); - #{} -> - %% No #cg_break{} instructions reference this label. - %% #cg_break{} instructions must reference the labels for - %% #cg_phi{} instructions; therefore this label is - %% unreachable and can be dropped. - Is = drop_upto_label(Is0), - fix_phis_1(Is, none, Map0) - end; -fix_phis_1([{label,L}=I|Is], _Lbl, Map) -> - [I|fix_phis_1(Is, L, Map)]; -fix_phis_1([#cg_unreachable{}|Is0], _Lbl, Map) -> - Is = drop_upto_label(Is0), - fix_phis_1(Is, none, Map); -fix_phis_1([#cg_break{args=Args,phi=Target}|Is], Lbl, Map) when is_integer(Lbl) -> - %% Pair each argument with the label for this block and save in the map. - Pairs1 = case Map of - #{Target:=Pairs0} -> Pairs0; - #{} -> [] - end, - Pairs = [[{Arg,Lbl} || Arg <- Args]|Pairs1], - I = make_uncond_branch(Target), - [I|fix_phis_1(Is, none, Map#{Target=>Pairs})]; -fix_phis_1([I|Is], Lbl, Map) -> - [I|fix_phis_1(Is, Lbl, Map)]; -fix_phis_1([], _, Map) -> - [] = maps:to_list(Map), %Assertion. - []. - -gen_phis([V|Vs], Preds0) -> - {Pairs,Preds} = collect_preds(Preds0, [], []), - [_|_] = Pairs, %Assertion. - [#b_set{op=phi,dst=V,args=Pairs}|gen_phis(Vs, Preds)]; -gen_phis([], _) -> []. - -collect_preds([[First|Rest]|T], ColAcc, RestAcc) -> - collect_preds(T, [First|ColAcc], [Rest|RestAcc]); -collect_preds([], ColAcc, RestAcc) -> - {keysort(2, ColAcc),RestAcc}. - -drop_upto_label([{label,_}|_]=Is) -> Is; -drop_upto_label([_|Is]) -> drop_upto_label(Is). - -%% fix_sets(Is0, Acc, St0) -> {Is,St}. -%% Ensure that #b_set.dst is filled in with a proper variable. -%% (For convenience, for instructions that don't have a useful return value, -%% the code generator would set #b_set.dst to `none`.) - -fix_sets([#b_set{op=remove_message,dst=Dst}=Set,#b_ret{arg=Dst}=Ret|Is], Acc, St) -> - %% The remove_message instruction, which is an instruction without - %% value, was used in effect context in an `after` block. Example: - %% - %% try - %% . . . - %% after - %% . - %% . - %% . - %% receive _ -> ignored end - %% end, - %% ok. - %% - fix_sets(Is, [Ret#b_ret{arg=#b_literal{val=ok}},Set|Acc], St); -fix_sets([#b_set{dst=none}=Set|Is], Acc, St0) -> - {Dst,St} = new_ssa_var('@ssa_ignored', St0), - I = Set#b_set{dst=Dst}, - fix_sets(Is, [I|Acc], St); -fix_sets([I|Is], Acc, St) -> - fix_sets(Is, [I|Acc], St); -fix_sets([], Acc, St) -> - {reverse(Acc),St}. - -%% build_map(Is) -> #{}. -%% Split up the sequential instruction stream into blocks and -%% store them in a map. - -build_map(Is) -> - Linear0 = build_graph_1(Is, [], []), - Linear = beam_ssa:trim_unreachable(Linear0), - maps:from_list(Linear). - -build_graph_1([{label,L}|Is], Lbls, []) -> - build_graph_1(Is, [L|Lbls], []); -build_graph_1([{label,L}|Is], Lbls, [_|_]=BlockAcc) -> - make_blocks(Lbls, BlockAcc) ++ build_graph_1(Is, [L], []); -build_graph_1([I|Is], Lbls, BlockAcc) -> - build_graph_1(Is, Lbls, [I|BlockAcc]); -build_graph_1([], Lbls, BlockAcc) -> - make_blocks(Lbls, BlockAcc). - -make_blocks(Lbls, [Last0|Is0]) -> - Is = reverse(Is0), - Last = beam_ssa:normalize(Last0), - Block = #b_blk{is=Is,last=Last}, - [{L,Block} || L <- Lbls]. diff --git a/lib/compiler/src/beam_listing.erl b/lib/compiler/src/beam_listing.erl index a3491a3824..b9165341f7 100644 --- a/lib/compiler/src/beam_listing.erl +++ b/lib/compiler/src/beam_listing.erl @@ -22,7 +22,6 @@ -export([module/2]). -include("core_parse.hrl"). --include("v3_kernel.hrl"). -include("beam_ssa.hrl"). -include("beam_disasm.hrl"). @@ -30,25 +29,22 @@ -type code() :: cerl:c_module() | beam_utils:module_code() - | #k_mdef{} | [_]. %form-based format -spec module(file:io_device(), code()) -> 'ok'. module(File, #c_module{}=Core) -> - %% This is a core module. + %% This is a Core Erlang module. io:put_chars(File, core_pp:format(Core)); -module(File, #k_mdef{}=Kern) -> - %% This is a kernel module. - io:put_chars(File, v3_kernel_pp:format(Kern)); module(File, #b_module{name=Mod,exports=Exp,attributes=Attr,body=Fs}) -> + %% This an SSA module. io:format(File, "module ~p.\n", [Mod]), io:format(File, "exports ~p.\n", [Exp]), io:format(File, "attributes ~kp.\n\n", [Attr]), PP = [beam_ssa_pp:format_function(F) || F <- Fs], io:put_chars(File, lists:join($\n, PP)); module(Stream, {Mod,Exp,Attr,Code,NumLabels}) -> - %% This is output from v3_codegen. + %% This is BEAM code. io:format(Stream, "{module, ~kp}. %% version = ~w\n", [Mod, beam_opcodes:format_number()]), io:format(Stream, "\n{exports, ~p}.\n", [Exp]), diff --git a/lib/compiler/src/beam_ssa.erl b/lib/compiler/src/beam_ssa.erl index 448b3b4313..87dd8c5565 100644 --- a/lib/compiler/src/beam_ssa.erl +++ b/lib/compiler/src/beam_ssa.erl @@ -48,7 +48,7 @@ b_ret/0,b_br/0,b_switch/0,terminator/0, b_var/0,b_literal/0,b_remote/0,b_local/0, value/0,argument/0,label/0, - var_name/0,var_base/0,literal_value/0, + var_name/0,literal_value/0, op/0,anno/0,block_map/0,dominator_map/0, rename_map/0,rename_proplist/0,usage_map/0, definition_map/0,predecessor_map/0]). @@ -78,8 +78,7 @@ -type argument() :: value() | b_remote() | b_local() | phi_value(). -type label() :: non_neg_integer(). --type var_name() :: var_base() | {var_base(),non_neg_integer()}. --type var_base() :: atom() | non_neg_integer(). +-type var_name() :: atom() | non_neg_integer(). -type literal_value() :: atom() | integer() | float() | list() | nil() | tuple() | map() | binary() | fun(). @@ -446,7 +445,7 @@ successors(L, Blocks) -> -spec def(Ls, Blocks) -> Def when Ls :: [label()], Blocks :: block_map(), - Def :: ordsets:ordset(var_name()). + Def :: ordsets:ordset(b_var()). def(Ls, Blocks) when is_map(Blocks) -> Blks = [map_get(L, Blocks) || L <- Ls], @@ -454,10 +453,10 @@ def(Ls, Blocks) when is_map(Blocks) -> -spec def_unused(Ls, Used, Blocks) -> {Def,Unused} when Ls :: [label()], - Used :: ordsets:ordset(var_name()), + Used :: ordsets:ordset(b_var()), Blocks :: block_map(), - Def :: ordsets:ordset(var_name()), - Unused :: ordsets:ordset(var_name()). + Def :: ordsets:ordset(b_var()), + Unused :: ordsets:ordset(b_var()). def_unused(Ls, Unused, Blocks) when is_map(Blocks) -> Blks = [map_get(L, Blocks) || L <- Ls], @@ -687,7 +686,7 @@ trim_unreachable(Blocks) when is_map(Blocks) -> trim_unreachable([_|_]=Blocks) -> trim_unreachable_1(Blocks, sets:from_list([0], [{version, 2}])). --spec used(b_blk() | b_set() | terminator()) -> [var_name()]. +-spec used(b_blk() | b_set() | terminator()) -> [b_var()]. used(#b_blk{is=Is,last=Last}) -> used_1([Last|Is], ordsets:new()); diff --git a/lib/compiler/src/beam_ssa_bc_size.erl b/lib/compiler/src/beam_ssa_bc_size.erl index 1c4dd58048..9f1e802dd0 100644 --- a/lib/compiler/src/beam_ssa_bc_size.erl +++ b/lib/compiler/src/beam_ssa_bc_size.erl @@ -546,7 +546,7 @@ literal_expr_args([], Acc) -> cg_size_calc(Expr, L, #b_blk{}=Blk0, Count0, Acc0) -> {[Fail,PhiL,InitWrL],Count1} = new_blocks(3, Count0), - {PhiDst,Count2} = new_var('@ssa_tmp', Count1), + {PhiDst,Count2} = new_var(Count1), {Acc1,Alloc,NextBlk,Count} = cg_size_calc_1(L, Expr, Fail, Count2, Acc0), BrPhiBlk = #b_blk{is=[],last=cg_br(PhiL)}, @@ -570,7 +570,7 @@ cg_size_calc_1(L, #b_literal{}=Alloc, _Fail, Count, Acc) -> {Acc,Alloc,L,Count}; cg_size_calc_1(L0, {Op,Args0}, Fail, Count0, Acc0) -> {Args,Acc1,L,Count1} = cg_atomic_args(Args0, L0, Fail, Count0, Acc0, []), - {Dst,Count3} = new_var('@ssa_tmp', Count1), + {Dst,Count3} = new_var(Count1), {NextBlkL,Count4} = new_block(Count3), I = #b_set{op=Op,args=Args,dst=Dst}, {SuccBlk,Count} = cg_succeeded(I, NextBlkL, Fail, Count4), @@ -578,7 +578,7 @@ cg_size_calc_1(L0, {Op,Args0}, Fail, Count0, Acc0) -> {Acc,Dst,NextBlkL,Count}. cg_succeeded(#b_set{dst=OpDst}=I, Succ, Fail, Count0) -> - {Bool,Count} = new_var('@ssa_bool', Count0), + {Bool,Count} = new_var(Count0), SuccI = #b_set{op={succeeded,guard},args=[OpDst],dst=Bool}, Blk = #b_blk{is=[I,SuccI],last=#b_br{bool=Bool,succ=Succ,fail=Fail}}, {Blk,Count}. @@ -602,8 +602,8 @@ cg_atomic_args([A|As], L, Fail, Count0, BlkAcc0, Acc) -> cg_atomic_args([], NextBlk, _Fail, Count, BlkAcc, Acc) -> {reverse(Acc),BlkAcc,NextBlk,Count}. -new_var(Base, Count) -> - {#b_var{name={Base,Count}},Count+1}. +new_var(Count) -> + {#b_var{name=Count},Count+1}. new_blocks(N, Count) -> new_blocks(N, Count, []). diff --git a/lib/compiler/src/beam_ssa_bool.erl b/lib/compiler/src/beam_ssa_bool.erl index 7633f8c9a1..5303ff5b7d 100644 --- a/lib/compiler/src/beam_ssa_bool.erl +++ b/lib/compiler/src/beam_ssa_bool.erl @@ -309,7 +309,7 @@ pre_opt([L|Ls], Sub0, Reached0, Count0, Blocks) -> {#b_set{}=Test0,#b_br{}=Br0} -> %% Here is a #b_switch{} that has been reduced to %% a '=:=' followed by a two-way `br`. - Bool = #b_var{name={'@ssa_bool',Count0}}, + Bool = #b_var{name=Count0}, Count = Count0 + 1, Test = Test0#b_set{dst=Bool}, Br = beam_ssa:normalize(Br0#b_br{bool=Bool}), diff --git a/lib/compiler/src/beam_ssa_bsm.erl b/lib/compiler/src/beam_ssa_bsm.erl index 71736b3bc0..2000ea83d5 100644 --- a/lib/compiler/src/beam_ssa_bsm.erl +++ b/lib/compiler/src/beam_ssa_bsm.erl @@ -393,7 +393,7 @@ amb_create_alias(#b_var{}=Arg0, Context, Lbl, State0) -> %% promotion will be inserted later by amb_insert_promotions/2. Counter = State0#amb.cnt, - Alias = #b_var{name={'@ssa_bsm_alias', Counter}}, + Alias = #b_var{name=Counter}, Promotion = #b_set{op=bs_get_tail,dst=Alias,args=[Context]}, Promotions = maps:put({Lbl, Arg0}, Promotion, Promotions0), @@ -767,7 +767,7 @@ aca_cs_is([#b_set{op=Op, _ -> aca_cs_args(Args0, VRs0) end, Counter = Counter0 + 1, - Dst = #b_var{name={'@ssa_bsm_aca',Counter}}, + Dst = #b_var{name=Counter0}, I = I0#b_set{dst=Dst,args=Args}, VRs = maps:put(Dst0, Dst, VRs0), aca_cs_is(Is, Counter, VRs, BRs, [I | Acc]); diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl index 9f6169829b..2f2d1a9ece 100644 --- a/lib/compiler/src/beam_ssa_codegen.erl +++ b/lib/compiler/src/beam_ssa_codegen.erl @@ -34,14 +34,14 @@ splitwith/2,takewhile/2]). -record(cg, {lcount=1 :: beam_label(), %Label counter - functable=#{} :: #{fa()=>beam_label()}, - labels=#{} :: #{ssa_label()=>0|beam_label()}, + functable=#{} :: #{fa() => beam_label()}, + labels=#{} :: #{ssa_label() => 0|beam_label()}, used_labels=gb_sets:empty() :: gb_sets:set(ssa_label()), - regs=#{} :: #{beam_ssa:var_name()=>ssa_register()}, + regs=#{} :: #{beam_ssa:b_var() => ssa_register()}, ultimate_fail=1 :: beam_label(), catches=gb_sets:empty() :: gb_sets:set(ssa_label()), fc_label=1 :: beam_label() - }). + }). -spec module(beam_ssa:b_module(), [compile:option()]) -> {'ok',beam_asm:module_code()}. @@ -68,7 +68,7 @@ module(#b_module{name=Mod,exports=Es,attributes=Attrs,body=Fs}, Opts) -> stack=none :: 'none' | pos_integer(), words=#need{} :: #need{}, live :: 'undefined' | pos_integer(), - def_yregs=[] :: [yreg()] + def_yregs=[] :: [b_var()] }). -record(cg_br, {bool :: beam_ssa:value(), diff --git a/lib/compiler/src/beam_ssa_dead.erl b/lib/compiler/src/beam_ssa_dead.erl index 5294fecb7c..27a6baaab4 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,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())}. +-type used_vars() :: #{beam_ssa:label():=sets:set(beam_ssa:b_var())}. -type basic_type_test() :: atom() | {'is_tagged_tuple',pos_integer(),atom()}. -type type_test() :: basic_type_test() | {'not',basic_type_test()}. diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl index 13e4114319..213b6e79a2 100644 --- a/lib/compiler/src/beam_ssa_opt.erl +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -682,7 +682,7 @@ opt_tail_phi_arg({PredL,Sub0}, Is0, Ret0, {Blocks0,Count0,Cost0}) -> {Blocks,Count,Cost}. new_names([#b_set{dst=Dst}=I|Is], Sub0, Count0, Acc) -> - {NewDst,Count} = new_var(Dst, Count0), + {NewDst,Count} = new_var(Count0), Sub = Sub0#{Dst=>NewDst}, new_names(Is, Sub, Count, [I#b_set{dst=NewDst}|Acc]); new_names([], Sub, Count, Acc) -> @@ -1208,8 +1208,9 @@ are_map_keys_literals([]) -> %%% bother implementing a new instruction? %%% +-type fr_status() :: 'original' | 'copy'. -record(fs, - {regs=#{} :: #{beam_ssa:b_var():=beam_ssa:b_var()}, + {regs=#{} :: #{beam_ssa:b_var() := {beam_ssa:b_var(),fr_status()}}, non_guards :: gb_sets:set(beam_ssa:label()), bs :: beam_ssa:block_map(), preds :: #{beam_ssa:label() => [beam_ssa:label()]} @@ -1302,7 +1303,7 @@ float_number([B|Bs0], Count0) -> float_conv([{L,#b_blk{is=Is0,last=Last}=Blk0}|Bs0], Fail, Count0) -> case Is0 of [#b_set{op={float,convert}}=Conv] -> - {Bool,Count1} = new_var('@ssa_bool', Count0), + {Bool,Count1} = new_var(Count0), Succeeded = #b_set{op={succeeded,body},dst=Bool, args=[Conv#b_set.dst]}, Is = [Conv,Succeeded], @@ -1365,7 +1366,7 @@ float_optimizable_is(_) -> float_opt_is([#b_set{op={succeeded,_},args=[Src]}=I0], #fs{regs=Rs}=Fs, Count, Acc) -> case Rs of - #{Src:=Fr} -> + #{Src := {Fr,_}} -> I = I0#b_set{args=[Fr]}, {reverse(Acc, [I]),Fs,Count}; #{} -> @@ -1400,9 +1401,9 @@ float_make_op(#b_set{op={bif,Op},dst=Dst,args=As0,anno=Anno}=I0, Ts, ArgTypes, #fs{regs=Rs0}=Fs, Count0) -> {As1,Rs1,Count1} = float_load(As0, Ts, ArgTypes, Anno, Rs0, Count0, []), {As,Is0} = unzip(As1), - {FrDst,Count2} = new_var('@fr', Count1), + {FrDst,Count2} = new_var(Count1), I = I0#b_set{op={float,Op},dst=FrDst,args=As}, - Rs = Rs1#{Dst=>FrDst}, + Rs = Rs1#{Dst => {FrDst,original}}, Is = append(Is0) ++ [I], {Is,Fs#fs{regs=Rs},Count2}. @@ -1414,17 +1415,17 @@ float_load([], [], [], _Anno, Rs, Count, Acc) -> float_reg_arg(A, T, AT, Anno0, Rs, Count0) -> case Rs of - #{A:=Fr} -> + #{A := {Fr,_}} -> {{Fr,[]},Rs,Count0}; #{} -> - {Dst,Count} = new_var('@fr_copy', Count0), + {Dst,Count} = new_var(Count0), I0 = float_load_reg(T, A, Dst), Anno = case AT of - any-> Anno0; + any -> Anno0; _ -> Anno0#{arg_types => #{0 => AT}} end, I = I0#b_set{anno=Anno}, - {{Dst,[I]},Rs#{A=>Dst},Count} + {{Dst,[I]},Rs#{A => {Dst,copy}},Count} end. float_load_reg(convert, #b_var{}=Src, Dst) -> @@ -1442,9 +1443,9 @@ float_load_reg(float, Src, Dst) -> #b_set{op={float,put},dst=Dst,args=[Src]}. float_flush_regs(#fs{regs=Rs}) -> - maps:fold(fun(_, #b_var{name={'@fr_copy',_}}, Acc) -> + maps:fold(fun(_, {#b_var{},copy}, Acc) -> Acc; - (Dst, Fr, Acc) -> + (Dst, {Fr,original}, Acc) -> [#b_set{op={float,get},dst=Dst,args=[Fr]}|Acc] end, [], Rs). @@ -2317,7 +2318,7 @@ opt_tup_size_1(_, _, _, Count, Acc) -> opt_tup_size_2(PreIs, TupleSizeIs, PreL, EqL, Tuple, Fail, Count0, Acc) -> IsTupleL = Count0, TupleSizeL = Count0 + 1, - Bool = #b_var{name={'@ssa_bool',Count0+2}}, + Bool = #b_var{name=Count0+2}, Count = Count0 + 3, True = #b_literal{val=true}, @@ -2358,7 +2359,7 @@ opt_sw([{L,#b_blk{is=Is,last=#b_switch{}=Sw0}=Blk0}|Bs], Count0, Acc) -> case Sw0 of #b_switch{arg=Arg,fail=Fail,list=[{Lit,Lbl}]} -> %% Rewrite a single value switch to a br. - {Bool,Count} = new_var('@ssa_bool', Count0), + {Bool,Count} = new_var(Count0), IsEq = #b_set{op={bif,'=:='},dst=Bool,args=[Arg,Lit]}, Br = #b_br{bool=Bool,succ=Lbl,fail=Fail}, Blk = Blk0#b_blk{is=Is++[IsEq],last=Br}, @@ -2367,7 +2368,7 @@ opt_sw([{L,#b_blk{is=Is,last=#b_switch{}=Sw0}=Blk0}|Bs], Count0, Acc) -> list=[{#b_literal{val=B1},Lbl},{#b_literal{val=B2},Lbl}]} when B1 =:= not B2 -> %% Replace with is_boolean test. - {Bool,Count} = new_var('@ssa_bool', Count0), + {Bool,Count} = new_var(Count0), IsBool = #b_set{op={bif,is_boolean},dst=Bool,args=[Arg]}, Br = #b_br{bool=Bool,succ=Lbl,fail=Fail}, Blk = Blk0#b_blk{is=Is++[IsBool],last=Br}, @@ -3450,8 +3451,8 @@ is_viable_match(#b_set{op=bs_match,args=Args}) -> build_bs_ensure_match(L, {_,Size,Unit}, Count0, Blocks0) -> BsMatchL = Count0, Count1 = Count0 + 1, - {NewCtx,Count2} = new_var('@context', Count1), - {SuccBool,Count} = new_var('@ssa_bool', Count2), + {NewCtx,Count2} = new_var(Count1), + {SuccBool,Count} = new_var(Count2), BsMatchBlk0 = map_get(L, Blocks0), @@ -3529,10 +3530,5 @@ sub_arg(Old, Sub) -> #{} -> Old end. -new_var(#b_var{name={Base,N}}, Count) -> - true = is_integer(N), %Assertion. - {#b_var{name={Base,Count}},Count+1}; -new_var(#b_var{name=Base}, Count) -> - {#b_var{name={Base,Count}},Count+1}; -new_var(Base, Count) when is_atom(Base) -> - {#b_var{name={Base,Count}},Count+1}. +new_var(Count) -> + {#b_var{name=Count},Count+1}. diff --git a/lib/compiler/src/beam_ssa_pp.erl b/lib/compiler/src/beam_ssa_pp.erl index b2f682b705..e7bc2a97c0 100644 --- a/lib/compiler/src/beam_ssa_pp.erl +++ b/lib/compiler/src/beam_ssa_pp.erl @@ -214,13 +214,6 @@ format_var(Var, FuncAnno) -> [_|_]=Reg -> [Reg,$/,VarString] end. -format_var_1(#b_var{name={Name,Uniq}}) -> - if - is_atom(Name) -> - io_lib:format("~ts:~p", [Name,Uniq]); - is_integer(Name) -> - io_lib:format("_~p:~p", [Name,Uniq]) - end; format_var_1(#b_var{name=Name}) when is_atom(Name) -> atom_to_list(Name); format_var_1(#b_var{name=Name}) when is_integer(Name) -> diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl index dc76755aad..3827c4f123 100644 --- a/lib/compiler/src/beam_ssa_pre_codegen.erl +++ b/lib/compiler/src/beam_ssa_pre_codegen.erl @@ -71,7 +71,7 @@ -include("beam_ssa.hrl"). -include("beam_asm.hrl"). --import(lists, [all/2,any/2,append/1,duplicate/2, +-import(lists, [all/2,any/2,append/1, foldl/3,last/1,member/2,partition/2, reverse/1,reverse/2,seq/2,sort/1,sort/2, usort/1,zip/2]). @@ -255,7 +255,7 @@ make_bs_getpos_map([], _, Count, Acc) -> {maps:from_list(Acc),Count}. make_bs_setpos_map([{Bef,{Ctx,_}=Ps}|T], SavePoints, Count, Acc) -> - Ignored = #b_var{name={'@ssa_ignored',Count}}, + Ignored = #b_var{name=Count}, Args = [Ctx, get_savepoint(Ps, SavePoints)], I = #b_set{op=bs_set_position,dst=Ignored,args=Args}, make_bs_setpos_map(T, SavePoints, Count+1, [{Bef,I}|Acc]); @@ -263,7 +263,7 @@ make_bs_setpos_map([], _, Count, Acc) -> {maps:from_list(Acc),Count}. get_savepoint({_,_}=Ps, SavePoints) -> - Name = {'@ssa_bs_position', map_get(Ps, SavePoints)}, + Name = map_get(Ps, SavePoints), #b_var{name=Name}. make_bs_pos_dict([{Ctx,Pts}|T], Count0, Acc0) -> @@ -703,7 +703,7 @@ sanitize_is([#b_set{op=get_map_element,args=Args0}=I0|Is], case sanitize_args(Args0, Values) of [#b_literal{}=Map,Key] -> %% Bind the literal map to a variable. - {MapVar,Count} = new_var('@ssa_map', Count0), + {MapVar,Count} = new_var(Count0), I = I0#b_set{args=[MapVar,Key]}, Copy = #b_set{op=copy,dst=MapVar,args=[Map]}, sanitize_is(Is, Last, InBlocks, Blocks, Count, @@ -1030,7 +1030,7 @@ expand_mf_instr(#b_set{args=[#b_literal{val=badrecord} | _Args]}=I, expand_mf_instr(#b_set{args=[#b_literal{}|_]=Args}=I0, Is, Count0, Acc) -> %% We don't have a specialized instruction for this: simulate it with %% `erlang:error/1` instead. - {Tuple, Count} = new_var('@match_fail', Count0), + {Tuple, Count} = new_var(Count0), Put = #b_set{op=put_tuple,dst=Tuple,args=Args}, Call = I0#b_set{op=call, args=[#b_remote{mod=#b_literal{val=erlang}, @@ -1144,7 +1144,7 @@ expand_update_tuple_list_1([], _Src, Count, Acc) -> expand_update_tuple_list_1([Index0, Value | Updates], Src, Count0, Acc) -> %% Change to the 0-based indexing used by `set_tuple_element`. Index = #b_literal{val=(Index0#b_literal.val - 1)}, - {Dst, Count} = new_var('@ssa_dummy', Count0), + {Dst, Count} = new_var(Count0), SetOp = #b_set{op=set_tuple_element, dst=Dst, args=[Value, Src, Index]}, @@ -1526,11 +1526,10 @@ rce_reroute_terminator(#b_switch{list=List0}=Last, Exit, New) -> %% in the exit block following the receive. recv_fix_common([Msg0|T], Exit, Rm, Blocks0, Count0) -> - {Msg,Count1} = new_var('@recv', Count0), + {Msg,Count1} = new_var(Count0), RPO = beam_ssa:rpo([Exit], Blocks0), Blocks1 = beam_ssa:rename_vars(#{Msg0=>Msg}, RPO, Blocks0), - N = length(Rm), - {MsgVars,Count} = new_vars(duplicate(N, '@recv'), Count1), + {MsgVars,Count} = new_vars(length(Rm), Count1), PhiArgs = fix_exit_phi_args(MsgVars, Rm, Exit, Blocks1), Phi = #b_set{op=phi,dst=Msg,args=PhiArgs}, ExitBlk0 = map_get(Exit, Blocks1), @@ -1578,7 +1577,7 @@ fix_receive([L|Ls], Defs, Blocks0, Count0) -> {RmDefs,Unused} = beam_ssa:def_unused(RPO, Defs, Blocks0), Def = ordsets:subtract(Defs, RmDefs), Used = ordsets:subtract(Def, Unused), - {NewVars,Count} = new_vars([Base || #b_var{name=Base} <- Used], Count0), + {NewVars,Count} = new_vars(length(Used), Count0), Ren = zip(Used, NewVars), Blocks1 = beam_ssa:rename_vars(Ren, RPO, Blocks0), #b_blk{is=Is0} = Blk1 = map_get(L, Blocks1), @@ -1697,8 +1696,8 @@ find_rm_act([]) -> %%% Find out which variables need to be stored in Y registers. %%% --record(dk, {d :: ordsets:ordset(var_name()), - k :: sets:set(var_name()) +-record(dk, {d :: ordsets:ordset(b_var()), + k :: sets:set(b_var()) }). %% find_yregs(St0) -> St. @@ -1968,12 +1967,12 @@ copy_retval_is([#b_set{}]=Is, false, _Yregs, Copy, Count, Acc) -> {reverse(Acc, acc_copy(Is, Copy)),Count}; copy_retval_is([#b_set{},#b_set{op=succeeded}]=Is, false, _Yregs, Copy, Count, Acc) -> {reverse(Acc, acc_copy(Is, Copy)),Count}; -copy_retval_is([#b_set{op=Op,dst=#b_var{name=RetName}=Dst}=I0|Is], RC, Yregs, +copy_retval_is([#b_set{op=Op,dst=#b_var{}=Dst}=I0|Is], RC, Yregs, Copy0, Count0, Acc0) when Op =:= call; Op =:= old_make_fun -> {I1,Count1,Acc} = place_retval_copy(I0, Yregs, Copy0, Count0, Acc0), case sets:is_element(Dst, Yregs) of true -> - {NewVar,Count} = new_var(RetName, Count1), + {NewVar,Count} = new_var(Count1), Copy = #b_set{op=copy,dst=Dst,args=[NewVar]}, I = I1#b_set{dst=NewVar}, copy_retval_is(Is, RC, Yregs, Copy, Count, [I|Acc]); @@ -2071,10 +2070,10 @@ place_retval_copy(#b_set{args=[F|Args0]}=I0, Yregs0, RetCopy, Count0, Acc0) -> copy_func_args(Args, Yregs, Acc, Count) -> copy_func_args_1(reverse(Args), Yregs, Acc, [], Count). -copy_func_args_1([#b_var{name=AName}=A|As], Yregs, InstrAcc, ArgAcc, Count0) -> +copy_func_args_1([#b_var{}=A|As], Yregs, InstrAcc, ArgAcc, Count0) -> case sets:is_element(A, Yregs) of true -> - {NewVar,Count} = new_var(AName, Count0), + {NewVar,Count} = new_var(Count0), Copy = #b_set{op=copy,dst=NewVar,args=[A]}, copy_func_args_1(As, Yregs, [Copy|InstrAcc], [NewVar|ArgAcc], Count); false -> @@ -2440,7 +2439,7 @@ update_act_map([], _, ActMap) -> ActMap. rename_vars([], _, _, Blocks, Count) -> {[],Blocks,Count}; rename_vars(Vs, L, RPO, Blocks0, Count0) -> - {NewVars,Count} = new_vars([Base || #b_var{name=Base} <- Vs], Count0), + {NewVars,Count} = new_vars(length(Vs), Count0), Ren = zip(Vs, NewVars), Blocks1 = beam_ssa:rename_vars(Ren, RPO, Blocks0), #b_blk{is=Is0} = Blk0 = map_get(L, Blocks1), @@ -3229,14 +3228,10 @@ is_yreg({x,_}) -> false; is_yreg({z,_}) -> false; is_yreg({fr,_}) -> false. -new_vars([Base|Vs0], Count0) -> - {V,Count1} = new_var(Base, Count0), - {Vs,Count} = new_vars(Vs0, Count1), - {[V|Vs],Count}; -new_vars([], Count) -> {[],Count}. - -new_var({Base,Int}, Count) -> - true = is_integer(Int), %Assertion. - {#b_var{name={Base,Count}},Count+1}; -new_var(Base, Count) -> - {#b_var{name={Base,Count}},Count+1}. +new_vars(N, Count0) when is_integer(N), N >= 0 -> + Count = Count0 + N, + Vars = [#b_var{name=I} || I <- lists:seq(Count0, Count-1)], + {Vars,Count}. + +new_var(Count) -> + {#b_var{name=Count},Count+1}. diff --git a/lib/compiler/src/beam_ssa_private_append.erl b/lib/compiler/src/beam_ssa_private_append.erl index c295492762..9db25dc3fc 100644 --- a/lib/compiler/src/beam_ssa_private_append.erl +++ b/lib/compiler/src/beam_ssa_private_append.erl @@ -619,9 +619,8 @@ patch_literal_tuple([], [], Patched, Extra, _, Cnt0) -> I = #b_set{op=put_tuple,dst=V,args=reverse(Patched)}, {V, [I|Extra], Cnt}. -%% As beam_ssa_opt:new_var/2, but with a hard-coded base new_var(Count) -> - {#b_var{name={alias_opt,Count}},Count+1}. + {#b_var{name=Count},Count+1}. %% Done with an accumulator to reverse the reversed block order from %% patch_appends_f/5. diff --git a/lib/compiler/src/beam_ssa_recv.erl b/lib/compiler/src/beam_ssa_recv.erl index 240a0b1a34..a0d74f137c 100644 --- a/lib/compiler/src/beam_ssa_recv.erl +++ b/lib/compiler/src/beam_ssa_recv.erl @@ -790,7 +790,7 @@ insert_markers([], Blocks, Count) -> insert_reserve(Lbl, Dst, Anno, Blocks0, Count0) -> #{ Lbl := #b_blk{is=Is0}=Blk } = Blocks0, - Var = #b_var{name={'@ssa_recv_marker', Count0}}, + Var = #b_var{name=Count0}, Count = Count0 + 1, Reserve = #b_set{anno=Anno,op=recv_marker_reserve,args=[],dst=Var}, @@ -808,7 +808,7 @@ insert_reserve_is([I | Is], Reserve, Var) -> insert_bind(Lbl, Ref, Marker, Blocks0, Count0) -> #{ Lbl := #b_blk{is=Is0,last=Last}=Blk } = Blocks0, - Ignored = #b_var{name={'@ssa_ignored', Count0}}, + Ignored = #b_var{name=Count0}, Count = Count0 + 1, Bind = #b_set{ op=recv_marker_bind, @@ -853,7 +853,7 @@ insert_clears(Clears0, Blocks0, Count0) -> beam_ssa:insert_on_edges(Insertions, Blocks0, Count). insert_clears_1([{From, To, Ref} | Clears], Count0, Acc) -> - Ignored = #b_var{name={'@ssa_ignored', Count0}}, + Ignored = #b_var{name=Count0}, Count = Count0 + 1, Clear = #b_set{op=recv_marker_clear,args=[Ref],dst=Ignored}, diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl index 725eb9ee29..3d993808f6 100644 --- a/lib/compiler/src/beam_ssa_type.erl +++ b/lib/compiler/src/beam_ssa_type.erl @@ -64,7 +64,7 @@ -type metadata() :: #metadata{}. -type meta_cache() :: #{ func_id() => metadata() }. --type type_db() :: #{ beam_ssa:var_name() := ssa_type() }. +-type type_db() :: #{ beam_ssa:b_var() := ssa_type() }. %% The types are the same as in 'beam_types.hrl', with the addition of %% `(fun(type_db()) -> type())` that defers figuring out the type until it's diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index 9ff09911f9..7fcbec9a3b 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -865,10 +865,7 @@ kernel_passes() -> {iff,clint,?pass(core_lint_module)}, %% Kernel Erlang and code generation. - ?pass(v3_kernel), - {iff,dkern,{listing,"kernel"}}, - {iff,'to_kernel',{done,"kernel"}}, - {pass,beam_kernel_to_ssa}, + ?pass(core_to_ssa), {iff,dssa,{listing,"ssa"}}, {iff,ssalint,{pass,beam_ssa_lint}}, {delay, @@ -1527,8 +1524,8 @@ core_fold_module_after_inlining(Code0, #compile{options=Opts}=St) -> {ok,Code,_Ws} = sys_core_fold:module(Code0, Opts), {ok,Code,St}. -v3_kernel(Code0, #compile{options=Opts,warnings=Ws0}=St) -> - {ok,Code,Ws} = v3_kernel:module(Code0, Opts), +core_to_ssa(Code0, #compile{options=Opts,warnings=Ws0}=St) -> + {ok,Code,Ws} = beam_core_to_ssa:module(Code0, Opts), case Ws =:= [] orelse test_core_inliner(St) of false -> {ok,Code,St#compile{warnings=Ws0++Ws}}; @@ -1838,7 +1835,7 @@ ignore_warning({_Location,Pass,{Category,_}}, Ignore) -> IgnoreMod = case Pass of v3_core -> true; sys_core_fold -> true; - v3_kernel -> true; + beam_core_to_ssa -> true; _ -> false end, IgnoreMod andalso sets:is_element(Category, Ignore); @@ -2094,11 +2091,11 @@ pre_load() -> beam_block, beam_call_types, beam_clean, + beam_core_to_ssa, beam_dict, beam_digraph, beam_flatten, beam_jump, - beam_kernel_to_ssa, beam_opcodes, beam_ssa, beam_ssa_alias, @@ -2133,7 +2130,6 @@ pre_load() -> sys_core_alias, sys_core_bsm, sys_core_fold, - v3_core, - v3_kernel], + v3_core], _ = code:ensure_modules_loaded(L), ok. diff --git a/lib/compiler/src/compiler.app.src b/lib/compiler/src/compiler.app.src index be1a6d7236..90448a67a0 100644 --- a/lib/compiler/src/compiler.app.src +++ b/lib/compiler/src/compiler.app.src @@ -27,12 +27,12 @@ beam_block, beam_call_types, beam_clean, + beam_core_to_ssa, beam_dict, beam_digraph, beam_disasm, beam_flatten, beam_jump, - beam_kernel_to_ssa, beam_listing, beam_opcodes, beam_ssa, @@ -77,9 +77,7 @@ sys_core_prepare, sys_messages, sys_pre_attributes, - v3_core, - v3_kernel, - v3_kernel_pp + v3_core ]}, {registered, []}, {applications, [kernel, stdlib]}, diff --git a/lib/compiler/src/v3_kernel.erl b/lib/compiler/src/v3_kernel.erl deleted file mode 100644 index 46063bccdf..0000000000 --- a/lib/compiler/src/v3_kernel.erl +++ /dev/null @@ -1,2316 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1999-2023. 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 : Transform Core Erlang to Kernel Erlang - -%% Kernel erlang is like Core Erlang with a few significant -%% differences: -%% -%% 1. It is flat! There are no nested calls or sub-blocks. -%% -%% 2. All variables are unique in a function. There is no scoping, or -%% rather the scope is the whole function. -%% -%% 3. Pattern matching (in cases and receives) has been compiled. -%% -%% 4. All remote-calls are to statically named m:f/a. Meta-calls are -%% passed via erlang:apply/3. -%% -%% The translation is done in two passes: -%% -%% 1. Basic translation, translate variable/function names, flatten -%% completely, pattern matching compilation. -%% -%% 2. Fun-lifting (lambda-lifting), variable usage annotation and -%% last-call handling. -%% -%% All new Kexprs are created in the first pass, they are just -%% annotated in the second. -%% -%% Functions and BIFs -%% -%% Functions are "call"ed or "enter"ed if it is a last call, their -%% return values may be ignored. BIFs are things which are known to -%% be internal by the compiler and can only be called, their return -%% values cannot be ignored. -%% -%% Letrec's are handled rather naively. All the functions in one -%% letrec are handled as one block to find the free variables. While -%% this is not optimal it reflects how letrec's often are used. We -%% don't have to worry about variable shadowing and nested letrec's as -%% this is handled in the variable/function name translation. There -%% is a little bit of trickery to ensure letrec transformations fit -%% into the scheme of things. -%% -%% To ensure unique variable names we use a variable substitution -%% table and keep the set of all defined variables. The nested -%% scoping of Core means that we must also nest the substitution -%% tables, but the defined set must be passed through to match the -%% flat structure of Kernel and to make sure variables with the same -%% name from different scopes get different substitutions. -%% -%% We also use these substitutions to handle the variable renaming -%% necessary in pattern matching compilation. -%% -%% The pattern matching compilation assumes that the values of -%% different types don't overlap. This means that as there is no -%% character type yet in the machine all characters must be converted -%% to integers! - --module(v3_kernel). - --export([module/2,format_error/1]). - --import(lists, [all/2,droplast/1,flatten/1,foldl/3,foldr/3, - map/2,mapfoldl/3,member/2,keyfind/3,last/1, - partition/2,reverse/1,sort/1,sort/2, - splitwith/2]). --import(ordsets, [add_element/2,intersection/2, - subtract/2,union/2,union/1]). - --include("core_parse.hrl"). --include("v3_kernel.hrl"). - -%% Matches collapse max segment in v3_core. --define(EXPAND_MAX_SIZE_SEGMENT, 1024). - -%% These are not defined in v3_kernel.hrl. -get_kanno(Kthing) -> element(2, Kthing). -set_kanno(Kthing, Anno) -> setelement(2, Kthing, Anno). -copy_anno(Kdst, Ksrc) -> - Anno = get_kanno(Ksrc), - set_kanno(Kdst, Anno). - -%% Internal kernel expressions and help functions. -%% N.B. the annotation field is ALWAYS the first field! - --record(ivalues, {anno=[],args}). --record(ifun, {anno=[],vars,body}). --record(iset, {anno=[],vars,arg,body}). --record(iletrec, {anno=[],defs}). --record(ialias, {anno=[],vars,pat}). --record(iclause, {anno=[],isub,osub,pats,guard,body}). - --type warning() :: term(). % XXX: REFINE - -%% State record for kernel translator. --record(kern, {func, %Current host function - fargs=[] :: [#k_var{}], %Arguments for current function - vcount=0, %Variable counter - fcount=0, %Fun counter - ds=sets:new([{version, 2}]) :: sets:set(), %Defined variables - funs=[], %Fun functions - free=#{}, %Free variables - ws=[] :: [warning()], %Warnings. - no_shared_fun_wrappers=false :: boolean(), - no_min_max_bifs=false :: boolean(), - labels=sets:new([{version, 2}]) - }). - --spec module(cerl:c_module(), [compile:option()]) -> - {'ok', #k_mdef{}, [warning()]}. - -module(#c_module{anno=A,name=M,exports=Es,attrs=As,defs=Fs}, Options) -> - Kas = attributes(As), - Kes = map(fun (#c_var{name={_,_}=Fname}) -> Fname end, Es), - NoSharedFunWrappers = proplists:get_bool(no_shared_fun_wrappers, - Options), - NoMinMaxBifs = proplists:get_bool(no_min_max_bifs, Options), - St0 = #kern{no_shared_fun_wrappers=NoSharedFunWrappers, - no_min_max_bifs=NoMinMaxBifs}, - {Kfs,St} = mapfoldl(fun function/2, St0, Fs), - {ok,#k_mdef{anno=A,name=M#c_literal.val,exports=Kes,attributes=Kas, - body=Kfs ++ St#kern.funs},sort(St#kern.ws)}. - -attributes([{#c_literal{val=Name},#c_literal{val=Val}}|As]) -> - case include_attribute(Name) of - false -> - attributes(As); - true -> - [{Name,Val}|attributes(As)] - end; -attributes([]) -> []. - -include_attribute(type) -> false; -include_attribute(spec) -> false; -include_attribute(callback) -> false; -include_attribute(opaque) -> false; -include_attribute(export_type) -> false; -include_attribute(record) -> false; -include_attribute(optional_callbacks) -> false; -include_attribute(file) -> false; -include_attribute(compile) -> false; -include_attribute(_) -> true. - -function({#c_var{name={F,Arity}=FA},Body}, St0) -> - %%io:format("~w/~w~n", [F,Arity]), - try - %% Find a suitable starting value for the variable counter. Note - %% that this pass assumes that new_var_name/1 returns a variable - %% name distinct from any variable used in the entire body of - %% the function. We use integers as variable names to avoid - %% filling up the atom table when compiling huge functions. - Count = cerl_trees:next_free_variable_name(Body), - St1 = St0#kern{func=FA,vcount=Count,fcount=0,ds=sets:new([{version, 2}])}, - {#ifun{anno=Ab,vars=Kvs,body=B0},[],St2} = expr(Body, new_sub(), St1), - {B1,_,St3} = ubody(B0, return, St2), - %%B1 = B0, St3 = St2, %Null second pass - {make_fdef(Ab, F, Arity, Kvs, B1),St3} - catch - Class:Error:Stack -> - io:fwrite("Function: ~w/~w\n", [F,Arity]), - erlang:raise(Class, Error, Stack) - end. - -%% body(Cexpr, Sub, State) -> {Kexpr,[PreKepxr],State}. -%% Do the main sequence of a body. A body ends in an atomic value or -%% values. Must check if vector first so do expr. - -body(#c_values{anno=A,es=Ces}, Sub, St0) -> - %% Do this here even if only in bodies. - {Kes,Pe,St1} = atomic_list(Ces, Sub, St0), - {#ivalues{anno=A,args=Kes},Pe,St1}; -body(Ce, Sub, St0) -> - expr(Ce, Sub, St0). - -%% guard(Cexpr, Sub, State) -> {Kexpr,State}. -%% We handle guards almost as bodies. The only special thing we -%% must do is to make the final Kexpr a #k_test{}. - -guard(G0, Sub, St0) -> - {Ge0,Pre,St1} = expr(G0, Sub, St0), - {Ge,St} = gexpr_test(Ge0, St1), - {pre_seq(Pre, Ge),St}. - -%% gexpr_test(Kexpr, State) -> {Kexpr,State}. -%% Builds the final boolean test from the last Kexpr in a guard test. -%% Must enter try blocks and isets and find the last Kexpr in them. -%% This must end in a recognised BEAM test! - -gexpr_test(#k_bif{anno=A, - op=#k_remote{mod=#k_literal{val=erlang}, - name=#k_literal{val=F},arity=Ar}=Op, - args=Kargs}=Ke, St) -> - %% Either convert to test if ok, or add test. - %% At this stage, erlang:float/1 is not a type test. (It should - %% have been converted to erlang:is_float/1.) - case erl_internal:new_type_test(F, Ar) orelse - erl_internal:comp_op(F, Ar) of - true -> {#k_test{anno=A,op=Op,args=Kargs},St}; - false -> gexpr_test_add(Ke, St) %Add equality test - end; -gexpr_test(#k_try{arg=B0,vars=[#k_var{name=X}],body=#k_var{name=X}, - handler=#k_literal{val=false}}=Try, St0) -> - {B,St} = gexpr_test(B0, St0), - %%ok = io:fwrite("~w: ~p~n", [?LINE,{B0,B}]), - {Try#k_try{arg=B},St}; -gexpr_test(#iset{body=B0}=Iset, St0) -> - {B1,St1} = gexpr_test(B0, St0), - {Iset#iset{body=B1},St1}; -gexpr_test(Ke, St) -> gexpr_test_add(Ke, St). %Add equality test - -gexpr_test_add(Ke, St0) -> - Test = #k_remote{mod=#k_literal{val='erlang'}, - name=#k_literal{val='=:='}, - arity=2}, - {Ae,Ap,St1} = force_atomic(Ke, St0), - {pre_seq(Ap, #k_test{anno=get_kanno(Ke), - op=Test,args=[Ae,#k_literal{val='true'}]}),St1}. - -%% expr(Cexpr, Sub, State) -> {Kexpr,[PreKexpr],State}. -%% Convert a Core expression, flattening it at the same time. - -expr(#c_var{anno=A,name={Name0,Arity}}=Fname, Sub, St) -> - case St#kern.no_shared_fun_wrappers of - false -> - Name = get_fsub(Name0, Arity, Sub), - {#k_local{anno=A,name=Name,arity=Arity},[],St}; - true -> - %% For backward compatibility with OTP 22 and earlier, - %% use the pre-generated name for the fun wrapper. - %% There will be one wrapper function for each occurrence - %% of `fun F/A`. - Vs = [#c_var{name=list_to_atom("V" ++ integer_to_list(V))} || - V <- integers(1, Arity)], - Fun = #c_fun{anno=A,vars=Vs,body=#c_apply{anno=A,op=Fname,args=Vs}}, - expr(Fun, Sub, St) - end; -expr(#c_var{anno=A,name=V}, Sub, St) -> - {#k_var{anno=A,name=get_vsub(V, Sub)},[],St}; -expr(#c_literal{anno=A,val=V}, _Sub, St) -> - {#k_literal{anno=A,val=V},[],St}; -expr(#c_cons{anno=A,hd=Ch,tl=Ct}, Sub, St0) -> - %% Do cons in two steps, first the expressions left to right, then - %% any remaining literals right to left. - {Kh0,Hp0,St1} = expr(Ch, Sub, St0), - {Kt0,Tp0,St2} = expr(Ct, Sub, St1), - {Kt1,Tp1,St3} = force_atomic(Kt0, St2), - {Kh1,Hp1,St4} = force_atomic(Kh0, St3), - {#k_cons{anno=A,hd=Kh1,tl=Kt1},Hp0 ++ Tp0 ++ Tp1 ++ Hp1,St4}; -expr(#c_tuple{anno=A,es=Ces}, Sub, St0) -> - {Kes,Ep,St1} = atomic_list(Ces, Sub, St0), - {#k_tuple{anno=A,es=Kes},Ep,St1}; -expr(#c_map{anno=A,arg=Var,es=Ces}, Sub, St0) -> - expr_map(A, Var, Ces, Sub, St0); -expr(#c_binary{anno=A,segments=Cv}, Sub, St0) -> - try atomic_bin(Cv, Sub, St0) of - {Kv,Ep,St1} -> - {#k_binary{anno=A,segs=Kv},Ep,St1} - catch - throw:{bad_segment_size,Location} -> - St1 = add_warning(Location, {failed,bad_segment_size}, A, St0), - Erl = #c_literal{val=erlang}, - Name = #c_literal{val=error}, - Args = [#c_literal{val=badarg}], - Error = #c_call{anno=A,module=Erl,name=Name,args=Args}, - expr(Error, Sub, St1) - end; -expr(#c_fun{anno=A,vars=Cvs,body=Cb}, Sub0, - #kern{fargs=OldFargs}=St0) -> - {Kvs,Sub1,St1} = pattern_list(Cvs, Sub0, St0), - %%ok = io:fwrite("~w: ~p~n", [?LINE,{{Cvs,Sub0,St0},{Kvs,Sub1,St1}}]), - {Kb,Pb,St2} = body(Cb, Sub1, St1#kern{fargs=Kvs}), - {#ifun{anno=A,vars=Kvs,body=pre_seq(Pb, Kb)},[],St2#kern{fargs=OldFargs}}; -expr(#c_seq{arg=Ca,body=Cb}, Sub, St0) -> - {Ka,Pa,St1} = body(Ca, Sub, St0), - {Kb,Pb,St2} = body(Cb, Sub, St1), - {Kb,Pa ++ [Ka] ++ Pb,St2}; -expr(#c_let{anno=A,vars=Cvs,arg=Ca,body=Cb}, Sub0, St0) -> - %%ok = io:fwrite("~w: ~p~n", [?LINE,{Cvs,Sub0,St0}]), - {Ka,Pa,St1} = body(Ca, Sub0, St0), - {Kps,Sub1,St2} = pattern_list(Cvs, Sub0, St1), - %%ok = io:fwrite("~w: ~p~n", [?LINE,{Kps,Sub1,St1,St2}]), - %% Break known multiple values into separate sets. - Sets = case Ka of - #ivalues{args=Kas} -> - foldr2(fun (V, Val, Sb) -> - [#iset{vars=[V],arg=Val}|Sb] end, - [], Kps, Kas); - _Other -> - [#iset{anno=A,vars=Kps,arg=Ka}] - end, - {Kb,Pb,St3} = body(Cb, Sub1, St2), - {Kb,Pa ++ Sets ++ Pb,St3}; -expr(#c_letrec{anno=A,defs=Cfs,body=Cb}, Sub, St) -> - case member(letrec_goto, A) of - true -> - letrec_goto(Cfs, Cb, Sub, St); - false -> - letrec_local_function(A, Cfs, Cb, Sub, St) - end; -expr(#c_case{arg=Ca,clauses=Ccs}, Sub, St0) -> - {Ka,Pa,St1} = body(Ca, Sub, St0), %This is a body! - {Kvs,Pv,St2} = match_vars(Ka, St1), %Must have variables here! - {Km,St3} = kmatch(Kvs, Ccs, Sub, St2), - Match = flatten_seq(build_match(Km)), - {last(Match),Pa ++ Pv ++ droplast(Match),St3}; -expr(#c_apply{anno=A,op=Cop,args=Cargs}, Sub, St) -> - c_apply(A, Cop, Cargs, Sub, St); -expr(#c_call{anno=A,module=M0,name=F0,args=Cargs}, Sub, St0) -> - Ar = length(Cargs), - {[M,F|Kargs],Ap,St1} = atomic_list([M0,F0|Cargs], Sub, St0), - Remote = #k_remote{mod=M,name=F,arity=Ar}, - case call_type(M0, F0, Cargs, St1) of - bif -> - {#k_bif{anno=A,op=Remote,args=Kargs},Ap,St1}; - call -> - {#k_call{anno=A,op=Remote,args=Kargs},Ap,St1}; - error -> - %% Invalid call (e.g. M:42/3). Issue a warning, and let - %% the generated code use the old explicit apply. - St = add_warning(get_location(A), {failed,bad_call}, A, St0), - Call = #c_call{anno=A, - module=#c_literal{val=erlang}, - name=#c_literal{val=apply}, - args=[M0,F0,cerl:make_list(Cargs)]}, - expr(Call, Sub, St) - end; -expr(#c_primop{anno=A,name=#c_literal{val=match_fail},args=[Arg]}, Sub, St) -> - translate_match_fail(Arg, Sub, A, St); -expr(#c_primop{anno=A,name=#c_literal{val=N},args=Cargs}, Sub, St0) -> - {Kargs,Ap,St1} = atomic_list(Cargs, Sub, St0), - Ar = length(Cargs), - {#k_bif{anno=A,op=#k_internal{name=N,arity=Ar},args=Kargs},Ap,St1}; -expr(#c_try{anno=A,arg=Ca,vars=Cvs,body=Cb,evars=Evs,handler=Ch}, Sub0, St0) -> - %% The normal try expression. The body and exception handler - %% variables behave as let variables. - {Ka,Pa,St1} = body(Ca, Sub0, St0), - {Kcvs,Sub1,St2} = pattern_list(Cvs, Sub0, St1), - {Kb,Pb,St3} = body(Cb, Sub1, St2), - {Kevs,Sub2,St4} = pattern_list(Evs, Sub0, St3), - {Kh,Ph,St5} = body(Ch, Sub2, St4), - {#k_try{anno=A,arg=pre_seq(Pa, Ka), - vars=Kcvs,body=pre_seq(Pb, Kb), - evars=Kevs,handler=pre_seq(Ph, Kh)},[],St5}; -expr(#c_catch{anno=A,body=Cb}, Sub, St0) -> - {Kb,Pb,St1} = body(Cb, Sub, St0), - {#k_catch{anno=A,body=pre_seq(Pb, Kb)},[],St1}; -expr(#c_opaque{anno=A,val=V}, _, St) -> - {#k_opaque{anno=A,val=V},[],St}. - -%% Implement letrec in the traditional way as a local -%% function for each definition in the letrec. - -letrec_local_function(A, Cfs, Cb, Sub0, St0) -> - %% Make new function names and store substitution. - {Fs0,{Sub1,St1}} = - mapfoldl(fun ({#c_var{name={F,Ar}},B0}, {Sub,S0}) -> - {N,St1} = new_fun_name(atom_to_list(F) - ++ "/" ++ - integer_to_list(Ar), - S0), - B = set_kanno(B0, [{letrec_name,N}]), - {{N,B},{set_fsub(F, Ar, N, Sub),St1}} - end, {Sub0,St0}, Cfs), - %% Run translation on functions and body. - {Fs1,St2} = mapfoldl(fun ({N,Fd0}, S1) -> - {Fd1,[],St2} = expr(Fd0, Sub1, S1), - Fd = set_kanno(Fd1, A), - {{N,Fd},St2} - end, St1, Fs0), - {Kb,Pb,St3} = body(Cb, Sub1, St2), - {Kb,[#iletrec{anno=A,defs=Fs1}|Pb],St3}. - -%% Implement letrec with the single definition as a label and each -%% apply of it as a goto. - -letrec_goto([{#c_var{name={Label,_Arity}},Cfail}], Cb, Sub0, - #kern{labels=Labels0}=St0) -> - #c_fun{vars=FunVars,body=FunBody} = Cfail, - {Kvars,{FunSub,St1}} = - mapfoldl(fun(#c_var{anno=A,name=V}, {SubInt,StInt0}) -> - {New,StInt1} = new_var_name(StInt0), - {#k_var{anno=A,name=New}, - {set_vsub(V, New, SubInt), - StInt1#kern{ds=sets:add_element(New, StInt1#kern.ds)}}} - end, {Sub0,St0}, FunVars), - Labels = sets:add_element(Label, Labels0), - {Kb,Pb,St2} = body(Cb, Sub0, St1#kern{labels=Labels}), - {Kfail,Fb,St3} = body(FunBody, FunSub, St2), - case {Kb,Kfail,Fb} of - {#k_goto{label=Label},#k_goto{}=InnerGoto,[]} -> - {InnerGoto,Pb,St3}; - {_,_,_} -> - St4 = St3#kern{labels=Labels0}, - Alt = #k_letrec_goto{label=Label,vars=Kvars, - first=Kb,then=pre_seq(Fb, Kfail)}, - {Alt,Pb,St4} - end. - -%% translate_match_fail(Arg, Sub, Anno, St) -> {Kexpr,[PreKexpr],State}. -%% Translate match_fail primops, paying extra attention to `function_clause` -%% errors that may have been inlined from other functions. - -translate_match_fail(Arg, Sub, Anno, St0) -> - {Cargs,ExtraAnno,St1} = - case {cerl:data_type(Arg),cerl:data_es(Arg)} of - {tuple,[#c_literal{val=function_clause} | _]=As} -> - translate_fc_args(As, Sub, Anno, St0); - {tuple,[#c_literal{} | _]=As} -> - {As,[],St0}; - {{atomic,Reason}, []} -> - {[#c_literal{val=Reason}],[],St0} - end, - {Kargs,Ap,St} = atomic_list(Cargs, Sub, St1), - Ar = length(Cargs), - Primop = #k_bif{anno=ExtraAnno ++ Anno, - op=#k_internal{name=match_fail,arity=Ar}, - args=Kargs}, - {Primop,Ap,St}. - -translate_fc_args(As, Sub, Anno, #kern{fargs=Fargs}=St0) -> - {ExtraAnno, St} = - case same_args(As, Fargs, Sub) of - true -> - %% The arguments for the `function_clause` exception are - %% the arguments for the current function in the correct - %% order. - {[], St0}; - false -> - %% The arguments in the `function_clause` exception don't - %% match the arguments for the current function because of - %% inlining. - case keyfind(function, 1, Anno) of - false -> - {Name, St1} = new_fun_name("inlined", St0), - {[{inlined,{Name,length(As) - 1}}], St1}; - {_,{Name0,Arity}} -> - %% This is function that has been inlined. - Name1 = ["-inlined-",Name0,"/",Arity,"-"], - Name = list_to_atom(lists:concat(Name1)), - {[{inlined,{Name,Arity}}], St0} - end - end, - {As, ExtraAnno, St}. - -same_args([#c_var{name=Cv}|Vs], [#k_var{name=Kv}|As], Sub) -> - get_vsub(Cv, Sub) =:= Kv andalso same_args(Vs, As, Sub); -same_args([], [], _Sub) -> true; -same_args(_, _, _) -> false. - -expr_map(A,Var0,Ces,Sub,St0) -> - {Var,Mps,St1} = expr(Var0, Sub, St0), - {Km,Eps,St2} = map_split_pairs(A, Var, Ces, Sub, St1), - {Km,Eps++Mps,St2}. - -map_split_pairs(A, Var, Ces, Sub, St0) -> - %% 1. Force variables. - %% 2. Group adjacent pairs with literal keys. - %% 3. Within each such group, remove multiple assignments to the same key. - %% 4. Partition each group according to operator ('=>' and ':='). - Pairs0 = [{Op,K,V} || - #c_map_pair{op=#c_literal{val=Op},key=K,val=V} <- Ces], - {Pairs,Esp,St1} = foldr(fun - ({Op,K0,V0}, {Ops,Espi,Sti0}) when Op =:= assoc; Op =:= exact -> - {K,Eps1,Sti1} = atomic(K0, Sub, Sti0), - {V,Eps2,Sti2} = atomic(V0, Sub, Sti1), - {[{Op,K,V}|Ops],Eps1 ++ Eps2 ++ Espi,Sti2} - end, {[],[],St0}, Pairs0), - map_split_pairs_1(A, Var, Pairs, Esp, St1). - -map_split_pairs_1(A, Map0, [{Op,Key,Val}|Pairs1]=Pairs0, Esp0, St0) -> - {Map1,Em,St1} = force_atomic(Map0, St0), - case Key of - #k_var{} -> - %% Don't combine variable keys with other keys. - Kes = [#k_map_pair{key=Key,val=Val}], - Map = #k_map{anno=A,op=Op,var=Map1,es=Kes}, - map_split_pairs_1(A, Map, Pairs1, Esp0 ++ Em, St1); - _ -> - %% Literal key. Split off all literal keys. - {L,Pairs} = splitwith(fun({_,#k_var{},_}) -> false; - ({_,_,_}) -> true - end, Pairs0), - {Map,Esp,St2} = map_group_pairs(A, Map1, L, Esp0 ++ Em, St1), - map_split_pairs_1(A, Map, Pairs, Esp, St2) - end; -map_split_pairs_1(_, Map, [], Esp, St0) -> - {Map,Esp,St0}. - -map_group_pairs(A, Var, Pairs0, Esp, St0) -> - Pairs = map_remove_dup_keys(Pairs0), - Assoc = [#k_map_pair{key=K,val=V} || {_,{assoc,K,V}} <- Pairs], - Exact = [#k_map_pair{key=K,val=V} || {_,{exact,K,V}} <- Pairs], - case {Assoc,Exact} of - {[_|_],[]} -> - {#k_map{anno=A,op=assoc,var=Var,es=Assoc},Esp,St0}; - {[],[_|_]} -> - {#k_map{anno=A,op=exact,var=Var,es=Exact},Esp,St0}; - {[_|_],[_|_]} -> - Map = #k_map{anno=A,op=assoc,var=Var,es=Assoc}, - {Mvar,Em,St1} = force_atomic(Map, St0), - {#k_map{anno=A,op=exact,var=Mvar,es=Exact},Esp ++ Em,St1} - end. - -map_remove_dup_keys(Es) -> - map_remove_dup_keys(Es, #{}). - -map_remove_dup_keys([{assoc,K0,V}|Es0], Used0) -> - K = map_key_clean(K0), - Op = case Used0 of - #{K:={exact,_,_}} -> exact; - #{} -> assoc - end, - Used1 = Used0#{K=>{Op,K0,V}}, - map_remove_dup_keys(Es0, Used1); -map_remove_dup_keys([{exact,K0,V}|Es0], Used0) -> - K = map_key_clean(K0), - Op = case Used0 of - #{K:={assoc,_,_}} -> assoc; - #{} -> exact - end, - Used1 = Used0#{K=>{Op,K0,V}}, - map_remove_dup_keys(Es0, Used1); -map_remove_dup_keys([], Used) -> - %% We must sort the map entries to ensure consistent - %% order from compilation to compilation. - sort(maps:to_list(Used)). - -%% Clean a map key from annotations. -map_key_clean(#k_var{name=V}) -> {var,V}; -map_key_clean(#k_literal{val=V}) -> {lit,V}. - -%% call_type(Module, Function, Arity) -> call | bif | error. -%% Classify the call. -call_type(#c_literal{val=M}, #c_literal{val=F}, As, St) when is_atom(M), is_atom(F) -> - case is_remote_bif(M, F, As) of - false -> - call; - true -> - %% The guard BIFs min/2 and max/2 were introduced in - %% Erlang/OTP 26. If we are compiling for an earlier - %% version, we must translate them as call instructions. - case {M,F,St#kern.no_min_max_bifs} of - {erlang,min,true} -> call; - {erlang,max,true} -> call; - {_,_,_} -> bif - end - end; -call_type(#c_var{}, #c_literal{val=A}, _, _) when is_atom(A) -> call; -call_type(#c_literal{val=A}, #c_var{}, _, _) when is_atom(A) -> call; -call_type(#c_var{}, #c_var{}, _, _) -> call; -call_type(_, _, _, _) -> error. - -%% match_vars(Kexpr, State) -> {[Kvar],[PreKexpr],State}. -%% Force return from body into a list of variables. - -match_vars(#ivalues{args=As}, St) -> - foldr(fun (Ka, {Vs,Vsp,St0}) -> - {V,Vp,St1} = force_variable(Ka, St0), - {[V|Vs],Vp ++ Vsp,St1} - end, {[],[],St}, As); -match_vars(Ka, St0) -> - {V,Vp,St1} = force_variable(Ka, St0), - {[V],Vp,St1}. - -%% c_apply(A, Op, [Carg], Sub, State) -> {Kexpr,[PreKexpr],State}. -%% Transform application. - -c_apply(A, #c_var{anno=Ra,name={F0,Ar}}, Cargs, Sub, #kern{labels=Labels}=St0) -> - case sets:is_element(F0, Labels) of - true -> - %% This is a goto to a label in a letrec_goto construct. - {Kargs,Ap,St1} = atomic_list(Cargs, Sub, St0), - {#k_goto{label=F0,args=Kargs},Ap,St1}; - false -> - {Kargs,Ap,St1} = atomic_list(Cargs, Sub, St0), - F1 = get_fsub(F0, Ar, Sub), %Has it been rewritten - {#k_call{anno=A,op=#k_local{anno=Ra,name=F1,arity=Ar},args=Kargs}, - Ap,St1} - end; -c_apply(A, Cop, Cargs, Sub, St0) -> - {Kop,Op,St1} = variable(Cop, Sub, St0), - {Kargs,Ap,St2} = atomic_list(Cargs, Sub, St1), - {#k_call{anno=A,op=Kop,args=Kargs},Op ++ Ap,St2}. - -flatten_seq(#iset{anno=A,vars=Vs,arg=Arg,body=B}) -> - [#iset{anno=A,vars=Vs,arg=Arg}|flatten_seq(B)]; -flatten_seq(Ke) -> [Ke]. - -pre_seq([#iset{anno=A,vars=Vs,arg=Arg,body=B}|Ps], K) -> - B = undefined, %Assertion. - #iset{anno=A,vars=Vs,arg=Arg,body=pre_seq(Ps, K)}; -pre_seq([P|Ps], K) -> - #iset{vars=[],arg=P,body=pre_seq(Ps, K)}; -pre_seq([], K) -> K. - -%% atomic(Cexpr, Sub, State) -> {Katomic,[PreKexpr],State}. -%% Convert a Core expression making sure the result is an atomic -%% literal. - -atomic(Ce, Sub, St0) -> - {Ke,Kp,St1} = expr(Ce, Sub, St0), - {Ka,Ap,St2} = force_atomic(Ke, St1), - {Ka,Kp ++ Ap,St2}. - -force_atomic(Ke, St0) -> - case is_atomic(Ke) of - true -> {Ke,[],St0}; - false -> - {V,St1} = new_var(St0), - {V,[#iset{vars=[V],arg=Ke}],St1} - end. - -atomic_bin([#c_bitstr{anno=A,val=E0,size=S0,unit=U0,type=T,flags=Fs0}|Es0], - Sub, St0) -> - {E,Ap1,St1} = atomic(E0, Sub, St0), - {S1,Ap2,St2} = atomic(S0, Sub, St1), - validate_bin_element_size(S1, A), - U1 = cerl:concrete(U0), - Fs1 = cerl:concrete(Fs0), - {Es,Ap3,St3} = atomic_bin(Es0, Sub, St2), - {#k_bin_seg{anno=A,size=S1, - unit=U1, - type=cerl:concrete(T), - flags=Fs1, - seg=E,next=Es}, - Ap1++Ap2++Ap3,St3}; -atomic_bin([], _Sub, St) -> {#k_bin_end{},[],St}. - -validate_bin_element_size(#k_var{}, _Anno) -> ok; -validate_bin_element_size(#k_literal{val=Val}, Anno) -> - case Val of - all -> ok; - undefined -> ok; - _ when is_integer(Val), Val >= 0 -> ok; - _ -> throw({bad_segment_size,get_location(Anno)}) - end. - -%% atomic_list([Cexpr], Sub, State) -> {[Kexpr],[PreKexpr],State}. - -atomic_list(Ces, Sub, St) -> - foldr(fun (Ce, {Kes,Esp,St0}) -> - {Ke,Ep,St1} = atomic(Ce, Sub, St0), - {[Ke|Kes],Ep ++ Esp,St1} - end, {[],[],St}, Ces). - -%% is_atomic(Kexpr) -> boolean(). -%% Is a Kexpr atomic? - -is_atomic(#k_literal{}) -> true; -is_atomic(#k_var{}) -> true; -%%is_atomic(#k_char{}) -> true; %No characters -is_atomic(_) -> false. - -%% variable(Cexpr, Sub, State) -> {Kvar,[PreKexpr],State}. -%% Convert a Core expression making sure the result is a variable. - -variable(Ce, Sub, St0) -> - {Ke,Kp,St1} = expr(Ce, Sub, St0), - {Kv,Vp,St2} = force_variable(Ke, St1), - {Kv,Kp ++ Vp,St2}. - -force_variable(#k_var{}=Ke, St) -> {Ke,[],St}; -force_variable(Ke, St0) -> - {V,St1} = new_var(St0), - {V,[#iset{vars=[V],arg=Ke}],St1}. - -%% pattern(Cpat, Isub, Osub, State) -> {Kpat,Sub,State}. -%% Convert patterns. Variables shadow so rename variables that are -%% already defined. -%% -%% Patterns are complicated by sizes in binaries. These are pure -%% input variables which create no bindings. We, therefore, need to -%% carry around the original substitutions to get the correct -%% handling. - -pattern(#c_var{anno=A,name=V}, _Isub, Osub, St0) -> - case sets:is_element(V, St0#kern.ds) of - true -> - {New,St1} = new_var_name(St0), - {#k_var{anno=A,name=New}, - set_vsub(V, New, Osub), - St1#kern{ds=sets:add_element(New, St1#kern.ds)}}; - false -> - {#k_var{anno=A,name=V},Osub, - St0#kern{ds=sets:add_element(V, St0#kern.ds)}} - end; -pattern(#c_literal{anno=A,val=Val}, _Isub, Osub, St) -> - {#k_literal{anno=A,val=Val},Osub,St}; -pattern(#c_cons{anno=A,hd=Ch,tl=Ct}, Isub, Osub0, St0) -> - {Kh,Osub1,St1} = pattern(Ch, Isub, Osub0, St0), - {Kt,Osub2,St2} = pattern(Ct, Isub, Osub1, St1), - {#k_cons{anno=A,hd=Kh,tl=Kt},Osub2,St2}; -pattern(#c_tuple{anno=A,es=Ces}, Isub, Osub0, St0) -> - {Kes,Osub1,St1} = pattern_list(Ces, Isub, Osub0, St0), - {#k_tuple{anno=A,es=Kes},Osub1,St1}; -pattern(#c_map{anno=A,es=Ces}, Isub, Osub0, St0) -> - {Kes,Osub1,St1} = pattern_map_pairs(Ces, Isub, Osub0, St0), - {#k_map{anno=A,op=exact,es=Kes},Osub1,St1}; -pattern(#c_binary{anno=A,segments=Cv}, Isub, Osub0, St0) -> - {Kv,Osub1,St1} = pattern_bin(Cv, Isub, Osub0, St0), - {#k_binary{anno=A,segs=Kv},Osub1,St1}; -pattern(#c_alias{anno=A,var=Cv,pat=Cp}, Isub, Osub0, St0) -> - {Cvs,Cpat} = flatten_alias(Cp), - {Kvs,Osub1,St1} = pattern_list([Cv|Cvs], Isub, Osub0, St0), - {Kpat,Osub2,St2} = pattern(Cpat, Isub, Osub1, St1), - {#ialias{anno=A,vars=Kvs,pat=Kpat},Osub2,St2}. - -flatten_alias(#c_alias{var=V,pat=P}) -> - {Vs,Pat} = flatten_alias(P), - {[V|Vs],Pat}; -flatten_alias(Pat) -> {[],Pat}. - -pattern_map_pairs(Ces0, Isub, Osub0, St0) -> - %% pattern the pair keys and values as normal - {Kes,{Osub1,St1}} = mapfoldl(fun - (#c_map_pair{anno=A,key=Ck,val=Cv},{Osubi0,Sti0}) -> - {Kk,[],Sti1} = expr(Ck, Isub, Sti0), - {Kv,Osubi2,Sti2} = pattern(Cv, Isub, Osubi0, Sti1), - {#k_map_pair{anno=A,key=Kk,val=Kv},{Osubi2,Sti2}} - end, {Osub0, St0}, Ces0), - %% It is later assumed that these keys are term sorted - %% so we need to sort them here - Kes1 = sort(fun - (#k_map_pair{key=KkA},#k_map_pair{key=KkB}) -> - A = map_key_clean(KkA), - B = map_key_clean(KkB), - erts_internal:cmp_term(A,B) < 0 - end, Kes), - {Kes1,Osub1,St1}. - -pattern_bin(Es, Isub, Osub0, St) -> - pattern_bin_1(Es, Isub, Osub0, St). - -pattern_bin_1([#c_bitstr{anno=A,val=E0,size=S0,unit=U0,type=T,flags=Fs0}|Es0], - Isub, Osub0, St0) -> - {S1,[],St1} = expr(S0, Isub, St0), - S = case S1 of - #k_var{} -> S1; - #k_literal{val=Val} when is_integer(Val); is_atom(Val) -> S1; - _ -> - %% Bad size (coming from an optimization or Core Erlang - %% source code) - replace it with a known atom because - %% a literal or bit syntax construction can cause further - %% problems. - #k_literal{val=bad_size} - end, - U = cerl:concrete(U0), - Fs = cerl:concrete(Fs0), - {E,Osub1,St2} = pattern(E0, Isub, Osub0, St1), - {Es,Osub,St3} = pattern_bin_1(Es0, Isub, Osub1, St2), - {build_bin_seg(A, S, U, cerl:concrete(T), Fs, E, Es),Osub,St3}; -pattern_bin_1([], _Isub, Osub, St) -> - {#k_bin_end{},Osub,St}. - -%% build_bin_seg(Anno, Size, Unit, Type, Flags, Seg, Next) -> #k_bin_seg{}. -%% This function normalizes literal integers with size > 8 and literal -%% utf8 segments into integers with size = 8 (and potentially an integer -%% with size less than 8 at the end). This is so further optimizations -%% have a normalized view of literal integers, allowing us to generate -%% more literals and group more clauses. Those integers may be "squeezed" -%% later into the largest integer possible. -%% -build_bin_seg(A, #k_literal{val=Bits} = Sz, U, integer=Type, - [unsigned,big]=Flags, #k_literal{val=Int}=Seg, Next) when is_integer(Bits) -> - Size = Bits * U, - case integer_fits_and_is_expandable(Int, Size) of - true -> build_bin_seg_integer_recur(A, Size, Int, Next); - false -> #k_bin_seg{anno=A,size=Sz,unit=U,type=Type,flags=Flags,seg=Seg,next=Next} - end; -build_bin_seg(A, Sz, U, utf8=Type, [unsigned,big]=Flags, #k_literal{val=Utf8} = Seg, Next) -> - case utf8_fits(Utf8) of - {Int, Bits} -> build_bin_seg_integer_recur(A, Bits, Int, Next); - error -> #k_bin_seg{anno=A,size=Sz,unit=U,type=Type,flags=Flags,seg=Seg,next=Next} - end; -build_bin_seg(A, Sz, U, Type, Flags, Seg, Next) -> - #k_bin_seg{anno=A,size=Sz,unit=U,type=Type,flags=Flags,seg=Seg,next=Next}. - -build_bin_seg_integer_recur(A, Bits, Val, Next) when Bits > 8 -> - NextBits = Bits - 8, - NextVal = Val band ((1 bsl NextBits) - 1), - Last = build_bin_seg_integer_recur(A, NextBits, NextVal, Next), - build_bin_seg_integer(A, 8, Val bsr NextBits, Last); - -build_bin_seg_integer_recur(A, Bits, Val, Next) -> - build_bin_seg_integer(A, Bits, Val, Next). - -build_bin_seg_integer(A, Bits, Val, Next) -> - Sz = #k_literal{anno=A,val=Bits}, - Seg = #k_literal{anno=A,val=Val}, - #k_bin_seg{anno=A,size=Sz,unit=1,type=integer,flags=[unsigned,big],seg=Seg,next=Next}. - -integer_fits_and_is_expandable(Int, Size) when is_integer(Int), is_integer(Size), - 0 < Size, Size =< ?EXPAND_MAX_SIZE_SEGMENT -> - case <<Int:Size>> of - <<Int:Size>> -> true; - _ -> false - end; -integer_fits_and_is_expandable(_Int, _Size) -> - false. - -utf8_fits(Utf8) -> - try - Bin = <<Utf8/utf8>>, - Bits = bit_size(Bin), - <<Int:Bits>> = Bin, - {Int, Bits} - catch - _:_ -> error - end. - -%% pattern_list([Cexpr], Sub, State) -> {[Kexpr],Sub,State}. - -pattern_list(Ces, Sub, St) -> - pattern_list(Ces, Sub, Sub, St). - -pattern_list(Ces, Isub, Osub, St) -> - foldr(fun (Ce, {Kes,Osub0,St0}) -> - {Ke,Osub1,St1} = pattern(Ce, Isub, Osub0, St0), - {[Ke|Kes],Osub1,St1} - end, {[],Osub,St}, Ces). - -%% new_sub() -> Subs. -%% set_vsub(Name, Sub, Subs) -> Subs. -%% subst_vsub(Name, Sub, Subs) -> Subs. -%% get_vsub(Name, Subs) -> SubName. -%% Add/get substitute Sub for Name to VarSub. -%% -%% We're using a many-to-one bimap so we can rename all references to a -%% variable without having to scan through all of them, which can cause -%% compile times to explode (see record_SUITE:slow_compilation/1). - -new_sub() -> {#{}, #{}}. - -get_vsub(Key, Subs) -> - bimap_get(Key, Subs, Key). - -get_fsub(Name, Arity, Subs) -> - bimap_get({Name, Arity}, Subs, Name). - -set_vsub(Same, Same, Subs) -> - Subs; -set_vsub(Key, Val, Subs) -> - bimap_set(Key, Val, Subs). - -set_fsub(Name, Arity, Val, Subs) -> - set_vsub({Name, Arity}, Val, Subs). - -subst_vsub(Key, Val, Subs) -> - bimap_rename(Key, Val, Subs). - -bimap_get(Key, {Map, _InvMap}, Default) -> - case Map of - #{ Key := Val } -> Val; - _ -> Default - end. - -%% Maps Key to Val without touching existing references to Key. -bimap_set(Key, Val, {Map0, InvMap0}) -> - InvMap = bm_update_inv_lookup(Key, Val, Map0, InvMap0), - Map = Map0#{ Key => Val }, - {Map, InvMap}. - -bm_update_inv_lookup(Key, Val, Map, InvMap0) -> - InvMap = bm_cleanup_inv_lookup(Key, Map, InvMap0), - case InvMap of - #{ Val := Keys } -> - %% Other keys map to the same value, add ours to the set. - InvMap#{ Val := ordsets:add_element(Key, Keys) }; - #{} -> - InvMap#{ Val => [Key] } - end. - -bm_cleanup_inv_lookup(Key, Map, InvMap) when is_map_key(Key, Map) -> - #{ Key := Old } = Map, - case InvMap of - #{ Old := [Key] } -> - maps:remove(Old, InvMap); - #{ Old := [_|_]=Keys } -> - InvMap#{ Old := ordsets:del_element(Key, Keys) } - end; -bm_cleanup_inv_lookup(_Key, _Map, InvMap) -> - InvMap. - -%% Maps Key to Val, and replaces all existing references to Key with Val. -bimap_rename(Key, Val, {Map0, InvMap0}) when is_map_key(Key, InvMap0) -> - Keys = map_get(Key, InvMap0), - - Map1 = Map0#{ Key => Val }, - Map = bimap_update_lookup(Keys, Val, Map1), - - InvMap1 = maps:remove(Key, InvMap0), - InvMap = InvMap1#{ Val => ordsets:add_element(Key, Keys) }, - - {Map, InvMap}; -bimap_rename(Key, Val, Subs) -> - bimap_set(Key, Val, Subs). - -bimap_update_lookup([Key | Keys], Val, Map) -> - bimap_update_lookup(Keys, Val, Map#{ Key := Val }); -bimap_update_lookup([], _Val, Map) -> - Map. - -new_fun_name(St) -> - new_fun_name("anonymous", St). - -%% new_fun_name(Type, State) -> {FunName,State}. - -new_fun_name(Type, #kern{func={F,Arity},fcount=C}=St) -> - Name = "-" ++ atom_to_list(F) ++ "/" ++ integer_to_list(Arity) ++ - "-" ++ Type ++ "-" ++ integer_to_list(C) ++ "-", - {list_to_atom(Name),St#kern{fcount=C+1}}. - -%% new_var_name(State) -> {VarName,State}. - -new_var_name(#kern{vcount=C}=St) -> - {C,St#kern{vcount=C+1}}. - -%% new_var(State) -> {#k_var{},State}. - -new_var(St0) -> - {New,St1} = new_var_name(St0), - {#k_var{name=New},St1}. - -%% new_vars(Count, State) -> {[#k_var{}],State}. -%% Make Count new variables. - -new_vars(N, St) -> new_vars(N, St, []). - -new_vars(N, St0, Vs) when N > 0 -> - {V,St1} = new_var(St0), - new_vars(N-1, St1, [V|Vs]); -new_vars(0, St, Vs) -> {Vs,St}. - -make_vars(Vs) -> [ #k_var{name=V} || V <- Vs ]. - -%% is_remote_bif(Mod, Name, Arity) -> true | false. -%% Test if function is really a BIF. - -is_remote_bif(erlang, get, [_]) -> true; -is_remote_bif(erlang, is_record, [_,Tag,Sz]) -> - case {Tag,Sz} of - {#c_literal{val=Atom},#c_literal{val=Int}} - when is_atom(Atom), is_integer(Int) -> - %% Tag and size are literals. This is a guard BIF. - true; - {_,_} -> - false - end; -is_remote_bif(erlang, N, As) -> - Arity = length(As), - case erl_internal:guard_bif(N, Arity) of - true -> true; - false -> - try erl_internal:op_type(N, Arity) of - arith -> true; - bool -> true; - comp -> true; - list -> false; - send -> false - catch - _:_ -> false % not an op - end - end; -is_remote_bif(_, _, _) -> false. - -%% bif_vals(Name, Arity) -> integer(). -%% bif_vals(Mod, Name, Arity) -> integer(). -%% Determine how many return values a BIF has. Provision for BIFs to -%% return multiple values. Only used in bodies where a BIF may be -%% called for effect only. - -bif_vals(recv_peek_message, 0) -> 2; -bif_vals(_, _) -> 1. - -bif_vals(_, _, _) -> 1. - -%% foldr2(Fun, Acc, List1, List2) -> Acc. -%% Fold over two lists. - -foldr2(Fun, Acc0, [E1|L1], [E2|L2]) -> - Acc1 = Fun(E1, E2, Acc0), - foldr2(Fun, Acc1, L1, L2); -foldr2(_, Acc, [], []) -> Acc. - -%% This code implements the algorithm for an optimizing compiler for -%% pattern matching given "The Implementation of Functional -%% Programming Languages" by Simon Peyton Jones. The code is much -%% longer as the meaning of constructors is different from the book. -%% -%% In Erlang many constructors can have different values, e.g. 'atom' -%% or 'integer', whereas in the original algorithm thse would be -%% different constructors. Our view makes it easier in later passes to -%% handle indexing over each type. -%% -%% Patterns are complicated by having alias variables. The form of a -%% pattern is Pat | {alias,Pat,[AliasVar]}. This is hidden by access -%% functions to pattern arguments but the code must be aware of it. -%% -%% The compilation proceeds in two steps: -%% -%% 1. The patterns in the clauses to converted to lists of kernel -%% patterns. The Core clause is now hybrid, this is easier to work -%% with. Remove clauses with trivially false guards, this simplifies -%% later passes. Add locally defined vars and variable subs to each -%% clause for later use. -%% -%% 2. The pattern matching is optimised. Variable substitutions are -%% added to the VarSub structure and new variables are made visible. -%% The guard and body are then converted to Kernel form. - -%% kmatch([Var], [Clause], Sub, State) -> {Kexpr,State}. - -kmatch(Us, Ccs, Sub, St0) -> - {Cs,St1} = match_pre(Ccs, Sub, St0), %Convert clauses - Def = fail, - match(Us, Cs, Def, St1). %Do the match. - -%% match_pre([Cclause], Sub, State) -> {[Clause],State}. -%% Must be careful not to generate new substitutions here now! -%% Remove clauses with trivially false guards which will never -%% succeed. - -match_pre(Cs, Sub0, St) -> - foldr(fun (#c_clause{anno=A,pats=Ps,guard=G,body=B}, {Cs0,St0}) -> - {Kps,Osub1,St1} = pattern_list(Ps, Sub0, St0), - {[#iclause{anno=A,isub=Sub0,osub=Osub1, - pats=Kps,guard=G,body=B}| - Cs0],St1} - end, {[],St}, Cs). - -%% match([Var], [Clause], Default, State) -> {MatchExpr,State}. - -match([_U|_Us] = L, Cs, Def, St0) -> - %%ok = io:format("match ~p~n", [Cs]), - Pcss = partition(Cs), - foldr(fun (Pcs, {D,St}) -> match_varcon(L, Pcs, D, St) end, - {Def,St0}, Pcss); -match([], Cs, Def, St) -> - match_guard(Cs, Def, St). - -%% match_guard([Clause], Default, State) -> {IfExpr,State}. -%% Build a guard to handle guards. A guard *ALWAYS* fails if no -%% clause matches, there will be a surrounding 'alt' to catch the -%% failure. Drop redundant cases, i.e. those after a true guard. - -match_guard(Cs0, Def0, St0) -> - {Cs1,Def1,St1} = match_guard_1(Cs0, Def0, St0), - {build_alt(build_guard(Cs1), Def1),St1}. - -match_guard_1([#iclause{anno=A,osub=Osub,guard=G,body=B}|Cs0], Def0, St0) -> - case is_true_guard(G) of - true -> - %% The true clause body becomes the default. - {Kb,Pb,St1} = body(B, Osub, St0), - St2 = maybe_add_warning(Cs0, A, St1), - St = maybe_add_warning(Def0, A, St2), - {[],pre_seq(Pb, Kb),St}; - false -> - {Kg,St1} = guard(G, Osub, St0), - {Kb,Pb,St2} = body(B, Osub, St1), - {Cs1,Def1,St3} = match_guard_1(Cs0, Def0, St2), - {[#k_guard_clause{guard=Kg,body=pre_seq(Pb, Kb)}|Cs1], - Def1,St3} - end; -match_guard_1([], Def, St) -> {[],Def,St}. - -maybe_add_warning([C|_], MatchAnno, St) -> - maybe_add_warning(C, MatchAnno, St); -maybe_add_warning([], _MatchAnno, St) -> St; -maybe_add_warning(fail, _MatchAnno, St) -> St; -maybe_add_warning(Ke, MatchAnno, St) -> - case is_compiler_generated(Ke) of - true -> - St; - false -> - Anno = get_kanno(Ke), - Line = get_location(Anno), - MatchLine = get_line(MatchAnno), - Warn = case MatchLine of - none -> {nomatch,shadow}; - _ -> {nomatch,{shadow,MatchLine}} - end, - add_warning(Line, Warn, Anno, St) - end. - -get_location([Line|_]) when is_integer(Line) -> - Line; -get_location([{Line, Column} | _T]) when is_integer(Line), is_integer(Column) -> - {Line,Column}; -get_location([_|T]) -> - get_location(T); -get_location([]) -> - none. - -get_line([Line|_]) when is_integer(Line) -> Line; -get_line([{Line, _Column} | _T]) 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 - -%% is_true_guard(Guard) -> boolean(). -%% Test if a guard is trivially true. - -is_true_guard(#c_literal{val=true}) -> true; -is_true_guard(_) -> false. - -%% partition([Clause]) -> [[Clause]]. -%% Partition a list of clauses into groups which either contain -%% clauses with a variable first argument, or with a "constructor". - -partition([C1|Cs]) -> - V1 = is_var_clause(C1), - {More,Rest} = splitwith(fun (C) -> is_var_clause(C) =:= V1 end, Cs), - [[C1|More]|partition(Rest)]; -partition([]) -> []. - -%% match_varcon([Var], [Clause], Def, [Var], Sub, State) -> -%% {MatchExpr,State}. - -match_varcon(Us, [C|_]=Cs, Def, St) -> - case is_var_clause(C) of - true -> match_var(Us, Cs, Def, St); - false -> match_con(Us, Cs, Def, St) - end. - -%% match_var([Var], [Clause], Def, State) -> {MatchExpr,State}. -%% Build a call to "select" from a list of clauses all containing a -%% variable as the first argument. We must rename the variable in -%% each clause to be the match variable as these clause will share -%% this variable and may have different names for it. Rename aliases -%% as well. - -match_var([U|Us], Cs0, Def, St) -> - Cs1 = map(fun (#iclause{isub=Isub0,osub=Osub0,pats=[Arg|As]}=C) -> - Vs = [arg_arg(Arg)|arg_alias(Arg)], - Osub1 = foldl(fun (#k_var{name=V}, Acc) -> - subst_vsub(V, U#k_var.name, Acc) - end, Osub0, Vs), - Isub1 = foldl(fun (#k_var{name=V}, Acc) -> - subst_vsub(V, U#k_var.name, Acc) - end, Isub0, Vs), - C#iclause{isub=Isub1,osub=Osub1,pats=As} - end, Cs0), - match(Us, Cs1, Def, St). - -%% match_con(Variables, [Clause], Default, State) -> {SelectExpr,State}. -%% Build call to "select" from a list of clauses all containing a -%% constructor/constant as first argument. Group the constructors -%% according to type, the order is really irrelevant but tries to be -%% smart. -match_con([U|_Us] = L, Cs, Def, St0) -> - %% Extract clauses for different constructors (types). - %%ok = io:format("match_con ~p~n", [Cs]), - Ttcs0 = select_types(Cs, [], [], [], [], [], [], [], [], []), - Ttcs1 = [{T, Types} || {T, [_ | _] = Types} <- Ttcs0], - Ttcs = opt_single_valued(Ttcs1), - %%ok = io:format("ttcs = ~p~n", [Ttcs]), - {Scs,St1} = - mapfoldl(fun ({T,Tcs}, St) -> - {[S|_]=Sc,S1} = match_value(L, T, Tcs, fail, St), - %%ok = io:format("match_con type2 ~p~n", [T]), - Anno = get_kanno(S), - {#k_type_clause{anno=Anno,type=T,values=Sc},S1} end, - St0, Ttcs), - {build_alt_1st_no_fail(build_select(U, Scs), Def),St1}. - -select_types([NoExpC | Cs], Bin, BinCon, Cons, Tuple, Map, Atom, Float, Int, Nil) -> - C = expand_pat_lit_clause(NoExpC), - case clause_con(C) of - k_binary -> - select_types(Cs, [C |Bin], BinCon, Cons, Tuple, Map, Atom, Float, Int, Nil); - k_bin_seg -> - select_types(Cs, Bin, [C | BinCon], Cons, Tuple, Map, Atom, Float, Int, Nil); - k_bin_end -> - select_types(Cs, Bin, [C | BinCon], Cons, Tuple, Map, Atom, Float, Int, Nil); - k_cons -> - select_types(Cs, Bin, BinCon, [C | Cons], Tuple, Map, Atom, Float, Int, Nil); - k_tuple -> - select_types(Cs, Bin, BinCon, Cons, [C | Tuple], Map, Atom, Float, Int, Nil); - k_map -> - select_types(Cs, Bin, BinCon, Cons, Tuple, [C | Map], Atom, Float, Int, Nil); - k_atom -> - select_types(Cs, Bin, BinCon, Cons, Tuple, Map, [C | Atom], Float, Int, Nil); - k_float -> - select_types(Cs, Bin, BinCon, Cons, Tuple, Map, Atom, [C | Float], Int, Nil); - k_int -> - select_types(Cs, Bin, BinCon, Cons, Tuple, Map, Atom, Float, [C | Int], Nil); - k_nil -> - select_types(Cs, Bin, BinCon, Cons, Tuple, Map, Atom, Float, Int, [C | Nil]) - end; -select_types([], Bin, BinCon, Cons, Tuple, Map, Atom, Float, Int, Nil) -> - [{k_binary, reverse(Bin)}] ++ handle_bin_con(reverse(BinCon)) ++ - [ - {k_cons, reverse(Cons)}, - {k_tuple, reverse(Tuple)}, - {k_map, reverse(Map)}, - {k_atom, reverse(Atom)}, - {k_float, reverse(Float)}, - {k_int, reverse(Int)}, - {k_nil, reverse(Nil)} - ]. - -expand_pat_lit_clause(#iclause{pats=[#ialias{pat=#k_literal{anno=A,val=Val}}=Alias|Ps]}=C) -> - P = expand_pat_lit(Val, A), - C#iclause{pats=[Alias#ialias{pat=P}|Ps]}; -expand_pat_lit_clause(#iclause{pats=[#k_literal{anno=A,val=Val}|Ps]}=C) -> - P = expand_pat_lit(Val, A), - C#iclause{pats=[P|Ps]}; -expand_pat_lit_clause(C) -> C. - -expand_pat_lit([H|T], A) -> - #k_cons{anno=A,hd=#k_literal{anno=A,val=H},tl=#k_literal{anno=A,val=T}}; -expand_pat_lit(Tuple, A) when is_tuple(Tuple) -> - #k_tuple{anno=A,es=[#k_literal{anno=A,val=E} || E <- tuple_to_list(Tuple)]}; -expand_pat_lit(Lit, A) -> - #k_literal{anno=A,val=Lit}. - -%% opt_singled_valued([{Type,Clauses}]) -> [{Type,Clauses}]. -%% If a type only has one clause and if the pattern is a complex -%% literal, the matching can be done more efficiently by directly -%% comparing with the literal (that is especially true for binaries). -%% -%% It is important not to do this transformation for atomic literals -%% (such as `[]`), since that would cause the test for an empty list -%% to be executed before the test for a nonempty list. - -opt_single_valued(Ttcs) -> - opt_single_valued(Ttcs, [], []). - -opt_single_valued([{_,[#iclause{pats=[#k_literal{}|_]}]}=Ttc|Ttcs], TtcAcc, LitAcc) -> - %% This is an atomic literal. - opt_single_valued(Ttcs, [Ttc|TtcAcc], LitAcc); -opt_single_valued([{_,[#iclause{pats=[P0|Ps]}=Tc]}=Ttc|Ttcs], TtcAcc, LitAcc) -> - try combine_lit_pat(P0) of - P -> - LitTtc = Tc#iclause{pats=[P|Ps]}, - opt_single_valued(Ttcs, TtcAcc, [LitTtc|LitAcc]) - catch - not_possible -> - opt_single_valued(Ttcs, [Ttc|TtcAcc], LitAcc) - end; -opt_single_valued([Ttc|Ttcs], TtcAcc, LitAcc) -> - opt_single_valued(Ttcs, [Ttc|TtcAcc], LitAcc); -opt_single_valued([], TtcAcc, []) -> - reverse(TtcAcc); -opt_single_valued([], TtcAcc, LitAcc) -> - Literals = {k_literal,reverse(LitAcc)}, - %% Test the literals as early as possible. - case reverse(TtcAcc) of - [{k_binary,_}=Bin|Ttcs] -> - %% The delayed creation of sub binaries requires - %% bs_start_match2 to be the first instruction in the - %% function. - [Bin,Literals|Ttcs]; - Ttcs -> - [Literals|Ttcs] - end. - -combine_lit_pat(#ialias{pat=Pat0}=Alias) -> - Pat = combine_lit_pat(Pat0), - Alias#ialias{pat=Pat}; -combine_lit_pat(#k_literal{}) -> - %% This is an atomic literal. Rewriting would be a pessimization, - %% especially for `[]`. - throw(not_possible); -combine_lit_pat(Pat) -> - do_combine_lit_pat(Pat). - -do_combine_lit_pat(#k_binary{anno=A,segs=Segs}) -> - Bin = combine_bin_segs(Segs), - #k_literal{anno=A,val=Bin}; -do_combine_lit_pat(#k_cons{anno=A,hd=Hd0,tl=Tl0}) -> - #k_literal{val=Hd} = do_combine_lit_pat(Hd0), - #k_literal{val=Tl} = do_combine_lit_pat(Tl0), - #k_literal{anno=A,val=[Hd|Tl]}; -do_combine_lit_pat(#k_literal{}=Lit) -> - Lit; -do_combine_lit_pat(#k_tuple{anno=A,es=Es0}) -> - Es = [begin - #k_literal{val=Lit} = do_combine_lit_pat(El), - Lit - end || El <- Es0], - #k_literal{anno=A,val=list_to_tuple(Es)}; -do_combine_lit_pat(_) -> - throw(not_possible). - -combine_bin_segs(#k_bin_seg{size=#k_literal{val=8},unit=1,type=integer, - flags=[unsigned,big],seg=#k_literal{val=Int},next=Next}) - when is_integer(Int), 0 =< Int, Int =< 255 -> - <<Int,(combine_bin_segs(Next))/bits>>; -combine_bin_segs(#k_bin_end{}) -> - <<>>; -combine_bin_segs(_) -> - throw(not_possible). - -%% handle_bin_con([Clause]) -> [{Type,[Clause]}]. -%% Handle clauses for the k_bin_seg constructor. As k_bin_seg -%% matching can overlap, the k_bin_seg constructors cannot be -%% reordered, only grouped. - -handle_bin_con(Cs) -> - try - %% The usual way to match literals is to first extract the - %% value to a register, and then compare the register to the - %% literal value. Extracting the value is good if we need - %% compare it more than once. - %% - %% But we would like to combine the extracting and the - %% comparing into a single instruction if we know that - %% a binary segment must contain specific integer value - %% or the matching will fail, like in this example: - %% - %% <<42:8,...>> -> - %% <<42:8,...>> -> - %% . - %% . - %% . - %% <<42:8,...>> -> - %% <<>> -> - %% - %% The first segment must either contain the integer 42 - %% or the binary must end for the match to succeed. - %% - %% The way we do is to replace the generic #k_bin_seg{} - %% record with a #k_bin_int{} record if all clauses will - %% select the same literal integer (except for one or more - %% clauses that will end the binary). - - {BinSegs0,BinEnd} = - partition(fun (C) -> - clause_con(C) =:= k_bin_seg - end, Cs), - BinSegs = select_bin_int(BinSegs0), - case BinEnd of - [] -> BinSegs; - [_|_] -> BinSegs ++ [{k_bin_end,BinEnd}] - end - catch - throw:not_possible -> - handle_bin_con_not_possible(Cs) - end. - -handle_bin_con_not_possible([C1|Cs]) -> - Con = clause_con(C1), - {More,Rest} = splitwith(fun (C) -> clause_con(C) =:= Con end, Cs), - [{Con,[C1|More]}|handle_bin_con_not_possible(Rest)]; -handle_bin_con_not_possible([]) -> []. - -%% select_bin_int([Clause]) -> {k_bin_int,[Clause]} -%% If the first pattern in each clause selects the same integer, -%% rewrite all clauses to use #k_bin_int{} (which will later be -%% translated to a bs_match_string/4 instruction). -%% -%% If it is not possible to do this rewrite, a 'not_possible' -%% exception is thrown. - -select_bin_int([#iclause{pats=[#k_bin_seg{anno=A,type=integer, - size=#k_literal{val=Bits0}=Sz,unit=U, - flags=Fl,seg=#k_literal{val=Val}, - next=N}|Ps]}=C|Cs0]) when is_integer(Bits0) -> - Bits = U * Bits0, - if - Bits > ?EXPAND_MAX_SIZE_SEGMENT -> throw(not_possible); %Expands the code too much. - true -> ok - end, - select_assert_match_possible(Bits, Val, Fl), - P = #k_bin_int{anno=A,size=Sz,unit=U,flags=Fl,val=Val,next=N}, - case member(native, Fl) of - true -> throw(not_possible); - false -> ok - end, - Cs1 = [C#iclause{pats=[P|Ps]}|select_bin_int_1(Cs0, Bits, Fl, Val)], - Cs = reorder_bin_ints(Cs1), - [{k_bin_int,Cs}]; -select_bin_int(_) -> throw(not_possible). - -select_bin_int_1([#iclause{pats=[#k_bin_seg{anno=A,type=integer, - size=#k_literal{val=Bits0}=Sz, - unit=U, - flags=Fl,seg=#k_literal{val=Val}, - next=N}|Ps]}=C|Cs], - Bits, Fl, Val) when is_integer(Val) -> - if - Bits0*U =:= Bits -> ok; - true -> throw(not_possible) - end, - P = #k_bin_int{anno=A,size=Sz,unit=U,flags=Fl,val=Val,next=N}, - [C#iclause{pats=[P|Ps]}|select_bin_int_1(Cs, Bits, Fl, Val)]; -select_bin_int_1([], _, _, _) -> []; -select_bin_int_1(_, _, _, _) -> throw(not_possible). - -select_assert_match_possible(Sz, Val, Fs) - when is_integer(Sz), Sz >= 0, is_integer(Val) -> - EmptyBindings = erl_eval:new_bindings(), - MatchFun = match_fun(Val), - EvalFun = fun({integer,_,S}, B) -> {value,S,B} end, - Expr = [{bin_element,0,{integer,0,Val},{integer,0,Sz},[{unit,1}|Fs]}], - {value,Bin,EmptyBindings} = eval_bits:expr_grp(Expr, EmptyBindings, EvalFun), - try - {match,_} = eval_bits:match_bits(Expr, Bin, - EmptyBindings, - EmptyBindings, - MatchFun, EvalFun), - ok % this is just an assertion (i.e., no return value) - catch - throw:nomatch -> - throw(not_possible) - end; -select_assert_match_possible(_, _, _) -> - throw(not_possible). - -match_fun(Val) -> - fun(match, {{integer,_,_},NewV,Bs}) when NewV =:= Val -> - {match,Bs} - end. - -reorder_bin_ints([_]=Cs) -> - Cs; -reorder_bin_ints(Cs0) -> - %% It is safe to reorder clauses that match binaries if all - %% of the followings conditions are true: - %% - %% * The first segments for all of them match the same number of - %% bits (guaranteed by caller). - %% - %% * All segments have fixed sizes. - %% - %% * The patterns that follow are also safe to re-order. - try - Cs = sort([{reorder_bin_int_sort_key(C),C} || C <- Cs0]), - [C || {_,C} <- Cs] - catch - throw:not_possible -> - Cs0 - end. - -reorder_bin_int_sort_key(#iclause{pats=[Pat|More],guard=#c_literal{val=true}}) -> - case all(fun(#k_var{}) -> true; - (_) -> false - end, More) of - true -> - %% Only variables. Safe to re-order. - ok; - false -> - %% Not safe to re-order. For example: - %% f([<<"prefix">>, <<"action">>]) -> ... - %% f([<<"prefix">>, Variable]) -> ... - throw(not_possible) - end, - - %% Ensure that the remaining segments have fixed sizes. For example, the following - %% clauses are not safe to re-order: - %% f(<<"dd",_/binary>>) -> dd; - %% f(<<"d",_/binary>>) -> d. - ensure_fixed_size(Pat#k_bin_int.next), - - case Pat of - #k_bin_int{val=Val,next=#k_bin_end{}} -> - %% Sort before clauses with additional segments. This usually results in - %% better code. - [Val]; - #k_bin_int{val=Val} -> - [Val,more] - end; -reorder_bin_int_sort_key(#iclause{}) -> - throw(not_possible). - -ensure_fixed_size(#k_bin_seg{size=Size,next=Next}) -> - case Size of - #k_literal{val=Sz} when is_integer(Sz) -> - ensure_fixed_size(Next); - _ -> - throw(not_possible) - end; -ensure_fixed_size(#k_bin_end{}) -> - ok. - -%% match_value([Var], Con, [Clause], Default, State) -> {SelectExpr,State}. -%% At this point all the clauses have the same constructor, we must -%% now separate them according to value. - -match_value(Us0, T, Cs0, Def, St0) -> - {Us1,Cs1,St1} = partition_intersection(T, Us0, Cs0, St0), - UCss = group_value(T, Us1, Cs1), - %%ok = io:format("match_value ~p ~p~n", [T, Css]), - mapfoldl(fun ({Us,Cs}, St) -> match_clause(Us, Cs, Def, St) end, St1, UCss). - -%% partition_intersection(Type, Us, [Clause], State) -> {Us,Cs,State}. -%% Partitions a map into two maps with the most common keys to the -%% first map. -%% -%% case <M> of -%% <#{a,b}> -%% <#{a,c}> -%% <#{a}> -%% end -%% -%% becomes -%% -%% case <M,M> of -%% <#{a}, #{b}> -%% <#{a}, #{c}> -%% <#{a}, #{ }> -%% end -%% -%% The intention is to group as many keys together as possible and -%% thus reduce the number of lookups to that key. - -partition_intersection(k_map, [U|_]=Us, [_,_|_]=Cs0, St0) -> - Ps = [clause_val(C) || C <- Cs0], - case find_key_intersection(Ps) of - none -> - {Us,Cs0,St0}; - Ks -> - Cs1 = map(fun(#iclause{pats=[Arg|Args]}=C) -> - {Arg1,Arg2} = partition_keys(Arg, Ks), - C#iclause{pats=[Arg1,Arg2|Args]} - end, Cs0), - {[U|Us],Cs1,St0} - end; -partition_intersection(_, Us, Cs, St) -> - {Us,Cs,St}. - -partition_keys(#k_map{es=Pairs}=Map, Ks) -> - F = fun(#k_map_pair{key=Key}) -> - sets:is_element(map_key_clean(Key), Ks) - end, - {Ps1,Ps2} = partition(F, Pairs), - {Map#k_map{es=Ps1},Map#k_map{es=Ps2}}; -partition_keys(#ialias{pat=Map}=Alias, Ks) -> - %% Only alias one of them. - {Map1,Map2} = partition_keys(Map, Ks), - {Map1,Alias#ialias{pat=Map2}}. - -find_key_intersection(Ps) -> - Sets = [sets:from_list(Ks, [{version, 2}]) || Ks <- Ps], - Intersection = sets:intersection(Sets), - case sets:is_empty(Intersection) of - true -> - none; - false -> - All = all(fun (Kset) -> Kset =:= Intersection end, Sets), - case All of - true -> - %% All clauses test the same keys. Partitioning - %% the keys could only make the code worse. - none; - false -> - Intersection - end - end. - -%% group_value([Clause]) -> [[Clause]]. -%% Group clauses according to value. Here we know that -%% 1. Some types are singled valued -%% 2. The clauses in maps and bin_segs cannot be reordered, -%% only grouped -%% 3. Other types are disjoint and can be reordered - -group_value(k_cons, Us, Cs) -> [{Us,Cs}]; %These are single valued -group_value(k_nil, Us, Cs) -> [{Us,Cs}]; -group_value(k_binary, Us, Cs) -> [{Us,Cs}]; -group_value(k_bin_end, Us, Cs) -> [{Us,Cs}]; -group_value(k_bin_seg, Us, Cs) -> group_keeping_order(Us, Cs); -group_value(k_bin_int, Us, Cs) -> [{Us,Cs}]; -group_value(k_map, Us, Cs) -> group_keeping_order(Us, Cs); -group_value(_, Us, Cs) -> - Map = group_values(Cs, #{}), - %% We must sort the grouped values to ensure consistent - %% order from compilation to compilation. - sort([{Us,reverse(Vcs)} || _ := Vcs <- Map]). - -group_values([C|Cs], Acc) -> - Val = clause_val(C), - case Acc of - #{Val:=Gcs} -> - group_values(Cs, Acc#{Val:=[C|Gcs]}); - #{} -> - group_values(Cs, Acc#{Val=>[C]}) - end; -group_values([], Acc) -> Acc. - -group_keeping_order(Us, [C1|Cs]) -> - V1 = clause_val(C1), - {More,Rest} = splitwith(fun (C) -> clause_val(C) =:= V1 end, Cs), - [{Us,[C1|More]}|group_keeping_order(Us, Rest)]; -group_keeping_order(_, []) -> []. - -%% match_clause([Var], [Clause], Default, State) -> {Clause,State}. -%% At this point all the clauses have the same "value". Build one -%% select clause for this value and continue matching. Rename -%% aliases as well. - -match_clause([U|Us], [C|_]=Cs0, Def, St0) -> - Anno = get_kanno(C), - {Match0,Vs,St1} = get_match(get_con(Cs0), St0), - Match = sub_size_var(Match0, Cs0), - {Cs1,St2} = new_clauses(Cs0, U, St1), - Cs2 = squeeze_clauses_by_bin_integer_count(Cs1, []), - {B,St3} = match(Vs ++ Us, Cs2, Def, St2), - {#k_val_clause{anno=Anno,val=Match,body=B},St3}. - -sub_size_var(#k_bin_seg{size=#k_var{name=Name}=Kvar}=BinSeg, [#iclause{isub=Sub}|_]) -> - BinSeg#k_bin_seg{size=Kvar#k_var{name=get_vsub(Name, Sub)}}; -sub_size_var(K, _) -> K. - -get_con([C|_]) -> arg_arg(clause_arg(C)). %Get the constructor - -get_match(#k_cons{}, St0) -> - {[H,T]=L,St1} = new_vars(2, St0), - {#k_cons{hd=H,tl=T},L,St1}; -get_match(#k_binary{}, St0) -> - {[V]=Mes,St1} = new_vars(1, St0), - {#k_binary{segs=V},Mes,St1}; -get_match(#k_bin_seg{size=#k_literal{val=all},next={k_bin_end,[]}}=Seg, St0) -> - {[S,N],St1} = new_vars(2, St0), - {Seg#k_bin_seg{seg=S,next=N},[S],St1}; -get_match(#k_bin_seg{}=Seg, St0) -> - {[S,N],St1} = new_vars(2, St0), - {Seg#k_bin_seg{seg=S,next=N},[S,N],St1}; -get_match(#k_bin_int{}=BinInt, St0) -> - {N,St1} = new_var(St0), - {BinInt#k_bin_int{next=N},[N],St1}; -get_match(#k_tuple{es=Es}, St0) -> - {Mes,St1} = new_vars(length(Es), St0), - {#k_tuple{es=Mes},Mes,St1}; -get_match(#k_map{op=exact,es=Es0}, St0) -> - {Mes,St1} = new_vars(length(Es0), St0), - {Es,_} = mapfoldl(fun - (#k_map_pair{}=Pair, [V|Vs]) -> - {Pair#k_map_pair{val=V},Vs} - end, Mes, Es0), - {#k_map{op=exact,es=Es},Mes,St1}; -get_match(M, St) -> - {M,[],St}. - -new_clauses(Cs0, U, St) -> - Cs1 = map(fun (#iclause{isub=Isub0,osub=Osub0,pats=[Arg|As]}=C) -> - Head = case arg_arg(Arg) of - #k_cons{hd=H,tl=T} -> [H,T|As]; - #k_tuple{es=Es} -> Es ++ As; - #k_binary{segs=E} -> [E|As]; - #k_bin_seg{size=#k_literal{val=all}, - seg=S,next={k_bin_end,[]}} -> - [S|As]; - #k_bin_seg{seg=S,next=N} -> - [S,N|As]; - #k_bin_int{next=N} -> - [N|As]; - #k_map{op=exact,es=Es} -> - Vals = [V || #k_map_pair{val=V} <- Es], - Vals ++ As; - _Other -> - As - end, - Vs = arg_alias(Arg), - Osub1 = foldl(fun (#k_var{name=V}, Acc) -> - subst_vsub(V, U#k_var.name, Acc) - end, Osub0, Vs), - Isub1 = foldl(fun (#k_var{name=V}, Acc) -> - subst_vsub(V, U#k_var.name, Acc) - end, Isub0, Vs), - C#iclause{isub=Isub1,osub=Osub1,pats=Head} - end, Cs0), - {Cs1,St}. - -%% group and squeeze -%% The goal of those functions is to group subsequent integer k_bin_seg -%% literals by count so we can leverage bs_get_integer_16 whenever possible. -%% -%% The priority is to create large groups. So if we have three clauses matching -%% on 16-bits/16-bits/8-bits, we will first have a single 8-bits match for all -%% three clauses instead of clauses (one with 16 and another with 8). But note -%% the algorithm is recursive, so the remaining 8-bits for the first two clauses -%% will be grouped next. -%% -%% We also try to not create too large groups. If we have too many clauses, -%% it is preferable to match on 8-bits, select a branch, then match on the -%% next 8-bits, rather than match on 16-bits which would force us to have -%% to select to many values at the same time, which would not be efficient. -%% -%% Another restriction is that we create groups only if the end of the -%% group is a variadic clause or the end of the binary. That's because -%% if we have 16-bits/16-bits/catch-all, breaking it into a 16-bits lookup -%% will make the catch-all more expensive. -%% -%% Clauses are grouped in reverse when squeezing and then flattened and -%% re-reversed at the end. -squeeze_clauses_by_bin_integer_count([Clause | Clauses], Acc) -> - case clause_count_bin_integer_segments(Clause) of - {literal, N} -> squeeze_clauses_by_bin_integer_count(Clauses, N, 1, [Clause], Acc); - _ -> squeeze_clauses_by_bin_integer_count(Clauses, [[Clause] | Acc]) - end; -squeeze_clauses_by_bin_integer_count(_, Acc) -> - flat_reverse(Acc, []). - -squeeze_clauses_by_bin_integer_count([], N, Count, GroupAcc, Acc) -> - Squeezed = squeeze_clauses(GroupAcc, fix_count_without_variadic_segment(N), Count), - flat_reverse([Squeezed | Acc], []); -squeeze_clauses_by_bin_integer_count([#iclause{pats=[#k_bin_end{} | _]} = Clause], N, Count, GroupAcc, Acc) -> - Squeezed = squeeze_clauses(GroupAcc, fix_count_without_variadic_segment(N), Count), - flat_reverse([[Clause | Squeezed] | Acc], []); -squeeze_clauses_by_bin_integer_count([Clause | Clauses], N, Count, GroupAcc, Acc) -> - case clause_count_bin_integer_segments(Clause) of - {literal, NewN} -> - squeeze_clauses_by_bin_integer_count(Clauses, min(N, NewN), Count + 1, [Clause | GroupAcc], Acc); - - {variadic, NewN} when NewN =< N -> - Squeezed = squeeze_clauses(GroupAcc, NewN, Count), - squeeze_clauses_by_bin_integer_count(Clauses, [[Clause | Squeezed] | Acc]); - - _ -> - squeeze_clauses_by_bin_integer_count(Clauses, [[Clause | GroupAcc] | Acc]) - end. - -clause_count_bin_integer_segments(#iclause{pats=[#k_bin_seg{seg=#k_literal{}} = BinSeg | _]}) -> - count_bin_integer_segments(BinSeg, 0); -clause_count_bin_integer_segments(#iclause{pats=[#k_bin_seg{size=#k_literal{val=Size},unit=Unit, - type=integer,flags=[unsigned,big], - seg=#k_var{}} | _]}) - when ((Size * Unit) rem 8) =:= 0 -> - {variadic, (Size * Unit) div 8}; -clause_count_bin_integer_segments(_) -> - error. - -count_bin_integer_segments(#k_bin_seg{size=#k_literal{val=8},unit=1,type=integer,flags=[unsigned,big], - seg=#k_literal{val=Int},next=Next}, Count) - when is_integer(Int), 0 =< Int, Int =< 255 -> - count_bin_integer_segments(Next, Count + 1); -count_bin_integer_segments(_, Count) when Count > 0 -> - {literal, Count}; -count_bin_integer_segments(_, _Count) -> - error. - -%% Since 4 bytes in on 32-bits systems are bignums, we convert -%% anything more than 3 into 2 bytes lookup. The goal is to convert -%% any multi-clause segment into 2-byte lookups with a potential -%% 3 byte lookup at the end. -fix_count_without_variadic_segment(N) when N > 3 -> 2; -fix_count_without_variadic_segment(N) -> N. - -%% If we have more than 16 clauses, then it is better -%% to branch multiple times than getting a large integer. -%% We also abort if we have nothing to squeeze. -squeeze_clauses(Clauses, Size, Count) when Count >= 16; Size =< 1 -> Clauses; -squeeze_clauses(Clauses, Size, _Count) -> - squeeze_clauses(Clauses, Size). - -squeeze_clauses([#iclause{pats=[#k_bin_seg{seg=#k_literal{}} = BinSeg | Pats]} = Clause | Clauses], Size) -> - [Clause#iclause{pats=[squeeze_segments(BinSeg, 0, 0, Size) | Pats]} | - squeeze_clauses(Clauses, Size)]; -squeeze_clauses([], _Size) -> - []. - -squeeze_segments(#k_bin_seg{size=Sz, seg=#k_literal{val=Val}=Lit} = BinSeg, Acc, Size, 1) -> - BinSeg#k_bin_seg{size=Sz#k_literal{val=Size + 8}, seg=Lit#k_literal{val=(Acc bsl 8) bor Val}}; -squeeze_segments(#k_bin_seg{seg=#k_literal{val=Val},next=Next}, Acc, Size, Count) -> - squeeze_segments(Next, (Acc bsl 8) bor Val, Size + 8, Count - 1); -squeeze_segments(#k_bin_end{}, Acc, Size, Count) -> - error({Acc,Size,Count}). - - -flat_reverse([Head | Tail], Acc) -> flat_reverse(Tail, flat_reverse_1(Head, Acc)); -flat_reverse([], Acc) -> Acc. - -flat_reverse_1([Head | Tail], Acc) -> flat_reverse_1(Tail, [Head | Acc]); -flat_reverse_1([], Acc) -> Acc. - -%% build_guard([GuardClause]) -> GuardExpr. - -build_guard([]) -> fail; -build_guard(Cs) -> #k_guard{clauses=Cs}. - -%% build_select(Var, [ConClause]) -> SelectExpr. - -build_select(V, [Tc|_]=Tcs) -> - copy_anno(#k_select{var=V,types=Tcs}, Tc). - -%% build_alt(First, Then) -> AltExpr. -%% Build an alt, attempt some simple optimisation. - -build_alt(fail, Then) -> Then; -build_alt(First,Then) -> build_alt_1st_no_fail(First, Then). - -build_alt_1st_no_fail(First, fail) -> First; -build_alt_1st_no_fail(First, Then) -> - copy_anno(#k_alt{first=First,then=Then}, First). - -%% build_match(MatchExpr) -> Kexpr. -%% Build a match expr if there is a match. - -build_match(#k_alt{}=Km) -> copy_anno(#k_match{body=Km}, Km); -build_match(#k_select{}=Km) -> copy_anno(#k_match{body=Km}, Km); -build_match(#k_guard{}=Km) -> copy_anno(#k_match{body=Km}, Km); -build_match(Km) -> Km. - -%% clause_arg(Clause) -> FirstArg. -%% clause_con(Clause) -> Constructor. -%% clause_val(Clause) -> Value. -%% is_var_clause(Clause) -> boolean(). - -clause_arg(#iclause{pats=[Arg|_]}) -> Arg. - -clause_con(C) -> arg_con(clause_arg(C)). - -clause_val(C) -> arg_val(clause_arg(C), C). - -is_var_clause(C) -> clause_con(C) =:= k_var. - -%% arg_arg(Arg) -> Arg. -%% arg_alias(Arg) -> Aliases. -%% arg_con(Arg) -> Constructor. -%% arg_val(Arg) -> Value. -%% These are the basic functions for obtaining fields in an argument. - -arg_arg(#ialias{pat=Con}) -> Con; -arg_arg(Con) -> Con. - -arg_alias(#ialias{vars=As}) -> As; -arg_alias(_Con) -> []. - -arg_con(Arg) -> - case arg_arg(Arg) of - #k_cons{} -> k_cons; - #k_tuple{} -> k_tuple; - #k_map{} -> k_map; - #k_binary{} -> k_binary; - #k_bin_end{} -> k_bin_end; - #k_bin_seg{} -> k_bin_seg; - #k_var{} -> k_var; - #k_literal{val=[]} -> k_nil; - #k_literal{val=Val} -> - if - is_atom(Val) -> k_atom; - is_integer(Val) -> k_int; - is_float(Val) -> k_float; - true -> k_literal - end - end. - -arg_val(Arg, C) -> - case arg_arg(Arg) of - #k_literal{val=Lit} -> Lit; - #k_tuple{es=Es} -> length(Es); - #k_bin_seg{size=S,unit=U,type=T,flags=Fs} -> - case S of - #k_var{name=V} -> - #iclause{isub=Isub} = C, - {#k_var{name=get_vsub(V, Isub)},U,T,Fs}; - _ -> - {set_kanno(S, []),U,T,Fs} - end; - #k_map{op=exact,es=Es} -> - sort(fun(A,B) -> - %% on the form K :: {'lit' | 'var', term()} - %% lit < var as intended - erts_internal:cmp_term(A,B) < 0 - end, [map_key_clean(Key) || #k_map_pair{key=Key} <- Es]) - end. - -%% ubody_used_vars(Expr, State) -> [UsedVar] -%% Return all used variables for the body sequence. Much more -%% efficient than using ubody/3 if the body contains nested letrecs. -ubody_used_vars(Expr, St) -> - {_,Used,_} = ubody(Expr, return, St#kern{funs=ignore}), - Used. - -%% ubody(Expr, Break, State) -> {Expr,[UsedVar],State}. -%% Tag the body sequence with its used variables. These bodies -%% either end with a #k_break{}, or with #k_return{} or an expression -%% which itself can return, #k_enter{}, #k_match{} ... . - -ubody(#iset{vars=[],arg=#iletrec{}=Let,body=B0}, Br, St0) -> - %% An iletrec{} should never be last. - St = iletrec_funs(Let, St0), - ubody(B0, Br, St); -ubody(#iset{vars=[],arg=#k_literal{},body=B0}, Br, St0) -> - ubody(B0, Br, St0); -ubody(#iset{anno=A,vars=Vs,arg=E0,body=B0}, Br, St0) -> - {E1,Eu,St1} = uexpr(E0, {break,Vs}, St0), - {B1,Bu,St2} = ubody(B0, Br, St1), - Ns = lit_list_vars(Vs), - Used = union(Eu, subtract(Bu, Ns)), %Used external vars - {#k_seq{anno=A,arg=E1,body=B1},Used,St2}; -ubody(#ivalues{anno=A,args=As}, return, St) -> - Au = lit_list_vars(As), - {#k_return{anno=A,args=As},Au,St}; -ubody(#ivalues{anno=A,args=As}, {break,_Vbs}, St) -> - Au = lit_list_vars(As), - {#k_break{anno=A,args=As},Au,St}; -ubody(#k_goto{args=As}=Goto, _Br, St) -> - Au = lit_list_vars(As), - {Goto,Au,St}; -ubody(E, return, St0) -> - %% Enterable expressions need no trailing return. - case is_enter_expr(E) of - true -> uexpr(E, return, St0); - false -> - {Ea,Pa,St1} = force_atomic(E, St0), - ubody(pre_seq(Pa, #ivalues{args=[Ea]}), return, St1) - end; -ubody(E, {break,[_]} = Break, St0) -> - {Ea,Pa,St1} = force_atomic(E, St0), - ubody(pre_seq(Pa, #ivalues{args=[Ea]}), Break, St1); -ubody(E, {break,Rs}=Break, St0) -> - {Vs,St1} = new_vars(length(Rs), St0), - Iset = #iset{vars=Vs,arg=E}, - PreSeq = pre_seq([Iset], #ivalues{args=Vs}), - ubody(PreSeq, Break, St1). - -iletrec_funs(#iletrec{defs=Fs}, St0) -> - %% Use union of all free variables. - %% First just work out free variables for all functions. - Free = foldl(fun ({_,#ifun{vars=Vs,body=Fb0}}, Free0) -> - Fbu = ubody_used_vars(Fb0, St0), - Ns = lit_list_vars(Vs), - Free1 = subtract(Fbu, Ns), - union(Free1, Free0) - end, [], Fs), - FreeVs = make_vars(Free), - %% Add this free info to State. - St1 = foldl(fun ({N,#ifun{vars=Vs}}, Lst) -> - store_free(N, length(Vs), FreeVs, Lst) - end, St0, Fs), - iletrec_funs_gen(Fs, FreeVs, St1). - -%% Now regenerate local functions to use free variable information. -iletrec_funs_gen(_, _, #kern{funs=ignore}=St) -> - %% Optimization: The ultimate caller is only interested in the used variables, - %% not the updated state. Makes a difference if there are nested letrecs. - St; -iletrec_funs_gen(Fs, FreeVs, St) -> - foldl(fun ({N,#ifun{anno=Fa,vars=Vs,body=Fb0}}, Lst0) -> - Arity0 = length(Vs), - {Fb1,_,Lst1} = ubody(Fb0, return, Lst0), - Arity = Arity0 + length(FreeVs), - Fun = make_fdef(Fa, N, Arity, Vs++FreeVs, Fb1), - Lst1#kern{funs=[Fun|Lst1#kern.funs]} - end, St, Fs). - - -%% is_enter_expr(Kexpr) -> boolean(). -%% Test whether Kexpr is "enterable", i.e. can handle return from -%% within itself without extra #k_return{}. - -is_enter_expr(#k_try{}) -> true; -is_enter_expr(#k_call{}) -> true; -is_enter_expr(#k_match{}) -> true; -is_enter_expr(#k_letrec_goto{}) -> true; -is_enter_expr(_) -> false. - -%% uexpr(Expr, Break, State) -> {Expr,[UsedVar],State}. -%% Calculate the used variables for an expression. -%% Break = return | {break,[RetVar]}. - -uexpr(#k_test{anno=A,op=Op,args=As}=Test, {break,Rs}, St) -> - [] = Rs, %Sanity check - Used = union(op_vars(Op), lit_list_vars(As)), - {Test#k_test{anno=A},Used,St}; -uexpr(#iset{anno=A,vars=Vs,arg=E0,body=B0}, {break,_}=Br, St0) -> - Ns = lit_list_vars(Vs), - {E1,Eu,St1} = uexpr(E0, {break,Vs}, St0), - {B1,Bu,St2} = uexpr(B0, Br, St1), - Used = union(Eu, subtract(Bu, Ns)), - {#k_seq{anno=A,arg=E1,body=B1},Used,St2}; -uexpr(#k_call{anno=A,op=#k_local{name=F,arity=Ar}=Op,args=As0}=Call, Br, St) -> - Free = get_free(F, Ar, St), - As1 = As0 ++ Free, %Add free variables LAST! - Used = lit_list_vars(As1), - {case Br of - {break,Rs} -> - Call#k_call{anno=A, - op=Op#k_local{arity=Ar + length(Free)}, - args=As1,ret=Rs}; - return -> - #k_enter{anno=A, - op=Op#k_local{arity=Ar + length(Free)}, - args=As1} - end,Used,St}; -uexpr(#k_call{anno=A,op=Op,args=As}=Call, {break,Rs}, St) -> - Used = union(op_vars(Op), lit_list_vars(As)), - {Call#k_call{anno=A,ret=Rs},Used,St}; -uexpr(#k_call{anno=A,op=Op,args=As}, return, St) -> - Used = union(op_vars(Op), lit_list_vars(As)), - {#k_enter{anno=A,op=Op,args=As},Used,St}; -uexpr(#k_bif{anno=A,op=Op,args=As}=Bif, {break,Rs}, St0) -> - Used = union(op_vars(Op), lit_list_vars(As)), - {Brs,St1} = bif_returns(Op, Rs, St0), - {Bif#k_bif{anno=A,ret=Brs},Used,St1}; -uexpr(#k_match{anno=A,body=B0}, Br, St0) -> - Rs = break_rets(Br), - {B1,Bu,St1} = umatch(B0, Br, St0), - {#k_match{anno=A,body=B1,ret=Rs},Bu,St1}; -uexpr(#k_try{anno=A,arg=A0,vars=Vs,body=B0,evars=Evs,handler=H0}, - {break,Rs0}=Br, St0) -> - case {Vs,B0,H0,Rs0} of - {[#k_var{name=X}],#k_var{name=X},#k_literal{},[]} -> - %% This is a simple try/catch whose return value is - %% ignored: - %% - %% try E of V -> V when _:_:_ -> ignored_literal end, ... - %% - %% This is most probably a try/catch in a guard. To - %% correctly handle the #k_test{} that ends the body of - %% the guard, we MUST pass an empty list of break - %% variables when processing the body. - {A1,Bu,St} = ubody(A0, {break,[]}, St0), - {#k_try{anno=A,arg=A1,vars=[],body=#k_break{}, - evars=[],handler=#k_break{},ret=Rs0}, - Bu,St}; - {_,_,_,_} -> - %% The general try/catch (in a guard or in body). - {Avs,St1} = new_vars(length(Vs), St0), - {A1,Au,St2} = ubody(A0, {break,Avs}, St1), - {B1,Bu,St3} = ubody(B0, Br, St2), - {H1,Hu,St4} = ubody(H0, Br, St3), - Used = union([Au,subtract(Bu, lit_list_vars(Vs)), - subtract(Hu, lit_list_vars(Evs))]), - {#k_try{anno=A,arg=A1,vars=Vs,body=B1,evars=Evs,handler=H1,ret=Rs0}, - Used,St4} - end; -uexpr(#k_try{anno=A,arg=A0,vars=Vs,body=B0,evars=Evs,handler=H0}, - return, St0) -> - {Avs,St1} = new_vars(length(Vs), St0), %Need dummy names here - {A1,Au,St2} = ubody(A0, {break,Avs}, St1), %Must break to clean up here! - {B1,Bu,St3} = ubody(B0, return, St2), - {H1,Hu,St4} = ubody(H0, return, St3), - Used = union([Au,subtract(Bu, lit_list_vars(Vs)), - subtract(Hu, lit_list_vars(Evs))]), - {#k_try_enter{anno=A,arg=A1,vars=Vs,body=B1,evars=Evs,handler=H1}, - Used,St4}; -uexpr(#k_catch{anno=A,body=B0}, {break,Rs0}, St0) -> - {Rb,St1} = new_var(St0), - {B1,Bu,St2} = ubody(B0, {break,[Rb]}, St1), - %% Guarantee ONE return variable. - {Ns,St3} = new_vars(1 - length(Rs0), St2), - Rs1 = Rs0 ++ Ns, - {#k_catch{anno=A,body=B1,ret=Rs1},Bu,St3}; -uexpr(#ifun{anno=A,vars=Vs,body=B0}, {break,Rs}, St0) -> - {B1,Bu,St1} = ubody(B0, return, St0), %Return out of new function - Ns = lit_list_vars(Vs), - Free = subtract(Bu, Ns), %Free variables in fun - Fvs = make_vars(Free), - Arity = length(Vs) + length(Free), - {Fname,St} = - case keyfind(id, 1, A) of - {id,{_,_,Fname0}} -> - {Fname0,St1}; - false -> - %% No id annotation. Must invent a fun name. - new_fun_name(St1) - end, - Fun = make_fdef(A, Fname, Arity, Vs++Fvs, B1), - Local = #k_local{name=Fname,arity=Arity}, - {#k_bif{anno=A, - op=#k_internal{name=make_fun,arity=length(Free)+2}, - args=[Local|Fvs], - ret=Rs}, - Free,add_local_function(Fun, St)}; -uexpr(#k_local{anno=A,name=Name,arity=Arity}, {break,Rs}, St) -> - Free = lit_list_vars(get_free(Name, Arity, St)), - Fvs = make_vars(Free), - FreeCount = length(Fvs), - Bif = #k_bif{anno=A, - op=#k_internal{name=make_fun,arity=FreeCount+1}, - args=[#k_local{name=Name,arity=Arity+FreeCount} | Fvs], - ret=Rs}, - {Bif,Free,St}; -uexpr(#k_letrec_goto{anno=A,vars=Vs,first=F0,then=T0}=MatchAlt, Br, St0) -> - Rs = break_rets(Br), - Ns = lit_list_vars(Vs), - {F1,Fu,St1} = ubody(F0, Br, St0), - {T1,Tu,St2} = ubody(T0, Br, St1), - Used = subtract(union(Fu, Tu), Ns), - {MatchAlt#k_letrec_goto{anno=A,first=F1,then=T1,ret=Rs},Used,St2}; -uexpr(#k_opaque{}=O, _, St) -> - {O,[],St}; -uexpr(Lit, {break,Rs0}, St0) -> - %% Transform literals to puts here. - %%ok = io:fwrite("uexpr ~w:~p~n", [?LINE,Lit]), - Used = lit_vars(Lit), - {Rs,St1} = ensure_return_vars(Rs0, St0), - {#k_put{anno=get_kanno(Lit),arg=Lit,ret=Rs},Used,St1}. - -add_local_function(_, #kern{funs=ignore}=St) -> - St; -add_local_function(#k_fdef{func=Name,arity=Arity}=F, #kern{funs=Funs}=St) -> - case is_defined(Name, Arity, Funs) of - false -> - St#kern{funs=[F|Funs]}; - true -> - St - end. - -is_defined(Name, Arity, [#k_fdef{func=Name,arity=Arity}|_]) -> - true; -is_defined(Name, Arity, [#k_fdef{}|T]) -> - is_defined(Name, Arity, T); -is_defined(_, _, []) -> false. - -%% Make a #k_fdef{}, making sure that the body is always a #k_match{}. -make_fdef(Anno, Name, Arity, Vs, #k_match{}=Body) -> - #k_fdef{anno=Anno,func=Name,arity=Arity,vars=Vs,body=Body}; -make_fdef(Anno, Name, Arity, Vs, Body) -> - Ka = get_kanno(Body), - Match = #k_match{anno=Ka,body=Body,ret=[]}, - #k_fdef{anno=Anno,func=Name,arity=Arity,vars=Vs,body=Match}. - -%% get_free(Name, Arity, State) -> [Free]. -%% store_free(Name, Arity, [Free], State) -> State. - -get_free(F, A, #kern{free=FreeMap}) -> - Key = {F,A}, - case FreeMap of - #{Key:=Val} -> Val; - _ -> [] - end. - -store_free(F, A, Free, #kern{free=FreeMap0}=St) -> - Key = {F,A}, - FreeMap = FreeMap0#{Key=>Free}, - St#kern{free=FreeMap}. - -break_rets({break,Rs}) -> Rs; -break_rets(return) -> []. - -%% bif_returns(Op, [Ret], State) -> {[Ret],State}. - -bif_returns(#k_internal{name=match_fail}, Rs, St) -> - %% This is only used for effect, and may have any number of returns. - {Rs,St}; -bif_returns(#k_internal{name=N,arity=Ar}, Rs, St0) -> - %%ok = io:fwrite("uexpr ~w:~p~n", [?LINE,{N,Ar,Rs}]), - {Ns,St1} = new_vars(bif_vals(N, Ar) - length(Rs), St0), - {Rs ++ Ns,St1}; -bif_returns(#k_remote{mod=M,name=N,arity=Ar}, Rs, St0) -> - %%ok = io:fwrite("uexpr ~w:~p~n", [?LINE,{M,N,Ar,Rs}]), - {Ns,St1} = new_vars(bif_vals(M, N, Ar) - length(Rs), St0), - {Rs ++ Ns,St1}. - -%% ensure_return_vars([Ret], State) -> {[Ret],State}. - -ensure_return_vars([], St) -> new_vars(1, St); -ensure_return_vars([_]=Rs, St) -> {Rs,St}. - -%% umatch(Match, Break, State) -> {Match,[UsedVar],State}. -%% Calculate the used variables for a match expression. - -umatch(#k_alt{anno=A,first=F0,then=T0}, Br, St0) -> - {F1,Fu,St1} = umatch(F0, Br, St0), - {T1,Tu,St2} = umatch(T0, Br, St1), - Used = union(Fu, Tu), - {#k_alt{anno=A,first=F1,then=T1},Used,St2}; -umatch(#k_select{anno=A,var=V,types=Ts0}, Br, St0) -> - {Ts1,Tus,St1} = umatch_list(Ts0, Br, St0), - Used = add_element(V#k_var.name, Tus), - {#k_select{anno=A,var=V,types=Ts1},Used,St1}; -umatch(#k_type_clause{anno=A,type=T,values=Vs0}, Br, St0) -> - {Vs1,Vus,St1} = umatch_list(Vs0, Br, St0), - {#k_type_clause{anno=A,type=T,values=Vs1},Vus,St1}; -umatch(#k_val_clause{anno=A,val=P0,body=B0}, Br, St0) -> - {U0,Ps} = pat_vars(P0), - {B1,Bu,St1} = umatch(B0, Br, St0), - P = pat_anno_unused(P0, Bu, Ps), - Used = union(U0, subtract(Bu, Ps)), - {#k_val_clause{anno=A,val=P,body=B1},Used,St1}; -umatch(#k_guard{anno=A,clauses=Gs0}, Br, St0) -> - {Gs1,Gus,St1} = umatch_list(Gs0, Br, St0), - {#k_guard{anno=A,clauses=Gs1},Gus,St1}; -umatch(#k_guard_clause{anno=A,guard=G0,body=B0}, Br, St0) -> - {G1,Gu,St1} = uexpr(G0, {break,[]}, St0), - {B1,Bu,St2} = umatch(B0, Br, St1), - Used = union(Gu, Bu), - {#k_guard_clause{anno=A,guard=G1,body=B1},Used,St2}; -umatch(B0, Br, St0) -> ubody(B0, Br, St0). - -umatch_list(Ms0, Br, St) -> - foldr(fun (M0, {Ms1,Us,Sta}) -> - {M1,Mu,Stb} = umatch(M0, Br, Sta), - {[M1|Ms1],union(Mu, Us),Stb} - end, {[],[],St}, Ms0). - -pat_anno_unused(#k_tuple{es=Es0}=P, Used0, Ps) -> - %% Not extracting unused tuple elements is an optimization for - %% compile time and memory use during compilation. It is probably - %% worthwhile because it is common to extract only a few elements - %% from a huge record. - Used = intersection(Used0, Ps), - Es = [case member(V, Used) of - true -> Var; - false -> set_kanno(Var, [unused|get_kanno(Var)]) - end || #k_var{name=V}=Var <- Es0], - P#k_tuple{es=Es}; -pat_anno_unused(P, _Used, _Ps) -> P. - -%% op_vars(Op) -> [VarName]. - -op_vars(#k_remote{mod=Mod,name=Name}) -> - ordsets:from_list([V || #k_var{name=V} <- [Mod,Name]]); -op_vars(#k_internal{}) -> []; -op_vars(Atomic) -> lit_vars(Atomic). - -%% lit_vars(Literal) -> [VarName]. -%% Return the variables in a literal. - -lit_vars(#k_var{name=N}) -> [N]; -%%lit_vars(#k_char{}) -> []; -lit_vars(#k_cons{hd=H,tl=T}) -> - union(lit_vars(H), lit_vars(T)); -lit_vars(#k_map{var=Var,es=Es}) -> - lit_list_vars([Var|Es]); -lit_vars(#k_map_pair{key=K,val=V}) -> - union(lit_vars(K), lit_vars(V)); -lit_vars(#k_binary{segs=V}) -> lit_vars(V); -lit_vars(#k_bin_end{}) -> []; -lit_vars(#k_bin_seg{size=Size,seg=S,next=N}) -> - union(lit_vars(Size), union(lit_vars(S), lit_vars(N))); -lit_vars(#k_tuple{es=Es}) -> - lit_list_vars(Es); -lit_vars(#k_literal{}) -> []; -lit_vars(#k_opaque{}) -> []. - -lit_list_vars(Ps) -> - foldl(fun (P, Vs) -> union(lit_vars(P), Vs) end, [], Ps). - -%% pat_vars(Pattern) -> {[UsedVarName],[NewVarName]}. -%% Return variables in a pattern. All variables are new variables -%% except those in the size field of binary segments and the key -%% field in map_pairs. - -pat_vars(#k_var{name=N}) -> {[],[N]}; -%%pat_vars(#k_char{}) -> {[],[]}; -pat_vars(#k_literal{}) -> {[],[]}; -pat_vars(#k_cons{hd=H,tl=T}) -> - pat_list_vars([H,T]); -pat_vars(#k_binary{segs=V}) -> - pat_vars(V); -pat_vars(#k_bin_seg{size=Size,seg=S,next=N}) -> - {U1,New} = pat_list_vars([S,N]), - {[],U2} = pat_vars(Size), - {union(U1, U2),New}; -pat_vars(#k_bin_int{size=Size,next=N}) -> - {[],New} = pat_vars(N), - {[],U} = pat_vars(Size), - {U,New}; -pat_vars(#k_bin_end{}) -> {[],[]}; -pat_vars(#k_tuple{es=Es}) -> - pat_list_vars(Es); -pat_vars(#k_map{es=Es}) -> - pat_list_vars(Es); -pat_vars(#k_map_pair{key=K,val=V}) -> - {U1,New} = pat_vars(V), - {[], U2} = pat_vars(K), - {union(U1,U2),New}. - -pat_list_vars(Ps) -> - foldl(fun (P, {Used0,New0}) -> - {Used,New} = pat_vars(P), - {union(Used0, Used),union(New0, New)} end, - {[],[]}, Ps). - -%% List of integers in interval [N,M]. Empty list if N > M. - -integers(N, M) when N =< M -> - [N|integers(N + 1, M)]; -integers(_, _) -> []. - -%%% -%%% Handling of errors and warnings. -%%% - --type error() :: {'failed' | 'nomatch', term()}. - --spec format_error(error()) -> string(). - -format_error({nomatch,{shadow,Line}}) -> - M = io_lib:format("this clause cannot match because a previous clause at line ~p " - "always matches", [Line]), - flatten(M); -format_error({nomatch,shadow}) -> - "this clause cannot match because a previous clause always matches"; -format_error({failed,bad_call}) -> - "invalid module and/or function name; this call will always fail"; -format_error({failed,bad_segment_size}) -> - "binary construction will fail because the size of a segment is invalid". - -add_warning(none, Term, Anno, #kern{ws=Ws}=St) -> - File = get_file(Anno), - St#kern{ws=[{File,[{none,?MODULE,Term}]}|Ws]}; -add_warning(Line, Term, Anno, #kern{ws=Ws}=St) -> - File = get_file(Anno), - St#kern{ws=[{File,[{Line,?MODULE,Term}]}|Ws]}. - -is_compiler_generated(Ke) -> - Anno = get_kanno(Ke), - member(compiler_generated, Anno). diff --git a/lib/compiler/src/v3_kernel.hrl b/lib/compiler/src/v3_kernel.hrl deleted file mode 100644 index 5259a73418..0000000000 --- a/lib/compiler/src/v3_kernel.hrl +++ /dev/null @@ -1,76 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1999-2023. 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 : Kernel Erlang as records. - -%% It would be nice to incorporate some generic functions as well but -%% this could make including this file difficult. -%% N.B. the annotation field is ALWAYS the first field! - -%% Literals -%% NO CHARACTERS YET. -%%-record(k_char, {anno=[],val}). --record(k_literal, {anno=[],val}). - --record(k_tuple, {anno=[],es}). --record(k_map, {anno=[],var=#k_literal{val=#{}},op,es}). --record(k_map_pair, {anno=[],key,val}). --record(k_cons, {anno=[],hd,tl}). --record(k_binary, {anno=[],segs}). --record(k_bin_seg, {anno=[],size,unit,type,flags,seg,next}). --record(k_bin_int, {anno=[],size,unit,flags,val,next}). --record(k_bin_end, {anno=[]}). --record(k_var, {anno=[],name}). - --record(k_local, {anno=[],name,arity}). --record(k_remote, {anno=[],mod,name,arity}). --record(k_internal, {anno=[],name,arity}). - --record(k_mdef, {anno=[],name,exports,attributes,body}). --record(k_fdef, {anno=[],func,arity,vars,body}). - --record(k_seq, {anno=[],arg,body}). --record(k_put, {anno=[],arg,ret=[]}). --record(k_bif, {anno=[],op,args,ret=[]}). --record(k_test, {anno=[],op,args}). --record(k_call, {anno=[],op,args,ret=[]}). --record(k_enter, {anno=[],op,args}). --record(k_try, {anno=[],arg,vars,body,evars,handler,ret=[]}). --record(k_try_enter, {anno=[],arg,vars,body,evars,handler}). --record(k_catch, {anno=[],body,ret=[]}). - --record(k_letrec_goto, {anno=[],label,vars=[],first,then,ret=[]}). --record(k_goto, {anno=[],label,args=[]}). - --record(k_match, {anno=[],body,ret=[]}). --record(k_alt, {anno=[],first,then}). --record(k_select, {anno=[],var,types}). --record(k_type_clause, {anno=[],type,values}). --record(k_val_clause, {anno=[],val,body}). --record(k_guard, {anno=[],clauses}). --record(k_guard_clause, {anno=[],guard,body}). - --record(k_break, {anno=[],args=[]}). --record(k_return, {anno=[],args=[]}). - --record(k_opaque, {anno=[],val}). - -%%k_get_anno(Thing) -> element(2, Thing). -%%k_set_anno(Thing, Anno) -> setelement(2, Thing, Anno). diff --git a/lib/compiler/src/v3_kernel_pp.erl b/lib/compiler/src/v3_kernel_pp.erl deleted file mode 100644 index fa8c67b6af..0000000000 --- a/lib/compiler/src/v3_kernel_pp.erl +++ /dev/null @@ -1,509 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1999-2023. 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 : Kernel Erlang (naive) prettyprinter - --module(v3_kernel_pp). - --export([format/1]). - -%%-define(INCLUDE_ANNOTATIONS, 1). - --include("v3_kernel.hrl"). - -%% These are "internal" structures in sys_kernel which are here for -%% debugging purposes. --record(iset, {anno=[],vars,arg,body}). --record(ifun, {anno=[],vars,body}). - -%% ====================================================================== %% -%% format(Node) -> Text -%% Node = coreErlang() -%% Text = string() | [Text] -%% -%% Prettyprint-formats (naively) an abstract Core Erlang syntax -%% tree. - --record(ctxt, {indent = 0 :: non_neg_integer(), - item_indent = 2 :: non_neg_integer(), - body_indent = 2 :: non_neg_integer(), - tab_width = 8 :: non_neg_integer()}). - -canno(Cthing) -> element(2, Cthing). - --spec format(#k_mdef{}) -> iolist(). - -format(Node) -> format(Node, #ctxt{}). - -format(Node, Ctxt) -> - case canno(Node) of - [] -> - format_1(Node, Ctxt); - [L,{file,_}] when is_integer(L) -> - format_1(Node, Ctxt); - [{L,C},{file,_}] when is_integer(L), is_integer(C) -> - format_1(Node, Ctxt); - List -> - format_anno(List, Ctxt, fun (Ctxt1) -> - format_1(Node, Ctxt1) - end) - end. - - --ifndef(INCLUDE_ANNOTATIONS). -%% Don't include annotations (for readability). -format_anno(_Anno, Ctxt, ObjFun) -> - ObjFun(Ctxt). --else. -%% Include annotations (for debugging of annotations). -format_anno(Anno, Ctxt0, ObjFun) -> - Ctxt1 = ctxt_bump_indent(Ctxt0, 1), - ["( ", - ObjFun(Ctxt0), - nl_indent(Ctxt1), - "-| ",io_lib:write(Anno), - " )"]. --endif. - -%% format_1(Kexpr, Context) -> string(). - -%%format_1(#k_char{val=C}, _Ctxt) -> io_lib:write_char(C); -format_1(#k_var{name=V}, _Ctxt) -> - if is_atom(V) -> - case atom_to_list(V) of - [$_|Cs] -> "_X" ++ Cs; - [C|_Cs] = L when C >= $A, C =< $Z -> L; - Cs -> [$_|Cs] - end; - is_integer(V) -> [$_|integer_to_list(V)] - end; -format_1(#k_cons{hd=H,tl=T}, Ctxt) -> - Txt = ["["|format(H, ctxt_bump_indent(Ctxt, 1))], - [Txt|format_list_tail(T, ctxt_bump_indent(Ctxt, width(Txt, Ctxt)))]; -format_1(#k_tuple{es=Es}, Ctxt) -> - [${, - format_hseq(Es, ",", ctxt_bump_indent(Ctxt, 1), fun format/2), - $} - ]; -format_1(#k_map{var=#k_literal{val=M},op=assoc,es=Es}, Ctxt) when is_map(M), map_size(M) =:= 0 -> - ["~{", - format_hseq(Es, ",", ctxt_bump_indent(Ctxt, 1), fun format/2), - "}~" - ]; -format_1(#k_map{var=#k_literal{val=M},op=exact,es=Es}, Ctxt) when is_map(M), map_size(M) =:= 0 -> - ["::{", - format_hseq(Es, ",", ctxt_bump_indent(Ctxt, 1), fun format/2), - "}::" - ]; -format_1(#k_map{var=Var,op=assoc,es=Es}, Ctxt) -> - ["~{", - format_hseq(Es, ",", ctxt_bump_indent(Ctxt, 1), fun format/2), - " | ",format_1(Var, Ctxt), - "}~" - ]; -format_1(#k_map{var=Var,op=exact,es=Es}, Ctxt) -> - ["::{", - format_hseq(Es, ",", ctxt_bump_indent(Ctxt, 1), fun format/2), - " | ",format_1(Var, Ctxt), - "}::" - ]; -format_1(#k_map_pair{key=K,val=V}, Ctxt) -> - ["<",format(K, Ctxt),",",format(V, Ctxt),">"]; -format_1(#k_binary{segs=S}, Ctxt) -> - ["#<",format(S, ctxt_bump_indent(Ctxt, 2)),">#"]; -format_1(#k_bin_seg{next=Next}=S, Ctxt) -> - [format_bin_seg_1(S, Ctxt), - format_bin_seg(Next, ctxt_bump_indent(Ctxt, 2))]; -format_1(#k_bin_int{size=Sz,unit=U,flags=Fs,val=Val,next=Next}, Ctxt) -> - S = #k_bin_seg{size=Sz,unit=U,type=integer,flags=Fs, - seg=#k_literal{val=Val},next=Next}, - [format_bin_seg_1(S, Ctxt), - format_bin_seg(Next, ctxt_bump_indent(Ctxt, 2))]; -format_1(#k_bin_end{}, _Ctxt) -> "#<>#"; -format_1(#k_literal{val=A}, _Ctxt) when is_atom(A) -> - core_atom(A); -format_1(#k_literal{val=Term}, _Ctxt) -> - io_lib:format("~kp", [Term]); -format_1(#k_local{name=N,arity=A}, Ctxt) -> - "local " ++ format_fa_pair({N,A}, Ctxt); -format_1(#k_remote{mod=M,name=N,arity=A}, _Ctxt) -> - %% This is for our internal translator. - io_lib:format("remote ~ts:~ts/~w", [format(M),format(N),A]); -format_1(#k_internal{name=N,arity=A}, Ctxt) -> - "internal " ++ format_fa_pair({N,A}, Ctxt); -format_1(#k_seq{arg=A,body=B}, Ctxt) -> - Ctxt1 = ctxt_bump_indent(Ctxt, 2), - ["do", - nl_indent(Ctxt1), - format(A, Ctxt1), - nl_indent(Ctxt), - "then", - nl_indent(Ctxt) - | format(B, Ctxt) - ]; -format_1(#k_match{body=Bs,ret=Rs}, Ctxt) -> - Ctxt1 = ctxt_bump_indent(Ctxt, Ctxt#ctxt.item_indent), - ["match", - nl_indent(Ctxt1), - format(Bs, Ctxt1), - nl_indent(Ctxt), - "end", - format_ret(Rs, Ctxt1) - ]; -format_1(#k_alt{first=O,then=T}, Ctxt) -> - Ctxt1 = ctxt_bump_indent(Ctxt, Ctxt#ctxt.item_indent), - ["alt", - nl_indent(Ctxt1), - format(O, Ctxt1), - nl_indent(Ctxt1), - format(T, Ctxt1)]; -format_1(#k_letrec_goto{label=Label,vars=Vs,first=First,then=Then,ret=Rs}, Ctxt) -> - Ctxt1 = ctxt_bump_indent(Ctxt, Ctxt#ctxt.item_indent), - ["letrec_goto ", - atom_to_list(Label), - format_args(Vs, Ctxt), - nl_indent(Ctxt1), - format(Then, Ctxt1), - nl_indent(Ctxt1), - format(First, Ctxt1), - nl_indent(Ctxt), - "end", - format_ret(Rs, Ctxt1) - ]; -format_1(#k_goto{label=Label,args=As}, Ctxt) -> - ["goto ",atom_to_list(Label),format_args(As, Ctxt)]; -format_1(#k_select{var=V,types=Cs}, Ctxt) -> - Ctxt1 = ctxt_bump_indent(Ctxt, 2), - ["select ", - format(V, Ctxt), - nl_indent(Ctxt1), - format_vseq(Cs, "", "", Ctxt1, fun format/2) - ]; -format_1(#k_type_clause{type=T,values=Cs}, Ctxt) -> - Ctxt1 = ctxt_bump_indent(Ctxt, Ctxt#ctxt.body_indent), - ["type ", - io_lib:write(T), - nl_indent(Ctxt1), - format_vseq(Cs, "", "", Ctxt1, fun format/2) - ]; -format_1(#k_val_clause{val=Val,body=B}, Ctxt) -> - Ctxt1 = ctxt_bump_indent(Ctxt, Ctxt#ctxt.body_indent), - [format(Val, Ctxt), - " ->", - nl_indent(Ctxt1) - | format(B, Ctxt1) - ]; -format_1(#k_guard{clauses=Gs}, Ctxt) -> - Ctxt1 = ctxt_bump_indent(Ctxt, 5), - ["when ", - nl_indent(Ctxt1), - format_vseq(Gs, "", "", Ctxt1, fun format/2)]; -format_1(#k_guard_clause{guard=G,body=B}, Ctxt) -> - Ctxt1 = ctxt_bump_indent(Ctxt, Ctxt#ctxt.body_indent), - [format(G, Ctxt), - nl_indent(Ctxt), - "->", - nl_indent(Ctxt1) - | format(B, Ctxt1) - ]; -format_1(#k_call{op=Op,args=As,ret=Rs}, Ctxt) -> - Txt = ["call (",format(Op, ctxt_bump_indent(Ctxt, 6)),$)], - Ctxt1 = ctxt_bump_indent(Ctxt, 2), - [Txt,format_args(As, Ctxt1), - format_ret(Rs, Ctxt1) - ]; -format_1(#k_enter{op=Op,args=As}, Ctxt) -> - Txt = ["enter (",format(Op, ctxt_bump_indent(Ctxt, 7)),$)], - Ctxt1 = ctxt_bump_indent(Ctxt, 2), - [Txt,format_args(As, Ctxt1)]; -format_1(#k_bif{op=Op,args=As,ret=Rs}, Ctxt) -> - Txt = ["bif (",format(Op, ctxt_bump_indent(Ctxt, 5)),$)], - Ctxt1 = ctxt_bump_indent(Ctxt, 2), - [Txt,format_args(As, Ctxt1), - format_ret(Rs, Ctxt1) - ]; -format_1(#k_test{op=Op,args=As}, Ctxt) -> - Txt = ["test (",format(Op, ctxt_bump_indent(Ctxt, 6)),$)], - Ctxt1 = ctxt_bump_indent(Ctxt, 2), - [Txt,format_args(As, Ctxt1)]; -format_1(#k_put{arg=A,ret=Rs}, Ctxt) -> - [format(A, Ctxt), - format_ret(Rs, ctxt_bump_indent(Ctxt, 1)) - ]; -format_1(#k_try{arg=A,vars=Vs,body=B,evars=Evs,handler=H,ret=Rs}, Ctxt) -> - Ctxt1 = ctxt_bump_indent(Ctxt, Ctxt#ctxt.body_indent), - ["try", - nl_indent(Ctxt1), - format(A, Ctxt1), - nl_indent(Ctxt), - "of ", - format_hseq(Vs, ", ", ctxt_bump_indent(Ctxt, 3), fun format/2), - nl_indent(Ctxt1), - format(B, Ctxt1), - nl_indent(Ctxt), - "catch ", - format_hseq(Evs, ", ", ctxt_bump_indent(Ctxt, 6), fun format/2), - nl_indent(Ctxt1), - format(H, Ctxt1), - nl_indent(Ctxt), - "end", - format_ret(Rs, Ctxt) - ]; -format_1(#k_try_enter{arg=A,vars=Vs,body=B,evars=Evs,handler=H}, Ctxt) -> - Ctxt1 = ctxt_bump_indent(Ctxt, Ctxt#ctxt.body_indent), - ["try_enter", - nl_indent(Ctxt1), - format(A, Ctxt1), - nl_indent(Ctxt), - "of ", - format_hseq(Vs, ", ", ctxt_bump_indent(Ctxt, 3), fun format/2), - nl_indent(Ctxt1), - format(B, Ctxt1), - nl_indent(Ctxt), - "catch ", - format_hseq(Evs, ", ", ctxt_bump_indent(Ctxt, 6), fun format/2), - nl_indent(Ctxt1), - format(H, Ctxt1), - nl_indent(Ctxt), - "end" - ]; -format_1(#k_catch{body=B,ret=Rs}, Ctxt) -> - Ctxt1 = ctxt_bump_indent(Ctxt, Ctxt#ctxt.body_indent), - ["catch", - nl_indent(Ctxt1), - format(B, Ctxt1), - nl_indent(Ctxt), - "end", - format_ret(Rs, Ctxt1) - ]; -format_1(#k_break{args=As}, Ctxt) -> - ["<", - format_hseq(As, ",", ctxt_bump_indent(Ctxt, 1), fun format/2), - ">" - ]; -format_1(#k_return{args=As}, Ctxt) -> - ["<<", - format_hseq(As, ",", ctxt_bump_indent(Ctxt, 1), fun format/2), - ">>" - ]; -format_1(#k_fdef{func=F,arity=A,vars=Vs,body=B}, Ctxt) -> - Ctxt1 = ctxt_bump_indent(Ctxt, Ctxt#ctxt.body_indent), - ["fdef ", - format_fa_pair({F,A}, ctxt_bump_indent(Ctxt, 5)), - format_args(Vs, ctxt_bump_indent(Ctxt, 14)), - " =", - nl_indent(Ctxt1), - format(B, Ctxt1) - ]; -format_1(#k_mdef{name=N,exports=Es,attributes=As,body=B}, Ctxt) -> - ["module ", - format(#k_literal{val=N}, ctxt_bump_indent(Ctxt, 7)), - nl_indent(Ctxt), - "export [", - format_vseq(Es, - "", ",", - ctxt_bump_indent(Ctxt, 8), - fun format_fa_pair/2), - "]", - nl_indent(Ctxt), - "attributes [", - format_vseq(As, - "", ",", - ctxt_bump_indent(Ctxt, 12), - fun format_attribute/2), - "]", - nl_indent(Ctxt), - format_vseq(B, - "", "", - Ctxt, - fun format/2), - nl_indent(Ctxt) - | "end" - ]; -%% Internal sys_kernel structures. -format_1(#iset{vars=Vs,arg=A,body=B}, Ctxt) -> - Ctxt1 = ctxt_bump_indent(Ctxt, Ctxt#ctxt.body_indent), - ["set <", - format_hseq(Vs, ", ", ctxt_bump_indent(Ctxt, 5), fun format/2), - "> =", - nl_indent(Ctxt1), - format(A, Ctxt1), - nl_indent(Ctxt), - "in " - | format(B, ctxt_bump_indent(Ctxt, 2)) - ]; -format_1(#ifun{vars=Vs,body=B}, Ctxt) -> - Ctxt1 = ctxt_bump_indent(Ctxt, Ctxt#ctxt.body_indent), - ["fun ", - format_args(Vs, ctxt_bump_indent(Ctxt, 4)), - " ->", - nl_indent(Ctxt1) - | format(B, Ctxt1) - ]; -format_1(#k_opaque{val=V}, _Ctxt) -> - ["** Opaque: ", io_lib:write(V), " **\n"]; -format_1(Type, _Ctxt) -> - ["** Unsupported type: ", - io_lib:write(Type) - | " **" - ]. - -%% format_ret([RetVar], Context) -> Txt. -%% Format the return vars of kexpr. - -format_ret(Rs, Ctxt) -> - [" >> ", - "<", - format_hseq(Rs, ",", ctxt_bump_indent(Ctxt, 5), fun format/2), - ">"]. - -%% format_args([Arg], Context) -> Txt. -%% Format arguments. - -format_args(As, Ctxt) -> - [$(,format_hseq(As, ", ", ctxt_bump_indent(Ctxt, 1), fun format/2),$)]. - -%% format_hseq([Thing], Separator, Context, Fun) -> Txt. -%% Format a sequence horizontally. - -format_hseq([H], _Sep, Ctxt, Fun) -> - Fun(H, Ctxt); -format_hseq([H|T], Sep, Ctxt, Fun) -> - Txt = [Fun(H, Ctxt)|Sep], - Ctxt1 = ctxt_bump_indent(Ctxt, width(Txt, Ctxt)), - [Txt|format_hseq(T, Sep, Ctxt1, Fun)]; -format_hseq([], _, _, _) -> "". - -%% format_vseq([Thing], LinePrefix, LineSuffix, Context, Fun) -> Txt. -%% Format a sequence vertically. - -format_vseq([H], _Pre, _Suf, Ctxt, Fun) -> - Fun(H, Ctxt); -format_vseq([H|T], Pre, Suf, Ctxt, Fun) -> - [Fun(H, Ctxt),Suf,nl_indent(Ctxt),Pre| - format_vseq(T, Pre, Suf, Ctxt, Fun)]; -format_vseq([], _, _, _, _) -> "". - -format_fa_pair({F,A}, _Ctxt) -> [core_atom(F),$/,integer_to_list(A)]. - -%% format_attribute({Name,Val}, Context) -> Txt. - -format_attribute({Name,Val}, Ctxt) when is_list(Val) -> - Txt = format(#k_literal{val=Name}, Ctxt), - Ctxt1 = ctxt_bump_indent(Ctxt, width(Txt,Ctxt)+4), - [Txt," = ", - $[,format_vseq(Val, "", ",", Ctxt1, - fun (A, _C) -> io_lib:write(A) end),$] - ]; -format_attribute({Name,Val}, Ctxt) -> - Txt = format(#k_literal{val=Name}, Ctxt), - [Txt," = ",io_lib:write(Val)]. - -format_list_tail(#k_literal{anno=[],val=[]}, _Ctxt) -> "]"; -format_list_tail(#k_cons{anno=[],hd=H,tl=T}, Ctxt) -> - Txt = [$,|format(H, Ctxt)], - Ctxt1 = ctxt_bump_indent(Ctxt, width(Txt, Ctxt)), - [Txt|format_list_tail(T, Ctxt1)]; -format_list_tail(Tail, Ctxt) -> - ["|",format(Tail, ctxt_bump_indent(Ctxt, 1)), "]"]. - -format_bin_seg([], _Ctx) -> ""; -format_bin_seg(#k_bin_end{anno=[]}, _Ctxt) -> ""; -format_bin_seg(#k_bin_seg{anno=[],next=N}=Seg, Ctxt) -> - Txt = [$,|format_bin_seg_1(Seg, Ctxt)], - [Txt|format_bin_seg(N, ctxt_bump_indent(Ctxt, width(Txt, Ctxt)))]; -format_bin_seg(Seg, Ctxt) -> - ["|",format(Seg, ctxt_bump_indent(Ctxt, 2))]. - -format_bin_seg_1(#k_bin_seg{size=S,unit=U,type=T,flags=Fs,seg=Seg}, Ctxt) -> - [format(Seg, Ctxt), - ":",format(S, Ctxt),"*",io_lib:write(U), - ":",io_lib:write(T), - [[$-,io_lib:write(F)] || F <- Fs] - ]. - -% format_bin_elements(#k_binary_cons{hd=H,tl=T,size=S,info=I}, Ctxt) -> -% A = canno(T), -% Fe = fun (Eh, Es, Ei, Ct) -> -% [format(Eh, Ct),":",format(Es, Ct),"/",io_lib:write(Ei)] -% end, -% case T of -% #k_zero_binary{} when A == [] -> -% Fe(H, S, I, Ctxt); -% #k_binary_cons{} when A == [] -> -% Txt = [Fe(H, S, I, Ctxt)|","], -% Ctxt1 = ctxt_bump_indent(Ctxt, width(Txt, Ctxt)), -% [Txt|format_bin_elements(T, Ctxt1)]; -% _ -> -% Txt = [Fe(H, S, I, Ctxt)|"|"], -% [Txt|format(T, ctxt_bump_indent(Ctxt, width(Txt, Ctxt)))] -% end. - -indent(Ctxt) -> indent(Ctxt#ctxt.indent, Ctxt). - -indent(N, _Ctxt) when N =< 0 -> ""; -indent(N, Ctxt) -> - T = Ctxt#ctxt.tab_width, - lists:duplicate(N div T, $\t) ++ lists:duplicate(N rem T, $\s). - -nl_indent(Ctxt) -> [$\n|indent(Ctxt)]. - - -unindent(T, Ctxt) -> - unindent(T, Ctxt#ctxt.indent, Ctxt, []). - -unindent(T, N, _Ctxt, C) when N =< 0 -> - [T|C]; -unindent([$\s|T], N, Ctxt, C) -> - unindent(T, N - 1, Ctxt, C); -unindent([$\t|T], N, Ctxt, C) -> - Tab = Ctxt#ctxt.tab_width, - if N >= Tab -> - unindent(T, N - Tab, Ctxt, C); - true -> - unindent([lists:duplicate(Tab - N, $\s)|T], 0, Ctxt, C) - end; -unindent([L|T], N, Ctxt, C) when is_list(L) -> - unindent(L, N, Ctxt, [T|C]); -unindent([H|T], _N, _Ctxt, C) -> - [H|[T|C]]; -unindent([], N, Ctxt, [H|T]) -> - unindent(H, N, Ctxt, T); -unindent([], _, _, []) -> []. - - -width(Txt, Ctxt) -> - width(Txt, 0, Ctxt, []). - -width([$\t|T], A, Ctxt, C) -> - width(T, A + Ctxt#ctxt.tab_width, Ctxt, C); -width([$\n|T], _A, Ctxt, C) -> - width(unindent([T|C], Ctxt), Ctxt); -width([H|T], A, Ctxt, C) when is_list(H) -> - width(H, A, Ctxt, [T|C]); -width([_|T], A, Ctxt, C) -> - width(T, A + 1, Ctxt, C); -width([], A, Ctxt, [H|T]) -> - width(H, A, Ctxt, T); -width([], A, _, []) -> A. - -ctxt_bump_indent(Ctxt, Dx) -> - Ctxt#ctxt{indent=Ctxt#ctxt.indent + Dx}. - -core_atom(A) -> io_lib:write_string(atom_to_list(A), $'). diff --git a/lib/compiler/test/beam_ssa_check_SUITE_data/sanity_checks.erl b/lib/compiler/test/beam_ssa_check_SUITE_data/sanity_checks.erl index 47c60fd8d6..a97c00359b 100644 --- a/lib/compiler/test/beam_ssa_check_SUITE_data/sanity_checks.erl +++ b/lib/compiler/test/beam_ssa_check_SUITE_data/sanity_checks.erl @@ -244,7 +244,7 @@ t32(X) -> t33(X) -> %ssa% (X) when post_ssa_opt -> %ssa% A = bif:'=='(X, 1), -%ssa% br(A, 5, 4). +%ssa% br(A, 9, 8). true = X == 1. %% Check that we handle a branch and variable labels diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl index 7731871beb..a3a5a40f52 100644 --- a/lib/compiler/test/compile_SUITE.erl +++ b/lib/compiler/test/compile_SUITE.erl @@ -30,7 +30,7 @@ debug_info/4, custom_debug_info/1, custom_compile_info/1, file_1/1, forms_2/1, module_mismatch/1, outdir/1, binary/1, makedep/1, cond_and_ifdef/1, listings/1, listings_big/1, - other_output/1, kernel_listing/1, encrypted_abstr/1, + other_output/1, encrypted_abstr/1, strict_record/1, utf8_atoms/1, utf8_functions/1, extra_chunks/1, cover/1, env/1, core_pp/1, tuple_calls/1, core_roundtrip/1, asm/1, asm_labels/1, @@ -52,7 +52,7 @@ all() -> [app_test, appup_test, bigE_roundtrip, file_1, forms_2, module_mismatch, outdir, binary, makedep, cond_and_ifdef, listings, listings_big, - other_output, kernel_listing, encrypted_abstr, tuple_calls, + other_output, encrypted_abstr, tuple_calls, strict_record, utf8_atoms, utf8_functions, extra_chunks, cover, env, core_pp, core_roundtrip, asm, asm_labels, no_core_prepare, sys_pre_attributes, dialyzer, warnings, pre_load_check, @@ -519,7 +519,6 @@ do_file_listings(DataDir, PrivDir, [File|Files]) -> {dcore, ".core"}, {dcopt, ".copt"}, {dcbsm, ".core_bsm"}, - {dkern, ".kernel"}, {dssa, ".ssa"}, {dbool, ".bool"}, {dssashare, ".ssashare"}, @@ -539,7 +538,6 @@ do_file_listings(DataDir, PrivDir, [File|Files]) -> do_listing(Simple, TargetDir, to_core0, ".core"), ok = file:delete(filename:join(TargetDir, File ++ ".core")), do_listing(Simple, TargetDir, to_core, ".core"), - do_listing(Simple, TargetDir, to_kernel, ".kernel"), do_listing(Simple, TargetDir, to_dis, ".dis"), %% Final clean up. @@ -555,7 +553,6 @@ listings_big(Config) when is_list(Config) -> List = [{'S',".S"}, {'E',".E"}, {'P',".P"}, - {dkern, ".kernel"}, {dssa, ".ssa"}, {dssaopt, ".ssaopt"}, {dprecg, ".precodegen"}, @@ -610,12 +607,6 @@ other_output(Config) when is_list(Config) -> io:put_chars("to_core (forms)"), {ok,simple,Core} = compile:forms(PP, [to_core,binary,time]), - io:put_chars("to_kernel (file)"), - {ok,simple,Kernel} = compile:file(Simple, [to_kernel,binary,time]), - k_mdef = element(1, Kernel), - io:put_chars("to_kernel (forms)"), - {ok,simple,Kernel} = compile:forms(PP, [to_kernel,binary,time]), - io:put_chars("to_asm (file)"), {ok,simple,Asm} = compile:file(Simple, [to_asm,binary,time]), {simple,_,_,_,_} = Asm, @@ -624,33 +615,6 @@ other_output(Config) when is_list(Config) -> ok. -%% Smoke test and cover of pretty-printing of Kernel code. -kernel_listing(_Config) -> - TestBeams = get_unique_beam_files(), - Abstr = [begin {ok,{Mod,[{abstract_code, - {raw_abstract_v1,Abstr}}]}} = - beam_lib:chunks(Beam, [abstract_code]), - {Mod,Abstr} end || Beam <- TestBeams], - test_lib:p_run(fun(F) -> do_kernel_listing(F) end, Abstr). - -do_kernel_listing({M,A}) -> - try - {ok,M,Kern} = compile:forms(A, [to_kernel]), - IoList = v3_kernel_pp:format(Kern), - case unicode:characters_to_binary(IoList) of - Bin when is_binary(Bin) -> - ok - end - catch - throw:{error,Error} -> - io:format("*** compilation failure '~p' for module ~s\n", - [Error,M]), - error; - Class:Error:Stk -> - io:format("~p: ~p ~p\n~p\n", [M,Class,Error,Stk]), - error - end. - encrypted_abstr(Config) when is_list(Config) -> {Simple,Target} = get_files(Config, simple, "encrypted_abstr"), diff --git a/lib/compiler/test/misc_SUITE.erl b/lib/compiler/test/misc_SUITE.erl index 37fb3854de..a91c1bb296 100644 --- a/lib/compiler/test/misc_SUITE.erl +++ b/lib/compiler/test/misc_SUITE.erl @@ -179,24 +179,14 @@ silly_coverage(Config) when is_list(Config) -> {function,0,foo,2,[bad_clauses]}], expect_error(fun() -> v3_core:module(BadAbstr, []) end), - %% sys_core_fold, sys_core_alias, sys_core_bsm, v3_kernel + %% sys_core_fold, sys_core_alias, sys_core_bsm, beam_core_to_ssa BadCoreErlang = {c_module,[], - name,[],[], + {c_literal,[],name},[],[], [{{c_var,[],{foo,2}},seriously_bad_body}]}, expect_error(fun() -> sys_core_fold:module(BadCoreErlang, []) end), expect_error(fun() -> sys_core_alias:module(BadCoreErlang, []) end), expect_error(fun() -> sys_core_bsm:module(BadCoreErlang, []) end), - expect_error(fun() -> v3_kernel:module(BadCoreErlang, []) end), - - %% beam_kernel_to_ssa - BadKernel = {k_mdef,[],?MODULE, - [{foo,0}], - [], - [{k_fdef, - {k,[],[],[]}, - f,0,[], - seriously_bad_body}]}, - expect_error(fun() -> beam_kernel_to_ssa:module(BadKernel, []) end), + expect_error(fun() -> beam_core_to_ssa:module(BadCoreErlang, []) end), %% beam_ssa_lint %% beam_ssa_bool @@ -294,6 +284,7 @@ cover_beam_ssa_bc_size(N) -> cover_beam_ssa_bc_size(N + 1). bad_ssa_lint_input() -> + Ret = {b_var,100}, {b_module,#{},t, [{a,1},{b,1},{c,1},{module_info,0},{module_info,1}], [], @@ -328,14 +319,14 @@ bad_ssa_lint_input() -> #{0 => {b_blk,#{}, [{b_set,#{}, - {b_var,{'@ssa_ret',3}}, + Ret, call, [{b_remote, {b_literal,erlang}, {b_literal,get_module_info}, 1}, {b_var,'@unknown_variable'}]}], - {b_ret,#{},{b_var,{'@ssa_ret',3}}}}}, + {b_ret,#{},Ret}}}, 4}]}. expect_error(Fun) -> diff --git a/lib/compiler/test/record_SUITE.erl b/lib/compiler/test/record_SUITE.erl index a098529380..db8e3de3d9 100644 --- a/lib/compiler/test/record_SUITE.erl +++ b/lib/compiler/test/record_SUITE.erl @@ -270,10 +270,16 @@ record_test_3(Config) when is_list(Config) -> false = is_record(#foo{}, barf, 5), false = is_record(#foo{}, barf, 6), false = is_record({foo}, foo, 5), + false = is_record({foo}, foo, -1), + false = is_record(id({foo}), foo, -1), true = erlang:is_record(#foo{}, foo, 5), + true = erlang:is_record(#foo{}, id(foo), 5), false = erlang:is_record(#foo{}, barf, 5), false = erlang:is_record({foo}, foo, 5), + false = erlang:is_record({foo}, foo, -1), + false = erlang:is_record(id({foo}), foo, -1), + false = erlang:is_record({foo}, id(foo), -1), false = is_record([], foo), false = is_record(Config, foo), diff --git a/lib/compiler/test/warnings_SUITE.erl b/lib/compiler/test/warnings_SUITE.erl index df45210982..94f706f228 100644 --- a/lib/compiler/test/warnings_SUITE.erl +++ b/lib/compiler/test/warnings_SUITE.erl @@ -110,7 +110,7 @@ pattern(Config) when is_list(Config) -> pattern2(Config) when is_list(Config) -> %% Test warnings generated by sys_core_fold. %% If we disable Core Erlang optimizations, we expect that - %% v3_kernel should generate some of the warnings. + %% beam_core_to_ssa should generate some of the warnings. Source = <<"f(A) -> ok; f(B) -> error. t(A, B, C) -> @@ -137,21 +137,21 @@ pattern2(Config) when is_list(Config) -> ]}}], [] = run(Config, Ts), - %% Disable Core Erlang optimizations. v3_kernel should produce + %% Disable Core Erlang optimizations. beam_core_to_ssa should produce %% a warning for the clause that didn't match. Ts2 = [{pattern2, Source, [nowarn_unused_vars,no_copt], {warnings, - [{{2,17},v3_kernel,{nomatch,{shadow,1}}}, - {{11,21},v3_kernel,{nomatch,{shadow,10}}} + [{{2,17},beam_core_to_ssa,{nomatch,{shadow,1}}}, + {{11,21},beam_core_to_ssa,{nomatch,{shadow,10}}} ]}}], [] = run(Config, Ts2), ok. pattern3(Config) when is_list(Config) -> %% Test warnings generated by the pattern matching compiler - %% in v3_kernel. + %% in beam_core_to_ssa. Ts = [{pattern3, <<" @@ -165,8 +165,8 @@ pattern3(Config) when is_list(Config) -> ">>, [nowarn_unused_vars], {warnings, - [{{4,13},v3_kernel,{nomatch,{shadow,2}}}, - {{8,13},v3_kernel,{nomatch,{shadow,6}}}]}}], + [{{4,13},beam_core_to_ssa,{nomatch,{shadow,2}}}, + {{8,13},beam_core_to_ssa,{nomatch,{shadow,6}}}]}}], [] = run(Config, Ts), ok. @@ -350,11 +350,11 @@ bad_apply(Config) when is_list(Config) -> ">>, [], {warnings, - [{{2,22},v3_kernel,{failed,bad_call}}, - {{3,22},v3_kernel,{failed,bad_call}}, - {{4,22},v3_kernel,{failed,bad_call}}, - {{5,22},v3_kernel,{failed,bad_call}}, - {{6,22},v3_kernel,{failed,bad_call}}, + [{{2,22},beam_core_to_ssa,{failed,bad_call}}, + {{3,22},beam_core_to_ssa,{failed,bad_call}}, + {{4,22},beam_core_to_ssa,{failed,bad_call}}, + {{5,22},beam_core_to_ssa,{failed,bad_call}}, + {{6,22},beam_core_to_ssa,{failed,bad_call}}, {{7,22},sys_core_fold,{failed,bad_call}} ]}}], [] = run(Config, Ts), @@ -689,7 +689,7 @@ bin_construction(Config) when is_list(Config) -> {{10,21},v3_core,{failed,bad_binary}}, {{11,21},sys_core_fold,{failed,bad_unicode}}, {{12,21},sys_core_fold,{failed,bad_float_size}}, - {{16,18},v3_kernel,{failed,bad_segment_size}} + {{16,18},beam_core_to_ssa,{failed,bad_segment_size}} ]}}], [] = run(Config, Ts), @@ -1177,7 +1177,7 @@ opportunistic_warnings(Config) -> {warnings,[{{2,17},sys_core_fold,{nomatch,{shadow,1,{m,1}}}}, {{4,24},v3_core,{failed,bad_binary}}, {{5,45},sys_core_fold,{failed,{embedded_unit,8,28}}}, - {{6,43},v3_kernel,{failed,bad_segment_size}}, + {{6,43},beam_core_to_ssa,{failed,bad_segment_size}}, {{8,24},sys_core_fold,{ignored,useless_building}} ]}}], [] = run(Config, Ts1), @@ -1195,7 +1195,7 @@ opportunistic_warnings(Config) -> [nowarn_nomatch], {warnings,[{{4,24},v3_core,{failed,bad_binary}}, {{5,45},sys_core_fold,{failed,{embedded_unit,8,28}}}, - {{6,43},v3_kernel,{failed,bad_segment_size}}, + {{6,43},beam_core_to_ssa,{failed,bad_segment_size}}, {{8,24},sys_core_fold,{ignored,useless_building}} ]}}], [] = run(Config, Ts3), @@ -1216,7 +1216,7 @@ opportunistic_warnings(Config) -> {warnings,[{{2,17},sys_core_fold,{nomatch,{shadow,1,{m,1}}}}, {{4,24},v3_core,{failed,bad_binary}}, {{5,45},sys_core_fold,{failed,{embedded_unit,8,28}}}, - {{6,43},v3_kernel,{failed,bad_segment_size}} + {{6,43},beam_core_to_ssa,{failed,bad_segment_size}} ]}}], [] = run(Config, Ts5), diff --git a/lib/stdlib/test/qlc_SUITE.erl b/lib/stdlib/test/qlc_SUITE.erl index 2e1b722b8e..bc758cc308 100644 --- a/lib/stdlib/test/qlc_SUITE.erl +++ b/lib/stdlib/test/qlc_SUITE.erl @@ -366,7 +366,7 @@ nomatch(Config) when is_list(Config) -> end]). ">>, [], - {warnings,[{{5,24},v3_kernel,{nomatch,{shadow,4}}}]}}, + {warnings,[{{5,24},beam_core_to_ssa,{nomatch,{shadow,4}}}]}}, {nomatch1, <<"generator1() -> -- 2.35.3
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