From 8f725574800d559a1d71e43ab5f21c0bcb6fa142 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org>
Date: Tue, 23 Oct 2018 09:11:27 +0200
Subject: Add a diffable option for the compiler

Add the `diffable` option to produce a more diff-friendly output in
a .S file. In a diffable .S file, label numbers starts from 1 in each
function and local function calls use function names instead of labels.

scripts/diffable produces diff-friendly files but only for a hard-coded
sub set of files in OTP. Having the option directly in the compiler
makes is easier to diff the BEAM code for arbitrary source modules.
---
 lib/compiler/src/beam_listing.erl   |  2 +-
 lib/compiler/src/compile.erl        | 35 +++++++++++++++++++++++++++++++++++
 lib/compiler/test/compile_SUITE.erl |  1 +
 3 files changed, 37 insertions(+), 1 deletion(-)

diff --git a/lib/compiler/src/beam_listing.erl b/lib/compiler/src/beam_listing.erl
index 8a0ce5b50a..6121593b11 100644
--- a/lib/compiler/src/beam_listing.erl
+++ b/lib/compiler/src/beam_listing.erl
@@ -66,7 +66,7 @@ module(Stream, [_|_]=Fs) ->
     foreach(fun (F) -> io:format(Stream, "~p.\n", [F]) end, Fs).
 
 format_asm([{label,L}|Is]) ->
-    ["  {label,",integer_to_list(L),"}.\n"|format_asm(Is)];
+    [io_lib:format("  {label,~p}.\n", [L])|format_asm(Is)];
 format_asm([I|Is]) ->
     [io_lib:format("    ~p", [I]),".\n"|format_asm(Is)];
 format_asm([]) -> [].
diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl
index 27e6e8fe00..65c4f140c9 100644
--- a/lib/compiler/src/compile.erl
+++ b/lib/compiler/src/compile.erl
@@ -868,7 +868,9 @@ asm_passes() ->
        %% 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"}},
@@ -1926,6 +1928,39 @@ 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'.
 
diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl
index 5656743c76..8d8bbe9543 100644
--- a/lib/compiler/test/compile_SUITE.erl
+++ b/lib/compiler/test/compile_SUITE.erl
@@ -396,6 +396,7 @@ do_file_listings(DataDir, PrivDir, [File|Files]) ->
     do_listing(Simple, TargetDir, dclean, ".clean"),
     do_listing(Simple, TargetDir, dpeep, ".peep"),
     do_listing(Simple, TargetDir, dopt, ".optimize"),
+    do_listing(Simple, TargetDir, diffable, ".S"),
 
     %% First clean up.
     Listings = filename:join(PrivDir, listings),
-- 
cgit v1.2.3