%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2004-2022. 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_validator).

-include("beam_asm.hrl").

-define(UNICODE_MAX, (16#10FFFF)).

%% When an instruction throws an exception it does so by branching (branch/4)
%% to this label, following the {f,0} convention used in BIFs.
%%
%% Instructions that have fail labels but cannot throw exceptions should guard
%% themselves with assert_no_exception/1, and instructions that are statically
%% guaranteed not to fail should simply skip branching.
-define(EXCEPTION_LABEL, 0).

-compile({no_auto_import,[min/2]}).

%% Avoid warning for local function error/1 clashing with autoimported BIF.
-compile({no_auto_import,[error/1]}).

%% Interface for compiler.
-export([validate/2, format_error/1]).

-import(lists, [dropwhile/2,foldl/3,member/2,reverse/2,zip/2]).

%% To be called by the compiler.

-spec validate(Code, Level) -> Result when
      Code :: beam_utils:module_code(),
      Level :: strong | weak,
      Result :: ok | {error, [{atom(), list()}]}.

validate({Mod,Exp,Attr,Fs,Lc}, Level) when is_atom(Mod),
                                           is_list(Exp),
                                           is_list(Attr),
                                           is_integer(Lc) ->
    Ft = build_function_table(Fs, #{}),
    case validate_0(Fs, Mod, Level, Ft) of
        [] ->
            ok;
        Es0 ->
            Es = [{1,?MODULE,E} || E <- Es0],
            {error,[{atom_to_list(Mod),Es}]}
    end.

-spec format_error(term()) -> iolist().

format_error({{_M,F,A},{I,Off,limit}}) ->
    io_lib:format(
      "function ~p/~p+~p:~n"
      "  An implementation limit was reached.~n"
      "  Try reducing the complexity of this function.~n~n"
      "  Instruction: ~p~n", [F,A,Off,I]);
format_error({{_M,F,A},{undef_labels,Lbls}}) ->
    io_lib:format(
      "function ~p/~p:~n"
      "  Internal consistency check failed - please report this bug.~n"
      "  The following label(s) were referenced but not defined:~n", [F,A]) ++
	"  " ++ [[integer_to_list(L)," "] || L <- Lbls] ++ "\n";
format_error({{_M,F,A},{I,Off,Desc}}) ->
    io_lib:format(
      "function ~p/~p+~p:~n"
      "  Internal consistency check failed - please report this bug.~n"
      "  Instruction: ~p~n"
      "  Error:       ~p:~n", [F,A,Off,I,Desc]);
format_error(Error) ->
    io_lib:format("~p~n", [Error]).

%%%
%%% Local functions follow.
%%% 

%%%
%%% The validator follows.
%%%
%%% The purpose of the validator is to find errors in the generated
%%% code that may cause the emulator to crash or behave strangely.
%%% We don't care about type errors in the user's code that will
%%% cause a proper exception at run-time.
%%%

%%% Things currently not checked. XXX
%%%
%%% - Heap allocation for binaries.
%%%

%% validate(Module, Level, [Function], Ft) -> [] | [Error]
%%  A list of functions with their code. The code is in the same
%%  format as used in the compiler and in .S files.


validate_0([], _Module, _Level, _Ft) ->
    [];
validate_0([{function, Name, Arity, Entry, Code} | Fs], Module, Level, Ft) ->
    MFA = {Module, Name, Arity},
    try validate_1(Code, MFA, Entry, Level, Ft) of
        _ ->
            validate_0(Fs, Module, Level, Ft)
    catch
        throw:Error ->
            %% Controlled error.
            [Error | validate_0(Fs, Module, Level, Ft)];
        Class:Error:Stack ->
            %% Crash.
            io:fwrite("Function: ~w/~w\n", [Name,Arity]),
            erlang:raise(Class, Error, Stack)
    end.

-record(t_abstract, {kind}).

%% The types are the same as in 'beam_types.hrl', with the addition of:
%%
%% * `#t_abstract{}` which describes tuples under construction, match context
%%   positions, and so on.
%% * `(fun(values()) -> type())` that defers figuring out the type until it's
%%   actually used. Mainly used to coax more type information out of
%%   `get_tuple_element` where a test on one element (e.g. record tag) may
%%   affect the type of another.
-type validator_type() :: #t_abstract{} | fun((values()) -> type()) | type().

-record(value_ref, {id :: index()}).
-record(value, {op :: term(), args :: [argument()], type :: validator_type()}).

-type argument() :: #value_ref{} | beam_literal().
-type values() :: #{ #value_ref{} => #value{} }.

-type index() :: non_neg_integer().

%% Register tags describe the state of the register rather than the value they
%% contain (if any).
%%
%% initialized          The 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).
%%
%% uninitialized        The register contains any old garbage and can not be
%%                      passed to the garbage collector.
%%
%% {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.
-type tag() :: initialized |
               uninitialized |
               {catchtag, ordsets:ordset(label())} |
               {trytag, ordsets:ordset(label())}.

-type x_regs() :: #{ {x, index()} => #value_ref{} }.
-type y_regs() :: #{ {y, index()} => tag() | #value_ref{} }.

%% Emulation state
-record(st,
        {%% All known values.
         vs=#{} :: values(),
         %% Register states.
         xs=#{} :: x_regs(),
         ys=#{} :: y_regs(),
         f=init_fregs(),
         %% A set of all registers containing "fragile" terms. That is, terms
         %% that don't exist on our process heap and would be destroyed by a
         %% GC.
         fragile=sets:new([{version, 2}]) :: sets:set(),
         %% Number of Y registers.
         %%
         %% Note that this may be 0 if there's a frame without saved values,
         %% such as on a body-recursive call.
         numy=none :: none | undecided | index(),
         %% Available heap size.
         h=0,
         %% Available heap size for funs (aka lambdas).
         hl=0,
         %%Available heap size for floats.
         hf=0,
         %% List of hot catch/try tags
         ct=[],
         %% Previous instruction was setelement/3.
         setelem=false,
         %% put/1 instructions left.
         puts_left=none,
         %% Current receive state:
         %%
         %%   * 'none'            - Not in a receive loop.
         %%   * 'marked_position' - We've used a marker prior to loop_rec.
         %%   * 'entered_loop'    - We're in a receive loop.
         %%   * 'undecided'
         recv_state=none :: none | undecided | marked_position | entered_loop,
         %% Holds the current saved position for each `#t_bs_context{}`, in the
         %% sense that the position is equal to that of their context. They are
         %% invalidated whenever their context advances.
         %%
         %% These are used to update the unit of saved positions after
         %% operations that test the incoming unit, such as bs_test_unit and
         %% bs_get_binary2 with {atom,all}.
         ms_positions=#{} :: #{ Ctx :: #value_ref{} => Pos :: #value_ref{} }
        }).

-type label()        :: integer().
-type state()        :: #st{} | 'none'.

%% Validator state
-record(vst,
        {%% Current state
         current=none              :: state(),
         %% Validation level
         level                     :: strong | weak,
         %% States at labels
         branched=#{}              :: #{ label() => state() },
         %% All defined labels
         labels=sets:new([{version, 2}])    :: sets:set(),
         %% Information of other functions in the module
         ft=#{}                    :: #{ label() => map() },
         %% Counter for #value_ref{} creation
         ref_ctr=0                 :: index()
        }).

build_function_table([{function,Name,Arity,Entry,Code0}|Fs], Acc) ->
    Code = dropwhile(fun({label,L}) when L =:= Entry ->
                             false;
                        (_) ->
                             true
                     end, Code0),
    case Code of
        [{label,Entry} | Is] ->
            Info = #{ name => Name,
                      arity => Arity,
                      parameter_info => find_parameter_info(Is, #{}),
                      always_fails => always_fails(Is) },
            build_function_table(Fs, Acc#{ Entry => Info });
        _ ->
            %% Something is seriously wrong. Ignore it for now.
            %% It will be detected and diagnosed later.
            build_function_table(Fs, Acc)
    end;
build_function_table([], Acc) ->
    Acc.

find_parameter_info([{'%', {var_info, Reg, Info}} | Is], Acc) ->
    find_parameter_info(Is, Acc#{ Reg => Info });
find_parameter_info([{'%', _} | Is], Acc) ->
    find_parameter_info(Is, Acc);
find_parameter_info(_, Acc) ->
    Acc.

always_fails([{jump,_}|_]) -> true;
always_fails(_) -> false.

validate_1(Is, MFA0, Entry, Level, Ft) ->
    {Offset, MFA, Header, Body} = extract_header(Is, MFA0, Entry, 1, []),

    Vst0 = init_vst(MFA, Level, Ft),

    %% We validate the header after the body as the latter may jump to the
    %% former to raise 'function_clause' exceptions.
    Vst1 = validate_instrs(Body, MFA, Offset, Vst0),
    Vst = validate_instrs(Header, MFA, 1, Vst1),

    validate_branches(MFA, Vst).

extract_header([{func_info, {atom,Mod}, {atom,Name}, Arity}=I | Is],
               MFA0, Entry, Offset, Acc) ->
    {_, Name, Arity} = MFA0,                    %Assertion.
    MFA = {Mod, Name, Arity},

    case Is of
        [{label, Entry} | _] ->
            Header = reverse(Acc, [I]),
            {Offset + 1, MFA, Header, Is};
        _ ->
            error({MFA, no_entry_label})
    end;
extract_header([{label,_}=I | Is], MFA, Entry, Offset, Acc) ->
    extract_header(Is, MFA, Entry, Offset + 1, [I | Acc]);
extract_header([{line,_}=I | Is], MFA, Entry, Offset, Acc) ->
    extract_header(Is, MFA, Entry, Offset + 1, [I | Acc]);
extract_header(_Is, MFA, _Entry, _Offset, _Acc) ->
    error({MFA, invalid_function_header}).

init_vst({_, _, Arity}, Level, Ft) ->
    Vst = #vst{branched=#{},
               current=#st{},
               ft=Ft,
               labels=sets:new([{version, 2}]),
               level=Level},
    init_function_args(Arity - 1, Vst).

init_function_args(-1, Vst) ->
    Vst;
init_function_args(X, Vst) ->
    init_function_args(X - 1, create_term(any, argument, [], {x,X}, Vst)).

kill_heap_allocation(#vst{current=St0}=Vst) ->
    St = St0#st{h=0,hl=0,hf=0},
    Vst#vst{current=St}.

validate_branches(MFA, Vst) ->
    #vst{ branched=Targets0, labels=Labels0 } = Vst,
    Targets = maps:keys(Targets0),
    Labels = sets:to_list(Labels0),
    case Targets -- Labels of
        [_|_]=Undef ->
            Error = {undef_labels, Undef},
            error({MFA, Error});
        [] ->
            Vst
    end.

validate_instrs([I|Is], MFA, Offset, Vst0) ->
    validate_instrs(Is, MFA, Offset+1,
                    try
                        Vst = validate_mutation(I, Vst0),
                        vi(I, Vst)
                    catch Error ->
                        error({MFA, {I, Offset, Error}})
                    end);
validate_instrs([], _MFA, _Offset, Vst) ->
    Vst.

vi({label,Lbl}, #vst{current=St0,
                          ref_ctr=Counter0,
                          branched=Branched0,
                          labels=Labels0}=Vst) ->
    {St, Counter} = merge_states(Lbl, St0, Branched0, Counter0),

    Branched = Branched0#{ Lbl => St },
    Labels = sets:add_element(Lbl, Labels0),

    Vst#vst{current=St,
            ref_ctr=Counter,
            branched=Branched,
            labels=Labels};
vi(_I, #vst{current=none}=Vst) ->
    %% Ignore all unreachable code.
    Vst;

%%
%% Misc annotations
%%

vi({'%', {var_info, Reg, Info}}, Vst) ->
    validate_var_info(Info, Reg, Vst);
vi({'%', {remove_fragility, Reg}}, Vst) ->
    %% This is a hack to make prim_eval:'receive'/2 work.
    %%
    %% Normally it's illegal to pass fragile terms as a function argument as we
    %% have no way of knowing what the callee will do with it, but we know that
    %% prim_eval:'receive'/2 won't leak the term, nor cause a GC since it's
    %% disabled while matching messages.
    remove_fragility(Reg, Vst);
vi({'%',_}, Vst) ->
    Vst;
vi({line,_}, Vst) ->
    Vst;
vi(nif_start, Vst) ->
    Vst;
%%
%% Moves
%%

vi({move,Src,Dst}, Vst) ->
    assign(Src, Dst, Vst);
vi({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);
vi({fmove,Src,{fr,_}=Dst}, Vst) ->
    assert_type(#t_float{}, Src, Vst),
    set_freg(Dst, Vst);
vi({fmove,{fr,_}=Src,Dst}, Vst0) ->
    assert_freg_set(Src, Vst0),
    Vst = eat_heap_float(Vst0),
    create_term(#t_float{}, fmove, [], Dst, Vst);
vi({kill,Reg}, Vst) ->
    create_tag(initialized, kill, [], Reg, Vst);
vi({init,Reg}, Vst) ->
    create_tag(initialized, init, [], Reg, Vst);
vi({init_yregs,{list,Yregs}}, Vst0) ->
    case ordsets:from_list(Yregs) of
        [] -> error(empty_list);
        Yregs -> ok;
        _ -> error(not_ordset)
    end,
    foldl(fun(Y, Vst) ->
                  create_tag(initialized, init, [], Y, Vst)
          end, Vst0, Yregs);

%%
%% Matching and test instructions.
%%

vi({jump,{f,Lbl}}, Vst) ->
    assert_no_exception(Lbl),

    %% The next instruction is never executed.
    branch(Lbl, Vst, fun kill_state/1);
vi({select_val,Src,{f,Fail},{list,Choices}}, Vst) ->
    assert_term(Src, Vst),
    assert_choices(Choices),
    validate_select_val(Fail, Choices, Src, Vst);
vi({select_tuple_arity,Tuple0,{f,Fail},{list,Choices}}, Vst) ->
    Tuple = unpack_typed_arg(Tuple0),

    assert_type(#t_tuple{}, Tuple, Vst),
    assert_arities(Choices),
    validate_select_tuple_arity(Fail, Choices, Tuple, Vst);
vi({test,has_map_fields,{f,Lbl},Src,{list,List}}, Vst) ->
    verify_has_map_fields(Lbl, Src, List, Vst);
vi({test,is_atom,{f,Lbl},[Src]}, Vst) ->
    type_test(Lbl, #t_atom{}, Src, Vst);
vi({test,is_binary,{f,Lbl},[Src]}, Vst) ->
    type_test(Lbl, #t_bitstring{size_unit=8}, Src, Vst);
vi({test,is_bitstr,{f,Lbl},[Src]}, Vst) ->
    type_test(Lbl, #t_bitstring{}, Src, Vst);
vi({test,is_boolean,{f,Lbl},[Src]}, Vst) ->
    type_test(Lbl, beam_types:make_boolean(), Src, Vst);
vi({test,is_float,{f,Lbl},[Src]}, Vst) ->
    type_test(Lbl, #t_float{}, Src, Vst);
vi({test,is_function,{f,Lbl},[Src]}, Vst) ->
    type_test(Lbl, #t_fun{}, Src, Vst);
vi({test,is_function2,{f,Lbl},[Src,{integer,Arity}]}, Vst)
  when Arity >= 0, Arity =< ?MAX_FUNC_ARGS ->
    type_test(Lbl, #t_fun{arity=Arity}, Src, Vst);
vi({test,is_function2,{f,Lbl},[Src0,_Arity]}, Vst) ->
    Src = unpack_typed_arg(Src0),
    assert_term(Src, Vst),
    branch(Lbl, Vst,
           fun(FailVst) ->
                   %% We cannot subtract the function type when the arity is
                   %% unknown: `Src` may still be a function if the arity is
                   %% outside the allowed range.
                   FailVst
           end,
           fun(SuccVst) ->
                   update_type(fun meet/2, #t_fun{}, Src, SuccVst)
           end);
vi({test,is_tuple,{f,Lbl},[Src]}, Vst) ->
    type_test(Lbl, #t_tuple{}, Src, Vst);
vi({test,is_integer,{f,Lbl},[Src]}, Vst) ->
    type_test(Lbl, #t_integer{}, Src, Vst);
vi({test,is_nonempty_list,{f,Lbl},[Src]}, Vst) ->
    type_test(Lbl, #t_cons{}, Src, Vst);
vi({test,is_number,{f,Lbl},[Src]}, Vst) ->
    type_test(Lbl, number, Src, Vst);
vi({test,is_list,{f,Lbl},[Src]}, Vst) ->
    type_test(Lbl, #t_list{}, Src, Vst);
vi({test,is_map,{f,Lbl},[Src]}, Vst) ->
    type_test(Lbl, #t_map{}, Src, Vst);
vi({test,is_nil,{f,Lbl},[Src0]}, Vst) ->
    %% is_nil is an exact check against the 'nil' value, and should not be
    %% treated as a simple type test.
    Src = unpack_typed_arg(Src0),
    assert_term(Src, Vst),
    branch(Lbl, Vst,
           fun(FailVst) ->
                   update_ne_types(Src, nil, FailVst)
           end,
           fun(SuccVst) ->
                   update_eq_types(Src, nil, SuccVst)
           end);
vi({test,is_pid,{f,Lbl},[Src]}, Vst) ->
    type_test(Lbl, pid, Src, Vst);
vi({test,is_port,{f,Lbl},[Src]}, Vst) ->
    type_test(Lbl, port, Src, Vst);
vi({test,is_reference,{f,Lbl},[Src]}, Vst) ->
    type_test(Lbl, reference, Src, Vst);
vi({test,test_arity,{f,Lbl},[Tuple,Sz]}, Vst) when is_integer(Sz) ->
    assert_type(#t_tuple{}, Tuple, Vst),
    Type =  #t_tuple{exact=true,size=Sz},
    type_test(Lbl, Type, Tuple, Vst);
vi({test,is_tagged_tuple,{f,Lbl},[Src0,Sz,Atom]}, Vst) ->
    Src = unpack_typed_arg(Src0),
    assert_term(Src, Vst),
    Es = #{ 1 => get_literal_type(Atom) },
    Type = #t_tuple{exact=true,size=Sz,elements=Es},
    type_test(Lbl, Type, Src, Vst);
vi({test,is_eq_exact,{f,Lbl},[Src0,Val0]}, Vst) ->
    assert_no_exception(Lbl),
    Src = unpack_typed_arg(Src0),
    assert_term(Src, Vst),
    Val = unpack_typed_arg(Val0),
    assert_term(Val, Vst),
    branch(Lbl, Vst,
           fun(FailVst) ->
                   update_ne_types(Src, Val, FailVst)
           end,
           fun(SuccVst) ->
                   update_eq_types(Src, Val, SuccVst)
           end);
vi({test,is_ne_exact,{f,Lbl},[Src0,Val0]}, Vst) ->
    assert_no_exception(Lbl),
    Src = unpack_typed_arg(Src0),
    assert_term(Src, Vst),
    Val = unpack_typed_arg(Val0),
    assert_term(Val, Vst),
    branch(Lbl, Vst,
           fun(FailVst) ->
                   update_eq_types(Src, Val, FailVst)
           end,
           fun(SuccVst) ->
                   update_ne_types(Src, Val, SuccVst)
           end);


%%
%% Simple getters that can't fail.
%%

vi({get_list,Src,D1,D2}, Vst0) ->
    assert_not_literal(Src),
    assert_type(#t_cons{}, Src, Vst0),

    SrcType = get_term_type(Src, Vst0),
    {HeadType, _, _} = beam_call_types:types(erlang, hd, [SrcType]),
    {TailType, _, _} = beam_call_types:types(erlang, tl, [SrcType]),

    Vst = extract_term(HeadType, get_hd, [Src], D1, Vst0),
    extract_term(TailType, get_tl, [Src], D2, Vst, Vst0);
vi({get_hd,Src,Dst}, Vst) ->
    assert_not_literal(Src),
    assert_type(#t_cons{}, Src, Vst),

    SrcType = get_term_type(Src, Vst),
    {HeadType, _, _} = beam_call_types:types(erlang, hd, [SrcType]),

    extract_term(HeadType, get_hd, [Src], Dst, Vst);
vi({get_tl,Src,Dst}, Vst) ->
    assert_not_literal(Src),
    assert_type(#t_cons{}, Src, Vst),

    SrcType = get_term_type(Src, Vst),
    {TailType, _, _} = beam_call_types:types(erlang, tl, [SrcType]),

    extract_term(TailType, get_tl, [Src], Dst, Vst);
vi({get_tuple_element,Src,N,Dst}, Vst) ->
    Index = N+1,
    assert_not_literal(Src),
    assert_type(#t_tuple{size=Index}, Src, Vst),

    VRef = get_reg_vref(Src, Vst),
    Type = fun(Vs) ->
                    #value{type=T} = map_get(VRef, Vs),
                    #t_tuple{elements=Es} = normalize(concrete_type(T, Vs)),
                    beam_types:get_tuple_element(Index, Es)
           end,

    extract_term(Type, {bif,element}, [{integer,Index}, Src], Dst, Vst);

%%
%% Allocate, deallocate, et.al.
%%

vi({test_heap,Heap,Live}, Vst) ->
    test_heap(Heap, Live, Vst);
vi({allocate,Stk,Live}, Vst) ->
    allocate(uninitialized, Stk, 0, Live, Vst);
vi({allocate_heap,Stk,Heap,Live}, Vst) ->
    allocate(uninitialized, Stk, Heap, Live, Vst);
vi({allocate_zero,Stk,Live}, Vst) ->
    allocate(initialized, Stk, 0, Live, Vst);
vi({allocate_heap_zero,Stk,Heap,Live}, Vst) ->
    allocate(initialized, Stk, Heap, Live, Vst);
vi({deallocate,StkSize}, #vst{current=#st{numy=StkSize}}=Vst) ->
    verify_no_ct(Vst),
    deallocate(Vst);
vi({deallocate,_}, #vst{current=#st{numy=NumY}}) ->
    error({allocated,NumY});
vi({trim,N,Remaining}, #vst{current=St0}=Vst) ->
    #st{numy=NumY} = St0,
    if
        N =< NumY, N+Remaining =:= NumY ->
            Vst#vst{current=trim_stack(N, 0, NumY, St0)};
        N > NumY; N+Remaining =/= NumY ->
            error({trim,N,Remaining,allocated,NumY})
    end;

%%
%% Term-building instructions
%%

vi({put_list,A,B,Dst}, Vst0) ->
    Vst = eat_heap(2, Vst0),

    Head = get_term_type(A, Vst),
    Tail = get_term_type(B, Vst),

    create_term(beam_types:make_cons(Head, Tail), put_list, [A, B], Dst, Vst);
vi({put_tuple2,Dst,{list,Elements}}, Vst0) ->
    _ = [assert_term(El, Vst0) || El <- Elements],
    Size = length(Elements),
    Vst = eat_heap(Size+1, Vst0),
    {Es,_} = foldl(fun(Val, {Es0, Index}) ->
                       Type = get_term_type(Val, Vst0),
                       Es = beam_types:set_tuple_element(Index, Type, Es0),
                       {Es, Index + 1}
                   end, {#{}, 1}, Elements),
    Type = #t_tuple{exact=true,size=Size,elements=Es},
    create_term(Type, put_tuple2, [], Dst, Vst);
vi({put_tuple,Sz,Dst}, Vst0) when is_integer(Sz) ->
    Vst1 = eat_heap(1, Vst0),
    Vst = create_term(#t_abstract{kind=unfinished_tuple}, put_tuple, [],
                      Dst, Vst1),
    #vst{current=St0} = Vst,
    St = St0#st{puts_left={Sz,{Dst,Sz,#{}}}},
    Vst#vst{current=St};
vi({put,Src}, Vst0) ->
    assert_term(Src, Vst0),
    Vst = eat_heap(1, Vst0),
    #vst{current=St0} = Vst,
    case St0 of
        #st{puts_left=none} ->
            error(not_building_a_tuple);
        #st{puts_left={1,{Dst,Sz,Es0}}} ->
            ElementType = get_term_type(Src, Vst0),
            Es = beam_types:set_tuple_element(Sz, ElementType, Es0),
            St = St0#st{puts_left=none},
            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,
            ElementType = get_term_type(Src, Vst0),
            Es = beam_types:set_tuple_element(Index, ElementType, Es0),
            St = St0#st{puts_left={PutsLeft-1,{Dst,Sz,Es}}},
            Vst#vst{current=St}
    end;
vi({set_tuple_element,Src,Tuple,N}, Vst) ->
    %% This instruction never fails, though it may be invalid in some contexts;
    %% see validate_mutation/2
    I = N + 1,
    assert_term(Src, 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.
    #t_tuple{elements=Es0}=Type = normalize(get_term_type(Tuple, Vst)),
    Es = beam_types:set_tuple_element(I, get_term_type(Src, Vst), Es0),
    override_type(Type#t_tuple{elements=Es}, Tuple, Vst);

%%
%% Calls
%%

vi({apply,Live}, Vst) ->
    validate_body_call(apply, Live+2, Vst);
vi({apply_last,Live,N}, Vst) ->
    validate_tail_call(N, apply, Live+2, Vst);
vi({call,Live,Func}, Vst) ->
    validate_body_call(Func, Live, Vst);
vi({call_ext,Live,Func}, Vst) ->
    validate_body_call(Func, Live, Vst);
vi({call_only,Live,Func}, Vst) ->
    validate_tail_call(none, Func, Live, Vst);
vi({call_ext_only,Live,Func}, Vst) ->
    validate_tail_call(none, Func, Live, Vst);
vi({call_last,Live,Func,N}, Vst) ->
    validate_tail_call(N, Func, Live, Vst);
vi({call_ext_last,Live,Func,N}, Vst) ->
    validate_tail_call(N, Func, Live, Vst);
vi({call_fun2,{f,Lbl},Live,Fun0}, #vst{ft=Ft}=Vst) ->
    %% Fun call with known target. Verify that the target exists, agrees with
    %% the type annotation for `Fun`, and that it has environment variables.
    %%
    %% Direct fun calls without environment variables must be expressed as
    %% local fun calls.
    #{ name := Name, arity := TotalArity } = map_get(Lbl, Ft),
    #tr{t=#t_fun{target={Name,TotalArity}},r=Fun} = Fun0, %Assertion.
    true = Live < TotalArity,                   %Assertion.
    assert_term(Fun, Vst),
    validate_body_call('fun', Live, Vst);
vi({call_fun2,Tag,Live,Fun0}, Vst) ->
    Fun = unpack_typed_arg(Fun0),
    assert_term(Fun, Vst),

    case Tag of
        {atom,safe} -> ok;
        {atom,unsafe} -> ok;
        _ -> error({invalid_tag,Tag})
    end,

    branch(?EXCEPTION_LABEL, Vst,
           fun(SuccVst0) ->
                   SuccVst = update_type(fun meet/2, #t_fun{arity=Live},
                                         Fun, SuccVst0),
                   validate_body_call('fun', Live, SuccVst)
           end);
vi({call_fun,Live}, Vst) ->
    Fun = {x,Live},
    assert_term(Fun, Vst),

    branch(?EXCEPTION_LABEL, Vst,
           fun(SuccVst0) ->
                   SuccVst = update_type(fun meet/2, #t_fun{arity=Live},
                                         Fun, SuccVst0),
                   validate_body_call('fun', Live+1, SuccVst)
           end);
vi({make_fun2,{f,Lbl},_,_,NumFree}, #vst{ft=Ft}=Vst0) ->
    #{ name := Name, arity := TotalArity } = map_get(Lbl, Ft),
    Arity = TotalArity - NumFree,

    true = Arity >= 0,                          %Assertion.

    Vst = prune_x_regs(NumFree, Vst0),
    verify_call_args(make_fun, NumFree, Vst),
    verify_y_init(Vst),

    Type = #t_fun{target={Name,TotalArity},arity=Arity},

    create_term(Type, make_fun, [], {x,0}, Vst);
vi({make_fun3,{f,Lbl},_,_,Dst,{list,Env}}, #vst{ft=Ft}=Vst0) ->
    _ = [assert_term(E, Vst0) || E <- Env],
    NumFree = length(Env),

    #{ name := Name, arity := TotalArity } = map_get(Lbl, Ft),
    Arity = TotalArity - NumFree,
    true = Arity >= 0,                          %Assertion.

    Vst = eat_heap_fun(Vst0),

    Type = #t_fun{target={Name,TotalArity},arity=Arity},

    create_term(Type, make_fun, [], Dst, Vst);
vi(return, Vst) ->
    assert_durable_term({x,0}, Vst),
    verify_return(Vst);

%%
%% BIF calls
%%

vi({bif,Op,{f,Fail},Ss0,Dst0}, Vst0) ->
    Ss = [unpack_typed_arg(Arg) || Arg <- Ss0],
    Dst = unpack_typed_arg(Dst0),

    case is_float_arith_bif(Op, Ss) of
        true ->
            ?EXCEPTION_LABEL = Fail,            %Assertion.
            validate_float_arith_bif(Ss, Dst, Vst0);
        false ->
            validate_src(Ss, Vst0),
            validate_bif(bif, Op, Fail, Ss, Dst, Vst0, Vst0)
    end;
vi({gc_bif,Op,{f,Fail},Live,Ss0,Dst0}, Vst0) ->
    Ss = [unpack_typed_arg(Arg) || Arg <- Ss0],
    Dst = unpack_typed_arg(Dst0),

    validate_src(Ss, Vst0),
    verify_live(Live, Vst0),
    verify_y_init(Vst0),

    %% Heap allocations and X registers are killed regardless of whether we
    %% fail or not, as we may fail after GC.
    Vst1 = kill_heap_allocation(Vst0),
    Vst = prune_x_regs(Live, Vst1),

    validate_bif(gc_bif, Op, Fail, Ss, Dst, Vst0, Vst);

%%
%% Message instructions
%%

vi(send, Vst) ->
    validate_body_call(send, 2, Vst);
vi({loop_rec,{f,Fail},Dst}, Vst0) ->
    assert_no_exception(Fail),

    Vst = update_receive_state(entered_loop, Vst0),

    branch(Fail, Vst,
           fun(SuccVst0) ->
                   %% This term may not be part of the root set until
                   %% remove_message/0 is executed. If control transfers to the
                   %% loop_rec_end/1 instruction, no part of this term must be
                   %% stored in a Y register.
                   {Ref, SuccVst} = new_value(any, loop_rec, [], SuccVst0),
                   mark_fragile(Dst, set_reg_vref(Ref, Dst, SuccVst))
           end);
vi({loop_rec_end,Lbl}, Vst) ->
    assert_no_exception(Lbl),
    verify_y_init(Vst),
    kill_state(Vst);
vi({recv_marker_reserve=Op, Dst}, Vst) ->
    create_term(#t_abstract{kind=receive_marker}, Op, [], Dst, Vst);
vi({recv_marker_bind, Marker, Ref}, Vst) ->
    assert_type(#t_abstract{kind=receive_marker}, Marker, Vst),
    assert_durable_term(Ref, Vst),
    assert_not_literal(Ref),
    Vst;
vi({recv_marker_clear, Ref}, Vst) ->
    assert_durable_term(Ref, Vst),
    assert_not_literal(Ref),
    Vst;
vi({recv_marker_use, Ref}, Vst) ->
    assert_durable_term(Ref, Vst),
    assert_not_literal(Ref),
    update_receive_state(marked_position, Vst);
vi(remove_message, Vst0) ->
    Vst = update_receive_state(none, Vst0),

    %% The message term is no longer fragile. It can be used
    %% without restrictions.
    remove_fragility(Vst);
vi(timeout, Vst0) ->
    Vst = update_receive_state(none, Vst0),
    prune_x_regs(0, Vst);
vi({wait,{f,Lbl}}, Vst) ->
    assert_no_exception(Lbl),
    verify_y_init(Vst),
    branch(Lbl, Vst, fun kill_state/1);
vi({wait_timeout,{f,Lbl},Src}, Vst0) ->
    assert_no_exception(Lbl),

    assert_term(Src, Vst0),
    verify_y_init(Vst0),

    Vst = branch(Lbl, schedule_out(0, Vst0)),
    branch(?EXCEPTION_LABEL, Vst);

%%
%% Catch & try.
%%
vi({'catch',Dst,{f,Fail}}, Vst) when Fail =/= none ->
    init_try_catch_branch(catchtag, Dst, Fail, Vst);
vi({'try',Dst,{f,Fail}}, Vst) when Fail =/= none ->
    init_try_catch_branch(trytag, Dst, Fail, Vst);
vi({catch_end,Reg}, #vst{current=#st{ct=[Tag|_]}}=Vst0) ->
    case get_tag_type(Reg, Vst0) of
        {catchtag,_Fail}=Tag ->
            Vst = kill_catch_tag(Reg, Vst0),

            %% {x,0} contains the caught term, if any.
            create_term(any, catch_end, [], {x,0}, Vst);
        Type ->
            error({wrong_tag_type,Type})
    end;
vi({try_end,Reg}, #vst{current=#st{ct=[Tag|_]}}=Vst) ->
    case get_tag_type(Reg, Vst) of
        {trytag,_Fail}=Tag ->
            %% Kill the catch tag without affecting X registers.
            kill_catch_tag(Reg, Vst);
        Type ->
            error({wrong_tag_type,Type})
    end;
vi({try_case,Reg}, #vst{current=#st{ct=[Tag|_]}}=Vst0) ->
    case get_tag_type(Reg, Vst0) of
        {trytag,_Fail}=Tag ->
            %% Kill the catch tag and all other state (as if we've been
            %% scheduled out with no live registers). Only previously allocated
            %% Y registers are alive at this point.
            Vst1 = kill_catch_tag(Reg, Vst0),
            Vst2 = schedule_out(0, Vst1),

            %% Class:Error:Stacktrace
            ClassType = #t_atom{elements=[error,exit,throw]},
            Vst3 = create_term(ClassType, try_case, [], {x,0}, Vst2),
            Vst = create_term(any, try_case, [], {x,1}, Vst3),
            create_term(any, try_case, [], {x,2}, Vst);
        Type ->
            error({wrong_tag_type,Type})
    end;
vi(build_stacktrace, Vst0) ->
    verify_y_init(Vst0),
    verify_live(1, Vst0),

    Vst = prune_x_regs(1, Vst0),
    Reg = {x,0},

    assert_durable_term(Reg, Vst),
    create_term(#t_list{}, build_stacktrace, [], Reg, Vst);

%%
%% Map instructions.
%%

vi({get_map_elements,{f,Fail},Src0,{list,List}}, Vst) ->
    Src = unpack_typed_arg(Src0),
    verify_get_map(Fail, Src, List, Vst);
vi({put_map_assoc=Op,{f,Fail},Src,Dst,Live,{list,List}}, Vst) ->
    verify_put_map(Op, Fail, Src, Dst, Live, List, Vst);
vi({put_map_exact=Op,{f,Fail},Src,Dst,Live,{list,List}}, Vst) ->
    verify_put_map(Op, Fail, Src, Dst, Live, List, Vst);

%%
%% Bit syntax matching
%%

vi({bs_get_tail,Ctx,Dst,Live}, Vst0) ->
    assert_type(#t_bs_context{}, Ctx, Vst0),
    verify_live(Live, Vst0),
    verify_y_init(Vst0),

    #t_bs_context{tail_unit=Unit} = get_concrete_type(Ctx, Vst0),

    Vst = prune_x_regs(Live, Vst0),
    extract_term(#t_bitstring{size_unit=Unit}, bs_get_tail, [Ctx], Dst,
                 Vst, Vst0);
vi({bs_start_match4,Fail,Live,Src,Dst}, Vst) ->
    validate_bs_start_match(Fail, Live, Src, Dst, Vst);
vi({test,bs_start_match3,{f,_}=Fail,Live,[Src],Dst}, Vst) ->
    validate_bs_start_match(Fail, Live, Src, Dst, Vst);
vi({test,bs_match_string,{f,Fail},[Ctx,Stride,{string,String}]}, Vst) ->
    true = is_bitstring(String),                %Assertion.
    validate_bs_skip(Fail, Ctx, Stride, Vst);
vi({test,bs_skip_bits2,{f,Fail},[Ctx,Size,Unit,_Flags]}, Vst) ->
    assert_term(Size, Vst),

    Stride = case get_concrete_type(Size, Vst) of
                 #t_integer{elements={Same,Same}} -> Same * Unit;
                 _ -> Unit
             end,

    validate_bs_skip(Fail, Ctx, Stride, Vst);
vi({test,bs_test_tail2,{f,Fail},[Ctx,_Size]}, Vst) ->
    assert_no_exception(Fail),
    assert_type(#t_bs_context{}, Ctx, Vst),
    branch(Fail, Vst);
vi({test,bs_test_unit,{f,Fail},[Ctx,Unit]}, Vst) ->
    assert_type(#t_bs_context{}, Ctx, Vst),

    Type = #t_bs_context{tail_unit=Unit},

    branch(Fail, Vst,
           fun(FailVst) ->
                   update_type(fun subtract/2, Type, Ctx, FailVst)
           end,
           fun(SuccVst0) ->
                   SuccVst = update_bs_unit(Ctx, Unit, SuccVst0),
                   update_type(fun meet/2, Type, Ctx, SuccVst)
           end);
vi({test,bs_skip_utf8,{f,Fail},[Ctx,Live,_]}, Vst) ->
    validate_bs_skip(Fail, Ctx, 8, Live, Vst);
vi({test,bs_skip_utf16,{f,Fail},[Ctx,Live,_]}, Vst) ->
    validate_bs_skip(Fail, Ctx, 16, Live, Vst);
vi({test,bs_skip_utf32,{f,Fail},[Ctx,Live,_]}, Vst) ->
    validate_bs_skip(Fail, Ctx, 32, Live, Vst);
vi({test,bs_get_binary2=Op,{f,Fail},Live,[Ctx,{atom,all},Unit,_],Dst}, Vst) ->
    Type = #t_bitstring{size_unit=Unit},
    validate_bs_get_all(Op, Fail, Ctx, Live, Unit, Type, Dst, Vst);
vi({test,bs_get_binary2=Op,{f,Fail},Live,[Ctx,Size,Unit,_],Dst}, Vst) ->
    Type = #t_bitstring{size_unit=bsm_size_unit(Size, Unit)},
    Stride = bsm_stride(Size, Unit),
    validate_bs_get(Op, Fail, Ctx, Live, Stride, Type, Dst, Vst);
vi({test,bs_get_integer2=Op,{f,Fail},Live,
    [Ctx,{integer,Sz},Unit,{field_flags,Flags}],Dst},Vst) ->

    NumBits = Unit * Sz,
    Stride = NumBits,

    Type = if
               0 =< NumBits, NumBits =< 64 ->
                   Max = (1 bsl NumBits) - 1,
                   case member(unsigned, Flags) of
                       true ->
                           beam_types:make_integer(0, Max);
                       false ->
                           Min = -(Max + 1),
                           beam_types:make_integer(Min, Max)
                   end;
               true ->
                   %% Way too large or negative size.
                   #t_integer{}
           end,

    validate_bs_get(Op, Fail, Ctx, Live, Stride, Type, Dst, Vst);
vi({test,bs_get_integer2=Op,{f,Fail},Live,[Ctx,_Sz,Unit,_Flags],Dst},Vst) ->
    validate_bs_get(Op, Fail, Ctx, Live, Unit, #t_integer{}, Dst, Vst);
vi({test,bs_get_float2=Op,{f,Fail},Live,[Ctx,Size,Unit,_],Dst},Vst) ->
    Stride = bsm_stride(Size, Unit),
    validate_bs_get(Op, Fail, Ctx, Live, Stride, #t_float{}, Dst, Vst);
vi({test,bs_get_utf8=Op,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
    Type = beam_types:make_integer(0, ?UNICODE_MAX),
    validate_bs_get(Op, Fail, Ctx, Live, 8, Type, Dst, Vst);
vi({test,bs_get_utf16=Op,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
    Type = beam_types:make_integer(0, ?UNICODE_MAX),
    validate_bs_get(Op, Fail, Ctx, Live, 16, Type, Dst, Vst);
vi({test,bs_get_utf32=Op,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
    Type = beam_types:make_integer(0, ?UNICODE_MAX),
    validate_bs_get(Op, Fail, Ctx, Live, 32, Type, Dst, Vst);
vi({test,_Op,{f,Lbl},Ss}, Vst) ->
    %% is_lt, is_gt, et cetera.
    validate_src([unpack_typed_arg(Arg) || Arg <- Ss], Vst),
    branch(Lbl, Vst);

%%
%% Bit syntax positioning
%%

vi({bs_get_position, Ctx, Dst, Live}, Vst0) ->
    assert_type(#t_bs_context{}, Ctx, Vst0),

    verify_live(Live, Vst0),
    verify_y_init(Vst0),

    #t_bs_context{tail_unit=Unit} = get_concrete_type(Ctx, Vst0),

    Vst1 = prune_x_regs(Live, Vst0),
    Vst = create_term(#t_abstract{kind={ms_position, Unit}},
                       bs_get_position, [Ctx], Dst, Vst1, Vst0),

    mark_current_ms_position(Ctx, Dst, Vst);
vi({bs_set_position, Ctx, Pos}, Vst0) ->
    assert_type(#t_bs_context{}, Ctx, Vst0),
    assert_type(#t_abstract{kind={ms_position,1}}, Pos, Vst0),

    #t_abstract{kind={ms_position,Unit}} = get_concrete_type(Pos, Vst0),
    Vst = override_type(#t_bs_context{tail_unit=Unit}, Ctx, Vst0),

    mark_current_ms_position(Ctx, Pos, Vst);

%%
%% Floating-point instructions (excluding BIFs)
%%
vi({fconv,Src0,{fr,_}=Dst}, Vst) ->
    Src = unpack_typed_arg(Src0),

    assert_term(Src, Vst),
    branch(?EXCEPTION_LABEL, Vst,
           fun(SuccVst0) ->
                   SuccVst = update_type(fun meet/2, number, Src, SuccVst0),
                   set_freg(Dst, SuccVst)
           end);

%%
%% Exception-raising instructions
%%

vi({func_info, {atom, _Mod}, {atom, _Name}, Arity}, Vst) ->
    #vst{current=#st{numy=NumY}} = Vst,
    if
         NumY =:= none ->
            verify_live(Arity, Vst),
            verify_call_args(func_info, Arity, Vst),

            branch(?EXCEPTION_LABEL, Vst, fun kill_state/1);
         NumY =/= none ->
            error({allocated, NumY})
    end;
vi({badmatch,Src}, Vst) ->
    assert_durable_term(Src, Vst),
    branch(?EXCEPTION_LABEL, Vst, fun kill_state/1);
vi({case_end,Src}, Vst) ->
    assert_durable_term(Src, Vst),
    branch(?EXCEPTION_LABEL, Vst, fun kill_state/1);
vi(if_end, Vst) ->
    branch(?EXCEPTION_LABEL, Vst, fun kill_state/1);
vi({try_case_end,Src}, Vst) ->
    assert_durable_term(Src, Vst),
    branch(?EXCEPTION_LABEL, Vst, fun kill_state/1);
vi({badrecord,Src}, Vst) ->
    assert_durable_term(Src, Vst),
    branch(?EXCEPTION_LABEL, Vst, fun kill_state/1);
vi(raw_raise=I, Vst0) ->
    validate_body_call(I, 3, Vst0);

%%
%% Binary construction
%%

vi(bs_init_writable=I, Vst) ->
    validate_body_call(I, 1, Vst);
vi({bs_create_bin,{f,Fail},Heap,Live,Unit,Dst,{list,List0}}, Vst0) ->
    verify_live(Live, Vst0),
    verify_y_init(Vst0),
    List = [unpack_typed_arg(Arg) || Arg <- List0],
    verify_create_bin_list(List, Vst0),
    Vst = prune_x_regs(Live, Vst0),
    branch(Fail, Vst,
           fun(FailVst0) ->
                   heap_alloc(0, FailVst0)
           end,
           fun(SuccVst0) ->
                   SuccVst1 = update_create_bin_list(List, SuccVst0),
                   SuccVst = heap_alloc(Heap, SuccVst1),
                   create_term(#t_bitstring{size_unit=Unit}, bs_create_bin, [], Dst,
                               SuccVst)
           end);
vi({bs_init2,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) ->
    verify_live(Live, Vst0),
    verify_y_init(Vst0),
    if
        is_integer(Sz) -> ok;
        true -> assert_term(Sz, Vst0)
    end,
    Vst = heap_alloc(Heap, Vst0),
    branch(Fail, Vst,
           fun(SuccVst0) ->
                   SuccVst = prune_x_regs(Live, SuccVst0),
                   create_term(#t_bitstring{size_unit=8}, bs_init2, [], Dst,
                               SuccVst, SuccVst0)
           end);
vi({bs_init_bits,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) ->
    verify_live(Live, Vst0),
    verify_y_init(Vst0),
    if
        is_integer(Sz) -> ok;
        true -> assert_term(Sz, Vst0)
    end,
    Vst = heap_alloc(Heap, Vst0),
    branch(Fail, Vst,
           fun(SuccVst0) ->
                   SuccVst = prune_x_regs(Live, SuccVst0),
                   create_term(#t_bitstring{}, bs_init_bits, [], Dst, SuccVst)
           end);
vi({bs_add,{f,Fail},[A,B,_],Dst}, Vst) ->
    assert_term(A, Vst),
    assert_term(B, Vst),
    branch(Fail, Vst,
           fun(SuccVst) ->
                   create_term(#t_integer{}, bs_add, [A, B], Dst, SuccVst)
           end);
vi({bs_utf8_size,{f,Fail},A,Dst}, Vst) ->
    assert_term(A, Vst),
    branch(Fail, Vst,
           fun(SuccVst) ->
                   create_term(#t_integer{}, bs_utf8_size, [A], Dst, SuccVst)
           end);
vi({bs_utf16_size,{f,Fail},A,Dst}, Vst) ->
    assert_term(A, Vst),
    branch(Fail, Vst,
           fun(SuccVst) ->
                   create_term(#t_integer{}, bs_utf16_size, [A], Dst, SuccVst)
           end);
vi({bs_append,{f,Fail},Bits,Heap,Live,Unit,Bin,_Flags,Dst}, Vst0) ->
    verify_live(Live, Vst0),
    verify_y_init(Vst0),
    assert_term(Bits, Vst0),
    assert_term(Bin, Vst0),
    Vst = heap_alloc(Heap, Vst0),
    branch(Fail, Vst,
           fun(SuccVst0) ->
                   SuccVst = prune_x_regs(Live, SuccVst0),
                   create_term(#t_bitstring{size_unit=Unit}, bs_append,
                               [Bin], Dst, SuccVst, SuccVst0)
           end);
vi({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(#t_bitstring{size_unit=Unit}, bs_private_append,
                               [Bin], Dst, SuccVst)
           end);
vi({bs_put_string,Sz,_}, Vst) when is_integer(Sz) ->
    Vst;
vi({bs_put_binary,{f,Fail},Sz,_,_,Src}, Vst) ->
    assert_term(Sz, Vst),
    assert_term(Src, Vst),
    branch(Fail, Vst,
           fun(SuccVst) ->
                   update_type(fun meet/2, #t_bitstring{}, Src, SuccVst)
           end);
vi({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, #t_float{}, Src, SuccVst)
           end);
vi({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, #t_integer{}, Src, SuccVst)
           end);
vi({bs_put_utf8,{f,Fail},_,Src}, Vst) ->
    assert_term(Src, Vst),
    branch(Fail, Vst,
           fun(SuccVst) ->
                   update_type(fun meet/2, #t_integer{}, Src, SuccVst)
           end);
vi({bs_put_utf16,{f,Fail},_,Src}, Vst) ->
    assert_term(Src, Vst),
    branch(Fail, Vst,
           fun(SuccVst) ->
                   update_type(fun meet/2, #t_integer{}, Src, SuccVst)
           end);
vi({bs_put_utf32,{f,Fail},_,Src}, Vst) ->
    assert_term(Src, Vst),
    branch(Fail, Vst,
           fun(SuccVst) ->
                   update_type(fun meet/2, #t_integer{}, Src, SuccVst)
           end);

vi(_, _) ->
    error(unknown_instruction).

validate_var_info([{fun_type, Type} | Info], Reg, Vst0) ->
    %% Explicit type information inserted after make_fun2 instructions to mark
    %% the return type of the created fun.
    Vst = update_type(fun meet/2, #t_fun{type=Type}, Reg, Vst0),
    validate_var_info(Info, Reg, Vst);
validate_var_info([{type, none} | _Info], _Reg, Vst) ->
    %% Unreachable code, typically after a call that never returns.
    kill_state(Vst);
validate_var_info([{type, Type} | Info], Reg, Vst0) ->
    %% Explicit type information inserted by optimization passes to indicate
    %% that Reg has a certain type, so that we can accept cross-function type
    %% optimizations.
    Vst = update_type(fun meet/2, Type, Reg, Vst0),
    validate_var_info(Info, Reg, Vst);
validate_var_info([_ | Info], Reg, Vst) ->
    validate_var_info(Info, Reg, Vst);
validate_var_info([], _Reg, Vst) ->
    Vst.

%% Tail call.
%%  The stackframe must have a known size and be initialized.
%%  Does not return to the instruction following the call.
validate_tail_call(Deallocate, Func, Live, #vst{current=#st{numy=NumY}}=Vst0) ->
    verify_y_init(Vst0),
    verify_live(Live, Vst0),
    verify_call_args(Func, Live, Vst0),

    case will_call_succeed(Func, Live, Vst0) of
        yes when Deallocate =:= NumY ->
            %% The call cannot fail; we don't need to handle exceptions
            Vst = deallocate(Vst0),
            verify_return(Vst);
        'maybe' when Deallocate =:= NumY ->
            %% The call may fail; make sure we update exception state
            Vst = deallocate(Vst0),
            branch(?EXCEPTION_LABEL, Vst, fun verify_return/1);
        no ->
            %% The compiler is allowed to emit garbage values for "Deallocate"
            %% as we know that it will not be used in this case.
            branch(?EXCEPTION_LABEL, Vst0, fun kill_state/1);
        _ when Deallocate =/= NumY ->
            error({allocated, NumY})
    end.

%% A "plain" call.
%%  The stackframe must be initialized.
%%  The instruction will return to the instruction following the call.
validate_body_call(Func, Live,
                   #vst{current=#st{numy=NumY}}=Vst) when is_integer(NumY)->
    verify_y_init(Vst),
    verify_live(Live, Vst),
    verify_call_args(Func, Live, Vst),

    SuccFun = fun(SuccVst0) ->
                      {RetType, _, _} = call_types(Func, Live, SuccVst0),
                      true = RetType =/= none,  %Assertion.

                      SuccVst = schedule_out(0, SuccVst0),

                      create_term(RetType, call, [], {x,0}, SuccVst)
              end,

    case will_call_succeed(Func, Live, Vst) of
        yes ->
            SuccFun(Vst);
        'maybe' ->
            branch(?EXCEPTION_LABEL, Vst, SuccFun);
        no ->
            branch(?EXCEPTION_LABEL, Vst, fun kill_state/1)
    end;
validate_body_call(_, _, #vst{current=#st{numy=NumY}}) ->
    error({allocated, NumY}).

init_try_catch_branch(Kind, Dst, Fail, Vst0) ->
    assert_no_exception(Fail),

    Tag = {Kind, [Fail]},
    Vst = create_tag(Tag, 'try_catch', [], Dst, Vst0),

    #vst{current=St0} = Vst,
    #st{ct=Tags}=St0,
    St = St0#st{ct=[Tag|Tags]},
 
    Vst#vst{current=St}.

verify_has_map_fields(Lbl, Src, List, Vst) ->
    assert_type(#t_map{}, Src, Vst),
    assert_unique_map_keys(List),
    verify_map_fields(List, Src, Lbl, Vst).

verify_map_fields([Key | Keys], Map, Lbl, Vst) ->
    assert_term(Key, Vst),
    case bif_types(map_get, [Key, Map], Vst) of
        {none, _, _} -> kill_state(Vst);
        {_, _, _} -> verify_map_fields(Keys, Map, Lbl, Vst)
    end;
verify_map_fields([], _Map, Lbl, Vst) ->
    branch(Lbl, Vst).

verify_get_map(Fail, Src, List, Vst0) ->
    assert_no_exception(Fail),
    assert_not_literal(Src),                    %OTP 22.
    assert_type(#t_map{}, Src, Vst0),

    branch(Fail, Vst0,
           fun(FailVst) ->
                   clobber_map_vals(List, Src, FailVst)
           end,
           fun(SuccVst) ->
                   Keys = extract_map_keys(List),
                   assert_unique_map_keys(Keys),
                   extract_map_vals(List, Src, SuccVst)
           end).

%% get_map_elements may leave its destinations in an inconsistent state when
%% the fail label is taken. Consider the following:
%%
%%    {get_map_elements,{f,7},{x,1},{list,[{atom,a},{x,1},{atom,b},{x,2}]}}.
%%
%% If 'a' exists but not 'b', {x,1} is overwritten when we jump to {f,7}.
%%
%% We must be careful to preserve the uninitialized status for Y registers
%% that have been allocated but not yet defined.
clobber_map_vals([Key0, Dst | T], Map, Vst0) ->
    Key = unpack_typed_arg(Key0),
    case is_reg_initialized(Dst, Vst0) of
        true ->
            Vst = extract_term(any, {bif,map_get}, [Key, Map], Dst, Vst0),
            clobber_map_vals(T, Map, Vst);
        false ->
            clobber_map_vals(T, Map, Vst0)
    end;
clobber_map_vals([], _Map, Vst) ->
    Vst.

is_reg_initialized({x,_}=Reg, #vst{current=#st{xs=Xs}}) ->
    is_map_key(Reg, Xs);
is_reg_initialized({y,_}=Reg, #vst{current=#st{ys=Ys}}) ->
    case Ys of
        #{Reg:=Val} ->
            Val =/= uninitialized;
        #{} ->
            false
    end;
is_reg_initialized(V, #vst{}) -> error({not_a_register, V}).

extract_map_keys([Key,_Val | T]) ->
    [unpack_typed_arg(Key) | extract_map_keys(T)];
extract_map_keys([]) ->
    [].


extract_map_vals(List, Src, SuccVst) ->
    Seen = sets:new([{version, 2}]),
    extract_map_vals(List, Src, Seen, SuccVst, SuccVst).

extract_map_vals([Key0, Dst | Vs], Map, Seen0, Vst0, Vsti0) ->
    case sets:is_element(Dst, Seen0) of
        true ->
            %% The destinations must not overwrite each other.
            error(conflicting_destinations);
        false ->
            Key = unpack_typed_arg(Key0),
            assert_term(Key, Vst0),
            case bif_types(map_get, [Key, Map], Vst0) of
                {none, _, _} ->
                    kill_state(Vsti0);
                {DstType, _, _} ->
                    Vsti = extract_term(DstType, {bif,map_get},
                                        [Key, Map], Dst, Vsti0),
                    Seen = sets:add_element(Dst, Seen0),
                    extract_map_vals(Vs, Map, Seen, Vst0, Vsti)
            end
    end;
extract_map_vals([], _Map, _Seen, _Vst0, Vst) ->
    Vst.

verify_put_map(Op, Fail, Src, Dst, Live, List, Vst0) ->
    assert_type(#t_map{}, Src, Vst0),
    verify_live(Live, Vst0),
    verify_y_init(Vst0),

    _ = [assert_term(Term, Vst0) || Term <- List],
    Vst = heap_alloc(0, Vst0),

    SuccFun = fun(SuccVst0) ->
                      SuccVst = prune_x_regs(Live, SuccVst0),
                      Keys = extract_map_keys(List),
                      assert_unique_map_keys(Keys),

                      Type = put_map_type(Src, List, Vst),
                      create_term(Type, Op, [Src], Dst, SuccVst, SuccVst0)
              end,

    case Op of
        put_map_exact ->
            branch(Fail, Vst, SuccFun);
        put_map_assoc ->
            %% This instruction cannot fail.
            ?EXCEPTION_LABEL = Fail,            %Assertion.
            SuccFun(Vst)
    end.

put_map_type(Map0, List, Vst) ->
    Map = normalize(get_term_type(Map0, Vst)),
    pmt_1(List, Vst, Map).

pmt_1([Key0, Value0 | List], Vst, Acc0) ->
    Key = normalize(get_term_type(Key0, Vst)),
    Value = normalize(get_term_type(Value0, Vst)),
    {Acc, _, _} = beam_call_types:types(maps, put, [Key, Value, Acc0]),
    pmt_1(List, Vst, Acc);
pmt_1([], _Vst, Acc) ->
    Acc.

verify_create_bin_list([{atom,string},_Seg,Unit,Flags,Val,Size|Args], Vst) ->
    assert_bs_unit({atom,string}, Unit),
    assert_term(Flags, Vst),
    case Val of
        {string,Bs} when is_binary(Bs) -> ok;
        _ -> error({not_string,Val})
    end,
    assert_term(Flags, Vst),
    assert_term(Size, Vst),
    verify_create_bin_list(Args, Vst);
verify_create_bin_list([Type,_Seg,Unit,Flags,Val,Size|Args], Vst) ->
    assert_term(Type, Vst),
    assert_bs_unit(Type, Unit),
    assert_term(Flags, Vst),
    assert_term(Val, Vst),
    assert_term(Size, Vst),
    verify_create_bin_list(Args, Vst);
verify_create_bin_list([], _Vst) -> ok.

update_create_bin_list([{atom,string},_Seg,_Unit,_Flags,_Val,_Size|T], Vst) ->
    update_create_bin_list(T, Vst);
update_create_bin_list([{atom,Op},_Seg,_Unit,_Flags,Val,_Size|T], Vst0) ->
    Type = update_create_bin_type(Op),
    Vst = update_type(fun meet/2, Type, Val, Vst0),
    update_create_bin_list(T, Vst);
update_create_bin_list([], Vst) -> Vst.

update_create_bin_type(append) -> #t_bitstring{};
update_create_bin_type(private_append) -> #t_bitstring{};
update_create_bin_type(binary) -> #t_bitstring{};
update_create_bin_type(float) -> #t_float{};
update_create_bin_type(integer) -> #t_integer{};
update_create_bin_type(utf8) -> #t_integer{};
update_create_bin_type(utf16) -> #t_integer{};
update_create_bin_type(utf32) -> #t_integer{}.

assert_bs_unit({atom,Type}, 0) ->
    case Type of
        utf8 -> ok;
        utf16 -> ok;
        utf32 -> ok;
        _ -> error({zero_unit_invalid_for_type,Type})
    end;
assert_bs_unit({atom,_Type}, Unit) when is_integer(Unit), 0 < Unit, Unit =< 256 ->
    ok;
assert_bs_unit(_, Unit) ->
    error({invalid,Unit}).

%%
%% Common code for validating returns, whether naked or as part of a tail call.
%%

verify_return(#vst{current=#st{numy=NumY}}) when NumY =/= none ->
    error({stack_frame,NumY});
verify_return(#vst{current=#st{recv_state=State}}) when State =/= none ->
    %% Returning in the middle of a receive loop will ruin the next receive.
    error({return_in_receive,State});
verify_return(Vst) ->
    verify_no_ct(Vst),
    kill_state(Vst).

%%
%% 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) ->
    case will_bif_succeed(Op, Ss, Vst) of
        yes ->
            %% This BIF cannot fail (neither throw nor branch), make sure it's
            %% handled without updating exception state.
            validate_bif_1(Kind, Op, cannot_fail, Ss, Dst, OrigVst, Vst);
        no ->
            %% This BIF always fails; jump directly to the fail block or
            %% exception handler.
            branch(Fail, Vst, fun kill_state/1);
        'maybe' ->
            validate_bif_1(Kind, Op, Fail, Ss, Dst, OrigVst, Vst)
    end.

validate_bif_1(Kind, Op, cannot_fail, Ss, Dst, OrigVst, Vst0) ->
    %% This BIF explicitly cannot fail; it will not jump to a guard nor throw
    %% an exception. Validation will fail if it returns 'none' or has a type
    %% conflict on one of its arguments.

    {Type, ArgTypes, _CanSubtract} = bif_types(Op, Ss, Vst0),
    ZippedArgs = zip(Ss, ArgTypes),

    Vst = foldl(fun({A, T}, V) ->
                            update_type(fun meet/2, T, A, V)
                    end, Vst0, ZippedArgs),

    true = Type =/= none,                       %Assertion.

    extract_term(Type, {Kind, Op}, Ss, Dst, Vst, OrigVst);
validate_bif_1(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.
%%

validate_bs_start_match({atom,resume}, Live, Src, Dst, Vst0) ->
    assert_type(#t_bs_context{}, Src, Vst0),
    verify_live(Live, Vst0),
    verify_y_init(Vst0),

    Vst = assign(Src, Dst, Vst0),
    prune_x_regs(Live, Vst);
validate_bs_start_match({atom,no_fail}, Live, Src, Dst, Vst0) ->
    verify_live(Live, Vst0),
    verify_y_init(Vst0),

    Vst1 = update_type(fun meet/2, #t_bs_matchable{}, Src, Vst0),

    %% Retain the current unit, if known.
    SrcType = get_movable_term_type(Src, Vst1),
    TailUnit = beam_types:get_bs_matchable_unit(SrcType),

    Vst = prune_x_regs(Live, Vst1),
    extract_term(#t_bs_context{tail_unit=TailUnit}, bs_start_match,
                 [Src], Dst, Vst, Vst0);
validate_bs_start_match({f,Fail}, Live, Src, Dst, Vst) ->
    assert_no_exception(Fail),

    branch(Fail, Vst,
           fun(FailVst) ->
                   update_type(fun subtract/2, #t_bs_matchable{}, Src, FailVst)
           end,
           fun(SuccVst) ->
                   validate_bs_start_match({atom,no_fail}, Live,
                                           Src, Dst, SuccVst)
           end).

%%
%% Common code for validating bs_get* instructions.
%%
validate_bs_get(Op, Fail, Ctx0, Live, Stride, Type, Dst, Vst) ->
    Ctx = unpack_typed_arg(Ctx0),

    assert_no_exception(Fail),

    assert_type(#t_bs_context{}, Ctx, Vst),
    verify_live(Live, Vst),
    verify_y_init(Vst),

    branch(Fail, Vst,
           fun(SuccVst0) ->
                   SuccVst1 = advance_bs_context(Ctx, Stride, SuccVst0),
                   SuccVst = prune_x_regs(Live, SuccVst1),
                   extract_term(Type, Op, [Ctx], Dst, SuccVst, SuccVst0)
           end).

validate_bs_get_all(Op, Fail, Ctx0, Live, Stride, Type, Dst, Vst) ->
    Ctx = unpack_typed_arg(Ctx0),

    assert_no_exception(Fail),

    assert_type(#t_bs_context{}, Ctx, Vst),
    verify_live(Live, Vst),
    verify_y_init(Vst),

    branch(Fail, Vst,
           fun(SuccVst0) ->
                   %% This acts as an implicit unit test on the current match
                   %% position, so we'll update the unit in case we rewind here
                   %% later on.
                   SuccVst1 = update_bs_unit(Ctx, Stride, SuccVst0),

                   SuccVst2 = advance_bs_context(Ctx, Stride, SuccVst1),
                   SuccVst = prune_x_regs(Live, SuccVst2),
                   extract_term(Type, Op, [Ctx], Dst, SuccVst, SuccVst0)
           end).

%%
%% Common code for validating bs_skip* instructions.
%%
validate_bs_skip(Fail, Ctx, Stride, Vst) ->
    validate_bs_skip(Fail, Ctx, Stride, no_live, Vst).

validate_bs_skip(Fail, Ctx, Stride, Live, Vst) ->
    assert_no_exception(Fail),

    assert_type(#t_bs_context{}, Ctx, Vst),

    validate_bs_skip_1(Fail, Ctx, Stride, Live, Vst).

validate_bs_skip_1(Fail, Ctx, Stride, no_live, Vst) ->
    branch(Fail, Vst,
           fun(SuccVst) ->
                   advance_bs_context(Ctx, Stride, SuccVst)
           end);
validate_bs_skip_1(Fail, Ctx, Stride, Live, Vst) ->
    verify_y_init(Vst),
    verify_live(Live, Vst),
    branch(Fail, Vst,
           fun(SuccVst0) ->
                   SuccVst = advance_bs_context(Ctx, Stride, SuccVst0),
                   prune_x_regs(Live, SuccVst)
           end).

advance_bs_context(_Ctx, 0, Vst) ->
    %% We _KNOW_ we're not moving anywhere. Retain our current position and
    %% type.
    Vst;
advance_bs_context(_Ctx, Stride, _Vst) when Stride < 0 ->
    %% We _KNOW_ we'll fail at runtime.
    throw({invalid_argument, {negative_stride, Stride}});
advance_bs_context(Ctx, Stride, Vst0) ->
    Vst = update_type(fun join/2, #t_bs_context{tail_unit=Stride}, Ctx, Vst0),

    %% The latest saved position (if any) is no longer current, make sure
    %% it isn't updated on the next match operation.
    invalidate_current_ms_position(Ctx, Vst).

%% Updates the unit of our latest saved position, if it's current.
update_bs_unit(Ctx, Unit, #vst{current=St}=Vst) ->
    CtxRef = get_reg_vref(Ctx, Vst),

    case St#st.ms_positions of
        #{ CtxRef := PosRef } ->
            PosType = #t_abstract{kind={ms_position, Unit}},
            update_type(fun meet/2, PosType, PosRef, Vst);
        #{} ->
            Vst
    end.

mark_current_ms_position(Ctx, Pos, #vst{current=St0}=Vst) ->
    CtxRef = get_reg_vref(Ctx, Vst),
    PosRef = get_reg_vref(Pos, Vst),

    #st{ms_positions=MsPos0} = St0,

    MsPos = MsPos0#{ CtxRef => PosRef },

    St = St0#st{ ms_positions=MsPos },
    Vst#vst{current=St}.

invalidate_current_ms_position(Ctx, #vst{current=St0}=Vst) ->
    CtxRef = get_reg_vref(Ctx, Vst),
    #st{ms_positions=MsPos0} = St0,

    case MsPos0 of
        #{ CtxRef := _ } ->
            MsPos = maps:remove(CtxRef, MsPos0),
            St = St0#st{ms_positions=MsPos},
            Vst#vst{current=St};
        #{} ->
            Vst
    end.

%%
%% Common code for is_$type instructions.
%%
type_test(Fail, Type, Reg0, Vst) ->
    Reg = unpack_typed_arg(Reg0),

    assert_term(Reg, Vst),
    assert_no_exception(Fail),
    branch(Fail, Vst,
           fun(FailVst) ->
                   update_type(fun subtract/2, Type, Reg, FailVst)
           end,
           fun(SuccVst) ->
                   update_type(fun meet/2, Type, Reg, SuccVst)
           end).

%%
%% Special state handling for setelement/3 and set_tuple_element/3 instructions.
%% A possibility for garbage collection must not occur between setelement/3 and
%% set_tuple_element/3.
%%
%% Note that #vst.current will be 'none' if the instruction is unreachable.
%%

validate_mutation(I, Vst) ->
    vm_1(I, Vst).

vm_1({move,_,_}, Vst) ->
    Vst;
vm_1({swap,_,_}, Vst) ->
    Vst;
vm_1({call_ext,3,{extfunc,erlang,setelement,3}}, #vst{current=#st{}=St}=Vst) ->
    Vst#vst{current=St#st{setelem=true}};
vm_1({set_tuple_element,_,_,_}, #vst{current=#st{setelem=false}}) ->
    error(illegal_context_for_set_tuple_element);
vm_1({set_tuple_element,_,_,_}, #vst{current=#st{setelem=true}}=Vst) ->
    Vst;
vm_1({get_tuple_element,_,_,_}, Vst) ->
    Vst;
vm_1({line,_}, Vst) ->
    Vst;
vm_1(_, #vst{current=#st{setelem=true}=St}=Vst) ->
    Vst#vst{current=St#st{setelem=false}};
vm_1(_, Vst) -> Vst.

kill_state(Vst) ->
    Vst#vst{current=none}.

verify_call_args(_, 0, #vst{}) ->
    ok;
verify_call_args({f,Lbl}, Live, #vst{ft=Ft}=Vst) ->
    case Ft of
        #{ Lbl := FuncInfo } ->
            #{ arity := Live,
               parameter_info := ParamInfo } = FuncInfo,
            verify_local_args(Live - 1, ParamInfo, #{}, Vst);
        #{} ->
            error(local_call_to_unknown_function)
    end;
verify_call_args(_, Live, Vst) ->
    verify_remote_args_1(Live - 1, Vst).

verify_remote_args_1(-1, _) ->
    ok;
verify_remote_args_1(X, Vst) ->
    assert_durable_term({x, X}, Vst),
    verify_remote_args_1(X - 1, Vst).

verify_local_args(-1, _ParamInfo, _CtxIds, _Vst) ->
    ok;
verify_local_args(X, ParamInfo, CtxRefs, Vst) ->
    Reg = {x, X},
    assert_not_fragile(Reg, Vst),
    case get_movable_term_type(Reg, Vst) of
        #t_bs_context{}=Type ->
            VRef = get_reg_vref(Reg, Vst),
            case CtxRefs of
                #{ VRef := Other } ->
                    error({multiple_match_contexts, [Reg, Other]});
                #{} ->
                    verify_arg_type(Reg, Type, ParamInfo, Vst),
                    verify_local_args(X - 1, ParamInfo,
                                      CtxRefs#{ VRef => Reg }, Vst)
            end;
        Type ->
            verify_arg_type(Reg, Type, ParamInfo, Vst),
            verify_local_args(X - 1, ParamInfo, CtxRefs, Vst)
    end.

verify_arg_type(Reg, GivenType, ParamInfo, Vst) ->
    case {ParamInfo, GivenType} of
        {#{ Reg := Info }, #t_bs_context{}} ->
            %% Match contexts require explicit support, and may not be passed
            %% to a function that accepts arbitrary terms.
            case member(accepts_match_context, Info) of
                true -> verify_arg_type_1(Reg, GivenType, Info, Vst);
                false -> error(no_bs_start_match2)
            end;
        {_, #t_bs_context{}} ->
            error(no_bs_start_match2);
        {#{ Reg := Info }, _} ->
            verify_arg_type_1(Reg, GivenType, Info, Vst);
        {#{}, _} ->
            ok
    end.

verify_arg_type_1(Reg, GivenType, Info, Vst) ->
    RequiredType = proplists:get_value(type, Info, any),
    case meet(GivenType, RequiredType) of
        Type when Type =/= none, Vst#vst.level =:= weak ->
            %% Strictly speaking this should always match GivenType, but
            %% beam_jump:share/1 sometimes joins blocks in a manner that makes
            %% it very difficult to accurately reconstruct type information,
            %% for example:
            %%
            %%        bug({Tag, _, _} = Key) when Tag == a; Tag == b ->
            %%            foo(Key);
            %%        bug({Tag, _} = Key) when Tag == a; Tag == b ->
            %%            foo(Key).
            %%
            %%        foo(I) -> I.
            %%
            %% At the first call to foo/1, we know that we have either `{a,_}`
            %% or `{b,_}`, and at the second call `{a,_,_}` or `{b,_,_}`.
            %%
            %% When both calls to foo/1 are joined into the same block, all we
            %% know is that we have tuple with an arity of at least 2, whose
            %% first element is `a` or `b`.
            %%
            %% Fixing this properly is a big undertaking, so for now we've
            %% decided on a compromise that splits validation into two steps.
            %%
            %% We run a 'strong' pass directly after code generation in which
            %% arguments must be at least as narrow as expected, and a 'weak'
            %% pass after all optimizations have been applied in which we
            %% tolerate arguments that aren't in direct conflict.
            ok;
        GivenType ->
            true = GivenType =/= none,          %Assertion.
            ok;
        _ ->
            error({bad_arg_type, Reg, GivenType, RequiredType})
    end.

allocate(Tag, Stk, Heap, Live, #vst{current=#st{numy=none}=St}=Vst0) ->
    verify_live(Live, Vst0),
    Vst1 = Vst0#vst{current=St#st{numy=Stk}},
    Vst2 = prune_x_regs(Live, Vst1),
    Vst = init_stack(Tag, Stk - 1, Vst2),
    heap_alloc(Heap, Vst);
allocate(_, _, _, _, #vst{current=#st{numy=Numy}}) ->
    error({existing_stack_frame,{size,Numy}}).

deallocate(#vst{current=St}=Vst) ->
    Vst#vst{current=St#st{ys=#{},numy=none}}.

init_stack(_Tag, -1, Vst) ->
    Vst;
init_stack(Tag, Y, Vst) ->
    init_stack(Tag, Y - 1, create_tag(Tag, allocate, [], {y,Y}, Vst)).

trim_stack(From, To, Top, #st{ys=Ys0}=St) when From =:= Top ->
    Ys = maps:filter(fun({y,Y}, _) -> Y < To end, Ys0),
    St#st{numy=To,ys=Ys};
trim_stack(From, To, Top, St0) ->
    Src = {y, From},
    Dst = {y, To},

    #st{ys=Ys0} = St0,
    Ys = case Ys0 of
             #{ Src := Ref } -> Ys0#{ Dst => Ref };
             #{} -> error({invalid_shift,Src,Dst})
         end,
    St = St0#st{ys=Ys},

    trim_stack(From + 1, To + 1, Top, St).

test_heap(Heap, Live, Vst0) ->
    verify_live(Live, Vst0),
    verify_y_init(Vst0),
    Vst = prune_x_regs(Live, Vst0),
    heap_alloc(Heap, Vst).

heap_alloc(Heap, #vst{current=St0}=Vst) ->
    {HeapWords, Floats, Funs} = heap_alloc_1(Heap),

    St = St0#st{h=HeapWords,hf=Floats,hl=Funs},

    Vst#vst{current=St}.

heap_alloc_1({alloc, Alloc}) ->
    heap_alloc_2(Alloc, 0, 0, 0);
heap_alloc_1(HeapWords) when is_integer(HeapWords) ->
    {HeapWords, 0, 0}.

heap_alloc_2([{words, HeapWords} | T], 0, Floats, Funs) ->
    heap_alloc_2(T, HeapWords, Floats, Funs);
heap_alloc_2([{floats, Floats} | T], HeapWords, 0, Funs) ->
    heap_alloc_2(T, HeapWords, Floats, Funs);
heap_alloc_2([{funs, Funs} | T], HeapWords, Floats, 0) ->
    heap_alloc_2(T, HeapWords, Floats, Funs);
heap_alloc_2([], HeapWords, Floats, Funs) ->
    {HeapWords, Floats, Funs}.

schedule_out(Live, Vst0) when is_integer(Live) ->
    Vst1 = prune_x_regs(Live, Vst0),
    Vst2 = kill_heap_allocation(Vst1),
    Vst = kill_fregs(Vst2),
    update_receive_state(none, Vst).

prune_x_regs(Live, #vst{current=St0}=Vst) when is_integer(Live) ->
    #st{fragile=Fragile0,xs=Xs0} = St0,
    Fragile = sets:filter(fun({x,X}) ->
                                  X < Live;
                             ({y,_}) ->
                                  true
                         end, Fragile0),
    Xs = maps:filter(fun({x,X}, _) ->
                             X < Live
                     end, Xs0),
    St = St0#st{fragile=Fragile,xs=Xs},
    Vst#vst{current=St}.

%% All choices in a select_val list must be integers, floats, or atoms.
%% All must be of the same type.
assert_choices([{Tag,_},{f,_}|T]) ->
    if
        Tag =:= atom; Tag =:= float; Tag =:= integer ->
            assert_choices_1(T, Tag);
        true ->
            error(bad_select_list)
    end;
assert_choices([]) -> ok.

assert_choices_1([{Tag,_},{f,_}|T], Tag) ->
    assert_choices_1(T, Tag);
assert_choices_1([_,{f,_}|_], _Tag) ->
    error(bad_select_list);
assert_choices_1([], _Tag) -> ok.

assert_arities([Arity,{f,_}|T]) when is_integer(Arity) ->
    assert_arities(T);
assert_arities([]) -> ok;
assert_arities(_) -> error(bad_tuple_arity_list).

is_float_arith_bif(fadd, [_, _]) -> true;
is_float_arith_bif(fdiv, [_, _]) -> true;
is_float_arith_bif(fmul, [_, _]) -> true;
is_float_arith_bif(fnegate, [_]) -> true;
is_float_arith_bif(fsub, [_, _]) -> true;
is_float_arith_bif(_, _) -> false.

validate_float_arith_bif(Ss, Dst, Vst) ->
    _ = [assert_freg_set(S, Vst) || S <- Ss],
    set_freg(Dst, Vst).

init_fregs() -> 0.

kill_fregs(#vst{current=St0}=Vst) ->
    St = St0#st{f=init_fregs()},
    Vst#vst{current=St}.

set_freg({fr,Fr}=Freg, #vst{current=#st{f=Fregs0}=St}=Vst) ->
    check_limit(Freg),
    Bit = 1 bsl Fr,
    if
	Fregs0 band Bit =:= 0 ->
	    Fregs = Fregs0 bor Bit,
	    Vst#vst{current=St#st{f=Fregs}};
	true -> Vst
    end;
set_freg(Fr, _) -> error({bad_target,Fr}).

assert_freg_set({fr,Fr}=Freg, #vst{current=#st{f=Fregs}})
  when is_integer(Fr), 0 =< Fr ->
    if
	(Fregs bsr Fr) band 1 =:= 0 ->
	    error({uninitialized_reg,Freg});
	true ->
	    ok
    end;
assert_freg_set(Fr, _) -> error({bad_source,Fr}).

%%% Maps

%% A single item list may be either a list or a register.
%%
%% A list with more than item must contain unique literals.
%%
%% An empty list is not allowed.

assert_unique_map_keys([]) ->
    %% There is no reason to use the get_map_elements and
    %% has_map_fields instructions with empty lists.
    error(empty_field_list);
assert_unique_map_keys([_]) ->
    ok;
assert_unique_map_keys([_,_|_]=Ls) ->
    Vs = [begin
              assert_literal(L),
              L
          end || L <- Ls],
    case length(Vs) =:= sets:size(sets:from_list(Vs, [{version, 2}])) of
        true -> ok;
        false -> error(keys_not_unique)
    end.

bsm_stride({integer, Size}, Unit) ->
    Size * Unit;
bsm_stride(_Size, Unit) ->
    Unit.

bsm_size_unit({integer, Size}, Unit) ->
    max(1, Size) * max(1, Unit);
bsm_size_unit(_Size, Unit) ->
    max(1, Unit).

validate_select_val(_Fail, _Choices, _Src, #vst{current=none}=Vst) ->
    %% We've already branched on all of Src's possible values, so we know we
    %% can't reach the fail label or any of the remaining choices.
    Vst;
validate_select_val(Fail, [Val,{f,L}|T], Src, Vst0) ->
    Vst = branch(L, Vst0,
                 fun(BranchVst) ->
                         update_eq_types(Src, Val, BranchVst)
                 end,
                 fun(FailVst) ->
                         update_ne_types(Src, Val, FailVst)
                 end),
    validate_select_val(Fail, T, Src, Vst);
validate_select_val(Fail, [], _Src, Vst) ->
    branch(Fail, Vst,
           fun(SuccVst) ->
                   %% The next instruction is never executed.
                   kill_state(SuccVst)
           end).

validate_select_tuple_arity(_Fail, _Choices, _Src, #vst{current=none}=Vst) ->
    %% We've already branched on all of Src's possible values, so we know we
    %% 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 = #t_tuple{exact=true,size=Arity},
    Vst = branch(L, Vst0,
                 fun(BranchVst) ->
                         update_type(fun meet/2, Type, Tuple, BranchVst)
                 end,
                 fun(FailVst) ->
                         update_type(fun subtract/2, Type, Tuple, FailVst)
                 end),
    validate_select_tuple_arity(Fail, T, Tuple, Vst);
validate_select_tuple_arity(Fail, [], _, #vst{}=Vst) ->
    branch(Fail, Vst,
           fun(SuccVst) ->
                   %% The next instruction is never executed.
                   kill_state(SuccVst)
           end).

%%
%% Infers types from comparisons, looking at the expressions that produced the
%% compared values and updates their types if we've learned something new from
%% the comparison.
%%

infer_types(CompareOp, {Kind,_}=LHS, RHS, Vst) when Kind =:= x; Kind =:= y ->
    infer_types(CompareOp, get_reg_vref(LHS, Vst), RHS, Vst);
infer_types(CompareOp, LHS, {Kind,_}=RHS, Vst) when Kind =:= x; Kind =:= y ->
    infer_types(CompareOp, LHS, get_reg_vref(RHS, Vst), Vst);
infer_types(CompareOp, LHS, RHS, #vst{current=#st{vs=Vs}}=Vst0) ->
    case Vs of
        #{ LHS := LEntry, RHS := REntry } ->
            Vst = infer_types_1(LEntry, RHS, CompareOp, Vst0),
            infer_types_1(REntry, LHS, CompareOp, Vst);
        #{ LHS := LEntry } ->
            infer_types_1(LEntry, RHS, CompareOp, Vst0);
        #{ RHS := REntry } ->
            infer_types_1(REntry, LHS, CompareOp, Vst0);
        #{} ->
            Vst0
    end.

infer_types_1(#value{op={bif,'=:='},args=[LHS,RHS]}, Val, Op, Vst) ->
    case Val of
        {atom, Bool} when Op =:= eq_exact, Bool; Op =:= ne_exact, not Bool ->
            update_eq_types(LHS, RHS, Vst);
        {atom, Bool} when Op =:= ne_exact, Bool; Op =:= eq_exact, not Bool ->
            update_ne_types(LHS, RHS, Vst);
        _ ->
            Vst
    end;
infer_types_1(#value{op={bif,'=/='},args=[LHS,RHS]}, Val, Op, Vst) ->
    case Val of
        {atom, Bool} when Op =:= ne_exact, Bool; Op =:= eq_exact, not Bool ->
            update_ne_types(LHS, RHS, Vst);
        {atom, Bool} when Op =:= eq_exact, Bool; Op =:= ne_exact, not Bool ->
            update_eq_types(LHS, RHS, Vst);
        _ ->
            Vst
    end;
infer_types_1(#value{op={bif,is_atom},args=[Src]}, Val, Op, Vst) ->
    infer_type_test_bif(#t_atom{}, Src, Val, Op, Vst);
infer_types_1(#value{op={bif,is_boolean},args=[Src]}, Val, Op, Vst) ->
    infer_type_test_bif(beam_types:make_boolean(), Src, Val, Op, Vst);
infer_types_1(#value{op={bif,is_binary},args=[Src]}, Val, Op, Vst) ->
    infer_type_test_bif(#t_bitstring{size_unit=8}, Src, Val, Op, Vst);
infer_types_1(#value{op={bif,is_bitstring},args=[Src]}, Val, Op, Vst) ->
    infer_type_test_bif(#t_bitstring{}, Src, Val, Op, Vst);
infer_types_1(#value{op={bif,is_float},args=[Src]}, Val, Op, Vst) ->
    infer_type_test_bif(#t_float{}, Src, Val, Op, Vst);
infer_types_1(#value{op={bif,is_function},args=[Src,{integer,Arity}]},
              Val, Op, Vst)
  when Arity >= 0, Arity =< ?MAX_FUNC_ARGS ->
    infer_type_test_bif(#t_fun{arity=Arity}, Src, Val, Op, Vst);
infer_types_1(#value{op={bif,is_function},args=[Src,_Arity]}, Val, Op, Vst) ->
    case Val of
        {atom, Bool} when Op =:= eq_exact, Bool; Op =:= ne_exact, not Bool ->
            update_type(fun meet/2, #t_fun{}, Src, Vst);
        _ ->
            %% We cannot subtract the function type when the arity is unknown:
            %% `Src` may still be a function if the arity is outside the
            %% allowed range.
            Vst
    end;
infer_types_1(#value{op={bif,is_function},args=[Src]}, Val, Op, Vst) ->
    infer_type_test_bif(#t_fun{}, Src, Val, Op, Vst);
infer_types_1(#value{op={bif,is_integer},args=[Src]}, Val, Op, Vst) ->
    infer_type_test_bif(#t_integer{}, Src, Val, Op, Vst);
infer_types_1(#value{op={bif,is_list},args=[Src]}, Val, Op, Vst) ->
    infer_type_test_bif(#t_list{}, Src, Val, Op, Vst);
infer_types_1(#value{op={bif,is_map},args=[Src]}, Val, Op, Vst) ->
    infer_type_test_bif(#t_map{}, Src, Val, Op, Vst);
infer_types_1(#value{op={bif,is_number},args=[Src]}, Val, Op, Vst) ->
    infer_type_test_bif(number, Src, Val, Op, Vst);
infer_types_1(#value{op={bif,is_pid},args=[Src]}, Val, Op, Vst) ->
    infer_type_test_bif(pid, Src, Val, Op, Vst);
infer_types_1(#value{op={bif,is_port},args=[Src]}, Val, Op, Vst) ->
    infer_type_test_bif(port, Src, Val, Op, Vst);
infer_types_1(#value{op={bif,is_reference},args=[Src]}, Val, Op, Vst) ->
    infer_type_test_bif(reference, Src, Val, Op, Vst);
infer_types_1(#value{op={bif,is_tuple},args=[Src]}, Val, Op, Vst) ->
    infer_type_test_bif(#t_tuple{}, Src, Val, Op, Vst);
infer_types_1(#value{op={bif,tuple_size}, args=[Tuple]},
              {integer,Arity}, Op, Vst) ->
    Type = #t_tuple{exact=true,size=Arity},
    case Op of
        eq_exact -> update_type(fun meet/2, Type, Tuple, Vst);
        ne_exact -> update_type(fun subtract/2, Type, Tuple, Vst)
    end;
infer_types_1(#value{op={bif,element},args=[{integer,Index}, Tuple]},
              Val, eq_exact, Vst)
  when Index >= 1 ->
    ValType = get_term_type(Val, Vst),
    Es = beam_types:set_tuple_element(Index, ValType, #{}),
    TupleType = #t_tuple{size=Index,elements=Es},
    update_type(fun meet/2, TupleType, Tuple, Vst);
infer_types_1(#value{op={bif,element},args=[{integer,Index}, Tuple]},
              Val, ne_exact, Vst)
  when Index >= 1 ->
    %% Subtraction is only safe with singletons, see update_ne_types/3 for
    %% details.
    ValType = get_term_type(Val, Vst),
    case beam_types:is_singleton_type(ValType) of
        true ->
            case beam_types:set_tuple_element(Index, ValType, #{}) of
                #{ Index := _ }=Es ->
                    TupleType = #t_tuple{size=Index,elements=Es},
                    update_type(fun subtract/2, TupleType, Tuple, Vst);
                #{} ->
                    %% Index was above the element limit; subtraction is not
                    %% safe.
                    Vst
            end;
        false ->
            Vst
    end;
infer_types_1(_, _, _, Vst) ->
    Vst.

infer_type_test_bif(Type, Src, Val, Op, Vst) ->
    case Val of
        {atom, Bool} when Op =:= eq_exact, Bool; Op =:= ne_exact, not Bool ->
            update_type(fun meet/2, Type, Src, Vst);
        {atom, Bool} when Op =:= ne_exact, Bool; Op =:= eq_exact, not Bool ->
            update_type(fun subtract/2, Type, Src, Vst);
        _ ->
            Vst
    end.

%%%
%%% Keeping track of types.
%%%

%% Assigns Src to Dst and marks them as aliasing each other.
assign({y,_}=Src, {y,_}=Dst, Vst) ->
    %% The stack trimming optimization may generate a move from an initialized
    %% but unassigned Y register to another Y register.
    case get_raw_type(Src, Vst) of
        initialized -> create_tag(initialized, init, [], Dst, Vst);
        _ -> assign_1(Src, Dst, Vst)
    end;
assign({Kind,_}=Src, Dst, Vst) when Kind =:= x; Kind =:= y ->
    assign_1(Src, Dst, Vst);
assign(Literal, Dst, Vst) ->
    Type = get_literal_type(Literal),
    create_term(Type, move, [Literal], Dst, Vst).

%% Creates a special tag value that isn't a regular term, such as 'initialized'
%% or 'catchtag'
create_tag(Tag, _Op, _Ss, {y,_}=Dst, #vst{current=#st{ys=Ys0}=St0}=Vst) ->
    case maps:get(Dst, Ys0, uninitialized) of
        {catchtag,_}=Prev ->
            error(Prev);
        {trytag,_}=Prev ->
            error(Prev);
        _ ->
            check_try_catch_tags(Tag, Dst, Vst),
            Ys = Ys0#{ Dst => Tag },
            St = St0#st{ys=Ys},
            remove_fragility(Dst, Vst#vst{current=St})
    end;
create_tag(_Tag, _Op, _Ss, Dst, _Vst) ->
    error({invalid_tag_register, Dst}).

%% Wipes a special tag, leaving the register initialized but empty.
kill_tag({y,_}=Reg, #vst{current=#st{ys=Ys0}=St0}=Vst) ->
    _ = get_tag_type(Reg, Vst),                 %Assertion.
    Ys = Ys0#{ Reg => initialized },
    Vst#vst{current=St0#st{ys=Ys}}.

%% Creates a completely new term with the given type.
create_term(Type, Op, Ss0, Dst, Vst0) ->
    create_term(Type, Op, Ss0, Dst, Vst0, Vst0).

%% As create_term/4, but uses the incoming Vst for argument resolution in
%% case x-regs have been pruned and the sources can no longer be found.
create_term(Type, Op, Ss0, Dst, Vst0, OrigVst) ->
    {Ref, Vst1} = new_value(Type, Op, resolve_args(Ss0, OrigVst), Vst0),
    Vst = remove_fragility(Dst, Vst1),
    set_reg_vref(Ref, Dst, Vst).

%% Extracts a term from Ss, propagating fragility.
extract_term(Type, Op, Ss0, Dst, Vst0) ->
    extract_term(Type, Op, Ss0, Dst, Vst0, Vst0).

%% As extract_term/4, but uses the incoming Vst for argument resolution in
%% case x-regs have been pruned and the sources can no longer be found.
extract_term(Type, Op, Ss0, Dst, Vst0, OrigVst) ->
    {Ref, Vst1} = new_value(Type, Op, resolve_args(Ss0, OrigVst), Vst0),
    Vst = propagate_fragility(Dst, Ss0, Vst1),
    set_reg_vref(Ref, Dst, Vst).

%% Translates instruction arguments into the argument() type, decoupling them
%% from their registers, allowing us to infer their types after they've been
%% clobbered or moved.
resolve_args([{Kind,_}=Src | Args], Vst) when Kind =:= x; Kind =:= y ->
    [get_reg_vref(Src, Vst) | resolve_args(Args, Vst)];
resolve_args([Lit | Args], Vst) ->
    assert_literal(Lit),
    [Lit | resolve_args(Args, Vst)];
resolve_args([], _Vst) ->
    [].

%% Overrides the type of Reg. This is ugly but a necessity for certain
%% destructive operations.
override_type(Type, Reg, Vst) ->
    update_type(fun(_, T) -> T end, Type, Reg, Vst).

%% This is used when linear code finds out more and more information about a
%% type, so that the type gets more specialized.
update_type(Merge, With, #value_ref{}=Ref, Vst0) ->
    %% If the old type can't be merged with the new one, the type information
    %% is inconsistent and we know that some instructions will never be
    %% executed at run-time. For example:
    %%
    %%   {test,is_list,Fail,[Reg]}.
    %%   {test,is_tuple,Fail,[Reg]}.
    %%   {test,test_arity,Fail,[Reg,5]}.
    %%
    %% Note that the test_arity instruction can never be reached, so we need to
    %% kill the state to avoid raising an error when we encounter it.
    %%
    %% Simply returning `kill_state(Vst)` is unsafe however as we might be in
    %% the middle of an instruction, and altering the rest of the validator
    %% (eg. prune_x_regs/2) to no-op on dead states is prone to error.
    %%
    %% We therefore throw a 'type_conflict' error instead, which causes
    %% validation to fail unless we're in a context where such errors can be
    %% handled, such as in a branch handler.
    Current = get_concrete_type(Ref, Vst0),
    case Merge(Current, With) of
        none ->
            throw({type_conflict, Current, With});
        Type ->
            Vst = update_container_type(Type, Ref, Vst0),
            set_type(Type, Ref, Vst)
    end;
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) ->
    %% Literals always retain their type, but we still need to bail on type
    %% conflicts.
    Type = get_literal_type(Literal),
    case Merge(Type, With) of
        none -> throw({type_conflict, Type, With});
        _Type -> Vst
    end.

%% Updates the container the given value was extracted from, if any.
update_container_type(Type, Ref, #vst{current=#st{vs=Vs}}=Vst) ->
    case Vs of
        #{ Ref := #value{op={bif,element},
                         args=[{integer,Index},Tuple]} } when Index >= 1 ->
            Es = beam_types:set_tuple_element(Index, Type, #{}),
            TupleType = #t_tuple{size=Index,elements=Es},
            update_type(fun meet/2, TupleType, Tuple, Vst);
        #{} ->
            Vst
    end.

update_eq_types(LHS, RHS, Vst0) ->
    LType = get_term_type(LHS, Vst0),
    RType = get_term_type(RHS, Vst0),

    Vst1 = update_type(fun meet/2, RType, LHS, Vst0),
    Vst = update_type(fun meet/2, LType, RHS, Vst1),

    infer_types(eq_exact, LHS, RHS, Vst).

update_ne_types(LHS, RHS, Vst0) ->
    Vst1 = update_ne_types_1(LHS, RHS, Vst0),
    Vst = update_ne_types_1(RHS, LHS, Vst1),

    infer_types(ne_exact, LHS, RHS, Vst).

update_ne_types_1(LHS, RHS, Vst0) ->
    %% 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 =/= #t_integer{}`; all we know is that LHS isn't equal
    %% to some *specific integer* of unknown value, and if we were to subtract
    %% #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, Vst0),
    case beam_types:is_singleton_type(RType) of
        true ->
            Vst = update_type(fun subtract/2, RType, LHS, Vst0),

            %% If LHS has a specific value after subtraction we can infer types
            %% as if we've made an exact match, which is much stronger than
            %% ne_exact.
            LType = get_term_type(LHS, Vst),
            case beam_types:get_singleton_value(LType) of
                {ok, Value} ->
                    infer_types(eq_exact, LHS, value_to_literal(Value), Vst);
                error ->
                    Vst
            end;
        false ->
            Vst0
    end.

%% Helper functions for the above.

assign_1(Src, Dst, Vst0) ->
    assert_movable(Src, Vst0),
    Vst = propagate_fragility(Dst, [Src], Vst0),
    set_reg_vref(get_reg_vref(Src, Vst), Dst, Vst).

set_reg_vref(Ref, {x,_}=Dst, Vst) ->
    check_limit(Dst),
    #vst{current=#st{xs=Xs0}=St0} = Vst,
    St = St0#st{xs=Xs0#{ Dst => Ref }},
    Vst#vst{current=St};
set_reg_vref(Ref, {y,_}=Dst, #vst{current=#st{ys=Ys0}=St0} = Vst) ->
    check_limit(Dst),
    case Ys0 of
        #{ Dst := {catchtag,_}=Tag } ->
            error(Tag);
        #{ Dst := {trytag,_}=Tag } ->
            error(Tag);
        #{ Dst := _ } ->
            St = St0#st{ys=Ys0#{ Dst => Ref }},
            Vst#vst{current=St};
        #{} ->
            %% Storing into a non-existent Y register means that we haven't set
            %% up a (sufficiently large) stack.
            error({invalid_store, Dst})
    end;
set_reg_vref(_Ref, Dst, _Vst) ->
    error({invalid_register, Dst}).

get_reg_vref({x,_}=Src, #vst{current=#st{xs=Xs}}) ->
    check_limit(Src),
    case Xs of
        #{ Src := #value_ref{}=Ref } ->
            Ref;
        #{} ->
            error({uninitialized_reg, Src})
    end;
get_reg_vref({y,_}=Src, #vst{current=#st{ys=Ys}}) ->
    check_limit(Src),
    case Ys of
        #{ Src := #value_ref{}=Ref } ->
            Ref;
        #{ Src := initialized } ->
            error({unassigned, Src});
        #{ Src := Tag } when Tag =/= uninitialized ->
            error(Tag);
        #{} ->
            error({uninitialized_reg, Src})
    end;
get_reg_vref(Src, _Vst) ->
    error({invalid_register, Src}).

set_type(Type, #value_ref{}=Ref, #vst{current=#st{vs=Vs0}=St}=Vst) ->
    #{ Ref := #value{}=Entry } = Vs0,
    Vs = Vs0#{ Ref => Entry#value{type=Type} },
    Vst#vst{current=St#st{vs=Vs}}.

new_value(none, _, _, _) ->
    error(creating_none_value);
new_value(Type, Op, Ss, #vst{current=#st{vs=Vs0}=St,ref_ctr=Counter}=Vst) ->
    Ref = #value_ref{id=Counter},
    Vs = Vs0#{ Ref => #value{op=Op,args=Ss,type=Type} },

    {Ref, Vst#vst{current=St#st{vs=Vs},ref_ctr=Counter+1}}.

kill_catch_tag(Reg, #vst{current=#st{ct=[Tag|Tags]}=St}=Vst0) ->
    Vst = Vst0#vst{current=St#st{ct=Tags}},
    Tag = get_tag_type(Reg, Vst),               %Assertion.
    kill_tag(Reg, Vst).

check_try_catch_tags(Type, {y,N}=Reg, Vst) ->
    %% Every catch or try/catch must use a lower Y register number than any
    %% enclosing catch or try/catch. That will ensure that when the stack is
    %% scanned when an exception occurs, the innermost try/catch tag is found
    %% first.
    case is_try_catch_tag(Type) of
        true ->
            case collect_try_catch_tags(N - 1, Vst, []) of
                [_|_]=Bad -> error({bad_try_catch_nesting, Reg, Bad});
                [] -> ok
            end;
        false ->
            ok
    end.

assert_no_exception(?EXCEPTION_LABEL) ->
    error(throws_exception);
assert_no_exception(_) ->
    ok.

assert_term(Src, Vst) ->
    _ = get_term_type(Src, Vst),
    ok.

assert_movable(Src, Vst) ->
    _ = get_movable_term_type(Src, Vst),
    ok.

assert_literal(Src) ->
    case is_literal(Src) of
        true -> ok;
        false -> error({literal_required,Src})
    end.

assert_not_literal(Src) ->
    case is_literal(Src) of
        true -> error({literal_not_allowed,Src});
        false -> ok
    end.

is_literal(nil) -> true;
is_literal({atom,A}) when is_atom(A) -> true;
is_literal({float,F}) when is_float(F) -> true;
is_literal({integer,I}) when is_integer(I) -> true;
is_literal({literal,_L}) -> true;
is_literal(_) -> false.

value_to_literal([]) -> nil;
value_to_literal(A) when is_atom(A) -> {atom,A};
value_to_literal(F) when is_float(F) -> {float,F};
value_to_literal(I) when is_integer(I) -> {integer,I};
value_to_literal(Other) -> {literal,Other}.

%% These are just wrappers around their equivalents in beam_types, which
%% handle the validator-specific #t_abstract{} type.
%%
%% The funny-looking abstract types produced here are intended to provoke
%% errors on actual use; they do no harm just lying around.

normalize(#t_abstract{}=A) -> error({abstract_type, A});
normalize(T) -> beam_types:normalize(T).

join(Same, Same) ->
    Same;
join(#t_abstract{kind={ms_position, UnitA}},
     #t_abstract{kind={ms_position, UnitB}}) ->
    #t_abstract{kind={ms_position, gcd(UnitA, UnitB)}};
join(#t_abstract{}=A, B) ->
    #t_abstract{kind={join, A, B}};
join(A, #t_abstract{}=B) ->
    #t_abstract{kind={join, A, B}};
join(A, B) ->
    beam_types:join(A, B).

meet(Same, Same) ->
    Same;
meet(#t_abstract{kind={ms_position, UnitA}},
     #t_abstract{kind={ms_position, UnitB}}) ->
    Unit = UnitA * UnitB div gcd(UnitA, UnitB),
    #t_abstract{kind={ms_position, Unit}};
meet(#t_abstract{}=A, B) ->
    #t_abstract{kind={meet, A, B}};
meet(A, #t_abstract{}=B) ->
    #t_abstract{kind={meet, A, B}};
meet(A, B) ->
    beam_types:meet(A, B).

subtract(#t_abstract{}=A, B) ->
    #t_abstract{kind={subtract, A, B}};
subtract(A, #t_abstract{}=B) ->
    #t_abstract{kind={subtract, A, B}};
subtract(A, B) ->
    beam_types:subtract(A, B).

assert_type(RequiredType, Term, Vst) ->
    GivenType = get_movable_term_type(Term, Vst),
    case meet(RequiredType, GivenType) of
        GivenType ->
            ok;
        _RequiredType ->
            error({bad_type,{needed,RequiredType},{actual,GivenType}})
    end.

validate_src(Ss, Vst) when is_list(Ss) ->
    _ = [assert_term(S, Vst) || S <- Ss],
    ok.

unpack_typed_arg(#tr{r=Reg}) ->
    Reg;
unpack_typed_arg(Arg) ->
    Arg.

%% get_term_type(Src, ValidatorState) -> Type
%%  Gets the type of the source Src, resolving deferred types into
%%  a concrete type.
get_concrete_type(Src, #vst{current=#st{vs=Vs}}=Vst) ->
    concrete_type(get_raw_type(Src, Vst), Vs).

%% get_term_type(Src, ValidatorState) -> Type
%%  Get the type of the source Src. The returned type Type will be
%%  a standard Erlang type (no catch/try tags or match contexts).

get_term_type(Src, Vst) ->
    case get_movable_term_type(Src, Vst) of
        #t_bs_context{} -> error({match_context,Src});
        #t_abstract{} -> error({abstract_term,Src});
        Type -> Type
    end.

%% get_movable_term_type(Src, ValidatorState) -> Type
%%  Get the type of the source Src. The returned type Type will be
%%  a standard Erlang type (no catch/try tags). Match contexts are OK.

get_movable_term_type(Src, Vst) ->
    case get_concrete_type(Src, Vst) of
        #t_abstract{kind=unfinished_tuple=Kind} -> error({Kind,Src});
        initialized -> error({unassigned,Src});
        uninitialized -> error({uninitialized_reg,Src});
        {catchtag,_} -> error({catchtag,Src});
        {trytag,_} -> error({trytag,Src});
        Type -> Type
    end.

%% get_tag_type(Src, ValidatorState) -> Type
%%  Return the tag type of a Y register, erroring out if it contains a term.

get_tag_type({y,_}=Src, Vst) ->
    case get_raw_type(Src, Vst) of
        {catchtag, _}=Tag -> Tag;
        {trytag, _}=Tag -> Tag;
        uninitialized=Tag -> Tag;
        initialized=Tag -> Tag;
        Other -> error({invalid_tag,Src,Other})
    end;
get_tag_type(Src, _) ->
    error({invalid_tag_register,Src}).

%% get_raw_type(Src, ValidatorState) -> Type
%%  Return the type of a register without doing any validity checks or
%%  conversions.
get_raw_type({x,X}=Src, #vst{current=#st{xs=Xs}}=Vst) when is_integer(X) ->
    check_limit(Src),
    case Xs of
        #{ Src := #value_ref{}=Ref } -> get_raw_type(Ref, Vst);
        #{} -> uninitialized
    end;
get_raw_type({y,Y}=Src, #vst{current=#st{ys=Ys}}=Vst) when is_integer(Y) ->
    check_limit(Src),
    case Ys of
        #{ Src := #value_ref{}=Ref } -> get_raw_type(Ref, Vst);
        #{ Src := Tag } -> Tag;
        #{} -> uninitialized
    end;
get_raw_type(#value_ref{}=Ref, #vst{current=#st{vs=Vs}}) ->
    case Vs of
        #{ Ref := #value{type=Type} } -> Type;
        #{} -> none
    end;
get_raw_type(Src, #vst{current=#st{}}) ->
    get_literal_type(Src).

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
%%%

%% Forks the execution flow, with the provided funs returning the new state of
%% their respective branch; the "fail" fun returns the state where the branch
%% is taken, and the "success" fun returns the state where it's not.
%%
%% If either path is known not to be taken at runtime (eg. due to a type
%% conflict or argument errors), it will simply be discarded.
-spec branch(Lbl :: label(),
             Original :: #vst{},
             FailFun :: BranchFun,
             SuccFun :: BranchFun) -> #vst{} when
     BranchFun :: fun((#vst{}) -> #vst{}).
branch(Lbl, Vst0, FailFun, SuccFun) ->
    validate_branch(Lbl, Vst0),
    #vst{current=St0} = Vst0,

    try FailFun(Vst0) of
        Vst1 ->
            Vst2 = fork_state(Lbl, Vst1),
            Vst = Vst2#vst{current=St0},
            try SuccFun(Vst) of
                V -> V
            catch
                %% The instruction is guaranteed to fail; kill the state.
                {type_conflict, _, _} ->
                    kill_state(Vst);
                {invalid_argument, _} ->
                    kill_state(Vst)
            end
    catch
        %% This instruction is guaranteed not to fail, so we run the success
        %% branch *without* catching further errors to avoid hiding bugs in the
        %% validator itself; one of the branches must succeed.
        {type_conflict, _, _} ->
            SuccFun(Vst0);
        {invalid_argument, _} ->
            SuccFun(Vst0)
    end.

validate_branch(Lbl, #vst{current=#st{ct=Tags}}) ->
    validate_branch_1(Lbl, Tags).

validate_branch_1(Lbl, [{trytag, FailLbls} | Tags]) ->
    %% 'try_case' assumes that an exception has been thrown, so a direct branch
    %% will crash the emulator.
    %%
    %% (Jumping to a 'catch_end' is fine however as it will simply nop in the
    %% absence of an exception.)
    case ordsets:is_element(Lbl, FailLbls) of
        true -> error({illegal_branch, try_handler, Lbl});
        false -> validate_branch_1(Lbl, Tags)
    end;
validate_branch_1(Lbl, [_ | Tags]) ->
    validate_branch_1(Lbl, Tags);
validate_branch_1(_Lbl, _Tags) ->
    ok.

%% A shorthand version of branch/4 for when the state is only altered on
%% success.
branch(Fail, Vst, SuccFun) ->
    branch(Fail, Vst, fun(V) -> V end, SuccFun).

%% Shorthand of branch/4 for when the state is neither altered on failure nor
%% success.
branch(Fail, Vst) ->
    branch(Fail, Vst, fun(V) -> V end).

%% Directly branches off the state. This is an "internal" operation that should
%% be used sparingly.
fork_state(?EXCEPTION_LABEL, Vst0) ->
    #vst{current=#st{ct=CatchTags,numy=NumY}} = Vst0,

    %% The stack will be scanned looking for a catch tag, so all Y registers
    %% must be initialized.
    verify_y_init(Vst0),

    case CatchTags of
        [{_, [Fail]} | _] when is_integer(Fail) ->
            true = Fail =/= ?EXCEPTION_LABEL,   %Assertion.
            true = NumY =/= none,               %Assertion.

            %% Clear the receive marker and fork to our exception handler.
            Vst = update_receive_state(none, Vst0),
            fork_state(Fail, Vst);
        [] ->
            %% No catch handler; the exception leaves the function.
            Vst0;
        _ ->
            error(ambiguous_catch_try_state)
    end;
fork_state(L, #vst{current=St0,branched=Branched0,ref_ctr=Counter0}=Vst) ->
    {St, Counter} = merge_states(L, St0, Branched0, Counter0),
    Branched = Branched0#{ L => St },
    Vst#vst{branched=Branched,ref_ctr=Counter}.

%% merge_states/3 is used when there's more than one way to arrive at a
%% certain point, requiring the states to be merged down to the least
%% common subset for the subsequent code.

merge_states(L, St, Branched, Counter) when L =/= 0 ->
    case Branched of
        #{ L := OtherSt } -> merge_states_1(St, OtherSt, Counter);
        #{} -> {St, Counter}
    end.

merge_states_1(St, none, Counter) ->
    {St, Counter};
merge_states_1(none, St, Counter) ->
    {St, Counter};
merge_states_1(StA, StB, Counter0) ->
    #st{xs=XsA,ys=YsA,vs=VsA,fragile=FragA,numy=NumYA,
        h=HA,ct=CtA,recv_state=RecvStA,
        ms_positions=MsPosA} = StA,
    #st{xs=XsB,ys=YsB,vs=VsB,fragile=FragB,numy=NumYB,
        h=HB,ct=CtB,recv_state=RecvStB,
        ms_positions=MsPosB} = StB,

    %% When merging registers we drop all registers that aren't defined in both
    %% states, and resolve conflicts by creating new values (similar to phi
    %% nodes in SSA).
    %%
    %% While doing this we build a "merge map" detailing which values need to
    %% be kept and which new values need to be created to resolve conflicts.
    %% This map is then used to create a new value database where the types of
    %% all values have been joined.
    {Xs, Merge0, Counter1} = merge_regs(XsA, XsB, #{}, Counter0),
    {Ys, Merge, Counter} = merge_regs(YsA, YsB, Merge0, Counter1),
    Vs = merge_values(Merge, VsA, VsB),

    RecvSt = merge_receive_state(RecvStA, RecvStB),
    MsPos = merge_ms_positions(MsPosA, MsPosB, Vs),
    Fragile = merge_fragility(FragA, FragB),
    NumY = merge_stk(NumYA, NumYB),
    Ct = merge_ct(CtA, CtB),

    St = #st{xs=Xs,ys=Ys,vs=Vs,fragile=Fragile,numy=NumY,
             h=min(HA, HB),ct=Ct,recv_state=RecvSt,
             ms_positions=MsPos},

    {St, Counter}.

%% Merges the contents of two register maps, returning the updated "merge map"
%% and the new registers.
merge_regs(RsA, RsB, Merge, Counter) ->
    Keys = if
               map_size(RsA) =< map_size(RsB) -> maps:keys(RsA);
               map_size(RsA) > map_size(RsB) -> maps:keys(RsB)
           end,
    merge_regs_1(Keys, RsA, RsB, #{}, Merge, Counter).

merge_regs_1([Reg | Keys], RsA, RsB, Regs, Merge0, Counter0) ->
    case {RsA, RsB} of
        {#{ Reg := #value_ref{}=RefA }, #{ Reg := #value_ref{}=RefB }} ->
            {Ref, Merge, Counter} = merge_vrefs(RefA, RefB, Merge0, Counter0),
            merge_regs_1(Keys, RsA, RsB, Regs#{ Reg => Ref }, Merge, Counter);
        {#{ Reg := TagA }, #{ Reg := TagB }} ->
            %% Tags describe the state of the register rather than the value it
            %% contains, so if a register contains a tag in one state we have
            %% to merge it as a tag regardless of whether the other state says
            %% it's a value.
            {y, _} = Reg,                       %Assertion.
            merge_regs_1(Keys, RsA, RsB, Regs#{ Reg => merge_tags(TagA,TagB) },
                         Merge0, Counter0);
        {#{}, #{}} ->
            merge_regs_1(Keys, RsA, RsB, Regs, Merge0, Counter0)
    end;
merge_regs_1([], _, _, Regs, Merge, Counter) ->
    {Regs, Merge, Counter}.

merge_tags(Same, Same) ->
    Same;
merge_tags(uninitialized, _) ->
    uninitialized;
merge_tags(_, uninitialized) ->
    uninitialized;
merge_tags({trytag, LblsA}, {trytag, LblsB}) ->
    {trytag, ordsets:union(LblsA, LblsB)};
merge_tags({catchtag, LblsA}, {catchtag, LblsB}) ->
    {catchtag, ordsets:union(LblsA, LblsB)};
merge_tags(_A, _B) ->
    %% All other combinations leave the register initialized. Errors arising
    %% from this will be caught later on.
    initialized.

merge_vrefs(Ref, Ref, Merge, Counter) ->
    %% We have two (potentially) different versions of the same value, so we
    %% should join their types into the same value.
    {Ref, Merge#{ Ref => Ref }, Counter};
merge_vrefs(RefA, RefB, Merge, Counter) ->
    %% We have two different values, so we need to create a new value from
    %% their joined type if we haven't already done so.
    Key = {RefA, RefB},
    case Merge of
        #{ Key := Ref } ->
            {Ref, Merge, Counter};
        #{} ->
            Ref = #value_ref{id=Counter},
            {Ref, Merge#{ Key => Ref }, Counter + 1}
    end.

merge_values(Merge, VsA, VsB) ->
    maps:fold(fun(Spec, New, Acc) ->
                      mv_1(Spec, New, VsA, VsB, Acc)
              end, #{}, Merge).

mv_1(Same, Same, VsA, VsB, Acc0) ->
    %% We're merging different versions of the same value, so it's safe to
    %% reuse old entries if the type's unchanged.
    Entry = case {VsA, VsB} of
                {#{ Same := Entry0 }, #{ Same := Entry0 } } ->
                    Entry0;
                {#{ Same := #value{type=TypeA}=Entry0 },
                 #{ Same := #value{type=TypeB} } } ->
                    ConcreteA = concrete_type(TypeA, VsA),
                    ConcreteB = concrete_type(TypeB, VsB),
                    Entry0#value{type=join(ConcreteA, ConcreteB)}
            end,

    Acc = Acc0#{ Same => Entry },

    %% Type inference may depend on values that are no longer reachable from a
    %% register, so all arguments must be merged into the new state.
    mv_args(Entry#value.args, VsA, VsB, Acc);
mv_1({RefA, RefB}, New, VsA, VsB, Acc) ->
    #value{type=TypeA} = map_get(RefA, VsA),
    #value{type=TypeB} = map_get(RefB, VsB),

    ConcreteA = concrete_type(TypeA, VsA),
    ConcreteB = concrete_type(TypeB, VsB),
    Acc#{ New => #value{op=join,args=[],type=join(ConcreteA, ConcreteB)} }.

concrete_type(T, Vs) when is_function(T) -> T(Vs);
concrete_type(T, _Vs) -> T.

mv_args([#value_ref{}=Arg | Args], VsA, VsB, Acc0) ->
    case Acc0 of
        #{ Arg := _ } ->
            mv_args(Args, VsA, VsB, Acc0);
        #{} ->
            Acc = mv_1(Arg, Arg, VsA, VsB, Acc0),
            mv_args(Args, VsA, VsB, Acc)
    end;
mv_args([_ | Args], VsA, VsB, Acc) ->
    mv_args(Args, VsA, VsB, Acc);
mv_args([], _VsA, _VsB, Acc) ->
    Acc.

merge_fragility(FragileA, FragileB) ->
    sets:union(FragileA, FragileB).

merge_ms_positions(MsPosA, MsPosB, Vs) ->
    Keys = if
               map_size(MsPosA) =< map_size(MsPosB) -> maps:keys(MsPosA);
               map_size(MsPosA) > map_size(MsPosB) -> maps:keys(MsPosB)
           end,
    merge_ms_positions_1(Keys, MsPosA, MsPosB, Vs, #{}).

merge_ms_positions_1([Key | Keys], MsPosA, MsPosB, Vs, Acc) ->
    case {MsPosA, MsPosB} of
        {#{ Key := Pos }, #{ Key := Pos }} when is_map_key(Pos, Vs) ->
            merge_ms_positions_1(Keys, MsPosA, MsPosB, Vs, Acc#{ Key => Pos });
        {#{}, #{}} ->
            merge_ms_positions_1(Keys, MsPosA, MsPosB, Vs, Acc)
    end;
merge_ms_positions_1([], _MsPosA, _MsPosB, _Vs, Acc) ->
    Acc.

merge_receive_state(Same, Same) -> Same;
merge_receive_state(_, _) -> undecided.

merge_stk(S, S) -> S;
merge_stk(_, _) -> undecided.

merge_ct(S, S) -> S;
merge_ct(Ct0, Ct1) -> merge_ct_1(Ct0, Ct1).

merge_ct_1([], []) ->
    [];
merge_ct_1([{trytag, LblsA} | CtA], [{trytag, LblsB} | CtB]) ->
    [{trytag, ordsets:union(LblsA, LblsB)} | merge_ct_1(CtA, CtB)];
merge_ct_1([{catchtag, LblsA} | CtA], [{catchtag, LblsB} | CtB]) ->
    [{catchtag, ordsets:union(LblsA, LblsB)} | merge_ct_1(CtA, CtB)];
merge_ct_1(_, _) ->
    undecided.

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.
    verify_y_init_1(NumY - 1, Vst),
    ok;
verify_y_init(#vst{current=#st{numy=undecided,ys=Ys}}=Vst) ->
    HighestY = maps:fold(fun({y,Y}, _, Acc) -> max(Y, Acc) end, -1, Ys),
    verify_y_init_1(HighestY, Vst);
verify_y_init(#vst{}) ->
    ok.

verify_y_init_1(-1, _Vst) ->
    ok;
verify_y_init_1(Y, Vst) ->
    Reg = {y, Y},
    assert_not_fragile(Reg, Vst),
    case get_raw_type(Reg, Vst) of
        uninitialized -> error({uninitialized_reg,Reg});
        _ -> verify_y_init_1(Y - 1, Vst)
    end.

verify_live(0, _Vst) ->
    ok;
verify_live(Live, Vst) when is_integer(Live), 0 < Live, Live =< 1023 ->
    verify_live_1(Live - 1, Vst);
verify_live(Live, _Vst) ->
    error({bad_number_of_live_regs,Live}).

verify_live_1(-1, _) ->
    ok;
verify_live_1(X, Vst) when is_integer(X) ->
    Reg = {x, X},
    case get_raw_type(Reg, Vst) of
        uninitialized -> error({Reg, not_live});
        _ -> verify_live_1(X - 1, Vst)
    end.

verify_no_ct(#vst{current=#st{numy=none}}) ->
    ok;
verify_no_ct(#vst{current=#st{numy=undecided}}) ->
    error(unknown_size_of_stackframe);
verify_no_ct(#vst{current=St}=Vst) ->
    case collect_try_catch_tags(St#st.numy - 1, Vst, []) of
        [_|_]=Bad -> error({unfinished_catch_try,Bad});
        [] -> ok
    end.

%% Collects all try/catch tags, walking down from the Nth stack position.
collect_try_catch_tags(-1, _Vst, Acc) ->
    Acc;
collect_try_catch_tags(Y, Vst, Acc0) ->
    Tag = get_raw_type({y, Y}, Vst),
    Acc = case is_try_catch_tag(Tag) of
              true -> [{{y, Y}, Tag} | Acc0];
              false -> Acc0
          end,
    collect_try_catch_tags(Y - 1, Vst, Acc).

is_try_catch_tag({catchtag,_}) -> true;
is_try_catch_tag({trytag,_}) -> true;
is_try_catch_tag(_) -> false.

eat_heap(N, #vst{current=#st{h=Heap0}=St}=Vst) ->
    case Heap0-N of
	Neg when Neg < 0 ->
	    error({heap_overflow,{left,Heap0},{wanted,N}});
	Heap ->
	    Vst#vst{current=St#st{h=Heap}}
    end.

eat_heap_fun(#vst{current=#st{hl=HeapFuns0}=St}=Vst) ->
    case HeapFuns0-1 of
	Neg when Neg < 0 ->
	    error({heap_overflow,{left,{HeapFuns0,funs}},{wanted,{1,funs}}});
	HeapFuns ->
	    Vst#vst{current=St#st{hl=HeapFuns}}
    end.

eat_heap_float(#vst{current=#st{hf=HeapFloats0}=St}=Vst) ->
    case HeapFloats0-1 of
	Neg when Neg < 0 ->
	    error({heap_overflow,{left,{HeapFloats0,floats}},{wanted,{1,floats}}});
	HeapFloats ->
	    Vst#vst{current=St#st{hf=HeapFloats}}
    end.

%%%
%%% RECEIVE
%%%

%% When the compiler knows that a message it's matching in a receive can't
%% exist before a certain point (e.g. it matches a newly created ref), it will
%% use "receive markers" to tell the corresponding loop_rec where to start
%% looking.
%%
%% Since receive markers affect the next loop_rec instruction it's very
%% important that we properly exit the receive loop the mark is intended for,
%% either through timing out or matching a message. Should we return from the
%% function or enter a different receive loop, we risk skipping messages that
%% should have been matched.
update_receive_state(New0, #vst{current=St0}=Vst) ->
    #st{recv_state=Current} = St0,
    New = case {Current, New0} of
              {none, marked_position} ->
                  marked_position;
              {marked_position, entered_loop} ->
                  entered_loop;
              {none, entered_loop} ->
                  entered_loop;
              {_, none} ->
                  none;
              {_, _} ->
                  error({invalid_receive_state_change, Current, New0})
          end,
    St = St0#st{recv_state=New},
    Vst#vst{current=St}.

%% The loop_rec/2 instruction may return a reference to a term that is not
%% part of the root set. That term or any part of it must not be included in a
%% garbage collection. Therefore, the term (or any part of it) must not be
%% passed to another function, placed in another term, or live in a Y register
%% over an instruction that may GC.
%%
%% Fragility is marked on a per-register (rather than per-value) basis.

%% Marks Reg as fragile.
mark_fragile(Reg, Vst) ->
    #vst{current=#st{fragile=Fragile0}=St0} = Vst,
    Fragile = sets:add_element(Reg, Fragile0),
    St = St0#st{fragile=Fragile},
    Vst#vst{current=St}.

propagate_fragility(Reg, Args, #vst{current=St0}=Vst) ->
    #st{fragile=Fragile0} = St0,

    Sources = sets:from_list(Args, [{version, 2}]),
    Fragile = case sets:is_disjoint(Sources, Fragile0) of
                  true -> sets:del_element(Reg, Fragile0);
                  false -> sets:add_element(Reg, Fragile0)
              end,

    St = St0#st{fragile=Fragile},
    Vst#vst{current=St}.

%% Marks Reg as durable, must be used when assigning a newly created value to
%% a register.
remove_fragility(Reg, Vst) ->
    #vst{current=#st{fragile=Fragile0}=St0} = Vst,
    case sets:is_element(Reg, Fragile0) of
        true ->
            Fragile = sets:del_element(Reg, Fragile0),
            St = St0#st{fragile=Fragile},
            Vst#vst{current=St};
        false ->
            Vst
    end.

%% Marks all registers as durable.
remove_fragility(#vst{current=St0}=Vst) ->
    St = St0#st{fragile=sets:new([{version, 2}])},
    Vst#vst{current=St}.

assert_durable_term(Src, Vst) ->
    assert_term(Src, Vst),
    assert_not_fragile(Src, Vst).

assert_not_fragile({Kind,_}=Src, Vst) when Kind =:= x; Kind =:= y ->
    check_limit(Src),
    #vst{current=#st{fragile=Fragile}} = Vst,
    case sets:is_element(Src, Fragile) of
        true -> error({fragile_message_reference, Src});
        false -> ok
    end;
assert_not_fragile(Lit, #vst{}) ->
    assert_literal(Lit),
    ok.

%%%
%%% Return/argument types of calls and BIFs
%%%

bif_types(Op, Ss, Vst) ->
    Args = [normalize(get_term_type(Arg, Vst)) || Arg <- Ss],
    case {Op,Ss} of
        {element,[_,{literal,Tuple}]} when tuple_size(Tuple) > 0 ->
            case beam_call_types:types(erlang, Op, Args) of
                {any,ArgTypes,Safe} ->
                    RetType = join_tuple_elements(Tuple),
                    {RetType,ArgTypes,Safe};
                Other ->
                    Other
            end;
        {_,_} ->
            beam_call_types:types(erlang, Op, Args)
    end.

join_tuple_elements(Tuple) ->
    join_tuple_elements(tuple_size(Tuple), Tuple, none).

join_tuple_elements(0, _Tuple, Type) ->
    Type;
join_tuple_elements(I, Tuple, Type0) ->
    Type1 = beam_types:make_type_from_value(element(I, Tuple)),
    Type = beam_types:join(Type0, Type1),
    join_tuple_elements(I - 1, Tuple, Type).

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}.

will_bif_succeed(raise, [_,_], _Vst) ->
    %% Compiler-generated raise, the user-facing variant that can return
    %% 'badarg' is erlang:raise/3.
    no;
will_bif_succeed(Op, Ss, Vst) ->
    case is_float_arith_bif(Op, Ss) of
        true ->
            'maybe';
        false ->
            Args = [normalize(get_term_type(Arg, Vst)) || Arg <- Ss],
            beam_call_types:will_succeed(erlang, Op, Args)
    end.

will_call_succeed({extfunc,M,F,A}, _Live, Vst) ->
    beam_call_types:will_succeed(M, F, get_call_args(A, Vst));
will_call_succeed(bs_init_writable, _Live, _Vst) ->
    yes;
will_call_succeed(raw_raise, _Live, _Vst) ->
    no;
will_call_succeed({f,Lbl}, _Live, #vst{ft=Ft}) ->
    case Ft of
        #{Lbl := #{always_fails := true}} ->
            no;
        #{} ->
            'maybe'
    end;
will_call_succeed(apply, Live, Vst) ->
    Mod = get_term_type({x,Live-2}, Vst),
    Name = get_term_type({x,Live-1}, Vst),
    case {Mod,Name} of
        {#t_atom{},#t_atom{}} ->
            'maybe';
        {_,_} ->
            no
    end;
will_call_succeed(_Call, _Live, _Vst) ->
    'maybe'.

get_call_args(Arity, Vst) ->
    get_call_args_1(0, Arity, Vst).

get_call_args_1(Arity, Arity, _) ->
    [];
get_call_args_1(N, Arity, Vst) when N < Arity ->
    ArgType = normalize(get_movable_term_type({x,N}, Vst)),
    [ArgType | get_call_args_1(N + 1, Arity, Vst)].

check_limit({x,X}=Src) when is_integer(X) ->
    if
        %% Note: x(1023) is reserved for use by the BEAM loader.
        0 =< X, X < 1023 -> ok;
        1023 =< X -> error(limit);
        X < 0 -> error({bad_register, Src})
    end;
check_limit({y,Y}=Src) when is_integer(Y) ->
    if
        0 =< Y, Y < 1024 -> ok;
        1024 =< Y -> error(limit);
        Y < 0 -> error({bad_register, Src})
    end;
check_limit({fr,Fr}=Src) when is_integer(Fr) ->
    if
        0 =< Fr, Fr < 1023 -> ok;
        1023 =< Fr -> error(limit);
        Fr < 0 -> error({bad_register, Src})
    end.

min(A, B) when is_integer(A), is_integer(B), A < B -> A;
min(A, B) when is_integer(A), is_integer(B) -> B.

gcd(A, B) ->
    case A rem B of
        0 -> B;
        X -> gcd(B, X)
    end.

error(Error) -> throw(Error).
