[PATCH] D28075: MergeFunctions: Preserve debug info in thunks, under option -mergefunc-preserve-debug-info
Anmol P. Paralkar via Phabricator via llvm-commits
llvm-commits at lists.llvm.org
Wed Jan 4 16:52:01 PST 2017
appcs added a comment.
Thank you for your review, Adrian.
True, under -mergefunc-preserve-debug-info the existing debug info (from the entry block) for the merged function's arguments is
preserved.
Call sites of the merged function occurring from within the TU of the merged function's definition will point directly to the shared
implementation and call sites of the merged function that are external to the TU of the merged function's definition call the thunk
for the merged function (which tail call's the shared implementation passing forward the incoming arguments). This is existing
behaviour under -mergefunc and remains unchanged under -mergefunc-preserve-debug-info (except for the tail call part).
So, given two source files, thunk-debugability.c & thunk-debugability-aux.c with content as follows:
thunk-debugability.c {
1 int sumA(int *a, int size) {
2 int i, s;
3 for (i = 0, s = 0; i < size; i++)
4 s += a[i];
5 return s;
6 }
7
8 int sumB(int *a, int size) {
9 int i, s;
10 for (i = 0, s = 0; i < size; i++)
11 s += a[i];
12 return s;
13 }
14
15 extern int sumExternalTest(int *p, int size);
16
17 int main(void) {
18
19 int a[8] = { 1, 2, 3, 4, 5, 6, 7, 8 };
20
21 int x, y;
22
23 x = sumA(a, 8);
24 y = sumB(a, 8);
25 sumExternalTest(a, 8);
26
27 return (x == y) ? 0 : -1;
28 }
}
thunk-debugability-aux.c {
1 extern int sumA(int *a, int size);
2 extern int sumB(int *a, int size);
3
4 int sumExternalTest(int *p, int size) {
5
6 int x, y;
7
8 x = sumA(p, 8);
9 y = sumB(p, 8);
10
11 return (x == y ? x : -1);
12 }
}
Under -mergefunc (i.e. existing behaviour) {
sumA() [shared implementation] remains as per the definition given by the user
sumB() [merged function] as defined by the user is erased and a new sumB() is created containing a tail call to sumA()
The call made by main() to sumB() is replaced by a call to sumA()
The call to sumB() made by the external caller sumExternalTest() will call the thunk sumB() and in turn, sumB() will tail call sumA()
}
Under -mergefunc -mergefunc-preserve-debug-info (i.e. new behaviour) {
sumA() [shared implementation] remains as per the definition given by the user
sumB() [merged function] as defined by the user is retained, but is transformed as follows: {
The debug info for the arguments in the entry block is preserved
A call is made to sumA() passing forward the arguments received by sumB()
Debug info is created for the call to sumA() and its return value
The call to sumA() is not marked as a tail call [PS: Needs elaboration/clarification]
The rest of the CFG for sumB() is erased
}
The call made by main() to sumB() is replaced by a call to sumA()
The call to sumB() made by the external caller sumExternalTest() will call the thunk sumB() and in turn, sumB() will call sumA()
}
The rationale for the new behaviour is that under -mergefunc-preserve-debug-info it is possible to step from the call site of the
merged function into its thunk and from there, into the shared implementation with the backtrace truly indicating the execution
flow. Once that new flow (which is the essence of -mergefunc modulo the tail call, which is just optimization) is understood and
debugged, the user can recompile with -mergefunc-preserve-debug-info removed, if the need be.
With the above example source code (compiled at -O0 -mergefunc -mergefunc-preserve-debug-info):
Backtraces: {
With call to sumA() not marked as a tail call in thunk sumB(): {
Setting a breakpoint at a thunked function call site within the TU of its definition: {
(gdb) break thunk-debugability.c:24
Breakpoint 1 at 0x400609: file ./thunk-debugability.c, line 24.
(gdb) run
Starting program: /auto/compiler-migration/anmparal/code/upstreaming/MFI/thunk-debugability.mfig.exe
Breakpoint 1, main () at ./thunk-debugability.c:24
24 y = sumB(a, 8);
(gdb) step
sumA (a=0x7fffffffd6d0, size=8) at ./thunk-debugability.c:3
3 for (i = 0, s = 0; i < size; i++)
(gdb) bt
#0 sumA (a=0x7fffffffd6d0, size=8) at ./thunk-debugability.c:3
#1 0x000000000040060e in main () at ./thunk-debugability.c:24
(gdb)
}
Setting a breakpoint at a thunked function call site outside the TU of its definition: {
(gdb) break thunk-debugability-aux.c:9
Breakpoint 2 at 0x400507: file ./thunk-debugability-aux.c, line 9.
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /auto/compiler-migration/anmparal/code/upstreaming/MFI/thunk-debugability.mfig.exe
Breakpoint 2, sumExternalTest (p=0x7fffffffd6d0, size=8) at ./thunk-debugability-aux.c:9
9 y = sumB(p, 8);
(gdb) step
sumB (a=0x7fffffffd6d0, size=8) at ./thunk-debugability.c:8
8 int sumB(int *a, int size)
(gdb) bt
#0 sumB (a=0x7fffffffd6d0, size=8) at ./thunk-debugability.c:8
#1 0x0000000000400510 in sumExternalTest (p=0x7fffffffd6d0, size=8) at ./thunk-debugability-aux.c:9
#2 0x000000000040061f in main () at ./thunk-debugability.c:25
(gdb) step
sumA (a=0x7fffffffd6d0, size=8) at ./thunk-debugability.c:3
3 for (i = 0, s = 0; i < size; i++)
(gdb) bt
#0 sumA (a=0x7fffffffd6d0, size=8) at ./thunk-debugability.c:3
#1 0x00000000004005a4 in sumB (a=0x7fffffffd6d0, size=8) at ./thunk-debugability.c:8
#2 0x0000000000400510 in sumExternalTest (p=0x7fffffffd6d0, size=8) at ./thunk-debugability-aux.c:9
#3 0x000000000040061f in main () at ./thunk-debugability.c:25
(gdb)
}
}
With call to sumA() marked as a tail call in thunk sumB(): {
Setting a breakpoint at a thunked function call site within the TU of its definition: {
(gdb) break thunk-debugability.c:24
Breakpoint 1 at 0x400609: file ./thunk-debugability.c, line 24.
(gdb) run
Starting program: /auto/compiler-migration/anmparal/code/upstreaming/MFI/thunk-debugability.mfig.exe
Breakpoint 1, main () at ./thunk-debugability.c:24
24 y = sumB(a, 8);
(gdb) step
sumA (a=0x7fffffffd6d0, size=8) at ./thunk-debugability.c:3
3 for (i = 0, s = 0; i < size; i++)
(gdb) bt
#0 sumA (a=0x7fffffffd6d0, size=8) at ./thunk-debugability.c:3
#1 0x000000000040060e in main () at ./thunk-debugability.c:24
(gdb)
}
Setting a breakpoint at a thunked function call site outside the TU of its definition: {
(gdb) break thunk-debugability-aux.c:9
Breakpoint 2 at 0x400507: file ./thunk-debugability-aux.c, line 9.
(gdb) run
Starting program: /auto/compiler-migration/anmparal/code/upstreaming/MFI/thunk-debugability.mfig.exe
Breakpoint 2, sumExternalTest (p=0x7fffffffd6d0, size=8) at ./thunk-debugability-aux.c:9
9 y = sumB(p, 8);
(gdb) step
sumB (a=0x7fffffffd6d0, size=8) at ./thunk-debugability.c:8
8 int sumB(int *a, int size)
(gdb) bt
#0 sumB (a=0x7fffffffd6d0, size=8) at ./thunk-debugability.c:8
#1 0x0000000000400510 in sumExternalTest (p=0x7fffffffd6d0, size=8) at ./thunk-debugability-aux.c:9
#2 0x000000000040061f in main () at ./thunk-debugability.c:25
(gdb) step
sumA (a=0x7fffffffd6d0, size=8) at ./thunk-debugability.c:1
1 int sumA(int *a, int size)
(gdb) bt
#0 sumA (a=0x7fffffffd6d0, size=8) at ./thunk-debugability.c:1
#1 0x0000000000400510 in sumExternalTest (p=0x7fffffffd6d0, size=8) at ./thunk-debugability-aux.c:9
#2 0x000000000040061f in main () at ./thunk-debugability.c:25
(gdb)
}
}
}
So, these two scenarios have identical behaviour: {
With call to sumA() not marked as a tail call in thunk sumB() and setting a breakpoint at a thunked function call site within the TU of its definition.
With call to sumA() marked as a tail call in thunk sumB() and setting a breakpoint at a thunked function call site within the TU of its definition.
}
Whereas, in: {
With call to sumA() not marked as a tail call in thunk sumB() and setting a breakpoint at a thunked function call site outside the TU of its definition.
With call to sumA() marked as a tail call in thunk sumB() and setting a breakpoint at a thunked function call site outside the TU of its definition.
}
- the behaviour differs: When the call to sumA() is not marked as a tail call, the execution flow is clear (from call site, to thunk,
to shared implementation) but when the call to sumA() is marked as a tail call, note how across the step, sumB() is ("suddenly")
replaced by sumA()
Which behaviour do we feel is better for the debug experience?
I need to clarify: It is not true that the call to sumA() will be auto-converted into a tail call at -O<non-zero>
I apologize for making a misleading statement; I did not realize that I was passing an already -O<non-zero>'ed LLVM IR file to
opt -O<non-zero> -mergefunc -mergefunc-preserve-debug-info
So, do we want to conditionally tail call the shared implementation?
[debug] At -O0 -mergefunc -mergefunc-preserve-debug-info - it is an actual call
[production] At -O<non-zero> -mergefunc -mergefunc-preserve-debug-info - it is a tail call
- or do we just unconditionally keep the tail call, as-(currently)-is?
--
Thank you and @fhahn for the review; Please let me know which way we want to resolve the question of the tail call and
I'll re-spin, with the test reworked with the above (keeping a single TU) example code, incorporating the other feedback.
Anmol.
https://reviews.llvm.org/D28075
More information about the llvm-commits
mailing list