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.

tests/decompiler_fixtures/src/81_call_argument_identity.c source
#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/2
argument_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/2
argument_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/2
argument_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/2
argument_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;
    }
}

← 213 fixtures