%%
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 2000-2025. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%=======================================================================
%% Notes:
%%   1. It does NOT work for .beam files of previous BEAM versions.
%%   2. If handling of new BEAM instructions is needed, this should be 
%%      inserted at the end of function resolve_inst().
%%=======================================================================

-module(beam_disasm).
-moduledoc false.

-export([file/1]). %% the main function
-export([function__code/1, format_error/1]).
-ifdef(DEBUG_DISASM).
-export([dfs/1, df/1, files/1, pp/1, pp/2]).
-endif.

-author("Kostis Sagonas").

-include("beam_opcodes.hrl").
-include("beam_disasm.hrl").
-include("beam_asm.hrl").

%%-----------------------------------------------------------------------

-type index()        :: non_neg_integer().
-type literals()     :: gb_trees:tree(index(), term()).
-type types()        :: gb_trees:tree(index(), term()).
-type symbolic_tag() :: 'a' | 'f' | 'h' | 'i' | 'u' | 'x' | 'y' | 'z'.
-type disasm_tag()   :: symbolic_tag() | 'fr' | 'atom' | 'float' | 'literal'.
-type disasm_term()  :: 'nil' | {disasm_tag(), _}.

-type asm_form() :: {module(),
                     [{atom(), arity()}],
                     [beam_lib:attrib_entry()],
                     [#function{}],
                     beam_lib:label()}.

-export_type([asm_form/0]).

%%-----------------------------------------------------------------------

-define(NO_DEBUG(Str,Xs), ok).
-define(DEBUG(Str,Xs), io:format(Str,Xs)).
-define(exit(Reason), exit({?MODULE,?LINE,Reason})).

%%-----------------------------------------------------------------------
%% Utility functions to get/set their fields. (Uncomment and export
%% them when/if they get used in other files.)
%%-----------------------------------------------------------------------

%% -spec function__name(#function{}) -> atom().
%% function__name(#function{name = N}) -> N.
%% -spec function__arity(#function{}) -> arity().
%% function__arity(#function{arity = A}) -> A.
%% function__entry(#function{entry = E}) -> E.

-spec function__code(#function{}) -> [beam_instr()].
function__code(#function{code = Code}) -> Code.

-spec function__code_update(#function{}, [beam_instr()]) -> #function{}.
function__code_update(Function, NewCode) ->
  Function#function{code = NewCode}.

%%-----------------------------------------------------------------------
%% Error information

-spec format_error({'internal',term()} | {'error',atom(),term()}) -> string().

format_error({internal,Error}) ->
    io_lib:format("~p: disassembly failed with reason ~P.",
		  [?MODULE, Error, 25]);
format_error({error,Module,Error}) ->
    lists:flatten(Module:format_error(Error)).

%%-----------------------------------------------------------------------
%% User comfort functions to directly disassemble to file or to
%% stream, pretty-printed, and to just pretty-print, also commented.
%%-----------------------------------------------------------------------

-ifdef(DEBUG_DISASM).

dfs(Files) when is_list(Files) ->
    lists:foreach(fun df/1, Files).

df(Module) when is_atom(Module) ->
    case code:which(Module) of
	File when is_list(File) ->
	    df(File);
	Reason when is_atom(Reason) ->
	    {error,?MODULE,Reason}
    end;
df(File) when is_list(File) ->
    file(File, filename:rootname(File, ".beam")++".dis").

files(Files) when is_list(Files) ->
    lists:foreach(fun (File) -> file(File, group_leader()) end, Files).

file(File, Dest) ->
    case file(File) of
	#beam_file{code = DisasmCode} ->
	    pp(Dest, [{file,File}, {code,DisasmCode}]);
	Error -> Error
    end.

-spec pp([_]) -> 'ok' | {'error', atom()}.

pp(Disasm) ->
    pp(group_leader(), Disasm).

-spec pp(pid() | file:filename(), [_]) -> 'ok' | {'error', atom()}.

pp(Stream, Disasm) when is_pid(Stream), is_list(Disasm) ->
    NL = io_lib:nl(),
    lists:foreach(
      fun ({code,Code}) ->
	      lists:foreach(
		fun (#function{name=F,arity=A,entry=E,code=C}) ->
			io:format(Stream, "~p.~n", [{function,F,A,E}]),
			lists:foreach(
			  fun (I) -> 
				  io:put_chars(Stream, [pp_instr(I)|NL])
			  end, C),
			io:nl(Stream)
		end, Code);
	  (Item) ->
	      io:format(Stream, "~p.~n~n", [Item])
      end, Disasm),
    ok;
pp(File, Disasm) when is_list(Disasm) ->
    case file:open(File, [write]) of
	{ok,F} ->
	    Result = pp(F, Disasm),
	    ok = file:close(F),
	    Result;
	{error,_Reason} = Error -> Error
    end.

pp_instr({comment,I,Comment}) ->
    [pp_instr(I)|" % "++Comment];
pp_instr({comment,Comment}) ->
    ["%% "++Comment];
pp_instr({label,_}=I) ->
    io_lib:format("  ~p.", [I]);
pp_instr(I) ->
    io_lib:format("    ~p.", [I]).

-endif.

%%-----------------------------------------------------------------------
%% The main exported function
%%   File is either a file name or a binary containing the code.
%%   Call `format_error({error, Module, Reason})' for an error string.
%%-----------------------------------------------------------------------

-spec file(file:filename() | binary()) -> #beam_file{} | {'error',atom(),_}.

file(File) ->
    try process_chunks(File)
    catch error:Reason:Stack ->
            {error,?MODULE,{internal,{Reason,Stack}}}
    end.

%%-----------------------------------------------------------------------
%% Interface might need to be revised -- do not depend on it.
%%-----------------------------------------------------------------------

process_chunks(F) ->
    case beam_lib:chunks(F, [atoms,"Code","StrT",
			     indexed_imports,labeled_exports]) of
	{ok,{Module,
	     [{atoms,AtomsList},{"Code",CodeBin},{"StrT",StrBin},
	      {indexed_imports,ImportsList},{labeled_exports,Exports}]}} ->
	    Atoms = mk_atoms(AtomsList),
	    LambdaBin = optional_chunk(F, "FunT"),
	    Lambdas = beam_disasm_lambdas(LambdaBin, Atoms),
            Literals1 = case optional_chunk(F, literals) of
                            none -> [];
                            Literals0 -> Literals0
                        end,
	    Literals = gb_trees:from_orddict(Literals1),
	    TypeBin = optional_chunk(F, "Type"),
	    Types = beam_disasm_types(TypeBin),
	    Code = beam_disasm_code(CodeBin, Atoms, mk_imports(ImportsList),
				    StrBin, Lambdas, Literals, Types, Module),
	    Attributes =
		case optional_chunk(F, attributes) of
		    none -> [];
		    Atts when is_list(Atts) -> Atts
		end,
	    CompInfo = 
		case optional_chunk(F, "CInf") of
		    none -> [];
		    CompInfoBin when is_binary(CompInfoBin) ->
			binary_to_term(CompInfoBin)
		end,
	    #beam_file{module = Module,
		       labeled_exports = Exports,
		       attributes = Attributes,
		       compile_info = CompInfo,
		       code = Code};
	Error -> Error
    end.

%%-----------------------------------------------------------------------
%% Retrieve an optional chunk or return 'none' if the chunk doesn't exist.
%%-----------------------------------------------------------------------

optional_chunk(F, ChunkTag) ->
    case beam_lib:chunks(F, [ChunkTag]) of
	{ok,{_Module,[{ChunkTag,Chunk}]}} -> Chunk;
	{error,beam_lib,{missing_chunk,_,_}} -> none
    end.

%%-----------------------------------------------------------------------
%% Disassembles the lambda (fun) table of a BEAM file.
%%-----------------------------------------------------------------------

-type l_info() :: {non_neg_integer(), {_,_,_,_,_,_}}.
-spec beam_disasm_lambdas('none' | binary(), gb_trees:tree(index(), _)) ->
        'none' | [l_info()].

beam_disasm_lambdas(none, _) -> none;
beam_disasm_lambdas(<<_:32,Tab/binary>>, Atoms) ->
    disasm_lambdas(Tab, Atoms, 0).

disasm_lambdas(<<F:32,A:32,Lbl:32,Index:32,NumFree:32,OldUniq:32,More/binary>>,
	       Atoms, OldIndex) ->
    Info = {lookup(F, Atoms),A,Lbl,Index,NumFree,OldUniq},
    [{OldIndex,Info}|disasm_lambdas(More, Atoms, OldIndex+1)];
disasm_lambdas(<<>>, _, _) -> [].

%%-----------------------------------------------------------------------
%% Disassembles the type table of a BEAM file.
%%-----------------------------------------------------------------------

-spec beam_disasm_types('none' | binary()) -> types().

beam_disasm_types(none) ->
    gb_trees:empty();
beam_disasm_types(<<Version:32,Count:32,Table0/binary>>) ->
    case beam_types:convert_ext(Version, Table0) of
        none ->
            ?exit({beam_disasm_types,{unknown_type_version,Version}});
        Table ->
            Res = gb_trees:from_orddict(disasm_types(Table, 0)),
            Count = gb_trees:size(Res),                 %Assertion.
            Res
    end;
beam_disasm_types(<<_/binary>>) ->
    gb_trees:empty().

disasm_types(Types0, Index) ->
    case beam_types:decode_ext(Types0) of
        done ->
            [];
        {Types,Rest} ->
            [{Index,Types}|disasm_types(Rest, Index+1)]
    end.

%%-----------------------------------------------------------------------
%% Disassembles the code chunk of a BEAM file:
%%   - The code is first disassembled into a long list of instructions.
%%   - This list is then split into functions and all names are resolved.
%%-----------------------------------------------------------------------

beam_disasm_code(<<_SS:32, % Sub-Size (length of information before code)
		  _IS:32,  % Instruction Set Identifier (always 0)
		  _OM:32,  % Opcode Max
		  _L:32,_F:32,
		  CodeBin/binary>>, Atoms, Imports,
		 Str, Lambdas, Literals, Types, M) ->
    Code = binary_to_list(CodeBin),
    try disasm_code(Code, Atoms, Literals, Types) of
	DisasmCode ->
	    Functions = get_function_chunks(DisasmCode),
	    Labels = mk_labels(local_labels(Functions)),
	    [function__code_update(Function,
				   resolve_names(Is, Imports, Str,
						 Labels, Lambdas, Literals, M))
	     || Function = #function{code=Is} <:- Functions]
    catch
	error:Rsn ->
	    ?NO_DEBUG('code disassembling failed: ~p~n', [Rsn]),
	    ?exit(Rsn)
    end.

%%-----------------------------------------------------------------------

disasm_code([B|Bs], Atoms, Literals, Types) ->
    {Instr,RestBs} = disasm_instr(B, Bs, Atoms, Literals, Types),
    [Instr|disasm_code(RestBs, Atoms, Literals, Types)];
disasm_code([], _, _, _) ->
    [].

%%-----------------------------------------------------------------------
%% Splits the code stream into chunks representing the code of functions.
%%
%% NOTE: code actually looks like
%%   label L1: ... label Ln:
%%     func_info ...
%%   label entry:
%%     ...
%%     <on failure, use label Li to show where things died>
%%     ...
%% So the labels before each func_info should be included as well.
%% Ideally, only one such label is needed, but the BEAM compiler
%% before R8 didn't care to remove the redundant ones.
%%-----------------------------------------------------------------------

get_function_chunks([]) ->
    ?exit(empty_code_segment);
get_function_chunks(Code) ->
    get_funs(labels_r(Code, [])).

labels_r([], R) -> {R, []};
labels_r([{label,_}=I|Is], R) ->
    labels_r(Is, [I|R]);
labels_r([{line,_}=I|Is], R) ->
    labels_r(Is, [I|R]);
labels_r(Is, R) -> {R, Is}.

get_funs({[],[]}) -> [];
get_funs({_,[]}) ->
    ?exit(no_func_info_in_code_segment);
get_funs({LsR0,[{func_info,[{atom,M}=AtomM,{atom,F}=AtomF,ArityArg]}|Code0]})
  when is_atom(M), is_atom(F) ->
    Arity = resolve_arg_unsigned(ArityArg),
    {LsR,Code,RestCode} = get_fun(Code0, []),
    [{label,[{u,Entry}]}|_] = Code,
    [#function{name=F,
	       arity=Arity,
	       entry=Entry,
	       code=lists:reverse(LsR0, [{func_info,AtomM,AtomF,Arity}|Code])}
     |get_funs({LsR,RestCode})].

get_fun([{func_info,_}|_]=Is, R0) ->
    {LsR,R} = labels_r(R0, []),
    {LsR,lists:reverse(R),Is};
get_fun([{int_code_end,[]}], R) ->
    {[],lists:reverse(R),[]};
get_fun([I|Is], R) ->
    get_fun(Is, [I|R]);
get_fun([], R) ->
    ?DEBUG('warning: code segment did not end with int_code_end~n',[]),
    {[],lists:reverse(R),[]}.

%%-----------------------------------------------------------------------
%% Collects local labels -- I am not sure this is 100% what is needed.
%%-----------------------------------------------------------------------

local_labels(Funs) ->
    lists:sort(lists:foldl(fun (F, R) ->
				   local_labels_1(function__code(F), R)
			   end, [], Funs)).

local_labels_1(Code0, R) ->
    Code1 = lists:dropwhile(fun({label,_}) -> true;
			       ({line,_}) -> true;
			       ({func_info,_,_,_}) -> false
			    end, Code0),
    [{func_info,{atom,M},{atom,F},A}|Code] = Code1,
    local_labels_2(Code, R, {M,F,A}).

local_labels_2([{label,[{u,L}]}|Code], R, MFA) ->
    local_labels_2(Code, [{L,MFA}|R], MFA);
local_labels_2(_, R, _) -> R.

%%-----------------------------------------------------------------------
%% Disassembles a single BEAM instruction; most instructions are handled
%% in a generic way; indexing instructions are handled separately.
%%-----------------------------------------------------------------------

disasm_instr(B, Bs, Atoms, Literals, Types) ->
    {SymOp, Arity} = beam_opcodes:opname(B),
    case SymOp of
	select_val ->
	    disasm_select_inst(select_val, Bs, Atoms, Literals, Types);
	select_tuple_arity ->
	    disasm_select_inst(select_tuple_arity, Bs, Atoms, Literals, Types);
	put_map_assoc ->
	    disasm_map_inst(put_map_assoc, Arity, Bs, Atoms, Literals, Types);
	put_map_exact ->
	    disasm_map_inst(put_map_exact, Arity, Bs, Atoms, Literals, Types);
	get_map_elements ->
	    disasm_map_inst(get_map_elements, Arity, Bs, Atoms, Literals, Types);
	has_map_fields ->
	    disasm_map_inst(has_map_fields, Arity, Bs, Atoms, Literals, Types);
	put_tuple2 ->
	    disasm_put_tuple2(Bs, Atoms, Literals, Types);
	make_fun3 ->
	    disasm_make_fun3(Bs, Atoms, Literals, Types);
	init_yregs ->
	    disasm_init_yregs(Bs, Atoms, Literals, Types);
	bs_create_bin ->
	    disasm_bs_create_bin(Bs, Atoms, Literals, Types);
	bs_match ->
	    disasm_bs_match(Bs, Atoms, Literals, Types);
	update_record ->
	    disasm_update_record(Bs, Atoms, Literals, Types);
	_ ->
	    try decode_n_args(Arity, Bs, Atoms, Literals, Types) of
		{Args, RestBs} ->
		    ?NO_DEBUG("instr ~p~n", [{SymOp, Args}]),
		    {{SymOp, Args}, RestBs}
	    catch
		error:Rsn ->
		    ?NO_DEBUG("decode_n_args(~p,~p) failed~n", [Arity, Bs]),
		    ?exit({cannot_disasm_instr, {SymOp, Arity, Rsn}})
	    end
    end.

%%-----------------------------------------------------------------------
%% Disassembles a BEAM select_* instruction used for indexing.
%%   Currently handles {select_val,3} and {select_tuple_arity,3} insts.
%%
%%   The arguments of a "select"-type instruction look as follows:
%%       <reg>, {f,FailLabel}, {list, <num cases>, [<case1> ... <caseN>]}
%%   where each case is of the form [symbol,{f,Label}].
%%-----------------------------------------------------------------------

disasm_select_inst(Inst, Bs, Atoms, Literals, Types) ->
    {X, Bs1} = decode_arg(Bs, Atoms, Literals, Types),
    {F, Bs2} = decode_arg(Bs1, Atoms, Literals, Types),
    {Z, Bs3} = decode_arg(Bs2, Atoms, Literals, Types),
    {U, Bs4} = decode_arg(Bs3, Atoms, Literals, Types),
    {u, Len} = U,
    {List, RestBs} = decode_n_args(Len, Bs4, Atoms, Literals, Types),
    {{Inst, [X,F,{Z,U,List}]}, RestBs}.

disasm_map_inst(Inst, Arity, Bs0, Atoms, Literals, Types) ->
    {Args0,Bs1} = decode_n_args(Arity, Bs0, Atoms, Literals, Types),
    %% no droplast ..
    [Z|Args1]  = lists:reverse(Args0),
    Args       = lists:reverse(Args1),
    {U, Bs2}   = decode_arg(Bs1, Atoms, Literals, Types),
    {u, Len}   = U,
    {List, RestBs} = decode_n_args(Len, Bs2, Atoms, Literals, Types),
    {{Inst, Args ++ [{Z,U,List}]}, RestBs}.

disasm_put_tuple2(Bs, Atoms, Literals, Types) ->
    {X, Bs1} = decode_arg(Bs, Atoms, Literals, Types),
    {Z, Bs2} = decode_arg(Bs1, Atoms, Literals, Types),
    {U, Bs3} = decode_arg(Bs2, Atoms, Literals, Types),
    {u, Len} = U,
    {List, RestBs} = decode_n_args(Len, Bs3, Atoms, Literals, Types),
    {{put_tuple2, [X,{Z,U,List}]}, RestBs}.

disasm_make_fun3(Bs, Atoms, Literals, Types) ->
    {Fun, Bs1} = decode_arg(Bs, Atoms, Literals, Types),
    {Dst, Bs2} = decode_arg(Bs1, Atoms, Literals, Types),
    {Z, Bs3} = decode_arg(Bs2, Atoms, Literals, Types),
    {U, Bs4} = decode_arg(Bs3, Atoms, Literals, Types),
    {u, Len} = U,
    {List, RestBs} = decode_n_args(Len, Bs4, Atoms, Literals, Types),
    {{make_fun3, [Fun,Dst,{Z,U,List}]}, RestBs}.

disasm_init_yregs(Bs1, Atoms, Literals, Types) ->
    {Z, Bs2} = decode_arg(Bs1, Atoms, Literals, Types),
    {U, Bs3} = decode_arg(Bs2, Atoms, Literals, Types),
    {u, Len} = U,
    {List, RestBs} = decode_n_args(Len, Bs3, Atoms, Literals, Types),
    {{init_yregs, [{Z,U,List}]}, RestBs}.

disasm_bs_create_bin(Bs0, Atoms, Literals, Types) ->
    {A1, Bs1} = decode_arg(Bs0, Atoms, Literals, Types),
    {A2, Bs2} = decode_arg(Bs1, Atoms, Literals, Types),
    {A3, Bs3} = decode_arg(Bs2, Atoms, Literals, Types),
    {A4, Bs4} = decode_arg(Bs3, Atoms, Literals, Types),
    {A5, Bs5} = decode_arg(Bs4, Atoms, Literals, Types),
    {Z, Bs6} = decode_arg(Bs5, Atoms, Literals, Types),
    {U, Bs7} = decode_arg(Bs6, Atoms, Literals, Types),
    {u, Len} = U,
    {List, RestBs} = decode_n_args(Len, Bs7, Atoms, Literals, Types),
    {{bs_create_bin, [{A1,A2,A3,A4,A5,Z,U,List}]}, RestBs}.

disasm_bs_match(Bs0, Atoms, Literals, Types) ->
    {A1, Bs1} = decode_arg(Bs0, Atoms, Literals, Types),
    {A2, Bs2} = decode_arg(Bs1, Atoms, Literals, Types),
    Bs5 = Bs2,
    {Z, Bs6} = decode_arg(Bs5, Atoms, Literals, Types),
    {U, Bs7} = decode_arg(Bs6, Atoms, Literals, Types),
    {u, Len} = U,
    {List, RestBs} = decode_n_args(Len, Bs7, Atoms, Literals, Types),
    {{bs_match, [{A1,A2,Z,U,List}]}, RestBs}.

disasm_update_record(Bs1, Atoms, Literals, Types) ->
    {Hint, Bs2} = decode_arg(Bs1, Atoms, Literals, Types),
    {Size, Bs3} = decode_arg(Bs2, Atoms, Literals, Types),
    {Src, Bs4} = decode_arg(Bs3, Atoms, Literals, Types),
    {Dst, Bs6} = decode_arg(Bs4, Atoms, Literals, Types),
    {Z, Bs7} = decode_arg(Bs6, Atoms, Literals, Types),
    {U, Bs8} = decode_arg(Bs7, Atoms, Literals, Types),
    {u, Len} = U,
    {List, RestBs} = decode_n_args(Len, Bs8, Atoms, Literals, Types),
    {{update_record, [Hint,Size,Src,Dst,{{Z,U,List}}]}, RestBs}.

%%-----------------------------------------------------------------------
%% decode_arg([Byte]) -> {Arg, [Byte]}
%%
%% - an arg can have variable length, so we must return arg + remaining bytes
%% - decodes an argument into its 'raw' form: { Tag, Value }
%%   several types map to a single tag, so the byte code instr must then
%%   assign a type to it
%%-----------------------------------------------------------------------

-spec decode_arg([byte(),...]) -> {{disasm_tag(),_}, [byte()]}.

decode_arg([B|Bs]) ->
    Tag = decode_tag(B band 2#111),
    ?NO_DEBUG('Tag = ~p, B = ~p, Bs = ~p~n', [Tag, B, Bs]),
    case Tag of
	z ->
	    decode_z_tagged(Tag, B, Bs, no_literals, no_types);
	_ ->
	    %% all other cases are handled as if they were integers
	    decode_int(Tag, B, Bs)
    end.

-spec decode_arg([byte(),...],
                 gb_trees:tree(index(), _), literals(), types()) ->
        {disasm_term(), [byte()]}.

decode_arg([B|Bs0], Atoms, Literals, Types) ->
    Tag = decode_tag(B band 2#111),
    ?NO_DEBUG('Tag = ~p, B = ~p, Bs = ~p~n', [Tag, B, Bs0]),
    case Tag of
	z ->
	    decode_z_tagged(Tag, B, Bs0, Literals, Types);
	a ->
	    %% atom or nil
	    case decode_int(Tag, B, Bs0) of
		{{a,0},Bs} -> {nil,Bs};
		{{a,I},Bs} -> {{atom,lookup(I, Atoms)},Bs}
	    end;
	_ ->
	    %% all other cases are handled as if they were integers
	    decode_int(Tag, B, Bs0)
    end.

%%-----------------------------------------------------------------------
%% Decodes an integer value.  Handles positives, negatives, and bignums.
%%
%% Tries to do the opposite of:
%%   beam_asm:encode(1, 5) =            [81]
%%   beam_asm:encode(1, 1000) =         [105,232]
%%   beam_asm:encode(1, 2047) =         [233,255]
%%   beam_asm:encode(1, 2048) =         [25,8,0]
%%   beam_asm:encode(1,-1) =            [25,255,255]
%%   beam_asm:encode(1,-4294967295) =   [121,255,0,0,0,1]
%%   beam_asm:encode(1, 4294967295) =   [121,0,255,255,255,255]
%%   beam_asm:encode(1, 429496729501) = [121,99,255,255,255,157]
%%-----------------------------------------------------------------------

decode_int(Tag,B,Bs) when (B band 16#08) =:= 0 ->
    %% N < 16 = 4 bits, NNNN:0:TTT
    N = B bsr 4,
    {{Tag,N},Bs};
decode_int(Tag,B,Bs) when (B band 16#10) =:= 0 ->
    %% N < 2048 = 11 bits = 3:8 bits, NNN:01:TTT, NNNNNNNN
    [B1|Bs1] = Bs,
    Val0 = B band 2#11100000,
    N = (Val0 bsl 3) bor B1,
    ?NO_DEBUG('NNN:01:TTT, NNNNNNNN = ~n~p:01:~p, ~p = ~p~n', [Val0,Tag,B,N]),
    {{Tag,N},Bs1};
decode_int(Tag,B,Bs) ->
    {Len,Bs1} = decode_int_length(B,Bs),
    {IntBs,RemBs} = take_bytes(Len,Bs1),
    N = build_arg(IntBs),
    [F|_] = IntBs,
    Num = if F > 127, Tag =:= i -> decode_negative(N,Len);
	     true -> N
	  end,
    ?NO_DEBUG('Len = ~p, IntBs = ~p, Num = ~p~n', [Len,IntBs,Num]),
    {{Tag,Num},RemBs}.

-spec decode_int_length(integer(), [byte()]) -> {integer(), [byte()]}.

decode_int_length(B, Bs) ->
    %% The following imitates get_erlang_integer() in beam_load.c
    %% Len is the size of the integer value in bytes
    case B bsr 5 of
	7 ->
	    {Arg,ArgBs} = decode_arg(Bs),
	    case Arg of
		{u,L} ->
		    {L+9,ArgBs};  % 9 stands for 7+2
		_ -> 
		    ?exit({decode_int,weird_bignum_sublength,Arg})
	    end;
	L ->
	    {L+2,Bs}
    end.
    
-spec decode_negative(non_neg_integer(), non_neg_integer()) -> neg_integer().

decode_negative(N, Len) ->
    N - (1 bsl (Len*8)). % 8 is number of bits in a byte

%%-----------------------------------------------------------------------
%% Decodes lists and floating point numbers.
%%-----------------------------------------------------------------------

decode_z_tagged(Tag,B,Bs,Literals,Types) when (B band 16#08) =:= 0 ->
    N = B bsr 4,
    case N of
	0 -> % float
	    decode_float(Bs);
	1 -> % list
	    {{Tag,N},Bs};
	2 -> % fr
	    decode_fr(Bs);
	3 -> % allocation list
	    decode_alloc_list(Bs, Literals);
	4 -> % literal
	    {{u,LitIndex},RestBs} = decode_arg(Bs),
	    case gb_trees:get(LitIndex, Literals) of
		Float when is_float(Float) ->
		    {{float,Float},RestBs};
		Literal ->
		    {{literal,Literal},RestBs}
	    end;
        5 -> % type-tagged register
            decode_tr(Bs, Types);
	_ ->
	    ?exit({decode_z_tagged,{invalid_extended_tag,N}})
    end;
decode_z_tagged(_,B,_,_,_) ->
    ?exit({decode_z_tagged,{weird_value,B}}).

-spec decode_float([byte(),...]) -> {{'float', float()}, [byte()]}.

decode_float(Bs) ->
    {FL,RestBs} = take_bytes(8,Bs),
    <<Float:64/float>> = list_to_binary(FL),
    {{float,Float},RestBs}.

-spec decode_tr([byte(),...], term()) -> {#tr{}, [byte()]}.
decode_tr(Bs, Types) ->
    {Reg, RestBs0} = decode_arg(Bs),
    {{u, TypeIdx}, RestBs} = decode_arg(RestBs0),
    {#tr{r=Reg,t=gb_trees:get(TypeIdx, Types)}, RestBs}.

-spec decode_fr([byte(),...]) -> {{'fr', non_neg_integer()}, [byte()]}.

decode_fr(Bs) ->
    {{u,Fr},RestBs} = decode_arg(Bs),
    {{fr,Fr},RestBs}.

decode_alloc_list(Bs, Literals) ->
    {{u,N},RestBs} = decode_arg(Bs),
    decode_alloc_list_1(N, Literals, RestBs, []).

decode_alloc_list_1(0, _Literals, RestBs, Acc) ->
    {{u,{alloc,lists:reverse(Acc)}},RestBs};
decode_alloc_list_1(N, Literals, Bs0, Acc) ->
    {{u,Type},Bs1} = decode_arg(Bs0),
    {{u,Val},Bs} = decode_arg(Bs1),
    Res = case Type of
	      0 -> {words,Val};
	      1 -> {floats,Val};
              2 -> {funs,Val}
	  end,
    decode_alloc_list_1(N-1, Literals, Bs, [Res|Acc]).

%%-----------------------------------------------------------------------
%% take N bytes from a stream, return {Taken_bytes, Remaining_bytes}
%%-----------------------------------------------------------------------

-spec take_bytes(non_neg_integer(), [byte()]) -> {[byte()], [byte()]}.

take_bytes(N, Bs) ->
    take_bytes(N, Bs, []).

take_bytes(N, [B|Bs], Acc) when N > 0 ->
    take_bytes(N-1, Bs, [B|Acc]);
take_bytes(0, Bs, Acc) ->
    {lists:reverse(Acc), Bs}.

%%-----------------------------------------------------------------------
%% from a list of bytes Bn,Bn-1,...,B1,B0
%% build  (Bn << 8*n) bor ... bor (B1 << 8) bor (B0 << 0)
%%-----------------------------------------------------------------------

build_arg(Bs) ->
    build_arg(Bs, 0).

build_arg([B|Bs], N) ->
    build_arg(Bs, (N bsl 8) bor B);
build_arg([], N) ->
    N.

%%-----------------------------------------------------------------------
%% Decodes a bunch of arguments and returns them in a list
%%-----------------------------------------------------------------------

decode_n_args(N, Bs, Atoms, Literals, Types) when N >= 0 ->
    decode_n_args(N, [], Bs, Atoms, Literals, Types).

decode_n_args(N, Acc, Bs0, Atoms, Literals, Types) when N > 0 ->
    {A1,Bs} = decode_arg(Bs0, Atoms, Literals, Types),
    decode_n_args(N-1, [A1|Acc], Bs, Atoms, Literals, Types);
decode_n_args(0, Acc, Bs, _, _, _) ->
    {lists:reverse(Acc),Bs}.

%%-----------------------------------------------------------------------
%% Convert a numeric tag value into a symbolic one
%%-----------------------------------------------------------------------

-spec decode_tag(0..7) -> symbolic_tag().

decode_tag(?tag_u) -> u;
decode_tag(?tag_i) -> i;
decode_tag(?tag_a) -> a;
decode_tag(?tag_x) -> x;
decode_tag(?tag_y) -> y;
decode_tag(?tag_f) -> f;
decode_tag(?tag_h) -> h;
decode_tag(?tag_z) -> z.

%%-----------------------------------------------------------------------
%% - replace all references {a,I} with the atom with index I (or {atom,A})
%% - replace all references to {i,K} in an external call position with
%%    the proper MFA (position in list, first elt = 0, yields MFA to use)
%% - resolve strings, represented as <offset, length>, into their
%%   actual values by using string table
%%    (note: string table should be passed as a BINARY so that we can
%%    use binary_to_list/3!)
%% - convert instruction to its readable form ...
%% 
%% Currently, only the first three are done (systematically, at least).
%%
%% Note: It MAY be premature to remove the lists of args, since that
%%  representation means it is simpler to iterate over all args, etc.
%%-----------------------------------------------------------------------

resolve_names(Fun, Imports, Str, Lbls, Lambdas, Literals, M) ->
    [resolve_inst(Instr, Imports, Str, Lbls, Lambdas, Literals, M) || Instr <- Fun].

%%
%% Instructions that need to look up an entry in the Lambda table.
%% We handle these specially here to avoid adding an argument to
%% the clause for every instruction.
%%
%% - make_fun2/4 (R8, added in August 2001)
%% - make_fun3/3 (OTP 24)
%% - call_fun2/3 (OTP 25)
%%

resolve_inst({make_fun2,Args}, _, _, _, Lambdas, _, M) ->
    [OldIndex] = resolve_args(Args),
    {OldIndex,{F,A,_Lbl,_Index,NumFree,OldUniq}} =
	lists:keyfind(OldIndex, 1, Lambdas),
    {make_fun2,{M,F,A},OldIndex,OldUniq,NumFree};
resolve_inst({make_fun3,[Fun,Dst,{{z,1},{u,_},Env0}]}, _, _, _, Lambdas, _, M) ->
    OldIndex = resolve_arg(Fun),
    Env1 = resolve_args(Env0),
    {OldIndex,{F,A,_Lbl,_Index,_NumFree,OldUniq}} =
	lists:keyfind(OldIndex, 1, Lambdas),
    {make_fun3,{M,F,A},OldIndex,OldUniq,Dst,{list,Env1}};
resolve_inst({call_fun2,Args}, _, _, _, Lambdas, _, _) ->
    [Tag0,Arity,Func] = resolve_args(Args),
    Tag = case Tag0 of
              Index when is_integer(Index) ->
                  {Tag0,{_F,_A,Label,_Index,_NumFree,_OldUniq}} =
                      lists:keyfind(Tag0, 1, Lambdas),
                  {f,Label};
              _ ->
                  Tag0
          end,
    {call_fun2,Tag,Arity,Func};
resolve_inst(Instr, Imports, Str, Lbls, _Lambdas, _Literals, _M) ->
    %% io:format(?MODULE_STRING":resolve_inst ~p.~n", [Instr]),
    resolve_inst(Instr, Imports, Str, Lbls).

resolve_inst({label,[{u,L}]},_,_,_) ->
    {label,L};
resolve_inst(FuncInfo,_,_,_) when element(1, FuncInfo) =:= func_info -> 
    FuncInfo; % already resolved
%% resolve_inst(int_code_end,_,_,_,_) ->  % instruction already handled
%%    int_code_end;                       % should not really be handled here
resolve_inst({call,[{u,N},{f,L}]},_,_,Lbls) ->
    {call,N,lookup(L,Lbls)};
resolve_inst({call_last,[{u,N},{f,L},{u,U}]},_,_,Lbls) ->
    {call_last,N,lookup(L,Lbls),U};
resolve_inst({call_only,[{u,N},{f,L}]},_,_,Lbls) ->
    {call_only,N,lookup(L,Lbls)};
resolve_inst({call_ext,[{u,N},{u,MFAix}]},Imports,_,_) ->
    {call_ext,N,lookup(MFAix+1,Imports)};
resolve_inst({call_ext_last,[{u,N},{u,MFAix},{u,X}]},Imports,_,_) ->
    {call_ext_last,N,lookup(MFAix+1,Imports),X};
resolve_inst({bif0,Args},Imports,_,_) ->
    [Bif,Reg] = resolve_args(Args),
    {extfunc,_Mod,BifName,_Arity} = lookup(Bif+1,Imports),
    {bif,BifName,nofail,[],Reg};
resolve_inst({bif1,Args},Imports,_,_) ->
    [F,Bif,A1,Reg] = resolve_args(Args),
    {extfunc,_Mod,BifName,_Arity} = lookup(Bif+1,Imports),
    {bif,BifName,F,[A1],Reg};
resolve_inst({bif2,Args},Imports,_,_) ->
    [F,Bif,A1,A2,Reg] = resolve_args(Args),
    {extfunc,_Mod,BifName,_Arity} = lookup(Bif+1,Imports),
    {bif,BifName,F,[A1,A2],Reg};
resolve_inst({allocate,[{u,X0},{u,X1}]},_,_,_) ->
    {allocate,X0,X1};
resolve_inst({allocate_heap,[{u,X0},{u,X1},{u,X2}]},_,_,_) ->
    {allocate_heap,X0,X1,X2};
resolve_inst({allocate_zero,[{u,X0},{u,X1}]},_,_,_) ->
    {allocate_zero,X0,X1};
resolve_inst({allocate_heap_zero,[{u,X0},{u,X1},{u,X2}]},_,_,_) ->
    {allocate_heap_zero,X0,X1,X2};
resolve_inst({test_heap,[{u,X0},{u,X1}]},_,_,_) ->
    {test_heap,X0,X1};
resolve_inst({init,[Dst]},_,_,_) ->
    {init,Dst};
resolve_inst({deallocate,[{u,L}]},_,_,_) ->
    {deallocate,L};
resolve_inst({return,[]},_,_,_) ->
    return;
resolve_inst({send,[]},_,_,_) ->
    send;
resolve_inst({remove_message,[]},_,_,_) ->
    remove_message;
resolve_inst({timeout,[]},_,_,_) ->
    timeout;
resolve_inst({loop_rec,[Lbl,Dst]},_,_,_) ->
    {loop_rec,Lbl,Dst};
resolve_inst({loop_rec_end,[Lbl]},_,_,_) ->
    {loop_rec_end,Lbl};
resolve_inst({wait,[Lbl]},_,_,_) ->
    {wait,Lbl};
resolve_inst({wait_timeout,[Lbl,Int]},_,_,_) ->
    {wait_timeout,Lbl,resolve_arg(Int)};
resolve_inst({is_lt=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({is_ge=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({is_eq=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({is_ne=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({is_eq_exact=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({is_ne_exact=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({is_integer=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({is_float=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({is_number=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({is_atom=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({is_pid=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({is_reference=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({is_port=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({is_nil=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({is_binary=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({is_list=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({is_nonempty_list=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({is_tuple=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({test_arity=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({is_tagged_tuple=I,Args0},_,_,_) ->
    [F|Args] = resolve_args(Args0),
    {test,I,F,Args};
resolve_inst({select_val,Args},_,_,_) ->
    [Reg,FLbl,{{z,1},{u,_Len},List0}] = Args,
    List = resolve_args(List0),
    {select_val,Reg,FLbl,{list,List}};
resolve_inst({select_tuple_arity,Args},_,_,_) ->
    [Reg,FLbl,{{z,1},{u,_Len},List0}] = Args,
    List = resolve_args(List0),
    {select_tuple_arity,Reg,FLbl,{list,List}};
resolve_inst({jump,[Lbl]},_,_,_) ->
    {jump,Lbl};
resolve_inst({'catch',[Dst,Lbl]},_,_,_) ->
    {'catch',Dst,Lbl};
resolve_inst({catch_end,[Dst]},_,_,_) ->
    {catch_end,Dst};
resolve_inst({move,[Src,Dst]},_,_,_) ->
    {move,resolve_arg(Src),Dst};
resolve_inst({get_list,[Src,Dst1,Dst2]},_,_,_) ->
    {get_list,Src,Dst1,Dst2};
resolve_inst({get_tuple_element,[Src,{u,Off},Dst]},_,_,_) ->
    {get_tuple_element,resolve_arg(Src),Off,resolve_arg(Dst)};
resolve_inst({set_tuple_element,[Src,Dst,{u,Off}]},_,_,_) ->
    {set_tuple_element,resolve_arg(Src),resolve_arg(Dst),Off};
resolve_inst({put_list,[Src1,Src2,Dst]},_,_,_) ->
    {put_list,resolve_arg(Src1),resolve_arg(Src2),Dst};
resolve_inst({put_tuple,[{u,Arity},Dst]},_,_,_) ->
    {put_tuple,Arity,Dst};
resolve_inst({put,[Src]},_,_,_) ->
    {put,resolve_arg(Src)};
resolve_inst({badmatch,[X]},_,_,_) ->
    {badmatch,resolve_arg(X)};
resolve_inst({if_end,[]},_,_,_) ->
    if_end;
resolve_inst({case_end,[X]},_,_,_) ->
    {case_end,resolve_arg(X)};
resolve_inst({call_fun,[{u,N}]},_,_,_) ->
    {call_fun,N};
resolve_inst({is_function=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};
resolve_inst({call_ext_only,[{u,N},{u,MFAix}]},Imports,_,_) ->
    {call_ext_only,N,lookup(MFAix+1,Imports)};
%%
%% Instructions for handling binaries added in R7A & R7B
%%
resolve_inst({bs_put_integer,[Lbl,Arg2,{u,N},{u,U},Arg5]},_,_,_) ->
    [A2,A5] = resolve_args([Arg2,Arg5]),
    {bs_put_integer,Lbl,A2,N,decode_field_flags(U),A5};
resolve_inst({bs_put_binary,[Lbl,Arg2,{u,N},{u,U},Arg5]},_,_,_) ->
    [A2,A5] = resolve_args([Arg2,Arg5]),
    {bs_put_binary,Lbl,A2,N,decode_field_flags(U),A5};
resolve_inst({bs_put_float,[Lbl,Arg2,{u,N},{u,U},Arg5]},_,_,_) ->
    [A2,A5] = resolve_args([Arg2,Arg5]),
    {bs_put_float,Lbl,A2,N,decode_field_flags(U),A5};
resolve_inst({bs_put_string,[{u,Len},{u,Off}]},_,Strings,_) ->
    String = if Len > 0 -> binary_to_list(Strings, Off+1, Off+Len);
		true -> ""
	     end,
    {bs_put_string,Len,{string,String}};

%%
%% Instructions for handling floating point numbers added in June 2001 (R8).
%%
resolve_inst({fclearerror,[]},_,_,_) ->
    fclearerror;
resolve_inst({fcheckerror,[Arg]},_,_,_) ->
    {fcheckerror,resolve_arg(Arg)};
resolve_inst({fmove,Args},_,_,_) ->
    [FR,Reg] = resolve_args(Args),
    {fmove,FR,Reg};
resolve_inst({fconv,Args},_,_,_) ->
    [Reg,FR] = resolve_args(Args),
    {fconv,Reg,FR};
resolve_inst({fadd=I,Args},_,_,_) ->
    [F,A1,A2,Reg] = resolve_args(Args),
    {bif,I,F,[A1,A2],Reg};
resolve_inst({fsub=I,Args},_,_,_) ->
    [F,A1,A2,Reg] = resolve_args(Args),
    {bif,I,F,[A1,A2],Reg};
resolve_inst({fmul=I,Args},_,_,_) ->
    [F,A1,A2,Reg] = resolve_args(Args),
    {bif,I,F,[A1,A2],Reg};
resolve_inst({fdiv=I,Args},_,_,_) ->
    [F,A1,A2,Reg] = resolve_args(Args),
    {bif,I,F,[A1,A2],Reg};
resolve_inst({fnegate,Args},_,_,_) ->
    [F,Arg,Reg] = resolve_args(Args),
    {bif,fnegate,F,[Arg],Reg};

%%
%% Instructions for try expressions added in January 2003 (R10).
%%
resolve_inst({'try',[Reg,Lbl]},_,_,_) -> % analogous to 'catch'
    {'try',Reg,Lbl};
resolve_inst({try_end,[Reg]},_,_,_) ->   % analogous to 'catch_end'
    {try_end,Reg};
resolve_inst({try_case,[Reg]},_,_,_) ->  % analogous to 'catch_end'
    {try_case,Reg};
resolve_inst({try_case_end,[Arg]},_,_,_) ->
    {try_case_end,resolve_arg(Arg)};
resolve_inst({raise,[_Reg1,_Reg2]=Regs},_,_,_) ->
    {bif,raise,{f,0},Regs,{x,0}};

%%
%% New bit syntax instructions added in February 2004 (R10B).
%%
resolve_inst({bs_init2,[Lbl,Arg2,{u,W},{u,R},{u,F},Arg6]},_,_,_) ->
    [A2,A6] = resolve_args([Arg2,Arg6]),
    {bs_init2,Lbl,A2,W,R,decode_field_flags(F),A6};
resolve_inst({bs_add=I,[Lbl,Arg2,Arg3,Arg4,Arg5]},_,_,_) ->
    [A2,A3,A4,A5] = resolve_args([Arg2,Arg3,Arg4,Arg5]),
    {I,Lbl,[A2,A3,A4],A5};

%%
%% New apply instructions added in April 2004 (R10B).
%%
resolve_inst({apply,[{u,Arity}]},_,_,_) ->
    {apply,Arity};
resolve_inst({apply_last,[{u,Arity},{u,D}]},_,_,_) ->
    {apply_last,Arity,D};

%%
%% New test instruction added in April 2004 (R10B).
%%
resolve_inst({is_boolean=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};

%%
%% New instruction added in June 2005.
%%
resolve_inst({is_function2=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};

%%
%% New bit syntax matching added in Dec 2005 (R11B).
%%
resolve_inst({bs_start_match2=I,[F,Reg,{u,Live},{u,Max},Ms]},_,_,_) ->
    {test,I,F,[Reg,Live,Max,Ms]};
resolve_inst({bs_get_integer2=I,[Fail,Ms,{u,Live},Size0,{u,Unit},{u,Flags},Dst0]},_,_,_) ->
    [Size,Dst] = resolve_args([Size0,Dst0]),
    {test,I,Fail,Live,[Ms,Size,Unit,decode_field_flags(Flags)],Dst};
resolve_inst({bs_get_binary2=I,[Fail,Ms,{u,Live},Size0,{u,Unit},{u,Flags},Dst0]},_,_,_) ->
    [Size,Dst] = resolve_args([Size0,Dst0]),
    {test,I,Fail,Live,[Ms,Size,Unit,decode_field_flags(Flags)],Dst};
resolve_inst({bs_get_float2=I,[Fail,Ms,{u,Live},Size0,{u,Unit},{u,Flags},Dst0]},_,_,_) ->
    [Size,Dst] = resolve_args([Size0,Dst0]),
    {test,I,Fail,Live,[Ms,Size,Unit,decode_field_flags(Flags)],Dst};
resolve_inst({bs_skip_bits2=I,[Lbl,Ms,Arg2,{u,N},{u,U}]},_,_,_) ->
    A2 = resolve_arg(Arg2),
    {test,I,Lbl,[Ms,A2,N,decode_field_flags(U)]};
resolve_inst({bs_test_tail2=I,[F,Ms,{u,N}]},_,_,_) ->
    {test,I,F,[Ms,N]};
resolve_inst({bs_save2=I,[Ms,{u,N}]},_,_,_) ->
    {I,Ms,N};
resolve_inst({bs_restore2=I,[Ms,{u,N}]},_,_,_) ->
    {I,Ms,N};
resolve_inst({bs_save2=I,[Ms,{atom,_}=Atom]},_,_,_) ->
    %% New operand type in R12B.
    {I,Ms,Atom};
resolve_inst({bs_restore2=I,[Ms,{atom,_}=Atom]},_,_,_) ->
    %% New operand type in R12B.
    {I,Ms,Atom};

%%
%% New instructions for guard BIFs that may GC. Added in Jan 2006 (R11B).
%%
resolve_inst({gc_bif1,Args},Imports,_,_) ->
    [F,Live,Bif,A1,Reg] = resolve_args(Args),
    {extfunc,_Mod,BifName,_Arity} = lookup(Bif+1,Imports),
    {gc_bif,BifName,F,Live,[A1],Reg};
resolve_inst({gc_bif2,Args},Imports,_,_) ->
    [F,Live,Bif,A1,A2,Reg] = resolve_args(Args),
    {extfunc,_Mod,BifName,_Arity} = lookup(Bif+1,Imports),
    {gc_bif,BifName,F,Live,[A1,A2],Reg};

%%
%% New instruction in R14, gc_bif with 3 arguments
%%
resolve_inst({gc_bif3,Args},Imports,_,_) ->
    [F,Live,Bif,A1,A2,A3,Reg] = resolve_args(Args),
    {extfunc,_Mod,BifName,_Arity} = lookup(Bif+1,Imports),
    {gc_bif,BifName,F,Live,[A1,A2,A3],Reg};

%%
%% R11B-5.
%% 
resolve_inst({is_bitstr=I,Args0},_,_,_) ->
    [L|Args] = resolve_args(Args0),
    {test,I,L,Args};

%%
%% R12B.
%%
resolve_inst({bs_context_to_binary=I,[Reg0]},_,_,_) ->
    Reg = resolve_arg(Reg0),
    {I,Reg};
resolve_inst({bs_test_unit=I,[F,Ms,{u,N}]},_,_,_) ->
    {test,I,F,[Ms,N]};
resolve_inst({bs_match_string=I,[F,Ms,{u,Bits},{u,Off}]},_,Strings,_) ->
    Len = (Bits+7) div 8,
    String = if
		 Len > 0 -> 
		     <<_:Off/binary,Bin:Len/binary,_/binary>> = Strings,
		     Bin;
		 true -> <<>>
	     end,
    {test,I,F,[Ms,Bits,{string,String}]};
resolve_inst({bs_init_writable=I,[]},_,_,_) ->
    I;
resolve_inst({bs_append=I,[Lbl,Arg2,{u,W},{u,R},{u,U},Arg6,{u,F},Arg8]},_,_,_) ->
    [A2,A6,A8] = resolve_args([Arg2,Arg6,Arg8]),
    {I,Lbl,A2,W,R,U,A6,decode_field_flags(F),A8};
resolve_inst({bs_private_append=I,[Lbl,Arg2,{u,U},Arg4,{u,F},Arg6]},_,_,_) ->
    [A2,A4,A6] = resolve_args([Arg2,Arg4,Arg6]),
    {I,Lbl,A2,U,A4,decode_field_flags(F),A6};
resolve_inst({trim=I,[{u,N},{u,Remaining}]},_,_,_) ->
    {I,N,Remaining};
resolve_inst({bs_init_bits,[Lbl,Arg2,{u,W},{u,R},{u,F},Arg6]},_,_,_) ->
    [A2,A6] = resolve_args([Arg2,Arg6]),
    {bs_init_bits,Lbl,A2,W,R,decode_field_flags(F),A6};

%%
%% R12B-5.
%%
resolve_inst({bs_get_utf8=I,[Lbl,Arg2,Arg3,{u,U},Arg4]},_,_,_) ->
    [A2,A3,A4] = resolve_args([Arg2,Arg3,Arg4]),
    {test,I,Lbl,[A2,A3,decode_field_flags(U),A4]};
resolve_inst({bs_skip_utf8=I,[Lbl,Arg2,Arg3,{u,U}]},_,_,_) ->
    [A2,A3] = resolve_args([Arg2,Arg3]),
    {test,I,Lbl,[A2,A3,decode_field_flags(U)]};
resolve_inst({bs_get_utf16=I,[Lbl,Arg2,Arg3,{u,U},Arg4]},_,_,_) ->
    [A2,A3,A4] = resolve_args([Arg2,Arg3,Arg4]),
    {test,I,Lbl,[A2,A3,decode_field_flags(U),A4]};
resolve_inst({bs_skip_utf16=I,[Lbl,Arg2,Arg3,{u,U}]},_,_,_) ->
    [A2,A3] = resolve_args([Arg2,Arg3]),
    {test,I,Lbl,[A2,A3,decode_field_flags(U)]};
resolve_inst({bs_get_utf32=I,[Lbl,Arg2,Arg3,{u,U},Arg4]},_,_,_) ->
    [A2,A3,A4] = resolve_args([Arg2,Arg3,Arg4]),
    {test,I,Lbl,[A2,A3,decode_field_flags(U),A4]};
resolve_inst({bs_skip_utf32=I,[Lbl,Arg2,Arg3,{u,U}]},_,_,_) ->
    [A2,A3] = resolve_args([Arg2,Arg3]),
    {test,I,Lbl,[A2,A3,decode_field_flags(U)]};
resolve_inst({bs_utf8_size=I,[Lbl,Arg2,Arg3]},_,_,_) ->
    [A2,A3] = resolve_args([Arg2,Arg3]),
    {I,Lbl,A2,A3};
resolve_inst({bs_put_utf8=I,[Lbl,{u,U},Arg3]},_,_,_) ->
    A3 = resolve_arg(Arg3),
    {I,Lbl,decode_field_flags(U),A3};
resolve_inst({bs_utf16_size=I,[Lbl,Arg2,Arg3]},_,_,_) ->
    [A2,A3] = resolve_args([Arg2,Arg3]),
    {I,Lbl,A2,A3};
resolve_inst({bs_put_utf16=I,[Lbl,{u,U},Arg3]},_,_,_) ->
    A3 = resolve_arg(Arg3),
    {I,Lbl,decode_field_flags(U),A3};
resolve_inst({bs_put_utf32=I,[Lbl,{u,U},Arg3]},_,_,_) ->
    A3 = resolve_arg(Arg3),
    {I,Lbl,decode_field_flags(U),A3};

%%
%% R13B03.
%%
resolve_inst({on_load,[]},_,_,_) ->
    on_load;

%%
%% R14A.
%%
resolve_inst({recv_mark,[Lbl]},_,_,_) ->
    {recv_mark,Lbl};
resolve_inst({recv_set,[Lbl]},_,_,_) ->
    {recv_set,Lbl};

%%
%% R15A.
%%
resolve_inst({line,[Index]},_,_,_) ->
    {line,resolve_arg(Index)};

%%
%% 17.0
%%
resolve_inst({put_map_assoc,Args},_,_,_) ->
    [FLbl,Src,Dst,{u,N},{{z,1},{u,_Len},List0}] = Args,
    List = resolve_args(List0),
    {put_map_assoc,FLbl,Src,Dst,N,{list,List}};
resolve_inst({put_map_exact,Args},_,_,_) ->
    [FLbl,Src,Dst,{u,N},{{z,1},{u,_Len},List0}] = Args,
    List = resolve_args(List0),
    {put_map_exact,FLbl,Src,Dst,N,{list,List}};
resolve_inst({is_map=I,Args0},_,_,_) ->
    [FLbl|Args] = resolve_args(Args0),
    {test,I,FLbl,Args};
resolve_inst({has_map_fields,Args0},_,_,_) ->
    [FLbl,Src,{{z,1},{u,_Len},List0}] = Args0,
    List = resolve_args(List0),
    {test,has_map_fields,FLbl,Src,{list,List}};
resolve_inst({get_map_elements,Args0},_,_,_) ->
    [FLbl,Src,{{z,1},{u,_Len},List0}] = Args0,
    List = resolve_args(List0),
    {get_map_elements,FLbl,Src,{list,List}};

%%
%% OTP 21.
%%

resolve_inst({build_stacktrace,[]},_,_,_) ->
    build_stacktrace;
resolve_inst({raw_raise,[]},_,_,_) ->
    raw_raise;
resolve_inst({get_hd,[Src,Dst]},_,_,_) ->
    {get_hd,Src,Dst};
resolve_inst({get_tl,[Src,Dst]},_,_,_) ->
    {get_tl,Src,Dst};

%%
%% OTP 22.
%%

resolve_inst({put_tuple2,[Dst,{{z,1},{u,_},List0}]},_,_,_) ->
    List = resolve_args(List0),
    {put_tuple2,Dst,{list,List}};
resolve_inst({bs_start_match3=I,[Fail,Bin,{u,Live},Dst]},_,_,_) ->
    {test,I,Fail,Live,[Bin],Dst};
resolve_inst({bs_get_tail,[Src,Dst,{u,Live}]},_,_,_) ->
    {bs_get_tail,Src,Dst,Live};
resolve_inst({bs_get_position,[Src,Dst,{u,Live}]},_,_,_) ->
    {bs_get_position,Src,Dst,Live};
resolve_inst({bs_set_position,[Src,Dst]},_,_,_) ->
    {bs_set_position,Src,Dst};

%%
%% OTP 23.
%%

resolve_inst({bs_start_match4,[Fail,{u,Live},Src,Dst]},_,_,_) ->
    {bs_start_match4,Fail,Live,Src,Dst};
resolve_inst({swap,[_,_]=List},_,_,_) ->
    [R1,R2] = resolve_args(List),
    {swap,R1,R2};

%%
%% OTP 24.
%%

resolve_inst({init_yregs,[{{z,1},{u,_},List0}]},_,_,_) ->
    List = resolve_args(List0),
    {init_yregs,{list,List}};
resolve_inst({recv_marker_bind,[Mark,Ref]},_,_,_) ->
    {recv_marker_bind,Mark,Ref};
resolve_inst({recv_marker_clear,[Reg]},_,_,_) ->
    {recv_marker_clear,Reg};
resolve_inst({recv_marker_reserve,[Reg]},_,_,_) ->
    {recv_marker_reserve,Reg};
resolve_inst({recv_marker_use,[Reg]},_,_,_) ->
    {recv_marker_use,Reg};

%%
%% OTP 25.
%%

resolve_inst({bs_create_bin,
              [{Fail,{u,Heap},{u,Live},{u,Unit},Dst,{z,1},{u,_},List0}]},
             _, Strings, _) ->
    List = resolve_bs_create_bin_list(List0, Strings),
    {bs_create_bin,Fail,Heap,Live,Unit,Dst,{list,List}};
resolve_inst({nif_start,[]},_,_,_) ->
    nif_start;
resolve_inst({badrecord,[Arg]},_,_,_) ->
    {badrecord,resolve_arg(Arg)};

%%
%% OTP 26.
%%

resolve_inst({update_record,
              [Hint,{u,Size},Src,Dst,{{{z,1},{u,_},List0}}]},_,_,_) ->
    List = resolve_args(List0),
    {update_record,Hint,Size,Src,Dst,{list,List}};
resolve_inst({bs_match,[{Fail,Ctx,{z,1},{u,_},Args}]},_,_,_) ->
    List = resolve_args(Args),
    Commands = resolve_bs_match_commands(List),
    {bs_match,Fail,Ctx,{commands,Commands}};

%%
%% OTP 27.
%%

resolve_inst({executable_line,[Location,Index]},_,_,_) ->
    {executable_line,resolve_arg(Location),resolve_arg(Index)};

%%
%% OTP 28.
%%

resolve_inst({debug_line,[Kind,Location,Index,Live]},_,_,_) ->
    {debug_line,resolve_arg(Kind),resolve_arg(Location),
     resolve_arg(Index),resolve_arg(Live)};

%%
%% Catches instructions that are not yet handled.
%%
resolve_inst(X,_,_,_) -> ?exit({resolve_inst,X}).

%%-----------------------------------------------------------------------
%% Resolves arguments in a generic way.
%%-----------------------------------------------------------------------

resolve_args(Args) -> [resolve_arg(A) || A <- Args].

resolve_arg(#tr{r=Reg} = Arg) -> _ = resolve_arg(Reg), Arg;
resolve_arg({x,N} = Arg) when is_integer(N), N >= 0 -> Arg;
resolve_arg({y,N} = Arg) when is_integer(N), N >= 0 -> Arg;
resolve_arg({fr,N} = Arg) when is_integer(N), N >= 0 -> Arg;
resolve_arg({f,N} = Arg) when is_integer(N), N >= 0 -> Arg;
resolve_arg({u,_} = Arg) -> resolve_arg_unsigned(Arg);
resolve_arg({i,_} = Arg) -> resolve_arg_integer(Arg);
resolve_arg({atom,Atom} = Arg) when is_atom(Atom) -> Arg;
resolve_arg({float,F} = Arg) when is_float(F) -> Arg;
resolve_arg({literal,_} = Arg) -> Arg;
resolve_arg(nil) -> nil.

resolve_arg_unsigned({u,N}) when is_integer(N), N >= 0 -> N.

resolve_arg_integer({i,N}) when is_integer(N) -> {integer,N}.

%%-----------------------------------------------------------------------
%% Resolves the OpList for the bs_create_bin/6 instruction
%%-----------------------------------------------------------------------

resolve_bs_create_bin_list(
  [{atom,string}=Type,Seg0,Unit0,Flags,Offset0,Size0|Rest], Strings) ->
    [Seg,Unit,Offset,{integer,Len}=Size] =
        resolve_args([Seg0,Unit0,Offset0,Size0]),
    <<_:Offset/binary,Bin:Len/binary,_/binary>> = Strings,
    [Type,Seg,Unit,Flags,{string,Bin},Size |
     resolve_bs_create_bin_list(Rest, Strings)];
resolve_bs_create_bin_list([Type,Seg0,Unit0,Flags,Val0,Size0|Rest], Strings) ->
    [Seg,Unit,Val,Size] = resolve_args([Seg0,Unit0,Val0,Size0]),
    [Type,Seg,Unit,Flags,Val,Size |
     resolve_bs_create_bin_list(Rest, Strings)];
resolve_bs_create_bin_list([], _Str) ->
    [].

%%-----------------------------------------------------------------------
%% Resolves the Commands list for the bs_match/3 instruction
%%-----------------------------------------------------------------------

resolve_bs_match_commands([{atom,ensure_at_least},Size,Unit|Rest]) ->
    [{ensure_at_least,Size,Unit} | resolve_bs_match_commands(Rest)];
resolve_bs_match_commands([{atom,ensure_exactly},Stride|Rest]) ->
    [{ensure_exactly,Stride} | resolve_bs_match_commands(Rest)];
resolve_bs_match_commands([{atom,integer},Live,Flags0,Size,Unit,Dst|Rest]) ->
    Flags = resolve_bs_match_flags(Flags0),
    [{integer,Live,Flags,Size,Unit,Dst} |
     resolve_bs_match_commands(Rest)];
resolve_bs_match_commands([{atom,binary},Live,Flags0,Size,Unit,Dst|Rest]) ->
    Flags = resolve_bs_match_flags(Flags0),
    [{binary,Live,Flags,Size,Unit,Dst} |
     resolve_bs_match_commands(Rest)];
resolve_bs_match_commands([{atom,'=:='},nil,Bits,Value|Rest]) ->
    [{'=:=',nil,Bits,Value} | resolve_bs_match_commands(Rest)];
resolve_bs_match_commands([{atom,skip},Stride|Rest]) ->
    [{skip,Stride} | resolve_bs_match_commands(Rest)];
resolve_bs_match_commands([{atom,get_tail},Live,Src,Dst|Rest]) ->
    [{get_tail,Live,Src,Dst} | resolve_bs_match_commands(Rest)];
resolve_bs_match_commands([]) ->
    [].

resolve_bs_match_flags(nil) -> {literal,[]};
resolve_bs_match_flags({literal,[_|_]}=Flags) -> Flags.

%%-----------------------------------------------------------------------
%% The purpose of the following is just to add a hook for future changes.
%% Currently, field flags are numbers 1-2-4-8 and only two of these
%% numbers (BSF_LITTLE 2 -- BSF_SIGNED 4) have a semantic significance;
%% others are just hints for speeding up the execution; see "erl_bits.h".
%% Decodes field flags within bitstrings such as `Var/signed' or
%% `Var/little'. This is the opposite of `beam_asm:flag_to_bit/1'.
%% Also see "erl_bits.h".
%%-----------------------------------------------------------------------

decode_field_flags(0) ->
    {field_flags,[]};
decode_field_flags(FieldFlags) when is_integer(FieldFlags) ->
    FF = lists:filter(
           fun
               (little) -> (FieldFlags band 16#02) == 16#02;
               (signed) -> (FieldFlags band 16#04) == 16#04;
               (native) -> (FieldFlags band 16#10) == 16#10
           end, [little, signed, native]),
    {field_flags,FF}.

%%-----------------------------------------------------------------------
%% Private Utilities
%%-----------------------------------------------------------------------

mk_imports(ImportList) ->
    gb_trees:from_orddict([{I,{extfunc,M,F,A}} ||
                              {I,M,F,A} <:- ImportList]).

mk_atoms(AtomList) ->
    gb_trees:from_orddict(AtomList).

mk_labels(LabelList) ->
    gb_trees:from_orddict(LabelList).

lookup(I, Imports) ->
    gb_trees:get(I, Imports).
