Last active
August 31, 2017 05:31
-
-
Save bjorng/cfb0125cf93375e616231adf5531855d to your computer and use it in GitHub Desktop.
Compile some OTP applications into a diff-friendly assembly format
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env escript | |
%% -*- erlang -*- | |
-mode(compile). | |
main(Args) -> | |
case Args of | |
[] -> | |
do_compile("asm"); | |
[OutDir] -> | |
do_compile(OutDir); | |
_ -> | |
io:put_chars("usage: otp-diffable-asm [outdir]\n"), | |
halt(1) | |
end. | |
do_compile(OutDir) -> | |
_ = filelib:ensure_dir(filename:join(OutDir, "dummy")), | |
Apps = ["preloaded", | |
"stdlib", | |
"kernel", | |
"reltool", | |
"runtime_tools", | |
"xmerl", | |
"common_test", | |
"compiler", | |
"diameter", | |
"mnesia", | |
"inets", | |
"syntax_tools", | |
"parsetools", | |
"dialyzer", | |
"ssl", | |
"wx"], | |
Files = get_src(Apps), | |
Inc = make_includes(), | |
Opts = [{d,epmd_dist_high,42}, | |
{d,epmd_dist_low,37}, | |
{d,'VSN',1}, | |
{d,'COMPILER_VSN',1}, | |
{d,erlang_daemon_port,1337}|Inc], | |
p_run(fun(File) -> | |
compile_file(File, OutDir, Opts) | |
end, Files). | |
compile_file(File, OutDir, Opts) -> | |
try | |
do_compile_file(File, OutDir, Opts) | |
catch | |
Class:Error -> | |
io:format("~p: ~p ~p\n~p\n", | |
[File,Class,Error,erlang:get_stacktrace()]), | |
error | |
end. | |
do_compile_file(File, OutDir, Opts0) -> | |
Opts = [to_asm,binary,report_errors|Opts0], | |
case compile:file(File, Opts) of | |
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) | |
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). | |
make_includes() -> | |
Is = [{common_test,"include"}, | |
{kernel,"include"}, | |
{kernel,"src"}, | |
{public_key,"include"}, | |
{runtime_tools,"include"}, | |
{ssh,"include"}, | |
{snmp,"include"}, | |
{stdlib,"include"}, | |
{syntax_tools,"include"}, | |
{wx,"src"}, | |
{wx,"include"}, | |
{xmerl,"include"}], | |
[{i,filename:join(code:lib_dir(App), Path)} || {App,Path} <- Is]. | |
get_src(["preloaded"|Apps]) -> | |
WC = filename:join(code:root_dir(), "erts/preloaded/src/*.erl"), | |
filelib:wildcard(WC) ++ get_src(Apps); | |
get_src(["wx"|Apps]) -> | |
LibDir = code:lib_dir(wx), | |
WC1 = filename:join(LibDir, "src/gen/*.erl"), | |
WC2 = filename:join(LibDir, "src/*.erl"), | |
filelib:wildcard(WC1) ++ filelib:wildcard(WC2) ++ get_src(Apps); | |
get_src([App|Apps]) -> | |
WC = filename:join(code:lib_dir(App), "src/*.erl"), | |
filelib:wildcard(WC) ++ get_src(Apps); | |
get_src([]) -> []. | |
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}. | |
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). | |
%%% | |
%%% Run tasks in parallel. | |
%%% | |
p_run(Test, List) -> | |
N = erlang:system_info(schedulers) * 2, | |
p_run_loop(Test, List, N, [], 0). | |
p_run_loop(_, [], _, [], Errors) -> | |
io:put_chars("\r \n"), | |
case Errors of | |
0 -> | |
ok; | |
N -> | |
io:format("~p errors\n", [N]), | |
halt(1) | |
end; | |
p_run_loop(Test, [H|T], N, Refs, Errors) when length(Refs) < N -> | |
{_,Ref} = erlang:spawn_monitor(fun() -> exit(Test(H)) end), | |
p_run_loop(Test, T, N, [Ref|Refs], Errors); | |
p_run_loop(Test, List, N, Refs0, Errors0) -> | |
io:format("\r~p ", [length(List)+length(Refs0)]), | |
receive | |
{'DOWN',Ref,process,_,Res} -> | |
Errors = case Res of | |
ok -> Errors0; | |
error -> Errors0 + 1 | |
end, | |
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([]) -> []. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment