Fixture 81
call argument identity
C · 2 functions · 4 lanes · 6 of 8 function-lanes behave identically
2 of 4 lanes have a function that returns a different result after decompilation: clang-O2 (1/2), gcc-O2 (1/2).
Two call arguments computed from the SAME scratch register, in the identical lea -0x1(%rax) form, with the register redefined between them.
At -O0 GCC emits: mov -0x24(%rbp),%eax ; depth lea -0x1(%rax),%ecx ; 4th argument = depth - 1 mov -0x4(%rbp),%eax ; split <-- %eax redefined lea -0x1(%rax),%edx ; 3rd argument = split - 1
The use at the first lea must bind to the earlier definition. Binding it to the later one collapses both arguments to split - 1 and silently loses depth. Reduced from 36_quicksort, where the lost argument was the recursion depth bound, so the sort terminated early and returned an unsorted array.
#include <stdint.h>
/* Two call arguments computed from the SAME scratch register, in the identical
* `lea -0x1(%rax)` form, with the register redefined between them.
*
* At -O0 GCC emits:
* mov -0x24(%rbp),%eax ; depth
* lea -0x1(%rax),%ecx ; 4th argument = depth - 1
* mov -0x4(%rbp),%eax ; split <-- %eax redefined
* lea -0x1(%rax),%edx ; 3rd argument = split - 1
*
* The use at the first `lea` must bind to the earlier definition. Binding it to
* the later one collapses both arguments to `split - 1` and silently loses
* `depth`. Reduced from 36_quicksort, where the lost argument was the recursion
* depth bound, so the sort terminated early and returned an unsorted array.
*/
__attribute__((noinline)) int32_t
argument_sink(int32_t a, int32_t b, int32_t c, int32_t d) {
return a * 1000 + b * 100 + c * 10 + d;
}
__attribute__((noinline)) int32_t
two_decrements_one_scratch(const int32_t *values, int32_t low, int32_t high,
int32_t depth) {
int32_t split;
if (values == 0) {
return -1;
}
split = values[0] + low + high;
return argument_sink(0, low, split - 1, depth - 1);
} Recovered C
Generated by glaurung decompile --style decbench at b47f6b43.
baseline.json records the result after recompiling the C and calling it beside the
original with seeded inputs.
clang -O2
1/2argument_sink pass 4 lines
// glaurung: argument_sink @ 0x1110
int32_t argument_sink(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
return (unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(((arg1 * 100) + (arg0 * 1000)))) + ((unsigned long)((unsigned int)((arg2 + (arg2 * 4)))) * 2)))) + arg3));
} two_decrements_one_scratch fail 10 lines
// glaurung: two_decrements_one_scratch @ 0x1130
int32_t two_decrements_one_scratch(const int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
extern int argument_sink(int, int, int, int);
int ret;
if ((arg0 == 0)) {
return 0xffffffff;
}
ret = ((int (*)(int))argument_sink)(0);
return ret;
} gcc -O2
1/2argument_sink pass 4 lines
// glaurung: argument_sink @ 0x1120
int32_t argument_sink(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
return (unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(((arg0 * 1000) + (arg1 * 100)))) + ((unsigned long)((unsigned int)((arg2 + (arg2 * 4)))) * 2)))) + arg3));
} two_decrements_one_scratch fail 10 lines
// glaurung: two_decrements_one_scratch @ 0x1140
int32_t two_decrements_one_scratch(const int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
extern int argument_sink(int, int, int, int);
int ret;
if ((arg0 == 0)) {
return 0xffffffff;
}
ret = ((int (*)(int))argument_sink)(0);
return ret;
} clang -O0
2/2argument_sink pass 6 lines
// glaurung: argument_sink @ 0x1110
int32_t argument_sink(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
// x86-64 prologue: save rbp
// x86-64 epilogue: restore rbp
return (unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(((arg0 * 1000) + (arg1 * 100)))) + (arg2 * 10)))) + arg3));
} two_decrements_one_scratch pass 14 lines
// glaurung: two_decrements_one_scratch @ 0x1140
int32_t two_decrements_one_scratch(const int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
extern int argument_sink(int, int, int, int);
int split;
int var11;
// x86-64 prologue: save rbp, frame 32 bytes
if ((arg0 != 0)) {
split = ((unsigned int)(((unsigned long)((unsigned int)(*(int *)((long)arg0))) + arg1)) + arg2);
var11 = argument_sink(0, (unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(((unsigned long)((unsigned int)(split)) - 1))), (unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg3)) - 1))));
return (unsigned int)(var11);
} else {
return (unsigned int)(-1);
}
} gcc -O0
2/2argument_sink pass 8 lines
// glaurung: argument_sink @ 0x1119
int32_t argument_sink(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
int var9;
// x86-64 prologue: save rbp
var9 = ((unsigned int)(((unsigned long)((unsigned int)(arg2)) << 2)) + (unsigned int)(arg2));
// x86-64 epilogue: restore rbp
return (unsigned int)(((unsigned long)((unsigned int)(arg3)) + (unsigned long)((unsigned int)(((unsigned long)((unsigned int)((((unsigned long)((unsigned int)(arg0)) * 1000) + ((unsigned long)((unsigned int)(arg1)) * 100)))) + (unsigned long)((unsigned int)(((unsigned long)((unsigned int)(var9)) + (unsigned long)((unsigned int)(var9))))))))));
} two_decrements_one_scratch pass 14 lines
// glaurung: two_decrements_one_scratch @ 0x1155
int32_t two_decrements_one_scratch(const int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
extern int argument_sink(int, int, int, int);
int split;
int var11;
// x86-64 prologue: save rbp, frame 48 bytes
if ((arg0 != 0)) {
split = ((unsigned int)(arg2) + (unsigned int)(((unsigned long)((unsigned int)(*(int *)((long)arg0))) + (unsigned long)((unsigned int)(arg1)))));
var11 = argument_sink(0, (unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(((unsigned long)((unsigned int)(split)) - 1))), (unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg3)) - 1))));
return var11;
} else {
return 0xffffffff;
}
}