%% SPDX-License-Identifier: Apache-2.0 %% SPDX-FileCopyrightText: 2021 The Elixir Team %% SPDX-FileCopyrightText: 2012 Plataformatec %% Elixir compiler front-end to the Erlang backend. -module(elixir_compiler). -export([string/3, quoted/3, bootstrap/0, file/2, compile/4, interpret/3]). -include("elixir.hrl"). string(Contents, File, Callback) -> Forms = elixir:'string_to_quoted!'(Contents, 1, 1, File, elixir_config:get(parser_options)), quoted(Forms, File, Callback). quoted(Forms, File, Callback) -> Previous = get(elixir_module_binaries), try put(elixir_module_binaries, []), Env = (elixir_env:new())#{line := 1, file := File, tracers := elixir_config:get(tracers)}, elixir_lexical:run( Env, fun (LexicalEnv) -> optimize_defmodule(Forms, LexicalEnv) end, fun (#{lexical_tracker := Pid}) -> Callback(File, Pid) end ), lists:reverse(get(elixir_module_binaries)) after put(elixir_module_binaries, Previous) end. file(File, Callback) -> {ok, Bin} = file:read_file(File), string(elixir_utils:characters_to_list(Bin), File, Callback). %% In case the forms only holds defmodules, we optimize %% it by expanding them directly. optimize_defmodule(Forms, E) -> case (?key(E, module) == nil) andalso only_defmodule(Forms) andalso (not elixir_config:is_bootstrap()) of true -> expand_defmodule(Forms, E); false -> compile(Forms, [], [], E) end, ok. %% A version of compilation that uses eval (interpreted) interpret(Quoted, ArgsList, #{line := Line} = E) -> {Expanded, SE, EE} = elixir_expand:expand(Quoted, elixir_env:env_to_ex(E), E), elixir_env:check_unused_vars(SE, EE), {Vars, TS} = elixir_erl_var:from_env(E), {ErlExprs, _} = elixir_erl_pass:translate(Expanded, erl_anno:new(Line), TS), ListBinding = lists:zipwith(fun({_, Var}, Arg) -> {Var, Arg} end, Vars, ArgsList), Binding = maps:from_list(ListBinding), {value, Result, _} = try elixir:erl_eval(ErlExprs, Binding, E) catch Kind:Reason:Stacktrace -> erlang:raise(Kind, Reason, Stacktrace ++ 'Elixir.Macro.Env':stacktrace(E)) end, {Result, SE, EE}. compile(Quoted, ArgsList, CompilerOpts, #{line := Line} = E) -> Block = no_tail_optimize([{line, Line}], Quoted), {Expanded, SE, EE} = elixir_expand:expand(Block, elixir_env:env_to_ex(E), E), elixir_env:check_unused_vars(SE, EE), {Module, Fun, LabelledLocals} = elixir_erl_compiler:spawn(fun() -> spawned_compile(Expanded, CompilerOpts, E) end), Args = list_to_tuple(ArgsList), {dispatch(Module, Fun, Args, LabelledLocals), SE, EE}. spawned_compile(ExExprs, CompilerOpts, #{line := Line, file := File} = E) -> {Vars, S} = elixir_erl_var:from_env(E), {ErlExprs, _} = elixir_erl_pass:translate(ExExprs, erl_anno:new(Line), S), Module = retrieve_compiler_module(), Fun = code_fun(?key(E, module)), Forms = code_mod(Fun, ErlExprs, Line, File, Module, Vars), {Module, Binary} = elixir_erl_compiler:noenv_forms(Forms, File, [nowarn_nomatch | CompilerOpts]), code:load_binary(Module, "", Binary), {Module, Fun, is_purgeable(Binary)}. is_purgeable(<<"FOR1", _Size:32, "BEAM", Rest/binary>>) -> do_is_purgeable(Rest). do_is_purgeable(<<>>) -> true; do_is_purgeable(<<"LocT", 4:32, 0:32, _/binary>>) -> true; do_is_purgeable(<<"LocT", _:32, _/binary>>) -> false; do_is_purgeable(<<_:4/binary, Size:32, Beam/binary>>) -> <<_:(4 * trunc((Size+3) / 4))/binary, Rest/binary>> = Beam, do_is_purgeable(Rest). dispatch(Module, Fun, Args, Purgeable) -> try Module:Fun(Args) after return_compiler_module(Module, Purgeable) end. code_fun(nil) -> '__FILE__'; code_fun(_) -> '__MODULE__'. code_mod(Fun, Expr, Line, File, Module, Vars) when is_binary(File), is_integer(Line) -> Ann = erl_anno:new(Line), Tuple = {tuple, Ann, [{var, Ann, Var} || {_, Var} <- Vars]}, Relative = elixir_utils:relative_to_cwd(File), [{attribute, Ann, file, {elixir_utils:characters_to_list(Relative), 1}}, {attribute, Ann, module, Module}, {attribute, Ann, compile, no_auto_import}, {attribute, Ann, export, [{Fun, 1}, {'__RELATIVE__', 0}]}, {function, Ann, Fun, 1, [ {clause, Ann, [Tuple], [], [Expr]} ]}, {function, Ann, '__RELATIVE__', 0, [ {clause, Ann, [], [], [elixir_erl:elixir_to_erl(Relative)]} ]}]. retrieve_compiler_module() -> elixir_code_server:call(retrieve_compiler_module). return_compiler_module(Module, Purgeable) -> elixir_code_server:cast({return_compiler_module, Module, Purgeable}). only_defmodule({'__block__', _, Exprs}) -> lists:all(fun only_defmodule/1, Exprs); only_defmodule({defmodule, _, [_, [{do, _}]]}) -> true; only_defmodule(_) -> false. expand_defmodule({'__block__', _, Exprs}, E) -> lists:foldl(fun(Expr, _) -> expand_defmodule(Expr, E) end, nil, Exprs); expand_defmodule({defmodule, Meta, [Mod, [{do, Block}]]}, NoLineE) -> E = NoLineE#{line := ?line(Meta)}, Expanded = case Mod of {'__aliases__', AliasMeta, List} -> case elixir_aliases:expand_or_concat(AliasMeta, List, E, true) of Receiver when is_atom(Receiver) -> Receiver; _ -> 'Elixir.Macro':expand(Mod, E) end; _ -> 'Elixir.Macro':expand(Mod, E) end, ContextModules = [Expanded | ?key(E, context_modules)], elixir_module:compile(Meta, Expanded, Block, [], false, E#{context_modules := ContextModules}). no_tail_optimize(Meta, Block) -> {'__block__', Meta, [ {'=', Meta, [{result, Meta, ?MODULE}, Block]}, {{'.', Meta, [elixir_utils, noop]}, Meta, []}, {result, Meta, ?MODULE} ]}. %% Bootstrapper bootstrap() -> {ok, _} = application:ensure_all_started(elixir), elixir_config:static(#{bootstrap => true}), elixir_config:put(docs, false), elixir_config:put(ignore_module_conflict, true), elixir_config:put(on_undefined_variable, raise), elixir_config:put(parser_options, []), elixir_config:put(relative_paths, false), elixir_config:put(tracers, []), elixir_config:put(infer_signatures, []), {Init, Main} = bootstrap_files(), {ok, Cwd} = file:get_cwd(), Lib = filename:join(Cwd, "lib/elixir/lib"), [bootstrap_file(Lib, File) || File <- [<<"kernel.ex">> | Init]], [bootstrap_file(Lib, File) || File <- [<<"kernel.ex">> | Main]]. bootstrap_file(Lib, Suffix) -> try File = filename:join(Lib, Suffix), Mods = file(File, fun(_, _) -> ok end), _ = [binary_to_path(X, "lib/elixir/ebin") || X <- Mods], io:format("Compiled ~ts~n", [Suffix]) catch Kind:Reason:Stacktrace -> io:format("~p: ~p~nstacktrace: ~p~n", [Kind, Reason, Stacktrace]), erlang:halt(1) end. bootstrap_files() -> { [ <<"kernel/utils.ex">>, <<"macro/env.ex">>, <<"range.ex">>, <<"keyword.ex">>, <<"module.ex">>, <<"list.ex">>, <<"macro.ex">>, <<"kernel/typespec.ex">>, <<"code.ex">>, <<"code/identifier.ex">>, <<"protocol.ex">>, <<"stream/reducers.ex">>, <<"enum.ex">>, <<"regex.ex">>, <<"inspect/algebra.ex">>, <<"inspect.ex">>, <<"string.ex">>, <<"string/chars.ex">> ], [ <<"list/chars.ex">>, <<"bitwise.ex">>, <<"map.ex">>, <<"module/parallel_checker.ex">>, <<"module/behaviour.ex">>, <<"module/types/helpers.ex">>, <<"module/types/descr.ex">>, <<"module/types/of.ex">>, <<"module/types/pattern.ex">>, <<"module/types/apply.ex">>, <<"module/types/expr.ex">>, <<"module/types/traverse.ex">>, <<"module/types.ex">>, <<"exception.ex">>, <<"path.ex">>, <<"file.ex">>, <<"access.ex">>, <<"io.ex">>, <<"system.ex">>, <<"code/formatter.ex">>, <<"code/normalizer.ex">>, <<"kernel/cli.ex">>, <<"kernel/error_handler.ex">>, <<"kernel/parallel_compiler.ex">>, <<"kernel/lexical_tracker.ex">> ] }. binary_to_path({ModuleName, Binary}, CompilePath) -> Path = filename:join(CompilePath, atom_to_list(ModuleName) ++ ".beam"), case file:write_file(Path, Binary) of ok -> Path; {error, Reason} -> error('Elixir.File.Error':exception([{action, "write to"}, {path, Path}, {reason, Reason}])) end.