aboutsummaryrefslogtreecommitdiffstats
path: root/lib/compiler/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/compiler/src')
-rw-r--r--lib/compiler/src/Makefile8
-rw-r--r--lib/compiler/src/beam_asm.erl23
-rw-r--r--lib/compiler/src/beam_block.erl39
-rw-r--r--lib/compiler/src/beam_call_types.erl509
-rw-r--r--lib/compiler/src/beam_clean.erl52
-rw-r--r--lib/compiler/src/beam_dict.erl22
-rw-r--r--lib/compiler/src/beam_disasm.erl7
-rw-r--r--lib/compiler/src/beam_except.erl247
-rw-r--r--lib/compiler/src/beam_ssa.erl4
-rw-r--r--lib/compiler/src/beam_ssa_bsm.erl4
-rw-r--r--lib/compiler/src/beam_ssa_codegen.erl131
-rw-r--r--lib/compiler/src/beam_ssa_dead.erl8
-rw-r--r--lib/compiler/src/beam_ssa_opt.erl5
-rw-r--r--lib/compiler/src/beam_ssa_pre_codegen.erl116
-rw-r--r--lib/compiler/src/beam_ssa_type.erl1126
-rw-r--r--lib/compiler/src/beam_trim.erl7
-rw-r--r--lib/compiler/src/beam_types.erl411
-rw-r--r--lib/compiler/src/beam_types.hrl72
-rw-r--r--lib/compiler/src/beam_validator.erl1316
-rw-r--r--lib/compiler/src/cerl.erl2
-rw-r--r--lib/compiler/src/compile.erl13
-rw-r--r--lib/compiler/src/compiler.app.src3
-rwxr-xr-xlib/compiler/src/genop.tab6
-rw-r--r--lib/compiler/src/sys_core_fold.erl414
-rw-r--r--lib/compiler/src/v3_kernel.erl63
25 files changed, 2156 insertions, 2452 deletions
diff --git a/lib/compiler/src/Makefile b/lib/compiler/src/Makefile
index 87b0d345f2..f253f31d13 100644
--- a/lib/compiler/src/Makefile
+++ b/lib/compiler/src/Makefile
@@ -49,10 +49,10 @@ MODULES = \
beam_a \
beam_asm \
beam_block \
+ beam_call_types \
beam_clean \
beam_dict \
beam_disasm \
- beam_except \
beam_flatten \
beam_jump \
beam_listing \
@@ -72,6 +72,7 @@ MODULES = \
beam_ssa_type \
beam_kernel_to_ssa \
beam_trim \
+ beam_types \
beam_utils \
beam_validator \
beam_z \
@@ -104,6 +105,7 @@ HRL_FILES= \
beam_disasm.hrl \
beam_ssa_opt.hrl \
beam_ssa.hrl \
+ beam_types.hrl \
core_parse.hrl \
v3_kernel.hrl
@@ -190,6 +192,7 @@ release_docs_spec:
# Dependencies -- alphabetically, please
# ----------------------------------------------------
+$(EBIN)/beam_call_types.beam: beam_types.hrl
$(EBIN)/beam_disasm.beam: $(EGEN)/beam_opcodes.hrl beam_disasm.hrl
$(EBIN)/beam_listing.beam: core_parse.hrl v3_kernel.hrl beam_ssa.hrl
$(EBIN)/beam_kernel_to_ssa.beam: v3_kernel.hrl beam_ssa.hrl
@@ -204,7 +207,8 @@ $(EBIN)/beam_ssa_pp.beam: beam_ssa.hrl
$(EBIN)/beam_ssa_pre_codegen.beam: beam_ssa.hrl
$(EBIN)/beam_ssa_recv.beam: beam_ssa.hrl
$(EBIN)/beam_ssa_share.beam: beam_ssa.hrl
-$(EBIN)/beam_ssa_type.beam: beam_ssa.hrl
+$(EBIN)/beam_ssa_type.beam: beam_ssa.hrl beam_types.hrl
+$(EBIN)/beam_types.beam: beam_types.hrl
$(EBIN)/cerl.beam: core_parse.hrl
$(EBIN)/compile.beam: core_parse.hrl ../../stdlib/include/erl_compile.hrl
$(EBIN)/core_lib.beam: core_parse.hrl
diff --git a/lib/compiler/src/beam_asm.erl b/lib/compiler/src/beam_asm.erl
index df09dcb06c..60e19ec596 100644
--- a/lib/compiler/src/beam_asm.erl
+++ b/lib/compiler/src/beam_asm.erl
@@ -64,11 +64,30 @@ module(Code, ExtraChunks, CompileInfo, CompilerOpts) ->
assemble({Mod,Exp0,Attr0,Asm0,NumLabels}, ExtraChunks, CompileInfo, CompilerOpts) ->
{1,Dict0} = beam_dict:atom(Mod, beam_dict:new()),
{0,Dict1} = beam_dict:fname(atom_to_list(Mod) ++ ".erl", Dict0),
+ Dict2 = shared_fun_wrappers(CompilerOpts, Dict1),
NumFuncs = length(Asm0),
{Asm,Attr} = on_load(Asm0, Attr0),
Exp = cerl_sets:from_list(Exp0),
- {Code,Dict2} = assemble_1(Asm, Exp, Dict1, []),
- build_file(Code, Attr, Dict2, NumLabels, NumFuncs, ExtraChunks, CompileInfo, CompilerOpts).
+ {Code,Dict} = assemble_1(Asm, Exp, Dict2, []),
+ build_file(Code, Attr, Dict, NumLabels, NumFuncs,
+ ExtraChunks, CompileInfo, CompilerOpts).
+
+shared_fun_wrappers(Opts, Dict) ->
+ case proplists:get_bool(no_shared_fun_wrappers, Opts) of
+ false ->
+ %% The compiler in OTP 23 depends on the on the loader
+ %% using the new indices in funs and being able to have
+ %% multiple make_fun2 instructions referring to the same
+ %% fun entry. Artificially set the highest opcode for the
+ %% module to ensure that it cannot be loaded in OTP 22
+ %% and earlier.
+ Swap = beam_opcodes:opcode(swap, 2),
+ beam_dict:opcode(Swap, Dict);
+ true ->
+ %% Fun wrappers are not shared for compatibility with a
+ %% previous OTP release.
+ Dict
+ end.
on_load(Fs0, Attr0) ->
case proplists:get_value(on_load, Attr0) of
diff --git a/lib/compiler/src/beam_block.erl b/lib/compiler/src/beam_block.erl
index 707974b2c1..a734ca3a10 100644
--- a/lib/compiler/src/beam_block.erl
+++ b/lib/compiler/src/beam_block.erl
@@ -33,8 +33,9 @@ module({Mod,Exp,Attr,Fs0,Lc}, _Opts) ->
function({function,Name,Arity,CLabel,Is0}) ->
try
- Is1 = blockify(Is0),
- Is = embed_lines(Is1),
+ Is1 = swap_opt(Is0),
+ Is2 = blockify(Is1),
+ Is = embed_lines(Is2),
{function,Name,Arity,CLabel,Is}
catch
Class:Error:Stack ->
@@ -42,6 +43,40 @@ function({function,Name,Arity,CLabel,Is0}) ->
erlang:raise(Class, Error, Stack)
end.
+%%%
+%%% Try to use a `swap` instruction instead of a sequence of moves.
+%%%
+%%% Note that beam_ssa_codegen generates `swap` instructions only for
+%%% the moves within a single SSA instruction (such as `call`), not
+%%% for the moves generated by a sequence of SSA instructions.
+%%% Therefore, this optimization is needed.
+%%%
+
+swap_opt([{move,Reg1,{x,X}=Temp}=Move1,
+ {move,Reg2,Reg1}=Move2,
+ {move,Temp,Reg2}=Move3|Is]) when Reg1 =/= Temp ->
+ case is_unused(X, Is) of
+ true ->
+ [{swap,Reg1,Reg2}|swap_opt(Is)];
+ false ->
+ [Move1|swap_opt([Move2,Move3|Is])]
+ end;
+swap_opt([I|Is]) ->
+ [I|swap_opt(Is)];
+swap_opt([]) -> [].
+
+is_unused(X, [{call,A,_}|_]) when A =< X -> true;
+is_unused(X, [{call_ext,A,_}|_]) when A =< X -> true;
+is_unused(X, [{make_fun2,_,_,_,A}|_]) when A =< X -> true;
+is_unused(X, [{move,Src,Dst}|Is]) ->
+ case {Src,Dst} of
+ {{x,X},_} -> false;
+ {_,{x,X}} -> true;
+ {_,_} -> is_unused(X, Is)
+ end;
+is_unused(X, [{line,_}|Is]) -> is_unused(X, Is);
+is_unused(_, _) -> false.
+
%% blockify(Instructions0) -> Instructions
%% Collect sequences of instructions to basic blocks.
%% Also do some simple optimations on instructions outside the blocks.
diff --git a/lib/compiler/src/beam_call_types.erl b/lib/compiler/src/beam_call_types.erl
new file mode 100644
index 0000000000..d091b7866d
--- /dev/null
+++ b/lib/compiler/src/beam_call_types.erl
@@ -0,0 +1,509 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(beam_call_types).
+
+-include("beam_types.hrl").
+
+-import(lists, [duplicate/2,foldl/3]).
+
+-export([never_throws/3, types/3]).
+
+-spec never_throws(Mod, Func, Arity) -> boolean() when
+ Mod :: atom(),
+ Func :: atom(),
+ Arity :: non_neg_integer().
+
+never_throws(erlang, '/=', 2) -> true;
+never_throws(erlang, '<', 2) -> true;
+never_throws(erlang, '=/=', 2) -> true;
+never_throws(erlang, '=:=', 2) -> true;
+never_throws(erlang, '=<', 2) -> true;
+never_throws(erlang, '==', 2) -> true;
+never_throws(erlang, '>', 2) -> true;
+never_throws(erlang, '>=', 2) -> true;
+never_throws(erlang, is_atom, 1) -> true;
+never_throws(erlang, is_boolean, 1) -> true;
+never_throws(erlang, is_binary, 1) -> true;
+never_throws(erlang, is_bitstring, 1) -> true;
+never_throws(erlang, is_float, 1) -> true;
+never_throws(erlang, is_function, 1) -> true;
+never_throws(erlang, is_integer, 1) -> true;
+never_throws(erlang, is_list, 1) -> true;
+never_throws(erlang, is_map, 1) -> true;
+never_throws(erlang, is_number, 1) -> true;
+never_throws(erlang, is_pid, 1) -> true;
+never_throws(erlang, is_port, 1) -> true;
+never_throws(erlang, is_reference, 1) -> true;
+never_throws(erlang, is_tuple, 1) -> true;
+never_throws(erlang, get, 1) -> true;
+never_throws(erlang, self, 0) -> true;
+never_throws(erlang, node, 0) -> true;
+never_throws(_, _, _) -> false.
+
+%%
+%% Returns the inferred return and argument types for known functions, and
+%% whether it's safe to subtract argument types on failure.
+%%
+%% Note that the return type will be 'none' if we can statically determine that
+%% the function will fail at runtime.
+%%
+
+-spec types(Mod, Func, ArgTypes) -> {RetType, ArgTypes, CanSubtract} when
+ Mod :: atom(),
+ Func :: atom(),
+ ArgTypes :: [type()],
+ RetType :: type(),
+ CanSubtract :: boolean().
+
+%% Functions that only fail due to bad argument *types*, meaning it's safe to
+%% subtract argument types on failure.
+%%
+%% Note that these are all from the erlang module; suitable functions in other
+%% modules could fail due to the module not being loaded.
+types(erlang, 'map_size', [_]) ->
+ sub_safe(#t_integer{}, [#t_map{}]);
+types(erlang, 'tuple_size', [_]) ->
+ sub_safe(#t_integer{}, [#t_tuple{}]);
+types(erlang, 'bit_size', [_]) ->
+ sub_safe(#t_integer{}, [#t_bitstring{}]);
+types(erlang, 'byte_size', [_]) ->
+ sub_safe(#t_integer{}, [#t_bitstring{}]);
+types(erlang, 'hd', [_]) ->
+ sub_safe(any, [cons]);
+types(erlang, 'tl', [_]) ->
+ sub_safe(any, [cons]);
+types(erlang, 'length', [_]) ->
+ sub_safe(#t_integer{}, [list]);
+types(erlang, 'not', [_]) ->
+ Bool = beam_types:make_boolean(),
+ sub_safe(Bool, [Bool]);
+
+%% Boolean ops
+types(erlang, 'and', [_,_]) ->
+ Bool = beam_types:make_boolean(),
+ sub_unsafe(Bool, [Bool, Bool]);
+types(erlang, 'or', [_,_]) ->
+ Bool = beam_types:make_boolean(),
+ sub_unsafe(Bool, [Bool, Bool]);
+types(erlang, 'xor', [_,_]) ->
+ Bool = beam_types:make_boolean(),
+ sub_unsafe(Bool, [Bool, Bool]);
+
+%% Bitwise ops
+types(erlang, 'band', [_,_]=Args) ->
+ sub_unsafe(band_return_type(Args), [#t_integer{}, #t_integer{}]);
+types(erlang, 'bor', [_,_]) ->
+ sub_unsafe(#t_integer{}, [#t_integer{}, #t_integer{}]);
+types(erlang, 'bxor', [_,_]) ->
+ sub_unsafe(#t_integer{}, [#t_integer{}, #t_integer{}]);
+types(erlang, 'bsl', [_,_]) ->
+ sub_unsafe(#t_integer{}, [#t_integer{}, #t_integer{}]);
+types(erlang, 'bsr', [_,_]) ->
+ sub_unsafe(#t_integer{}, [#t_integer{}, #t_integer{}]);
+types(erlang, 'bnot', [_]) ->
+ sub_unsafe(#t_integer{}, [#t_integer{}]);
+
+%% Fixed-type arithmetic
+types(erlang, 'float', [_]) ->
+ sub_unsafe(float, [number]);
+types(erlang, 'round', [_]) ->
+ sub_unsafe(#t_integer{}, [number]);
+types(erlang, 'floor', [_]) ->
+ sub_unsafe(#t_integer{}, [number]);
+types(erlang, 'ceil', [_]) ->
+ sub_unsafe(#t_integer{}, [number]);
+types(erlang, 'trunc', [_]) ->
+ sub_unsafe(#t_integer{}, [number]);
+types(erlang, '/', [_,_]) ->
+ sub_unsafe(float, [number, number]);
+types(erlang, 'div', [_,_]) ->
+ sub_unsafe(#t_integer{}, [#t_integer{}, #t_integer{}]);
+types(erlang, 'rem', [_,_]) ->
+ sub_unsafe(#t_integer{}, [#t_integer{}, #t_integer{}]);
+
+%% Mixed-type arithmetic; '+'/2 and friends are handled in the catch-all
+%% clause for the 'erlang' module.
+types(erlang, 'abs', [_]=Args) ->
+ mixed_arith_types(Args);
+
+%% List operations
+types(erlang, '++', [LHS,RHS]) ->
+ %% `[] ++ RHS` yields RHS, even if RHS is not a list.
+ RetType = case {LHS, RHS} of
+ {cons, _} -> cons;
+ {_, cons} -> cons;
+ _ -> beam_types:join(list, RHS)
+ end,
+ sub_unsafe(RetType, [list, any]);
+types(erlang, '--', [_,_]) ->
+ sub_unsafe(list, [list, list]);
+
+%% Misc ops.
+types(erlang, 'binary_part', [_, _]) ->
+ PosLen = make_two_tuple(#t_integer{}, #t_integer{}),
+ Binary = #t_bitstring{unit=8},
+ sub_unsafe(Binary, [Binary, PosLen]);
+types(erlang, 'binary_part', [_, _, _]) ->
+ Binary = #t_bitstring{unit=8},
+ sub_unsafe(Binary, [Binary, #t_integer{}, #t_integer{}]);
+types(erlang, 'is_map_key', [_,_]) ->
+ sub_unsafe(beam_types:make_boolean(), [any,#t_map{}]);
+types(erlang, 'map_get', [_,_]) ->
+ sub_unsafe(any, [any,#t_map{}]);
+types(erlang, 'node', [_]) ->
+ sub_unsafe(#t_atom{}, [any]);
+types(erlang, 'node', []) ->
+ sub_unsafe(#t_atom{}, []);
+types(erlang, 'size', [_]) ->
+ sub_unsafe(#t_integer{}, [any]);
+types(erlang, 'size', [_]) ->
+ sub_unsafe(#t_integer{}, [any]);
+
+%% Tuple element ops
+types(erlang, element, [PosType, TupleType]) ->
+ Index = case PosType of
+ #t_integer{elements={Same,Same}} when is_integer(Same) ->
+ Same;
+ _ ->
+ 0
+ end,
+
+ RetType = case TupleType of
+ #t_tuple{size=Sz,elements=Es} when Index =< Sz,
+ Index >= 1 ->
+ beam_types:get_element_type(Index, Es);
+ _ ->
+ any
+ end,
+
+ sub_unsafe(RetType, [#t_integer{}, #t_tuple{size=Index}]);
+types(erlang, setelement, [PosType, TupleType, ArgType]) ->
+ RetType = case {PosType,TupleType} of
+ {#t_integer{elements={Index,Index}},
+ #t_tuple{elements=Es0,size=Size}=T} ->
+ %% This is an exact index, update the type of said
+ %% element or return 'none' if it's known to be out of
+ %% bounds.
+ Es = beam_types:set_element_type(Index, ArgType, Es0),
+ case T#t_tuple.exact of
+ false ->
+ T#t_tuple{size=max(Index, Size),elements=Es};
+ true when Index =< Size ->
+ T#t_tuple{elements=Es};
+ true ->
+ none
+ end;
+ {#t_integer{elements={Min,Max}},
+ #t_tuple{elements=Es0,size=Size}=T} ->
+ %% We know this will land between Min and Max, so kill
+ %% the types for those indexes.
+ Es = discard_tuple_element_info(Min, Max, Es0),
+ case T#t_tuple.exact of
+ false ->
+ T#t_tuple{elements=Es,size=max(Min, Size)};
+ true when Min =< Size ->
+ T#t_tuple{elements=Es,size=Size};
+ true ->
+ none
+ end;
+ {_,#t_tuple{}=T} ->
+ %% Position unknown, so we have to discard all element
+ %% information.
+ T#t_tuple{elements=#{}};
+ {#t_integer{elements={Min,_Max}},_} ->
+ #t_tuple{size=Min};
+ {_,_} ->
+ #t_tuple{}
+ end,
+ sub_unsafe(RetType, [#t_integer{}, #t_tuple{}, any]);
+
+types(erlang, make_fun, [_,_,Arity0]) ->
+ Type = case Arity0 of
+ #t_integer{elements={Arity,Arity}} when Arity >= 0 ->
+ #t_fun{arity=Arity};
+ _ ->
+ #t_fun{}
+ end,
+ sub_unsafe(Type, [#t_atom{}, #t_atom{}, #t_integer{}]);
+
+types(erlang, Name, Args) ->
+ Arity = length(Args),
+
+ case erl_bifs:is_exit_bif(erlang, Name, Arity) of
+ true ->
+ {none, Args, false};
+ false ->
+ case erl_internal:arith_op(Name, Arity) of
+ true ->
+ mixed_arith_types(Args);
+ false ->
+ IsTest =
+ erl_internal:new_type_test(Name, Arity) orelse
+ erl_internal:comp_op(Name, Arity),
+
+ RetType = case IsTest of
+ true -> beam_types:make_boolean();
+ false -> any
+ end,
+
+ sub_unsafe(RetType, duplicate(Arity, any))
+ end
+ end;
+
+%%
+%% Math BIFs
+%%
+
+types(math, cos, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, cosh, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, sin, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, sinh, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, tan, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, tanh, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, acos, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, acosh, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, asin, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, asinh, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, atan, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, atanh, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, erf, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, erfc, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, exp, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, log, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, log2, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, log10, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, sqrt, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, atan2, [_,_]) ->
+ sub_unsafe(float, [number, number]);
+types(math, pow, [_,_]) ->
+ sub_unsafe(float, [number, number]);
+types(math, ceil, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, floor, [_]) ->
+ sub_unsafe(float, [number]);
+types(math, fmod, [_,_]) ->
+ sub_unsafe(float, [number, number]);
+types(math, pi, []) ->
+ sub_unsafe(float, []);
+
+%%
+%% List functions
+%%
+
+%% Operator aliases.
+types(lists, append, [_,_]=Args) ->
+ types(erlang, '++', Args);
+types(lists, append, [_]) ->
+ %% This is implemented through folding the list over erlang:'++'/2, so it
+ %% can hypothetically return anything, but we can infer that its argument
+ %% is a list on success.
+ sub_unsafe(any, [list]);
+types(lists, subtract, [_,_]) ->
+ sub_unsafe(list, [list, list]);
+
+%% Functions returning booleans.
+types(lists, all, [_,_]) ->
+ sub_unsafe(beam_types:make_boolean(), [#t_fun{arity=1}, list]);
+types(lists, any, [_,_]) ->
+ sub_unsafe(beam_types:make_boolean(), [#t_fun{arity=1}, list]);
+types(lists, keymember, [_,_,_]) ->
+ sub_unsafe(beam_types:make_boolean(), [any, #t_integer{}, list]);
+types(lists, member, [_,_]) ->
+ sub_unsafe(beam_types:make_boolean(), [any, list]);
+types(lists, prefix, [_,_]) ->
+ sub_unsafe(beam_types:make_boolean(), [list, list]);
+types(lists, suffix, [_,_]) ->
+ sub_unsafe(beam_types:make_boolean(), [list, list]);
+
+%% Functions returning plain lists.
+types(lists, dropwhile, [_,_]) ->
+ sub_unsafe(list, [#t_fun{arity=1}, list]);
+types(lists, duplicate, [_,_]) ->
+ sub_unsafe(list, [#t_integer{}, any]);
+types(lists, filter, [_,_]) ->
+ sub_unsafe(list, [#t_fun{arity=1}, list]);
+types(lists, flatten, [_]) ->
+ sub_unsafe(list, [list]);
+types(lists, map, [_Fun, List]) ->
+ sub_unsafe(same_length_type(List), [#t_fun{arity=1}, list]);
+types(lists, reverse, [List]) ->
+ sub_unsafe(same_length_type(List), [list]);
+types(lists, sort, [List]) ->
+ sub_unsafe(same_length_type(List), [list]);
+types(lists, takewhile, [_,_]) ->
+ sub_unsafe(list, [#t_fun{arity=1}, list]);
+types(lists, usort, [List]) ->
+ sub_unsafe(same_length_type(List), [list]);
+types(lists, zip, [A,B]) ->
+ ZipType = lists_zip_type([A,B]),
+ sub_unsafe(ZipType, [ZipType, ZipType]);
+types(lists, zip3, [A,B,C]) ->
+ ZipType = lists_zip_type([A,B,C]),
+ sub_unsafe(ZipType, [ZipType, ZipType, ZipType]);
+types(lists, zipwith, [_,A,B]) ->
+ ZipType = lists_zip_type([A,B]),
+ sub_unsafe(ZipType, [#t_fun{arity=2}, ZipType, ZipType]);
+types(lists, zipwith3, [_,A,B,C]) ->
+ ZipType = lists_zip_type([A,B,C]),
+ sub_unsafe(ZipType, [#t_fun{arity=3}, ZipType, ZipType, ZipType]);
+
+%% Functions with complex return values.
+types(lists, partition, [_,_]) ->
+ sub_unsafe(make_two_tuple(list, list), [#t_fun{arity=1}, list]);
+types(lists, MapFold, [_Fun, _Init, List])
+ when MapFold =:= mapfoldl; MapFold =:= mapfoldr ->
+ ListType = same_length_type(List),
+ RetType = #t_tuple{size=2,
+ exact=true,
+ elements=#{ 1 => ListType }},
+ sub_unsafe(RetType, [#t_fun{arity=2}, any, list]);
+types(lists, splitwith, [_,_]) ->
+ sub_unsafe(make_two_tuple(list, list), [#t_fun{arity=1}, list]);
+types(lists, unzip, [List]) ->
+ ListType = same_length_type(List),
+ RetType = make_two_tuple(ListType, ListType),
+ sub_unsafe(RetType, [list]);
+
+%% Catch-all clause for unknown functions.
+
+types(_, _, Args) ->
+ sub_unsafe(any, [any || _ <- Args]).
+
+%%
+%% Helpers
+%%
+
+sub_unsafe(none, ArgTypes) ->
+ %% This is known to fail at runtime, but the type optimization pass
+ %% doesn't yet support cutting a block short at any point, so we
+ %% pretend it's raining instead.
+ %%
+ %% Actual exit BIFs get special treatment in the catch-all clause
+ %% for the 'erlang' module.
+ sub_unsafe(any, ArgTypes);
+sub_unsafe(RetType, ArgTypes) ->
+ {RetType, ArgTypes, false}.
+
+sub_safe(RetType, ArgTypes) ->
+ {RetType, ArgTypes, true}.
+
+mixed_arith_types([FirstType | _]=Args0) ->
+ RetType = foldl(fun(#t_integer{}, #t_integer{}) -> #t_integer{};
+ (#t_integer{}, number) -> number;
+ (#t_integer{}, float) -> float;
+ (float, #t_integer{}) -> float;
+ (float, number) -> float;
+ (float, float) -> float;
+ (number, #t_integer{}) -> number;
+ (number, float) -> float;
+ (number, number) -> number;
+ (any, _) -> number;
+ (_, _) -> none
+ end, FirstType, Args0),
+ sub_unsafe(RetType, [number || _ <- Args0]).
+
+band_return_type([#t_integer{elements={Int,Int}}, RHS]) when is_integer(Int) ->
+ band_return_type_1(RHS, Int);
+band_return_type([LHS, #t_integer{elements={Int,Int}}]) when is_integer(Int) ->
+ band_return_type_1(LHS, Int);
+band_return_type(_) ->
+ #t_integer{}.
+
+band_return_type_1(LHS, Int) ->
+ case LHS of
+ #t_integer{elements={Min0,Max0}} when Max0 - Min0 < 1 bsl 256 ->
+ {Intersection, Union} = range_masks(Min0, Max0),
+
+ Min = Intersection band Int,
+ Max = min(Max0, Union band Int),
+
+ #t_integer{elements={Min,Max}};
+ _ when Int >= 0 ->
+ %% The range is either unknown or too wide, conservatively assume
+ %% that the new range is 0 .. Int.
+ #t_integer{elements={0,Int}};
+ _ when Int < 0 ->
+ %% We can't infer boundaries when the range is unknown and the
+ %% other operand is a negative number, as the latter sign-extends
+ %% to infinity and we can't express an inverted range at the
+ %% moment (cf. X band -8; either less than -7 or greater than 7).
+ #t_integer{}
+ end.
+
+%% Returns two bitmasks describing all possible values between From and To.
+%%
+%% The first contains the bits that are common to all values, and the second
+%% contains the bits that are set by any value in the range.
+range_masks(From, To) when From =< To ->
+ range_masks_1(From, To, 0, -1, 0).
+
+range_masks_1(From, To, BitPos, Intersection, Union) when From < To ->
+ range_masks_1(From + (1 bsl BitPos), To, BitPos + 1,
+ Intersection band From, Union bor From);
+range_masks_1(_From, To, _BitPos, Intersection0, Union0) ->
+ Intersection = To band Intersection0,
+ Union = To bor Union0,
+ {Intersection, Union}.
+
+discard_tuple_element_info(Min, Max, Es) ->
+ foldl(fun(El, Acc) when Min =< El, El =< Max ->
+ maps:remove(El, Acc);
+ (_El, Acc) -> Acc
+ end, Es, maps:keys(Es)).
+
+%% For a lists function that return a list of the same length as the input
+%% list, return the type of the list.
+same_length_type(cons) -> cons;
+same_length_type(nil) -> nil;
+same_length_type(_) -> list.
+
+%% lists:zip/2 and friends only succeed when all arguments have the same
+%% length, so if one of them is cons, we can infer that all of them are cons
+%% on success.
+lists_zip_type(Types) ->
+ foldl(fun(cons, _) -> cons;
+ (_, cons) -> cons;
+ (nil, _) -> nil;
+ (_, T) -> T
+ end, list, Types).
+
+make_two_tuple(Type1, Type2) ->
+ #t_tuple{size=2,exact=true,
+ elements=#{1=>Type1,2=>Type2}}.
diff --git a/lib/compiler/src/beam_clean.erl b/lib/compiler/src/beam_clean.erl
index 7299654476..6b2b2ce085 100644
--- a/lib/compiler/src/beam_clean.erl
+++ b/lib/compiler/src/beam_clean.erl
@@ -34,7 +34,8 @@ module({Mod,Exp,Attr,Fs0,_}, Opts) ->
Used = find_all_used(WorkList, All, cerl_sets:from_list(WorkList)),
Fs1 = remove_unused(Order, Used, All),
{Fs2,Lc} = clean_labels(Fs1),
- Fs = maybe_remove_lines(Fs2, Opts),
+ Fs3 = fix_swap(Fs2, Opts),
+ Fs = maybe_remove_lines(Fs3, Opts),
{ok,{Mod,Exp,Attr,Fs,Lc}}.
%% Determine the rootset, i.e. exported functions and
@@ -137,31 +138,54 @@ function_replace([{function,Name,Arity,Entry,Asm0}|Fs], Dict, Acc) ->
function_replace([], _, Acc) -> Acc.
%%%
+%%% If compatibility with a previous release (OTP 22 or earlier) has
+%%% been requested, replace swap instructions with a sequence of moves.
+%%%
+
+fix_swap(Fs, Opts) ->
+ case proplists:get_bool(no_swap, Opts) of
+ false -> Fs;
+ true -> fold_functions(fun swap_moves/1, Fs)
+ end.
+
+swap_moves([{swap,Reg1,Reg2}|Is]) ->
+ Temp = {x,1022},
+ [{move,Reg1,Temp},{move,Reg2,Reg1},{move,Temp,Reg2}|swap_moves(Is)];
+swap_moves([I|Is]) ->
+ [I|swap_moves(Is)];
+swap_moves([]) -> [].
+
+%%%
%%% Remove line instructions if requested.
%%%
maybe_remove_lines(Fs, Opts) ->
case proplists:get_bool(no_line_info, Opts) of
false -> Fs;
- true -> remove_lines(Fs)
+ true -> fold_functions(fun remove_lines/1, Fs)
end.
-remove_lines([{function,N,A,Lbl,Is0}|T]) ->
- Is = remove_lines_fun(Is0),
- [{function,N,A,Lbl,Is}|remove_lines(T)];
-remove_lines([]) -> [].
-
-remove_lines_fun([{line,_}|Is]) ->
- remove_lines_fun(Is);
-remove_lines_fun([{block,Bl0}|Is]) ->
+remove_lines([{line,_}|Is]) ->
+ remove_lines(Is);
+remove_lines([{block,Bl0}|Is]) ->
Bl = remove_lines_block(Bl0),
- [{block,Bl}|remove_lines_fun(Is)];
-remove_lines_fun([I|Is]) ->
- [I|remove_lines_fun(Is)];
-remove_lines_fun([]) -> [].
+ [{block,Bl}|remove_lines(Is)];
+remove_lines([I|Is]) ->
+ [I|remove_lines(Is)];
+remove_lines([]) -> [].
remove_lines_block([{set,_,_,{line,_}}|Is]) ->
remove_lines_block(Is);
remove_lines_block([I|Is]) ->
[I|remove_lines_block(Is)];
remove_lines_block([]) -> [].
+
+
+%%%
+%%% Helpers.
+%%%
+
+fold_functions(F, [{function,N,A,Lbl,Is0}|T]) ->
+ Is = F(Is0),
+ [{function,N,A,Lbl,Is}|fold_functions(F, T)];
+fold_functions(_F, []) -> [].
diff --git a/lib/compiler/src/beam_dict.erl b/lib/compiler/src/beam_dict.erl
index b2056332e6..4d0cec857d 100644
--- a/lib/compiler/src/beam_dict.erl
+++ b/lib/compiler/src/beam_dict.erl
@@ -40,6 +40,7 @@
-type lambda_info() :: {label(),{index(),label(),non_neg_integer()}}.
-type lambda_tab() :: {non_neg_integer(),[lambda_info()]}.
+-type wrapper() :: #{label() => index()}.
-record(asm,
{atoms = #{} :: atom_tab(),
@@ -48,6 +49,7 @@
imports = gb_trees:empty() :: import_tab(),
strings = <<>> :: binary(), %String pool
lambdas = {0,[]} :: lambda_tab(),
+ wrappers = #{} :: wrapper(),
literals = dict:new() :: literal_tab(),
fnames = #{} :: fname_tab(),
lines = #{} :: line_tab(),
@@ -147,11 +149,21 @@ string(BinString, Dict) when is_binary(BinString) ->
-spec lambda(label(), non_neg_integer(), bdict()) ->
{non_neg_integer(), bdict()}.
-lambda(Lbl, NumFree, #asm{lambdas={OldIndex,Lambdas0}}=Dict) ->
- %% Set Index the same as OldIndex.
- Index = OldIndex,
- Lambdas = [{Lbl,{Index,Lbl,NumFree}}|Lambdas0],
- {OldIndex,Dict#asm{lambdas={OldIndex+1,Lambdas}}}.
+lambda(Lbl, NumFree, #asm{wrappers=Wrappers0,
+ lambdas={OldIndex,Lambdas0}}=Dict) ->
+ case Wrappers0 of
+ #{Lbl:=Index} ->
+ %% OTP 23: There old is a fun entry for this wrapper function.
+ %% Share the fun entry.
+ {Index,Dict};
+ #{} ->
+ %% Set Index the same as OldIndex.
+ Index = OldIndex,
+ Wrappers = Wrappers0#{Lbl=>Index},
+ Lambdas = [{Lbl,{Index,Lbl,NumFree}}|Lambdas0],
+ {OldIndex,Dict#asm{wrappers=Wrappers,
+ lambdas={OldIndex+1,Lambdas}}}
+ end.
%% Returns the index for a literal (adding it to the literal table if necessary).
%% literal(Literal, Dict) -> {Index,Dict'}
diff --git a/lib/compiler/src/beam_disasm.erl b/lib/compiler/src/beam_disasm.erl
index 7d048716e4..45b69d7e95 100644
--- a/lib/compiler/src/beam_disasm.erl
+++ b/lib/compiler/src/beam_disasm.erl
@@ -1123,6 +1123,13 @@ resolve_inst({put_tuple2,[Dst,{{z,1},{u,_},List0}]},_,_,_) ->
{put_tuple2,Dst,{list,List}};
%%
+%% OTP 23.
+%%
+resolve_inst({swap,[_,_]=List},_,_,_) ->
+ [R1,R2] = resolve_args(List),
+ {swap,R1,R2};
+
+%%
%% Catches instructions that are not yet handled.
%%
resolve_inst(X,_,_,_) -> ?exit({resolve_inst,X}).
diff --git a/lib/compiler/src/beam_except.erl b/lib/compiler/src/beam_except.erl
deleted file mode 100644
index 2305502800..0000000000
--- a/lib/compiler/src/beam_except.erl
+++ /dev/null
@@ -1,247 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2011-2018. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-
--module(beam_except).
--export([module/2]).
-
-%%% Rewrite certain calls to erlang:error/{1,2} to specialized
-%%% instructions:
-%%%
-%%% erlang:error({badmatch,Value}) => badmatch Value
-%%% erlang:error({case_clause,Value}) => case_end Value
-%%% erlang:error({try_clause,Value}) => try_case_end Value
-%%% erlang:error(if_clause) => if_end
-%%% erlang:error(function_clause, Args) => jump FuncInfoLabel
-%%%
-
--import(lists, [reverse/1,reverse/2,seq/2,splitwith/2]).
-
--spec module(beam_utils:module_code(), [compile:option()]) ->
- {'ok',beam_utils:module_code()}.
-
-module({Mod,Exp,Attr,Fs0,Lc}, _Opt) ->
- Fs = [function(F) || F <- Fs0],
- {ok,{Mod,Exp,Attr,Fs,Lc}}.
-
-function({function,Name,Arity,CLabel,Is0}) ->
- try
- Is = function_1(Is0),
- {function,Name,Arity,CLabel,Is}
- catch
- Class:Error:Stack ->
- io:fwrite("Function: ~w/~w\n", [Name,Arity]),
- erlang:raise(Class, Error, Stack)
- end.
-
--record(st,
- {lbl :: beam_asm:label(), %func_info label
- loc :: [_], %location for func_info
- arity :: arity() %arity for function
- }).
-
-function_1(Is0) ->
- case Is0 of
- [{label,Lbl},{line,Loc},{func_info,_,_,Arity}|_] ->
- St = #st{lbl=Lbl,loc=Loc,arity=Arity},
- translate(Is0, St, []);
- [{label,_}|_] ->
- %% No line numbers. The source must be a .S file.
- %% There is no need to do anything.
- Is0
- end.
-
-translate([{call_ext,Ar,{extfunc,erlang,error,Ar}}=I|Is], St, Acc) ->
- translate_1(Ar, I, Is, St, Acc);
-translate([I|Is], St, Acc) ->
- translate(Is, St, [I|Acc]);
-translate([], _, Acc) ->
- reverse(Acc).
-
-translate_1(Ar, I, Is, #st{arity=Arity}=St, [{line,_}=Line|Acc1]=Acc0) ->
- case dig_out(Ar, Arity, Acc1) of
- no ->
- translate(Is, St, [I|Acc0]);
- {yes,function_clause,Acc2} ->
- case {Is,Line,St} of
- {[return|_],{line,Loc},#st{lbl=Fi,loc=Loc}} ->
- Instr = {jump,{f,Fi}},
- translate(Is, St, [Instr|Acc2]);
- {_,_,_} ->
- %% Not a call_only instruction, or not the same
- %% location information as in in the line instruction
- %% before the func_info instruction. Not safe
- %% to translate to a jump.
- translate(Is, St, [I|Acc0])
- end;
- {yes,Instr,Acc2} ->
- translate(Is, St, [Instr,Line|Acc2])
- end.
-
-dig_out(1, _Arity, Is) ->
- dig_out(Is);
-dig_out(2, Arity, Is) ->
- dig_out_fc(Arity, Is);
-dig_out(_, _, _) -> no.
-
-dig_out([{block,Bl0}|Is]) ->
- case dig_out_block(reverse(Bl0)) of
- no -> no;
- {yes,What,[]} ->
- {yes,What,Is};
- {yes,What,Bl} ->
- {yes,What,[{block,Bl}|Is]}
- end;
-dig_out(_) -> no.
-
-dig_out_block([{set,[{x,0}],[{atom,if_clause}],move}]) ->
- {yes,if_end,[]};
-dig_out_block([{set,[{x,0}],[{literal,{Exc,Value}}],move}|Is]) ->
- translate_exception(Exc, {literal,Value}, Is, 0);
-dig_out_block([{set,[{x,0}],[{atom,Exc},Value],put_tuple2}|Is]) ->
- translate_exception(Exc, Value, Is, 3);
-dig_out_block(_) -> no.
-
-translate_exception(badmatch, Val, Is, Words) ->
- {yes,{badmatch,Val},fix_block(Is, Words)};
-translate_exception(case_clause, Val, Is, Words) ->
- {yes,{case_end,Val},fix_block(Is, Words)};
-translate_exception(try_clause, Val, Is, Words) ->
- {yes,{try_case_end,Val},fix_block(Is, Words)};
-translate_exception(_, _, _, _) -> no.
-
-fix_block(Is, 0) ->
- reverse(Is);
-fix_block(Is, Words) ->
- reverse(fix_block_1(Is, Words)).
-
-fix_block_1([{set,[],[],{alloc,Live,{F1,F2,Needed0,F3}}}|Is], Words) ->
- case Needed0 - Words of
- 0 ->
- Is;
- Needed ->
- true = Needed >= 0, %Assertion.
- [{set,[],[],{alloc,Live,{F1,F2,Needed,F3}}}|Is]
- end;
-fix_block_1([I|Is], Words) ->
- [I|fix_block_1(Is, Words)];
-fix_block_1([], _Words) ->
- %% Rare. The heap allocation was probably done by a binary
- %% construction instruction.
- [].
-
-dig_out_fc(Arity, Is0) ->
- Regs0 = maps:from_list([{{x,X},{arg,X}} || X <- seq(0, Arity-1)]),
- {Is,Acc0} = splitwith(fun({label,_}) -> false;
- ({test,_,_,_}) -> false;
- (_) -> true
- end, Is0),
- {Regs,Acc} = dig_out_fc_1(reverse(Is), Regs0, Acc0),
- case Regs of
- #{{x,0}:={atom,function_clause},{x,1}:=Args} ->
- case moves_from_stack(Args, 0, []) of
- {Moves,Arity} ->
- {yes,function_clause,reverse(Moves, Acc)};
- {_,_} ->
- no
- end;
- #{} ->
- no
- end.
-
-dig_out_fc_1([{block,Bl}|Is], Regs0, Acc) ->
- Regs = dig_out_fc_block(Bl, Regs0),
- dig_out_fc_1(Is, Regs, Acc);
-dig_out_fc_1([{bs_set_position,_,_}=I|Is], Regs, Acc) ->
- dig_out_fc_1(Is, Regs, [I|Acc]);
-dig_out_fc_1([{bs_get_tail,Src,Dst,Live0}|Is], Regs0, Acc) ->
- Regs = prune_xregs(Live0, Regs0),
- Live = dig_out_stack_live(Regs, Live0),
- I = {bs_get_tail,Src,Dst,Live},
- dig_out_fc_1(Is, Regs, [I|Acc]);
-dig_out_fc_1([_|_], _Regs, _Acc) ->
- {#{},[]};
-dig_out_fc_1([], Regs, Acc) ->
- {Regs,Acc}.
-
-dig_out_fc_block([{set,[],[],{alloc,Live,_}}|Is], Regs0) ->
- Regs = prune_xregs(Live, Regs0),
- dig_out_fc_block(Is, Regs);
-dig_out_fc_block([{set,[Dst],[Hd,Tl],put_list}|Is], Regs0) ->
- Regs = Regs0#{Dst=>{cons,get_reg(Hd, Regs0),get_reg(Tl, Regs0)}},
- dig_out_fc_block(Is, Regs);
-dig_out_fc_block([{set,[Dst],[Src],move}|Is], Regs0) ->
- Regs = Regs0#{Dst=>get_reg(Src, Regs0)},
- dig_out_fc_block(Is, Regs);
-dig_out_fc_block([{set,_,_,_}|_], _Regs) ->
- %% Unknown instruction. Fail.
- #{};
-dig_out_fc_block([], Regs) -> Regs.
-
-dig_out_stack_live(Regs, Default) ->
- Reg = {x,2},
- case Regs of
- #{Reg:=List} ->
- dig_out_stack_live_1(List, Default);
- #{} ->
- Default
- end.
-
-dig_out_stack_live_1({cons,{arg,N},T}, Live) ->
- dig_out_stack_live_1(T, max(N + 1, Live));
-dig_out_stack_live_1({cons,_,T}, Live) ->
- dig_out_stack_live_1(T, Live);
-dig_out_stack_live_1(nil, Live) ->
- Live;
-dig_out_stack_live_1(_, Live) -> Live.
-
-prune_xregs(Live, Regs) ->
- maps:filter(fun({x,X}, _) -> X < Live end, Regs).
-
-moves_from_stack({cons,{arg,N},_}, I, _Acc) when N =/= I ->
- %% Wrong argument. Give up.
- {[],-1};
-moves_from_stack({cons,H,T}, I, Acc) ->
- case H of
- {arg,I} ->
- moves_from_stack(T, I+1, Acc);
- _ ->
- moves_from_stack(T, I+1, [{move,H,{x,I}}|Acc])
- end;
-moves_from_stack(nil, I, Acc) ->
- {reverse(Acc),I};
-moves_from_stack({literal,[H|T]}, I, Acc) ->
- Cons = {cons,tag_literal(H),tag_literal(T)},
- moves_from_stack(Cons, I, Acc);
-moves_from_stack(_, _, _) ->
- %% Not understood. Give up.
- {[],-1}.
-
-
-get_reg(R, Regs) ->
- case Regs of
- #{R:=Val} -> Val;
- #{} -> R
- end.
-
-tag_literal([]) -> nil;
-tag_literal(T) when is_atom(T) -> {atom,T};
-tag_literal(T) when is_float(T) -> {float,T};
-tag_literal(T) when is_integer(T) -> {integer,T};
-tag_literal(T) -> {literal,T}.
diff --git a/lib/compiler/src/beam_ssa.erl b/lib/compiler/src/beam_ssa.erl
index a9977b0b1d..831e6489a9 100644
--- a/lib/compiler/src/beam_ssa.erl
+++ b/lib/compiler/src/beam_ssa.erl
@@ -79,7 +79,7 @@
-type var_base() :: atom() | non_neg_integer().
-type literal_value() :: atom() | integer() | float() | list() |
- nil() | tuple() | map() | binary().
+ nil() | tuple() | map() | binary() | fun().
-type op() :: {'bif',atom()} | {'float',float_op()} | prim_op() | cg_prim_op().
-type anno() :: #{atom() := any()}.
@@ -118,7 +118,7 @@
%% Primops only used internally during code generation.
-type cg_prim_op() :: 'bs_get' | 'bs_match_string' | 'bs_restore' | 'bs_skip' |
- 'copy' | 'put_tuple_arity' | 'put_tuple_element' |
+ 'copy' | 'match_fail' | 'put_tuple_arity' | 'put_tuple_element' |
'set_tuple_element'.
-import(lists, [foldl/3,keyfind/3,mapfoldl/3,member/2,reverse/1]).
diff --git a/lib/compiler/src/beam_ssa_bsm.erl b/lib/compiler/src/beam_ssa_bsm.erl
index 382e6f635e..abbda2ebe4 100644
--- a/lib/compiler/src/beam_ssa_bsm.erl
+++ b/lib/compiler/src/beam_ssa_bsm.erl
@@ -57,6 +57,7 @@
-export([module/2, format_error/1]).
-include("beam_ssa.hrl").
+-include("beam_types.hrl").
-import(lists, [member/2, reverse/1, splitwith/2, map/2, foldl/3, mapfoldl/3,
nth/2, max/1, unzip/1]).
@@ -879,8 +880,7 @@ annotate_context_parameters(F, ModInfo) ->
%% Assertion.
error(conflicting_parameter_types);
(K, suitable_for_reuse, Acc) ->
- T = beam_validator:type_anno(match_context),
- Acc#{ K => T };
+ Acc#{ K => #t_bs_context{} };
(_K, _V, Acc) ->
Acc
end, TypeAnno0, ParamInfo),
diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl
index 07f4c8b461..7248aca5f3 100644
--- a/lib/compiler/src/beam_ssa_codegen.erl
+++ b/lib/compiler/src/beam_ssa_codegen.erl
@@ -28,7 +28,7 @@
-include("beam_ssa.hrl").
--import(lists, [foldl/3,keymember/3,keysort/2,last/1,map/2,mapfoldl/3,
+-import(lists, [foldl/3,keymember/3,keysort/2,map/2,mapfoldl/3,
reverse/1,reverse/2,sort/1,splitwith/2,takewhile/2]).
-record(cg, {lcount=1 :: beam_label(), %Label counter
@@ -37,7 +37,8 @@
used_labels=gb_sets:empty() :: gb_sets:set(ssa_label()),
regs=#{} :: #{beam_ssa:var_name()=>ssa_register()},
ultimate_fail=1 :: beam_label(),
- catches=gb_sets:empty() :: gb_sets:set(ssa_label())
+ catches=gb_sets:empty() :: gb_sets:set(ssa_label()),
+ fc_label=1 :: beam_label()
}).
-spec module(beam_ssa:b_module(), [compile:option()]) ->
@@ -124,7 +125,7 @@ function(#b_function{anno=Anno,bs=Blocks}, AtomMod, St0) ->
Labels = (St4#cg.labels)#{0=>Entry,?BADARG_BLOCK=>0},
St5 = St4#cg{labels=Labels,used_labels=gb_sets:singleton(Entry),
ultimate_fail=Ult},
- {Body,St} = cg_fun(Blocks, St5),
+ {Body,St} = cg_fun(Blocks, St5#cg{fc_label=Fi}),
Asm = [{label,Fi},line(Anno),
{func_info,AtomMod,{atom,Name},Arity}] ++
add_parameter_annos(Body, Anno) ++
@@ -384,6 +385,7 @@ classify_heap_need(is_tagged_tuple) -> neutral;
classify_heap_need(kill_try_tag) -> gc;
classify_heap_need(landingpad) -> gc;
classify_heap_need(make_fun) -> gc;
+classify_heap_need(match_fail) -> gc;
classify_heap_need(new_try_tag) -> gc;
classify_heap_need(peek_message) -> gc;
classify_heap_need(put_map) -> gc;
@@ -1168,6 +1170,10 @@ cg_block([#cg_set{op=call}=I,
#cg_set{op=succeeded,dst=Bool}], {Bool,_Fail}, St) ->
%% A call in try/catch block.
cg_block([I], none, St);
+cg_block([#cg_set{op=match_fail}=I,
+ #cg_set{op=succeeded,dst=Bool}], {Bool,_Fail}, St) ->
+ %% A match_fail instruction in a try/catch block.
+ cg_block([I], none, St);
cg_block([#cg_set{op=get_map_element,dst=Dst0,args=Args0},
#cg_set{op=succeeded,dst=Bool}], {Bool,Fail0}, St) ->
[Dst,Map,Key] = beam_args([Dst0|Args0], St),
@@ -1229,6 +1235,28 @@ cg_block([#cg_set{op=copy}|_]=T0, Context, St0) ->
no ->
{Is,St}
end;
+cg_block([#cg_set{op=match_fail,args=Args0,anno=Anno}], none, St) ->
+ Args = beam_args(Args0, St),
+ Is = cg_match_fail(Args, line(Anno), none),
+ {Is,St};
+cg_block([#cg_set{op=match_fail,args=Args0,anno=Anno}|T], Context, St0) ->
+ FcLabel = case Context of
+ {return,_,none} ->
+ %% There is no stack frame. If this is a function_clause
+ %% exception, it is safe to jump to the label of the
+ %% func_info instruction.
+ St0#cg.fc_label;
+ _ ->
+ %% This is most probably not a function_clause.
+ %% If this is a function_clause exception
+ %% (rare), it is not safe to jump to the
+ %% func_info label.
+ none
+ end,
+ Args = beam_args(Args0, St0),
+ Is0 = cg_match_fail(Args, line(Anno), FcLabel),
+ {Is1,St} = cg_block(T, Context, St0),
+ {Is0++Is1,St};
cg_block([#cg_set{op=Op,dst=Dst0,args=Args0}=Set], none, St) ->
[Dst|Args] = beam_args([Dst0|Args0], St),
Is = cg_instr(Op, Args, Dst, Set),
@@ -1260,8 +1288,7 @@ cg_copy(T0, St) ->
end, T0),
Moves0 = cg_copy_1(Copies, St),
Moves1 = [Move || {move,Src,Dst}=Move <- Moves0, Src =/= Dst],
- Scratch = {x,1022},
- Moves = order_moves(Moves1, Scratch),
+ Moves = order_moves(Moves1),
{Moves,T}.
cg_copy_1([#cg_set{dst=Dst0,args=Args}|T], St) ->
@@ -1502,6 +1529,42 @@ cg_call(#cg_set{anno=Anno,op=call,dst=Dst0,args=Args0},
Is = setup_args(Args++[Func], Anno, Context, St) ++ Line ++ Call,
{Is,St}.
+cg_match_fail([{atom,function_clause}|Args], Line, Fc) ->
+ case Fc of
+ none ->
+ %% There is a stack frame (probably because of inlining).
+ %% Jumping to the func_info label is not allowed by
+ %% beam_validator. Rewrite the instruction as a call to
+ %% erlang:error/2.
+ make_fc(Args, Line);
+ _ ->
+ setup_args(Args) ++ [{jump,{f,Fc}}]
+ end;
+cg_match_fail([{atom,Op}], Line, _Fc) ->
+ [Line,Op];
+cg_match_fail([{atom,Op},Val], Line, _Fc) ->
+ [Line,{Op,Val}].
+
+make_fc(Args, Line) ->
+ %% Recreate the original call to erlang:error/2.
+ Live = foldl(fun({x,X}, A) -> max(X+1, A);
+ (_, A) -> A
+ end, 0, Args),
+ TmpReg = {x,Live},
+ StkMoves = build_stk(reverse(Args), TmpReg, nil),
+ [{test_heap,2*length(Args),Live}|StkMoves] ++
+ [{move,{atom,function_clause},{x,0}},
+ Line,
+ {call_ext,2,{extfunc,erlang,error,2}}].
+
+build_stk([V], _TmpReg, Tail) ->
+ [{put_list,V,Tail,{x,1}}];
+build_stk([V|Vs], TmpReg, Tail) ->
+ I = {put_list,V,Tail,TmpReg},
+ [I|build_stk(Vs, TmpReg, TmpReg)];
+build_stk([], _TmpReg, nil) ->
+ [{move,nil,{x,1}}].
+
build_call(call_fun, Arity, _Func, none, Dst) ->
[{call_fun,Arity}|copy({x,0}, Dst)];
build_call(call_fun, Arity, _Func, {return,Dst,N}, Dst) when is_integer(N) ->
@@ -1540,15 +1603,15 @@ build_apply(Arity, {return,Val,N}, _Dst) when is_integer(N) ->
build_apply(Arity, none, Dst) ->
[{apply,Arity}|copy({x,0}, Dst)].
-cg_instr(put_map, [{atom,assoc},SrcMap|Ss], Dst, Set) ->
- Live = get_live(Set),
- [{put_map_assoc,{f,0},SrcMap,Dst,Live,{list,Ss}}];
cg_instr(bs_get_tail, [Src], Dst, Set) ->
Live = get_live(Set),
[{bs_get_tail,Src,Dst,Live}];
cg_instr(bs_get_position, [Ctx], Dst, Set) ->
Live = get_live(Set),
[{bs_get_position,Ctx,Dst,Live}];
+cg_instr(put_map, [{atom,assoc},SrcMap|Ss], Dst, Set) ->
+ Live = get_live(Set),
+ [{put_map_assoc,{f,0},SrcMap,Dst,Live,{list,Ss}}];
cg_instr(Op, Args, Dst, _Set) ->
cg_instr(Op, Args, Dst).
@@ -1718,7 +1781,7 @@ cg_catch(Agg, T0, Context, St0) ->
cg_try(Agg, Tag, T0, Context, St0) ->
{Moves0,T1} = cg_extract(T0, Agg, St0),
- Moves = order_moves(Moves0, {x,3}),
+ Moves = order_moves(Moves0),
[#cg_set{op=kill_try_tag}|T2] = T1,
{T,St} = cg_block(T2, Context, St0),
{[{try_case,Tag}|Moves++T],St}.
@@ -1874,8 +1937,7 @@ setup_args([]) ->
[];
setup_args([_|_]=Args) ->
Moves = gen_moves(Args, 0, []),
- Scratch = {x,1+last(sort([length(Args)-1|[X || {x,X} <- Args]]))},
- order_moves(Moves, Scratch).
+ order_moves(Moves).
%% kill_yregs(Anno, #cg{}) -> [{kill,{y,Y}}].
%% Kill Y registers that will not be used again.
@@ -1895,47 +1957,48 @@ gen_moves([A|As], I, Acc) ->
gen_moves([], _, Acc) ->
keysort(3, Acc).
-%% order_moves([Move], ScratchReg) -> [Move]
+%% order_moves([Move]) -> [Move]
%% Orders move instruction so that source registers are not
%% destroyed before they are used. If there are cycles
%% (such as {move,{x,0},{x,1}}, {move,{x,1},{x,1}}),
-%% the scratch register is used to break up the cycle.
-%% If possible, the first move of the input list is placed
+%% swap instructions will be used to break up the cycle.
+%%
+%% If possible, the first move of the input list is placed
%% last in the result list (to make the move to {x,0} occur
%% just before the call to allow the Beam loader to coalesce
%% the instructions).
-order_moves(Ms, Scr) -> order_moves(Ms, Scr, []).
+order_moves(Ms) -> order_moves(Ms, []).
-order_moves([{move,_,_}=M|Ms0], ScrReg, Acc0) ->
- {Chain,Ms} = collect_chain(Ms0, [M], ScrReg),
+order_moves([{move,_,_}=M|Ms0], Acc0) ->
+ {Chain,Ms} = collect_chain(Ms0, [M]),
Acc = reverse(Chain, Acc0),
- order_moves(Ms, ScrReg, Acc);
-order_moves([], _, Acc) -> Acc.
+ order_moves(Ms, Acc);
+order_moves([], Acc) -> Acc.
-collect_chain(Ms, Path, ScrReg) ->
- collect_chain(Ms, Path, [], ScrReg).
+collect_chain(Ms, Path) ->
+ collect_chain(Ms, Path, []).
-collect_chain([{move,Src,Same}=M|Ms0], [{move,Same,_}|_]=Path, Others, ScrReg) ->
+collect_chain([{move,Src,Same}=M|Ms0], [{move,Same,_}|_]=Path, Others) ->
case keymember(Src, 3, Path) of
false ->
- collect_chain(reverse(Others, Ms0), [M|Path], [], ScrReg);
+ collect_chain(reverse(Others, Ms0), [M|Path], []);
true ->
- %% There is a cycle, which we must break up.
- {break_up_cycle(M, Path, ScrReg),reverse(Others, Ms0)}
+ %% There is a cycle.
+ {break_up_cycle(M, Path),reverse(Others, Ms0)}
end;
-collect_chain([M|Ms], Path, Others, ScrReg) ->
- collect_chain(Ms, Path, [M|Others], ScrReg);
-collect_chain([], Path, Others, _) ->
+collect_chain([M|Ms], Path, Others) ->
+ collect_chain(Ms, Path, [M|Others]);
+collect_chain([], Path, Others) ->
{Path,Others}.
-break_up_cycle({move,Src,_}=M, Path, ScrReg) ->
- [{move,ScrReg,Src},M|break_up_cycle1(Src, Path, ScrReg)].
+break_up_cycle({move,Src,_Dst}=M, Path) ->
+ break_up_cycle_1(Src, [M|Path], []).
-break_up_cycle1(Dst, [{move,Src,Dst}|Path], ScrReg) ->
- [{move,Src,ScrReg}|Path];
-break_up_cycle1(Dst, [M|Path], LastMove) ->
- [M|break_up_cycle1(Dst, Path, LastMove)].
+break_up_cycle_1(Dst, [{move,_Src,Dst}|Path], Acc) ->
+ reverse(Acc, Path);
+break_up_cycle_1(Dst, [{move,S,D}|Path], Acc) ->
+ break_up_cycle_1(Dst, Path, [{swap,S,D}|Acc]).
%%%
%%% General utility functions.
diff --git a/lib/compiler/src/beam_ssa_dead.erl b/lib/compiler/src/beam_ssa_dead.erl
index 64b9b3e222..88767456a3 100644
--- a/lib/compiler/src/beam_ssa_dead.erl
+++ b/lib/compiler/src/beam_ssa_dead.erl
@@ -730,8 +730,8 @@ will_succeed_1('=/=', A, '=:=', B) when A =:= B -> no;
will_succeed_1('<', A, '=:=', B) when B >= A -> no;
will_succeed_1('<', A, '=/=', B) when B >= A -> yes;
will_succeed_1('<', A, '<', B) when B >= A -> yes;
-will_succeed_1('<', A, '=<', B) when B > A -> yes;
-will_succeed_1('<', A, '>=', B) when B > A -> no;
+will_succeed_1('<', A, '=<', B) when B >= A -> yes;
+will_succeed_1('<', A, '>=', B) when B >= A -> no;
will_succeed_1('<', A, '>', B) when B >= A -> no;
will_succeed_1('=<', A, '=:=', B) when B > A -> no;
@@ -751,9 +751,9 @@ will_succeed_1('>=', A, '>', B) when B < A -> yes;
will_succeed_1('>', A, '=:=', B) when B =< A -> no;
will_succeed_1('>', A, '=/=', B) when B =< A -> yes;
will_succeed_1('>', A, '<', B) when B =< A -> no;
-will_succeed_1('>', A, '=<', B) when B < A -> no;
+will_succeed_1('>', A, '=<', B) when B =< A -> no;
will_succeed_1('>', A, '>=', B) when B =< A -> yes;
-will_succeed_1('>', A, '>', B) when B < A -> yes;
+will_succeed_1('>', A, '>', B) when B =< A -> yes;
will_succeed_1('==', A, '==', B) ->
if
diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl
index 90c0d3cf16..32b64b393f 100644
--- a/lib/compiler/src/beam_ssa_opt.erl
+++ b/lib/compiler/src/beam_ssa_opt.erl
@@ -145,7 +145,8 @@ prologue_passes(Opts) ->
?PASS(ssa_opt_linearize),
?PASS(ssa_opt_tuple_size),
?PASS(ssa_opt_record),
- ?PASS(ssa_opt_cse), %Helps the first type pass.
+ ?PASS(ssa_opt_cse), % Helps the first type pass.
+ ?PASS(ssa_opt_live), % ...
?PASS(ssa_opt_type_start)],
passes_1(Ps, Opts).
@@ -157,6 +158,8 @@ repeated_passes(Opts) ->
?PASS(ssa_opt_dead),
?PASS(ssa_opt_cse),
?PASS(ssa_opt_tail_phis),
+ ?PASS(ssa_opt_tuple_size),
+ ?PASS(ssa_opt_record),
?PASS(ssa_opt_type_continue)], %Must run after ssa_opt_dead to
%clean up phi nodes.
passes_1(Ps, Opts).
diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl
index 9af72afca7..a5fcb91cc0 100644
--- a/lib/compiler/src/beam_ssa_pre_codegen.erl
+++ b/lib/compiler/src/beam_ssa_pre_codegen.erl
@@ -108,7 +108,8 @@ functions([], _Ps, _UseBSM3) -> [].
intervals=[] :: [{b_var(),[range()]}],
res=[] :: [{b_var(),reservation()}] | #{b_var():=reservation()},
regs=#{} :: #{b_var():=ssa_register()},
- extra_annos=[] :: [{atom(),term()}]
+ extra_annos=[] :: [{atom(),term()}],
+ location :: term()
}).
-define(PASS(N), {N,fun N/1}).
@@ -120,6 +121,7 @@ passes(Opts) ->
%% Preliminaries.
?PASS(fix_bs),
?PASS(sanitize),
+ ?PASS(match_fail_instructions),
case FixTuples of
false -> ignore;
true -> ?PASS(fix_tuples)
@@ -162,7 +164,9 @@ passes(Opts) ->
function(#b_function{anno=Anno,args=Args,bs=Blocks0,cnt=Count0}=F0,
Ps, UseBSM3) ->
try
- St0 = #st{ssa=Blocks0,args=Args,use_bsm3=UseBSM3,cnt=Count0},
+ Location = maps:get(location, Anno, none),
+ St0 = #st{ssa=Blocks0,args=Args,use_bsm3=UseBSM3,
+ cnt=Count0,location=Location},
St = compile:run_sub_passes(Ps, St0),
#st{ssa=Blocks,cnt=Count,regs=Regs,extra_annos=ExtraAnnos} = St,
F1 = add_extra_annos(F0, ExtraAnnos),
@@ -854,6 +858,114 @@ prune_phi(#b_set{args=Args0}=Phi, Reachable) ->
gb_sets:is_element(Pred, Reachable)],
Phi#b_set{args=Args}.
+%%% Rewrite certain calls to erlang:error/{1,2} to specialized
+%%% instructions:
+%%%
+%%% erlang:error({badmatch,Value}) => badmatch Value
+%%% erlang:error({case_clause,Value}) => case_end Value
+%%% erlang:error({try_clause,Value}) => try_case_end Value
+%%% erlang:error(if_clause) => if_end
+%%% erlang:error(function_clause, Args) => jump FuncInfoLabel
+%%%
+%%% In SSA code, we represent those instructions as a 'match_fail'
+%%% instruction with the name of the BEAM instruction as the first
+%%% argument.
+
+match_fail_instructions(#st{ssa=Blocks0,args=Args,location=Location}=St) ->
+ Ls = maps:to_list(Blocks0),
+ Info = {length(Args),Location},
+ Blocks = match_fail_instrs_1(Ls, Info, Blocks0),
+ St#st{ssa=Blocks}.
+
+match_fail_instrs_1([{L,#b_blk{is=Is0}=Blk}|Bs], Arity, Blocks0) ->
+ case match_fail_instrs_blk(Is0, Arity, []) of
+ none ->
+ match_fail_instrs_1(Bs, Arity, Blocks0);
+ Is ->
+ Blocks = Blocks0#{L:=Blk#b_blk{is=Is}},
+ match_fail_instrs_1(Bs, Arity, Blocks)
+ end;
+match_fail_instrs_1([], _Arity, Blocks) -> Blocks.
+
+match_fail_instrs_blk([#b_set{op=put_tuple,dst=Dst,
+ args=[#b_literal{val=Tag},Val]},
+ #b_set{op=call,
+ args=[#b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=error}},
+ Dst]}=Call|Is],
+ _Arity, Acc) ->
+ match_fail_instr(Call, Tag, Val, Is, Acc);
+match_fail_instrs_blk([#b_set{op=call,
+ args=[#b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=error}},
+ #b_literal{val={Tag,Val}}]}=Call|Is],
+ _Arity, Acc) ->
+ match_fail_instr(Call, Tag, #b_literal{val=Val}, Is, Acc);
+match_fail_instrs_blk([#b_set{op=call,
+ args=[#b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=error}},
+ #b_literal{val=if_clause}]}=Call|Is],
+ _Arity, Acc) ->
+ I = Call#b_set{op=match_fail,args=[#b_literal{val=if_end}]},
+ reverse(Acc, [I|Is]);
+match_fail_instrs_blk([#b_set{op=call,anno=Anno,
+ args=[#b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=error}},
+ #b_literal{val=function_clause},
+ Stk]}=Call],
+ {Arity,Location}, Acc) ->
+ case match_fail_stk(Stk, Acc, [], []) of
+ {[_|_]=Vars,Is} when length(Vars) =:= Arity ->
+ case maps:get(location, Anno, none) of
+ Location ->
+ I = Call#b_set{op=match_fail,
+ args=[#b_literal{val=function_clause}|Vars]},
+ Is ++ [I];
+ _ ->
+ %% erlang:error/2 has a different location than the
+ %% func_info instruction at the beginning of the function
+ %% (probably because of inlining). Keep the original call.
+ reverse(Acc, [Call])
+ end;
+ _ ->
+ %% Either the stacktrace could not be picked apart (for example,
+ %% if the call to erlang:error/2 was handwritten) or the number
+ %% of arguments in the stacktrace was different from the arity
+ %% of the host function (because it is the implementation of a
+ %% fun). Keep the original call.
+ reverse(Acc, [Call])
+ end;
+match_fail_instrs_blk([I|Is], Arity, Acc) ->
+ match_fail_instrs_blk(Is, Arity, [I|Acc]);
+match_fail_instrs_blk(_, _, _) ->
+ none.
+
+match_fail_instr(Call, Tag, Val, Is, Acc) ->
+ Op = case Tag of
+ badmatch -> Tag;
+ case_clause -> case_end;
+ try_clause -> try_case_end;
+ _ -> none
+ end,
+ case Op of
+ none ->
+ none;
+ _ ->
+ I = Call#b_set{op=match_fail,args=[#b_literal{val=Op},Val]},
+ reverse(Acc, [I|Is])
+ end.
+
+match_fail_stk(#b_var{}=V, [#b_set{op=put_list,dst=V,args=[H,T]}|Is], IAcc, VAcc) ->
+ match_fail_stk(T, Is, IAcc, [H|VAcc]);
+match_fail_stk(#b_literal{val=[H|T]}, Is, IAcc, VAcc) ->
+ match_fail_stk(#b_literal{val=T}, Is, IAcc, [#b_literal{val=H}|VAcc]);
+match_fail_stk(#b_literal{val=[]}, [], IAcc, VAcc) ->
+ {reverse(VAcc),IAcc};
+match_fail_stk(T, [#b_set{op=Op}=I|Is], IAcc, VAcc)
+ when Op =:= bs_get_tail; Op =:= bs_set_position ->
+ match_fail_stk(T, Is, [I|IAcc], VAcc);
+match_fail_stk(_, _, _, _) -> none.
+
%%%
%%% Fix tuples.
%%%
diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl
index 68920e7dd3..79c67e5705 100644
--- a/lib/compiler/src/beam_ssa_type.erl
+++ b/lib/compiler/src/beam_ssa_type.erl
@@ -22,11 +22,12 @@
-export([opt_start/4, opt_continue/4, opt_finish/3]).
-include("beam_ssa_opt.hrl").
--import(lists, [all/2,any/2,droplast/1,foldl/3,last/1,member/2,
- keyfind/3,reverse/1,reverse/2,
- sort/1,split/2]).
+-include("beam_types.hrl").
--define(UNICODE_INT, #t_integer{elements={0,16#10FFFF}}).
+-import(lists, [all/2,any/2,droplast/1,duplicate/2,foldl/3,last/1,member/2,
+ keyfind/3,reverse/1,reverse/2,sort/1,split/2,zip/2]).
+
+-define(UNICODE_MAX, (16#10FFFF)).
-record(d,
{ds :: #{beam_ssa:b_var():=beam_ssa:b_set()},
@@ -37,21 +38,6 @@
sub = #{} :: #{beam_ssa:b_var():=beam_ssa:value()},
ret_type = [] :: [type()]}).
--define(ATOM_SET_SIZE, 5).
-
-%% Records that represent type information.
--record(t_atom, {elements=any :: 'any' | [atom()]}).
--record(t_integer, {elements=any :: 'any' | {integer(),integer()}}).
--record(t_bs_match, {type :: type()}).
--record(t_tuple, {size=0 :: integer(),
- exact=false :: boolean(),
- %% Known element types (1-based index), unknown elements are
- %% are assumed to be 'any'.
- elements=#{} :: #{ non_neg_integer() => type() }}).
-
--type type() :: 'any' | 'none' |
- #t_atom{} | #t_integer{} | #t_bs_match{} | #t_tuple{} |
- {'binary',pos_integer()} | 'cons' | 'float' | 'list' | 'map' | 'nil' | 'number'.
-type type_db() :: #{beam_ssa:var_name():=type()}.
-spec opt_start(Linear, Args, Anno, FuncDb) -> {Linear, FuncDb} when
@@ -98,7 +84,7 @@ join_arg_types(Args, ArgTypes, Anno) ->
end, Ts0, ParamTypes).
join_arg_types_1([Arg | Args], [TM | TMs], Ts) when map_size(TM) =/= 0 ->
- join_arg_types_1(Args, TMs, Ts#{ Arg => join(maps:values(TM))});
+ join_arg_types_1(Args, TMs, Ts#{ Arg => beam_types:join(maps:values(TM))});
join_arg_types_1([Arg | Args], [_TM | TMs], Ts) ->
join_arg_types_1(Args, TMs, Ts#{ Arg => any });
join_arg_types_1([], [], Ts) ->
@@ -157,39 +143,15 @@ opt_finish_1([Arg | Args], [TypeMap | TypeMaps], ParamInfo)
map_size(TypeMap) =:= 0 ->
opt_finish_1(Args, TypeMaps, ParamInfo);
opt_finish_1([Arg | Args], [TypeMap | TypeMaps], ParamInfo0) ->
- case join(maps:values(TypeMap)) of
- any ->
- opt_finish_1(Args, TypeMaps, ParamInfo0);
- JoinedType ->
- JoinedType = verified_type(JoinedType),
- ParamInfo = ParamInfo0#{ Arg => validator_anno(JoinedType) },
- opt_finish_1(Args, TypeMaps, ParamInfo)
- end;
+ JoinedType = beam_types:join(maps:values(TypeMap)),
+ ParamInfo = case JoinedType of
+ any -> ParamInfo0;
+ _ -> ParamInfo0#{ Arg => JoinedType }
+ end,
+ opt_finish_1(Args, TypeMaps, ParamInfo);
opt_finish_1([], [], ParamInfo) ->
ParamInfo.
-validator_anno(#t_tuple{size=Size,exact=Exact,elements=Elements0}) ->
- Elements = maps:fold(fun(Index, Type, Acc) ->
- Key = beam_validator:type_anno(integer, Index),
- Acc#{ Key => validator_anno(Type) }
- end, #{}, Elements0),
- beam_validator:type_anno(tuple, Size, Exact, Elements);
-validator_anno(#t_integer{elements={Same,Same}}) ->
- beam_validator:type_anno(integer, Same);
-validator_anno(#t_integer{}) ->
- beam_validator:type_anno(integer);
-validator_anno(float) ->
- beam_validator:type_anno(float);
-validator_anno(#t_atom{elements=[Val]}) ->
- beam_validator:type_anno(atom, Val);
-validator_anno(#t_atom{}=A) ->
- case t_is_boolean(A) of
- true -> beam_validator:type_anno(bool);
- false -> beam_validator:type_anno(atom)
- end;
-validator_anno(T) ->
- beam_validator:type_anno(T).
-
get_func_id(Anno) ->
#{func_info:={_Mod, Name, Arity}} = Anno,
#b_local{name=#b_literal{val=Name}, arity=Arity}.
@@ -223,7 +185,7 @@ opt_1(L, #b_blk{is=Is0,last=Last0}=Blk0, Bs, Ts0,
%% potentially narrow the type of the phi node
%% in the former successor.
Ls = maps:remove(L, D0#d.ls),
- RetType = join([none|D0#d.ret_type]),
+ RetType = beam_types:join([none|D0#d.ret_type]),
D = D0#d{ds=Ds,ls=Ls,sub=Sub,
func_db=Fdb,ret_type=[RetType]},
Blk = Blk0#b_blk{is=Is,last=Ret},
@@ -309,6 +271,12 @@ opt_is([#b_set{op=call,args=Args0,dst=Dst}=I0|Is],
opt_is(Is, Ts, Ds0, Fdb, D, Sub, Acc)
end
end;
+opt_is([#b_set{op=make_fun,args=Args0}=I0|Is],
+ Ts0, Ds0, Fdb0, D, Sub0, Acc) ->
+ Args = simplify_args(Args0, Sub0, Ts0),
+ I1 = beam_ssa:normalize(I0#b_set{args=Args}),
+ {Ts,Ds,Fdb,I} = opt_make_fun(I1, D, Ts0, Ds0, Fdb0),
+ opt_is(Is, Ts, Ds, Fdb, D, Sub0, [I|Acc]);
opt_is([#b_set{op=succeeded,args=[Arg],dst=Dst}=I],
Ts0, Ds0, Fdb, D, Sub0, Acc) ->
case Ds0 of
@@ -323,11 +291,11 @@ opt_is([#b_set{op=succeeded,args=[Arg],dst=Dst}=I],
#{} ->
Args = simplify_args([Arg], Sub0, Ts0),
Type = type(succeeded, Args, Ts0, Ds0),
- case get_literal_from_type(Type) of
- #b_literal{}=Lit ->
- Sub = Sub0#{Dst=>Lit},
+ case beam_types:get_singleton_value(Type) of
+ {ok, Lit} ->
+ Sub = Sub0#{Dst=>#b_literal{val=Lit}},
opt_is([], Ts0, Ds0, Fdb, D, Sub, Acc);
- none ->
+ error ->
Ts = Ts0#{Dst=>Type},
Ds = Ds0#{Dst=>I},
opt_is([], Ts, Ds, Fdb, D, Sub0, [I|Acc])
@@ -370,7 +338,7 @@ simplify_call(#b_set{op=call,args=[#b_remote{}=Rem|Args]}=I) ->
false ->
I
end;
- #b_remote{} ->
+ #b_remote{} ->
I
end;
simplify_call(I) -> I.
@@ -417,10 +385,18 @@ opt_call(#b_set{dst=Dst,args=[#b_local{}=Callee|Args]}=I0, D, Ts0, Ds0, Fdb0) ->
{Ts, Ds, I} = opt_local_call(I0, Ts0, Ds0, Fdb0),
case Fdb0 of
#{ Callee := #func_info{exported=false,arg_types=ArgTypes0}=Info } ->
+ %% Match contexts are treated as bitstrings when optimizing
+ %% arguments, as we don't yet support removing the
+ %% "bs_start_match3" instruction.
+ Types = [case get_type(Arg, Ts) of
+ #t_bs_context{} -> #t_bitstring{};
+ Type -> Type
+ end || Arg <- Args],
+
%% Update the argument types of *this exact call*, the types
%% will be joined later when the callee is optimized.
CallId = {D#d.func_id, Dst},
- ArgTypes = update_arg_types(Args, ArgTypes0, CallId, Ts0),
+ ArgTypes = update_arg_types(Types, ArgTypes0, CallId),
Fdb = Fdb0#{ Callee => Info#func_info{arg_types=ArgTypes} },
{Ts, Ds, Fdb, I};
@@ -429,7 +405,13 @@ opt_call(#b_set{dst=Dst,args=[#b_local{}=Callee|Args]}=I0, D, Ts0, Ds0, Fdb0) ->
%% can receive anything as part of an external call.
{Ts, Ds, Fdb0, I}
end;
+opt_call(#b_set{dst=Dst,args=[#b_var{}=Fun|Args]}=I, _D, Ts0, Ds0, Fdb) ->
+ Type = #t_fun{arity=length(Args)},
+ Ts = Ts0#{ Fun => Type, Dst => any },
+ Ds = Ds0#{ Dst => I },
+ {Ts, Ds, Fdb, I};
opt_call(#b_set{dst=Dst}=I, _D, Ts0, Ds0, Fdb) ->
+ %% #b_remote{} and literal funs
Ts = update_types(I, Ts0, Ds0),
Ds = Ds0#{ Dst => I },
{Ts, Ds, Fdb, I}.
@@ -442,22 +424,43 @@ opt_local_call(#b_set{dst=Dst,args=[Id|_]}=I0, Ts0, Ds0, Fdb) ->
I = case Type of
any -> I0;
none -> I0;
- _ -> beam_ssa:add_anno(result_type, validator_anno(Type), I0)
+ _ -> beam_ssa:add_anno(result_type, Type, I0)
end,
Ts = Ts0#{ Dst => Type },
Ds = Ds0#{ Dst => I },
{Ts, Ds, I}.
-update_arg_types([Arg | Args], [TypeMap0 | TypeMaps], CallId, Ts) ->
- %% Match contexts are treated as bitstrings when optimizing arguments, as
- %% we don't yet support removing the "bs_start_match3" instruction.
- NewType = case get_type(Arg, Ts) of
- #t_bs_match{} -> {binary, 1};
- Type -> Type
- end,
- TypeMap = TypeMap0#{ CallId => NewType },
- [TypeMap | update_arg_types(Args, TypeMaps, CallId, Ts)];
-update_arg_types([], [], _CallId, _Ts) ->
+%% While we have no way to know which arguments a fun will be called with, we
+%% do know its free variables and can update their types as if this were a
+%% local call.
+opt_make_fun(#b_set{op=make_fun,
+ dst=Dst,
+ args=[#b_local{}=Callee | FreeVars]}=I,
+ D, Ts0, Ds0, Fdb0) ->
+ Ts = update_types(I, Ts0, Ds0),
+ Ds = Ds0#{ Dst => I },
+ case Fdb0 of
+ #{ Callee := #func_info{exported=false,arg_types=ArgTypes0}=Info } ->
+ ArgCount = Callee#b_local.arity - length(FreeVars),
+
+ FVTypes = [get_type(FreeVar, Ts) || FreeVar <- FreeVars],
+ Types = duplicate(ArgCount, any) ++ FVTypes,
+
+ CallId = {D#d.func_id, Dst},
+ ArgTypes = update_arg_types(Types, ArgTypes0, CallId),
+
+ Fdb = Fdb0#{ Callee => Info#func_info{arg_types=ArgTypes} },
+ {Ts, Ds, Fdb, I};
+ #{} ->
+ %% We can't narrow the argument types of exported functions as they
+ %% can receive anything as part of an external call.
+ {Ts, Ds, Fdb0, I}
+ end.
+
+update_arg_types([ArgType | ArgTypes], [TypeMap0 | TypeMaps], CallId) ->
+ TypeMap = TypeMap0#{ CallId => ArgType },
+ [TypeMap | update_arg_types(ArgTypes, TypeMaps, CallId)];
+update_arg_types([], [], _CallId) ->
[].
simplify(#b_set{op={bif,'and'},args=Args}=I, Ts) ->
@@ -483,7 +486,7 @@ simplify(#b_set{op={bif,'or'},args=Args}=I, Ts) ->
I
end;
simplify(#b_set{op={bif,element},args=[#b_literal{val=Index},Tuple]}=I0, Ts) ->
- case t_tuple_size(get_type(Tuple, Ts)) of
+ case beam_types:get_tuple_size(get_type(Tuple, Ts)) of
{_,Size} when is_integer(Index), 1 =< Index, Index =< Size ->
I = I0#b_set{op=get_tuple_element,
args=[Tuple,#b_literal{val=Index-1}]},
@@ -519,19 +522,36 @@ simplify(#b_set{op={bif,tuple_size},args=[Term]}=I, Ts) ->
_ ->
I
end;
-simplify(#b_set{op={bif,'=='},args=Args}=I, Ts) ->
+simplify(#b_set{op={bif,is_function},args=[Fun,#b_literal{val=Arity}]}=I, Ts)
+ when is_integer(Arity), Arity >= 0 ->
+ case get_type(Fun, Ts) of
+ #t_fun{arity=any} ->
+ I;
+ #t_fun{arity=Arity} ->
+ #b_literal{val=true};
+ any ->
+ I;
+ _ ->
+ #b_literal{val=false}
+ end;
+simplify(#b_set{op={bif,Op0},args=Args}=I, Ts) when Op0 =:= '=='; Op0 =:= '/=' ->
Types = get_types(Args, Ts),
- EqEq = case {meet(Types),join(Types)} of
+ EqEq0 = case {beam_types:meet(Types),beam_types:join(Types)} of
{none,any} -> true;
{#t_integer{},#t_integer{}} -> true;
{float,float} -> true;
- {{binary,_},_} -> true;
+ {#t_bitstring{},_} -> true;
{#t_atom{},_} -> true;
{_,_} -> false
end,
+ EqEq = EqEq0 orelse any_non_numeric_argument(Args, Ts),
case EqEq of
true ->
- simplify(I#b_set{op={bif,'=:='}}, Ts);
+ Op = case Op0 of
+ '==' -> '=:=';
+ '/=' -> '=/='
+ end,
+ simplify(I#b_set{op={bif,Op}}, Ts);
false ->
eval_bif(I, Ts)
end;
@@ -539,14 +559,25 @@ simplify(#b_set{op={bif,'=:='},args=[Same,Same]}, _Ts) ->
#b_literal{val=true};
simplify(#b_set{op={bif,'=:='},args=[A1,_A2]=Args}=I, Ts) ->
[T1,T2] = get_types(Args, Ts),
- case meet(T1, T2) of
+ case beam_types:meet(T1, T2) of
none ->
#b_literal{val=false};
_ ->
- case {t_is_boolean(T1),T2} of
+ case {beam_types:is_boolean_type(T1),T2} of
{true,#t_atom{elements=[true]}} ->
%% Bool =:= true ==> Bool
A1;
+ {true,#t_atom{elements=[false]}} ->
+ %% Bool =:= false ==> not Bool
+ %%
+ %% This will be further optimized to eliminate the
+ %% 'not', swapping the success and failure
+ %% branches in the br instruction. If A1 comes
+ %% from a type test (such as is_atom/1) or a
+ %% comparison operator (such as >=) that can be
+ %% translated to test instruction, this
+ %% optimization will eliminate one instruction.
+ simplify(I#b_set{op={bif,'not'},args=[A1]}, Ts);
{_,_} ->
eval_bif(I, Ts)
end
@@ -563,10 +594,10 @@ simplify(#b_set{op={bif,Op},args=Args}=I, Ts) ->
simplify(#b_set{op=get_tuple_element,args=[Tuple,#b_literal{val=N}]}=I, Ts) ->
case get_type(Tuple, Ts) of
#t_tuple{size=Size,elements=Es} when Size > N ->
- ElemType = get_element_type(N + 1, Es),
- case get_literal_from_type(ElemType) of
- #b_literal{}=Lit -> Lit;
- none -> I
+ ElemType = beam_types:get_element_type(N + 1, Es),
+ case beam_types:get_singleton_value(ElemType) of
+ {ok, Val} -> #b_literal{val=Val};
+ error -> I
end;
none ->
%% Will never be executed because of type conflict.
@@ -597,6 +628,44 @@ simplify(#b_set{op=wait_timeout,args=[#b_literal{val=infinity}]}=I, _Ts) ->
I#b_set{op=wait,args=[]};
simplify(I, _Ts) -> I.
+any_non_numeric_argument([#b_literal{val=Lit}|_], _Ts) ->
+ is_non_numeric(Lit);
+any_non_numeric_argument([#b_var{}=V|T], Ts) ->
+ is_non_numeric_type(get_type(V, Ts)) orelse any_non_numeric_argument(T, Ts);
+any_non_numeric_argument([], _Ts) -> false.
+
+is_non_numeric([H|T]) ->
+ is_non_numeric(H) andalso is_non_numeric(T);
+is_non_numeric(Tuple) when is_tuple(Tuple) ->
+ is_non_numeric_tuple(Tuple, tuple_size(Tuple));
+is_non_numeric(Map) when is_map(Map) ->
+ %% Note that 17.x and 18.x compare keys in different ways.
+ %% Be very conservative -- require that both keys and values
+ %% are non-numeric.
+ is_non_numeric(maps:to_list(Map));
+is_non_numeric(Num) when is_number(Num) ->
+ false;
+is_non_numeric(_) -> true.
+
+is_non_numeric_tuple(Tuple, El) when El >= 1 ->
+ is_non_numeric(element(El, Tuple)) andalso
+ is_non_numeric_tuple(Tuple, El-1);
+is_non_numeric_tuple(_Tuple, 0) -> true.
+
+is_non_numeric_type(#t_atom{}) -> true;
+is_non_numeric_type(#t_bitstring{}) -> true;
+is_non_numeric_type(nil) -> true;
+is_non_numeric_type(#t_tuple{size=Size,exact=true,elements=Types})
+ when map_size(Types) =:= Size ->
+ is_non_numeric_tuple_type(Size, Types);
+is_non_numeric_type(_) -> false.
+
+is_non_numeric_tuple_type(0, _Types) ->
+ true;
+is_non_numeric_tuple_type(Pos, Types) ->
+ is_non_numeric_type(map_get(Pos, Types)) andalso
+ is_non_numeric_tuple_type(Pos - 1, Types).
+
make_literal_list(Args) ->
make_literal_list(Args, []).
@@ -609,7 +678,7 @@ make_literal_list([], Acc) ->
is_safe_bool_op(Args, Ts) ->
[T1,T2] = get_types(Args, Ts),
- t_is_boolean(T1) andalso t_is_boolean(T2).
+ beam_types:is_boolean_type(T1) andalso beam_types:is_boolean_type(T2).
all_same([{H,_}|T]) ->
all(fun({E,_}) -> E =:= H end, T).
@@ -656,9 +725,9 @@ simplify_arg(#b_var{}=Arg0, Sub, Ts) ->
LitArg;
#b_var{}=Arg ->
Type = get_type(Arg, Ts),
- case get_literal_from_type(Type) of
- none -> Arg;
- #b_literal{}=Lit -> Lit
+ case beam_types:get_singleton_value(Type) of
+ {ok, Val} -> #b_literal{val=Val};
+ error -> Arg
end
end;
simplify_arg(#b_remote{mod=Mod,name=Name}=Rem, Sub, Ts) ->
@@ -713,7 +782,7 @@ opt_switch(#b_switch{fail=Fail,list=List0}=Sw0, Type, Ts, Ds) ->
#t_integer{elements={_,_}=Range} ->
simplify_switch_int(Sw1, Range);
#t_atom{elements=[_|_]} ->
- case t_is_boolean(Type) of
+ case beam_types:is_boolean_type(Type) of
true ->
#b_br{} = Br = simplify_switch_bool(Sw1, Ts, Ds),
opt_terminator(Br, Ts, Ds);
@@ -727,7 +796,7 @@ opt_switch(#b_switch{fail=Fail,list=List0}=Sw0, Type, Ts, Ds) ->
prune_switch_list([{_,Fail}|T], Fail, Type, Ts) ->
prune_switch_list(T, Fail, Type, Ts);
prune_switch_list([{Arg,_}=Pair|T], Fail, Type, Ts) ->
- case meet(get_type(Arg, Ts), Type) of
+ case beam_types:meet(get_type(Arg, Ts), Type) of
none ->
%% Different types. This value can never match.
prune_switch_list(T, Fail, Type, Ts);
@@ -787,7 +856,7 @@ update_successors(#b_ret{arg=Arg}, Ts, D) ->
%% type.
D;
#{} ->
- RetType = join([get_type(Arg, Ts) | D#d.ret_type]),
+ RetType = beam_types:join([get_type(Arg, Ts) | D#d.ret_type]),
D#d{ret_type=[RetType]}
end.
@@ -796,14 +865,14 @@ subtract_sw_list(V, List, Ts) ->
sub_sw_list_1(Type, [{Val,_}|T], Ts) ->
ValType = get_type(Val, Ts),
- sub_sw_list_1(subtract(Type, ValType), T, Ts);
+ sub_sw_list_1(beam_types:subtract(Type, ValType), T, Ts);
sub_sw_list_1(Type, [], _Ts) ->
Type.
update_successor_bool(#b_var{}=Var, BoolValue, S, Ts, D) ->
- case t_is_boolean(get_type(Var, Ts)) of
+ case beam_types:is_boolean_type(get_type(Var, Ts)) of
true ->
- update_successor(S, Ts#{Var:=t_atom(BoolValue)}, D);
+ update_successor(S, Ts#{ Var := beam_types:make_atom(BoolValue) }, D);
false ->
%% The `br` terminator is preceeded by an instruction that
%% does not produce a boolean value, such a `new_try_tag`.
@@ -830,109 +899,43 @@ update_types(#b_set{op=Op,dst=Dst,args=Args}, Ts, Ds) ->
type(phi, Args, Ts, _Ds) ->
Types = [get_type(A, Ts) || {A,_} <- Args],
- join(Types);
-type({bif,'band'}, Args, Ts, _Ds) ->
- band_type(Args, Ts);
+ beam_types:join(Types);
type({bif,Bif}, Args, Ts, _Ds) ->
- case bif_type(Bif, Args) of
- number ->
- arith_op_type(Args, Ts);
- Type ->
- Type
- end;
+ {RetType, _, _} = beam_call_types:types(erlang, Bif, get_types(Args, Ts)),
+ RetType;
type(bs_init, _Args, _Ts, _Ds) ->
- {binary, 1};
-type(bs_extract, [Ctx], Ts, _Ds) ->
- #t_bs_match{type=Type} = get_type(Ctx, Ts),
- Type;
-type(bs_match, Args, _Ts, _Ds) ->
- #t_bs_match{type=bs_match_type(Args)};
+ #t_bitstring{};
+type(bs_extract, [Ctx], _Ts, Ds) ->
+ #b_set{op=bs_match,args=Args} = map_get(Ctx, Ds),
+ bs_match_type(Args);
+type(bs_match, _Args, _Ts, _Ds) ->
+ #t_bs_context{};
type(bs_get_tail, _Args, _Ts, _Ds) ->
- {binary, 1};
+ #t_bitstring{};
type(call, [#b_remote{mod=#b_literal{val=Mod},
name=#b_literal{val=Name}}|Args], Ts, _Ds) ->
- case {Mod,Name,Args} of
- {erlang,setelement,[Pos,Tuple,Arg]} ->
- case {get_type(Pos, Ts),get_type(Tuple, Ts)} of
- {#t_integer{elements={Index,Index}},
- #t_tuple{elements=Es0,size=Size}=T} ->
- %% This is an exact index, update the type of said element
- %% or return 'none' if it's known to be out of bounds.
- Es = set_element_type(Index, get_type(Arg, Ts), Es0),
- case T#t_tuple.exact of
- false ->
- T#t_tuple{size=max(Index, Size),elements=Es};
- true when Index =< Size ->
- T#t_tuple{elements=Es};
- true ->
- none
- end;
- {#t_integer{elements={Min,_}}=IntType,
- #t_tuple{elements=Es0,size=Size}=T} ->
- %% Remove type information for all indices that
- %% falls into the range of the integer.
- Es = remove_element_info(IntType, Es0),
- case T#t_tuple.exact of
- false ->
- T#t_tuple{elements=Es,size=max(Min, Size)};
- true when Min =< Size ->
- T#t_tuple{elements=Es,size=Size};
- true ->
- none
- end;
- {_,#t_tuple{}=T} ->
- %% Position unknown, so we have to discard all element
- %% information.
- T#t_tuple{elements=#{}};
- {#t_integer{elements={Min,_Max}},_} ->
- #t_tuple{size=Min};
- {_,_} ->
- #t_tuple{}
- end;
- {erlang,'++',[LHS,RHS]} ->
- LType = get_type(LHS, Ts),
- RType = get_type(RHS, Ts),
- case LType =:= cons orelse RType =:= cons of
- true ->
- cons;
- false ->
- %% `[] ++ RHS` yields RHS, even if RHS is not a list.
- join(list, RType)
- end;
- {erlang,'--',[_,_]} ->
- list;
- {lists,F,Args} ->
- Types = get_types(Args, Ts),
- lists_function_type(F, Types);
- {math,_,_} ->
- case is_math_bif(Name, length(Args)) of
- false -> any;
- true -> float
- end;
- {_,_,_} ->
- case erl_bifs:is_exit_bif(Mod, Name, length(Args)) of
- true -> none;
- false -> any
- end
- end;
+ {RetType, _, _} = beam_call_types:types(Mod, Name, get_types(Args, Ts)),
+ RetType;
type(get_tuple_element, [Tuple, Offset], Ts, _Ds) ->
#t_tuple{size=Size,elements=Es} = get_type(Tuple, Ts),
#b_literal{val=N} = Offset,
true = Size > N, %Assertion.
- get_element_type(N + 1, Es);
+ beam_types:get_element_type(N + 1, Es);
type(is_nonempty_list, [_], _Ts, _Ds) ->
- t_boolean();
+ beam_types:make_boolean();
type(is_tagged_tuple, [_,#b_literal{},#b_literal{}], _Ts, _Ds) ->
- t_boolean();
+ beam_types:make_boolean();
+type(make_fun, [#b_local{arity=TotalArity}|Env], _Ts, _Ds) ->
+ #t_fun{arity=TotalArity-length(Env)};
type(put_map, _Args, _Ts, _Ds) ->
- map;
+ #t_map{};
type(put_list, _Args, _Ts, _Ds) ->
cons;
type(put_tuple, Args, Ts, _Ds) ->
{Es, _} = foldl(fun(Arg, {Es0, Index}) ->
- Type = get_type(Arg, Ts),
- Es = set_element_type(Index, Type, Es0),
- {Es, Index + 1}
+ Type = get_type(Arg, Ts),
+ Es = beam_types:set_element_type(Index, Type, Es0),
+ {Es, Index + 1}
end, {#{}, 1}, Args),
#t_tuple{exact=true,size=length(Args),elements=Es};
type(succeeded, [#b_var{}=Src], Ts, Ds) ->
@@ -941,130 +944,51 @@ type(succeeded, [#b_var{}=Src], Ts, Ds) ->
Types = get_types(BifArgs, Ts),
case {Bif,Types} of
{BoolOp,[T1,T2]} when BoolOp =:= 'and'; BoolOp =:= 'or' ->
- case t_is_boolean(T1) andalso t_is_boolean(T2) of
- true -> t_atom(true);
- false -> t_boolean()
+ BothBool = beam_types:is_boolean_type(T1) andalso
+ beam_types:is_boolean_type(T2),
+ case BothBool of
+ true -> beam_types:make_atom(true);
+ false -> beam_types:make_boolean()
end;
- {byte_size,[{binary,_}]} ->
- t_atom(true);
- {bit_size,[{binary,_}]} ->
- t_atom(true);
- {map_size,[map]} ->
- t_atom(true);
+ {byte_size,[#t_bitstring{}]} ->
+ beam_types:make_atom(true);
+ {bit_size,[#t_bitstring{}]} ->
+ beam_types:make_atom(true);
+ {map_size,[#t_map{}]} ->
+ beam_types:make_atom(true);
{'not',[Type]} ->
- case t_is_boolean(Type) of
- true -> t_atom(true);
- false -> t_boolean()
+ case beam_types:is_boolean_type(Type) of
+ true -> beam_types:make_atom(true);
+ false -> beam_types:make_boolean()
end;
- {size,[{binary,_}]} ->
- t_atom(true);
+ {size,[#t_bitstring{}]} ->
+ beam_types:make_atom(true);
{tuple_size,[#t_tuple{}]} ->
- t_atom(true);
+ beam_types:make_atom(true);
{_,_} ->
- t_boolean()
+ beam_types:make_boolean()
end;
#b_set{op=get_hd} ->
- t_atom(true);
+ beam_types:make_atom(true);
#b_set{op=get_tl} ->
- t_atom(true);
+ beam_types:make_atom(true);
#b_set{op=get_tuple_element} ->
- t_atom(true);
+ beam_types:make_atom(true);
#b_set{op=wait} ->
- t_atom(false);
+ beam_types:make_atom(false);
#b_set{} ->
- t_boolean()
+ beam_types:make_boolean()
end;
type(succeeded, [#b_literal{}], _Ts, _Ds) ->
- t_atom(true);
+ beam_types:make_atom(true);
type(_, _, _, _) -> any.
-arith_op_type(Args, Ts) ->
- Types = get_types(Args, Ts),
- foldl(fun(#t_integer{}, unknown) -> t_integer();
- (#t_integer{}, number) -> number;
- (#t_integer{}, float) -> float;
- (#t_integer{}, #t_integer{}) -> t_integer();
- (float, unknown) -> float;
- (float, #t_integer{}) -> float;
- (float, number) -> float;
- (number, unknown) -> number;
- (number, #t_integer{}) -> number;
- (number, float) -> float;
- (any, _) -> number;
- (Same, Same) -> Same;
- (_, _) -> none
- end, unknown, Types).
-
-lists_function_type(F, Types) ->
- case {F,Types} of
- %% Functions that return booleans.
- {all,[_,_]} ->
- t_boolean();
- {any,[_,_]} ->
- t_boolean();
- {keymember,[_,_,_]} ->
- t_boolean();
- {member,[_,_]} ->
- t_boolean();
- {prefix,[_,_]} ->
- t_boolean();
- {suffix,[_,_]} ->
- t_boolean();
-
- %% Functions that return lists.
- {dropwhile,[_,_]} ->
- list;
- {duplicate,[_,_]} ->
- list;
- {filter,[_,_]} ->
- list;
- {flatten,[_]} ->
- list;
- {map,[_Fun,List]} ->
- same_length_type(List);
- {MapFold,[_Fun,_Acc,List]} when MapFold =:= mapfoldl;
- MapFold =:= mapfoldr ->
- #t_tuple{size=2,exact=true,
- elements=#{1=>same_length_type(List)}};
- {partition,[_,_]} ->
- t_two_tuple(list, list);
- {reverse,[List]} ->
- same_length_type(List);
- {sort,[List]} ->
- same_length_type(List);
- {splitwith,[_,_]} ->
- t_two_tuple(list, list);
- {takewhile,[_,_]} ->
- list;
- {unzip,[List]} ->
- ListType = same_length_type(List),
- t_two_tuple(ListType, ListType);
- {usort,[List]} ->
- same_length_type(List);
- {zip,[_,_]} ->
- list;
- {zipwith,[_,_,_]} ->
- list;
- {_,_} ->
- any
- end.
-
-%% For a lists function that return a list of the same
-%% length as the input list, return the type of the list.
-same_length_type(cons) -> cons;
-same_length_type(nil) -> nil;
-same_length_type(_) -> list.
-
-t_two_tuple(Type1, Type2) ->
- #t_tuple{size=2,exact=true,
- elements=#{1=>Type1,2=>Type2}}.
-
%% will_succeed(TestOperation, Type) -> yes|no|maybe.
%% Test whether TestOperation applied to an argument of type Type
%% will succeed. Return yes, no, or maybe.
%%
-%% Type is a type as described in the comment for verified_type/1 at
-%% the very end of this file, but it will *never* be 'any'.
+%% Type can be any type as described in beam_types.hrl, but it must *never* be
+%% any.
will_succeed(is_atom, Type) ->
case Type of
@@ -1073,13 +997,13 @@ will_succeed(is_atom, Type) ->
end;
will_succeed(is_binary, Type) ->
case Type of
- {binary,U} when U rem 8 =:= 0 -> yes;
- {binary,_} -> maybe;
+ #t_bitstring{unit=U} when U rem 8 =:= 0 -> yes;
+ #t_bitstring{} -> maybe;
_ -> no
end;
will_succeed(is_bitstring, Type) ->
case Type of
- {binary,_} -> yes;
+ #t_bitstring{} -> yes;
_ -> no
end;
will_succeed(is_boolean, Type) ->
@@ -1087,7 +1011,7 @@ will_succeed(is_boolean, Type) ->
#t_atom{elements=any} ->
maybe;
#t_atom{elements=Es} ->
- case t_is_boolean(Type) of
+ case beam_types:is_boolean_type(Type) of
true ->
yes;
false ->
@@ -1105,6 +1029,11 @@ will_succeed(is_float, Type) ->
number -> maybe;
_ -> no
end;
+will_succeed(is_function, Type) ->
+ case Type of
+ #t_fun{} -> yes;
+ _ -> no
+ end;
will_succeed(is_integer, Type) ->
case Type of
#t_integer{} -> yes;
@@ -1119,7 +1048,7 @@ will_succeed(is_list, Type) ->
end;
will_succeed(is_map, Type) ->
case Type of
- map -> yes;
+ #t_map{} -> yes;
_ -> no
end;
will_succeed(is_number, Type) ->
@@ -1136,35 +1065,12 @@ will_succeed(is_tuple, Type) ->
end;
will_succeed(_, _) -> maybe.
-
-band_type([Other,#b_literal{val=Int}], Ts) when is_integer(Int) ->
- band_type_1(Int, Other, Ts);
-band_type([_,_], _) -> t_integer().
-
-band_type_1(Int, OtherSrc, Ts) ->
- Type = band_type_2(Int, 0),
- OtherType = get_type(OtherSrc, Ts),
- meet(Type, OtherType).
-
-band_type_2(N, Bits) when Bits < 64 ->
- case 1 bsl Bits of
- P when P =:= N + 1 ->
- t_integer(0, N);
- P when P > N + 1 ->
- t_integer();
- _ ->
- band_type_2(N, Bits+1)
- end;
-band_type_2(_, _) ->
- %% Negative or large positive number. Give up.
- t_integer().
-
bs_match_type([#b_literal{val=Type}|Args]) ->
bs_match_type(Type, Args).
bs_match_type(binary, Args) ->
[_,_,_,#b_literal{val=U}] = Args,
- {binary,U};
+ #t_bitstring{unit=U};
bs_match_type(float, _) ->
float;
bs_match_type(integer, Args) ->
@@ -1176,24 +1082,24 @@ bs_match_type(integer, Args) ->
NumBits = Size * Unit,
case member(unsigned, Flags) of
true ->
- t_integer(0, (1 bsl NumBits)-1);
+ beam_types:make_integer(0, (1 bsl NumBits)-1);
false ->
%% Signed integer. Don't bother.
- t_integer()
+ #t_integer{}
end;
[_|_] ->
- t_integer()
+ #t_integer{}
end;
bs_match_type(skip, _) ->
any;
bs_match_type(string, _) ->
any;
bs_match_type(utf8, _) ->
- ?UNICODE_INT;
+ beam_types:make_integer(0, ?UNICODE_MAX);
bs_match_type(utf16, _) ->
- ?UNICODE_INT;
+ beam_types:make_integer(0, ?UNICODE_MAX);
bs_match_type(utf32, _) ->
- ?UNICODE_INT.
+ beam_types:make_integer(0, ?UNICODE_MAX).
simplify_switch_atom(#t_atom{elements=Atoms}, #b_switch{list=List0}=Sw) ->
case sort([A || {#b_literal{val=A},_} <- List0]) of
@@ -1225,14 +1131,14 @@ eq_ranges(_, _, _) -> false.
simplify_is_record(I, #t_tuple{exact=Exact,
size=Size,
elements=Es},
- RecSize, RecTag, Ts) ->
+ RecSize, #b_literal{val=TagVal}=RecTag, Ts) ->
TagType = maps:get(1, Es, any),
- TagMatch = case get_literal_from_type(TagType) of
- #b_literal{}=RecTag -> yes;
- #b_literal{} -> no;
- none ->
+ TagMatch = case beam_types:get_singleton_value(TagType) of
+ {ok, TagVal} -> yes;
+ {ok, _} -> no;
+ error ->
%% Is it at all possible for the tag to match?
- case meet(get_type(RecTag, Ts), TagType) of
+ case beam_types:meet(get_type(RecTag, Ts), TagType) of
none -> no;
_ -> maybe
end
@@ -1262,7 +1168,7 @@ simplify_switch_bool(#b_switch{arg=B,fail=Fail,list=List0}, Ts, Ds) ->
simplify_not(#b_br{bool=#b_var{}=V,succ=Succ,fail=Fail}=Br0, Ts, Ds) ->
case Ds of
#{V:=#b_set{op={bif,'not'},args=[Bool]}} ->
- case t_is_boolean(get_type(Bool, Ts)) of
+ case beam_types:is_boolean_type(get_type(Bool, Ts)) of
true ->
Br = Br0#b_br{bool=Bool,succ=Fail,fail=Succ},
beam_ssa:normalize(Br);
@@ -1339,31 +1245,7 @@ get_type(#b_var{}=V, Ts) ->
#{V:=T} = Ts,
T;
get_type(#b_literal{val=Val}, _Ts) ->
- if
- is_atom(Val) ->
- t_atom(Val);
- is_float(Val) ->
- float;
- is_integer(Val) ->
- t_integer(Val);
- is_list(Val), Val =/= [] ->
- cons;
- is_map(Val) ->
- map;
- Val =:= {} ->
- #t_tuple{exact=true};
- is_tuple(Val) ->
- {Es, _} = foldl(fun(E, {Es0, Index}) ->
- Type = get_type(#b_literal{val=E}, #{}),
- Es = set_element_type(Index, Type, Es0),
- {Es, Index + 1}
- end, {#{}, 1}, tuple_to_list(Val)),
- #t_tuple{exact=true,size=tuple_size(Val),elements=Es};
- Val =:= [] ->
- nil;
- true ->
- any
- end.
+ beam_types:make_type_from_value(Val).
%% infer_types(Var, Types, #d{}) -> {SuccTypes,FailTypes}
%% Looking at the expression that defines the variable Var, infer
@@ -1388,8 +1270,7 @@ get_type(#b_literal{val=Val}, _Ts) ->
infer_types_br(#b_var{}=V, Ts, #d{ds=Ds}) ->
#{V:=#b_set{op=Op,args=Args}} = Ds,
- PosTypes0 = infer_type(Op, Args, Ds),
- NegTypes0 = infer_type_negative(Op, Args, Ds),
+ {PosTypes0, NegTypes0} = infer_type(Op, Args, Ts, Ds),
%% We must be careful with types inferred from '=:='.
%%
@@ -1402,7 +1283,7 @@ infer_types_br(#b_var{}=V, Ts, #d{ds=Ds}) ->
%% it is single-valued, e.g. if it is [] or the atom 'true'.
EqTypes = infer_eq_type(Op, Args, Ts, Ds),
- NegTypes1 = [P || {_,T}=P <- EqTypes, is_singleton_type(T)],
+ NegTypes1 = [P || {_,T}=P <- EqTypes, beam_types:is_singleton_type(T)],
PosTypes = EqTypes ++ PosTypes0,
SuccTs = meet_types(PosTypes, Ts),
@@ -1426,7 +1307,7 @@ infer_eq_type({bif,'=:='}, [#b_var{}=Arg0,#b_var{}=Arg1], Ts, _Ds) ->
%% be inferred that L1 is 'cons' (the meet of 'cons' and 'list').
Type0 = get_type(Arg0, Ts),
Type1 = get_type(Arg1, Ts),
- Type = meet(Type0, Type1),
+ Type = beam_types:meet(Type0, Type1),
[{V,MeetType} ||
{V,OrigType,MeetType} <-
[{Arg0,Type0,Type},{Arg1,Type1,Type}],
@@ -1441,177 +1322,72 @@ infer_eq_lit(#b_set{op=get_tuple_element,
args=[#b_var{}=Tuple,#b_literal{val=N}]},
#b_literal{}=Lit) ->
Index = N + 1,
- Es = set_element_type(Index, get_type(Lit, #{}), #{}),
+ Es = beam_types:set_element_type(Index, get_type(Lit, #{}), #{}),
[{Tuple,#t_tuple{size=Index,elements=Es}}];
infer_eq_lit(_, _) -> [].
-infer_type_negative(Op, Args, Ds) ->
- case is_negative_inference_safe(Op, Args) of
- true ->
- infer_type(Op, Args, Ds);
- false ->
- []
- end.
-
-%% Conservative list of instructions for which negative
-%% inference is safe.
-is_negative_inference_safe(is_nonempty_list, _Args) -> true;
-is_negative_inference_safe(_, _) -> false.
-
-infer_type({bif,element}, [#b_literal{val=Pos},#b_var{}=Tuple], _Ds) ->
- if
- is_integer(Pos), 1 =< Pos ->
- [{Tuple,#t_tuple{size=Pos}}];
- true ->
- []
- end;
-infer_type({bif,element}, [#b_var{}=Position,#b_var{}=Tuple], _Ds) ->
- [{Position,t_integer()},{Tuple,#t_tuple{}}];
-infer_type({bif,Bif}, [#b_var{}=Src]=Args, _Ds) ->
- case inferred_bif_type(Bif, Args) of
- any -> [];
- T -> [{Src,T}]
- end;
-infer_type({bif,binary_part}, [#b_var{}=Src,_], _Ds) ->
- [{Src,{binary,8}}];
-infer_type({bif,is_map_key}, [_,#b_var{}=Src], _Ds) ->
- [{Src,map}];
-infer_type({bif,map_get}, [_,#b_var{}=Src], _Ds) ->
- [{Src,map}];
-infer_type({bif,Bif}, [_,_]=Args, _Ds) ->
- case inferred_bif_type(Bif, Args) of
- any -> [];
- T -> [{A,T} || #b_var{}=A <- Args]
- end;
-infer_type({bif,binary_part}, [#b_var{}=Src,Pos,Len], _Ds) ->
- [{Src,{binary,8}}|
- [{V,t_integer()} || #b_var{}=V <- [Pos,Len]]];
-infer_type(bs_start_match, [#b_var{}=Bin], _Ds) ->
- [{Bin,{binary,1}}];
-infer_type(is_nonempty_list, [#b_var{}=Src], _Ds) ->
- [{Src,cons}];
-infer_type(is_tagged_tuple, [#b_var{}=Src,#b_literal{val=Size},
- #b_literal{}=Tag], _Ds) ->
- Es = set_element_type(1, get_type(Tag, #{}), #{}),
- [{Src,#t_tuple{exact=true,size=Size,elements=Es}}];
-infer_type(succeeded, [#b_var{}=Src], Ds) ->
+infer_type(succeeded, [#b_var{}=Src], Ts, Ds) ->
#b_set{op=Op,args=Args} = maps:get(Src, Ds),
- infer_type(Op, Args, Ds);
-infer_type(_Op, _Args, _Ds) ->
- [].
-
-%% bif_type(Name, Args) -> Type
-%% Return the return type for the guard BIF or operator Name with
-%% arguments Args.
-%%
-%% Note that that the following BIFs are handle elsewhere:
-%%
-%% band/2
-
-bif_type(abs, [_]) -> number;
-bif_type(bit_size, [_]) -> t_integer();
-bif_type(byte_size, [_]) -> t_integer();
-bif_type(ceil, [_]) -> t_integer();
-bif_type(float, [_]) -> float;
-bif_type(floor, [_]) -> t_integer();
-bif_type(is_map_key, [_,_]) -> t_boolean();
-bif_type(length, [_]) -> t_integer();
-bif_type(map_size, [_]) -> t_integer();
-bif_type(node, []) -> #t_atom{};
-bif_type(node, [_]) -> #t_atom{};
-bif_type(round, [_]) -> t_integer();
-bif_type(size, [_]) -> t_integer();
-bif_type(trunc, [_]) -> t_integer();
-bif_type(tuple_size, [_]) -> t_integer();
-bif_type('bnot', [_]) -> t_integer();
-bif_type('bor', [_,_]) -> t_integer();
-bif_type('bsl', [_,_]) -> t_integer();
-bif_type('bsr', [_,_]) -> t_integer();
-bif_type('bxor', [_,_]) -> t_integer();
-bif_type('div', [_,_]) -> t_integer();
-bif_type('rem', [_,_]) -> t_integer();
-bif_type('/', [_,_]) -> float;
-bif_type(Name, Args) ->
- Arity = length(Args),
- case erl_internal:new_type_test(Name, Arity) orelse
- erl_internal:bool_op(Name, Arity) orelse
- erl_internal:comp_op(Name, Arity) of
- true ->
- t_boolean();
- false ->
- case erl_internal:arith_op(Name, Arity) of
- true -> number;
- false -> any
- end
- end.
+ infer_type(Op, Args, Ts, Ds);
+infer_type(bs_start_match, [#b_var{}=Bin], _Ts, _Ds) ->
+ T = {Bin,#t_bitstring{}},
+ {[T], [T]};
+infer_type(is_nonempty_list, [#b_var{}=Src], _Ts, _Ds) ->
+ T = {Src,cons},
+ {[T], [T]};
+infer_type(is_tagged_tuple, [#b_var{}=Src,#b_literal{val=Size},
+ #b_literal{}=Tag], _Ts, _Ds) ->
+ Es = beam_types:set_element_type(1, get_type(Tag, #{}), #{}),
+ T = {Src,#t_tuple{exact=true,size=Size,elements=Es}},
+ {[T], [T]};
+
+%% Type tests are handled separately from other BIFs as we're inferring types
+%% based on their result rather than whether they succeeded, so we know that
+%% subtraction is always safe.
+infer_type({bif,is_atom}, [Arg], _Ts, _Ds) ->
+ T = {Arg, #t_atom{}},
+ {[T], [T]};
+infer_type({bif,is_binary}, [Arg], _Ts, _Ds) ->
+ T = {Arg, #t_bitstring{unit=8}},
+ {[T], [T]};
+infer_type({bif,is_bitstring}, [Arg], _Ts, _Ds) ->
+ T = {Arg, #t_bitstring{}},
+ {[T], [T]};
+infer_type({bif,is_boolean}, [Arg], _Ts, _Ds) ->
+ T = {Arg, beam_types:make_boolean()},
+ {[T], [T]};
+infer_type({bif,is_float}, [Arg], _Ts, _Ds) ->
+ T = {Arg, float},
+ {[T], [T]};
+infer_type({bif,is_integer}, [Arg], _Ts, _Ds) ->
+ T = {Arg, #t_integer{}},
+ {[T], [T]};
+infer_type({bif,is_list}, [Arg], _Ts, _Ds) ->
+ T = {Arg, list},
+ {[T], [T]};
+infer_type({bif,is_map}, [Arg], _Ts, _Ds) ->
+ T = {Arg, #t_map{}},
+ {[T], [T]};
+infer_type({bif,is_number}, [Arg], _Ts, _Ds) ->
+ T = {Arg, number},
+ {[T], [T]};
+infer_type({bif,is_tuple}, [Arg], _Ts, _Ds) ->
+ T = {Arg, #t_tuple{}},
+ {[T], [T]};
+
+infer_type({bif,Op}, Args, Ts, _Ds) ->
+ ArgTypes = get_types(Args, Ts),
+
+ {_, PosTypes0, CanSubtract} = beam_call_types:types(erlang, Op, ArgTypes),
+ PosTypes = [T || {#b_var{},_}=T <- zip(Args, PosTypes0)],
+
+ case CanSubtract of
+ true -> {PosTypes, PosTypes};
+ false -> {PosTypes, []}
+ end;
-inferred_bif_type(is_atom, [_]) -> t_atom();
-inferred_bif_type(is_binary, [_]) -> {binary,8};
-inferred_bif_type(is_bitstring, [_]) -> {binary,1};
-inferred_bif_type(is_boolean, [_]) -> t_boolean();
-inferred_bif_type(is_float, [_]) -> float;
-inferred_bif_type(is_integer, [_]) -> t_integer();
-inferred_bif_type(is_list, [_]) -> list;
-inferred_bif_type(is_map, [_]) -> map;
-inferred_bif_type(is_number, [_]) -> number;
-inferred_bif_type(is_tuple, [_]) -> #t_tuple{};
-inferred_bif_type(abs, [_]) -> number;
-inferred_bif_type(bit_size, [_]) -> {binary,1};
-inferred_bif_type('bnot', [_]) -> t_integer();
-inferred_bif_type(byte_size, [_]) -> {binary,1};
-inferred_bif_type(ceil, [_]) -> number;
-inferred_bif_type(float, [_]) -> number;
-inferred_bif_type(floor, [_]) -> number;
-inferred_bif_type(hd, [_]) -> cons;
-inferred_bif_type(length, [_]) -> list;
-inferred_bif_type(map_size, [_]) -> map;
-inferred_bif_type('not', [_]) -> t_boolean();
-inferred_bif_type(round, [_]) -> number;
-inferred_bif_type(trunc, [_]) -> number;
-inferred_bif_type(tl, [_]) -> cons;
-inferred_bif_type(tuple_size, [_]) -> #t_tuple{};
-inferred_bif_type('and', [_,_]) -> t_boolean();
-inferred_bif_type('or', [_,_]) -> t_boolean();
-inferred_bif_type('xor', [_,_]) -> t_boolean();
-inferred_bif_type('band', [_,_]) -> t_integer();
-inferred_bif_type('bor', [_,_]) -> t_integer();
-inferred_bif_type('bsl', [_,_]) -> t_integer();
-inferred_bif_type('bsr', [_,_]) -> t_integer();
-inferred_bif_type('bxor', [_,_]) -> t_integer();
-inferred_bif_type('div', [_,_]) -> t_integer();
-inferred_bif_type('rem', [_,_]) -> t_integer();
-inferred_bif_type('+', [_,_]) -> number;
-inferred_bif_type('-', [_,_]) -> number;
-inferred_bif_type('*', [_,_]) -> number;
-inferred_bif_type('/', [_,_]) -> number;
-inferred_bif_type(_, _) -> any.
-
-is_math_bif(cos, 1) -> true;
-is_math_bif(cosh, 1) -> true;
-is_math_bif(sin, 1) -> true;
-is_math_bif(sinh, 1) -> true;
-is_math_bif(tan, 1) -> true;
-is_math_bif(tanh, 1) -> true;
-is_math_bif(acos, 1) -> true;
-is_math_bif(acosh, 1) -> true;
-is_math_bif(asin, 1) -> true;
-is_math_bif(asinh, 1) -> true;
-is_math_bif(atan, 1) -> true;
-is_math_bif(atanh, 1) -> true;
-is_math_bif(erf, 1) -> true;
-is_math_bif(erfc, 1) -> true;
-is_math_bif(exp, 1) -> true;
-is_math_bif(log, 1) -> true;
-is_math_bif(log2, 1) -> true;
-is_math_bif(log10, 1) -> true;
-is_math_bif(sqrt, 1) -> true;
-is_math_bif(atan2, 2) -> true;
-is_math_bif(pow, 2) -> true;
-is_math_bif(ceil, 1) -> true;
-is_math_bif(floor, 1) -> true;
-is_math_bif(fmod, 2) -> true;
-is_math_bif(pi, 0) -> true;
-is_math_bif(_, _) -> false.
+infer_type(_Op, _Args, _Ts, _Ds) ->
+ {[], []}.
join_types(Ts0, Ts1) ->
if
@@ -1626,7 +1402,7 @@ join_types_1([V|Vs], Ts0, Ts1) ->
{#{V:=Same},#{V:=Same}} ->
join_types_1(Vs, Ts0, Ts1);
{#{V:=T0},#{V:=T1}} ->
- case join(T0, T1) of
+ case beam_types:join(T0, T1) of
T1 ->
join_types_1(Vs, Ts0, Ts1);
T ->
@@ -1638,326 +1414,18 @@ join_types_1([V|Vs], Ts0, Ts1) ->
join_types_1([], Ts0, Ts1) ->
maps:merge(Ts0, Ts1).
-join([T1,T2|Ts]) ->
- join([join(T1, T2)|Ts]);
-join([T]) -> T.
-
-get_literal_from_type(#t_atom{elements=[Atom]}) ->
- #b_literal{val=Atom};
-get_literal_from_type(#t_integer{elements={Int,Int}}) ->
- #b_literal{val=Int};
-get_literal_from_type(nil) ->
- #b_literal{val=[]};
-get_literal_from_type(_) -> none.
-
-remove_element_info(#t_integer{elements={Min,Max}}, Es) ->
- foldl(fun(El, Acc) when Min =< El, El =< Max ->
- maps:remove(El, Acc);
- (_El, Acc) -> Acc
- end, Es, maps:keys(Es)).
-
-t_atom() ->
- #t_atom{elements=any}.
-
-t_atom(Atom) when is_atom(Atom) ->
- #t_atom{elements=[Atom]}.
-
-t_boolean() ->
- #t_atom{elements=[false,true]}.
-
-t_integer() ->
- #t_integer{elements=any}.
-
-t_integer(Int) when is_integer(Int) ->
- #t_integer{elements={Int,Int}}.
-
-t_integer(Min, Max) when is_integer(Min), is_integer(Max) ->
- #t_integer{elements={Min,Max}}.
-
-t_is_boolean(#t_atom{elements=[F,T]}) ->
- F =:= false andalso T =:= true;
-t_is_boolean(#t_atom{elements=[B]}) ->
- is_boolean(B);
-t_is_boolean(_) -> false.
-
-t_tuple_size(#t_tuple{size=Size,exact=false}) ->
- {at_least,Size};
-t_tuple_size(#t_tuple{size=Size,exact=true}) ->
- {exact,Size};
-t_tuple_size(_) ->
- none.
-
-is_singleton_type(Type) ->
- get_literal_from_type(Type) =/= none.
-
-get_element_type(Index, Es) ->
- case Es of
- #{ Index := T } -> T;
- #{} -> any
- end.
-
-set_element_type(_Key, none, Es) ->
- Es;
-set_element_type(Key, any, Es) ->
- maps:remove(Key, Es);
-set_element_type(Key, Type, Es) ->
- Es#{ Key => Type }.
-
-%% join(Type1, Type2) -> Type
-%% Return the "join" of Type1 and Type2. The join is a more general
-%% type than Type1 and Type2. For example:
-%%
-%% join(#t_integer{elements=any}, #t_integer=elements={0,3}}) ->
-%% #t_integer{}
-%%
-%% The join for two different types result in 'any', which is
-%% the top element for our type lattice:
-%%
-%% join(#t_integer{}, map) -> any
-
--spec join(type(), type()) -> type().
-
-join(T, T) ->
- verified_type(T);
-join(none, T) ->
- verified_type(T);
-join(T, none) ->
- verified_type(T);
-join(any, _) -> any;
-join(_, any) -> any;
-join(#t_atom{elements=[_|_]=Set1}, #t_atom{elements=[_|_]=Set2}) ->
- Set = ordsets:union(Set1, Set2),
- case ordsets:size(Set) of
- Size when Size =< ?ATOM_SET_SIZE ->
- #t_atom{elements=Set};
- _Size ->
- #t_atom{elements=any}
- end;
-join(#t_atom{elements=any}=T, #t_atom{elements=[_|_]}) -> T;
-join(#t_atom{elements=[_|_]}, #t_atom{elements=any}=T) -> T;
-join({binary,U1}, {binary,U2}) ->
- {binary,gcd(U1, U2)};
-join(#t_integer{}, #t_integer{}) -> t_integer();
-join(list, cons) -> list;
-join(cons, list) -> list;
-join(nil, cons) -> list;
-join(cons, nil) -> list;
-join(nil, list) -> list;
-join(list, nil) -> list;
-join(#t_integer{}, float) -> number;
-join(float, #t_integer{}) -> number;
-join(#t_integer{}, number) -> number;
-join(number, #t_integer{}) -> number;
-join(float, number) -> number;
-join(number, float) -> number;
-join(#t_tuple{size=Sz,exact=ExactA,elements=EsA},
- #t_tuple{size=Sz,exact=ExactB,elements=EsB}) ->
- Exact = ExactA and ExactB,
- Es = join_tuple_elements(Sz, EsA, EsB),
- #t_tuple{size=Sz,exact=Exact,elements=Es};
-join(#t_tuple{size=SzA,elements=EsA}, #t_tuple{size=SzB,elements=EsB}) ->
- Sz = min(SzA, SzB),
- Es = join_tuple_elements(Sz, EsA, EsB),
- #t_tuple{size=Sz,elements=Es};
-join(_T1, _T2) ->
- %%io:format("~p ~p\n", [_T1,_T2]),
- any.
-
-join_tuple_elements(MinSize, EsA, EsB) ->
- Es0 = join_elements(EsA, EsB),
- maps:filter(fun(Index, _Type) -> Index =< MinSize end, Es0).
-
-join_elements(Es1, Es2) ->
- Keys = if
- map_size(Es1) =< map_size(Es2) -> maps:keys(Es1);
- map_size(Es1) > map_size(Es2) -> maps:keys(Es2)
- end,
- join_elements_1(Keys, Es1, Es2, #{}).
-
-join_elements_1([Key | Keys], Es1, Es2, Acc0) ->
- case {Es1, Es2} of
- {#{ Key := Type1 }, #{ Key := Type2 }} ->
- Acc = set_element_type(Key, join(Type1, Type2), Acc0),
- join_elements_1(Keys, Es1, Es2, Acc);
- {#{}, #{}} ->
- join_elements_1(Keys, Es1, Es2, Acc0)
- end;
-join_elements_1([], _Es1, _Es2, Acc) ->
- Acc.
-
-gcd(A, B) ->
- case A rem B of
- 0 -> B;
- X -> gcd(B, X)
- end.
-
meet_types([{V,T0}|Vs], Ts) ->
#{V:=T1} = Ts,
- case meet(T0, T1) of
+ case beam_types:meet(T0, T1) of
T1 -> meet_types(Vs, Ts);
T -> meet_types(Vs, Ts#{V:=T})
end;
meet_types([], Ts) -> Ts.
-meet([T1,T2|Ts]) ->
- meet([meet(T1, T2)|Ts]);
-meet([T]) -> T.
-
subtract_types([{V,T0}|Vs], Ts) ->
#{V:=T1} = Ts,
- case subtract(T1, T0) of
+ case beam_types:subtract(T1, T0) of
T1 -> subtract_types(Vs, Ts);
T -> subtract_types(Vs, Ts#{V:=T})
end;
subtract_types([], Ts) -> Ts.
-
-%% subtract(Type1, Type2) -> Type.
-%% Subtract Type2 from Type1. Example:
-%%
-%% subtract(list, cons) -> nil
-
-subtract(#t_atom{elements=[_|_]=Set0}, #t_atom{elements=[_|_]=Set1}) ->
- case ordsets:subtract(Set0, Set1) of
- [] -> none;
- [_|_]=Set -> #t_atom{elements=Set}
- end;
-subtract(number, float) -> #t_integer{};
-subtract(number, #t_integer{elements=any}) -> float;
-subtract(list, cons) -> nil;
-subtract(list, nil) -> cons;
-subtract(T, _) -> T.
-
-%% meet(Type1, Type2) -> Type
-%% Return the "meet" of Type1 and Type2. The meet is a narrower
-%% type than Type1 and Type2. For example:
-%%
-%% meet(#t_integer{elements=any}, #t_integer{elements={0,3}}) ->
-%% #t_integer{elements={0,3}}
-%%
-%% The meet for two different types result in 'none', which is
-%% the bottom element for our type lattice:
-%%
-%% meet(#t_integer{}, map) -> none
-
--spec meet(type(), type()) -> type().
-
-meet(T, T) ->
- verified_type(T);
-meet(#t_atom{elements=[_|_]=Set1}, #t_atom{elements=[_|_]=Set2}) ->
- case ordsets:intersection(Set1, Set2) of
- [] ->
- none;
- [_|_]=Set ->
- #t_atom{elements=Set}
- end;
-meet(#t_atom{elements=[_|_]}=T, #t_atom{elements=any}) ->
- T;
-meet(#t_atom{elements=any}, #t_atom{elements=[_|_]}=T) ->
- T;
-meet(#t_integer{elements={_,_}}=T, #t_integer{elements=any}) ->
- T;
-meet(#t_integer{elements=any}, #t_integer{elements={_,_}}=T) ->
- T;
-meet(#t_integer{elements={Min1,Max1}},
- #t_integer{elements={Min2,Max2}}) ->
- #t_integer{elements={max(Min1, Min2),min(Max1, Max2)}};
-meet(#t_integer{}=T, number) -> T;
-meet(float=T, number) -> T;
-meet(number, #t_integer{}=T) -> T;
-meet(number, float=T) -> T;
-meet(list, cons) -> cons;
-meet(list, nil) -> nil;
-meet(cons, list) -> cons;
-meet(nil, list) -> nil;
-meet(#t_tuple{}=T1, #t_tuple{}=T2) ->
- meet_tuples(T1, T2);
-meet({binary,U1}, {binary,U2}) ->
- {binary,max(U1, U2)};
-meet(any, T) ->
- verified_type(T);
-meet(T, any) ->
- verified_type(T);
-meet(_, _) ->
- %% Inconsistent types. There will be an exception at runtime.
- none.
-
-meet_tuples(#t_tuple{size=Sz1,exact=true},
- #t_tuple{size=Sz2,exact=true}) when Sz1 =/= Sz2 ->
- none;
-meet_tuples(#t_tuple{size=Sz1,exact=Ex1,elements=Es1},
- #t_tuple{size=Sz2,exact=Ex2,elements=Es2}) ->
- Size = max(Sz1, Sz2),
- Exact = Ex1 or Ex2,
- case meet_elements(Es1, Es2) of
- none ->
- none;
- Es ->
- #t_tuple{size=Size,exact=Exact,elements=Es}
- end.
-
-meet_elements(Es1, Es2) ->
- Keys = maps:keys(Es1) ++ maps:keys(Es2),
- meet_elements_1(Keys, Es1, Es2, #{}).
-
-meet_elements_1([Key | Keys], Es1, Es2, Acc) ->
- case {Es1, Es2} of
- {#{ Key := Type1 }, #{ Key := Type2 }} ->
- case meet(Type1, Type2) of
- none -> none;
- Type -> meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type })
- end;
- {#{ Key := Type1 }, _} ->
- meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type1 });
- {_, #{ Key := Type2 }} ->
- meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type2 })
- end;
-meet_elements_1([], _Es1, _Es2, Acc) ->
- Acc.
-
-%% verified_type(Type) -> Type
-%% Returns the passed in type if it is one of the defined types.
-%% Crashes if there is anything wrong with the type.
-%%
-%% Here are all possible types:
-%%
-%% any Any Erlang term (top element for the type lattice).
-%%
-%% #t_atom{} Any atom or some specific atoms.
-%% {binary,Unit} Binary/bitstring aligned to unit Unit.
-%% float Floating point number.
-%% #t_integer{} Integer
-%% list Empty or nonempty list.
-%% map Map.
-%% nil Empty list.
-%% cons Cons (nonempty list).
-%% number A number (float or integer).
-%% #t_tuple{} Tuple.
-%%
-%% none No type (bottom element for the type lattice).
-
--spec verified_type(T) -> T when
- T :: type().
-
-verified_type(any=T) -> T;
-verified_type(none=T) -> T;
-verified_type(#t_atom{elements=any}=T) -> T;
-verified_type(#t_atom{elements=[_|_]}=T) -> T;
-verified_type({binary,U}=T) when is_integer(U) -> T;
-verified_type(#t_integer{elements=any}=T) -> T;
-verified_type(#t_integer{elements={Min,Max}}=T)
- when is_integer(Min), is_integer(Max) -> T;
-verified_type(list=T) -> T;
-verified_type(map=T) -> T;
-verified_type(nil=T) -> T;
-verified_type(cons=T) -> T;
-verified_type(number=T) -> T;
-verified_type(#t_tuple{size=Size,elements=Es}=T) ->
- %% All known elements must have a valid index and type. 'any' is prohibited
- %% since it's implicit and should never be present in the map.
- maps:fold(fun(Index, Element, _) when is_integer(Index),
- 1 =< Index, Index =< Size,
- Element =/= any, Element =/= none ->
- verified_type(Element)
- end, [], Es),
- T;
-verified_type(float=T) -> T.
diff --git a/lib/compiler/src/beam_trim.erl b/lib/compiler/src/beam_trim.erl
index acf3838da4..ad8839cc7d 100644
--- a/lib/compiler/src/beam_trim.erl
+++ b/lib/compiler/src/beam_trim.erl
@@ -244,6 +244,9 @@ remap([{make_fun2,_,_,_,_}=I|T], Map, Acc) ->
remap([{deallocate,N}|Is], Map, Acc) ->
I = {deallocate,Map({frame_size,N})},
remap(Is, Map, [I|Acc]);
+remap([{swap,Reg1,Reg2}|Is], Map, Acc) ->
+ I = {swap,Map(Reg1),Map(Reg2)},
+ remap(Is, Map, [I|Acc]);
remap([{test,Name,Fail,Ss}|Is], Map, Acc) ->
I = {test,Name,Fail,[Map(S) || S <- Ss]},
remap(Is, Map, [I|Acc]);
@@ -382,6 +385,8 @@ frame_size([{bs_set_position,_,_}|Is], Safe) ->
frame_size(Is, Safe);
frame_size([{bs_get_tail,_,_,_}|Is], Safe) ->
frame_size(Is, Safe);
+frame_size([{swap,_,_}|Is], Safe) ->
+ frame_size(Is, Safe);
frame_size(_, _) -> throw(not_possible).
frame_size_branch(0, Is, Safe) ->
@@ -444,6 +449,8 @@ is_not_used(Y, [{line,_}|Is]) ->
is_not_used(Y, Is);
is_not_used(Y, [{make_fun2,_,_,_,_}|Is]) ->
is_not_used(Y, Is);
+is_not_used(Y, [{swap,Reg1,Reg2}|Is]) ->
+ Y =/= Reg1 andalso Y =/= Reg2 andalso is_not_used(Y, Is);
is_not_used(Y, [{test,_,_,Ss}|Is]) ->
not member(Y, Ss) andalso is_not_used(Y, Is);
is_not_used(Y, [{test,_Op,{f,_},_Live,Ss,Dst}|Is]) ->
diff --git a/lib/compiler/src/beam_types.erl b/lib/compiler/src/beam_types.erl
new file mode 100644
index 0000000000..07d3c3d3f2
--- /dev/null
+++ b/lib/compiler/src/beam_types.erl
@@ -0,0 +1,411 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(beam_types).
+
+-include("beam_types.hrl").
+
+-import(lists, [foldl/3]).
+
+-export([meet/1, meet/2, join/1, join/2, subtract/2]).
+
+-export([get_singleton_value/1,
+ get_tuple_size/1,
+ is_singleton_type/1,
+ is_boolean_type/1]).
+
+-export([get_element_type/2, set_element_type/3]).
+
+-export([make_type_from_value/1]).
+
+-export([make_atom/1,
+ make_boolean/0,
+ make_integer/1,
+ make_integer/2,
+ make_tuple/2,
+ make_tuple/3]).
+
+%% Return the "join" of Type1 and Type2. The join is a more general
+%% type than Type1 and Type2. For example:
+%%
+%% join(#t_integer{elements=any}, #t_integer=elements={0,3}}) ->
+%% #t_integer{}
+%%
+%% The join for two different types result in 'any', which is
+%% the top element for our type lattice:
+%%
+%% join(#t_integer{}, #t_map{}) -> any
+
+-spec join(type(), type()) -> type().
+
+join(T, T) ->
+ verified_type(T);
+join(none, T) ->
+ verified_type(T);
+join(T, none) ->
+ verified_type(T);
+join(any, _) -> any;
+join(_, any) -> any;
+join(#t_atom{elements=[_|_]=Set1}, #t_atom{elements=[_|_]=Set2}) ->
+ Set = ordsets:union(Set1, Set2),
+ case ordsets:size(Set) of
+ Size when Size =< ?ATOM_SET_SIZE ->
+ #t_atom{elements=Set};
+ _Size ->
+ #t_atom{elements=any}
+ end;
+join(#t_atom{elements=any}=T, #t_atom{elements=[_|_]}) -> T;
+join(#t_atom{elements=[_|_]}, #t_atom{elements=any}=T) -> T;
+join(#t_bitstring{unit=U1}, #t_bitstring{unit=U2}) ->
+ #t_bitstring{unit=gcd(U1, U2)};
+join(#t_fun{}, #t_fun{}) ->
+ #t_fun{};
+join(#t_integer{elements={MinA,MaxA}},
+ #t_integer{elements={MinB,MaxB}}) ->
+ #t_integer{elements={min(MinA,MinB),max(MaxA,MaxB)}};
+join(#t_bs_context{slots=SlotsA,valid=ValidA},
+ #t_bs_context{slots=SlotsB,valid=ValidB}) ->
+ #t_bs_context{slots=min(SlotsA, SlotsB),
+ valid=ValidA band ValidB};
+join(#t_integer{}, #t_integer{}) -> #t_integer{};
+join(list, cons) -> list;
+join(cons, list) -> list;
+join(nil, cons) -> list;
+join(cons, nil) -> list;
+join(nil, list) -> list;
+join(list, nil) -> list;
+join(#t_integer{}, float) -> number;
+join(float, #t_integer{}) -> number;
+join(#t_integer{}, number) -> number;
+join(number, #t_integer{}) -> number;
+join(float, number) -> number;
+join(number, float) -> number;
+join(#t_tuple{size=Sz,exact=ExactA,elements=EsA},
+ #t_tuple{size=Sz,exact=ExactB,elements=EsB}) ->
+ Exact = ExactA and ExactB,
+ Es = join_tuple_elements(Sz, EsA, EsB),
+ #t_tuple{size=Sz,exact=Exact,elements=Es};
+join(#t_tuple{size=SzA,elements=EsA}, #t_tuple{size=SzB,elements=EsB}) ->
+ Sz = min(SzA, SzB),
+ Es = join_tuple_elements(Sz, EsA, EsB),
+ #t_tuple{size=Sz,elements=Es};
+join(_T1, _T2) ->
+ %%io:format("~p ~p\n", [_T1,_T2]),
+ any.
+
+join_tuple_elements(MinSize, EsA, EsB) ->
+ Es0 = join_elements(EsA, EsB),
+ maps:filter(fun(Index, _Type) -> Index =< MinSize end, Es0).
+
+join_elements(Es1, Es2) ->
+ Keys = if
+ map_size(Es1) =< map_size(Es2) -> maps:keys(Es1);
+ map_size(Es1) > map_size(Es2) -> maps:keys(Es2)
+ end,
+ join_elements_1(Keys, Es1, Es2, #{}).
+
+join_elements_1([Key | Keys], Es1, Es2, Acc0) ->
+ case {Es1, Es2} of
+ {#{ Key := Type1 }, #{ Key := Type2 }} ->
+ Acc = set_element_type(Key, join(Type1, Type2), Acc0),
+ join_elements_1(Keys, Es1, Es2, Acc);
+ {#{}, #{}} ->
+ join_elements_1(Keys, Es1, Es2, Acc0)
+ end;
+join_elements_1([], _Es1, _Es2, Acc) ->
+ Acc.
+
+gcd(A, B) ->
+ case A rem B of
+ 0 -> B;
+ X -> gcd(B, X)
+ end.
+
+%% Joins all the given types into a single type.
+-spec join([type()]) -> type().
+
+join([T1,T2|Ts]) ->
+ join([join(T1, T2)|Ts]);
+join([T]) -> T.
+
+%% Return the "meet" of Type1 and Type2. The meet is a narrower
+%% type than Type1 and Type2. For example:
+%%
+%% meet(#t_integer{elements=any}, #t_integer{elements={0,3}}) ->
+%% #t_integer{elements={0,3}}
+%%
+%% The meet for two different types result in 'none', which is
+%% the bottom element for our type lattice:
+%%
+%% meet(#t_integer{}, #t_map{}) -> none
+
+-spec meet(type(), type()) -> type().
+
+meet(T, T) ->
+ verified_type(T);
+meet(#t_atom{elements=[_|_]=Set1}, #t_atom{elements=[_|_]=Set2}) ->
+ case ordsets:intersection(Set1, Set2) of
+ [] ->
+ none;
+ [_|_]=Set ->
+ #t_atom{elements=Set}
+ end;
+meet(#t_atom{elements=[_|_]}=T, #t_atom{elements=any}) ->
+ T;
+meet(#t_atom{elements=any}, #t_atom{elements=[_|_]}=T) ->
+ T;
+meet(#t_fun{arity=any}, #t_fun{}=T) ->
+ T;
+meet(#t_fun{}=T, #t_fun{arity=any}) ->
+ T;
+meet(#t_integer{elements={_,_}}=T, #t_integer{elements=any}) ->
+ T;
+meet(#t_integer{elements=any}, #t_integer{elements={_,_}}=T) ->
+ T;
+meet(#t_integer{elements={MinA,MaxA}}, #t_integer{elements={MinB,MaxB}})
+ when MinA >= MinB, MaxA =< MaxB;
+ MinB >= MinA, MaxB =< MaxA ->
+ #t_integer{elements={max(MinA, MinB),min(MaxA, MaxB)}};
+meet(#t_integer{}=T, number) -> T;
+meet(float=T, number) -> T;
+meet(number, #t_integer{}=T) -> T;
+meet(number, float=T) -> T;
+meet(list, cons) -> cons;
+meet(list, nil) -> nil;
+meet(cons, list) -> cons;
+meet(nil, list) -> nil;
+meet(#t_tuple{}=T1, #t_tuple{}=T2) ->
+ meet_tuples(T1, T2);
+meet(#t_bitstring{unit=U1}, #t_bitstring{unit=U2}) ->
+ #t_bitstring{unit=U1 * U2 div gcd(U1, U2)};
+meet(any, T) ->
+ verified_type(T);
+meet(T, any) ->
+ verified_type(T);
+meet(_, _) ->
+ %% Inconsistent types. There will be an exception at runtime.
+ none.
+
+meet_tuples(#t_tuple{size=Sz1,exact=true},
+ #t_tuple{size=Sz2,exact=true}) when Sz1 =/= Sz2 ->
+ none;
+meet_tuples(#t_tuple{size=Sz1,exact=Ex1,elements=Es1},
+ #t_tuple{size=Sz2,exact=Ex2,elements=Es2}) ->
+ Size = max(Sz1, Sz2),
+ Exact = Ex1 or Ex2,
+ case meet_elements(Es1, Es2) of
+ none ->
+ none;
+ Es ->
+ #t_tuple{size=Size,exact=Exact,elements=Es}
+ end.
+
+meet_elements(Es1, Es2) ->
+ Keys = maps:keys(Es1) ++ maps:keys(Es2),
+ meet_elements_1(Keys, Es1, Es2, #{}).
+
+meet_elements_1([Key | Keys], Es1, Es2, Acc) ->
+ case {Es1, Es2} of
+ {#{ Key := Type1 }, #{ Key := Type2 }} ->
+ case meet(Type1, Type2) of
+ none -> none;
+ Type -> meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type })
+ end;
+ {#{ Key := Type1 }, _} ->
+ meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type1 });
+ {_, #{ Key := Type2 }} ->
+ meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type2 })
+ end;
+meet_elements_1([], _Es1, _Es2, Acc) ->
+ Acc.
+
+%% Meets all the given types into a single type.
+-spec meet([type()]) -> type().
+
+meet([T1,T2|Ts]) ->
+ meet([meet(T1, T2)|Ts]);
+meet([T]) -> T.
+
+%% Subtract Type2 from Type1. Example:
+%% subtract(list, cons) -> nil
+
+-spec subtract(type(), type()) -> type().
+
+subtract(#t_atom{elements=[_|_]=Set0}, #t_atom{elements=[_|_]=Set1}) ->
+ case ordsets:subtract(Set0, Set1) of
+ [] -> none;
+ [_|_]=Set -> #t_atom{elements=Set}
+ end;
+subtract(number, float) -> #t_integer{};
+subtract(number, #t_integer{elements=any}) -> float;
+subtract(list, cons) -> nil;
+subtract(list, nil) -> cons;
+subtract(T, _) -> T.
+
+%% Verifies that the given type is well-formed.
+
+-spec verified_type(T) -> T when
+ T :: type().
+
+verified_type(any=T) -> T;
+verified_type(none=T) -> T;
+verified_type(#t_atom{elements=any}=T) -> T;
+verified_type(#t_atom{elements=[_|_]}=T) -> T;
+verified_type(#t_bitstring{unit=U}=T) when is_integer(U), U >= 1 -> T;
+verified_type(#t_bs_context{}=T) -> T;
+verified_type(#t_fun{arity=Arity}=T) when Arity =:= any; is_integer(Arity) -> T;
+verified_type(#t_integer{elements=any}=T) -> T;
+verified_type(#t_integer{elements={Min,Max}}=T)
+ when is_integer(Min), is_integer(Max), Min =< Max -> T;
+verified_type(list=T) -> T;
+verified_type(#t_map{}=T) -> T;
+verified_type(nil=T) -> T;
+verified_type(cons=T) -> T;
+verified_type(number=T) -> T;
+verified_type(#t_tuple{size=Size,elements=Es}=T) ->
+ %% All known elements must have a valid index and type. 'any' is prohibited
+ %% since it's implicit and should never be present in the map.
+ maps:fold(fun(Index, Element, _) when is_integer(Index),
+ 1 =< Index, Index =< Size,
+ Element =/= any, Element =/= none ->
+ verified_type(Element)
+ end, [], Es),
+ T;
+verified_type(float=T) -> T.
+
+%%%
+%%% Type operators
+%%%
+
+-spec get_singleton_value(Type) -> Result when
+ Type :: type(),
+ Result :: {ok, term()} | error.
+get_singleton_value(#t_atom{elements=[Atom]}) ->
+ {ok, Atom};
+get_singleton_value(#t_integer{elements={Int,Int}}) ->
+ {ok, Int};
+get_singleton_value(nil) ->
+ {ok, []};
+get_singleton_value(_) ->
+ error.
+
+-spec get_tuple_size(Type) -> Result when
+ Type :: type(),
+ Result :: none | {at_least, Size} | {exact, Size},
+ Size :: non_neg_integer().
+get_tuple_size(#t_tuple{size=Size,exact=false}) ->
+ {at_least,Size};
+get_tuple_size(#t_tuple{size=Size,exact=true}) ->
+ {exact,Size};
+get_tuple_size(_) ->
+ none.
+
+-spec is_boolean_type(type()) -> boolean().
+is_boolean_type(#t_atom{elements=[F,T]}) ->
+ F =:= false andalso T =:= true;
+is_boolean_type(#t_atom{elements=[B]}) ->
+ is_boolean(B);
+is_boolean_type(_) -> false.
+
+-spec is_singleton_type(type()) -> boolean().
+is_singleton_type(Type) ->
+ get_singleton_value(Type) =/= error.
+
+-spec set_element_type(Key, Type, Elements) -> Elements when
+ Key :: term(),
+ Type :: type(),
+ Elements :: elements().
+set_element_type(_Key, none, Es) ->
+ Es;
+set_element_type(Key, any, Es) ->
+ maps:remove(Key, Es);
+set_element_type(Key, Type, Es) ->
+ Es#{ Key => Type }.
+
+-spec get_element_type(Key, Elements) -> type() when
+ Key :: term(),
+ Elements :: elements().
+get_element_type(Index, Es) ->
+ case Es of
+ #{ Index := T } -> T;
+ #{} -> any
+ end.
+
+%%%
+%%% Type constructors
+%%%
+
+-spec make_type_from_value(term()) -> type().
+make_type_from_value(Value) ->
+ mtfv_1(Value).
+
+mtfv_1([]) -> nil;
+mtfv_1([_|_]) -> cons;
+mtfv_1(A) when is_atom(A) -> #t_atom{elements=[A]};
+mtfv_1(B) when is_binary(B) -> #t_bitstring{unit=8};
+mtfv_1(B) when is_bitstring(B) -> #t_bitstring{};
+mtfv_1(F) when is_float(F) -> float;
+mtfv_1(F) when is_function(F) ->
+ {arity, Arity} = erlang:fun_info(F, arity),
+ #t_fun{arity=Arity};
+mtfv_1(I) when is_integer(I) -> make_integer(I);
+mtfv_1(M) when is_map(M) -> #t_map{};
+mtfv_1(T) when is_tuple(T) ->
+ {Es,_} = foldl(fun(Val, {Es0, Index}) ->
+ Type = mtfv_1(Val),
+ Es = set_element_type(Index, Type, Es0),
+ {Es, Index + 1}
+ end, {#{}, 1}, tuple_to_list(T)),
+ #t_tuple{exact=true,size=tuple_size(T),elements=Es};
+mtfv_1(_Term) ->
+ any.
+
+-spec make_atom(atom()) -> type().
+make_atom(Atom) when is_atom(Atom) ->
+ #t_atom{elements=[Atom]}.
+
+-spec make_boolean() -> type().
+make_boolean() ->
+ #t_atom{elements=[false,true]}.
+
+-spec make_integer(integer()) -> type().
+make_integer(Int) when is_integer(Int) ->
+ make_integer(Int, Int).
+
+-spec make_integer(Min, Max) -> type() when
+ Min :: integer(),
+ Max :: integer().
+make_integer(Min, Max) when is_integer(Min), is_integer(Max), Min =< Max ->
+ #t_integer{elements={Min,Max}}.
+
+-spec make_tuple(Size, Exact) -> type() when
+ Size :: non_neg_integer(),
+ Exact :: boolean().
+make_tuple(Size, Exact) ->
+ make_tuple(Size, Exact, #{}).
+
+-spec make_tuple(Size, Exact, Elements) -> type() when
+ Size :: non_neg_integer(),
+ Exact :: boolean(),
+ Elements :: #{ non_neg_integer() => type() }.
+make_tuple(Size, Exact, Elements) ->
+ #t_tuple{size=Size,
+ exact=Exact,
+ elements=Elements}.
diff --git a/lib/compiler/src/beam_types.hrl b/lib/compiler/src/beam_types.hrl
new file mode 100644
index 0000000000..825eca4c64
--- /dev/null
+++ b/lib/compiler/src/beam_types.hrl
@@ -0,0 +1,72 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. 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%
+%%
+
+%% 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_bitstring{} Bitstring.
+%% - #t_bs_context{} Match context.
+%% - #t_fun{} Fun.
+%% - #t_map{} Map.
+%% - number Any number.
+%% -- float Floating point number.
+%% -- integer Integer.
+%% - list Any list.
+%% -- cons Cons (nonempty list).
+%% -- nil The empty list.
+%% - #t_tuple{} Tuple.
+%% - #t_abstract{} Psuedo-type used in the validator to track tuples
+% under construction, match context positions, etc.
+%%
+%% none No type (bottom element).
+
+-define(ATOM_SET_SIZE, 5).
+
+-record(t_atom, {elements=any :: 'any' | [atom()]}).
+-record(t_fun, {arity=any :: arity() | 'any'}).
+-record(t_integer, {elements=any :: 'any' | {integer(),integer()}}).
+-record(t_bitstring, {unit=1 :: pos_integer()}).
+-record(t_bs_context, {slots=0 :: non_neg_integer(),
+ valid=0 :: non_neg_integer()}).
+-record(t_map, {elements=#{} :: map_elements()}).
+-record(t_tuple, {size=0 :: integer(),
+ exact=false :: boolean(),
+ elements=#{} :: tuple_elements()}).
+-record(t_abstract, {kind :: atom()}).
+
+%% Known element types, unknown elements are assumed to be 'any'. The key is
+%% a 1-based integer index for tuples, and a plain literal for maps (that is,
+%% not wrapped in a #b_literal{}, just the value itself).
+
+-type tuple_elements() :: #{ Key :: pos_integer() => type() }.
+-type map_elements() :: #{ Key :: term() => type() }.
+
+-type elements() :: tuple_elements() | map_elements().
+
+-type type() :: any | none |
+ list | number |
+ #t_atom{} | #t_bitstring{} | #t_bs_context{} | #t_fun{} |
+ #t_integer{} | #t_map{} | #t_tuple{} | #t_abstract{} |
+ 'cons' | 'float' | 'nil'.
diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl
index ebe9631e09..90049b3a25 100644
--- a/lib/compiler/src/beam_validator.erl
+++ b/lib/compiler/src/beam_validator.erl
@@ -19,6 +19,10 @@
-module(beam_validator).
+-include("beam_types.hrl").
+
+-define(UNICODE_MAX, (16#10FFFF)).
+
-compile({no_auto_import,[min/2]}).
%% Avoid warning for local function error/1 clashing with autoimported BIF.
@@ -26,7 +30,6 @@
%% Interface for compiler.
-export([module/2, format_error/1]).
--export([type_anno/1, type_anno/2, type_anno/4]).
-import(lists, [dropwhile/2,foldl/3,member/2,reverse/1,sort/1,zip/2]).
@@ -45,34 +48,6 @@ module({Mod,Exp,Attr,Fs,Lc}=Code, _Opts)
{error,[{atom_to_list(Mod),Es}]}
end.
-%% Provides a stable interface for type annotations, used by certain passes to
-%% indicate that we can safely assume that a register has a given type.
--spec type_anno(term()) -> term().
-type_anno(atom) -> {atom,[]};
-type_anno(bool) -> bool;
-type_anno({binary,_}) -> binary;
-type_anno(cons) -> cons;
-type_anno(float) -> {float,[]};
-type_anno(integer) -> {integer,[]};
-type_anno(list) -> list;
-type_anno(map) -> map;
-type_anno(match_context) -> match_context;
-type_anno(number) -> number;
-type_anno(nil) -> nil.
-
--spec type_anno(term(), term()) -> term().
-type_anno(atom, Value) when is_atom(Value) -> {atom, Value};
-type_anno(float, Value) when is_float(Value) -> {float, Value};
-type_anno(integer, Value) when is_integer(Value) -> {integer, Value}.
-
--spec type_anno(term(), term(), term(), term()) -> term().
-type_anno(tuple, Size, Exact, Elements) when is_integer(Size), Size >= 0,
- is_map(Elements) ->
- case Exact of
- true -> {tuple, Size, Elements};
- false -> {tuple, [Size], Elements}
- end.
-
-spec format_error(term()) -> iolist().
format_error({{_M,F,A},{I,Off,limit}}) ->
@@ -119,7 +94,7 @@ format_error(Error) ->
%% format as used in the compiler and in .S files.
validate(Module, Fs) ->
- Ft = index_parameter_types(Fs, []),
+ Ft = build_function_table(Fs, []),
validate_0(Module, Fs, Ft).
validate_0(_Module, [], _) -> [];
@@ -149,30 +124,6 @@ validate_0(Module, [{function,Name,Ar,Entry,Code}|Fs], Ft) ->
{literal, term()} |
nil.
--type tuple_sz() :: [non_neg_integer()] | %% Inexact
- non_neg_integer(). %% Exact.
-
-%% Match context type.
--record(ms,
- {id=make_ref() :: reference(), %Unique ID.
- valid=0 :: non_neg_integer(), %Valid slots
- slots=0 :: non_neg_integer() %Number of slots
- }).
-
--type type() :: binary |
- cons |
- list |
- map |
- nil |
- #ms{} |
- ms_position |
- none |
- number |
- term |
- tuple_in_progress |
- {tuple, tuple_sz(), #{ literal() => type() }} |
- literal().
-
-type tag() :: initialized |
uninitialized |
{catchtag, [label()]} |
@@ -200,7 +151,7 @@ validate_0(Module, [{function,Name,Ar,Entry,Code}|Fs], Ft) ->
numy=none :: none | undecided | index(),
%% Available heap size.
h=0,
- %Available heap size for floats.
+ %%Available heap size for floats.
hf=0,
%% Floating point state.
fls=undefined,
@@ -225,36 +176,32 @@ validate_0(Module, [{function,Name,Ar,Entry,Code}|Fs], Ft) ->
branched=gb_trees:empty() :: branched_tab(),
%% All defined labels
labels=gb_sets:empty() :: label_set(),
- %% Argument information of other functions in the module
+ %% Information of other functions in the module
ft=gb_trees:empty() :: ft_tab(),
%% Counter for #value_ref{} creation
ref_ctr=0 :: index()
}).
-index_parameter_types([{function,_,_,Entry,Code0}|Fs], Acc0) ->
+build_function_table([{function,_,Arity,Entry,Code0}|Fs], Acc0) ->
Code = dropwhile(fun({label,L}) when L =:= Entry -> false;
(_) -> true
end, Code0),
case Code of
[{label,Entry}|Is] ->
- Acc = index_parameter_types_1(Is, Entry, Acc0),
- index_parameter_types(Fs, Acc);
+ Info = #{ arity => Arity,
+ parameter_types => find_parameter_types(Is, #{}) },
+ build_function_table(Fs, [{Entry, Info} | Acc0]);
_ ->
- %% Something serious is wrong. Ignore it for now.
+ %% Something is seriously wrong. Ignore it for now.
%% It will be detected and diagnosed later.
- index_parameter_types(Fs, Acc0)
+ build_function_table(Fs, Acc0)
end;
-index_parameter_types([], Acc) ->
+build_function_table([], Acc) ->
gb_trees:from_orddict(sort(Acc)).
-index_parameter_types_1([{'%', {type_info, Reg, Type0}} | Is], Entry, Acc) ->
- Type = case Type0 of
- match_context -> #ms{};
- _ -> Type0
- end,
- Key = {Entry, Reg},
- index_parameter_types_1(Is, Entry, [{Key, Type} | Acc]);
-index_parameter_types_1(_, _, Acc) ->
+find_parameter_types([{'%', {type_info, Reg, Type}} | Is], Acc) ->
+ find_parameter_types(Is, Acc#{ Reg => Type });
+find_parameter_types(_, Acc) ->
Acc.
validate_1(Is, Name, Arity, Entry, Ft) ->
@@ -326,7 +273,7 @@ init_vst(Arity, Ls1, Ls2, Ft) ->
init_function_args(-1, Vst) ->
Vst;
init_function_args(X, Vst) ->
- init_function_args(X - 1, create_term(term, argument, [], {x,X}, Vst)).
+ init_function_args(X - 1, create_term(any, argument, [], {x,X}, Vst)).
kill_heap_allocation(St) ->
St#st{h=0,hf=0}.
@@ -385,13 +332,30 @@ valfun_1({bs_get_tail,Ctx,Dst,Live}, Vst0) ->
verify_live(Live, Vst0),
verify_y_init(Vst0),
Vst = prune_x_regs(Live, Vst0),
- extract_term(binary, bs_get_tail, [Ctx], Dst, Vst, Vst0);
+ extract_term(#t_bitstring{}, bs_get_tail, [Ctx], Dst, Vst, Vst0);
valfun_1(bs_init_writable=I, Vst) ->
call(I, 1, Vst);
valfun_1(build_stacktrace=I, Vst) ->
call(I, 1, Vst);
valfun_1({move,Src,Dst}, Vst) ->
assign(Src, Dst, Vst);
+valfun_1({swap,RegA,RegB}, Vst0) ->
+ assert_movable(RegA, Vst0),
+ assert_movable(RegB, Vst0),
+
+ %% We don't expect fragile registers to be swapped.
+ %% Therefore, we can conservatively make both registers
+ %% fragile if one of the register is fragile instead of
+ %% swapping the fragility of the registers.
+ Sources = [RegA,RegB],
+ Vst1 = propagate_fragility(RegA, Sources, Vst0),
+ Vst2 = propagate_fragility(RegB, Sources, Vst1),
+
+ %% Swap the value references.
+ VrefA = get_reg_vref(RegA, Vst2),
+ VrefB = get_reg_vref(RegB, Vst2),
+ Vst = set_reg_vref(VrefB, RegA, Vst2),
+ set_reg_vref(VrefA, RegB, Vst);
valfun_1({fmove,Src,{fr,_}=Dst}, Vst) ->
assert_type(float, Src, Vst),
set_freg(Dst, Vst);
@@ -399,7 +363,7 @@ valfun_1({fmove,{fr,_}=Src,Dst}, Vst0) ->
assert_freg_set(Src, Vst0),
assert_fls(checked, Vst0),
Vst = eat_heap_float(Vst0),
- create_term({float,[]}, fmove, [], Dst, Vst);
+ create_term(float, fmove, [], Dst, Vst);
valfun_1({kill,Reg}, Vst) ->
create_tag(initialized, kill, [], Reg, Vst);
valfun_1({init,Reg}, Vst) ->
@@ -407,17 +371,16 @@ valfun_1({init,Reg}, Vst) ->
valfun_1({test_heap,Heap,Live}, Vst) ->
test_heap(Heap, Live, Vst);
valfun_1({bif,Op,{f,_},Ss,Dst}=I, Vst) ->
- case is_bif_safe(Op, length(Ss)) of
- false ->
- %% Since the BIF can fail, make sure that any catch state
- %% is updated.
- valfun_2(I, Vst);
- true ->
- %% It can't fail, so we finish handling it here (not updating
- %% catch state).
- validate_src(Ss, Vst),
- Type = bif_return_type(Op, Ss, Vst),
- extract_term(Type, {bif,Op}, Ss, Dst, Vst)
+ case beam_call_types:never_throws(erlang, Op, length(Ss)) of
+ true ->
+ %% It can't fail, so we finish handling it here (not updating
+ %% catch state).
+ {RetType, _, _} = bif_types(Op, Ss, Vst),
+ extract_term(RetType, {bif,Op}, Ss, Dst, Vst);
+ false ->
+ %% Since the BIF can fail, make sure that any catch state
+ %% is updated.
+ valfun_2(I, Vst)
end;
%% Put instructions.
valfun_1({put_list,A,B,Dst}, Vst0) ->
@@ -431,14 +394,15 @@ valfun_1({put_tuple2,Dst,{list,Elements}}, Vst0) ->
Vst = eat_heap(Size+1, Vst0),
{Es,_} = foldl(fun(Val, {Es0, Index}) ->
Type = get_term_type(Val, Vst0),
- Es = set_element_type({integer,Index}, Type, Es0),
+ Es = beam_types:set_element_type(Index, Type, Es0),
{Es, Index + 1}
end, {#{}, 1}, Elements),
- Type = {tuple,Size,Es},
+ Type = #t_tuple{exact=true,size=Size,elements=Es},
create_term(Type, put_tuple2, [], Dst, Vst);
valfun_1({put_tuple,Sz,Dst}, Vst0) when is_integer(Sz) ->
Vst1 = eat_heap(1, Vst0),
- Vst = create_term(tuple_in_progress, put_tuple, [], Dst, Vst1),
+ Vst = create_term(#t_abstract{kind=tuple_in_progress}, put_tuple, [],
+ Dst, Vst1),
#vst{current=St0} = Vst,
St = St0#st{puts_left={Sz,{Dst,Sz,#{}}}},
Vst#vst{current=St};
@@ -450,12 +414,15 @@ valfun_1({put,Src}, Vst0) ->
#st{puts_left=none} ->
error(not_building_a_tuple);
#st{puts_left={1,{Dst,Sz,Es0}}} ->
- Es = Es0#{ {integer,Sz} => get_term_type(Src, Vst0) },
+ ElementType = get_term_type(Src, Vst0),
+ Es = beam_types:set_element_type(Sz, ElementType, Es0),
St = St0#st{puts_left=none},
- create_term({tuple,Sz,Es}, put_tuple, [], Dst, Vst#vst{current=St});
+ Type = #t_tuple{exact=true,size=Sz,elements=Es},
+ create_term(Type, put_tuple, [], Dst, Vst#vst{current=St});
#st{puts_left={PutsLeft,{Dst,Sz,Es0}}} when is_integer(PutsLeft) ->
Index = Sz - PutsLeft + 1,
- Es = Es0#{ {integer,Index} => get_term_type(Src, Vst0) },
+ ElementType = get_term_type(Src, Vst0),
+ Es = beam_types:set_element_type(Index, ElementType, Es0),
St = St0#st{puts_left={PutsLeft-1,{Dst,Sz,Es}}},
Vst#vst{current=St}
end;
@@ -469,8 +436,10 @@ valfun_1(remove_message, Vst) ->
%% The message term is no longer fragile. It can be used
%% without restrictions.
remove_fragility(Vst);
-valfun_1({'%', {type_info, Reg, match_context}}, Vst) ->
- update_type(fun meet/2, #ms{}, Reg, Vst);
+valfun_1({'%', {type_info, Reg, #t_bs_context{}=Type}}, Vst) ->
+ %% This is a gross hack, but we'll be rid of it once we have proper union
+ %% types.
+ override_type(Type, Reg, Vst);
valfun_1({'%', {type_info, Reg, Type}}, Vst) ->
%% Explicit type information inserted by optimization passes to indicate
%% that Reg has a certain type, so that we can accept cross-function type
@@ -490,15 +459,15 @@ valfun_1({line,_}, Vst) ->
Vst;
%% Exception generating calls
valfun_1({call_ext,Live,Func}=I, Vst) ->
- case call_return_type(Func, Vst) of
- exception ->
- verify_live(Live, Vst),
+ case call_types(Func, Live, Vst) of
+ {none, _, _} ->
+ verify_live(Live, Vst),
%% The stack will be scanned, so Y registers
%% must be initialized.
verify_y_init(Vst),
- kill_state(Vst);
- _ ->
- valfun_2(I, Vst)
+ kill_state(Vst);
+ _ ->
+ valfun_2(I, Vst)
end;
valfun_1(_I, #vst{current=#st{ct=undecided}}) ->
error(unknown_catch_try_state);
@@ -534,7 +503,7 @@ valfun_1({catch_end,Reg}, #vst{current=#st{ct=[Fail|_]}}=Vst0) ->
case get_tag_type(Reg, Vst0) of
{catchtag,Fail} ->
%% {x,0} contains the caught term, if any.
- create_term(term, catch_end, [], {x,0}, kill_catch_tag(Reg, Vst0));
+ create_term(any, catch_end, [], {x,0}, kill_catch_tag(Reg, Vst0));
Type ->
error({wrong_tag_type,Type})
end;
@@ -553,31 +522,32 @@ valfun_1({try_case,Reg}, #vst{current=#st{ct=[Fail|_]}}=Vst0) ->
Vst1 = prune_x_regs(0, kill_catch_tag(Reg, Vst0)),
%% Class:Error:Stacktrace
- Vst2 = create_term({atom,[]}, try_case, [], {x,0}, Vst1),
- Vst = create_term(term, try_case, [], {x,1}, Vst2),
- create_term(term, try_case, [], {x,2}, Vst);
+ Vst2 = create_term(#t_atom{}, try_case, [], {x,0}, Vst1),
+ Vst = create_term(any, try_case, [], {x,1}, Vst2),
+ create_term(any, try_case, [], {x,2}, Vst);
Type ->
error({wrong_tag_type,Type})
end;
valfun_1({get_list,Src,D1,D2}, Vst0) ->
assert_not_literal(Src),
assert_type(cons, Src, Vst0),
- Vst = extract_term(term, get_hd, [Src], D1, Vst0),
- extract_term(term, get_tl, [Src], D2, Vst);
+ Vst = extract_term(any, get_hd, [Src], D1, Vst0),
+ extract_term(any, get_tl, [Src], D2, Vst);
valfun_1({get_hd,Src,Dst}, Vst) ->
assert_not_literal(Src),
assert_type(cons, Src, Vst),
- extract_term(term, get_hd, [Src], Dst, Vst);
+ extract_term(any, get_hd, [Src], Dst, Vst);
valfun_1({get_tl,Src,Dst}, Vst) ->
assert_not_literal(Src),
assert_type(cons, Src, Vst),
- extract_term(term, get_tl, [Src], Dst, Vst);
+ extract_term(any, get_tl, [Src], Dst, Vst);
valfun_1({get_tuple_element,Src,N,Dst}, Vst) ->
+ Index = N+1,
assert_not_literal(Src),
- assert_type({tuple_element,N+1}, Src, Vst),
- Index = {integer,N+1},
- Type = get_element_type(Index, Src, Vst),
- extract_term(Type, {bif,element}, [Index, Src], Dst, Vst);
+ assert_type(#t_tuple{size=Index}, Src, Vst),
+ #t_tuple{elements=Es} = get_term_type(Src, Vst),
+ Type = beam_types:get_element_type(Index, Es),
+ extract_term(Type, {bif,element}, [{integer,Index}, Src], Dst, Vst);
valfun_1({jump,{f,Lbl}}, Vst) ->
branch(Lbl, Vst,
fun(SuccVst) ->
@@ -608,9 +578,9 @@ init_try_catch_branch(Tag, Dst, Fail, Vst0) ->
%% Set the initial state at the try/catch label. Assume that Y registers
%% contain terms or try/catch tags.
init_catch_handler_1(Reg, initialized, Vst) ->
- create_term(term, 'catch_handler', [], Reg, Vst);
+ create_term(any, 'catch_handler', [], Reg, Vst);
init_catch_handler_1(Reg, uninitialized, Vst) ->
- create_term(term, 'catch_handler', [], Reg, Vst);
+ create_term(any, 'catch_handler', [], Reg, Vst);
init_catch_handler_1(_, _, Vst) ->
Vst.
@@ -672,8 +642,16 @@ valfun_4({apply,Live}, Vst) ->
valfun_4({apply_last,Live,_}, Vst) ->
tail_call(apply, Live+2, Vst);
valfun_4({call_fun,Live}, Vst) ->
- validate_src([{x,Live}], Vst),
- call('fun', Live+1, Vst);
+ Fun = {x,Live},
+ assert_term(Fun, Vst),
+
+ %% An exception is raised on error, hence branching to 0.
+ branch(0, Vst,
+ fun(SuccVst0) ->
+ SuccVst = update_type(fun meet/2, #t_fun{arity=Live},
+ Fun, SuccVst0),
+ call('fun', Live+1, SuccVst)
+ end);
valfun_4({call,Live,Func}, Vst) ->
call(Func, Live, Vst);
valfun_4({call_ext,Live,Func}, Vst) ->
@@ -692,53 +670,26 @@ valfun_4({call_ext_last,Live,Func,StkSize},
tail_call(Func, Live, Vst);
valfun_4({call_ext_last,_,_,_}, #vst{current=#st{numy=NumY}}) ->
error({allocated,NumY});
-valfun_4({make_fun2,_,_,_,Live}, Vst) ->
- call(make_fun, Live, Vst);
-%% Other BIFs
-valfun_4({bif,element,{f,Fail},[Pos,Src],Dst}, Vst) ->
- branch(Fail, Vst,
- fun(SuccVst0) ->
- PosType = get_term_type(Pos, SuccVst0),
- TupleType = {tuple,[get_tuple_size(PosType)],#{}},
+valfun_4({make_fun2,{f,Lbl},_,_,NumFree}, #vst{ft=Ft}=Vst0) ->
+ #{ arity := Arity0 } = gb_trees:get(Lbl, Ft),
+ Arity = Arity0 - NumFree,
- SuccVst1 = update_type(fun meet/2, TupleType,
- Src, SuccVst0),
- SuccVst = update_type(fun meet/2, {integer,[]},
- Pos, SuccVst1),
+ true = Arity >= 0, %Assertion.
- ElementType = get_element_type(PosType, Src, SuccVst),
- extract_term(ElementType, {bif,element}, [Pos,Src],
- Dst, SuccVst)
- end);
+ Vst = prune_x_regs(NumFree, Vst0),
+ verify_call_args(make_fun, NumFree, Vst),
+ verify_y_init(Vst),
+
+ create_term(#t_fun{arity=Arity}, make_fun, [], {x,0}, Vst);
+%% Other BIFs
valfun_4({bif,raise,{f,0},Src,_Dst}, Vst) ->
validate_src(Src, Vst),
kill_state(Vst);
valfun_4(raw_raise=I, Vst) ->
call(I, 3, Vst);
-valfun_4({bif,Op,{f,Fail},[Src]=Ss,Dst}, Vst) when Op =:= hd; Op =:= tl ->
- assert_term(Src, Vst),
- branch(Fail, Vst,
- fun(FailVst) ->
- update_type(fun subtract/2, cons, Src, FailVst)
- end,
- fun(SuccVst0) ->
- SuccVst = update_type(fun meet/2, cons, Src, SuccVst0),
- extract_term(term, {bif,Op}, Ss, Dst, SuccVst)
- end);
valfun_4({bif,Op,{f,Fail},Ss,Dst}, Vst) ->
validate_src(Ss, Vst),
- branch(Fail, Vst,
- fun(SuccVst0) ->
- %% Infer argument types. Note that we can't subtract
- %% types as the BIF could fail for reasons other than
- %% bad argument types.
- ArgTypes = bif_arg_types(Op, Ss),
- SuccVst = foldl(fun({Arg, T}, V) ->
- update_type(fun meet/2, T, Arg, V)
- end, SuccVst0, zip(Ss, ArgTypes)),
- Type = bif_return_type(Op, Ss, SuccVst),
- extract_term(Type, {bif,Op}, Ss, Dst, SuccVst)
- end);
+ validate_bif(bif, Op, Fail, Ss, Dst, Vst, Vst);
valfun_4({gc_bif,Op,{f,Fail},Live,Ss,Dst}, #vst{current=St0}=Vst0) ->
validate_src(Ss, Vst0),
verify_live(Live, Vst0),
@@ -749,19 +700,7 @@ valfun_4({gc_bif,Op,{f,Fail},Live,Ss,Dst}, #vst{current=St0}=Vst0) ->
St = kill_heap_allocation(St0),
Vst = prune_x_regs(Live, Vst0#vst{current=St}),
- branch(Fail, Vst,
- fun(SuccVst0) ->
- ArgTypes = bif_arg_types(Op, Ss),
- SuccVst = foldl(fun({Arg, T}, V) ->
- update_type(fun meet/2, T, Arg, V)
- end, SuccVst0, zip(Ss, ArgTypes)),
-
- Type = bif_return_type(Op, Ss, SuccVst),
-
- %% We're passing Vst0 as the original because the
- %% registers were pruned before the branch.
- extract_term(Type, {gc_bif,Op}, Ss, Dst, SuccVst, Vst0)
- end);
+ validate_bif(gc_bif, Op, Fail, Ss, Dst, Vst0, Vst);
valfun_4(return, #vst{current=#st{numy=none}}=Vst) ->
assert_durable_term({x,0}, Vst),
kill_state(Vst);
@@ -773,7 +712,7 @@ valfun_4({loop_rec,{f,Fail},Dst}, Vst) ->
%% part of this term must be stored in a Y register.
branch(Fail, Vst,
fun(SuccVst0) ->
- {Ref, SuccVst} = new_value(term, loop_rec, [], SuccVst0),
+ {Ref, SuccVst} = new_value(any, loop_rec, [], SuccVst0),
mark_fragile(Dst, set_reg_vref(Ref, Dst, SuccVst))
end);
valfun_4({wait,_}, Vst) ->
@@ -793,21 +732,21 @@ valfun_4(send, Vst) ->
valfun_4({set_tuple_element,Src,Tuple,N}, Vst) ->
I = N + 1,
assert_term(Src, Vst),
- assert_type({tuple_element,I}, Tuple, Vst),
+ assert_type(#t_tuple{size=I}, Tuple, Vst),
%% Manually update the tuple type; we can't rely on the ordinary update
%% helpers as we must support overwriting (rather than just widening or
%% narrowing) known elements, and we can't use extract_term either since
%% the source tuple may be aliased.
- {tuple, Sz, Es0} = get_term_type(Tuple, Vst),
- Es = set_element_type({integer,I}, get_term_type(Src, Vst), Es0),
- override_type({tuple, Sz, Es}, Tuple, Vst);
+ #t_tuple{elements=Es0}=Type = get_term_type(Tuple, Vst),
+ Es = beam_types:set_element_type(I, get_term_type(Src, Vst), Es0),
+ override_type(Type#t_tuple{elements=Es}, Tuple, Vst);
%% Match instructions.
valfun_4({select_val,Src,{f,Fail},{list,Choices}}, Vst) ->
assert_term(Src, Vst),
assert_choices(Choices),
validate_select_val(Fail, Choices, Src, Vst);
valfun_4({select_tuple_arity,Tuple,{f,Fail},{list,Choices}}, Vst) ->
- assert_type(tuple, Tuple, Vst),
+ assert_type(#t_tuple{}, Tuple, Vst),
assert_arities(Choices),
validate_select_tuple_arity(Fail, Choices, Tuple, Vst);
@@ -835,18 +774,34 @@ valfun_4({test,bs_skip_utf16,{f,Fail},[Ctx,Live,_]}, Vst) ->
validate_bs_skip_utf(Fail, Ctx, Live, Vst);
valfun_4({test,bs_skip_utf32,{f,Fail},[Ctx,Live,_]}, Vst) ->
validate_bs_skip_utf(Fail, Ctx, Live, Vst);
-valfun_4({test,bs_get_integer2=Op,{f,Fail},Live,[Ctx,_,_,_],Dst}, Vst) ->
- validate_bs_get(Op, Fail, Ctx, Live, {integer, []}, Dst, Vst);
+valfun_4({test,bs_get_integer2=Op,{f,Fail},Live,
+ [Ctx,{integer,Size},Unit,{field_flags,Flags}],Dst},Vst)
+ when Size * Unit =< 64 ->
+ Type = case member(unsigned, Flags) of
+ true ->
+ NumBits = Size * Unit,
+ beam_types:make_integer(0, (1 bsl NumBits)-1);
+ false ->
+ %% Signed integer or way too large, don't bother.
+ #t_integer{}
+ end,
+ validate_bs_get(Op, Fail, Ctx, Live, Type, Dst, Vst);
+valfun_4({test,bs_get_integer2=Op,{f,Fail},Live,
+ [Ctx,_Size,_Unit,_Flags],Dst},Vst) ->
+ validate_bs_get(Op, Fail, Ctx, Live, #t_integer{}, Dst, Vst);
valfun_4({test,bs_get_float2=Op,{f,Fail},Live,[Ctx,_,_,_],Dst}, Vst) ->
- validate_bs_get(Op, Fail, Ctx, Live, {float, []}, Dst, Vst);
-valfun_4({test,bs_get_binary2=Op,{f,Fail},Live,[Ctx,_,_,_],Dst}, Vst) ->
- validate_bs_get(Op, Fail, Ctx, Live, binary, Dst, Vst);
+ validate_bs_get(Op, Fail, Ctx, Live, float, Dst, Vst);
+valfun_4({test,bs_get_binary2=Op,{f,Fail},Live,[Ctx,_,Unit,_],Dst}, Vst) ->
+ validate_bs_get(Op, Fail, Ctx, Live, #t_bitstring{unit=Unit}, Dst, Vst);
valfun_4({test,bs_get_utf8=Op,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
- validate_bs_get(Op, Fail, Ctx, Live, {integer, []}, Dst, Vst);
+ Type = beam_types:make_integer(0, ?UNICODE_MAX),
+ validate_bs_get(Op, Fail, Ctx, Live, Type, Dst, Vst);
valfun_4({test,bs_get_utf16=Op,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
- validate_bs_get(Op, Fail, Ctx, Live, {integer, []}, Dst, Vst);
+ Type = beam_types:make_integer(0, ?UNICODE_MAX),
+ validate_bs_get(Op, Fail, Ctx, Live, Type, Dst, Vst);
valfun_4({test,bs_get_utf32=Op,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
- validate_bs_get(Op, Fail, Ctx, Live, {integer, []}, Dst, Vst);
+ Type = beam_types:make_integer(0, ?UNICODE_MAX),
+ validate_bs_get(Op, Fail, Ctx, Live, Type, Dst, Vst);
valfun_4({bs_save2,Ctx,SavePoint}, Vst) ->
bsm_save(Ctx, SavePoint, Vst);
valfun_4({bs_restore2,Ctx,SavePoint}, Vst) ->
@@ -856,31 +811,32 @@ valfun_4({bs_get_position, Ctx, Dst, Live}, Vst0) ->
verify_live(Live, Vst0),
verify_y_init(Vst0),
Vst = prune_x_regs(Live, Vst0),
- create_term(ms_position, bs_get_position, [Ctx], Dst, Vst, Vst0);
+ create_term(#t_abstract{kind=ms_position}, bs_get_position, [Ctx],
+ Dst, Vst, Vst0);
valfun_4({bs_set_position, Ctx, Pos}, Vst) ->
bsm_validate_context(Ctx, Vst),
- assert_type(ms_position, Pos, Vst),
+ assert_type(#t_abstract{kind=ms_position}, Pos, Vst),
Vst;
%% Other test instructions.
valfun_4({test,has_map_fields,{f,Lbl},Src,{list,List}}, Vst) ->
- assert_type(map, Src, Vst),
+ assert_type(#t_map{}, Src, Vst),
assert_unique_map_keys(List),
branch(Lbl, Vst, fun(V) -> V end);
valfun_4({test,is_atom,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, {atom,[]}, Src, Vst);
+ type_test(Lbl, #t_atom{}, Src, Vst);
valfun_4({test,is_binary,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, binary, Src, Vst);
+ type_test(Lbl, #t_bitstring{unit=8}, Src, Vst);
valfun_4({test,is_bitstr,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, binary, Src, Vst);
+ type_test(Lbl, #t_bitstring{}, Src, Vst);
valfun_4({test,is_boolean,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, bool, Src, Vst);
+ type_test(Lbl, beam_types:make_boolean(), Src, Vst);
valfun_4({test,is_float,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, {float,[]}, Src, Vst);
+ type_test(Lbl, float, Src, Vst);
valfun_4({test,is_tuple,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, {tuple,[0],#{}}, Src, Vst);
+ type_test(Lbl, #t_tuple{}, Src, Vst);
valfun_4({test,is_integer,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, {integer,[]}, Src, Vst);
+ type_test(Lbl, #t_integer{}, Src, Vst);
valfun_4({test,is_nonempty_list,{f,Lbl},[Src]}, Vst) ->
type_test(Lbl, cons, Src, Vst);
valfun_4({test,is_number,{f,Lbl},[Src]}, Vst) ->
@@ -888,7 +844,7 @@ valfun_4({test,is_number,{f,Lbl},[Src]}, Vst) ->
valfun_4({test,is_list,{f,Lbl},[Src]}, Vst) ->
type_test(Lbl, list, Src, Vst);
valfun_4({test,is_map,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, map, Src, Vst);
+ type_test(Lbl, #t_map{}, Src, Vst);
valfun_4({test,is_nil,{f,Lbl},[Src]}, Vst) ->
%% is_nil is an exact check against the 'nil' value, and should not be
%% treated as a simple type test.
@@ -901,12 +857,13 @@ valfun_4({test,is_nil,{f,Lbl},[Src]}, Vst) ->
update_eq_types(Src, nil, SuccVst)
end);
valfun_4({test,test_arity,{f,Lbl},[Tuple,Sz]}, Vst) when is_integer(Sz) ->
- assert_type(tuple, Tuple, Vst),
- Type = {tuple, Sz, #{}},
+ assert_type(#t_tuple{}, Tuple, Vst),
+ Type = #t_tuple{exact=true,size=Sz},
type_test(Lbl, Type, Tuple, Vst);
valfun_4({test,is_tagged_tuple,{f,Lbl},[Src,Sz,Atom]}, Vst) ->
assert_term(Src, Vst),
- Type = {tuple, Sz, #{ {integer,1} => Atom }},
+ Es = #{ 1 => get_literal_type(Atom) },
+ Type = #t_tuple{exact=true,size=Sz,elements=Es},
type_test(Lbl, Type, Src, Vst);
valfun_4({test,is_eq_exact,{f,Lbl},[Src,Val]=Ss}, Vst) ->
validate_src(Ss, Vst),
@@ -935,19 +892,19 @@ valfun_4({bs_add,{f,Fail},[A,B,_],Dst}, Vst) ->
assert_term(B, Vst),
branch(Fail, Vst,
fun(SuccVst) ->
- create_term({integer,[]}, bs_add, [A, B], Dst, SuccVst)
+ create_term(#t_integer{}, bs_add, [A, B], Dst, SuccVst)
end);
valfun_4({bs_utf8_size,{f,Fail},A,Dst}, Vst) ->
assert_term(A, Vst),
branch(Fail, Vst,
fun(SuccVst) ->
- create_term({integer,[]}, bs_utf8_size, [A], Dst, SuccVst)
+ create_term(#t_integer{}, bs_utf8_size, [A], Dst, SuccVst)
end);
valfun_4({bs_utf16_size,{f,Fail},A,Dst}, Vst) ->
assert_term(A, Vst),
branch(Fail, Vst,
fun(SuccVst) ->
- create_term({integer,[]}, bs_utf16_size, [A], Dst, SuccVst)
+ create_term(#t_integer{}, bs_utf16_size, [A], Dst, SuccVst)
end);
valfun_4({bs_init2,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) ->
verify_live(Live, Vst0),
@@ -962,7 +919,8 @@ valfun_4({bs_init2,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) ->
branch(Fail, Vst,
fun(SuccVst0) ->
SuccVst = prune_x_regs(Live, SuccVst0),
- create_term(binary, bs_init2, [], Dst, SuccVst, SuccVst0)
+ create_term(#t_bitstring{unit=8}, bs_init2, [], Dst,
+ SuccVst, SuccVst0)
end);
valfun_4({bs_init_bits,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) ->
verify_live(Live, Vst0),
@@ -977,9 +935,9 @@ valfun_4({bs_init_bits,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) ->
branch(Fail, Vst,
fun(SuccVst0) ->
SuccVst = prune_x_regs(Live, SuccVst0),
- create_term(binary, bs_init_bits, [], Dst, SuccVst)
+ create_term(#t_bitstring{}, bs_init_bits, [], Dst, SuccVst)
end);
-valfun_4({bs_append,{f,Fail},Bits,Heap,Live,_Unit,Bin,_Flags,Dst}, Vst0) ->
+valfun_4({bs_append,{f,Fail},Bits,Heap,Live,Unit,Bin,_Flags,Dst}, Vst0) ->
verify_live(Live, Vst0),
verify_y_init(Vst0),
assert_term(Bits, Vst0),
@@ -988,14 +946,16 @@ valfun_4({bs_append,{f,Fail},Bits,Heap,Live,_Unit,Bin,_Flags,Dst}, Vst0) ->
branch(Fail, Vst,
fun(SuccVst0) ->
SuccVst = prune_x_regs(Live, SuccVst0),
- create_term(binary, bs_append, [Bin], Dst, SuccVst, SuccVst0)
+ create_term(#t_bitstring{unit=Unit}, bs_append,
+ [Bin], Dst, SuccVst, SuccVst0)
end);
-valfun_4({bs_private_append,{f,Fail},Bits,_Unit,Bin,_Flags,Dst}, Vst) ->
+valfun_4({bs_private_append,{f,Fail},Bits,Unit,Bin,_Flags,Dst}, Vst) ->
assert_term(Bits, Vst),
assert_term(Bin, Vst),
branch(Fail, Vst,
fun(SuccVst) ->
- create_term(binary, bs_private_append, [Bin], Dst, SuccVst)
+ create_term(#t_bitstring{unit=Unit}, bs_private_append,
+ [Bin], Dst, SuccVst)
end);
valfun_4({bs_put_string,Sz,_}, Vst) when is_integer(Sz) ->
Vst;
@@ -1004,39 +964,39 @@ valfun_4({bs_put_binary,{f,Fail},Sz,_,_,Src}, Vst) ->
assert_term(Src, Vst),
branch(Fail, Vst,
fun(SuccVst) ->
- update_type(fun meet/2, binary, Src, SuccVst)
+ update_type(fun meet/2, #t_bitstring{}, Src, SuccVst)
end);
valfun_4({bs_put_float,{f,Fail},Sz,_,_,Src}, Vst) ->
assert_term(Sz, Vst),
assert_term(Src, Vst),
branch(Fail, Vst,
fun(SuccVst) ->
- update_type(fun meet/2, {float,[]}, Src, SuccVst)
+ update_type(fun meet/2, float, Src, SuccVst)
end);
valfun_4({bs_put_integer,{f,Fail},Sz,_,_,Src}, Vst) ->
assert_term(Sz, Vst),
assert_term(Src, Vst),
branch(Fail, Vst,
fun(SuccVst) ->
- update_type(fun meet/2, {integer,[]}, Src, SuccVst)
+ update_type(fun meet/2, #t_integer{}, Src, SuccVst)
end);
valfun_4({bs_put_utf8,{f,Fail},_,Src}, Vst) ->
assert_term(Src, Vst),
branch(Fail, Vst,
fun(SuccVst) ->
- update_type(fun meet/2, {integer,[]}, Src, SuccVst)
+ update_type(fun meet/2, #t_integer{}, Src, SuccVst)
end);
valfun_4({bs_put_utf16,{f,Fail},_,Src}, Vst) ->
assert_term(Src, Vst),
branch(Fail, Vst,
fun(SuccVst) ->
- update_type(fun meet/2, {integer,[]}, Src, SuccVst)
+ update_type(fun meet/2, #t_integer{}, Src, SuccVst)
end);
valfun_4({bs_put_utf32,{f,Fail},_,Src}, Vst) ->
assert_term(Src, Vst),
branch(Fail, Vst,
fun(SuccVst) ->
- update_type(fun meet/2, {integer,[]}, Src, SuccVst)
+ update_type(fun meet/2, #t_integer{}, Src, SuccVst)
end);
%% Map instructions.
valfun_4({put_map_assoc=Op,{f,Fail},Src,Dst,Live,{list,List}}, Vst) ->
@@ -1050,7 +1010,7 @@ valfun_4(_, _) ->
verify_get_map(Fail, Src, List, Vst0) ->
assert_not_literal(Src), %OTP 22.
- assert_type(map, Src, Vst0),
+ assert_type(#t_map{}, Src, Vst0),
branch(Fail, Vst0,
fun(FailVst) ->
@@ -1071,7 +1031,7 @@ verify_get_map(Fail, Src, List, Vst0) ->
clobber_map_vals([Key,Dst|T], Map, Vst0) ->
case is_reg_defined(Dst, Vst0) of
true ->
- Vst = extract_term(term, {bif,map_get}, [Key, Map], Dst, Vst0),
+ Vst = extract_term(any, {bif,map_get}, [Key, Map], Dst, Vst0),
clobber_map_vals(T, Map, Vst);
false ->
clobber_map_vals(T, Map, Vst0)
@@ -1085,13 +1045,13 @@ extract_map_keys([]) -> [].
extract_map_vals([Key,Dst|Vs], Map, Vst0, Vsti0) ->
assert_term(Key, Vst0),
- Vsti = extract_term(term, {bif,map_get}, [Key, Map], Dst, Vsti0),
+ Vsti = extract_term(any, {bif,map_get}, [Key, Map], Dst, Vsti0),
extract_map_vals(Vs, Map, Vst0, Vsti);
extract_map_vals([], _Map, _Vst0, Vst) ->
Vst.
verify_put_map(Op, Fail, Src, Dst, Live, List, Vst0) ->
- assert_type(map, Src, Vst0),
+ assert_type(#t_map{}, Src, Vst0),
verify_live(Live, Vst0),
verify_y_init(Vst0),
_ = [assert_term(Term, Vst0) || Term <- List],
@@ -1102,10 +1062,40 @@ verify_put_map(Op, Fail, Src, Dst, Live, List, Vst0) ->
SuccVst = prune_x_regs(Live, SuccVst0),
Keys = extract_map_keys(List),
assert_unique_map_keys(Keys),
- create_term(map, Op, [Src], Dst, SuccVst, SuccVst0)
+ create_term(#t_map{}, Op, [Src], Dst, SuccVst, SuccVst0)
end).
%%
+%% Common code for validating BIFs.
+%%
+%% OrigVst is the state we entered the instruction with, which is needed for
+%% gc_bifs as X registers are pruned prior to calling this function, which may
+%% have clobbered the sources.
+%%
+validate_bif(Kind, Op, Fail, Ss, Dst, OrigVst, Vst) ->
+ {Type, ArgTypes, CanSubtract} = bif_types(Op, Ss, Vst),
+ ZippedArgs = zip(Ss, ArgTypes),
+
+ FailFun = case CanSubtract of
+ true ->
+ fun(FailVst0) ->
+ foldl(fun({A, T}, V) ->
+ update_type(fun subtract/2, T, A, V)
+ end, FailVst0, ZippedArgs)
+ end;
+ false ->
+ fun(S) -> S end
+ end,
+ SuccFun = fun(SuccVst0) ->
+ SuccVst = foldl(fun({A, T}, V) ->
+ update_type(fun meet/2, T, A, V)
+ end, SuccVst0, ZippedArgs),
+ extract_term(Type, {Kind,Op}, Ss, Dst, SuccVst, OrigVst)
+ end,
+
+ branch(Fail, Vst, FailFun, SuccFun).
+
+%%
%% Common code for validating bs_start_match* instructions.
%%
@@ -1113,18 +1103,18 @@ validate_bs_start_match(Fail, Live, Type, Src, Dst, Vst) ->
verify_live(Live, Vst),
verify_y_init(Vst),
- %% #ms{} can represent either a match context or a term, so we have to mark
- %% the source as a term if it fails with a match context as an input. This
- %% hack is only needed until we get proper union types.
+ %% #t_bs_context{} can represent either a match context or a term, so we
+ %% have to mark the source as a term if it fails with a match context as an
+ %% input. This hack is only needed until we get proper union types.
branch(Fail, Vst,
fun(FailVst) ->
case get_movable_term_type(Src, FailVst) of
- #ms{} -> override_type(term, Src, FailVst);
+ #t_bs_context{} -> override_type(any, Src, FailVst);
_ -> FailVst
end
end,
fun(SuccVst0) ->
- SuccVst1 = update_type(fun meet/2, binary,
+ SuccVst1 = update_type(fun meet/2, #t_bitstring{},
Src, SuccVst0),
SuccVst = prune_x_regs(Live, SuccVst1),
extract_term(Type, bs_start_match, [Src], Dst,
@@ -1203,12 +1193,12 @@ kill_state(Vst) ->
call(Name, Live, #vst{current=St0}=Vst0) ->
verify_call_args(Name, Live, Vst0),
verify_y_init(Vst0),
- case call_return_type(Name, Vst0) of
- Type when Type =/= exception ->
- %% Type is never 'exception' because it has been handled earlier.
+ case call_types(Name, Live, Vst0) of
+ {RetType, _, _} ->
+ %% Type is never 'none' because it has been handled earlier.
St = St0#st{f=init_fregs()},
Vst = prune_x_regs(0, Vst0#vst{current=St}),
- create_term(Type, call, [], {x,0}, Vst)
+ create_term(RetType, call, [], {x,0}, Vst)
end.
%% Tail call.
@@ -1223,8 +1213,15 @@ tail_call(Name, Live, Vst0) ->
verify_call_args(_, 0, #vst{}) ->
ok;
-verify_call_args({f,Lbl}, Live, Vst) when is_integer(Live)->
- verify_local_args(Live - 1, Lbl, #{}, Vst);
+verify_call_args({f,Lbl}, Live, #vst{ft=Ft}=Vst) when is_integer(Live) ->
+ case gb_trees:lookup(Lbl, Ft) of
+ {value, FuncInfo} ->
+ #{ arity := Live,
+ parameter_types := ParamTypes } = FuncInfo,
+ verify_local_args(Live - 1, ParamTypes, #{}, Vst);
+ none ->
+ error(local_call_to_unknown_function)
+ end;
verify_call_args(_, Live, Vst) when is_integer(Live)->
verify_remote_args_1(Live - 1, Vst);
verify_call_args(_, Live, _) ->
@@ -1236,87 +1233,50 @@ verify_remote_args_1(X, Vst) ->
assert_durable_term({x, X}, Vst),
verify_remote_args_1(X - 1, Vst).
-verify_local_args(-1, _Lbl, _CtxIds, _Vst) ->
+verify_local_args(-1, _ParamTypes, _CtxIds, _Vst) ->
ok;
-verify_local_args(X, Lbl, CtxIds, Vst) ->
+verify_local_args(X, ParamTypes, CtxRefs, Vst) ->
Reg = {x, X},
assert_not_fragile(Reg, Vst),
case get_movable_term_type(Reg, Vst) of
- #ms{id=Id}=Type ->
- case CtxIds of
- #{ Id := Other } ->
+ #t_bs_context{}=Type ->
+ VRef = get_reg_vref(Reg, Vst),
+ case CtxRefs of
+ #{ VRef := Other } ->
error({multiple_match_contexts, [Reg, Other]});
#{} ->
- verify_arg_type(Lbl, Reg, Type, Vst),
- verify_local_args(X - 1, Lbl, CtxIds#{ Id => Reg }, Vst)
+ verify_arg_type(Reg, Type, ParamTypes),
+ verify_local_args(X - 1, ParamTypes,
+ CtxRefs#{ VRef => Reg }, Vst)
end;
Type ->
- verify_arg_type(Lbl, Reg, Type, Vst),
- verify_local_args(X - 1, Lbl, CtxIds, Vst)
+ verify_arg_type(Reg, Type, ParamTypes),
+ verify_local_args(X - 1, ParamTypes, CtxRefs, Vst)
end.
%% Verifies that the given argument narrows to what the function expects.
-verify_arg_type(Lbl, Reg, #ms{}, #vst{ft=Ft}) ->
+verify_arg_type(Reg, #t_bs_context{}, ParamTypes) ->
%% Match contexts require explicit support, and may not be passed to a
%% function that accepts arbitrary terms.
- case gb_trees:lookup({Lbl, Reg}, Ft) of
- {value, #ms{}} -> ok;
- _ -> error(no_bs_start_match2)
+ case ParamTypes of
+ #{ Reg := #t_bs_context{}} -> ok;
+ #{} -> error(no_bs_start_match2)
end;
-verify_arg_type(Lbl, Reg, GivenType, #vst{ft=Ft}) ->
- case gb_trees:lookup({Lbl, Reg}, Ft) of
- {value, #ms{}} ->
+verify_arg_type(Reg, GivenType, ParamTypes) ->
+ case ParamTypes of
+ #{ Reg := #t_bs_context{}} ->
%% Functions that accept match contexts also accept all other
%% terms. This will change once we support union types.
ok;
- {value, RequiredType} ->
- case vat_1(GivenType, RequiredType) of
- true -> ok;
- false -> error({bad_arg_type, Reg, GivenType, RequiredType})
+ #{ Reg := RequiredType } ->
+ case meet(GivenType, RequiredType) of
+ GivenType -> ok;
+ _ -> error({bad_arg_type, Reg, GivenType, RequiredType})
end;
- none ->
+ #{} ->
ok
end.
-%% Checks whether the Given argument is compatible with the Required one. This
-%% is essentially a relaxed version of 'meet(Given, Req) =:= Given', where we
-%% accept that the Given value has the right type but not necessarily the exact
-%% same value; if {atom,gurka} is required, we'll consider {atom,[]} valid.
-%%
-%% This will catch all problems that could crash the emulator, like passing a
-%% 1-tuple when the callee expects a 3-tuple, but some value errors might slip
-%% through.
-vat_1(Same, Same) -> true;
-vat_1({atom,A}, {atom,B}) -> A =:= B orelse is_list(A) orelse is_list(B);
-vat_1({atom,A}, bool) -> is_boolean(A) orelse is_list(A);
-vat_1(bool, {atom,B}) -> is_boolean(B) orelse is_list(B);
-vat_1(cons, list) -> true;
-vat_1({float,A}, {float,B}) -> A =:= B orelse is_list(A) orelse is_list(B);
-vat_1({float,_}, number) -> true;
-vat_1({integer,A}, {integer,B}) -> A =:= B orelse is_list(A) orelse is_list(B);
-vat_1({integer,_}, number) -> true;
-vat_1(_, {literal,_}) -> false;
-vat_1({literal,_}=Lit, Required) -> vat_1(get_literal_type(Lit), Required);
-vat_1(nil, list) -> true;
-vat_1({tuple,SzA,EsA}, {tuple,SzB,EsB}) ->
- if
- is_list(SzB) ->
- tuple_sz(SzA) >= tuple_sz(SzB) andalso vat_elements(EsA, EsB);
- SzA =:= SzB ->
- vat_elements(EsA, EsB);
- SzA =/= SzB ->
- false
- end;
-vat_1(_, _) -> false.
-
-vat_elements(EsA, EsB) ->
- maps:fold(fun(Key, Req, Acc) ->
- case EsA of
- #{ Key := Given } -> Acc andalso vat_1(Given, Req);
- #{} -> false
- end
- end, true, EsB).
-
allocate(Tag, Stk, Heap, Live, #vst{current=#st{numy=none}=St}=Vst0) ->
verify_live(Live, Vst0),
Vst1 = Vst0#vst{current=St#st{numy=Stk}},
@@ -1498,9 +1458,9 @@ assert_unique_map_keys([_,_|_]=Ls) ->
%%%
bsm_match_state() ->
- #ms{}.
+ #t_bs_context{}.
bsm_match_state(Slots) ->
- #ms{slots=Slots}.
+ #t_bs_context{slots=Slots}.
bsm_validate_context(Reg, Vst) ->
_ = bsm_get_context(Reg, Vst),
@@ -1508,7 +1468,7 @@ bsm_validate_context(Reg, Vst) ->
bsm_get_context({Kind,_}=Reg, Vst) when Kind =:= x; Kind =:= y->
case get_movable_term_type(Reg, Vst) of
- #ms{}=Ctx -> Ctx;
+ #t_bs_context{}=Ctx -> Ctx;
_ -> error({no_bsm_context,Reg})
end;
bsm_get_context(Reg, _) ->
@@ -1521,8 +1481,8 @@ bsm_save(Reg, {atom,start}, Vst) ->
Vst;
bsm_save(Reg, SavePoint, Vst) ->
case bsm_get_context(Reg, Vst) of
- #ms{valid=Bits,slots=Slots}=Ctxt0 when SavePoint < Slots ->
- Ctx = Ctxt0#ms{valid=Bits bor (1 bsl SavePoint),slots=Slots},
+ #t_bs_context{valid=Bits,slots=Slots}=Ctxt0 when SavePoint < Slots ->
+ Ctx = Ctxt0#t_bs_context{valid=Bits bor (1 bsl SavePoint),slots=Slots},
override_type(Ctx, Reg, Vst);
_ -> error({illegal_save,SavePoint})
end.
@@ -1534,7 +1494,7 @@ bsm_restore(Reg, {atom,start}, Vst) ->
Vst;
bsm_restore(Reg, SavePoint, Vst) ->
case bsm_get_context(Reg, Vst) of
- #ms{valid=Bits,slots=Slots} when SavePoint < Slots ->
+ #t_bs_context{valid=Bits,slots=Slots} when SavePoint < Slots ->
case Bits band (1 bsl SavePoint) of
0 -> error({illegal_restore,SavePoint,not_set});
_ -> Vst
@@ -1567,7 +1527,7 @@ validate_select_tuple_arity(_Fail, _Choices, _Src, #vst{current=none}=Vst) ->
%% can't reach the fail label or any of the remaining choices.
Vst;
validate_select_tuple_arity(Fail, [Arity,{f,L}|T], Tuple, Vst0) ->
- Type = {tuple, Arity, #{}},
+ Type = #t_tuple{exact=true,size=Arity},
Vst = branch(L, Vst0,
fun(BranchVst) ->
update_type(fun meet/2, Type, Tuple, BranchVst)
@@ -1602,41 +1562,46 @@ infer_types_1(#value{op={bif,'=:='},args=[LHS,RHS]}) ->
Infer_R(RHS, Infer_L(LHS, S));
(_, S) -> S
end;
-infer_types_1(#value{op={bif,element},args=[{integer,Index}=Key,Tuple]}) ->
+infer_types_1(#value{op={bif,element},args=[{integer,Index},Tuple]}) ->
fun(Val, S) ->
case is_value_alive(Tuple, S) of
true ->
- Type = {tuple,[Index], #{ Key => get_term_type(Val, S) }},
+ ElementType = get_term_type(Val, S),
+ Es = beam_types:set_element_type(Index, ElementType, #{}),
+ Type = #t_tuple{size=Index,elements=Es},
update_type(fun meet/2, Type, Tuple, S);
false ->
S
end
end;
infer_types_1(#value{op={bif,is_atom},args=[Src]}) ->
- infer_type_test_bif({atom,[]}, Src);
+ infer_type_test_bif(#t_atom{}, Src);
infer_types_1(#value{op={bif,is_boolean},args=[Src]}) ->
- infer_type_test_bif(bool, Src);
+ infer_type_test_bif(beam_types:make_boolean(), Src);
infer_types_1(#value{op={bif,is_binary},args=[Src]}) ->
- infer_type_test_bif(binary, Src);
+ infer_type_test_bif(#t_bitstring{unit=8}, Src);
infer_types_1(#value{op={bif,is_bitstring},args=[Src]}) ->
- infer_type_test_bif(binary, Src);
+ infer_type_test_bif(#t_bitstring{}, Src);
infer_types_1(#value{op={bif,is_float},args=[Src]}) ->
infer_type_test_bif(float, Src);
infer_types_1(#value{op={bif,is_integer},args=[Src]}) ->
- infer_type_test_bif({integer,{}}, Src);
+ infer_type_test_bif(#t_integer{}, Src);
infer_types_1(#value{op={bif,is_list},args=[Src]}) ->
infer_type_test_bif(list, Src);
infer_types_1(#value{op={bif,is_map},args=[Src]}) ->
- infer_type_test_bif(map, Src);
+ infer_type_test_bif(#t_map{}, Src);
infer_types_1(#value{op={bif,is_number},args=[Src]}) ->
infer_type_test_bif(number, Src);
infer_types_1(#value{op={bif,is_tuple},args=[Src]}) ->
- infer_type_test_bif({tuple,[0],#{}}, Src);
+ infer_type_test_bif(#t_tuple{}, Src);
infer_types_1(#value{op={bif,tuple_size}, args=[Tuple]}) ->
fun({integer,Arity}, S) ->
case is_value_alive(Tuple, S) of
- true -> update_type(fun meet/2, {tuple,Arity,#{}}, Tuple, S);
- false -> S
+ true ->
+ Type = #t_tuple{exact=true,size=Arity},
+ update_type(fun meet/2, Type, Tuple, S);
+ false ->
+ S
end;
(_, S) -> S
end;
@@ -1644,10 +1609,14 @@ infer_types_1(_) ->
fun(_, S) -> S end.
infer_type_test_bif(Type, Src) ->
- fun({atom,true}, S) ->
+ fun({atom,Bool}, S) when is_boolean(Bool) ->
case is_value_alive(Src, S) of
- true -> update_type(fun meet/2, Type, Src, S);
- false -> S
+ true when Bool =:= true ->
+ update_type(fun meet/2, Type, Src, S);
+ true when Bool =:= false ->
+ update_type(fun subtract/2, Type, Src, S);
+ false ->
+ S
end;
(_, S) ->
S
@@ -1761,26 +1730,52 @@ update_type(Merge, With, #value_ref{}=Ref, Vst) ->
update_type(Merge, With, {Kind,_}=Reg, Vst) when Kind =:= x; Kind =:= y ->
update_type(Merge, With, get_reg_vref(Reg, Vst), Vst);
update_type(Merge, With, Literal, Vst) ->
- assert_literal(Literal),
%% Literals always retain their type, but we still need to bail on type
%% conflicts.
- case Merge(Literal, With) of
- none -> throw({type_conflict, Literal, With});
+ Type = get_literal_type(Literal),
+ case Merge(Type, With) of
+ none -> throw({type_conflict, Type, With});
_Type -> Vst
end.
+update_ne_types(LHS, {atom,Bool}=RHS, Vst) when is_boolean(Bool) ->
+ %% This is a stopgap to make negative inference work for type test BIFs
+ %% like is_tuple. Consider the following unoptimized code:
+ %%
+ %% {call_ext,2,{extfunc,erlang,'--',2}}.
+ %% {bif,is_tuple,{f,0},[{x,0}],{x,1}}.
+ %% {test,is_eq_exact,{x,1},{f,2},{atom,false}}.
+ %% ... snip ...
+ %% {label,1}.
+ %% {test,is_eq_exact,{x,1},{f,1},{atom,true}}.
+ %% ... unreachable because {x,0} is known to be a list, so {x,1} can't
+ %% be true ...
+ %% {label,2}.
+ %% ... unreachable because {x,1} is neither true nor false! ...
+ %%
+ %% If we fail to determine that the first is_eq_exact never fails, our
+ %% state will be inconsistent after the second is_eq_exact check; we know
+ %% for certain that {x,0} is a list so infer_types says it can't succeed,
+ %% but it can't fail either because we also know that {x,1} is a boolean,
+ %% and the first check ruled out 'false'.
+ LType = get_term_type(LHS, Vst),
+ RType = get_term_type(RHS, Vst),
+ case beam_types:is_boolean_type(LType) of
+ true -> update_eq_types(LHS, {atom, not Bool}, Vst);
+ false -> update_type(fun subtract/2, RType, LHS, Vst)
+ end;
update_ne_types(LHS, RHS, Vst) ->
%% While updating types on equality is fairly straightforward, inequality
%% is a bit trickier since all we know is that the *value* of LHS differs
%% from RHS, so we can't blindly subtract their types.
%%
- %% Consider `number =/= {integer,[]}`; all we know is that LHS isn't equal
+ %% Consider `number =/= #t_integer{}`; all we know is that LHS isn't equal
%% to some *specific integer* of unknown value, and if we were to subtract
- %% {integer,[]} we would erroneously infer that the new type is {float,[]}.
+ %% #t_integer{} we would erroneously infer that the new type is float.
%%
%% Therefore, we only subtract when we know that RHS has a specific value.
RType = get_term_type(RHS, Vst),
- case is_literal(RType) of
+ case beam_types:is_singleton_type(RType) of
true -> update_type(fun subtract/2, RType, LHS, Vst);
false -> Vst
end.
@@ -1848,16 +1843,9 @@ get_reg_vref({y,_}=Src, #vst{current=#st{ys=Ys}}) ->
end.
set_type(Type, #value_ref{}=Ref, #vst{current=#st{vs=Vs0}=St}=Vst) ->
- case Vs0 of
- #{ Ref := #value{}=Entry } ->
- Vs = Vs0#{ Ref => Entry#value{type=Type} },
- Vst#vst{current=St#st{vs=Vs}};
- #{} ->
- %% Dead references may happen during type inference and are not an
- %% error in and of themselves. If a problem were to arise from this
- %% it'll explode elsewhere.
- Vst
- end.
+ #{ Ref := #value{}=Entry } = Vs0,
+ Vs = Vs0#{ Ref => Entry#value{type=Type} },
+ Vst#vst{current=St#st{vs=Vs}}.
new_value(Type, Op, Ss, #vst{current=#st{vs=Vs0}=St,ref_ctr=Counter}=Vst) ->
Ref = #value_ref{id=Counter},
@@ -1920,304 +1908,41 @@ is_literal(_) -> false.
%%
%% First non-term types:
%%
-%% initialized Only for Y registers. Means that the Y register
-%% has been initialized with some valid term so that
-%% it is safe to pass to the garbage collector.
-%% NOT safe to use in any other way (will not crash the
-%% emulator, but clearly points to a bug in the compiler).
-%%
-%% {catchtag,[Lbl]} A special term used within a catch. Must only be used
-%% by the catch instructions; NOT safe to use in other
-%% instructions.
-%%
-%% {trytag,[Lbl]} A special term used within a try block. Must only be
-%% used by the catch instructions; NOT safe to use in other
-%% instructions.
-%%
-%% exception Can only be used as a type returned by
-%% call_return_type/2 (which gives the type of the value
-%% returned by a call). Thus 'exception' is never stored
-%% as type descriptor for a register.
-%%
-%% #ms{} A match context for bit syntax matching. We do allow
-%% it to moved/to from stack, but otherwise it must only
-%% be accessed by bit syntax matching instructions.
-%%
-%%
-%% Normal terms:
-%%
-%% term Any valid Erlang (but not of the special types above).
-%%
-%% binary Binary or bitstring.
-%%
-%% bool The atom 'true' or the atom 'false'.
-%%
-%% cons Cons cell: [_|_]
-%%
-%% nil Empty list: []
-%%
-%% list List: [] or [_|_]
+%% initialized Only for Y registers. Means that the Y register
+%% has been initialized with some valid term so that
+%% it is safe to pass to the garbage collector.
+%% NOT safe to use in any other way (will not crash the
+%% emulator, but clearly points to a bug in the compiler).
%%
-%% {tuple,[Sz],Es} Tuple. An element has been accessed using
-%% element/2 or setelement/3 so that it is known that
-%% the type is a tuple of size at least Sz. Es is a map
-%% containing known types by tuple index.
+%% {catchtag,[Lbl]} A special term used within a catch. Must only be used
+%% by the catch instructions; NOT safe to use in other
+%% instructions.
%%
-%% {tuple,Sz,Es} Tuple. A test_arity instruction has been seen
-%% so that it is known that the size is exactly Sz.
+%% {trytag,[Lbl]} A special term used within a try block. Must only be
+%% used by the catch instructions; NOT safe to use in other
+%% instructions.
%%
-%% {atom,[]} Atom.
-%% {atom,Atom}
+%% #t_bs_context{} A match context for bit syntax matching. We do allow
+%% it to moved/to from stack, but otherwise it must only
+%% be accessed by bit syntax matching instructions.
%%
-%% {integer,[]} Integer.
-%% {integer,Integer}
-%%
-%% {float,[]} Float.
-%% {float,Float}
-%%
-%% number Integer or Float of unknown value
-%%
-%% map Map.
-%%
-%% none A conflict in types. There will be an exception at runtime.
-%%
-
-%% join(Type1, Type2) -> Type
-%% Return the most specific type possible.
-join(Same, Same) ->
- Same;
-join(none, Other) ->
- Other;
-join(Other, none) ->
- Other;
-join({literal,_}=T1, T2) ->
- join_literal(T1, T2);
-join(T1, {literal,_}=T2) ->
- join_literal(T2, T1);
-join({tuple,Size,EsA}, {tuple,Size,EsB}) ->
- Es = join_tuple_elements(tuple_sz(Size), EsA, EsB),
- {tuple, Size, Es};
-join({tuple,A,EsA}, {tuple,B,EsB}) ->
- Size = min(tuple_sz(A), tuple_sz(B)),
- Es = join_tuple_elements(Size, EsA, EsB),
- {tuple, [Size], Es};
-join({Type,A}, {Type,B})
- when Type =:= atom; Type =:= integer; Type =:= float ->
- if A =:= B -> {Type,A};
- true -> {Type,[]}
- end;
-join({Type,_}, number)
- when Type =:= integer; Type =:= float ->
- number;
-join(number, {Type,_})
- when Type =:= integer; Type =:= float ->
- number;
-join({integer,_}, {float,_}) ->
- number;
-join({float,_}, {integer,_}) ->
- number;
-join(bool, {atom,A}) ->
- join_bool(A);
-join({atom,A}, bool) ->
- join_bool(A);
-join({atom,A}, {atom,B}) when is_boolean(A), is_boolean(B) ->
- bool;
-join({atom,_}, {atom,_}) ->
- {atom,[]};
-join(#ms{id=Id1,valid=B1,slots=Slots1},
- #ms{id=Id2,valid=B2,slots=Slots2}) ->
- Id = if
- Id1 =:= Id2 -> Id1;
- true -> make_ref()
- end,
- #ms{id=Id,valid=B1 band B2,slots=min(Slots1, Slots2)};
-join(T1, T2) when T1 =/= T2 ->
- %% We've exhaused all other options, so the type must either be a list or
- %% a 'term'.
- join_list(T1, T2).
+%% These are simple wrappers around
-join_tuple_elements(Limit, EsA, EsB) ->
- Es0 = join_elements(EsA, EsB),
- maps:filter(fun({integer,Index}, _Type) -> Index =< Limit end, Es0).
+join(#t_abstract{}=Same, #t_abstract{}=Same) -> Same;
+join(A, B) -> beam_types:join(A, B).
-join_elements(Es1, Es2) ->
- Keys = if
- map_size(Es1) =< map_size(Es2) -> maps:keys(Es1);
- map_size(Es1) > map_size(Es2) -> maps:keys(Es2)
- end,
- join_elements_1(Keys, Es1, Es2, #{}).
+meet(#t_abstract{}=Same, #t_abstract{}=Same) -> Same;
+meet(A, B) -> beam_types:meet(A, B).
-join_elements_1([Key | Keys], Es1, Es2, Acc0) ->
- Type = case {Es1, Es2} of
- {#{ Key := Same }, #{ Key := Same }} -> Same;
- {#{ Key := Type1 }, #{ Key := Type2 }} -> join(Type1, Type2);
- {#{}, #{}} -> term
- end,
- Acc = set_element_type(Key, Type, Acc0),
- join_elements_1(Keys, Es1, Es2, Acc);
-join_elements_1([], _Es1, _Es2, Acc) ->
- Acc.
+subtract(A, B) -> beam_types:subtract(A, B).
-%% Joins types of literals; note that the left argument must either be a
-%% literal or exactly equal to the second argument.
-join_literal(Same, Same) ->
- Same;
-join_literal({literal,_}=Lit, T) ->
- join_literal(T, get_literal_type(Lit));
-join_literal(T1, T2) ->
- %% We're done extracting the types, try merging them again.
- join(T1, T2).
-
-join_list(nil, cons) -> list;
-join_list(nil, list) -> list;
-join_list(cons, list) -> list;
-join_list(T, nil) -> join_list(nil, T);
-join_list(T, cons) -> join_list(cons, T);
-join_list(_, _) ->
- %% Not a list, so it must be a term.
- term.
-
-join_bool([]) -> {atom,[]};
-join_bool(true) -> bool;
-join_bool(false) -> bool;
-join_bool(_) -> {atom,[]}.
-
-%% meet(Type1, Type2) -> Type
-%% Return the meet of two types. The meet is a more specific type.
-%% It will be 'none' if the types are in conflict.
-
-meet(Same, Same) ->
- Same;
-meet(term, Other) ->
- Other;
-meet(Other, term) ->
- Other;
-meet(#ms{}, binary) ->
- #ms{};
-meet(binary, #ms{}) ->
- #ms{};
-meet({literal,_}, {literal,_}) ->
- none;
-meet(T1, {literal,_}=T2) ->
- meet(T2, T1);
-meet({literal,_}=T1, T2) ->
- case meet(get_literal_type(T1), T2) of
- none -> none;
- _ -> T1
- end;
-meet(T1, T2) ->
- case {erlang:min(T1, T2),erlang:max(T1, T2)} of
- {{atom,_}=A,{atom,[]}} -> A;
- {bool,{atom,B}=Atom} when is_boolean(B) -> Atom;
- {bool,{atom,[]}} -> bool;
- {cons,list} -> cons;
- {{float,_}=T,{float,[]}} -> T;
- {{integer,_}=T,{integer,[]}} -> T;
- {list,nil} -> nil;
- {number,{integer,_}=T} -> T;
- {number,{float,_}=T} -> T;
- {{tuple,Size1,Es1},{tuple,Size2,Es2}} ->
- Es = meet_elements(Es1, Es2),
- case {Size1,Size2,Es} of
- {_, _, none} ->
- none;
- {[Sz1],[Sz2],_} ->
- Sz = erlang:max(Sz1, Sz2),
- assert_tuple_elements(Sz, Es),
- {tuple,[Sz],Es};
- {Sz1,[Sz2],_} when Sz2 =< Sz1 ->
- assert_tuple_elements(Sz1, Es),
- {tuple,Sz1,Es};
- {Sz,Sz,_} ->
- assert_tuple_elements(Sz, Es),
- {tuple,Sz,Es};
- {_,_,_} ->
- none
- end;
- {_,_} -> none
+assert_type(RequiredType, Term, Vst) ->
+ GivenType = get_term_type(Term, Vst),
+ case meet(RequiredType, GivenType) of
+ GivenType -> ok;
+ _RequiredType -> error({bad_type,{needed,RequiredType},{actual,GivenType}})
end.
-meet_elements(Es1, Es2) ->
- Keys = maps:keys(Es1) ++ maps:keys(Es2),
- meet_elements_1(Keys, Es1, Es2, #{}).
-
-meet_elements_1([Key | Keys], Es1, Es2, Acc) ->
- case {Es1, Es2} of
- {#{ Key := Type1 }, #{ Key := Type2 }} ->
- case meet(Type1, Type2) of
- none -> none;
- Type -> meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type })
- end;
- {#{ Key := Type1 }, _} ->
- meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type1 });
- {_, #{ Key := Type2 }} ->
- meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type2 })
- end;
-meet_elements_1([], _Es1, _Es2, Acc) ->
- Acc.
-
-%% No tuple elements may have an index above the known size.
-assert_tuple_elements(Limit, Es) ->
- true = maps:fold(fun({integer,Index}, _T, true) ->
- Index =< Limit
- end, true, Es). %Assertion.
-
-%% subtract(Type1, Type2) -> Type
-%% Subtract Type2 from Type2. Example:
-%% subtract(list, nil) -> cons
-
-subtract(Same, Same) -> none;
-subtract(list, nil) -> cons;
-subtract(list, cons) -> nil;
-subtract(number, {integer,[]}) -> {float,[]};
-subtract(number, {float,[]}) -> {integer,[]};
-subtract(bool, {atom,false}) -> {atom, true};
-subtract(bool, {atom,true}) -> {atom, false};
-subtract(Type, _) -> Type.
-
-assert_type(WantedType, Term, Vst) ->
- Type = get_term_type(Term, Vst),
- assert_type(WantedType, Type).
-
-assert_type(Correct, Correct) -> ok;
-assert_type(float, {float,_}) -> ok;
-assert_type(tuple, {tuple,_,_}) -> ok;
-assert_type(tuple, {literal,Tuple}) when is_tuple(Tuple) -> ok;
-assert_type({tuple_element,I}, {tuple,[Sz],_})
- when 1 =< I, I =< Sz ->
- ok;
-assert_type({tuple_element,I}, {tuple,Sz,_})
- when is_integer(Sz), 1 =< I, I =< Sz ->
- ok;
-assert_type({tuple_element,I}, {literal,Lit}) when I =< tuple_size(Lit) ->
- ok;
-assert_type(cons, {literal,[_|_]}) ->
- ok;
-assert_type(Needed, Actual) ->
- error({bad_type,{needed,Needed},{actual,Actual}}).
-
-get_element_type(Key, Src, Vst) ->
- get_element_type_1(Key, get_term_type(Src, Vst)).
-
-get_element_type_1({integer,_}=Key, {tuple,_Sz,Es}) ->
- case Es of
- #{ Key := Type } -> Type;
- #{} -> term
- end;
-get_element_type_1(_Index, _Type) ->
- term.
-
-set_element_type(_Key, none, Es) ->
- Es;
-set_element_type(Key, term, Es) ->
- maps:remove(Key, Es);
-set_element_type(Key, Type, Es) ->
- Es#{ Key => Type }.
-
-get_tuple_size({integer,[]}) -> 0;
-get_tuple_size({integer,Sz}) -> Sz;
-get_tuple_size(_) -> 0.
-
validate_src(Ss, Vst) when is_list(Ss) ->
_ = [assert_term(S, Vst) || S <- Ss],
ok.
@@ -2228,7 +1953,7 @@ validate_src(Ss, Vst) when is_list(Ss) ->
get_term_type(Src, Vst) ->
case get_movable_term_type(Src, Vst) of
- #ms{} -> error({match_context,Src});
+ #t_bs_context{} -> error({match_context,Src});
Type -> Type
end.
@@ -2238,12 +1963,11 @@ get_term_type(Src, Vst) ->
get_movable_term_type(Src, Vst) ->
case get_raw_type(Src, Vst) of
+ #t_abstract{kind=tuple_in_progress=Kind} -> error({Kind,Src});
initialized -> error({unassigned,Src});
uninitialized -> error({uninitialized_reg,Src});
{catchtag,_} -> error({catchtag,Src});
{trytag,_} -> error({trytag,Src});
- tuple_in_progress -> error({tuple_in_progress,Src});
- {literal,_}=Lit -> get_literal_type(Lit);
Type -> Type
end.
@@ -2286,32 +2010,22 @@ get_raw_type(Src, #vst{}) ->
get_literal_type(Src).
is_value_alive(#value_ref{}=Ref, #vst{current=#st{vs=Vs}}) ->
- is_map_key(Ref, Vs).
-
-get_literal_type(nil=T) -> T;
-get_literal_type({atom,A}=T) when is_atom(A) -> T;
-get_literal_type({float,F}=T) when is_float(F) -> T;
-get_literal_type({integer,I}=T) when is_integer(I) -> T;
-get_literal_type({literal,[_|_]}) -> cons;
-get_literal_type({literal,Bitstring}) when is_bitstring(Bitstring) -> binary;
-get_literal_type({literal,Map}) when is_map(Map) -> map;
-get_literal_type({literal,Tuple}) when is_tuple(Tuple) -> glt_1(Tuple);
-get_literal_type({literal,_}) -> term;
-get_literal_type(T) -> error({not_literal,T}).
-
-glt_1([]) -> nil;
-glt_1(A) when is_atom(A) -> {atom, A};
-glt_1(F) when is_float(F) -> {float, F};
-glt_1(I) when is_integer(I) -> {integer, I};
-glt_1(T) when is_tuple(T) ->
- {Es,_} = foldl(fun(Val, {Es0, Index}) ->
- Type = glt_1(Val),
- Es = set_element_type({integer,Index}, Type, Es0),
- {Es, Index + 1}
- end, {#{}, 1}, tuple_to_list(T)),
- {tuple, tuple_size(T), Es};
-glt_1(L) ->
- {literal, L}.
+ is_map_key(Ref, Vs);
+is_value_alive(_, _) ->
+ false.
+
+get_literal_type(nil) ->
+ beam_types:make_type_from_value([]);
+get_literal_type({atom,A}) when is_atom(A) ->
+ beam_types:make_type_from_value(A);
+get_literal_type({float,F}) when is_float(F) ->
+ beam_types:make_type_from_value(F);
+get_literal_type({integer,I}) when is_integer(I) ->
+ beam_types:make_type_from_value(I);
+get_literal_type({literal,L}) ->
+ beam_types:make_type_from_value(L);
+get_literal_type(T) ->
+ error({not_literal,T}).
%%%
%%% Branch tracking
@@ -2502,9 +2216,6 @@ merge_ct_1([C0|Ct0], [C1|Ct1]) ->
merge_ct_1([], []) -> [];
merge_ct_1(_, _) -> undecided.
-tuple_sz([Sz]) -> Sz;
-tuple_sz(Sz) -> Sz.
-
verify_y_init(#vst{current=#st{numy=NumY,ys=Ys}}=Vst) when is_integer(NumY) ->
HighestY = maps:fold(fun({y,Y}, _, Acc) -> max(Y, Acc) end, -1, Ys),
true = NumY > HighestY, %Assertion.
@@ -2646,319 +2357,26 @@ assert_not_fragile(Lit, #vst{}) ->
ok.
%%%
-%%% Return/argument types of BIFs
+%%% Return/argument types of calls and BIFs
%%%
-bif_return_type('-', Src, Vst) ->
- arith_return_type(Src, Vst);
-bif_return_type('+', Src, Vst) ->
- arith_return_type(Src, Vst);
-bif_return_type('*', Src, Vst) ->
- arith_return_type(Src, Vst);
-bif_return_type(abs, [Num], Vst) ->
- case get_term_type(Num, Vst) of
- {float,_}=T -> T;
- {integer,_}=T -> T;
- _ -> number
- end;
-bif_return_type(float, _, _) -> {float,[]};
-bif_return_type('/', _, _) -> {float,[]};
-%% Binary operations
-bif_return_type('binary_part', [_,_], _) -> binary;
-bif_return_type('binary_part', [_,_,_], _) -> binary;
-bif_return_type('bit_size', [_], _) -> {integer,[]};
-bif_return_type('byte_size', [_], _) -> {integer,[]};
-%% Integer operations.
-bif_return_type(ceil, [_], _) -> {integer,[]};
-bif_return_type('div', [_,_], _) -> {integer,[]};
-bif_return_type(floor, [_], _) -> {integer,[]};
-bif_return_type('rem', [_,_], _) -> {integer,[]};
-bif_return_type(length, [_], _) -> {integer,[]};
-bif_return_type(size, [_], _) -> {integer,[]};
-bif_return_type(trunc, [_], _) -> {integer,[]};
-bif_return_type(round, [_], _) -> {integer,[]};
-bif_return_type('band', [_,_], _) -> {integer,[]};
-bif_return_type('bor', [_,_], _) -> {integer,[]};
-bif_return_type('bxor', [_,_], _) -> {integer,[]};
-bif_return_type('bnot', [_], _) -> {integer,[]};
-bif_return_type('bsl', [_,_], _) -> {integer,[]};
-bif_return_type('bsr', [_,_], _) -> {integer,[]};
-%% Booleans.
-bif_return_type('==', [_,_], _) -> bool;
-bif_return_type('/=', [_,_], _) -> bool;
-bif_return_type('=<', [_,_], _) -> bool;
-bif_return_type('<', [_,_], _) -> bool;
-bif_return_type('>=', [_,_], _) -> bool;
-bif_return_type('>', [_,_], _) -> bool;
-bif_return_type('=:=', [_,_], _) -> bool;
-bif_return_type('=/=', [_,_], _) -> bool;
-bif_return_type('not', [_], _) -> bool;
-bif_return_type('and', [_,_], _) -> bool;
-bif_return_type('or', [_,_], _) -> bool;
-bif_return_type('xor', [_,_], _) -> bool;
-bif_return_type(is_atom, [_], _) -> bool;
-bif_return_type(is_boolean, [_], _) -> bool;
-bif_return_type(is_binary, [_], _) -> bool;
-bif_return_type(is_float, [_], _) -> bool;
-bif_return_type(is_function, [_], _) -> bool;
-bif_return_type(is_function, [_,_], _) -> bool;
-bif_return_type(is_integer, [_], _) -> bool;
-bif_return_type(is_list, [_], _) -> bool;
-bif_return_type(is_map, [_], _) -> bool;
-bif_return_type(is_number, [_], _) -> bool;
-bif_return_type(is_pid, [_], _) -> bool;
-bif_return_type(is_port, [_], _) -> bool;
-bif_return_type(is_reference, [_], _) -> bool;
-bif_return_type(is_tuple, [_], _) -> bool;
-%% Misc.
-bif_return_type(tuple_size, [_], _) -> {integer,[]};
-bif_return_type(map_size, [_], _) -> {integer,[]};
-bif_return_type(node, [], _) -> {atom,[]};
-bif_return_type(node, [_], _) -> {atom,[]};
-bif_return_type(hd, [_], _) -> term;
-bif_return_type(tl, [_], _) -> term;
-bif_return_type(get, [_], _) -> term;
-bif_return_type(Bif, _, _) when is_atom(Bif) -> term.
-
-%% Generic
-bif_arg_types(tuple_size, [_]) -> [{tuple,[0],#{}}];
-bif_arg_types(map_size, [_]) -> [map];
-bif_arg_types(is_map_key, [_,_]) -> [term, map];
-bif_arg_types(map_get, [_,_]) -> [term, map];
-bif_arg_types(length, [_]) -> [list];
-bif_arg_types(hd, [_]) -> [cons];
-bif_arg_types(tl, [_]) -> [cons];
-%% Boolean
-bif_arg_types('not', [_]) -> [bool];
-bif_arg_types('and', [_,_]) -> [bool, bool];
-bif_arg_types('or', [_,_]) -> [bool, bool];
-bif_arg_types('xor', [_,_]) -> [bool, bool];
-%% Binary
-bif_arg_types('binary_part', [_,_]) ->
- PosLen = {tuple, 2, #{ {integer,1} => {integer,[]},
- {integer,2} => {integer,[]} }},
- [binary, PosLen];
-bif_arg_types('binary_part', [_,_,_]) ->
- [binary, {integer,[]}, {integer,[]}];
-bif_arg_types('bit_size', [_]) -> [binary];
-bif_arg_types('byte_size', [_]) -> [binary];
-%% Numerical
-bif_arg_types('-', [_]) -> [number];
-bif_arg_types('-', [_,_]) -> [number,number];
-bif_arg_types('+', [_]) -> [number];
-bif_arg_types('+', [_,_]) -> [number,number];
-bif_arg_types('*', [_,_]) -> [number, number];
-bif_arg_types('/', [_,_]) -> [number, number];
-bif_arg_types(abs, [_]) -> [number];
-bif_arg_types(ceil, [_]) -> [number];
-bif_arg_types(float, [_]) -> [number];
-bif_arg_types(floor, [_]) -> [number];
-bif_arg_types(trunc, [_]) -> [number];
-bif_arg_types(round, [_]) -> [number];
-%% Integer-specific
-bif_arg_types('div', [_,_]) -> [{integer,[]}, {integer,[]}];
-bif_arg_types('rem', [_,_]) -> [{integer,[]}, {integer,[]}];
-bif_arg_types('band', [_,_]) -> [{integer,[]}, {integer,[]}];
-bif_arg_types('bor', [_,_]) -> [{integer,[]}, {integer,[]}];
-bif_arg_types('bxor', [_,_]) -> [{integer,[]}, {integer,[]}];
-bif_arg_types('bnot', [_]) -> [{integer,[]}];
-bif_arg_types('bsl', [_,_]) -> [{integer,[]}, {integer,[]}];
-bif_arg_types('bsr', [_,_]) -> [{integer,[]}, {integer,[]}];
-%% Unsafe type tests that may fail if an argument doesn't have the right type.
-bif_arg_types(is_function, [_,_]) -> [term, {integer,[]}];
-bif_arg_types(_, Args) -> [term || _Arg <- Args].
-
-is_bif_safe('/=', 2) -> true;
-is_bif_safe('<', 2) -> true;
-is_bif_safe('=/=', 2) -> true;
-is_bif_safe('=:=', 2) -> true;
-is_bif_safe('=<', 2) -> true;
-is_bif_safe('==', 2) -> true;
-is_bif_safe('>', 2) -> true;
-is_bif_safe('>=', 2) -> true;
-is_bif_safe(is_atom, 1) -> true;
-is_bif_safe(is_boolean, 1) -> true;
-is_bif_safe(is_binary, 1) -> true;
-is_bif_safe(is_bitstring, 1) -> true;
-is_bif_safe(is_float, 1) -> true;
-is_bif_safe(is_function, 1) -> true;
-is_bif_safe(is_integer, 1) -> true;
-is_bif_safe(is_list, 1) -> true;
-is_bif_safe(is_map, 1) -> true;
-is_bif_safe(is_number, 1) -> true;
-is_bif_safe(is_pid, 1) -> true;
-is_bif_safe(is_port, 1) -> true;
-is_bif_safe(is_reference, 1) -> true;
-is_bif_safe(is_tuple, 1) -> true;
-is_bif_safe(get, 1) -> true;
-is_bif_safe(self, 0) -> true;
-is_bif_safe(node, 0) -> true;
-is_bif_safe(_, _) -> false.
-
-arith_return_type([A], Vst) ->
- %% Unary '+' or '-'.
- case get_term_type(A, Vst) of
- {integer,_} -> {integer,[]};
- {float,_} -> {float,[]};
- _ -> number
- end;
-arith_return_type([A,B], Vst) ->
- TypeA = get_term_type(A, Vst),
- TypeB = get_term_type(B, Vst),
- case {TypeA, TypeB} of
- {{integer,_},{integer,_}} -> {integer,[]};
- {{float,_},_} -> {float,[]};
- {_,{float,_}} -> {float,[]};
- {_,_} -> number
- end;
-arith_return_type(_, _) -> number.
+bif_types(Op, Ss, Vst) ->
+ Args = [get_term_type(Arg, Vst) || Arg <- Ss],
+ beam_call_types:types(erlang, Op, Args).
-%%%
-%%% Return/argument types of calls
-%%%
+call_types({extfunc,M,F,A}, A, Vst) ->
+ Args = get_call_args(A, Vst),
+ beam_call_types:types(M, F, Args);
+call_types(_, A, Vst) ->
+ {any, get_call_args(A, Vst), false}.
-call_return_type({extfunc,M,F,A}, Vst) -> call_return_type_1(M, F, A, Vst);
-call_return_type(_, _) -> term.
+get_call_args(Arity, Vst) ->
+ get_call_args_1(0, Arity, Vst).
-call_return_type_1(erlang, setelement, 3, Vst) ->
- IndexType = get_term_type({x,0}, Vst),
- TupleType =
- case get_term_type({x,1}, Vst) of
- {literal,Tuple}=Lit when is_tuple(Tuple) -> get_literal_type(Lit);
- {tuple,_,_}=TT -> TT;
- _ -> {tuple,[0],#{}}
- end,
- case IndexType of
- {integer,I} when is_integer(I) ->
- case meet({tuple,[I],#{}}, TupleType) of
- {tuple, Sz, Es0} ->
- ValueType = get_term_type({x,2}, Vst),
- Es = set_element_type({integer,I}, ValueType, Es0),
- {tuple, Sz, Es};
- none ->
- TupleType
- end;
- _ ->
- %% The index could point anywhere, so we must discard all element
- %% information.
- setelement(3, TupleType, #{})
- end;
-call_return_type_1(erlang, '++', 2, Vst) ->
- LType = get_term_type({x,0}, Vst),
- RType = get_term_type({x,1}, Vst),
- case LType =:= cons orelse RType =:= cons of
- true ->
- cons;
- false ->
- %% `[] ++ RHS` yields RHS, even if RHS is not a list
- join(list, RType)
- end;
-call_return_type_1(erlang, '--', 2, _Vst) ->
- list;
-call_return_type_1(erlang, F, A, _) ->
- erlang_mod_return_type(F, A);
-call_return_type_1(lists, F, A, Vst) ->
- lists_mod_return_type(F, A, Vst);
-call_return_type_1(math, F, A, _) ->
- math_mod_return_type(F, A);
-call_return_type_1(M, F, A, _) when is_atom(M), is_atom(F), is_integer(A), A >= 0 ->
- term.
-
-erlang_mod_return_type(exit, 1) -> exception;
-erlang_mod_return_type(throw, 1) -> exception;
-erlang_mod_return_type(error, 1) -> exception;
-erlang_mod_return_type(error, 2) -> exception;
-erlang_mod_return_type(F, A) when is_atom(F), is_integer(A), A >= 0 -> term.
-
-math_mod_return_type(cos, 1) -> {float,[]};
-math_mod_return_type(cosh, 1) -> {float,[]};
-math_mod_return_type(sin, 1) -> {float,[]};
-math_mod_return_type(sinh, 1) -> {float,[]};
-math_mod_return_type(tan, 1) -> {float,[]};
-math_mod_return_type(tanh, 1) -> {float,[]};
-math_mod_return_type(acos, 1) -> {float,[]};
-math_mod_return_type(acosh, 1) -> {float,[]};
-math_mod_return_type(asin, 1) -> {float,[]};
-math_mod_return_type(asinh, 1) -> {float,[]};
-math_mod_return_type(atan, 1) -> {float,[]};
-math_mod_return_type(atanh, 1) -> {float,[]};
-math_mod_return_type(erf, 1) -> {float,[]};
-math_mod_return_type(erfc, 1) -> {float,[]};
-math_mod_return_type(exp, 1) -> {float,[]};
-math_mod_return_type(log, 1) -> {float,[]};
-math_mod_return_type(log2, 1) -> {float,[]};
-math_mod_return_type(log10, 1) -> {float,[]};
-math_mod_return_type(sqrt, 1) -> {float,[]};
-math_mod_return_type(atan2, 2) -> {float,[]};
-math_mod_return_type(pow, 2) -> {float,[]};
-math_mod_return_type(ceil, 1) -> {float,[]};
-math_mod_return_type(floor, 1) -> {float,[]};
-math_mod_return_type(fmod, 2) -> {float,[]};
-math_mod_return_type(pi, 0) -> {float,[]};
-math_mod_return_type(F, A) when is_atom(F), is_integer(A), A >= 0 -> term.
-
-lists_mod_return_type(all, 2, _Vst) ->
- bool;
-lists_mod_return_type(any, 2, _Vst) ->
- bool;
-lists_mod_return_type(keymember, 3, _Vst) ->
- bool;
-lists_mod_return_type(member, 2, _Vst) ->
- bool;
-lists_mod_return_type(prefix, 2, _Vst) ->
- bool;
-lists_mod_return_type(suffix, 2, _Vst) ->
- bool;
-lists_mod_return_type(dropwhile, 2, _Vst) ->
- list;
-lists_mod_return_type(duplicate, 2, _Vst) ->
- list;
-lists_mod_return_type(filter, 2, _Vst) ->
- list;
-lists_mod_return_type(flatten, 1, _Vst) ->
- list;
-lists_mod_return_type(map, 2, Vst) ->
- same_length_type({x,1}, Vst);
-lists_mod_return_type(MF, 3, Vst) when MF =:= mapfoldl; MF =:= mapfoldr ->
- ListType = same_length_type({x,2}, Vst),
- {tuple,2,#{ {integer,1} => ListType} };
-lists_mod_return_type(partition, 2, _Vst) ->
- two_tuple(list, list);
-lists_mod_return_type(reverse, 1, Vst) ->
- same_length_type({x,0}, Vst);
-lists_mod_return_type(seq, 2, _Vst) ->
- list;
-lists_mod_return_type(sort, 1, Vst) ->
- same_length_type({x,0}, Vst);
-lists_mod_return_type(sort, 2, Vst) ->
- same_length_type({x,1}, Vst);
-lists_mod_return_type(splitwith, 2, _Vst) ->
- two_tuple(list, list);
-lists_mod_return_type(takewhile, 2, _Vst) ->
- list;
-lists_mod_return_type(unzip, 1, Vst) ->
- ListType = same_length_type({x,0}, Vst),
- two_tuple(ListType, ListType);
-lists_mod_return_type(usort, 1, Vst) ->
- same_length_type({x,0}, Vst);
-lists_mod_return_type(zip, 2, _Vst) ->
- list;
-lists_mod_return_type(zipwith, 3, _Vst) ->
- list;
-lists_mod_return_type(_, _, _) ->
- term.
-
-two_tuple(Type1, Type2) ->
- {tuple,2,#{ {integer,1} => Type1,
- {integer,2} => Type2 }}.
-
-same_length_type(Reg, Vst) ->
- case get_term_type(Reg, Vst) of
- {literal,[_|_]} -> cons;
- cons -> cons;
- nil -> nil;
- _ -> list
- end.
+get_call_args_1(Arity, Arity, _) ->
+ [];
+get_call_args_1(N, Arity, Vst) when N < Arity ->
+ [get_movable_term_type({x,N}, Vst) | get_call_args_1(N + 1, Arity, Vst)].
check_limit({x,X}=Src) when is_integer(X) ->
if
diff --git a/lib/compiler/src/cerl.erl b/lib/compiler/src/cerl.erl
index 62cd5b5120..bc28f58712 100644
--- a/lib/compiler/src/cerl.erl
+++ b/lib/compiler/src/cerl.erl
@@ -263,7 +263,7 @@
%% @see subtrees/1
%% @see meta/1
--type ctype() :: 'alias' | 'apply' | 'binary' | 'bitrst' | 'call' | 'case'
+-type ctype() :: 'alias' | 'apply' | 'binary' | 'bitstr' | 'call' | 'case'
| 'catch' | 'clause' | 'cons' | 'fun' | 'let' | 'letrec'
| 'literal' | 'map' | 'map_pair' | 'module' | 'primop'
| 'receive' | 'seq' | 'try' | 'tuple' | 'values' | 'var'.
diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl
index 28db8986ff..42f9e8b902 100644
--- a/lib/compiler/src/compile.erl
+++ b/lib/compiler/src/compile.erl
@@ -265,7 +265,10 @@ expand_opt(r19, Os) ->
expand_opt(r20, Os) ->
expand_opt_before_21(Os);
expand_opt(r21, Os) ->
- [no_put_tuple2 | expand_opt(no_bsm3, Os)];
+ [no_shared_fun_wrappers,
+ no_swap, no_put_tuple2 | expand_opt(no_bsm3, Os)];
+expand_opt(r22, Os) ->
+ [no_shared_fun_wrappers, no_swap | Os];
expand_opt({debug_info_key,_}=O, Os) ->
[encrypt_debug_info,O|Os];
expand_opt(no_type_opt, Os) ->
@@ -275,7 +278,8 @@ expand_opt(no_type_opt, Os) ->
expand_opt(O, Os) -> [O|Os].
expand_opt_before_21(Os) ->
- [no_put_tuple2, no_get_hd_tl, no_ssa_opt_record,
+ [no_shared_fun_wrappers, no_swap,
+ no_put_tuple2, no_get_hd_tl, no_ssa_opt_record,
no_utf8_atoms | expand_opt(no_bsm3, Os)].
%% format_error(ErrorDescriptor) -> string()
@@ -860,8 +864,6 @@ asm_passes() ->
{unless,no_postopt,
[{pass,beam_block},
{iff,dblk,{listing,"block"}},
- {unless,no_except,{pass,beam_except}},
- {iff,dexcept,{listing,"except"}},
{unless,no_jopt,{pass,beam_jump}},
{iff,djmp,{listing,"jump"}},
{unless,no_peep_opt,{pass,beam_peep}},
@@ -2093,9 +2095,9 @@ pre_load() ->
L = [beam_a,
beam_asm,
beam_block,
+ beam_call_types,
beam_clean,
beam_dict,
- beam_except,
beam_flatten,
beam_jump,
beam_kernel_to_ssa,
@@ -2112,6 +2114,7 @@ pre_load() ->
beam_ssa_share,
beam_ssa_type,
beam_trim,
+ beam_types,
beam_utils,
beam_validator,
beam_z,
diff --git a/lib/compiler/src/compiler.app.src b/lib/compiler/src/compiler.app.src
index a086a3a8d3..092757ae65 100644
--- a/lib/compiler/src/compiler.app.src
+++ b/lib/compiler/src/compiler.app.src
@@ -24,10 +24,10 @@
beam_a,
beam_asm,
beam_block,
+ beam_call_types,
beam_clean,
beam_dict,
beam_disasm,
- beam_except,
beam_flatten,
beam_jump,
beam_kernel_to_ssa,
@@ -47,6 +47,7 @@
beam_ssa_share,
beam_ssa_type,
beam_trim,
+ beam_types,
beam_utils,
beam_validator,
beam_z,
diff --git a/lib/compiler/src/genop.tab b/lib/compiler/src/genop.tab
index 86590fad87..0a38d17857 100755
--- a/lib/compiler/src/genop.tab
+++ b/lib/compiler/src/genop.tab
@@ -596,3 +596,9 @@ BEAM_FORMAT_NUMBER=0
## @spec bs_set_positon Ctx Pos
## @doc Sets the current position of Ctx to Pos
168: bs_set_position/2
+
+# OTP 23
+
+## @spec swap Register1 Register2
+## @doc Swaps the contents of two registers.
+169: swap/2
diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl
index 4939a94a92..63c67639d4 100644
--- a/lib/compiler/src/sys_core_fold.erl
+++ b/lib/compiler/src/sys_core_fold.erl
@@ -99,10 +99,6 @@
t=#{} :: map(), %Types
in_guard=false}). %In guard or not.
--type type_info() :: cerl:cerl() | 'bool' | 'integer' | {'fun', pos_integer()}.
--type yes_no_maybe() :: 'yes' | 'no' | 'maybe'.
--type sub() :: #sub{}.
-
-spec module(cerl:c_module(), [compile:option()]) ->
{'ok', cerl:c_module(), [_]}.
@@ -315,10 +311,10 @@ expr(#c_seq{arg=Arg0,body=B0}=Seq0, Ctxt, Sub) ->
false ->
%% Arg cannot be "values" here - only a single value
%% make sense here.
- case {Ctxt,is_safe_simple(Arg, Sub)} of
+ case {Ctxt,is_safe_simple(Arg)} of
{effect,true} -> B1;
{effect,false} ->
- case is_safe_simple(B1, Sub) of
+ case is_safe_simple(B1) of
true -> Arg;
false -> Seq0#c_seq{arg=Arg,body=B1}
end;
@@ -442,7 +438,7 @@ expr(#c_catch{anno=Anno,body=B}, effect, Sub) ->
expr(#c_catch{body=B0}=Catch, _, Sub) ->
%% We can remove catch if the value is simple
B1 = body(B0, value, Sub),
- case is_safe_simple(B1, Sub) of
+ case is_safe_simple(B1) of
true -> B1;
false -> Catch#c_catch{body=B1}
end;
@@ -458,7 +454,7 @@ expr(#c_try{arg=E0,vars=[#c_var{name=X}],body=#c_var{name=X},
%% We can remove try/catch if the expression is an
%% expression that cannot fail.
- case is_safe_bool_expr(E2, Sub) orelse is_safe_simple(E2, Sub) of
+ case is_safe_bool_expr(E2) orelse is_safe_simple(E2) of
true -> E2;
false -> Try#c_try{arg=E2}
end;
@@ -472,7 +468,7 @@ expr(#c_try{anno=A,arg=E0,vars=Vs0,body=B0,evars=Evs0,handler=H0}=Try, _, Sub0)
E1 = body(E0, value, Sub0),
{Vs1,Sub1} = var_list(Vs0, Sub0),
B1 = body(B0, value, Sub1),
- case is_safe_simple(E1, Sub0) of
+ case is_safe_simple(E1) of
true ->
expr(#c_let{anno=A,vars=Vs1,arg=E1,body=B1}, value, Sub0);
false ->
@@ -602,20 +598,20 @@ is_literal_fun(_) -> false.
%% Currently, we don't attempt to check binaries because they
%% are difficult to check.
-is_safe_simple(#c_var{}=Var, _) ->
+is_safe_simple(#c_var{}=Var) ->
not cerl:is_c_fname(Var);
-is_safe_simple(#c_cons{hd=H,tl=T}, Sub) ->
- is_safe_simple(H, Sub) andalso is_safe_simple(T, Sub);
-is_safe_simple(#c_tuple{es=Es}, Sub) -> is_safe_simple_list(Es, Sub);
-is_safe_simple(#c_literal{}, _) -> true;
+is_safe_simple(#c_cons{hd=H,tl=T}) ->
+ is_safe_simple(H) andalso is_safe_simple(T);
+is_safe_simple(#c_tuple{es=Es}) -> is_safe_simple_list(Es);
+is_safe_simple(#c_literal{}) -> true;
is_safe_simple(#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=Name},
- args=Args}, Sub) when is_atom(Name) ->
+ args=Args}) when is_atom(Name) ->
NumArgs = length(Args),
case erl_internal:bool_op(Name, NumArgs) of
true ->
%% Boolean operators are safe if the arguments are boolean.
- all(fun(C) -> is_boolean_type(C, Sub) =:= yes end, Args);
+ all(fun is_bool_expr/1, Args);
false ->
%% We need a rather complicated test to ensure that
%% we only allow safe calls that are allowed in a guard.
@@ -624,9 +620,9 @@ is_safe_simple(#c_call{module=#c_literal{val=erlang},
(erl_internal:comp_op(Name, NumArgs) orelse
erl_internal:new_type_test(Name, NumArgs))
end;
-is_safe_simple(_, _) -> false.
+is_safe_simple(_) -> false.
-is_safe_simple_list(Es, Sub) -> all(fun(E) -> is_safe_simple(E, Sub) end, Es).
+is_safe_simple_list(Es) -> all(fun(E) -> is_safe_simple(E) end, Es).
%% will_fail(Expr) -> true|false.
%% Determine whether the expression will fail with an exception.
@@ -853,7 +849,7 @@ useless_call(_, _) -> no.
%% Anything that will not have any effect will be thrown away.
make_effect_seq([H|T], Sub) ->
- case is_safe_simple(H, Sub) of
+ case is_safe_simple(H) of
true -> make_effect_seq(T, Sub);
false -> #c_seq{arg=H,body=make_effect_seq(T, Sub)}
end;
@@ -959,138 +955,14 @@ fold_lit_args(Call, Module, Name, Args0) ->
%% Attempt to evaluate some pure BIF calls with one or more
%% non-literals arguments.
%%
-fold_non_lit_args(Call, erlang, is_boolean, [Arg], Sub) ->
- eval_is_boolean(Call, Arg, Sub);
fold_non_lit_args(Call, erlang, length, [Arg], _) ->
eval_length(Call, Arg);
fold_non_lit_args(Call, erlang, '++', [Arg1,Arg2], _) ->
eval_append(Call, Arg1, Arg2);
fold_non_lit_args(Call, lists, append, [Arg1,Arg2], _) ->
eval_append(Call, Arg1, Arg2);
-fold_non_lit_args(Call, erlang, is_function, [Arg1], Sub) ->
- eval_is_function_1(Call, Arg1, Sub);
-fold_non_lit_args(Call, erlang, is_function, [Arg1,Arg2], Sub) ->
- eval_is_function_2(Call, Arg1, Arg2, Sub);
-fold_non_lit_args(Call, erlang, N, Args, Sub) ->
- NumArgs = length(Args),
- case erl_internal:comp_op(N, NumArgs) of
- true ->
- eval_rel_op(Call, N, Args, Sub);
- false ->
- case erl_internal:bool_op(N, NumArgs) of
- true ->
- eval_bool_op(Call, N, Args, Sub);
- false ->
- Call
- end
- end;
fold_non_lit_args(Call, _, _, _, _) -> Call.
-eval_is_function_1(Call, Arg1, Sub) ->
- case get_type(Arg1, Sub) of
- none -> Call;
- {'fun',_} -> #c_literal{anno=cerl:get_ann(Call),val=true};
- _ -> #c_literal{anno=cerl:get_ann(Call),val=false}
- end.
-
-eval_is_function_2(Call, Arg1, #c_literal{val=Arity}, Sub)
- when is_integer(Arity), Arity > 0 ->
- case get_type(Arg1, Sub) of
- none -> Call;
- {'fun',Arity} -> #c_literal{anno=cerl:get_ann(Call),val=true};
- _ -> #c_literal{anno=cerl:get_ann(Call),val=false}
- end;
-eval_is_function_2(Call, _Arg1, _Arg2, _Sub) -> Call.
-
-%% Evaluate a relational operation using type information.
-eval_rel_op(Call, Op, [#c_var{name=V},#c_var{name=V}], _) ->
- Bool = erlang:Op(same, same),
- #c_literal{anno=cerl:get_ann(Call),val=Bool};
-eval_rel_op(Call, '=:=', [Term,#c_literal{val=true}], Sub) ->
- %% BoolVar =:= true ==> BoolVar
- case is_boolean_type(Term, Sub) of
- yes -> Term;
- maybe -> Call;
- no -> #c_literal{val=false}
- end;
-eval_rel_op(Call, '==', Ops, Sub) ->
- case is_exact_eq_ok(Ops, Sub) of
- true ->
- Name = #c_literal{anno=cerl:get_ann(Call),val='=:='},
- Call#c_call{name=Name};
- false ->
- Call
- end;
-eval_rel_op(Call, '/=', Ops, Sub) ->
- case is_exact_eq_ok(Ops, Sub) of
- true ->
- Name = #c_literal{anno=cerl:get_ann(Call),val='=/='},
- Call#c_call{name=Name};
- false ->
- Call
- end;
-eval_rel_op(Call, _, _, _) -> Call.
-
-is_exact_eq_ok([A,B]=L, Sub) ->
- case is_int_type(A, Sub) =:= yes andalso is_int_type(B, Sub) =:= yes of
- true -> true;
- false -> is_exact_eq_ok_1(L)
- end.
-
-is_exact_eq_ok_1([#c_literal{val=Lit}|_]) ->
- is_non_numeric(Lit);
-is_exact_eq_ok_1([_|T]) ->
- is_exact_eq_ok_1(T);
-is_exact_eq_ok_1([]) -> false.
-
-is_non_numeric([H|T]) ->
- is_non_numeric(H) andalso is_non_numeric(T);
-is_non_numeric(Tuple) when is_tuple(Tuple) ->
- is_non_numeric_tuple(Tuple, tuple_size(Tuple));
-is_non_numeric(Map) when is_map(Map) ->
- %% Note that 17.x and 18.x compare keys in different ways.
- %% Be very conservative -- require that both keys and values
- %% are non-numeric.
- is_non_numeric(maps:to_list(Map));
-is_non_numeric(Num) when is_number(Num) ->
- false;
-is_non_numeric(_) -> true.
-
-is_non_numeric_tuple(Tuple, El) when El >= 1 ->
- is_non_numeric(element(El, Tuple)) andalso
- is_non_numeric_tuple(Tuple, El-1);
-is_non_numeric_tuple(_Tuple, 0) -> true.
-
-%% Evaluate a bool op using type information. We KNOW that
-%% there must be at least one non-literal argument (i.e.
-%% there is no need to handle the case that all argments
-%% are literal).
-
-eval_bool_op(Call, 'and', [#c_literal{val=true},Term], Sub) ->
- eval_bool_op_1(Call, Term, Term, Sub);
-eval_bool_op(Call, 'and', [Term,#c_literal{val=true}], Sub) ->
- eval_bool_op_1(Call, Term, Term, Sub);
-eval_bool_op(Call, 'and', [#c_literal{val=false}=Res,Term], Sub) ->
- eval_bool_op_1(Call, Res, Term, Sub);
-eval_bool_op(Call, 'and', [Term,#c_literal{val=false}=Res], Sub) ->
- eval_bool_op_1(Call, Res, Term, Sub);
-eval_bool_op(Call, _, _, _) -> Call.
-
-eval_bool_op_1(Call, Res, Term, Sub) ->
- case is_boolean_type(Term, Sub) of
- yes -> Res;
- no -> eval_failure(Call, badarg);
- maybe -> Call
- end.
-
-%% Evaluate is_boolean/1 using type information.
-eval_is_boolean(Call, Term, Sub) ->
- case is_boolean_type(Term, Sub) of
- no -> #c_literal{val=false};
- yes -> #c_literal{val=true};
- maybe -> Call
- end.
-
%% eval_length(Call, List) -> Val.
%% Evaluates the length for the prefix of List which has a known
%% shape.
@@ -1804,7 +1676,7 @@ opt_bool_case_guard(#c_case{arg=#c_literal{}}=Case) ->
%%
Case;
opt_bool_case_guard(#c_case{arg=Arg,clauses=Cs0}=Case) ->
- case is_safe_bool_expr(Arg, sub_new()) of
+ case is_safe_bool_expr(Arg) of
false ->
Case;
true ->
@@ -1945,7 +1817,7 @@ case_opt_arg(E0, Sub, Cs, LitExpr) ->
{error,Cs};
false ->
%% If possible, expand this variable to a previously
- %% matched term.
+ %% constructed tuple
E = case_expand_var(E0, Sub),
case_opt_arg_1(E, Cs, LitExpr)
end
@@ -2004,13 +1876,8 @@ case_opt_compiler_generated(Core) ->
case_expand_var(E, #sub{t=Tdb}) ->
Key = cerl:var_name(E),
case Tdb of
- #{Key:=T} ->
- case cerl:is_c_tuple(T) of
- false -> E;
- true -> T
- end;
- _ ->
- E
+ #{Key:=T} -> T;
+ _ -> E
end.
%% case_opt_nomatch(E, Clauses, LitExpr) -> Clauses'
@@ -2302,43 +2169,30 @@ is_simple_case_arg(_) -> false.
%% Check whether the Core expression is guaranteed to return
%% a boolean IF IT RETURNS AT ALL.
%%
-is_bool_expr(Core) ->
- is_bool_expr(Core, sub_new()).
-%% is_bool_expr(Core, Sub) -> true|false
-%% Check whether the Core expression is guaranteed to return
-%% a boolean IF IT RETURNS AT ALL. Uses type information
-%% to be able to identify more expressions as booleans.
-%%
is_bool_expr(#c_call{module=#c_literal{val=erlang},
- name=#c_literal{val=Name},args=Args}=Call, _) ->
+ name=#c_literal{val=Name},args=Args}=Call) ->
NumArgs = length(Args),
erl_internal:comp_op(Name, NumArgs) orelse
erl_internal:new_type_test(Name, NumArgs) orelse
erl_internal:bool_op(Name, NumArgs) orelse
will_fail(Call);
is_bool_expr(#c_try{arg=E,vars=[#c_var{name=X}],body=#c_var{name=X},
- handler=#c_literal{val=false}}, Sub) ->
- is_bool_expr(E, Sub);
-is_bool_expr(#c_case{clauses=Cs}, Sub) ->
- is_bool_expr_list(Cs, Sub);
-is_bool_expr(#c_clause{body=B}, Sub) ->
- is_bool_expr(B, Sub);
-is_bool_expr(#c_let{vars=[V],arg=Arg,body=B}, Sub0) ->
- Sub = case is_bool_expr(Arg, Sub0) of
- true -> update_types(V, [bool], Sub0);
- false -> Sub0
- end,
- is_bool_expr(B, Sub);
-is_bool_expr(#c_let{body=B}, Sub) ->
- %% Binding of multiple variables.
- is_bool_expr(B, Sub);
-is_bool_expr(C, Sub) ->
- is_boolean_type(C, Sub) =:= yes.
-
-is_bool_expr_list([C|Cs], Sub) ->
- is_bool_expr(C, Sub) andalso is_bool_expr_list(Cs, Sub);
-is_bool_expr_list([], _) -> true.
+ handler=#c_literal{val=false}}) ->
+ is_bool_expr(E);
+is_bool_expr(#c_case{clauses=Cs}) ->
+ is_bool_expr_list(Cs);
+is_bool_expr(#c_clause{body=B}) ->
+ is_bool_expr(B);
+is_bool_expr(#c_let{body=B}) ->
+ is_bool_expr(B);
+is_bool_expr(#c_literal{val=Val}) ->
+ is_boolean(Val);
+is_bool_expr(_) -> false.
+
+is_bool_expr_list([C|Cs]) ->
+ is_bool_expr(C) andalso is_bool_expr_list(Cs);
+is_bool_expr_list([]) -> true.
%% is_safe_bool_expr(Core) -> true|false
%% Check whether the Core expression ALWAYS returns a boolean
@@ -2346,17 +2200,17 @@ is_bool_expr_list([], _) -> true.
%% is suitable for a guard (no calls to non-guard BIFs, local
%% functions, or is_record/2).
%%
-is_safe_bool_expr(Core, Sub) ->
- is_safe_bool_expr_1(Core, Sub, cerl_sets:new()).
+is_safe_bool_expr(Core) ->
+ is_safe_bool_expr_1(Core, cerl_sets:new()).
is_safe_bool_expr_1(#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=is_record},
args=[A,#c_literal{val=Tag},#c_literal{val=Size}]},
- Sub, _BoolVars) when is_atom(Tag), is_integer(Size) ->
- is_safe_simple(A, Sub);
+ _BoolVars) when is_atom(Tag), is_integer(Size) ->
+ is_safe_simple(A);
is_safe_bool_expr_1(#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=is_record}},
- _Sub, _BoolVars) ->
+ _BoolVars) ->
%% The is_record/2 BIF is NOT allowed in guards.
%% The is_record/3 BIF where its second argument is not an atom or its third
%% is not an integer is NOT allowed in guards.
@@ -2368,49 +2222,49 @@ is_safe_bool_expr_1(#c_call{module=#c_literal{val=erlang},
is_safe_bool_expr_1(#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=is_function},
args=[A,#c_literal{val=Arity}]},
- Sub, _BoolVars) when is_integer(Arity), Arity >= 0 ->
- is_safe_simple(A, Sub);
+ _BoolVars) when is_integer(Arity), Arity >= 0 ->
+ is_safe_simple(A);
is_safe_bool_expr_1(#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=is_function}},
- _Sub, _BoolVars) ->
+ _BoolVars) ->
false;
is_safe_bool_expr_1(#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=Name},args=Args},
- Sub, BoolVars) ->
+ BoolVars) ->
NumArgs = length(Args),
case (erl_internal:comp_op(Name, NumArgs) orelse
erl_internal:new_type_test(Name, NumArgs)) andalso
- is_safe_simple_list(Args, Sub) of
+ is_safe_simple_list(Args) of
true ->
true;
false ->
%% Boolean operators are safe if all arguments are boolean.
erl_internal:bool_op(Name, NumArgs) andalso
- is_safe_bool_expr_list(Args, Sub, BoolVars)
+ is_safe_bool_expr_list(Args, BoolVars)
end;
-is_safe_bool_expr_1(#c_let{vars=Vars,arg=Arg,body=B}, Sub, BoolVars) ->
- case is_safe_simple(Arg, Sub) of
+is_safe_bool_expr_1(#c_let{vars=Vars,arg=Arg,body=B}, BoolVars) ->
+ case is_safe_simple(Arg) of
true ->
- case {is_safe_bool_expr_1(Arg, Sub, BoolVars),Vars} of
+ case {is_safe_bool_expr_1(Arg, BoolVars),Vars} of
{true,[#c_var{name=V}]} ->
- is_safe_bool_expr_1(B, Sub, cerl_sets:add_element(V, BoolVars));
+ is_safe_bool_expr_1(B, cerl_sets:add_element(V, BoolVars));
{false,_} ->
- is_safe_bool_expr_1(B, Sub, BoolVars)
+ is_safe_bool_expr_1(B, BoolVars)
end;
false -> false
end;
-is_safe_bool_expr_1(#c_literal{val=Val}, _Sub, _) ->
+is_safe_bool_expr_1(#c_literal{val=Val}, _BoolVars) ->
is_boolean(Val);
-is_safe_bool_expr_1(#c_var{name=V}, _Sub, BoolVars) ->
+is_safe_bool_expr_1(#c_var{name=V}, BoolVars) ->
cerl_sets:is_element(V, BoolVars);
-is_safe_bool_expr_1(_, _, _) -> false.
+is_safe_bool_expr_1(_, _) -> false.
-is_safe_bool_expr_list([C|Cs], Sub, BoolVars) ->
- case is_safe_bool_expr_1(C, Sub, BoolVars) of
- true -> is_safe_bool_expr_list(Cs, Sub, BoolVars);
+is_safe_bool_expr_list([C|Cs], BoolVars) ->
+ case is_safe_bool_expr_1(C, BoolVars) of
+ true -> is_safe_bool_expr_list(Cs, BoolVars);
false -> false
end;
-is_safe_bool_expr_list([], _, _) -> true.
+is_safe_bool_expr_list([], _) -> true.
%% simplify_let(Let, Sub) -> Expr | impossible
%% If the argument part of an let contains a complex expression, such
@@ -2785,7 +2639,7 @@ opt_simple_let_2(Let0, Vs0, Arg0, Body, PrevBody, Sub) ->
%% with exported variables, but the return value is
%% ignored). We can remove the first variable and the
%% the first value returned from the 'let' argument.
- Arg2 = remove_first_value(Arg1, Sub),
+ Arg2 = remove_first_value(Arg1),
Let1 = Let0#c_let{vars=Vars,arg=Arg2,body=Body},
post_opt_let(Let1, Sub);
true ->
@@ -2805,36 +2659,36 @@ post_opt_let(Let0, Sub) ->
opt_build_stacktrace(Let1).
-%% remove_first_value(Core0, Sub) -> Core.
+%% remove_first_value(Core0) -> Core.
%% Core0 is an expression that returns at least two values.
%% Remove the first value returned from Core0.
-remove_first_value(#c_values{es=[V|Vs]}, Sub) ->
+remove_first_value(#c_values{es=[V|Vs]}) ->
Values = core_lib:make_values(Vs),
- case is_safe_simple(V, Sub) of
+ case is_safe_simple(V) of
false ->
#c_seq{arg=V,body=Values};
true ->
Values
end;
-remove_first_value(#c_case{clauses=Cs0}=Core, Sub) ->
- Cs = remove_first_value_cs(Cs0, Sub),
+remove_first_value(#c_case{clauses=Cs0}=Core) ->
+ Cs = remove_first_value_cs(Cs0),
Core#c_case{clauses=Cs};
-remove_first_value(#c_receive{clauses=Cs0,action=Act0}=Core, Sub) ->
- Cs = remove_first_value_cs(Cs0, Sub),
- Act = remove_first_value(Act0, Sub),
+remove_first_value(#c_receive{clauses=Cs0,action=Act0}=Core) ->
+ Cs = remove_first_value_cs(Cs0),
+ Act = remove_first_value(Act0),
Core#c_receive{clauses=Cs,action=Act};
-remove_first_value(#c_let{body=B}=Core, Sub) ->
- Core#c_let{body=remove_first_value(B, Sub)};
-remove_first_value(#c_seq{body=B}=Core, Sub) ->
- Core#c_seq{body=remove_first_value(B, Sub)};
-remove_first_value(#c_primop{}=Core, _Sub) ->
+remove_first_value(#c_let{body=B}=Core) ->
+ Core#c_let{body=remove_first_value(B)};
+remove_first_value(#c_seq{body=B}=Core) ->
+ Core#c_seq{body=remove_first_value(B)};
+remove_first_value(#c_primop{}=Core) ->
Core;
-remove_first_value(#c_call{}=Core, _Sub) ->
+remove_first_value(#c_call{}=Core) ->
Core.
-remove_first_value_cs(Cs, Sub) ->
- [C#c_clause{body=remove_first_value(B, Sub)} ||
+remove_first_value_cs(Cs) ->
+ [C#c_clause{body=remove_first_value(B)} ||
#c_clause{body=B}=C <- Cs].
%% maybe_suppress_warnings(Arg, #c_var{}, PreviousBody) -> Arg'
@@ -2962,54 +2816,6 @@ move_case_into_arg(Expr, _) ->
Expr.
%%%
-%%% Retrieving information about types.
-%%%
-
--spec get_type(cerl:cerl(), #sub{}) -> type_info() | 'none'.
-
-get_type(#c_var{name=V}, #sub{t=Tdb}) ->
- case Tdb of
- #{V:=Type} -> Type;
- _ -> none
- end;
-get_type(C, _) ->
- case cerl:type(C) of
- binary -> C;
- map -> C;
- _ ->
- case cerl:is_data(C) of
- true -> C;
- false -> none
- end
- end.
-
--spec is_boolean_type(cerl:cerl(), sub()) -> yes_no_maybe().
-
-is_boolean_type(Var, Sub) ->
- case get_type(Var, Sub) of
- none ->
- maybe;
- bool ->
- yes;
- C ->
- B = cerl:is_c_atom(C) andalso
- is_boolean(cerl:atom_val(C)),
- yes_no(B)
- end.
-
--spec is_int_type(cerl:cerl(), sub()) -> yes_no_maybe().
-
-is_int_type(Var, Sub) ->
- case get_type(Var, Sub) of
- none -> maybe;
- integer -> yes;
- C -> yes_no(cerl:is_c_int(C))
- end.
-
-yes_no(true) -> yes;
-yes_no(false) -> no.
-
-%%%
%%% Update type information.
%%%
@@ -3020,70 +2826,14 @@ update_let_types(_Vs, _Arg, Sub) ->
%% that returns multiple values.
Sub.
-update_let_types_1([#c_var{}=V|Vs], [A|As], Sub0) ->
- Sub = update_types_from_expr(V, A, Sub0),
+update_let_types_1([#c_var{name=V}|Vs], [A|As], Sub0) ->
+ Sub = update_types(V, A, Sub0),
update_let_types_1(Vs, As, Sub);
update_let_types_1([], [], Sub) -> Sub.
-update_types_from_expr(V, Expr, Sub) ->
- Type = extract_type(Expr, Sub),
- update_types(V, [Type], Sub).
-
-extract_type(#c_call{module=#c_literal{val=erlang},
- name=#c_literal{val=Name},
- args=Args}=Call, Sub) ->
- case returns_integer(Name, Args) of
- true -> integer;
- false -> extract_type_1(Call, Sub)
- end;
-extract_type(Expr, Sub) ->
- extract_type_1(Expr, Sub).
-
-extract_type_1(Expr, Sub) ->
- case is_bool_expr(Expr, Sub) of
- false -> Expr;
- true -> bool
- end.
-
-returns_integer('band', [_,_]) -> true;
-returns_integer('bnot', [_]) -> true;
-returns_integer('bor', [_,_]) -> true;
-returns_integer('bxor', [_,_]) -> true;
-returns_integer(bit_size, [_]) -> true;
-returns_integer('bsl', [_,_]) -> true;
-returns_integer('bsr', [_,_]) -> true;
-returns_integer(byte_size, [_]) -> true;
-returns_integer(ceil, [_]) -> true;
-returns_integer('div', [_,_]) -> true;
-returns_integer(floor, [_]) -> true;
-returns_integer(length, [_]) -> true;
-returns_integer('rem', [_,_]) -> true;
-returns_integer('round', [_]) -> true;
-returns_integer(size, [_]) -> true;
-returns_integer(tuple_size, [_]) -> true;
-returns_integer(trunc, [_]) -> true;
-returns_integer(_, _) -> false.
-
-%% update_types(Expr, Pattern, Sub) -> Sub'
-%% Update the type database.
-
--spec update_types(cerl:c_var(), [type_info()], sub()) -> sub().
-
-update_types(#c_var{name=V}, Pat, #sub{t=Tdb0}=Sub) ->
- Tdb = update_types_1(V, Pat, Tdb0),
- Sub#sub{t=Tdb}.
-
-update_types_1(V, [#c_tuple{}=P], Types) ->
- Types#{V=>P};
-update_types_1(V, [#c_literal{val=Bool}], Types) when is_boolean(Bool) ->
- Types#{V=>bool};
-update_types_1(V, [#c_fun{vars=Vars}], Types) ->
- Types#{V=>{'fun',length(Vars)}};
-update_types_1(V, [#c_var{name={_,Arity}}], Types) ->
- Types#{V=>{'fun',Arity}};
-update_types_1(V, [Type], Types) when is_atom(Type) ->
- Types#{V=>Type};
-update_types_1(_, _, Types) -> Types.
+update_types(V, #c_tuple{}=P, #sub{t=Tdb}=Sub) ->
+ Sub#sub{t=Tdb#{V=>P}};
+update_types(_, _, Sub) -> Sub.
%% kill_types(V, Tdb) -> Tdb'
%% Kill any entries that references the variable,
@@ -3099,10 +2849,6 @@ kill_types2(V, [{_,#c_tuple{}=Tuple}=Entry|Tdb]) ->
false -> [Entry|kill_types2(V, Tdb)];
true -> kill_types2(V, Tdb)
end;
-kill_types2(V, [{_, {'fun',_}}=Entry|Tdb]) ->
- [Entry|kill_types2(V, Tdb)];
-kill_types2(V, [{_,Atom}=Entry|Tdb]) when is_atom(Atom) ->
- [Entry|kill_types2(V, Tdb)];
kill_types2(_, []) -> [].
%% copy_type(DestVar, SrcVar, Tdb) -> Tdb'
diff --git a/lib/compiler/src/v3_kernel.erl b/lib/compiler/src/v3_kernel.erl
index e2b8787224..6fd1790c1a 100644
--- a/lib/compiler/src/v3_kernel.erl
+++ b/lib/compiler/src/v3_kernel.erl
@@ -81,8 +81,11 @@
-export([module/2,format_error/1]).
--import(lists, [map/2,foldl/3,foldr/3,mapfoldl/3,splitwith/2,member/2,
- keyfind/3,partition/2,droplast/1,last/1,sort/1,reverse/1]).
+-import(lists, [droplast/1,flatten/1,foldl/3,foldr/3,
+ map/2,mapfoldl/3,member/2,
+ keyfind/3,keyreplace/4,
+ last/1,partition/2,reverse/1,
+ splitwith/2,sort/1]).
-import(ordsets, [add_element/2,del_element/2,union/2,union/1,subtract/2]).
-import(cerl, [c_tuple/1]).
@@ -120,15 +123,19 @@ copy_anno(Kdst, Ksrc) ->
funs=[], %Fun functions
free=#{}, %Free variables
ws=[] :: [warning()], %Warnings.
- guard_refc=0}). %> 0 means in guard
+ guard_refc=0, %> 0 means in guard
+ no_shared_fun_wrappers=false :: boolean()
+ }).
-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) ->
+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),
- St0 = #kern{},
+ NoSharedFunWrappers = proplists:get_bool(no_shared_fun_wrappers,
+ Options),
+ St0 = #kern{no_shared_fun_wrappers=NoSharedFunWrappers},
{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},lists:sort(St#kern.ws)}.
@@ -716,16 +723,27 @@ gexpr_test_add(Ke, St0) ->
%% expr(Cexpr, Sub, State) -> {Kexpr,[PreKexpr],State}.
%% Convert a Core expression, flattening it at the same time.
-expr(#c_var{anno=A,name={_Name,Arity}}=Fname, Sub, St) ->
- %% A local in an expression.
- %% For now, these are wrapped into a fun by reverse
- %% eta-conversion, but really, there should be exactly one
- %% such "lambda function" for each escaping local name,
- %% instead of one for each occurrence as done now.
+expr(#c_var{anno=A0,name={Name,Arity}}=Fname, Sub, St) ->
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);
+ V <- integers(1, Arity)],
+ case St#kern.no_shared_fun_wrappers of
+ false ->
+ %% Generate a (possibly shared) wrapper function for calling
+ %% this function.
+ Wrapper0 = ["-fun.",atom_to_list(Name),"/",integer_to_list(Arity),"-"],
+ Wrapper = list_to_atom(flatten(Wrapper0)),
+ Id = {id,{0,0,Wrapper}},
+ A = keyreplace(id, 1, A0, Id),
+ Fun = #c_fun{anno=A,vars=Vs,body=#c_apply{anno=A,op=Fname,args=Vs}},
+ expr(Fun, Sub, 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`.
+ Fun = #c_fun{anno=A0,vars=Vs,body=#c_apply{anno=A0,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) ->
@@ -2446,8 +2464,21 @@ uexpr(Lit, {break,Rs0}, St0) ->
{#k_put{anno=#k{us=Used,ns=lit_list_vars(Rs),a=get_kanno(Lit)},
arg=Lit,ret=Rs},Used,St1}.
-add_local_function(_, #kern{funs=ignore}=St) -> St;
-add_local_function(F, #kern{funs=Funs}=St) -> St#kern{funs=[F|Funs]}.
+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) ->