Fixture 129

struct by value

C · 3 functions · 4 lanes · 10 of 12 function-lanes behave identically

2 of 4 lanes have a function that returns a different result after decompilation: clang-O0 (2/3), gcc-O0 (2/3).

Struct passing crosses an ABI boundary that depends on size: a two-word struct travels in registers on SysV x86-64, while a larger one is passed in memory with a hidden pointer. Both spellings look identical in C.

tests/decompiler_fixtures/src/129_struct_by_value.c source
#include <stdint.h>

/* Struct passing crosses an ABI boundary that depends on size: a two-word
 * struct travels in registers on SysV x86-64, while a larger one is passed in
 * memory with a hidden pointer. Both spellings look identical in C. */

struct Small {
    int32_t a;
    int32_t b;
};

struct Large {
    int32_t a;
    int32_t b;
    int32_t c;
    int32_t d;
    int32_t e;
};

static int32_t consume_small(struct Small value) {
    return value.a * 10 + value.b;
}

static int32_t consume_large(struct Large value) {
    return value.a + value.b + value.c + value.d + value.e;
}

__attribute__((noinline)) int32_t
pass_small_by_value(int32_t a, int32_t b) {
    struct Small value;
    value.a = a;
    value.b = b;
    return consume_small(value);
}

__attribute__((noinline)) int32_t
pass_large_by_value(int32_t seed) {
    struct Large value;
    value.a = seed;
    value.b = seed + 1;
    value.c = seed + 2;
    value.d = seed + 3;
    value.e = seed + 4;
    return consume_large(value);
}

__attribute__((noinline)) int32_t
returns_small_struct_field(int32_t a, int32_t b, int32_t which) {
    struct Small value;
    value.a = a;
    value.b = b;
    /* Returning the whole struct would cross the same boundary in reverse. */
    return which ? value.a : value.b;
}

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 -O0

2/3
pass_large_by_value fail 19 lines
// glaurung: pass_large_by_value @ 0x1150
__attribute__((no_stack_protector)) int32_t pass_large_by_value(int32_t arg0) {
    extern int consume_large(long);
    unsigned char local_18[20];
    int ret;
    unsigned char stack_1[20];
    // x86-64 prologue: save rbp, frame 48 bytes
    *(int *)(&local_18[0]) = arg0;
    *(int *)((&local_18[0] + 4)) = ((unsigned long)((unsigned int)(arg0)) + 1);
    *(int *)((&local_18[0] + 8)) = ((unsigned long)((unsigned int)(arg0)) + 2);
    *(int *)((&local_18[0] + 12)) = ((unsigned long)((unsigned int)(arg0)) + 3);
    *(int *)((&local_18[0] + 16)) = ((unsigned long)((unsigned int)(arg0)) + 4);
    *(long *)(&stack_1[0]) = *(long *)(&local_18[0]);
    *(long *)((&stack_1[0] + 8)) = *(long *)((&local_18[0] + 8));
    *(int *)((&stack_1[0] + 16)) = *(int *)((&local_18[0] + 16));
    ret = consume_large(arg0);
    // x86-64 epilogue: restore rbp
    return ret;
}
pass_small_by_value pass 12 lines
// glaurung: pass_small_by_value @ 0x1100
__attribute__((no_stack_protector)) int32_t pass_small_by_value(int32_t arg0, int32_t arg1) {
    extern int consume_small(long);
    unsigned char local_10[8];
    int ret;
    // x86-64 prologue: save rbp, frame 16 bytes
    *(int *)(&local_10[0]) = arg0;
    *(int *)((&local_10[0] + 4)) = arg1;
    ret = consume_small(*(long *)(&local_10[0]));
    // x86-64 epilogue: restore rbp
    return ret;
}
returns_small_struct_field pass 12 lines
// glaurung: returns_small_struct_field @ 0x11d0
__attribute__((no_stack_protector)) int32_t returns_small_struct_field(int32_t arg0, int32_t arg1, int32_t arg2) {
    unsigned char local_18[8];
    // x86-64 prologue: save rbp
    *(int *)(&local_18[0]) = arg0;
    *(int *)((&local_18[0] + 4)) = arg1;
    if (((unsigned long)((unsigned int)(arg2)) == 0)) {
        return (unsigned int)(*(int *)((&local_18[0] + 4)));
    } else {
        return (unsigned int)(*(int *)(&local_18[0]));
    }
}

gcc -O0

2/3
pass_large_by_value fail 21 lines
// glaurung: pass_large_by_value @ 0x1167
__attribute__((no_stack_protector)) int32_t pass_large_by_value(int32_t arg0) {
    extern int consume_large(long);
    unsigned char local_20[20];
    int ret;
    unsigned char stack_1[20];
    long var15;
    // x86-64 prologue: save rbp, frame 48 bytes
    *(int *)(&local_20[0]) = arg0;
    *(int *)((&local_20[0] + 4)) = ((unsigned long)((unsigned int)(arg0)) + 1);
    *(int *)((&local_20[0] + 8)) = ((unsigned long)((unsigned int)(arg0)) + 2);
    *(int *)((&local_20[0] + 12)) = ((unsigned long)((unsigned int)(arg0)) + 3);
    *(int *)((&local_20[0] + 16)) = ((unsigned long)((unsigned int)(arg0)) + 4);
    var15 = *(long *)((&local_20[0] + 8));
    *(long *)(&stack_1[0]) = *(long *)(&local_20[0]);
    *(long *)((&stack_1[0] + 8)) = var15;
    *(int *)((&stack_1[0] + 16)) = *(int *)((&local_20[0] + 16));
    ret = consume_large(arg0);
    // x86-64 epilogue: restore rbp
    return ret;
}
pass_small_by_value pass 12 lines
// glaurung: pass_small_by_value @ 0x113b
__attribute__((no_stack_protector)) int32_t pass_small_by_value(int32_t arg0, int32_t arg1) {
    extern int consume_small(long);
    unsigned char local_8[8];
    int ret;
    // x86-64 prologue: save rbp, frame 24 bytes
    *(int *)(&local_8[0]) = arg0;
    *(int *)((&local_8[0] + 4)) = arg1;
    ret = consume_small(*(long *)(&local_8[0]));
    // x86-64 epilogue: restore rbp
    return ret;
}
returns_small_struct_field pass 12 lines
// glaurung: returns_small_struct_field @ 0x11c7
__attribute__((no_stack_protector)) int32_t returns_small_struct_field(int32_t arg0, int32_t arg1, int32_t arg2) {
    unsigned char local_8[8];
    // x86-64 prologue: save rbp
    *(int *)(&local_8[0]) = arg0;
    *(int *)((&local_8[0] + 4)) = arg1;
    if (((unsigned long)((unsigned int)(arg2)) == 0)) {
        return (unsigned int)(*(int *)((&local_8[0] + 4)));
    } else {
        return (unsigned int)(*(int *)(&local_8[0]));
    }
}

clang -O2

3/3
pass_large_by_value pass 4 lines
// glaurung: pass_large_by_value @ 0x1110
int32_t pass_large_by_value(int32_t arg0) {
    return (unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)((arg0 + (arg0 * 2)))) + (arg0 * 2)))) + 10));
}
pass_small_by_value pass 4 lines
// glaurung: pass_small_by_value @ 0x1100
int32_t pass_small_by_value(int32_t arg0, int32_t arg1) {
    return (unsigned int)((arg1 + ((unsigned long)((unsigned int)((arg0 + (arg0 * 4)))) * 2)));
}
returns_small_struct_field pass 4 lines
// glaurung: returns_small_struct_field @ 0x1120
int32_t returns_small_struct_field(int32_t arg0, int32_t arg1, int32_t arg2) {
    return (((unsigned long)((unsigned int)(arg2)) == 0) ? arg1 : (unsigned long)((unsigned int)(arg0)));
}

gcc -O2

3/3
pass_large_by_value pass 4 lines
// glaurung: pass_large_by_value @ 0x1110
int32_t pass_large_by_value(int32_t arg0) {
    return (unsigned int)(((arg0 + (arg0 * 4)) + 10));
}
pass_small_by_value pass 4 lines
// glaurung: pass_small_by_value @ 0x1100
int32_t pass_small_by_value(int32_t arg0, int32_t arg1) {
    return (unsigned int)((arg1 + ((unsigned long)((unsigned int)((arg0 + (arg0 * 4)))) * 2)));
}
returns_small_struct_field pass 4 lines
// glaurung: returns_small_struct_field @ 0x1120
int32_t returns_small_struct_field(int32_t arg0, int32_t arg1, int32_t arg2) {
    return (((unsigned long)((unsigned int)(arg2)) != 0) ? arg0 : (unsigned long)((unsigned int)(arg1)));
}

← 213 fixtures