%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
%% Purpose: Run the Erlang compiler.

-module(compile).

%% High-level interface.
-export([file/1,file/2,noenv_file/2,format_error/1,iofile/1]).
-export([forms/1,forms/2,noenv_forms/2]).
-export([output_generated/1,noenv_output_generated/1]).
-export([options/0]).
-export([env_compiler_options/0]).

%% Erlc interface.
-export([compile/3,compile_beam/3,compile_asm/3,compile_core/3]).

%% Utility functions for compiler passes.
-export([run_sub_passes/2]).

-export_type([option/0]).

-include("erl_compile.hrl").
-include("core_parse.hrl").

-import(lists, [member/2,reverse/1,reverse/2,keyfind/3,last/1,
		map/2,flatmap/2,foreach/2,foldr/3,any/2]).

-define(SUB_PASS_TIMES, compile__sub_pass_times).

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

-type abstract_code() :: [erl_parse:abstract_form()].

%% Internal representations used for 'from_asm' and 'from_beam' compilation can
%% also be valid, but have no relevant types defined.
-type forms() :: abstract_code() | cerl:c_module().

-type option() :: atom() | {atom(), term()} | {'d', atom(), term()}.

-type err_info() :: {erl_anno:line() | 'none',
		     module(), term()}. %% ErrorDescriptor
-type errors()   :: [{file:filename(), [err_info()]}].
-type warnings() :: [{file:filename(), [err_info()]}].
-type mod_ret()  :: {'ok', module()}
                  | {'ok', module(), cerl:c_module()} %% with option 'to_core'
                  | {'ok',                            %% with option 'to_pp'
                     module() | [],                   %% module() if 'to_exp'
                     abstract_code()}
                  | {'ok', module(), warnings()}.
-type bin_ret()  :: {'ok', module(), binary()}
                  | {'ok', module(), binary(), warnings()}.
-type err_ret()  :: 'error' | {'error', errors(), warnings()}.
-type comp_ret() :: mod_ret() | bin_ret() | err_ret().


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

%%
%%  Exported functions
%%


%% file(FileName)
%% file(FileName, Options)
%%  Compile the module in file FileName.

-define(DEFAULT_OPTIONS, [verbose,report_errors,report_warnings]).

-spec file(module() | file:filename()) -> comp_ret().

file(File) -> file(File, ?DEFAULT_OPTIONS).

-spec file(module() | file:filename(), [option()] | option()) -> comp_ret().

file(File, Opts) when is_list(Opts) ->
    do_compile({file,File}, Opts++env_default_opts());
file(File, Opt) ->
    file(File, [Opt|?DEFAULT_OPTIONS]).

-spec forms(abstract_code()) -> comp_ret().

forms(Forms) -> forms(Forms, ?DEFAULT_OPTIONS).

-spec forms(forms(), [option()] | option()) -> comp_ret().

forms(Forms, Opts) when is_list(Opts) ->
    do_compile({forms,Forms}, [binary|Opts++env_default_opts()]);
forms(Forms, Opt) when is_atom(Opt) ->
    forms(Forms, [Opt|?DEFAULT_OPTIONS]).

%% Given a list of compilation options, returns true if compile:file/2
%% would have generated a Beam file, false otherwise (if only a binary or a
%% listing file would have been generated).

-spec output_generated([option()]) -> boolean().

output_generated(Opts) ->
    noenv_output_generated(Opts++env_default_opts()).

%%
%% Variants of the same function that don't consult ERL_COMPILER_OPTIONS
%% for default options.
%%

-spec noenv_file(module() | file:filename(), [option()] | option()) -> comp_ret().

noenv_file(File, Opts) when is_list(Opts) ->
    do_compile({file,File}, Opts);
noenv_file(File, Opt) ->
    noenv_file(File, [Opt|?DEFAULT_OPTIONS]).

-spec noenv_forms(forms(), [option()] | option()) -> comp_ret().

noenv_forms(Forms, Opts) when is_list(Opts) ->
    do_compile({forms,Forms}, [binary|Opts]);
noenv_forms(Forms, Opt) when is_atom(Opt) ->
    noenv_forms(Forms, [Opt|?DEFAULT_OPTIONS]).

-spec noenv_output_generated([option()]) -> boolean().

noenv_output_generated(Opts) ->
    {_,Passes} = passes(file, expand_opts(Opts)),
    any(fun ({save_binary,_T,_F}) -> true;
	    (_Other) -> false
	end, Passes).

%%
%% Retrieve ERL_COMPILER_OPTIONS as a list of terms
%%

-spec env_compiler_options() -> [term()].

env_compiler_options() -> env_default_opts().


%%%
%%% Run sub passes from a compiler pass.
%%%

-spec run_sub_passes([term()], term()) -> term().

run_sub_passes(Ps, St) ->
    case get(?SUB_PASS_TIMES) of
        undefined ->
            Runner = fun(_Name, Run, S) -> Run(S) end,
            run_sub_passes_1(Ps, Runner, St);
        Times when is_list(Times) ->
            Runner = fun(Name, Run, S0) ->
                             T1 = erlang:monotonic_time(),
                             S = Run(S0),
                             T2 = erlang:monotonic_time(),
                             put(?SUB_PASS_TIMES,
                                 [{Name,T2-T1}|get(?SUB_PASS_TIMES)]),
                             S
                     end,
            run_sub_passes_1(Ps, Runner, St)
    end.

%%
%%  Local functions
%%

-define(pass(P), {P,fun P/2}).
-define(pass(P,T), {P,fun T/1,fun P/2}).

env_default_opts() ->
    Key = "ERL_COMPILER_OPTIONS",
    case os:getenv(Key) of
	false -> [];
	Str when is_list(Str) ->
	    case erl_scan:string(Str) of
		{ok,Tokens,_} ->
                    Dot = {dot, erl_anno:new(1)},
		    case erl_parse:parse_term(Tokens ++ [Dot]) of
			{ok,List} when is_list(List) -> List;
			{ok,Term} -> [Term];
			{error,_Reason} ->
			    io:format("Ignoring bad term in ~s\n", [Key]),
			    []
		    end;
		{error, {_,_,_Reason}, _} ->
		    io:format("Ignoring bad term in ~s\n", [Key]),
		    []
	    end
    end.

do_compile(Input, Opts0) ->
    Opts = expand_opts(Opts0),
    IntFun = fun() -> try
                          internal(Input, Opts)
                      catch
                          error:Reason ->
                              {error,Reason}
                      end
             end,
    %% Some tools, like Dialyzer, has already spawned workers
    %% and spawning extra workers actually slow the compilation
    %% down instead of speeding it up, so we provide a mechanism
    %% to bypass the compiler process.
    case lists:member(no_spawn_compiler_process, Opts) of
        true ->
            IntFun();
        false ->
            {Pid,Ref} =
                spawn_monitor(fun() ->
                                      exit(IntFun())
                              end),
            receive
                {'DOWN',Ref,process,Pid,Rep} -> Rep
            end
    end.

expand_opts(Opts0) ->
    %% {debug_info_key,Key} implies debug_info.
    Opts = case {proplists:get_value(debug_info_key, Opts0),
		 proplists:get_value(encrypt_debug_info, Opts0),
		 proplists:get_value(debug_info, Opts0)} of
	       {undefined,undefined,_} -> Opts0;
	       {_,_,undefined} -> [debug_info|Opts0];
	       {_,_,_} -> Opts0
	   end,
    %% iff,unless processing is to complex...
    Opts1 = case proplists:is_defined(makedep_side_effect,Opts) of
                true -> proplists:delete(makedep,Opts);
                false -> Opts
            end,
    foldr(fun expand_opt/2, [], Opts1).

expand_opt(basic_validation, Os) ->
    [no_code_generation,to_pp,binary|Os];
expand_opt(strong_validation, Os) ->
    [no_code_generation,to_kernel,binary|Os];
expand_opt(report, Os) ->
    [report_errors,report_warnings|Os];
expand_opt(return, Os) ->
    [return_errors,return_warnings|Os];
expand_opt(no_bsm3, Os) ->
    %% The new bsm pass requires bsm3 instructions.
    [no_bsm3,no_bsm_opt|Os];
expand_opt(r16, Os) ->
    expand_opt_before_21(Os);
expand_opt(r17, Os) ->
    expand_opt_before_21(Os);
expand_opt(r18, Os) ->
    expand_opt_before_21(Os);
expand_opt(r19, Os) ->
    expand_opt_before_21(Os);
expand_opt(r20, Os) ->
    expand_opt_before_21(Os);
expand_opt(r21, Os) ->
    [no_put_tuple2 | expand_opt(no_bsm3, Os)];
expand_opt({debug_info_key,_}=O, Os) ->
    [encrypt_debug_info,O|Os];
expand_opt(no_type_opt=O, Os) ->
    %% Be sure to keep the no_type_opt option so that it will
    %% be recorded in the BEAM file, allowing the test suites
    %% to recompile the file with this option.
    [O,no_ssa_opt_type_start,
     no_ssa_opt_type_continue,
     no_ssa_opt_type_finish | Os];
expand_opt(O, Os) -> [O|Os].

expand_opt_before_21(Os) ->
    [no_put_tuple2, no_get_hd_tl, no_ssa_opt_record,
     no_utf8_atoms | expand_opt(no_bsm3, Os)].

%% format_error(ErrorDescriptor) -> string()

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

format_error(no_native_support) ->
    "this system is not configured for native-code compilation.";
format_error(no_crypto) ->
    "this system is not configured with crypto support.";
format_error(bad_crypto_key) ->
    "invalid crypto key.";
format_error(no_crypto_key) ->
    "no crypto key supplied.";
format_error({unimplemented_instruction,Instruction}) ->
    io_lib:fwrite("native-code compilation failed because of an "
                  "unimplemented instruction (~s).",
		  [Instruction]);
format_error({native, E}) ->
    io_lib:fwrite("native-code compilation failed with reason: ~tP.",
		  [E, 25]);
format_error({native_crash,E,Stk}) ->
    io_lib:fwrite("native-code compilation crashed with reason: ~tP.\n~tP\n",
		  [E,25,Stk,25]);
format_error({open,E}) ->
    io_lib:format("open error '~ts'", [file:format_error(E)]);
format_error({epp,E}) ->
    epp:format_error(E);
format_error(write_error) ->
    "error writing file";
format_error({write_error, Error}) ->
    io_lib:format("error writing file: ~ts", [file:format_error(Error)]);
format_error({rename,From,To,Error}) ->
    io_lib:format("failed to rename ~ts to ~ts: ~ts",
		  [From,To,file:format_error(Error)]);
format_error({delete,File,Error}) ->
    io_lib:format("failed to delete file ~ts: ~ts",
		  [File,file:format_error(Error)]);
format_error({delete_temp,File,Error}) ->
    io_lib:format("failed to delete temporary file ~ts: ~ts",
		  [File,file:format_error(Error)]);
format_error({parse_transform,M,R}) ->
    io_lib:format("error in parse transform '~ts': ~tp", [M, R]);
format_error({undef_parse_transform,M}) ->
    io_lib:format("undefined parse transform '~ts'", [M]);
format_error({core_transform,M,R}) ->
    io_lib:format("error in core transform '~s': ~tp", [M, R]);
format_error({crash,Pass,Reason}) ->
    io_lib:format("internal error in ~p;\ncrash reason: ~ts", [Pass,format_error_reason(Reason)]);
format_error({bad_return,Pass,Reason}) ->
    io_lib:format("internal error in ~p;\nbad return value: ~ts", [Pass,format_error_reason(Reason)]);
format_error({module_name,Mod,Filename}) ->
    io_lib:format("Module name '~s' does not match file name '~ts'", [Mod,Filename]);
format_error(reparsing_invalid_unicode) ->
    "Non-UTF-8 character(s) detected, but no encoding declared. Encode the file in UTF-8 or add \"%% coding: latin-1\" at the beginning of the file. Retrying with latin-1 encoding.".

format_error_reason({Reason, Stack}) when is_list(Stack) ->
    StackFun = fun
	(escript, run,      2) -> true;
	(escript, start,    1) -> true;
	(init,    start_it, 1) -> true;
	(init,    start_em, 1) -> true;
	(_Mod, _Fun, _Arity)   -> false
    end,
    FormatFun = fun (Term, _) -> io_lib:format("~tp", [Term]) end,
    [io_lib:format("~tp", [Reason]),"\n\n",
     erl_error:format_stacktrace(1, Stack, StackFun, FormatFun)];
format_error_reason(Reason) ->
    io_lib:format("~tp", [Reason]).

-type err_warn_info() :: tuple().

%% The compile state record.
-record(compile, {filename="" :: file:filename(),
		  dir=""      :: file:filename(),
		  base=""     :: file:filename(),
		  ifile=""    :: file:filename(),
		  ofile=""    :: file:filename(),
		  module=[]   :: module() | [],
		  core_code=[] :: cerl:c_module() | [],
		  abstract_code=[] :: abstract_code(), %Abstract code for debugger.
		  options=[]  :: [option()],  %Options for compilation
		  mod_options=[]  :: [option()], %Options for module_info
                  encoding=none :: none | epp:source_encoding(),
		  errors=[]     :: [err_warn_info()],
		  warnings=[]   :: [err_warn_info()],
		  extra_chunks=[] :: [{binary(), binary()}]}).

internal({forms,Forms}, Opts0) ->
    {_,Ps} = passes(forms, Opts0),
    Source = proplists:get_value(source, Opts0, ""),
    Opts1 = proplists:delete(source, Opts0),
    Compile = build_compile(Opts1),
    internal_comp(Ps, Forms, Source, "", Compile);
internal({file,File}, Opts) ->
    {Ext,Ps} = passes(file, Opts),
    Compile = build_compile(Opts),
    internal_comp(Ps, none, File, Ext, Compile).

build_compile(Opts0) ->
    ExtraChunks = proplists:get_value(extra_chunks, Opts0, []),
    Opts1 = proplists:delete(extra_chunks, Opts0),
    #compile{options=Opts1,mod_options=Opts1,extra_chunks=ExtraChunks}.

internal_comp(Passes, Code0, File, Suffix, St0) ->
    Dir = filename:dirname(File),
    Base = filename:basename(File, Suffix),
    St1 = St0#compile{filename=File, dir=Dir, base=Base,
		      ifile=erlfile(Dir, Base, Suffix),
		      ofile=objfile(Base, St0)},
    Opts = St1#compile.options,
    Run0 = case member(time, Opts) of
	       true  ->
		   io:format("Compiling ~tp\n", [File]),
		   fun run_tc/3;
	       false ->
                   fun({_Name,Fun}, Code, St) ->
                           catch Fun(Code, St)
                   end
	   end,
    Run = case keyfind(eprof, 1, Opts) of
	      {eprof,EprofPass} ->
		  fun(P, Code, St) ->
			  run_eprof(P, Code, EprofPass, St)
		  end;
	      false ->
		  Run0
	  end,
    case fold_comp(Passes, Run, Code0, St1) of
	{ok,Code,St2} -> comp_ret_ok(Code, St2);
	{error,St2} -> comp_ret_err(St2)
    end.

fold_comp([{delay,Ps0}|Passes], Run, Code, #compile{options=Opts}=St) ->
    Ps = select_passes(Ps0, Opts) ++ Passes,
    fold_comp(Ps, Run, Code, St);
fold_comp([{Name,Test,Pass}|Ps], Run, Code, St) ->
    case Test(St) of
	false ->				%Pass is not needed.
	    fold_comp(Ps, Run, Code, St);
	true ->					%Run pass in the usual way.
	    fold_comp([{Name,Pass}|Ps], Run, Code, St)
    end;
fold_comp([{Name,Pass}|Ps], Run, Code0, St0) ->
    case Run({Name,Pass}, Code0, St0) of
	{ok,Code,St1} ->
            fold_comp(Ps, Run, Code, St1);
	{error,_St1}=Error ->
            Error;
	{'EXIT',Reason} ->
	    Es = [{St0#compile.ifile,[{none,?MODULE,{crash,Name,Reason}}]}],
	    {error,St0#compile{errors=St0#compile.errors ++ Es}};
	Other ->
	    Es = [{St0#compile.ifile,[{none,?MODULE,{bad_return,Name,Other}}]}],
	    {error,St0#compile{errors=St0#compile.errors ++ Es}}
    end;
fold_comp([], _Run, Code, St) -> {ok,Code,St}.

run_sub_passes_1([{Name,Run}|Ps], Runner, St0)
  when is_atom(Name), is_function(Run, 1) ->
    try Runner(Name, Run, St0) of
        St ->
            run_sub_passes_1(Ps, Runner, St)
    catch
        C:E:Stk ->
            io:format("Sub pass ~s\n", [Name]),
            erlang:raise(C, E, Stk)
    end;
run_sub_passes_1([], _, St) -> St.

run_tc({Name,Fun}, Code, St) ->
    put(?SUB_PASS_TIMES, []),
    T1 = erlang:monotonic_time(),
    Val = (catch Fun(Code, St)),
    T2 = erlang:monotonic_time(),
    Times = erase(?SUB_PASS_TIMES),
    Elapsed = erlang:convert_time_unit(T2 - T1, native, microsecond),
    Mem0 = erts_debug:flat_size(Val)*erlang:system_info(wordsize),
    Mem = lists:flatten(io_lib:format("~.1f kB", [Mem0/1024])),
    io:format(" ~-30s: ~10.3f s ~12s\n",
	      [Name,Elapsed/1000000,Mem]),
    print_times(Times, Name),
    Val.

print_times(Times0, Name) ->
    Fam0 = sofs:relation(Times0),
    Fam1 = sofs:rel2fam(Fam0),
    Fam2 = sofs:to_external(Fam1),
    Fam3 = [{W,lists:sum(Times)} || {W,Times} <- Fam2],
    Fam = reverse(lists:keysort(2, Fam3)),
    Total = case lists:sum([T || {_,T} <- Fam]) of
                0 -> 1;
                Total0 -> Total0
            end,
    case Fam of
        [] ->
            ok;
        [_|_] ->
            io:format("    %% Sub passes of ~s from slowest to fastest:\n", [Name]),
            print_times_1(Fam, Total)
    end.

print_times_1([{Name,T}|Ts], Total) ->
    Elapsed = erlang:convert_time_unit(T, native, microsecond),
    io:format("    ~-27s: ~10.3f s ~3w %\n",
              [Name,Elapsed/1000000,round(100*T/Total)]),
    print_times_1(Ts, Total);
print_times_1([], _Total) -> ok.

run_eprof({Name,Fun}, Code, Name, St) ->
    io:format("~p: Running eprof\n", [Name]),
    c:appcall(tools, eprof, start_profiling, [[self()]]),
    Val = (catch Fun(Code, St)),
    c:appcall(tools, eprof, stop_profiling, []),
    c:appcall(tools, eprof, analyze, []),
    Val;
run_eprof({_,Fun}, Code, _, St) ->
    catch Fun(Code, St).

comp_ret_ok(Code, #compile{warnings=Warn0,module=Mod,options=Opts}=St) ->
    case werror(St) of
        true ->
            case member(report_warnings, Opts) of
                true ->
		    io:format("~p: warnings being treated as errors\n",
			      [?MODULE]);
                false ->
		    ok
            end,
            comp_ret_err(St);
        false ->
            Warn = messages_per_file(Warn0),
            report_warnings(St#compile{warnings = Warn}),
            Ret1 = case member(binary, Opts) andalso
		       not member(no_code_generation, Opts) of
                       true -> [Code];
                       false -> []
                   end,
            Ret2 = case member(return_warnings, Opts) of
                       true -> Ret1 ++ [Warn];
                       false -> Ret1
                   end,
            list_to_tuple([ok,Mod|Ret2])
    end.

comp_ret_err(#compile{warnings=Warn0,errors=Err0,options=Opts}=St) ->
    Warn = messages_per_file(Warn0),
    Err = messages_per_file(Err0),
    report_errors(St#compile{errors=Err}),
    report_warnings(St#compile{warnings=Warn}),
    case member(return_errors, Opts) of
	true -> {error,Err,Warn};
	false -> error
    end.

not_werror(St) -> not werror(St).

werror(#compile{options=Opts,warnings=Ws}) ->
    Ws =/= [] andalso member(warnings_as_errors, Opts).

%% messages_per_file([{File,[Message]}]) -> [{File,[Message]}]
messages_per_file(Ms) ->
    T = lists:sort([{File,M} || {File,Messages} <- Ms, M <- Messages]),
    PrioMs = [erl_scan, epp, erl_parse],
    {Prio0, Rest} =
        lists:mapfoldl(fun(M, A) ->
                               lists:partition(fun({_,{_,Mod,_}}) -> Mod =:= M;
                                                  (_) -> false
                                               end, A)
                       end, T, PrioMs),
    Prio = lists:sort(fun({_,{L1,_,_}}, {_,{L2,_,_}}) -> L1 =< L2 end,
                      lists:append(Prio0)),
    flatmap(fun mpf/1, [Prio, Rest]).

mpf(Ms) ->
    [{File,[M || {F,M} <- Ms, F =:= File]} ||
	File <- lists:usort([F || {F,_} <- Ms])].

%% passes(forms|file, [Option]) -> {Extension,[{Name,PassFun}]}
%%  Figure out the extension of the input file and which passes
%%  that need to be run.

passes(Type, Opts) ->
    {Ext,Passes0} = passes_1(Opts),
    Passes1 = case Type of
		  file ->
                      Passes0;
		  forms ->
                      fix_first_pass(Passes0)
	      end,
    Passes = select_passes(Passes1, Opts),

    %% If the last pass saves the resulting binary to a file,
    %% insert a first pass to remove the file (unless the
    %% source file is a BEAM file).
    {Ext,case last(Passes) of
	     {save_binary,_TestFun,_Fun} ->
		 case Passes of
		     [{read_beam_file,_}|_] ->
			 %% The BEAM is both input and output.
			 %% Don't remove it.
			 Passes;
		     _ ->
			 [?pass(remove_file)|Passes]
		 end;
	     _ ->
		 Passes
	 end}.

passes_1([Opt|Opts]) ->
    case pass(Opt) of
	{_,_}=Res -> Res;
	none -> passes_1(Opts)
    end;
passes_1([]) ->
    {".erl",[?pass(parse_module)|standard_passes()]}.

pass(from_core) ->
    {".core",[?pass(parse_core)|core_passes()]};
pass(from_asm) ->
    {".S",[?pass(beam_consult_asm)|asm_passes()]};
pass(from_beam) ->
    {".beam",[?pass(read_beam_file)|binary_passes()]};
pass(_) -> none.

%% For compilation from forms, replace the first pass with a pass
%% that retrieves the module name. The module name is needed for
%% proper diagnostics and for compilation to native code.

fix_first_pass([{parse_core,_}|Passes]) ->
    [?pass(get_module_name_from_core)|Passes];
fix_first_pass([{beam_consult_asm,_}|Passes]) ->
    [?pass(get_module_name_from_asm)|Passes];
fix_first_pass([{read_beam_file,_}|Passes]) ->
    [?pass(get_module_name_from_beam)|Passes];
fix_first_pass([_|Passes]) ->
    %% When compiling from abstract code, the module name
    %% will be set after running the v3_core pass.
    Passes.


%% select_passes([Command], Opts) -> [{Name,Function}]
%%  Interpret the lists of commands to return a pure list of passes.
%%
%%  Command can be one of:
%%
%%    {pass,Mod}	Will be expanded to a call to the external
%%			function Mod:module(Code, Options).  This
%%			function must transform the code and return
%%			{ok,NewCode} or {error,Term}.
%%			Example: {pass,beam_codegen}
%%
%%    {Name,Fun}	Name is an atom giving the name of the pass.
%%    			Fun is an 'fun' taking one argument: a compile record.
%%			The fun should return {ok,NewCompileRecord} or
%%			{error,NewCompileRecord}.
%%			Note: ?pass(Name) is equvivalent to {Name,fun Name/1}.
%%			Example: ?pass(parse_module)
%%
%%    {Name,Test,Fun}	Like {Name,Fun} above, but the pass will be run
%%			(and listed by the `time' option) only if Test(St)
%%			returns true.
%%
%%    {src_listing,Ext}	Produces an Erlang source listing with the
%%			the file extension Ext.  (Ext should not contain
%%			a period.)  No more passes will be run.
%%
%%    {listing,Ext}	Produce an listing of the terms in the internal
%%			representation.  The extension of the listing
%%			file will be Ext.  (Ext should not contain
%%			a period.)   No more passes will be run.
%%
%%    done              End compilation at this point.
%%
%%    {done,Ext}        End compilation at this point. Produce a listing
%%                      as with {listing,Ext}, unless 'binary' is
%%                      specified, in which case the current
%%                      representation of the code is returned without
%%                      creating an output file.
%%
%%    {iff,Flag,Cmd}	If the given Flag is given in the option list,
%%			Cmd will be interpreted as a command.
%%			Otherwise, Cmd will be ignored.
%%			Example: {iff,dcg,{listing,"codegen}}
%%
%%    {unless,Flag,Cmd}	If the given Flag is NOT given in the option list,
%%			Cmd will be interpreted as a command.
%%			Otherwise, Cmd will be ignored.
%%			Example: {unless,no_kernopt,{pass,sys_kernopt}}
%%

select_passes([{pass,Mod}|Ps], Opts) ->
    F = fun(Code0, St) ->
		case catch Mod:module(Code0, St#compile.options) of
		    {ok,Code} ->
			{ok,Code,St};
		    {ok,Code,Ws} ->
			{ok,Code,St#compile{warnings=St#compile.warnings++Ws}};
		    {error,Es} ->
			{error,St#compile{errors=St#compile.errors ++ Es}}
		end
	end,
    [{Mod,F}|select_passes(Ps, Opts)];
select_passes([{src_listing,Ext}|_], _Opts) ->
    [{listing,fun (Code, St) -> src_listing(Ext, Code, St) end}];
select_passes([{listing,Ext}|_], _Opts) ->
    [{listing,fun (Code, St) -> listing(Ext, Code, St) end}];
select_passes([done|_], _Opts) ->
    [];
select_passes([{done,Ext}|_], Opts) ->
    select_passes([{unless,binary,{listing,Ext}}], Opts);
select_passes([{iff,Flag,Pass}|Ps], Opts) ->
    select_cond(Flag, true, Pass, Ps, Opts);
select_passes([{unless,Flag,Pass}|Ps], Opts) ->
    select_cond(Flag, false, Pass, Ps, Opts);
select_passes([{_,Fun}=P|Ps], Opts) when is_function(Fun) ->
    [P|select_passes(Ps, Opts)];
select_passes([{delay,Passes0}|Ps], Opts) when is_list(Passes0) ->
    %% Delay evaluation of compiler options and which compiler passes to run.
    %% Since we must know beforehand whether a listing will be produced, we
    %% will go through the list of passes and evaluate all conditions that
    %% select a list pass.
    case select_list_passes(Passes0, Opts) of
	{done,Passes} ->
	    [{delay,Passes}];
	{not_done,Passes} ->
	    [{delay,Passes}|select_passes(Ps, Opts)]
    end;
select_passes([{_,Test,Fun}=P|Ps], Opts) when is_function(Test),
					      is_function(Fun) ->
    [P|select_passes(Ps, Opts)];
select_passes([], _Opts) ->
    [];
select_passes([List|Ps], Opts) when is_list(List) ->
    case select_passes(List, Opts) of
	[] -> select_passes(Ps, Opts);
	Nested ->
	    case last(Nested) of
		{listing,_Fun} -> Nested;
		_Other         -> Nested ++ select_passes(Ps, Opts)
	    end
    end.

select_cond(Flag, ShouldBe, Pass, Ps, Opts) ->
    ShouldNotBe = not ShouldBe,
    case member(Flag, Opts) of
	ShouldBe    -> select_passes([Pass|Ps], Opts);
	ShouldNotBe -> select_passes(Ps, Opts)
    end.

%% select_list_passes([Pass], Opts) -> {done,[Pass]} | {not_done,[Pass]}
%%  Evaluate all conditions having to do with listings in the list of
%%  passes.

select_list_passes(Ps, Opts) ->
    select_list_passes_1(Ps, Opts, []).

select_list_passes_1([{iff,Flag,{listing,_}=Listing}|Ps], Opts, Acc) ->
    case member(Flag, Opts) of
	true -> {done,reverse(Acc, [Listing])};
	false -> select_list_passes_1(Ps, Opts, Acc)
    end;
select_list_passes_1([{iff,Flag,{done,Ext}}|Ps], Opts, Acc) ->
    case member(Flag, Opts) of
	false ->
	    select_list_passes_1(Ps, Opts, Acc);
	true ->
	    {done,case member(binary, Opts) of
		      false -> reverse(Acc, [{listing,Ext}]);
		      true -> reverse(Acc)
		  end}
    end;
select_list_passes_1([{iff=Op,Flag,List0}|Ps], Opts, Acc) when is_list(List0) ->
    case select_list_passes(List0, Opts) of
	{done,List} -> {done,reverse(Acc) ++ List};
	{not_done,List} -> select_list_passes_1(Ps, Opts, [{Op,Flag,List}|Acc])
    end;
select_list_passes_1([{unless=Op,Flag,List0}|Ps], Opts, Acc) when is_list(List0) ->
    case select_list_passes(List0, Opts) of
	{done,List} -> {done,reverse(Acc) ++ List};
	{not_done,List} -> select_list_passes_1(Ps, Opts, [{Op,Flag,List}|Acc])
    end;
select_list_passes_1([P|Ps], Opts, Acc) ->
    select_list_passes_1(Ps, Opts, [P|Acc]);
select_list_passes_1([], _, Acc) ->
    {not_done,reverse(Acc)}.

%% The standard passes (almost) always run.

standard_passes() ->
    [?pass(transform_module),

     {iff,makedep_side_effect,?pass(makedep_and_output)},
     {iff,makedep,[
	 ?pass(makedep),
	 {unless,binary,?pass(makedep_output)}
       ]},
     {iff,makedep,done},

     {iff,'dpp',{listing,"pp"}},
     ?pass(lint_module),
     {iff,'P',{src_listing,"P"}},
     {iff,'to_pp',{done,"P"}},

     {iff,'dabstr',{listing,"abstr"}},
     {iff,debug_info,?pass(save_abstract_code)},

     ?pass(expand_records),
     {iff,'dexp',{listing,"expand"}},
     {iff,'E',{src_listing,"E"}},
     {iff,'to_exp',{done,"E"}},

     %% Conversion to Core Erlang.
     ?pass(core),
     {iff,'dcore',{listing,"core"}},
     {iff,'to_core0',{done,"core"}}
     | core_passes()].

core_passes() ->
    %% Optimization and transforms of Core Erlang code.
    [{iff,clint0,?pass(core_lint_module)},
     {delay,
      [{unless,no_copt,
       [{core_old_inliner,fun test_old_inliner/1,fun core_old_inliner/2},
	{iff,doldinline,{listing,"oldinline"}},
	{unless,no_fold,{pass,sys_core_fold}},
	{iff,dcorefold,{listing,"corefold"}},
	{core_inline_module,fun test_core_inliner/1,fun core_inline_module/2},
	{iff,dinline,{listing,"inline"}},
        {core_fold_after_inlining,fun test_any_inliner/1,
         fun core_fold_module_after_inlining/2},
        {iff,dcopt,{listing,"copt"}},
        {unless,no_alias,{pass,sys_core_alias}},
        {iff,dalias,{listing,"core_alias"}},
	?pass(core_transforms)]},
       {iff,'to_core',{done,"core"}}]}
     | kernel_passes()].

kernel_passes() ->
    %% Optimizations that must be done after all other optimizations.
    [{pass,sys_core_bsm},
     {iff,dcbsm,{listing,"core_bsm"}},

     {iff,clint,?pass(core_lint_module)},
     {iff,core,?pass(save_core_code)},

     %% Kernel Erlang and code generation.
     ?pass(v3_kernel),
     {iff,dkern,{listing,"kernel"}},
     {iff,'to_kernel',{done,"kernel"}},
     {pass,beam_kernel_to_ssa},
     {iff,dssa,{listing,"ssa"}},
     {iff,ssalint,{pass,beam_ssa_lint}},
     {delay,
      [{unless,no_share_opt,{pass,beam_ssa_share}},
       {iff,dssashare,{listing,"ssashare"}},
       {iff,ssalint,{pass,beam_ssa_lint}},
       {unless,no_bsm_opt,{pass,beam_ssa_bsm}},
       {iff,dssabsm,{listing,"ssabsm"}},
       {iff,ssalint,{pass,beam_ssa_lint}},
       {unless,no_fun_opt,{pass,beam_ssa_funs}},
       {iff,dssafuns,{listing,"ssafuns"}},
       {iff,ssalint,{pass,beam_ssa_lint}},
       {unless,no_ssa_opt,{pass,beam_ssa_opt}},
       {iff,dssaopt,{listing,"ssaopt"}},
       {iff,ssalint,{pass,beam_ssa_lint}},
       {unless,no_recv_opt,{pass,beam_ssa_recv}},
       {iff,drecv,{listing,"recv"}}]},
     {pass,beam_ssa_pre_codegen},
     {iff,dprecg,{listing,"precodegen"}},
     {iff,ssalint,{pass,beam_ssa_lint}},
     {pass,beam_ssa_codegen},
     {iff,dcg,{listing,"codegen"}},
     {iff,doldcg,{listing,"codegen"}}
     | asm_passes()].

asm_passes() ->
    %% Assembly level optimisations.
    [{delay,
      [{pass,beam_a},
       {iff,da,{listing,"a"}},
       {unless,no_postopt,
	[{pass,beam_block},
	 {iff,dblk,{listing,"block"}},
	 {unless,no_except,{pass,beam_except}},
	 {iff,dexcept,{listing,"except"}},
	 {unless,no_jopt,{pass,beam_jump}},
	 {iff,djmp,{listing,"jump"}},
	 {unless,no_peep_opt,{pass,beam_peep}},
	 {iff,dpeep,{listing,"peep"}},
	 {pass,beam_clean},
	 {iff,dclean,{listing,"clean"}},
	 {unless,no_stack_trimming,{pass,beam_trim}},
	 {iff,dtrim,{listing,"trim"}},
	 {pass,beam_flatten}]},

       %% If post optimizations are turned off, we still
       %% need to do a few clean-ups to code.
       {iff,no_postopt,[{pass,beam_clean}]},

       {iff,diffable,?pass(diffable)},
       {pass,beam_z},
       {iff,diffable,{listing,"S"}},
       {iff,dz,{listing,"z"}},
       {iff,dopt,{listing,"optimize"}},
       {iff,'S',{listing,"S"}},
       {iff,'to_asm',{done,"S"}}]},
     {pass,beam_validator},
     ?pass(beam_asm)
     | binary_passes()].

binary_passes() ->
    [{iff,'to_dis',?pass(to_dis)},
     {native_compile,fun test_native/1,fun native_compile/2},
     {unless,binary,?pass(save_binary,not_werror)}
    ].

%%%
%%% Compiler passes.
%%%

%% Remove the target file so we don't have an old one if the compilation fail.
remove_file(Code, St) ->
    _ = file:delete(St#compile.ofile),
    {ok,Code,St}.

-record(asm_module, {module,
		     exports,
		     labels,
		     functions=[],
		     cfun,
		     code,
		     attributes=[]}).

preprocess_asm_forms(Forms) ->
    R = #asm_module{},
    R1 = collect_asm(Forms, R),
    {R1#asm_module.module,
     {R1#asm_module.module,
      R1#asm_module.exports,
      R1#asm_module.attributes,
      R1#asm_module.functions,
      R1#asm_module.labels}}.

collect_asm([], R) ->
    case R#asm_module.cfun of
	undefined ->
	    R;
	{A,B,C} ->
	    R#asm_module{functions=R#asm_module.functions++
			 [{function,A,B,C,R#asm_module.code}]}
    end;
collect_asm([{module,M} | Rest], R) ->
    collect_asm(Rest, R#asm_module{module=M});
collect_asm([{exports,M} | Rest], R) ->
    collect_asm(Rest, R#asm_module{exports=M});
collect_asm([{labels,M} | Rest], R) ->
    collect_asm(Rest, R#asm_module{labels=M});
collect_asm([{function,A,B,C} | Rest], R) ->
    R1 = case R#asm_module.cfun of
	     undefined ->
		 R;
	     {A0,B0,C0} ->
		 R#asm_module{functions=R#asm_module.functions++
			      [{function,A0,B0,C0,R#asm_module.code}]}
	 end,
    collect_asm(Rest, R1#asm_module{cfun={A,B,C}, code=[]});
collect_asm([{attributes, Attr} | Rest], R) ->
    collect_asm(Rest, R#asm_module{attributes=Attr});
collect_asm([X | Rest], R) ->
    collect_asm(Rest, R#asm_module{code=R#asm_module.code++[X]}).

beam_consult_asm(_Code, St) ->
    case file:consult(St#compile.ifile) of
	{ok,Forms0} ->
            Encoding = epp:read_encoding(St#compile.ifile),
	    {Module,Forms} = preprocess_asm_forms(Forms0),
	    {ok,Forms,St#compile{module=Module,encoding=Encoding}};
	{error,E} ->
	    Es = [{St#compile.ifile,[{none,?MODULE,{open,E}}]}],
	    {error,St#compile{errors=St#compile.errors ++ Es}}
    end.

get_module_name_from_asm({Mod,_,_,_,_}=Asm, St) ->
    {ok,Asm,St#compile{module=Mod}};
get_module_name_from_asm(Asm, St) ->
    %% Invalid Beam assembly code. Let it crash in a later pass.
    {ok,Asm,St}.

read_beam_file(_Code, St) ->
    case file:read_file(St#compile.ifile) of
	{ok,Beam} ->
	    Infile = St#compile.ifile,
	    case no_native_compilation(Infile, St) of
		true ->
		    {ok,none,St#compile{module=none}};
		false ->
		    Mod0 = filename:rootname(filename:basename(Infile)),
		    Mod = list_to_atom(Mod0),
		    {ok,Beam,St#compile{module=Mod,ofile=Infile}}
	    end;
	{error,E} ->
	    Es = [{St#compile.ifile,[{none,?MODULE,{open,E}}]}],
	    {error,St#compile{errors=St#compile.errors ++ Es}}
    end.

get_module_name_from_beam(Beam, St) ->
    case beam_lib:info(Beam) of
        {error,beam_lib,Error} ->
	    Es = [{"((forms))",[{none,beam_lib,Error}]}],
            {error,St#compile{errors=St#compile.errors ++ Es}};
        Info ->
            {module,Mod} = keyfind(module, 1, Info),
            {ok,Beam,St#compile{module=Mod}}
    end.

no_native_compilation(BeamFile, #compile{options=Opts0}) ->
    case beam_lib:chunks(BeamFile, ["CInf"]) of
	{ok,{_,[{"CInf",Term0}]}} ->
	    Term = binary_to_term(Term0),

	    %% Compiler options in the beam file will override
	    %% options passed to the compiler.
	    Opts = proplists:get_value(options, Term, []) ++ Opts0,
	    member(no_new_funs, Opts) orelse not is_native_enabled(Opts);
	_ -> false
    end.

parse_module(_Code, St0) ->
    case do_parse_module(utf8, St0) of
	{ok,_,_}=Ret ->
	    Ret;
	{error,_}=Ret ->
	    Ret;
	{invalid_unicode,File,Line} ->
	    case do_parse_module(latin1, St0) of
		{ok,Code,St} ->
		    Es = [{File,[{Line,?MODULE,reparsing_invalid_unicode}]}],
		    {ok,Code,St#compile{warnings=Es++St#compile.warnings}};
		{error,St} ->
		    Es = [{File,[{Line,?MODULE,reparsing_invalid_unicode}]}],
		    {error,St#compile{errors=Es++St#compile.errors}}
	    end
    end.

do_parse_module(DefEncoding, #compile{ifile=File,options=Opts,dir=Dir}=St) ->
    SourceName0 = proplists:get_value(source, Opts, File),
    SourceName = case member(deterministic, Opts) of
                     true -> filename:basename(SourceName0);
                     false -> SourceName0
                 end,
    R = epp:parse_file(File,
                       [{includes,[".",Dir|inc_paths(Opts)]},
                        {source_name, SourceName},
                        {macros,pre_defs(Opts)},
                        {default_encoding,DefEncoding},
                        extra]),
    case R of
	{ok,Forms,Extra} ->
	    Encoding = proplists:get_value(encoding, Extra),
	    case find_invalid_unicode(Forms, File) of
		none ->
		    {ok,Forms,St#compile{encoding=Encoding}};
		{invalid_unicode,_,_}=Ret ->
		    case Encoding of
			none ->
			    Ret;
			_ ->
			    {ok,Forms,St#compile{encoding=Encoding}}
		    end
	    end;
	{error,E} ->
	    Es = [{St#compile.ifile,[{none,?MODULE,{epp,E}}]}],
	    {error,St#compile{errors=St#compile.errors ++ Es}}
    end.

find_invalid_unicode([H|T], File0) ->
    case H of
	{attribute,_,file,{File,_}} ->
	    find_invalid_unicode(T, File);
	{error,{Line,file_io_server,invalid_unicode}} ->
	    {invalid_unicode,File0,Line};
	_Other ->
	    find_invalid_unicode(T, File0)
    end;
find_invalid_unicode([], _) -> none.

parse_core(_Code, St) ->
    case file:read_file(St#compile.ifile) of
	{ok,Bin} ->
	    case core_scan:string(binary_to_list(Bin)) of
		{ok,Toks,_} ->
		    case core_parse:parse(Toks) of
			{ok,Mod} ->
			    Name = (Mod#c_module.name)#c_literal.val,
			    {ok,Mod,St#compile{module=Name}};
			{error,E} ->
			    Es = [{St#compile.ifile,[E]}],
			    {error,St#compile{errors=St#compile.errors ++ Es}}
		    end;
		{error,E,_} ->
		    Es = [{St#compile.ifile,[E]}],
		    {error,St#compile{errors=St#compile.errors ++ Es}}
	    end;
	{error,E} ->
	    Es = [{St#compile.ifile,[{none,compile,{open,E}}]}],
	    {error,St#compile{errors=St#compile.errors ++ Es}}
    end.

get_module_name_from_core(Core, St) ->
    try
        Mod = cerl:concrete(cerl:module_name(Core)),
        {ok,Core,St#compile{module=Mod}}
    catch
        _:_ ->
            %% Invalid Core Erlang code. Let it crash in a later pass.
            {ok,Core,St}
    end.

compile_options([{attribute,_L,compile,C}|Fs]) when is_list(C) ->
    C ++ compile_options(Fs);
compile_options([{attribute,_L,compile,C}|Fs]) ->
    [C|compile_options(Fs)];
compile_options([_F|Fs]) -> compile_options(Fs);
compile_options([]) -> [].

clean_parse_transforms(Fs) ->
    clean_parse_transforms_1(Fs, []).

clean_parse_transforms_1([{attribute,L,compile,C0}|Fs], Acc) when is_list(C0) ->
    C = lists:filter(fun({parse_transform,_}) -> false;
			(_) -> true
		     end, C0),
    clean_parse_transforms_1(Fs, [{attribute,L,compile,C}|Acc]);
clean_parse_transforms_1([{attribute,_,compile,{parse_transform,_}}|Fs], Acc) ->
    clean_parse_transforms_1(Fs, Acc);
clean_parse_transforms_1([F|Fs], Acc) ->
    clean_parse_transforms_1(Fs, [F|Acc]);
clean_parse_transforms_1([], Acc) -> reverse(Acc).

transforms(Os) -> [ M || {parse_transform,M} <- Os ].

transform_module(Code0, #compile{options=Opt}=St) ->
    %% Extract compile options from code into options field.
    case transforms(Opt ++ compile_options(Code0)) of
	[] ->
            %% No parse transforms.
            {ok,Code0,St};
	Ts ->
	    %% Remove parse_transform attributes from the abstract code to
	    %% prevent parse transforms to be run more than once.
	    Code = clean_parse_transforms(Code0),
	    foldl_transform(Ts, Code, St)
    end.

foldl_transform([T|Ts], Code0, St) ->
    Name = "transform " ++ atom_to_list(T),
    case code:ensure_loaded(T) =:= {module,T} andalso
        erlang:function_exported(T, parse_transform, 2) of
        true ->
            Fun = fun(Code, S) ->
                          T:parse_transform(Code, S#compile.options)
                  end,
            Run = case member(time, St#compile.options) of
                      true  ->
                          fun run_tc/3;
                      false ->
                          fun({_Name,F}, Code, S) ->
                                  catch F(Code, S)
                          end
                  end,
            case Run({Name, Fun}, Code0, St) of
                {error,Es,Ws} ->
                    {error,St#compile{warnings=St#compile.warnings ++ Ws,
                                      errors=St#compile.errors ++ Es}};
                {'EXIT',R} ->
                    Es = [{St#compile.ifile,[{none,compile,
                                              {parse_transform,T,R}}]}],
                    {error,St#compile{errors=St#compile.errors ++ Es}};
                {warning, Forms, Ws} ->
                    foldl_transform(Ts, Forms,
                                    St#compile{warnings=St#compile.warnings ++ Ws});
                Forms ->
                    foldl_transform(Ts, Forms, St)
            end;
        false ->
            Es = [{St#compile.ifile,[{none,compile,
                                      {undef_parse_transform,T}}]}],
            {error,St#compile{errors=St#compile.errors ++ Es}}
    end;
foldl_transform([], Code, St) -> {ok,Code,St}.

get_core_transforms(Opts) -> [M || {core_transform,M} <- Opts].

core_transforms(Code, St) ->
    %% The options field holds the complete list of options at this
    Ts = get_core_transforms(St#compile.options),
    foldl_core_transforms(Ts, Code, St).

foldl_core_transforms([T|Ts], Code0, St) ->
    Name = "core transform " ++ atom_to_list(T),
    Fun = fun(Code, S) -> T:core_transform(Code, S#compile.options) end,
    Run = case member(time, St#compile.options) of
	      true ->
                  fun run_tc/3;
	      false ->
                  fun({_Name,F}, Code, S) ->
                          catch F(Code, S)
                  end
	  end,
    case Run({Name, Fun}, Code0, St) of
	{'EXIT',R} ->
	    Es = [{St#compile.ifile,[{none,compile,{core_transform,T,R}}]}],
	    {error,St#compile{errors=St#compile.errors ++ Es}};
	Forms ->
	    foldl_core_transforms(Ts, Forms, St)
    end;
foldl_core_transforms([], Code, St) -> {ok,Code,St}.

%%% Fetches the module name from a list of forms. The module attribute must
%%% be present.
get_module([{attribute,_,module,M} | _]) -> M;
get_module([_ | Rest]) ->
    get_module(Rest).

%%% A #compile state is returned, where St.base has been filled in
%%% with the module name from Forms, as a string, in case it wasn't
%%% set in St (i.e., it was "").
add_default_base(St, Forms) ->
    F = St#compile.filename,
    case F of
	"" ->
 	    M = get_module(Forms),
	    St#compile{base=atom_to_list(M)};
	_ ->
	    St
    end.

lint_module(Code, St) ->
    case erl_lint:module(Code, St#compile.ifile, St#compile.options) of
	{ok,Ws} ->
	    %% Insert name of module as base name, if needed. This is
	    %% for compile:forms to work with listing files.
	    St1 = add_default_base(St, Code),
	    {ok,Code,St1#compile{warnings=St1#compile.warnings ++ Ws}};
	{error,Es,Ws} ->
	    {error,St#compile{warnings=St#compile.warnings ++ Ws,
			      errors=St#compile.errors ++ Es}}
    end.

core_lint_module(Code, St) ->
    case core_lint:module(Code, St#compile.options) of
	{ok,Ws} ->
	    {ok,Code,St#compile{warnings=St#compile.warnings ++ Ws}};
	{error,Es,Ws} ->
	    {error,St#compile{warnings=St#compile.warnings ++ Ws,
			      errors=St#compile.errors ++ Es}}
    end.

%% makedep + output and continue
makedep_and_output(Code0, St) ->
    {ok,DepCode,St1} = makedep(Code0,St),
    case makedep_output(DepCode, St1) of
        {ok,_IgnoreCode,St2} ->
            {ok,Code0,St2};
        {error,St2} ->
            {error,St2}
    end.

makedep(Code0, #compile{ifile=Ifile,ofile=Ofile,options=Opts}=St) ->

    %% Get the target of the Makefile rule.
    Target0 =
	case proplists:get_value(makedep_target, Opts) of
	    undefined ->
		%% The target is derived from the output filename: possibly
		%% remove the current working directory to obtain a relative
		%% path.
		shorten_filename(Ofile);
	    T ->
		%% The caller specified one.
		T
	end,

    %% Quote the target is the called asked for this.
    Target1 = case proplists:get_value(makedep_quote_target, Opts) of
		  true ->
		      %% For now, only "$" is replaced by "$$".
		      Fun = fun
				($$) -> "$$";
				(C)  -> C
			    end,
		      map(Fun, Target0);
		  _ ->
		      Target0
	      end,
    Target = Target1 ++ ":",

    %% List the dependencies (includes) for this target.
    {MainRule,PhonyRules} = makedep_add_headers(
      Ifile,          % The input file name.
      Code0,          % The parsed source.
      [],             % The list of dependencies already added.
      length(Target), % The current line length.
      Target,         % The target.
      "",             % Phony targets.
      Opts),

    %% Prepare the content of the Makefile. For instance:
    %%   hello.erl: hello.hrl common.hrl
    %%
    %% Or if phony targets are enabled:
    %%   hello.erl: hello.hrl common.hrl
    %%
    %%   hello.hrl:
    %%
    %%   common.hrl:
    Makefile = case proplists:get_value(makedep_phony, Opts) of
		   true -> MainRule ++ PhonyRules;
		   _ -> MainRule
	       end,
    Code = iolist_to_binary([Makefile,"\n"]),
    {ok,Code,St}.

makedep_add_headers(Ifile, [{attribute,_,file,{File,_}}|Rest],
		    Included, LineLen, MainTarget, Phony, Opts) ->
    %% The header "File" exists, add it to the dependencies.
    {Included1,LineLen1,MainTarget1,Phony1} =
	makedep_add_header(Ifile, Included, LineLen, MainTarget, Phony, File),
    makedep_add_headers(Ifile, Rest, Included1, LineLen1,
			MainTarget1, Phony1, Opts);
makedep_add_headers(Ifile, [{error,{_,epp,{include,file,File}}}|Rest],
		    Included, LineLen, MainTarget, Phony, Opts) ->
    %% The header "File" doesn't exist, do we add it to the dependencies?
    case proplists:get_value(makedep_add_missing, Opts) of
        true ->
            {Included1,LineLen1,MainTarget1,Phony1} =
		makedep_add_header(Ifile, Included, LineLen, MainTarget,
				   Phony, File),
            makedep_add_headers(Ifile, Rest, Included1, LineLen1,
				MainTarget1, Phony1, Opts);
        _ ->
            makedep_add_headers(Ifile, Rest, Included, LineLen,
				MainTarget, Phony, Opts)
    end;
makedep_add_headers(Ifile, [_|Rest], Included, LineLen,
		    MainTarget, Phony, Opts) ->
    makedep_add_headers(Ifile, Rest, Included,
			LineLen, MainTarget, Phony, Opts);
makedep_add_headers(_Ifile, [], _Included, _LineLen,
		    MainTarget, Phony, _Opts) ->
    {MainTarget,Phony}.

makedep_add_header(Ifile, Included, LineLen, MainTarget, Phony, File) ->
    case member(File, Included) of
	true ->
	    %% This file was already listed in the dependencies, skip it.
            {Included,LineLen,MainTarget,Phony};
	false ->
            Included1 = [File|Included],

	    %% Remove "./" in front of the dependency filename.
	    File1 = case File of
			"./" ++ File0 -> File0;
			_ -> File
	    end,

	    %% Prepare the phony target name.
	    Phony1 = case File of
			 Ifile -> Phony;
			 _     -> Phony ++ "\n\n" ++ File1 ++ ":"
	    end,

	    %% Add the file to the dependencies. Lines longer than 76 columns
	    %% are splitted.
	    if
		LineLen + 1 + length(File1) > 76 ->
                    LineLen1 = 2 + length(File1),
                    MainTarget1 = MainTarget ++ " \\\n  " ++ File1,
                    {Included1,LineLen1,MainTarget1,Phony1};
		true ->
                    LineLen1 = LineLen + 1 + length(File1),
                    MainTarget1 = MainTarget ++ " " ++ File1,
                    {Included1,LineLen1,MainTarget1,Phony1}
	    end
    end.

makedep_output(Code, #compile{options=Opts,ofile=Ofile}=St) ->
    %% Write this Makefile (Code) to the selected output.
    %% If no output is specified, the default is to write to a file named after
    %% the output file.
    Output0 = case proplists:get_value(makedep_output, Opts) of
		  undefined ->
		      %% Prepare the default filename.
		      outfile(filename:basename(Ofile, ".beam"), "Pbeam", Opts);
		  O ->
		      O
	      end,

    %% If the caller specified an io_device(), there's nothing to do. If he
    %% specified a filename, we must create it. Furthermore, this created file
    %% must be closed before returning.
    Ret = case Output0 of
	      _ when is_list(Output0) ->
		  case file:delete(Output0) of
		      Ret2 when Ret2 =:= ok; Ret2 =:= {error,enoent} ->
			  case file:open(Output0, [write]) of
			      {ok,IODev} ->
				  {ok,IODev,true};
			      {error,Reason2} ->
				  {error,open,Reason2}
			  end;
		      {error,Reason1} ->
			  {error,delete,Reason1}
		  end;
	      _ ->
		  {ok,Output0,false}
	  end,

    case Ret of
	{ok,Output1,CloseOutput} ->
	    try
		%% Write the Makefile.
		io:fwrite(Output1, "~ts", [Code]),
		%% Close the file if relevant.
		if
		    CloseOutput -> ok = file:close(Output1);
		    true -> ok
		end,
		{ok,Code,St}
	    catch
		error:_ ->
		    %% Couldn't write to output Makefile.
		    Err = {St#compile.ifile,[{none,?MODULE,write_error}]},
		    {error,St#compile{errors=St#compile.errors++[Err]}}
	    end;
	{error,open,Reason} ->
	    %% Couldn't open output Makefile.
	    Err = {St#compile.ifile,[{none,?MODULE,{open,Reason}}]},
	    {error,St#compile{errors=St#compile.errors++[Err]}};
	{error,delete,Reason} ->
	    %% Couldn't open output Makefile.
	    Err = {St#compile.ifile,[{none,?MODULE,{delete,Output0,Reason}}]},
	    {error,St#compile{errors=St#compile.errors++[Err]}}
    end.

expand_records(Code0, #compile{options=Opts}=St) ->
    Code = erl_expand_records:module(Code0, Opts),
    {ok,Code,St}.

core(Forms, #compile{options=Opts0}=St) ->
    Opts1 = lists:flatten([C || {attribute,_,compile,C} <- Forms] ++ Opts0),
    Opts = expand_opts(Opts1),
    {ok,Core,Ws} = v3_core:module(Forms, Opts),
    Mod = cerl:concrete(cerl:module_name(Core)),
    {ok,Core,St#compile{module=Mod,options=Opts,
                        warnings=St#compile.warnings++Ws}}.

core_fold_module_after_inlining(Code0, #compile{options=Opts}=St) ->
    %% Inlining may produce code that generates spurious warnings.
    %% Ignore all warnings.
    {ok,Code,_Ws} = sys_core_fold:module(Code0, Opts),
    {ok,Code,St}.

v3_kernel(Code0, #compile{options=Opts,warnings=Ws0}=St) ->
    {ok,Code,Ws} = v3_kernel:module(Code0, Opts),
    case Ws =:= [] orelse test_core_inliner(St) of
	false ->
	    {ok,Code,St#compile{warnings=Ws0++Ws}};
	true ->
	    %% cerl_inline may produce code that generates spurious
	    %% warnings. Ignore any such warnings.
	    {ok,Code,St}
    end.

test_old_inliner(#compile{options=Opts}) ->
    %% The point of this test is to avoid loading the old inliner
    %% if we know that it will not be used.
    any(fun({inline,_}) -> true;
	   (_) -> false
	end, Opts).

test_core_inliner(#compile{options=Opts}) ->
    case any(fun(no_inline) -> true;
		(_) -> false
	     end, Opts) of
	true -> false;
	false ->
	    any(fun(inline) -> true;
		   (_) -> false
		end, Opts)
    end.

test_any_inliner(St) ->
    test_old_inliner(St) orelse test_core_inliner(St).

core_old_inliner(Code0, #compile{options=Opts}=St) ->
    {ok,Code} = sys_core_inline:module(Code0, Opts),
    {ok,Code,St}.

core_inline_module(Code0, #compile{options=Opts}=St) ->
    Code = cerl_inline:core_transform(Code0, Opts),
    {ok,Code,St}.

save_abstract_code(Code, St) ->
    {ok,Code,St#compile{abstract_code=erl_parse:anno_to_term(Code)}}.

debug_info(#compile{module=Module,mod_options=Opts0,ofile=OFile,abstract_code=Abst}) ->
    AbstOpts = cleanup_compile_options(Opts0),
    Opts1 = proplists:delete(debug_info, Opts0),
    {Backend,Metadata,Opts2} =
	case proplists:get_value(debug_info, Opts0, false) of
	    {OptBackend,OptMetadata} when is_atom(OptBackend) -> {OptBackend,OptMetadata,Opts1};
	    false -> {erl_abstract_code,{none,AbstOpts},Opts1};
	    true -> {erl_abstract_code,{Abst,AbstOpts},[debug_info | Opts1]}
	end,
    DebugInfo = erlang:term_to_binary({debug_info_v1,Backend,Metadata}, [compressed]),

    case member(encrypt_debug_info, Opts2) of
	true ->
	    case lists:keytake(debug_info_key, 1, Opts2) of
		{value,{_, Key},Opts3} ->
		    encrypt_debug_info(DebugInfo, Key, [{debug_info_key,'********'} | Opts3]);
		false ->
		    Mode = proplists:get_value(crypto_mode, Opts2, des3_cbc),
		    case beam_lib:get_crypto_key({debug_info, Mode, Module, OFile}) of
			error ->
			    {error, [{none,?MODULE,no_crypto_key}]};
			Key ->
			    encrypt_debug_info(DebugInfo, {Mode, Key}, Opts2)
		    end
	    end;
	false ->
	    {ok,DebugInfo,Opts2}
    end.

encrypt_debug_info(DebugInfo, Key, Opts) ->
    try
	RealKey = generate_key(Key),
	case start_crypto() of
	    ok -> {ok,encrypt(RealKey, DebugInfo),Opts};
	    {error,_}=E -> E
	end
    catch
	error:_ ->
	    {error,[{none,?MODULE,bad_crypto_key}]}
    end.

cleanup_compile_options(Opts) ->
    IsDeterministic = lists:member(deterministic, Opts),
    lists:filter(fun(Opt) ->
                         keep_compile_option(Opt, IsDeterministic)
                 end, Opts).

%% Include paths and current directory don't affect compilation, but they might
%% be helpful so we include them unless we're doing a deterministic build.
keep_compile_option({i, _}, Deterministic) ->
    not Deterministic;
keep_compile_option({cwd, _}, Deterministic) ->
    not Deterministic;
%% We are storing abstract, not asm or core.
keep_compile_option(from_asm, _Deterministic) ->
    false;
keep_compile_option(from_core, _Deterministic) ->
    false;
%% Parse transform and macros have already been applied.
keep_compile_option({parse_transform, _}, _Deterministic) ->
    false;
keep_compile_option({d, _, _}, _Deterministic) ->
    false;
%% Do not affect compilation result on future calls.
keep_compile_option(Option, _Deterministic) ->
    effects_code_generation(Option).

start_crypto() ->
    try crypto:start() of
	{error,{already_started,crypto}} -> ok;
	ok -> ok
    catch
	error:_ ->
	    {error,[{none,?MODULE,no_crypto}]}
    end.

generate_key({Type,String}) when is_atom(Type), is_list(String) ->
    beam_lib:make_crypto_key(Type, String);
generate_key(String) when is_list(String) ->
    generate_key({des3_cbc,String}).

encrypt({des3_cbc=Type,Key,IVec,BlockSize}, Bin0) ->
    Bin1 = case byte_size(Bin0) rem BlockSize of
	       0 -> Bin0;
	       N -> list_to_binary([Bin0,crypto:strong_rand_bytes(BlockSize-N)])
	   end,
    Bin = crypto:block_encrypt(Type, Key, IVec, Bin1),
    TypeString = atom_to_list(Type),
    list_to_binary([0,length(TypeString),TypeString,Bin]).

save_core_code(Code, St) ->
    {ok,Code,St#compile{core_code=cerl:from_records(Code)}}.

beam_asm(Code0, #compile{ifile=File,extra_chunks=ExtraChunks,options=CompilerOpts}=St) ->
    case debug_info(St) of
	{ok,DebugInfo,Opts0} ->
	    Opts1 = [O || O <- Opts0, effects_code_generation(O)],
	    Chunks = [{<<"Dbgi">>, DebugInfo} | ExtraChunks],
	    CompileInfo = compile_info(File, CompilerOpts, Opts1),
	    {ok,Code} = beam_asm:module(Code0, Chunks, CompileInfo, CompilerOpts),
	    {ok,Code,St#compile{abstract_code=[]}};
	{error,Es} ->
	    {error,St#compile{errors=St#compile.errors ++ [{File,Es}]}}
    end.

compile_info(File, CompilerOpts, Opts) ->
    IsSlim = member(slim, CompilerOpts),
    IsDeterministic = member(deterministic, CompilerOpts),
    Info0 = proplists:get_value(compile_info, Opts, []),
    Info1 =
	case paranoid_absname(File) of
	    [_|_] = Source when not IsSlim, not IsDeterministic ->
		[{source,Source} | Info0];
	    _ ->
		Info0
	end,
    Info2 =
	case IsDeterministic of
	    false -> [{options,proplists:delete(compile_info, Opts)} | Info1];
	    true -> Info1
	end,
    Info2.

paranoid_absname(""=File) ->
    File;
paranoid_absname(File) ->
    case file:get_cwd() of
	{ok,Cwd} ->
	    filename:absname(File, Cwd);
	_ ->
	    File
    end.

test_native(#compile{options=Opts}) ->
    %% This test is done late, in case some other option has turned off native.
    %% 'native' given on the command line can be overridden by
    %% 'no_native' in the module itself.
    is_native_enabled(Opts).

is_native_enabled([native|_]) -> true;
is_native_enabled([no_native|_]) -> false;
is_native_enabled([_|Opts]) -> is_native_enabled(Opts);
is_native_enabled([]) -> false.

native_compile(none, St) -> {ok,none,St};
native_compile(Code, St) ->
    case erlang:system_info(hipe_architecture) of
	undefined ->
	    Ws = [{St#compile.ifile,[{none,compile,no_native_support}]}],
	    {ok,Code,St#compile{warnings=St#compile.warnings ++ Ws}};
	_ ->
	    native_compile_1(Code, St)
    end.

native_compile_1(Code, St) ->
    Opts0 = St#compile.options,
    IgnoreErrors = member(ignore_native_errors, Opts0),
    Opts = case keyfind(hipe, 1, Opts0) of
	       {hipe,L} when is_list(L) -> L;
	       {hipe,X} -> [X];
	       _ -> []
	   end,
    try hipe:compile(St#compile.module,
		     St#compile.core_code,
		     Code,
		     Opts) of
	{ok,{_Type,Bin}=T} when is_binary(Bin) ->
	    {ok,embed_native_code(Code, T),St};
	{error,R} ->
	    case IgnoreErrors of
		true ->
		    Ws = [{St#compile.ifile,[{none,?MODULE,{native,R}}]}],
		    {ok,Code,St#compile{warnings=St#compile.warnings ++ Ws}};
		false ->
		    Es = [{St#compile.ifile,[{none,?MODULE,{native,R}}]}],
		    {error,St#compile{errors=St#compile.errors ++ Es}}
	    end
    catch
        exit:{unimplemented_instruction,_}=Unimplemented ->
            Ws = [{St#compile.ifile,
                   [{none,?MODULE,Unimplemented}]}],
            {ok,Code,St#compile{warnings=St#compile.warnings ++ Ws}};
	Class:R:Stack ->
	    case IgnoreErrors of
		true ->
		    Ws = [{St#compile.ifile,
			   [{none,?MODULE,{native_crash,R,Stack}}]}],
		    {ok,Code,St#compile{warnings=St#compile.warnings ++ Ws}};
		false ->
		    erlang:raise(Class, R, Stack)
	    end
    end.

embed_native_code(Code, {Architecture,NativeCode}) ->
    {ok, _, Chunks0} = beam_lib:all_chunks(Code),
    ChunkName = hipe_unified_loader:chunk_name(Architecture),
    Chunks1 = lists:keydelete(ChunkName, 1, Chunks0),
    Chunks = Chunks1 ++ [{ChunkName,NativeCode}],
    {ok,BeamPlusNative} = beam_lib:build_module(Chunks),
    BeamPlusNative.

%% effects_code_generation(Option) -> true|false.
%%  Determine whether the option could have any effect on the
%%  generated code in the BEAM file (as opposed to how
%%  errors will be reported).

effects_code_generation(Option) ->
    case Option of
	beam -> false;
	report_warnings -> false;
	report_errors -> false;
	return_errors-> false;
	return_warnings-> false;
	warnings_as_errors -> false;
	binary -> false;
	verbose -> false;
	{cwd,_} -> false;
	{outdir, _} -> false;
	_ -> true
    end.

save_binary(none, St) -> {ok,none,St};
save_binary(Code, #compile{module=Mod,ofile=Outfile,options=Opts}=St) ->
    %% Test that the module name and output file name match.
    case member(no_error_module_mismatch, Opts) of
	true ->
	    save_binary_1(Code, St);
	false ->
	    Base = filename:rootname(filename:basename(Outfile)),
	    case atom_to_list(Mod) of
		Base ->
		    save_binary_1(Code, St);
		_ ->
		    Es = [{St#compile.ofile,
			   [{none,?MODULE,{module_name,Mod,Base}}]}],
		    {error,St#compile{errors=St#compile.errors ++ Es}}
	    end
    end.

save_binary_1(Code, St) ->
    Ofile = St#compile.ofile,
    Tfile = tmpfile(Ofile),		%Temp working file
    case write_binary(Tfile, Code, St) of
	ok ->
	    case file:rename(Tfile, Ofile) of
		ok ->
		    {ok,none,St};
		{error,RenameError} ->
		    Es0 = [{Ofile,[{none,?MODULE,{rename,Tfile,Ofile,
						  RenameError}}]}],
		    Es = case file:delete(Tfile) of
			     ok -> Es0;
			     {error,DeleteError} ->
				 Es0 ++
				     [{Ofile,
				       [{none,?MODULE,{delete_temp,Tfile,
						       DeleteError}}]}]
			 end,
		    {error,St#compile{errors=St#compile.errors ++ Es}}
	    end;
	{error,Error} ->
	    Es = [{Tfile,[{none,compile,{write_error,Error}}]}],
	    {error,St#compile{errors=St#compile.errors ++ Es}}
    end.

write_binary(Name, Bin, St) ->
    Opts = case member(compressed, St#compile.options) of
	       true -> [compressed];
	       false -> []
	   end,
    case file:write_file(Name, Bin, Opts) of
	ok -> ok;
	{error,_}=Error -> Error
    end.

%% report_errors(State) -> ok
%% report_warnings(State) -> ok

report_errors(#compile{options=Opts,errors=Errors}) ->
    case member(report_errors, Opts) of
	true ->
	    foreach(fun ({{F,_L},Eds}) -> list_errors(F, Eds);
			({F,Eds}) -> list_errors(F, Eds) end,
		    Errors);
	false -> ok
    end.

report_warnings(#compile{options=Opts,warnings=Ws0}) ->
    Werror = member(warnings_as_errors, Opts),
    P = case Werror of
	    true -> "";
	    false -> "Warning: "
	end,
    ReportWerror = Werror andalso member(report_errors, Opts),
    case member(report_warnings, Opts) orelse ReportWerror of
	true ->
	    Ws1 = flatmap(fun({{F,_L},Eds}) -> format_message(F, P, Eds);
			     ({F,Eds}) -> format_message(F, P, Eds) end,
			  Ws0),
	    Ws = lists:sort(Ws1),
	    foreach(fun({_,Str}) -> io:put_chars(Str) end, Ws);
	false -> ok
    end.

format_message(F, P, [{none,Mod,E}|Es]) ->
    M = {none,io_lib:format("~ts: ~s~ts\n", [F,P,Mod:format_error(E)])},
    [M|format_message(F, P, Es)];
format_message(F, P, [{{Line,Column}=Loc,Mod,E}|Es]) ->
    M = {{F,Loc},io_lib:format("~ts:~w:~w ~s~ts\n",
                                [F,Line,Column,P,Mod:format_error(E)])},
    [M|format_message(F, P, Es)];
format_message(F, P, [{Line,Mod,E}|Es]) ->
    M = {{F,{Line,0}},io_lib:format("~ts:~w: ~s~ts\n",
                                [F,Line,P,Mod:format_error(E)])},
    [M|format_message(F, P, Es)];
format_message(F, P, [{Mod,E}|Es]) ->
    %% Not documented and not expected to be used any more, but
    %% keep a while just in case.
    M = {none,io_lib:format("~ts: ~s~ts\n", [F,P,Mod:format_error(E)])},
    [M|format_message(F, P, Es)];
format_message(_, _, []) -> [].

%% list_errors(File, ErrorDescriptors) -> ok

list_errors(F, [{none,Mod,E}|Es]) ->
    io:fwrite("~ts: ~ts\n", [F,Mod:format_error(E)]),
    list_errors(F, Es);
list_errors(F, [{{Line,Column},Mod,E}|Es]) ->
    io:fwrite("~ts:~w:~w: ~ts\n", [F,Line,Column,Mod:format_error(E)]),
    list_errors(F, Es);
list_errors(F, [{Line,Mod,E}|Es]) ->
    io:fwrite("~ts:~w: ~ts\n", [F,Line,Mod:format_error(E)]),
    list_errors(F, Es);
list_errors(F, [{Mod,E}|Es]) ->
    %% Not documented and not expected to be used any more, but
    %% keep a while just in case.
    io:fwrite("~ts: ~ts\n", [F,Mod:format_error(E)]),
    list_errors(F, Es);
list_errors(_F, []) -> ok.

%% erlfile(Dir, Base) -> ErlFile
%% outfile(Base, Extension, Options) -> OutputFile
%% objfile(Base, Target, Options) -> ObjFile
%% tmpfile(ObjFile) -> TmpFile
%%  Work out the correct input and output file names.

-spec iofile(atom() | file:filename_all()) ->
                    {file:name_all(),file:name_all()}.

iofile(File) when is_atom(File) ->
    iofile(atom_to_list(File));
iofile(File) ->
    {filename:dirname(File), filename:basename(File, ".erl")}.

erlfile(".", Base, Suffix) ->
    Base ++ Suffix;
erlfile(Dir, Base, Suffix) ->
    filename:join(Dir, Base ++ Suffix).

outfile(Base, Ext, Opts) when is_atom(Ext) ->
    outfile(Base, atom_to_list(Ext), Opts);
outfile(Base, Ext, Opts) ->
    Obase = case keyfind(outdir, 1, Opts) of
		{outdir, Odir} -> filename:join(Odir, Base);
		_Other -> Base			% Not found or bad format
	    end,
    Obase ++ "." ++ Ext.

objfile(Base, St) ->
    outfile(Base, "beam", St#compile.options).

tmpfile(Ofile) ->
    reverse([$#|tl(reverse(Ofile))]).

%% pre_defs(Options)
%% inc_paths(Options)
%%  Extract the predefined macros and include paths from the option list.

pre_defs([{d,M,V}|Opts]) ->
    [{M,V}|pre_defs(Opts)];
pre_defs([{d,M}|Opts]) ->
    [M|pre_defs(Opts)];
pre_defs([_|Opts]) ->
    pre_defs(Opts);
pre_defs([]) -> [].

inc_paths(Opts) ->
    [ P || {i,P} <- Opts, is_list(P) ].

src_listing(Ext, Code, St) ->
    listing(fun (Lf, {_Mod,_Exp,Fs}) -> do_src_listing(Lf, Fs);
		(Lf, Fs) -> do_src_listing(Lf, Fs) end,
	    Ext, Code, St).

do_src_listing(Lf, Fs) ->
    Opts = [lists:keyfind(encoding, 1, io:getopts(Lf))],
    foreach(fun (F) -> io:put_chars(Lf, [erl_pp:form(F, Opts),"\n"]) end,
	    Fs).

listing(Ext, Code, St0) ->
    St = St0#compile{encoding = none},
    listing(fun(Lf, Fs) -> beam_listing:module(Lf, Fs) end, Ext, Code, St).

listing(LFun, Ext, Code, St) ->
    Lfile = outfile(St#compile.base, Ext, St#compile.options),
    case file:open(Lfile, [write,delayed_write]) of
	{ok,Lf} ->
            Code = restore_expanded_types(Ext, Code),
            output_encoding(Lf, St),
	    LFun(Lf, Code),
	    ok = file:close(Lf),
	    {ok,Code,St};
	{error,Error} ->
	    Es = [{Lfile,[{none,compile,{write_error,Error}}]}],
	    {error,St#compile{errors=St#compile.errors ++ Es}}
    end.

to_dis(Code, #compile{module=Module,ofile=Outfile}=St) ->
    Loaded = code:is_loaded(Module),
    Sticky = code:is_sticky(Module),
    _ = [code:unstick_mod(Module) || Sticky],

    {module,Module} = code:load_binary(Module, "", Code),
    DestDir = filename:dirname(Outfile),
    DisFile = filename:join(DestDir, atom_to_list(Module) ++ ".dis"),
    ok = erts_debug:dis_to_file(Module, DisFile),

    %% Restore loaded module
    _ = [{module, Module} = code:load_file(Module) || Loaded =/= false],
    [code:stick_mod(Module) || Sticky],
    {ok,Code,St}.

output_encoding(F, #compile{encoding = none}) ->
    ok = io:setopts(F, [{encoding, epp:default_encoding()}]);
output_encoding(F, #compile{encoding = Encoding}) ->
    ok = io:setopts(F, [{encoding, Encoding}]),
    ok = io:fwrite(F, <<"%% ~s\n">>, [epp:encoding_to_string(Encoding)]).

restore_expanded_types("E", {M,I,Fs0}) ->
    Fs = restore_expand_module(Fs0),
    {M,I,Fs};
restore_expanded_types(_Ext, Code) -> Code.

restore_expand_module([{attribute,Line,type,[Type]}|Fs]) ->
    [{attribute,Line,type,Type}|restore_expand_module(Fs)];
restore_expand_module([{attribute,Line,opaque,[Type]}|Fs]) ->
    [{attribute,Line,opaque,Type}|restore_expand_module(Fs)];
restore_expand_module([{attribute,Line,spec,[Arg]}|Fs]) ->
    [{attribute,Line,spec,Arg}|restore_expand_module(Fs)];
restore_expand_module([{attribute,Line,callback,[Arg]}|Fs]) ->
    [{attribute,Line,callback,Arg}|restore_expand_module(Fs)];
restore_expand_module([{attribute,Line,record,[R]}|Fs]) ->
    [{attribute,Line,record,R}|restore_expand_module(Fs)];
restore_expand_module([F|Fs]) ->
    [F|restore_expand_module(Fs)];
restore_expand_module([]) -> [].

%%%
%%% Transform the BEAM code to make it more friendly for
%%% diffing: using function names instead of labels for
%%% local calls and number labels relative to each function.
%%%

diffable(Code0, St) ->
    {Mod,Exp,Attr,Fs0,NumLabels} = Code0,
    EntryLabels0 = [{Entry,{Name,Arity}} ||
                       {function,Name,Arity,Entry,_} <- Fs0],
    EntryLabels = maps:from_list(EntryLabels0),
    Fs = [diffable_fix_function(F, EntryLabels) || F <- Fs0],
    Code = {Mod,Exp,Attr,Fs,NumLabels},
    {ok,Code,St}.

diffable_fix_function({function,Name,Arity,Entry0,Is0}, LabelMap0) ->
    Entry = maps:get(Entry0, LabelMap0),
    {Is1,LabelMap} = diffable_label_map(Is0, 1, LabelMap0, []),
    Fb = fun(Old) -> error({no_fb,Old}) end,
    Is = beam_utils:replace_labels(Is1, [], LabelMap, Fb),
    {function,Name,Arity,Entry,Is}.

diffable_label_map([{label,Old}|Is], New, Map, Acc) ->
    case Map of
        #{Old:=NewLabel} ->
            diffable_label_map(Is, New, Map, [{label,NewLabel}|Acc]);
        #{} ->
            diffable_label_map(Is, New+1, Map#{Old=>New}, [{label,New}|Acc])
    end;
diffable_label_map([I|Is], New, Map, Acc) ->
    diffable_label_map(Is, New, Map, [I|Acc]);
diffable_label_map([], _New, Map, Acc) ->
    {Acc,Map}.

-spec options() -> 'ok'.

options() ->
    help(standard_passes()).

help([{delay,Ps}|T]) ->
    help(Ps),
    help(T);
help([{iff,Flag,{src_listing,Ext}}|T]) ->
    io:fwrite("~p - Generate .~s source listing file\n", [Flag,Ext]),
    help(T);
help([{iff,Flag,{listing,Ext}}|T]) ->
    io:fwrite("~p - Generate .~s file\n", [Flag,Ext]),
    help(T);
help([{iff,Flag,{Name,Fun}}|T]) when is_function(Fun) ->
    io:fwrite("~p - Run ~s\n", [Flag,Name]),
    help(T);
help([{iff,_Flag,Action}|T]) ->
    help(Action),
    help(T);
help([{unless,Flag,{pass,Pass}}|T]) ->
    io:fwrite("~p - Skip the ~s pass\n", [Flag,Pass]),
    help(T);
help([{unless,no_postopt=Flag,List}|T]) when is_list(List) ->
    %% Hard-coded knowledge here.
    io:fwrite("~p - Skip all post optimisation\n", [Flag]),
    help(List),
    help(T);
help([{unless,_Flag,Action}|T]) ->
    help(Action),
    help(T);
help([_|T]) ->
    help(T);
help(_) ->
    ok.


%% compile(AbsFileName, Outfilename, Options)
%%   Compile entry point for erl_compile.

-spec compile(file:filename(), _, #options{}) -> 'ok' | 'error'.

compile(File0, _OutFile, Options) ->
    pre_load(),
    File = shorten_filename(File0),
    case file(File, make_erl_options(Options)) of
	{ok,_Mod} -> ok;
	Other -> Other
    end.

-spec compile_beam(file:filename(), _, #options{}) -> 'ok' | 'error'.

compile_beam(File0, _OutFile, Opts) ->
    File = shorten_filename(File0),
    case file(File, [from_beam|make_erl_options(Opts)]) of
	{ok,_Mod} -> ok;
	Other -> Other
    end.

-spec compile_asm(file:filename(), _, #options{}) -> 'ok' | 'error'.

compile_asm(File0, _OutFile, Opts) ->
    File = shorten_filename(File0),
    case file(File, [from_asm|make_erl_options(Opts)]) of
	{ok,_Mod} -> ok;
	Other -> Other
    end.

-spec compile_core(file:filename(), _, #options{}) -> 'ok' | 'error'.

compile_core(File0, _OutFile, Opts) ->
    File = shorten_filename(File0),
    case file(File, [from_core|make_erl_options(Opts)]) of
	{ok,_Mod} -> ok;
	Other -> Other
    end.

shorten_filename(Name0) ->
    {ok,Cwd} = file:get_cwd(),
    case lists:prefix(Cwd, Name0) of
	false -> Name0;
	true ->
	    case lists:nthtail(length(Cwd), Name0) of
		"/"++N -> N;
		N -> N
	    end
    end.

%% Converts generic compiler options to specific options.

make_erl_options(Opts) ->
    #options{includes=Includes,
	     defines=Defines,
	     outdir=Outdir,
	     warning=Warning,
	     verbose=Verbose,
	     specific=Specific,
	     output_type=OutputType,
	     cwd=Cwd} = Opts,
    Options = [verbose || Verbose] ++
	[report_warnings || Warning =/= 0] ++
	map(fun ({Name,Value}) ->
		    {d,Name,Value};
		(Name) ->
		    {d,Name}
	    end, Defines) ++
	case OutputType of
	    undefined -> [];
	    jam -> [jam];
	    beam -> [beam];
	    native -> [native]
	end,
    Options ++ [report_errors, {cwd, Cwd}, {outdir, Outdir}|
	        [{i, Dir} || Dir <- Includes]] ++ Specific.

pre_load() ->
    L = [beam_a,
	 beam_asm,
	 beam_block,
	 beam_clean,
	 beam_dict,
	 beam_except,
	 beam_flatten,
	 beam_jump,
	 beam_kernel_to_ssa,
	 beam_opcodes,
	 beam_peep,
	 beam_ssa,
	 beam_ssa_bsm,
	 beam_ssa_codegen,
	 beam_ssa_dead,
         beam_ssa_funs,
	 beam_ssa_opt,
	 beam_ssa_pre_codegen,
	 beam_ssa_recv,
	 beam_ssa_share,
	 beam_ssa_type,
	 beam_trim,
	 beam_utils,
	 beam_validator,
	 beam_z,
	 cerl,
	 cerl_clauses,
	 cerl_sets,
	 cerl_trees,
	 core_lib,
	 epp,
	 erl_bifs,
	 erl_expand_records,
	 erl_lint,
	 erl_parse,
	 erl_scan,
	 sys_core_alias,
	 sys_core_bsm,
	 sys_core_fold,
	 v3_core,
	 v3_kernel],
    _ = code:ensure_modules_loaded(L),
    ok.
