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.
#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/3pass_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/3pass_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/3pass_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/3pass_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)));
}