aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/diffable
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/diffable')
-rwxr-xr-xscripts/diffable317
1 files changed, 149 insertions, 168 deletions
diff --git a/scripts/diffable b/scripts/diffable
index 08d2d5cb35..d2208d9d89 100755
--- a/scripts/diffable
+++ b/scripts/diffable
@@ -3,56 +3,85 @@
-mode(compile).
main(Args0) ->
- {Args,Opts} = opts(Args0, #{format=>asm,no_compile=>false}),
+ DefOpts = #{format=>asm,no_compile=>false,legacy=>false},
+ {Args,Opts} = opts(Args0, DefOpts),
case Args of
[OutDir] ->
do_compile(OutDir, Opts);
_ ->
- usage(),
- halt(1)
+ usage()
end.
usage() ->
- S = "usage: otp-diffable-asm [OPTION] DIRECTORY\n\n"
- "Options:\n"
- " --asm Output to .S files (default)\n"
- " --dis Output to .dis files\n"
- " --no-compile Disassemble from BEAM files (use with --dis)\n"
- "\n"
- "DESCRIPTION\n"
- "\n"
- "Compile some applications from OTP (more than 700 modules) to either\n"
- ".S files or .dis files. The files are massaged to make them diff-friendly.\n"
- "\n"
- "EXAMPLES\n"
- "\n"
- "This example shows how the effectiveness of a compiler \n"
- "optimization can be verified (alternatively, that pure code\n"
- "refactoring has no effect on the generated code):\n"
- "\n"
- "$ scripts/diffable old\n"
- "# Hack the compiler.\n"
- "$ scripts/diffable new\n"
- "$ diff -u old new\n"
- "\n"
- "This example shows how the effectiveness of loader hacks\n"
- "can be verified:\n"
- "\n"
- "$ scripts/diffable --dis --no-compile old\n"
- "# Hack ops.tab and/or one of the *instr.tab files.\n"
- "$ scripts/diffable --dis --no-compile new\n"
- "$ diff -u old new\n",
- io:put_chars(S).
-
-opts(["--asm"|Args], Opts) ->
- opts(Args, Opts#{format:=asm});
-opts(["--dis"|Args], Opts) ->
- opts(Args, Opts#{format:=dis});
-opts(["--no-compile"|Args], Opts) ->
- opts(Args, Opts#{format:=dis,no_compile:=true});
+ S = ["usage: otp-diffable-asm [OPTION] DIRECTORY\n\n"
+ "Options:\n"
+ " --asm Output to .S files (default)\n"
+ " --legacy-asm Output to legacy .S files\n"
+ " --dis Output to .dis files\n"
+ " --no-compile Disassemble from BEAM files (use with --dis)\n"
+ "\n"
+ "DESCRIPTION\n"
+ "\n"
+ "Compile some applications from OTP (about 1000 modules) to either\n"
+ ".S files or .dis files. The files are massaged to make them diff-friendly.\n"
+ "\n"
+ "The --legacy-asm options forces the output file to be in Latin1 encoding\n"
+ "and adds a latin1 encoding comment to the first line of the file.\n"
+ "\n"
+ "INCLUDING THE ELIXIR STANDARD LIBRARY\n"
+ "\n"
+ "If the Elixir repository is installed alongside the Erlang/OTP repository,\n"
+ "the Elixir standard library will be included in the compilation. For this\n"
+ "to work, the Elixir repository must be installed in: \n",
+ "\n"
+ " ",elixir_root(),"\n"
+ "\n"
+ "Here is how to install Elixir:\n"
+ "\n"
+ " cd ",filename:dirname(elixir_root()),"\n"
+ " git clone [email protected]:elixir-lang/elixir.git\n"
+ " cd elixir\n"
+ " PATH=",code:root_dir(),"/bin:$PATH make clean test\n"
+ "\n"
+ "EXAMPLES\n"
+ "\n"
+ "This example shows how the effectiveness of a compiler \n"
+ "optimization can be verified (alternatively, that pure code\n"
+ "refactoring has no effect on the generated code):\n"
+ "\n"
+ "$ scripts/diffable old\n"
+ "# Hack the compiler.\n"
+ "$ scripts/diffable new\n"
+ "$ diff -u old new\n"
+ "\n"
+ "This example shows how the effectiveness of loader hacks\n"
+ "can be verified:\n"
+ "\n"
+ "$ scripts/diffable --dis --no-compile old\n"
+ "# Hack ops.tab and/or one of the *instr.tab files.\n"
+ "$ scripts/diffable --dis --no-compile new\n"
+ "$ diff -u old new\n"],
+ io:put_chars(S),
+ halt(1).
+
+opts(["--"++Opt|Args], Opts0) ->
+ Opts = opt(Opt, Opts0),
+ opts(Args, Opts);
opts(Args, Opts) ->
{Args,Opts}.
+opt("asm", Opts) ->
+ Opts#{format:=asm};
+opt("dis", Opts) ->
+ Opts#{format:=dis};
+opt("legacy-asm", Opts) ->
+ Opts#{format:=asm,legacy:=true};
+opt("no-compile", Opts) ->
+ Opts#{format:=dis,no_compile:=true};
+opt(Opt, _Opts) ->
+ io:format("Uknown option: --~ts\n\n", [Opt]),
+ usage().
+
do_compile(OutDir, Opts0) ->
Opts1 = Opts0#{outdir=>OutDir},
_ = filelib:ensure_dir(filename:join(OutDir, "dummy")),
@@ -93,17 +122,26 @@ compile_file(CF, File) ->
CF(File)
catch
Class:Error:Stk ->
- io:format("~s: ~p ~p\n~p\n",
- [File,Class,Error,Stk]),
+ if
+ is_list(File) ->
+ io:format("~s: ~p ~p\n~p\n\n",
+ [File,Class,Error,Stk]);
+ true ->
+ io:format("~p: ~p ~p\n~p\n\n",
+ [File,Class,Error,Stk])
+ end,
error
end.
+elixir_root() ->
+ filename:join(filename:dirname(code:root_dir()), "elixir").
+
%%%
%%% Get names of files (either .erl files or BEAM files).
%%%
get_files(Apps, #{format:=dis,no_compile:=true}=Opts) ->
- Files = get_beams(Apps),
+ Files = get_elixir_beams() ++ get_beams(Apps),
{Files,Opts};
get_files(Apps, #{}=Opts) ->
Inc = make_includes(),
@@ -113,20 +151,40 @@ get_files(Apps, #{}=Opts) ->
{d,'COMPILER_VSN',1},
{d,erlang_daemon_port,1337}|Inc],
Files0 = get_src(Apps),
- Files = add_opts(Files0, CompilerOpts),
+ Files1 = add_opts(Files0, CompilerOpts),
+ Files = [{Beam,elixir} || Beam <- get_elixir_beams()] ++ Files1,
{Files,Opts}.
+get_elixir_beams() ->
+ ElixirEbin = filename:join(elixir_root(), "lib/elixir/ebin"),
+ case filelib:is_dir(ElixirEbin) of
+ true ->
+ true = code:add_patha(ElixirEbin),
+ filelib:wildcard(filename:join(ElixirEbin, "*.beam"));
+ false ->
+ []
+ end.
+
add_opts([F|Fs], Opts0) ->
- Opts = case filename:basename(F) of
- "group_history.erl" ->
+ Opts = case vsn_is_harmful(F) of
+ true ->
Opts0 -- [{d,'VSN',1}];
- _ ->
+ false ->
Opts0
end,
[{F,Opts}|add_opts(Fs, Opts0)];
add_opts([], _Opts) ->
[].
+vsn_is_harmful(F) ->
+ case filename:basename(F) of
+ "group_history.erl" ->
+ true;
+ _ ->
+ App = filename:basename(filename:dirname(filename:dirname(F))),
+ App =:= "ssl"
+ end.
+
get_src(["preloaded"|Apps]) ->
WC = filename:join(code:root_dir(), "erts/preloaded/src/*.erl"),
filelib:wildcard(WC) ++ get_src(Apps);
@@ -188,109 +246,48 @@ get_beams([]) -> [].
%%% Generate renumbered .S files.
%%%
-compile_to_asm_fun(#{outdir:=OutDir}) ->
+compile_to_asm_fun(#{outdir:=OutDir}=Opts) ->
fun(File) ->
- compile_to_asm(File, OutDir)
+ Legacy = map_get(legacy, Opts),
+ compile_to_asm(File, OutDir, Legacy)
end.
-compile_to_asm({File,Opts}, OutDir) ->
- case compile:file(File, [to_asm,binary,report_errors|Opts]) of
+compile_to_asm({Beam,elixir}, OutDir, _Legacy) ->
+ Abst = get_abstract_from_beam(Beam),
+ Source = filename:rootname(Beam, ".beam"),
+ Opts = [diffable,{outdir,OutDir},report_errors,{source,Source}],
+ case compile:forms(Abst, Opts) of
+ {ok,_Mod,_Asm} ->
+ ok;
error ->
- error;
- {ok,Mod,Asm0} ->
- {ok,Asm1} = beam_a:module(Asm0, []),
- Asm2 = renumber_asm(Asm1),
- {ok,Asm} = beam_z:module(Asm2, []),
- print_asm(Mod, OutDir, Asm)
+ error
+ end;
+compile_to_asm({File,Opts}, OutDir, Legacy) ->
+ case compile:file(File, [diffable,{outdir,OutDir},report_errors|Opts]) of
+ {ok,_Mod} ->
+ case Legacy of
+ true ->
+ legacy_asm(OutDir, File);
+ false ->
+ ok
+ end;
+ error ->
+ error
end.
-print_asm(Mod, OutDir, Asm) ->
- S = atom_to_list(Mod) ++ ".S",
- Name = filename:join(OutDir, S),
- {ok,Fd} = file:open(Name, [write,raw,delayed_write]),
- ok = beam_listing(Fd, Asm),
- ok = file:close(Fd).
-
-renumber_asm({Mod,Exp,Attr,Fs0,NumLabels}) ->
- EntryLabels = maps:from_list(entry_labels(Fs0)),
- Fs = [fix_func(F, EntryLabels) || F <- Fs0],
- {Mod,Exp,Attr,Fs,NumLabels}.
-
-entry_labels(Fs) ->
- [{Entry,{Name,Arity}} || {function,Name,Arity,Entry,_} <- Fs].
-
-fix_func({function,Name,Arity,Entry0,Is0}, LabelMap0) ->
- Entry = maps:get(Entry0, LabelMap0),
- LabelMap = label_map(Is0, 1, LabelMap0),
- Is = replace(Is0, [], LabelMap),
- {function,Name,Arity,Entry,Is}.
+legacy_asm(OutDir, Source) ->
+ ModName = filename:rootname(filename:basename(Source)),
+ File = filename:join(OutDir, ModName),
+ AsmFile = File ++ ".S",
+ {ok,Asm0} = file:read_file(AsmFile),
+ Asm1 = unicode:characters_to_binary(Asm0, utf8, latin1),
+ Asm = [<<"%% -*- encoding:latin-1 -*-\n">>|Asm1],
+ ok = file:write_file(AsmFile, Asm).
-label_map([{label,Old}|Is], New, Map) ->
- case maps:is_key(Old, Map) of
- false ->
- label_map(Is, New+1, Map#{Old=>New});
- true ->
- label_map(Is, New, Map)
- end;
-label_map([_|Is], New, Map) ->
- label_map(Is, New, Map);
-label_map([], _New, Map) ->
- Map.
-
-replace([{label,Lbl}|Is], Acc, D) ->
- replace(Is, [{label,label(Lbl, D)}|Acc], D);
-replace([{test,Test,{f,Lbl},Ops}|Is], Acc, D) ->
- replace(Is, [{test,Test,{f,label(Lbl, D)},Ops}|Acc], D);
-replace([{test,Test,{f,Lbl},Live,Ops,Dst}|Is], Acc, D) ->
- replace(Is, [{test,Test,{f,label(Lbl, D)},Live,Ops,Dst}|Acc], D);
-replace([{select,I,R,{f,Fail0},Vls0}|Is], Acc, D) ->
- Vls = lists:map(fun ({f,L}) -> {f,label(L, D)};
- (Other) -> Other
- end, Vls0),
- Fail = label(Fail0, D),
- replace(Is, [{select,I,R,{f,Fail},Vls}|Acc], D);
-replace([{'try',R,{f,Lbl}}|Is], Acc, D) ->
- replace(Is, [{'try',R,{f,label(Lbl, D)}}|Acc], D);
-replace([{'catch',R,{f,Lbl}}|Is], Acc, D) ->
- replace(Is, [{'catch',R,{f,label(Lbl, D)}}|Acc], D);
-replace([{jump,{f,Lbl}}|Is], Acc, D) ->
- replace(Is, [{jump,{f,label(Lbl, D)}}|Acc], D);
-replace([{loop_rec,{f,Lbl},R}|Is], Acc, D) ->
- replace(Is, [{loop_rec,{f,label(Lbl, D)},R}|Acc], D);
-replace([{loop_rec_end,{f,Lbl}}|Is], Acc, D) ->
- replace(Is, [{loop_rec_end,{f,label(Lbl, D)}}|Acc], D);
-replace([{wait,{f,Lbl}}|Is], Acc, D) ->
- replace(Is, [{wait,{f,label(Lbl, D)}}|Acc], D);
-replace([{wait_timeout,{f,Lbl},To}|Is], Acc, D) ->
- replace(Is, [{wait_timeout,{f,label(Lbl, D)},To}|Acc], D);
-replace([{bif,Name,{f,Lbl},As,R}|Is], Acc, D) when Lbl =/= 0 ->
- replace(Is, [{bif,Name,{f,label(Lbl, D)},As,R}|Acc], D);
-replace([{gc_bif,Name,{f,Lbl},Live,As,R}|Is], Acc, D) when Lbl =/= 0 ->
- replace(Is, [{gc_bif,Name,{f,label(Lbl, D)},Live,As,R}|Acc], D);
-replace([{call,Ar,{f,Lbl}}|Is], Acc, D) ->
- replace(Is, [{call,Ar,{f,label(Lbl,D)}}|Acc], D);
-replace([{make_fun2,{f,Lbl},U1,U2,U3}|Is], Acc, D) ->
- replace(Is, [{make_fun2,{f,label(Lbl, D)},U1,U2,U3}|Acc], D);
-replace([{bs_init,{f,Lbl},Info,Live,Ss,Dst}|Is], Acc, D) when Lbl =/= 0 ->
- replace(Is, [{bs_init,{f,label(Lbl, D)},Info,Live,Ss,Dst}|Acc], D);
-replace([{bs_put,{f,Lbl},Info,Ss}|Is], Acc, D) when Lbl =/= 0 ->
- replace(Is, [{bs_put,{f,label(Lbl, D)},Info,Ss}|Acc], D);
-replace([{put_map=I,{f,Lbl},Op,Src,Dst,Live,List}|Is], Acc, D)
- when Lbl =/= 0 ->
- replace(Is, [{I,{f,label(Lbl, D)},Op,Src,Dst,Live,List}|Acc], D);
-replace([{get_map_elements=I,{f,Lbl},Src,List}|Is], Acc, D) when Lbl =/= 0 ->
- replace(Is, [{I,{f,label(Lbl, D)},Src,List}|Acc], D);
-replace([{recv_mark=I,{f,Lbl}}|Is], Acc, D) ->
- replace(Is, [{I,{f,label(Lbl, D)}}|Acc], D);
-replace([{recv_set=I,{f,Lbl}}|Is], Acc, D) ->
- replace(Is, [{I,{f,label(Lbl, D)}}|Acc], D);
-replace([I|Is], Acc, D) ->
- replace(Is, [I|Acc], D);
-replace([], Acc, _) ->
- lists:reverse(Acc).
-
-label(Old, D) when is_integer(Old) ->
- maps:get(Old, D).
+get_abstract_from_beam(Beam) ->
+ {ok,{_Mod,[AbstChunk]}} = beam_lib:chunks(Beam, [abstract_code]),
+ {abstract_code,{raw_abstract_v1,Abst}} = AbstChunk,
+ Abst.
%%%
%%% Compile and disassemble the loaded code.
@@ -305,6 +302,15 @@ compile_to_dis_fun(#{outdir:=OutDir,no_compile:=true}) ->
dis_only(File, OutDir)
end.
+compile_to_dis({File,elixir}, OutDir) ->
+ {ok,Beam} = file:read_file(File),
+ Mod0 = filename:rootname(filename:basename(File)),
+ Mod = list_to_atom(Mod0),
+ Dis0 = disasm(Mod, Beam),
+ Dis1 = renumber_disasm(Dis0, Mod, Mod),
+ Dis = format_disasm(Dis1),
+ OutFile = filename:join(OutDir, atom_to_list(Mod)++".dis"),
+ ok = file:write_file(OutFile, Dis);
compile_to_dis({File,Opts}, OutDir) ->
case compile:file(File, [to_asm,binary,report_errors|Opts]) of
error ->
@@ -598,28 +604,3 @@ p_run_loop(Test, List, N, Refs0, Errors0) ->
Refs = Refs0 -- [Ref],
p_run_loop(Test, List, N, Refs, Errors)
end.
-
-%%%
-%%% Borrowed from beam_listing and tweaked.
-%%%
-
-beam_listing(Stream, {Mod,Exp,Attr,Code,NumLabels}) ->
- Head = ["%% -*- encoding:latin-1 -*-\n",
- io_lib:format("{module, ~p}. %% version = ~w\n",
- [Mod, beam_opcodes:format_number()]),
- io_lib:format("\n{exports, ~p}.\n", [Exp]),
- io_lib:format("\n{attributes, ~p}.\n", [Attr]),
- io_lib:format("\n{labels, ~p}.\n", [NumLabels])],
- ok = file:write(Stream, Head),
- lists:foreach(
- fun ({function,Name,Arity,Entry,Asm}) ->
- S = [io_lib:format("\n\n{function, ~w, ~w, ~w}.\n",
- [Name,Arity,Entry])|format_asm(Asm)],
- ok = file:write(Stream, S)
- end, Code).
-
-format_asm([{label,_}=I|Is]) ->
- [io_lib:format(" ~p", [I]),".\n"|format_asm(Is)];
-format_asm([I|Is]) ->
- [io_lib:format(" ~p", [I]),".\n"|format_asm(Is)];
-format_asm([]) -> [].