diff --git a/erts/emulator/test/op_SUITE.erl b/erts/emulator/test/op_SUITE.erl index c623f0ff2012..d767a28986bb 100644 --- a/erts/emulator/test/op_SUITE.erl +++ b/erts/emulator/test/op_SUITE.erl @@ -1006,11 +1006,11 @@ combined_relops(_Config) -> {func_info,{atom,?FUNCTION_NAME},{atom,test},1}, {label,2}, {test,is_ge,{f,4}, - [{tr,{x,0},{t_integer,{0,1000}}}, + [{tr,{x,0},#beam_types:t_integer{elements={0,1000}}}, {integer,10}]}, {test,is_ge, {f,3}, - [{tr,{x,0},{t_integer,{0,1000}}}, + [{tr,{x,0},#beam_types:t_integer{elements={0,1000}}}, {integer,5}]}, {label,3}, {move,{atom,a},{x,0}}, diff --git a/erts/emulator/test/tuple_SUITE_data/get_two_tuple_elements.S b/erts/emulator/test/tuple_SUITE_data/get_two_tuple_elements.S index cdfbbf6f23a6..976fec9b9bdc 100644 --- a/erts/emulator/test/tuple_SUITE_data/get_two_tuple_elements.S +++ b/erts/emulator/test/tuple_SUITE_data/get_two_tuple_elements.S @@ -48,118 +48,182 @@ {function, get_two_tuple_elements, 0, 2}. {label,1}. - {line,[{location,"get_two_tuple_elements.erl",4}]}. + {line,[{location,"erts/emulator/test/tuple_SUITE_data/get_two_tuple_elements.erl", + 26}]}. {func_info,{atom,get_two_tuple_elements},{atom,get_two_tuple_elements},0}. {label,2}. {allocate,2,0}. {init_yregs,{list,[{y,0},{y,1}]}}. {move,{atom,x},{x,0}}. - {line,[{location,"get_two_tuple_elements.erl",5}]}. - {call,1,{f,9}}. - {'%',{var_info,{x,0}, - [{type,{t_union,{t_atom,[x]}, - none, - {t_integer,{1,2}}, - none,none,none}}]}}. + {line,[{location,"erts/emulator/test/tuple_SUITE_data/get_two_tuple_elements.erl", + 27}]}. + {call,1,{f,9}}. % id/1 + {'%', + {var_info, + {x,0}, + [{type, + #beam_types:t_union{ + atom = #beam_types:t_atom{elements = [x]}, + list = none, + number = #beam_types:t_integer{elements = {1,2}}, + tuple_set = none,native_record_set = none, + other = none}}]}}. {move,{x,0},{y,1}}. {move,{integer,1},{x,0}}. - {line,[{location,"get_two_tuple_elements.erl",5}]}. - {call,1,{f,9}}. - {'%',{var_info,{x,0}, - [{type,{t_union,{t_atom,[x]}, - none, - {t_integer,{1,2}}, - none,none,none}}]}}. + {call,1,{f,9}}. % id/1 + {'%', + {var_info, + {x,0}, + [{type, + #beam_types:t_union{ + atom = #beam_types:t_atom{elements = [x]}, + list = none, + number = #beam_types:t_integer{elements = {1,2}}, + tuple_set = none,native_record_set = none, + other = none}}]}}. {move,{x,0},{y,0}}. {move,{integer,2},{x,0}}. - {line,[{location,"get_two_tuple_elements.erl",5}]}. - {call,1,{f,9}}. - {'%',{var_info,{x,0}, - [{type,{t_union,{t_atom,[x]}, - none, - {t_integer,{1,2}}, - none,none,none}}]}}. + {call,1,{f,9}}. % id/1 + {'%', + {var_info, + {x,0}, + [{type, + #beam_types:t_union{ + atom = #beam_types:t_atom{elements = [x]}, + list = none, + number = #beam_types:t_integer{elements = {1,2}}, + tuple_set = none,native_record_set = none, + other = none}}]}}. {test_heap,7,1}. {put_tuple2,{x,0},{list,[{atom,x},{atom,y},{x,0}]}}. {put_tuple2,{x,1},{list,[{y,0},{x,0}]}}. {move,{y,1},{x,0}}. {trim,2,0}. - {line,[{location,"get_two_tuple_elements.erl",5}]}. - {call,2,{f,5}}. - {'%',{var_info,{x,0},[{type,{t_number,any}}]}}. - {test,is_eq_exact,{f,3},[{x,0},{integer,3}]}. + {call,2,{f,5}}. % xx/2 + {'%',{var_info,{x,0},[{type,#beam_types:t_integer{elements = {2,4}}}]}}. + {test,is_eq_exact, + {f,3}, + [{tr,{x,0},#beam_types:t_integer{elements = {2,4}}},{integer,3}]}. {move,{atom,ok},{x,0}}. {deallocate,0}. return. {label,3}. - {line,[{location,"get_two_tuple_elements.erl",5}]}. {badmatch,{x,0}}. {function, xx, 2, 5}. {label,4}. - {line,[{location,"get_two_tuple_elements.erl",8}]}. + {line,[{location,"erts/emulator/test/tuple_SUITE_data/get_two_tuple_elements.erl", + 30}]}. {func_info,{atom,get_two_tuple_elements},{atom,xx},2}. {label,5}. - {'%',{var_info,{x,0}, - [{type,{t_union,{t_atom,[x]}, - none, - {t_integer,{1,2}}, - none,none,none}}]}}. + {'%', + {var_info, + {x,0}, + [{type, + #beam_types:t_union{ + atom = #beam_types:t_atom{elements = [x]}, + list = none, + number = #beam_types:t_integer{elements = {1,2}}, + tuple_set = none,native_record_set = none, + other = none}}]}}. {'%', {var_info, {x,1}, [{type, - {t_tuple,2,true, - #{1 => {t_union,{t_atom,[x]},none,{t_integer,{1,2}},none,none,none}, - 2 => - {t_tuple,3,true, - #{1 => {t_atom,[x]}, - 2 => {t_atom,[y]}, - 3 => - {t_union, - {t_atom,[x]}, - none, - {t_integer,{1,2}}, - none,none,none}}}}}}]}}. + #beam_types:t_tuple{ + size = 2,exact = true, + elements = + #{1 => + #beam_types:t_union{ + atom = #beam_types:t_atom{elements = [x]}, + list = none, + number = #beam_types:t_integer{elements = {1,2}}, + tuple_set = none,native_record_set = none,other = none}, + 2 => + #beam_types:t_tuple{ + size = 3,exact = true, + elements = + #{1 => #beam_types:t_atom{elements = [x]}, + 2 => #beam_types:t_atom{elements = [y]}, + 3 => + #beam_types:t_union{ + atom = #beam_types:t_atom{elements = [x]}, + list = none, + number = #beam_types:t_integer{elements = {1,2}}, + tuple_set = none,native_record_set = none, + other = none}}}}}}]}}. {get_tuple_element,{x,1},0,{x,0}}. {get_tuple_element,{x,1},1,{x,1}}. {get_tuple_element,{x,1},2,{x,1}}. {move,nil,{x,2}}. - {call_only,3,{f,7}}. + {call_only,3,{f,7}}. % yy/3 {function, yy, 3, 7}. {label,6}. - {line,[{location,"get_two_tuple_elements.erl",17}]}. + {line,[{location,"erts/emulator/test/tuple_SUITE_data/get_two_tuple_elements.erl", + 39}]}. {func_info,{atom,get_two_tuple_elements},{atom,yy},3}. {label,7}. - {'%',{var_info,{x,0}, - [{type,{t_union,{t_atom,[x]}, - none, - {t_integer,{1,2}}, - none,none,none}}]}}. - {'%',{var_info,{x,1}, - [{type,{t_union,{t_atom,[x]}, - none, - {t_integer,{1,2}}, - none,none,none}}]}}. + {'%', + {var_info, + {x,0}, + [{type, + #beam_types:t_union{ + atom = #beam_types:t_atom{elements = [x]}, + list = none, + number = #beam_types:t_integer{elements = {1,2}}, + tuple_set = none,native_record_set = none, + other = none}}]}}. + {'%', + {var_info, + {x,1}, + [{type, + #beam_types:t_union{ + atom = #beam_types:t_atom{elements = [x]}, + list = none, + number = #beam_types:t_integer{elements = {1,2}}, + tuple_set = none,native_record_set = none, + other = none}}]}}. {'%',{var_info,{x,2},[{type,nil}]}}. - {line,[{location,"get_two_tuple_elements.erl",18}]}. - {gc_bif,'+',{f,0},2,[{x,0},{x,1}],{x,0}}. + {line,[{location,"erts/emulator/test/tuple_SUITE_data/get_two_tuple_elements.erl", + 40}]}. + {gc_bif,'+', + {f,0}, + 2, + [{tr,{x,0}, + #beam_types:t_union{ + atom = #beam_types:t_atom{elements = [x]}, + list = none, + number = #beam_types:t_integer{elements = {1,2}}, + tuple_set = none,native_record_set = none,other = none}}, + {tr,{x,1}, + #beam_types:t_union{ + atom = #beam_types:t_atom{elements = [x]}, + list = none, + number = #beam_types:t_integer{elements = {1,2}}, + tuple_set = none,native_record_set = none,other = none}}], + {x,0}}. return. {function, id, 1, 9}. {label,8}. - {line,[{location,"get_two_tuple_elements.erl",20}]}. + {line,[{location,"erts/emulator/test/tuple_SUITE_data/get_two_tuple_elements.erl", + 42}]}. {func_info,{atom,get_two_tuple_elements},{atom,id},1}. {label,9}. - {'%',{var_info,{x,0}, - [{type,{t_union,{t_atom,[x]}, - none, - {t_integer,{1,2}}, - none,none,none}}]}}. + {'%', + {var_info, + {x,0}, + [{type, + #beam_types:t_union{ + atom = #beam_types:t_atom{elements = [x]}, + list = none, + number = #beam_types:t_integer{elements = {1,2}}, + tuple_set = none,native_record_set = none, + other = none}}]}}. return. @@ -169,7 +233,6 @@ {func_info,{atom,get_two_tuple_elements},{atom,module_info},0}. {label,11}. {move,{atom,get_two_tuple_elements},{x,0}}. - {line,[]}. {call_ext_only,1,{extfunc,erlang,get_module_info,1}}. @@ -180,5 +243,4 @@ {label,13}. {move,{x,0},{x,1}}. {move,{atom,get_two_tuple_elements},{x,0}}. - {line,[]}. {call_ext_only,2,{extfunc,erlang,get_module_info,2}}. diff --git a/lib/compiler/src/Makefile b/lib/compiler/src/Makefile index 8a9c5194e612..cf7620731df8 100644 --- a/lib/compiler/src/Makefile +++ b/lib/compiler/src/Makefile @@ -114,7 +114,6 @@ HRL_FILES= \ beam_asm.hrl \ beam_disasm.hrl \ beam_ssa_alias_debug.hrl \ - beam_ssa_opt.hrl \ beam_ssa.hrl \ beam_types.hrl \ core_parse.hrl diff --git a/lib/compiler/src/beam_asm.hrl b/lib/compiler/src/beam_asm.hrl index 35d985743de9..94d835f09c79 100644 --- a/lib/compiler/src/beam_asm.hrl +++ b/lib/compiler/src/beam_asm.hrl @@ -43,4 +43,4 @@ %% %% To aid in the above, the validator will explode upon encountering them in an %% unfamiliar context. --record(tr, {r :: beam_reg(), t :: type()}). +-record(tr, {r :: beam_reg(), t :: beam_types:type()}). diff --git a/lib/compiler/src/beam_call_types.erl b/lib/compiler/src/beam_call_types.erl index deb789f3f3bb..94cf15d7f6f7 100644 --- a/lib/compiler/src/beam_call_types.erl +++ b/lib/compiler/src/beam_call_types.erl @@ -29,6 +29,9 @@ -export([will_succeed/3, types/3, arith_type/2]). +-type type() :: beam_types:type(). +-type normal_type() :: beam_types:normal_type(). + %% %% Define an upper limit for functions that return sizes of data %% structures. The chosen value is about half the maxium size of a diff --git a/lib/compiler/src/beam_core_to_ssa.erl b/lib/compiler/src/beam_core_to_ssa.erl index bb79aab90627..ece7d0d5de81 100644 --- a/lib/compiler/src/beam_core_to_ssa.erl +++ b/lib/compiler/src/beam_core_to_ssa.erl @@ -159,7 +159,7 @@ get_anno(#cg_select{anno=Anno}) -> Anno. ds :: sets:set(), %Defined variables funs=[], %Fun functions free=#{}, %Free variables - rec_defaults :: #{atom() => type()}, %Native records. + rec_defaults :: #{atom() => beam_types:type()}, %Native records. ws=[] :: [warning()], %Warnings. beam_debug_info=false :: boolean() }. @@ -168,7 +168,10 @@ get_anno(#cg_select{anno=Anno}) -> Anno. {'ok', #b_module{}, [warning()]}. module(#c_module{name=#c_literal{val=Mod},exports=Es,attrs=As,defs=Fs}, Options) -> - _ = beam_ssa:module_info(module), %Load modules with records. + %% Load modules with exported native records. + _ = beam_ssa:module_info(module), + _ = beam_ssa_opt:module_info(module), + _ = beam_types:module_info(module), Records = records(As), Anno = #{records => Records}, diff --git a/lib/compiler/src/beam_dict.erl b/lib/compiler/src/beam_dict.erl index 63d63f2441ba..d0af9ac4b5f2 100644 --- a/lib/compiler/src/beam_dict.erl +++ b/lib/compiler/src/beam_dict.erl @@ -246,7 +246,7 @@ fname(Name, #asm{fnames=Fnames}=Dict) -> {Index,Dict#asm{fnames=Fnames#{Name=>Index}}} end. --spec type(type(), bdict()) -> {non_neg_integer(), bdict()} | none. +-spec type(beam_types:type(), bdict()) -> {non_neg_integer(), bdict()} | none. type(Type, #asm{types=Types0}=Dict) -> ExtType = beam_types:encode_ext(Type), diff --git a/lib/compiler/src/beam_ssa.erl b/lib/compiler/src/beam_ssa.erl index 21b3907729b5..ea85f3b96ddf 100644 --- a/lib/compiler/src/beam_ssa.erl +++ b/lib/compiler/src/beam_ssa.erl @@ -135,7 +135,7 @@ -type var_name() :: atom() | non_neg_integer(). -type literal_value() :: atom() | integer() | float() | list() | - nil() | tuple() | map() | binary() | fun(). + nil() | tuple() | map() | binary() | fun() | record(). -type op() :: {'bif',atom()} | {'float',float_op()} | diff --git a/lib/compiler/src/beam_ssa_alias.erl b/lib/compiler/src/beam_ssa_alias.erl index 45877c965704..6e2dad8e223f 100644 --- a/lib/compiler/src/beam_ssa_alias.erl +++ b/lib/compiler/src/beam_ssa_alias.erl @@ -32,10 +32,17 @@ %% information. -define(MAX_REPETITIONS, 16). --include("beam_ssa_opt.hrl"). +-include("beam_ssa.hrl"). + -include("beam_types.hrl"). -include("beam_ssa_alias_debug.hrl"). +-type func_id() :: beam_ssa_opt:func_id(). +-type func_info_db() :: beam_ssa_opt:func_info_db(). +-type st_map() :: beam_ssa_opt:st_map(). + +-import_record(beam_ssa_opt, [func_info, opt_st]). + -ifdef(DEBUG_ALIAS). -define(DP(FMT, ARGS), io:format(FMT, ARGS)). -define(DP(FMT), io:format(FMT)). @@ -101,7 +108,7 @@ fn(#b_local{name=#b_literal{val=N},arity=A}) -> -type sharing_state() :: any(). % A graph --type type_db() :: #{ beam_ssa:b_var() := type() }. +-type type_db() :: #{ beam_ssa:b_var() := beam_types:type() }. %%% %%% Optimization pass which calculates the alias status of values and diff --git a/lib/compiler/src/beam_ssa_bc_size.erl b/lib/compiler/src/beam_ssa_bc_size.erl index 56ca653d4e00..1b19278c2fc7 100644 --- a/lib/compiler/src/beam_ssa_bc_size.erl +++ b/lib/compiler/src/beam_ssa_bc_size.erl @@ -42,8 +42,11 @@ -export([opt/1]). -import(lists, [any/2,member/2,reverse/1,sort/1]). +-import_record(beam_ssa_opt, [func_info, opt_st]). --include("beam_ssa_opt.hrl"). +-include("beam_ssa.hrl"). + +-type st_map() :: beam_ssa_opt:st_map(). -spec opt(st_map()) -> st_map(). diff --git a/lib/compiler/src/beam_ssa_check.erl b/lib/compiler/src/beam_ssa_check.erl index b917ca5bb9f0..7b2e02eefcc4 100644 --- a/lib/compiler/src/beam_ssa_check.erl +++ b/lib/compiler/src/beam_ssa_check.erl @@ -26,6 +26,7 @@ -import(lists, [reverse/1, flatten/1]). -include("beam_ssa.hrl"). +-include("beam_types.hrl"). %%-define(DEBUG, true). @@ -264,8 +265,12 @@ env_post({list,_,Elems}, #b_literal{val=Ls}, Env) -> post_list(Elems, Ls, Env); env_post({list,_,Elems}, Ls, Env) when is_list(Ls) -> post_list(Elems, Ls, Env); +env_post({tuple,_,Es}, #b_literal{val=Ls}, Env) when is_record(Ls) -> + post_tuple(Es, record_to_list(Ls), Env); env_post({tuple,_,Es}, #b_literal{val=Ls}, Env) -> post_tuple(Es, tuple_to_list(Ls), Env); +env_post({tuple,_,Es}, Tuple, Env) when is_record(Tuple) -> + post_tuple(Es, record_to_list(Tuple), Env); env_post({tuple,_,Es}, Tuple, Env) when is_tuple(Tuple) -> post_tuple(Es, tuple_to_list(Tuple), Env); env_post({map,_,Elems}, #b_literal{val=Map}, Env) when is_map(Map) -> @@ -323,11 +328,15 @@ post_tuple([Elem|Elements], [A|Actual], Env0) -> post_tuple([], [], Env) -> Env. -post_map([{Key,Val}|Items], Map, Env) -> - K = build_map_key(Key, Env), - V = build_map_key(Val, Env), - #{K := V} = Map, +record_to_list(R) -> + Name = records:get_name(R), + Fs = records:get_field_names(R), + [Name | [records:get(F, R) || F <- Fs]]. +post_map([{Key,Val}|Items], Map, Env0) -> + K = build_map_key(Key, Env0), + #{K := V} = Map, + Env = env_post(Val, V, Env0), post_map(Items, maps:remove(K, Map), Env); post_map([], Map, Env) -> 0 = maps:size(Map), diff --git a/lib/compiler/src/beam_ssa_destructive_update.erl b/lib/compiler/src/beam_ssa_destructive_update.erl index d9eb926d3253..c10fe0d5606c 100644 --- a/lib/compiler/src/beam_ssa_destructive_update.erl +++ b/lib/compiler/src/beam_ssa_destructive_update.erl @@ -96,7 +96,7 @@ -import(lists, [foldl/3, foldr/3, keysort/2, splitwith/2, reverse/1]). --include("beam_ssa_opt.hrl"). +-include("beam_ssa.hrl"). -include("beam_types.hrl"). %% -define(DEBUG, true). @@ -115,6 +115,11 @@ ff(#b_local{name=#b_literal{val=N},arity=A}) -> %% build get_hd and get_tuple_element chains before giving up. -define(ELEMENT_DEPTH_LIMIT, 15). +-import_record(beam_ssa_opt, [func_info, opt_st]). + +-type func_info_db() :: beam_ssa_opt:func_info_db(). +-type st_map() :: beam_ssa_opt:st_map(). + -spec opt(st_map(), func_info_db()) -> {st_map(), func_info_db()}. opt(StMap, FuncDb) -> %% Ignore functions which are not in the function db (never diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl index 0343597a0bd6..4f98c7ad9116 100644 --- a/lib/compiler/src/beam_ssa_opt.erl +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -43,7 +43,60 @@ -export([module/2]). --include("beam_ssa_opt.hrl"). +-include("beam_ssa.hrl"). + +-export_record([func_info, opt_st]). +-export_type([func_id/0, func_info_db/0, st_map/0]). + +-record #func_info{ + %% Local calls going in/out of this function. + in = [] :: ordsets:ordset(func_id()), + out = [] :: ordsets:ordset(func_id()), + + %% Whether the function is exported or not; some optimizations may + %% need to be suppressed if it is. + exported = true :: boolean(), + + %% The inferred types of each argument (as opposed to parameter), + %% indexed by call site. + %% + %% This is more effective than the naive approach of joining into a + %% "parameter_type" as we go as it lets us narrow parameter types + %% without having to visit all callers on each pass, which helps a lot + %% when dealing with co-recursive functions. + arg_types = [] :: list(arg_type_map()), + + %% The success types of this function, grouping return values by their + %% argument types at the time of return. + %% + %% This gives us more precise types than a naive join of all returned + %% values, as we can rule out the cases where the arguments are + %% incompatible with the ones we're passing. + %% + %% Note that the argument types are those seen on successful return, + %% they do not cover all types that are provided to the function. + succ_types = [] :: success_type_set()}. + +-type arg_key() :: {CallerId :: func_id(), + CallDst :: beam_ssa:b_var()}. +-type arg_type_map() :: #{ arg_key() => term() }. + +-type call_self() :: {call_self, ArgTypes :: [term()]}. +-type success_type_set() :: [{ArgTypes :: [term()], + RetType :: call_self() | term()}]. + +%% Per-function metadata used by various optimization passes to perform +%% module-level optimization. If a function is absent it means that +%% module-level optimization has been turned off for said function. +-type func_id() :: beam_ssa:b_local(). +-type func_info_db() :: #{ func_id() => #func_info{} }. + +-record #opt_st{ssa :: [{beam_ssa:label(),beam_ssa:b_blk()}] | + beam_ssa:block_map(), + args :: [beam_ssa:b_var()], + cnt :: beam_ssa:label(), + anno :: beam_ssa:anno()}. +-type st_map() :: #{ func_id() => #opt_st{} }. -import(lists, [all/2,append/1,droplast/1,duplicate/2,flatten/1,foldl/3, keyfind/3,last/1,mapfoldl/3,member/2, @@ -172,11 +225,15 @@ changed_types([Id | Ids], Fdb0, Fdb, In0, Out0) -> #{ Id := #func_info{arg_types=ATs,succ_types=ST} }} -> In = case ST0 =:= ST of true -> In0; - false -> changed_types_1([Id], #func_info.in, Fdb, In0) + false -> + GetIn = fun(#func_info{in=In}) -> In end, + changed_types_1([Id], GetIn, Fdb, In0) end, Out = case ATs0 =:= ATs of true -> Out0; - false -> changed_types_1([Id], #func_info.out, Fdb, Out0) + false -> + GetOut = fun(#func_info{out=Out}) -> Out end, + changed_types_1([Id], GetOut, Fdb, Out0) end, changed_types(Ids, Fdb0, Fdb, In, Out); _ -> @@ -194,7 +251,7 @@ changed_types_1([Id | Ids], Direction, Fdb, Seen0) -> false -> case Fdb of #{ Id := FuncInfo } -> - Next = element(Direction, FuncInfo), + Next = Direction(FuncInfo), Seen1 = sets:add_element(Id, Seen0), Seen2 = changed_types_1(Next, Direction, Fdb, Seen1), diff --git a/lib/compiler/src/beam_ssa_opt.hrl b/lib/compiler/src/beam_ssa_opt.hrl deleted file mode 100644 index 2aa8a4b447b6..000000000000 --- a/lib/compiler/src/beam_ssa_opt.hrl +++ /dev/null @@ -1,73 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% SPDX-License-Identifier: Apache-2.0 -%% -%% Copyright Ericsson AB 2019-2025. 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% -%% - --include("beam_ssa.hrl"). - --record(func_info, - {%% Local calls going in/out of this function. - in = ordsets:new() :: ordsets:ordset(func_id()), - out = ordsets:new() :: ordsets:ordset(func_id()), - - %% Whether the function is exported or not; some optimizations may - %% need to be suppressed if it is. - exported = true :: boolean(), - - %% The inferred types of each argument (as opposed to parameter), - %% indexed by call site. - %% - %% This is more effective than the naive approach of joining into a - %% "parameter_type" as we go as it lets us narrow parameter types - %% without having to visit all callers on each pass, which helps a lot - %% when dealing with co-recursive functions. - arg_types = [] :: list(arg_type_map()), - - %% The success types of this function, grouping return values by their - %% argument types at the time of return. - %% - %% This gives us more precise types than a naive join of all returned - %% values, as we can rule out the cases where the arguments are - %% incompatible with the ones we're passing. - %% - %% Note that the argument types are those seen on successful return, - %% they do not cover all types that are provided to the function. - succ_types = [] :: success_type_set()}). - --type arg_key() :: {CallerId :: func_id(), - CallDst :: beam_ssa:b_var()}. --type arg_type_map() :: #{ arg_key() => term() }. - --type call_self() :: {call_self, ArgTypes :: [term()]}. --type success_type_set() :: [{ArgTypes :: [term()], - RetType :: call_self() | term()}]. - -%% Per-function metadata used by various optimization passes to perform -%% module-level optimization. If a function is absent it means that -%% module-level optimization has been turned off for said function. --type func_id() :: beam_ssa:b_local(). --type func_info_db() :: #{ func_id() => #func_info{} }. - --record(opt_st, {ssa :: [{beam_ssa:label(),beam_ssa:b_blk()}] | - beam_ssa:block_map(), - args :: [beam_ssa:b_var()], - cnt :: beam_ssa:label(), - anno :: beam_ssa:anno()}). --type st_map() :: #{ func_id() => #opt_st{} }. diff --git a/lib/compiler/src/beam_ssa_pp.erl b/lib/compiler/src/beam_ssa_pp.erl index d805a1ae84ff..29020eb74315 100644 --- a/lib/compiler/src/beam_ssa_pp.erl +++ b/lib/compiler/src/beam_ssa_pp.erl @@ -308,7 +308,7 @@ format_live_interval(#b_var{}=Dst, #{live_intervals:=Intervals}) -> end; format_live_interval(_, _) -> []. --spec format_type(type()) -> iolist(). +-spec format_type(beam_types:type()) -> iolist(). format_type(any) -> "any()"; diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl index 2f38ae67aa73..49bf1a43f160 100644 --- a/lib/compiler/src/beam_ssa_type.erl +++ b/lib/compiler/src/beam_ssa_type.erl @@ -32,15 +32,20 @@ -moduledoc false. -export([opt_start/2, opt_continue/4, opt_finish/3]). -%% FIXME: Workaround for bug. --compile([no_ssa_opt]). - --include("beam_ssa_opt.hrl"). +-include("beam_ssa.hrl"). -include("beam_types.hrl"). -import(lists, [duplicate/2,foldl/3,member/2, keyfind/3,reverse/1,split/2,zip/2]). +-type type() :: beam_types:type(). +-type normal_type() :: beam_types:normal_type(). + +-type func_id() :: beam_ssa_opt:func_id(). +-type func_info_db() :: beam_ssa_opt:func_info_db(). + +-import_record(beam_ssa_opt, [func_info, opt_st]). + %% The maximum number of #b_ret{} terminators a function can have before %% collapsing success types into a single entry. Consider the following code: %% diff --git a/lib/compiler/src/beam_types.erl b/lib/compiler/src/beam_types.erl index f7831dcf6510..ed043cc25aee 100644 --- a/lib/compiler/src/beam_types.erl +++ b/lib/compiler/src/beam_types.erl @@ -56,10 +56,177 @@ -export([decode_ext/1, encode_ext/1, convert_ext/2]). +-export_type([type/0, normal_type/0]). + +-export_record([t_atom, t_bitstring, t_bs_context, t_bs_matchable, t_cons, + t_float, t_fun, t_integer, t_list, t_map, t_number, + t_record, t_tuple, t_union]). + %% This is exported to help catch errors in property test generators and is not %% meant to be used outside of test suites. -export([verified_type/1]). +%% Common term types for passes operating on beam SSA and assembly. +%% +%% The type lattice is as follows: +%% +%% any Any Erlang term (top element). +%% +%% - #t_atom{} Atom, or a set thereof. +%% - #t_number{} Any number. +%% -- #t_float{} Floating point number. +%% -- #t_integer{} Integer. +%% - #t_list{} Any list. +%% -- #t_cons{} Cons (nonempty list). +%% -- nil The empty list. +%% - #t_tuple{} Tuple. +%% - #t_record{} Native Record. +%% - other Other types. +%% -- #t_fun{} Fun. +%% -- #t_map{} Map. +%% -- identifier +%% -- pid +%% -- port +%% -- reference +%% -- #t_bs_matchable{} Binary-matchable types. +%% -- #t_bitstring{} Bitstring. +%% -- #t_bs_context{} Match context. +%% +%% none No type (bottom element). +%% +%% We also use #t_union{} to represent conflicting types produced by certain +%% expressions, e.g. the "#t_atom{} or #t_tuple{}" of lists:keyfind/3, which is +%% very useful for preserving type information when we would otherwise have +%% reduced it to 'any'. Since few operations can make direct use of this extra +%% type information, types should generally be normalized to one of the above +%% before use. +%% +%% When adding a new type it's important that the lattice stays consistent [1]. +%% In brief, the following properties must hold: +%% +%% * All types must be unambiguous; any given value must narrow down to a +%% single type, and multiple supertypes are not allowed. +%% +%% * `meet` is used when we know more about a value (e.g. type tests), so it +%% must not return a more general type than either of its arguments. In other +%% words, we're only allowed to *add* knowledge in a `meet`. +%% +%% * `join` is used when we know less about a value (e.g. phi node), so it +%% must not return a more specific type than either of its arguments. In +%% other words we're only allowed to *remove* knowledge in a `join`. +%% +%% * Both `join` and `meet` must be commutative, associative, and idempotent. +%% +%% Maintaining the above may seem trivial but subtle errors can creep in when +%% adding fields or restrictions to a type. ?TUPLE_ELEMENT_LIMIT is a great +%% example of this. +%% +%% The property test suite ensures that the above holds, so don't forget to +%% add your new types there. You should also consider increasing ?REPETITIONS +%% during development to ensure it hits all nooks and crannies. +%% +%% [1] https://en.wikipedia.org/wiki/Lattice_(order)#General_lattice + +-type float_range() :: 'any' | {'-inf',float()} | {float(),'+inf'}. + +-record #t_atom{elements=any :: 'any' | ordsets:ordset(atom())}. +-record #t_bitstring{size_unit=1 :: pos_integer(), + %% The appendable flag indicates whether the bitstring + %% originated as <<>> and has only been appended to by + %% `bs_create_bin` with the bitstring as the leftmost + %% fragment. + appendable=false :: boolean()}. +-record #t_bs_context{tail_unit=1 :: pos_integer()}. +-record #t_bs_matchable{tail_unit=1 :: pos_integer()}. +-record #t_float{elements=any :: float_range()}. +-record #t_fun{arity=any :: arity() | 'any', + target=any :: {atom(), non_neg_integer()} | 'any', + type=any :: type()}. +-record #t_integer{elements=any :: 'any' | beam_bounds:range()}. +-record #t_number{elements=any :: 'any' | beam_bounds:range()}. + +%% `super_key` and `super_value` are the join of all key and value types. +%% +%% Note that we don't track specific elements as we have no obvious way to +%% limit them. See ?TUPLE_ELEMENT_LIMIT for details. +-record #t_map{super_key=any :: type(), + super_value=any :: type()}. + +-record #t_record{name = nil :: nil | {atom(), atom()}, + exported = unknown :: unknown | yes | no, + type = #{} :: record_elements()}. + +-type native_records() :: ordsets:ordset(#t_record{}). +-type native_record_set() :: #t_record{} | native_records(). + +-type record_elements() :: #{Key :: atom() => Type :: missing | {present, type()}}. + +%% `type` is the join of all list elements, and `terminator` is the tail of the +%% last cons cell ('nil' for proper lists). +%% +%% Note that `type` may not be updated unless the entire list is known, and +%% that the terminator being known is not a guarantee that the rest of the list +%% is. +-record #t_cons{type=any :: type(), terminator=any :: type()}. +-record #t_list{type=any :: type(), terminator=any :: type()}. + +-record #t_tuple{size=0 :: integer(), + exact=false :: boolean(), + elements=#{} :: tuple_elements()}. + +%% Known element types, where the key is a 1-based integer index. Unknown +%% elements are assumed to be 'any', and indexes above ?TUPLE_ELEMENT_LIMIT are +%% ignored for performance reasons. +%% +%% Cutting off all indexes above a certain limit may seem strange, but is +%% required to ensure that a meet of two types always returns a type that's at +%% least as specific as either type. Consider the following types: +%% +%% A = #t_tuple{elements=#{ ... elements 1 .. 6 ... }} +%% B = #t_tuple{elements=#{ ... elements 7 .. 13 ... }} +%% +%% If we'd collapse types once a tuple has more than 12 elements, meet(A, B) +%% would suddenly be less specific than either A or B. Ignoring all elements +%% above a certain index avoids this problem, at the small price of losing type +%% information in huge tuples. + +-type tuple_elements() :: #{Key :: pos_integer() => type()}. + +-type normal_type() :: 'any' | 'none' | + #t_number{} | #t_float{} | #t_integer{} | + #t_atom{} | + #t_bitstring{} | #t_bs_context{} | #t_bs_matchable{} | + #t_fun{} | + #t_list{} | #t_cons{} | 'nil' | + 'other' | + #t_map{} | + 'identifier' | + 'pid' | + 'port' | + 'reference' | + #t_tuple{} | + #t_record{}. + +-type other_type() :: 'none' | #t_fun{} | #t_map{} | + 'pid' | 'port' | 'reference' | 'identifier' | + #t_bitstring{} | #t_bs_context{} | + #t_bs_matchable{}. + +-type record_key() :: {Arity :: integer(), Tag :: normal_type() }. +-type record_set() :: ordsets:ordset({record_key(), #t_tuple{}}). +-type tuple_set() :: #t_tuple{} | record_set(). + +%% The fields in the union must not overlap. In particular, that means +%% that the type `any` is not allowed in any field. +-record #t_union{atom=none :: 'none' | #t_atom{}, + list=none :: 'none' | #t_list{} | #t_cons{} | nil, + number=none :: 'none' | #t_number{} | #t_float{} | #t_integer{}, + tuple_set=none :: 'none' | tuple_set(), + native_record_set=none :: 'none' | native_record_set(), + other=none :: 'other' | other_type()}. + +-type type() :: #t_union{} | normal_type(). + -define(IS_LIST_TYPE(N), is_record(N, t_list) orelse is_record(N, t_cons) orelse diff --git a/lib/compiler/src/beam_types.hrl b/lib/compiler/src/beam_types.hrl index 93c694cd2ba8..a7fe0eaef6d4 100644 --- a/lib/compiler/src/beam_types.hrl +++ b/lib/compiler/src/beam_types.hrl @@ -23,174 +23,19 @@ %% Type version, must be bumped whenever the external type format changes. -define(BEAM_TYPES_VERSION, 4). -%% Common term types for passes operating on beam SSA and assembly. Helper -%% functions for wrangling these can be found in beam_types.erl -%% -%% The type lattice is as follows: -%% -%% any Any Erlang term (top element). -%% -%% - #t_atom{} Atom, or a set thereof. -%% - #t_number{} Any number. -%% -- #t_float{} Floating point number. -%% -- #t_integer{} Integer. -%% - #t_list{} Any list. -%% -- #t_cons{} Cons (nonempty list). -%% -- nil The empty list. -%% - #t_tuple{} Tuple. -%% - #t_record{} Native Record. -%% - other Other types. -%% -- #t_fun{} Fun. -%% -- #t_map{} Map. -%% -- identifier -%% -- pid -%% -- port -%% -- reference -%% -- #t_bs_matchable{} Binary-matchable types. -%% -- #t_bitstring{} Bitstring. -%% -- #t_bs_context{} Match context. -%% -%% none No type (bottom element). -%% -%% We also use #t_union{} to represent conflicting types produced by certain -%% expressions, e.g. the "#t_atom{} or #t_tuple{}" of lists:keyfind/3, which is -%% very useful for preserving type information when we would otherwise have -%% reduced it to 'any'. Since few operations can make direct use of this extra -%% type information, types should generally be normalized to one of the above -%% before use. -%% -%% When adding a new type it's important that the lattice stays consistent [1]. -%% In brief, the following properties must hold: -%% -%% * All types must be unambiguous; any given value must narrow down to a -%% single type, and multiple supertypes are not allowed. -%% -%% * `meet` is used when we know more about a value (e.g. type tests), so it -%% must not return a more general type than either of its arguments. In other -%% words, we're only allowed to *add* knowledge in a `meet`. -%% -%% * `join` is used when we know less about a value (e.g. phi node), so it -%% must not return a more specific type than either of its arguments. In -%% other words we're only allowed to *remove* knowledge in a `join`. -%% -%% * Both `join` and `meet` must be commutative, associative, and idempotent. -%% -%% Maintaining the above may seem trivial but subtle errors can creep in when -%% adding fields or restrictions to a type. ?TUPLE_ELEMENT_LIMIT is a great -%% example of this. -%% -%% The property test suite ensures that the above holds, so don't forget to -%% add your new types there. You should also consider increasing ?REPETITIONS -%% during development to ensure it hits all nooks and crannies. -%% -%% [1] https://en.wikipedia.org/wiki/Lattice_(order)#General_lattice +-if(?MODULE =/= beam_types). +-import_record(beam_types, + [t_atom, t_bitstring, t_bs_context, t_bs_matchable, t_cons, + t_float, t_fun, t_integer, t_list, t_map, t_number, + t_record, t_tuple, t_union]). +-endif. -define(ATOM_SET_SIZE, 5). -%% Documented limits +%% Documented limits. -define(MAX_FUNC_ARGS, 255). -define(MAX_TUPLE_SIZE, (1 bsl 24) - 1). - --type float_range() :: 'any' | {'-inf',float()} | {float(),'+inf'}. - --record(t_atom, {elements=any :: 'any' | ordsets:ordset(atom())}). --record(t_bitstring, {size_unit=1 :: pos_integer(), - %% The appendable flag indicates whether the bitstring - %% originated as <<>> and has only been appended to by - %% `bs_create_bin` with the bitstring as the leftmost - %% fragment. - appendable=false :: boolean()}). --record(t_bs_context, {tail_unit=1 :: pos_integer()}). --record(t_bs_matchable, {tail_unit=1 :: pos_integer()}). --record(t_float, {elements=any :: float_range()}). --record(t_fun, {arity=any :: arity() | 'any', - target=any :: {atom(), non_neg_integer()} | 'any', - type=any :: type() }). --record(t_integer, {elements=any :: 'any' | beam_bounds:range()}). --record(t_number, {elements=any :: 'any' | beam_bounds:range()}). - -%% `super_key` and `super_value` are the join of all key and value types. -%% -%% Note that we don't track specific elements as we have no obvious way to -%% limit them. See ?TUPLE_ELEMENT_LIMIT for details. --record(t_map, {super_key=any :: type(), - super_value=any :: type()}). - --record(t_record, {name = nil :: nil | {atom(), atom()}, - exported = unknown :: unknown | yes | no, - type = #{} :: record_elements()}). - --type native_records() :: ordsets:ordset(#t_record{}). --type native_record_set() :: #t_record{} | native_records(). - --type record_elements() :: #{Key :: atom() => Type :: missing | {present, type()}}. - -%% `type` is the join of all list elements, and `terminator` is the tail of the -%% last cons cell ('nil' for proper lists). -%% -%% Note that `type` may not be updated unless the entire list is known, and -%% that the terminator being known is not a guarantee that the rest of the list -%% is. --record(t_cons, {type=any :: type(), terminator=any :: type()}). --record(t_list, {type=any :: type(), terminator=any :: type()}). - --record(t_tuple, {size=0 :: integer(), - exact=false :: boolean(), - elements=#{} :: tuple_elements()}). - -%% Known element types, where the key is a 1-based integer index. Unknown -%% elements are assumed to be 'any', and indexes above ?TUPLE_ELEMENT_LIMIT are -%% ignored for performance reasons. -%% -%% Cutting off all indexes above a certain limit may seem strange, but is -%% required to ensure that a meet of two types always returns a type that's at -%% least as specific as either type. Consider the following types: -%% -%% A = #t_tuple{elements=#{ ... elements 1 .. 6 ... }} -%% B = #t_tuple{elements=#{ ... elements 7 .. 13 ... }} -%% -%% If we'd collapse types once a tuple has more than 12 elements, meet(A, B) -%% would suddenly be less specific than either A or B. Ignoring all elements -%% above a certain index avoids this problem, at the small price of losing type -%% information in huge tuples. - -define(TUPLE_ELEMENT_LIMIT, 12). --type tuple_elements() :: #{ Key :: pos_integer() => type() }. - --type normal_type() :: 'any' | 'none' | - #t_number{} | #t_float{} | #t_integer{} | - #t_atom{} | - #t_bitstring{} | #t_bs_context{} | #t_bs_matchable{} | - #t_fun{} | - #t_list{} | #t_cons{} | 'nil' | - 'other' | - #t_map{} | - 'identifier' | - 'pid' | - 'port' | - 'reference' | - #t_tuple{} | - #t_record{}. - --type other_type() :: 'none' | #t_fun{} | #t_map{} | - 'pid' | 'port' | 'reference' | 'identifier' | - #t_bitstring{} | #t_bs_context{} | - #t_bs_matchable{}. - --type record_key() :: {Arity :: integer(), Tag :: normal_type() }. --type record_set() :: ordsets:ordset({record_key(), #t_tuple{}}). --type tuple_set() :: #t_tuple{} | record_set(). - -%% The fields in the union must not overlap. In particular, that means -%% that the type `any` is not allowed in any field. --record(t_union, {atom=none :: 'none' | #t_atom{}, - list=none :: 'none' | #t_list{} | #t_cons{} | nil, - number=none :: 'none' | #t_number{} | #t_float{} | #t_integer{}, - tuple_set=none :: 'none' | tuple_set(), - native_record_set=none :: 'none' | native_record_set(), - other=none :: 'other' | other_type()}). - --type type() :: #t_union{} | normal_type(). -ifdef(BEAM_TYPES_INTERNAL). %% Internal constants used by beam_types.erl and its whitebox tests diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index 230c42c7f423..363519b12c99 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -44,6 +44,8 @@ -import(lists, [dropwhile/2,foldl/3,member/2,reverse/2,zip/2]). +-type type() :: beam_types:type(). + %% To be called by the compiler. -spec validate(Code, Level) -> Result when @@ -53,6 +55,10 @@ validate({Mod,Exp,Attr,Anno,Fs,Lc}, Level) when is_atom(Mod), is_list(Exp), is_list(Attr), is_map(Anno), is_integer(Lc) -> + %% Ensure that `beam_types` is loaded if compilation started from a + %% .S file. + _ = beam_types:module_info(module), + RecDefaults = record_defaults(Anno), Ft = build_function_table(Fs, #{}), case validate_0(Fs, Mod, RecDefaults, Level, Ft) of diff --git a/lib/compiler/src/core_parse.yrl b/lib/compiler/src/core_parse.yrl index 2756148c6126..e9ba97a4d61c 100644 --- a/lib/compiler/src/core_parse.yrl +++ b/lib/compiler/src/core_parse.yrl @@ -28,7 +28,7 @@ Expect 0. Nonterminals -module_definition module_export module_attribute module_defs +module_definition init module_export module_attribute module_defs exported_names exported_name attribute_list attribute function_definition function_definitions @@ -92,14 +92,18 @@ Rootsymbol module_definition. %% Grammar module_definition -> - 'module' atom module_export module_attribute module_defs 'end' : - #c_module{name=#c_literal{val=tok_val('$2')},exports='$3', - attrs='$4',defs='$5'}. + init 'module' atom module_export module_attribute module_defs 'end' : + #c_module{name=#c_literal{val=tok_val('$3')},exports='$4', + attrs='$5',defs='$6'}. module_definition -> - '(' 'module' atom module_export module_attribute module_defs 'end' - '-|' annotation ')' : - #c_module{anno='$9',name=#c_literal{val=tok_val('$3')},exports='$4', - attrs='$5',defs='$6'}. + init '(' 'module' atom module_export module_attribute module_defs 'end' + '-|' annotation ')' : + #c_module{anno='$10',name=#c_literal{val=tok_val('$4')},exports='$5', + attrs='$6',defs='$7'}. + +%% We must ensure that `v3_core` is loaded before we attempt to +%% create any native records belonging to it. +init -> '$empty' : v3_core:module_info(module). module_export -> '[' ']' : []. module_export -> '[' exported_names ']' : '$2'. diff --git a/lib/compiler/test/beam_validator_SUITE.erl b/lib/compiler/test/beam_validator_SUITE.erl index 389dbb4e0b45..3d2d50fdae5e 100644 --- a/lib/compiler/test/beam_validator_SUITE.erl +++ b/lib/compiler/test/beam_validator_SUITE.erl @@ -51,6 +51,8 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("stdlib/include/assert.hrl"). +-import_record(beam_types, [t_atom, t_cons]). + init_per_testcase(Case, Config) when is_atom(Case), is_list(Config) -> Config. @@ -245,7 +247,7 @@ bad_catch_try(Config) when is_list(Config) -> {{catch_end,{x,9}}, 8,{invalid_tag_register,{x,9}}}}, {{bad_catch_try,bad_3,1}, - {{catch_end,{y,1}},9,{invalid_tag,{y,1},{t_atom,[kalle]}}}}, + {{catch_end,{y,1}},9,{invalid_tag,{y,1},#t_atom{elements=[kalle]}}}}, {{bad_catch_try,bad_4,1}, {{'try',{x,0},{f,15}},6,{invalid_tag_register,{x,0}}}}, {{bad_catch_try,bad_5,1}, @@ -260,7 +262,8 @@ cons_guard(Config) when is_list(Config) -> [{{cons,foo,1}, {{get_list,{x,0},{x,1},{x,2}}, 5, - {bad_type,{needed,{t_cons,any,any}},{actual,any}}}}] = Errors, + {bad_type,{needed,#t_cons{type=any,terminator=any}}, + {actual,any}}}}] = Errors, ok. freg_range(Config) when is_list(Config) -> diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl index 971949de2730..f4d66787f97f 100644 --- a/lib/stdlib/src/erl_parse.yrl +++ b/lib/stdlib/src/erl_parse.yrl @@ -1977,6 +1977,12 @@ normalise({map,_,Pairs}=M) -> end, Pairs)); normalise({'fun',_,{function,{atom,_,M},{atom,_,F},{integer,_,A}}}) -> fun M:F/A; +normalise({'record',_,{M,N},Fs0}=R) -> + Fs1 = lists:map(fun + ({record_field,_,K,V}) -> {normalise(K),normalise(V)}; + (_) -> erlang:error({badarg,R}) + end, Fs0), + records:create(M, N, Fs1, #{is_exported=>true}); %% Special case for unary +/-. normalise({op,_,'+',{char,_,I}}) -> I; normalise({op,_,'+',{integer,_,I}}) -> I;