Fixture 91

union type punning

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

All 4 lanes recompile and return the same results as the original.

Reading a union member other than the one last stored is defined in C (it reinterprets the object representation). The byte order and the trap-free integer layout make this a precise test of storage-identity recovery.

tests/decompiler_fixtures/src/91_union_type_punning.c source
#include <stdint.h>

/* Reading a union member other than the one last stored is defined in C (it
 * reinterprets the object representation). The byte order and the trap-free
 * integer layout make this a precise test of storage-identity recovery. */

union Punner {
    uint32_t word;
    uint8_t bytes[4];
    struct {
        uint16_t low;
        uint16_t high;
    } halves;
};

__attribute__((noinline)) int32_t
pun_byte_of_word(uint32_t word, int32_t index) {
    union Punner punner;
    punner.word = word;
    if (index < 0 || index > 3) {
        return -1;
    }
    return (int32_t)punner.bytes[index];
}

__attribute__((noinline)) uint32_t
pun_halves_swapped(uint32_t word) {
    union Punner punner;
    uint16_t swap;
    punner.word = word;
    swap = punner.halves.low;
    punner.halves.low = punner.halves.high;
    punner.halves.high = swap;
    return punner.word;
}

__attribute__((noinline)) int32_t
pun_is_little_endian(void) {
    union Punner punner;
    punner.word = 1u;
    return punner.bytes[0] == 1u;
}

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

3/3
pun_byte_of_word pass 20 lines
// glaurung: pun_byte_of_word @ 0x1100
__attribute__((no_stack_protector)) int32_t pun_byte_of_word(uint32_t arg0, int32_t arg1) {
    unsigned char local_10[4];
    int local_4;
    // x86-64 prologue: save rbp
    *(int *)(&local_10[0]) = arg0;
    if (((long)(arg1) < 0)) {
        local_4 = -1;
        // x86-64 epilogue: restore rbp
        return (unsigned int)(local_4);
    }
    if (((((unsigned long)((unsigned int)(arg1)) == 3) | ((long)(arg1) < 3)) == 0)) {
        local_4 = -1;
        // x86-64 epilogue: restore rbp
        return (unsigned int)(local_4);
    }
    local_4 = (unsigned char)(*(char *)((&local_10[0] + (long)(arg1))));
    // x86-64 epilogue: restore rbp
    return (unsigned int)(local_4);
}
pun_halves_swapped pass 12 lines
// glaurung: pun_halves_swapped @ 0x1150
__attribute__((no_stack_protector)) uint32_t pun_halves_swapped(uint32_t arg0) {
    unsigned short swap;
    unsigned char local_8[4];
    // x86-64 prologue: save rbp
    *(int *)(&local_8[0]) = arg0;
    swap = *(short *)(&local_8[0]);
    *(short *)(&local_8[0]) = *(short *)((&local_8[0] + 2));
    *(short *)((&local_8[0] + 2)) = swap;
    // x86-64 epilogue: restore rbp
    return (unsigned int)(*(int *)(&local_8[0]));
}
pun_is_little_endian pass 8 lines
// glaurung: pun_is_little_endian @ 0x1180
__attribute__((no_stack_protector)) int32_t pun_is_little_endian(void) {
    unsigned char local_8[4];
    // x86-64 prologue: save rbp
    *(int *)(&local_8[0]) = 1;
    // x86-64 epilogue: restore rbp
    return ((unsigned long)((unsigned int)((unsigned char)(*(char *)(&local_8[0])))) == 1);
}

clang -O2

3/3
pun_byte_of_word pass 11 lines
// glaurung: pun_byte_of_word @ 0x1100
__attribute__((no_stack_protector)) int32_t pun_byte_of_word(uint32_t arg0, int32_t arg1) {
    unsigned char local_8[8];
    long ret;
    *(int *)(&local_8[0]) = arg0;
    ret = 0xffffffff;
    if (((unsigned long)((unsigned long)((unsigned int)(arg1))) <= (unsigned long)(3))) {
        ret = (unsigned int)((unsigned char)(*(char *)((&local_8[0] + (unsigned long)((unsigned int)(arg1))))));
    }
    return ret;
}
pun_halves_swapped pass 4 lines
// glaurung: pun_halves_swapped @ 0x1120
uint32_t pun_halves_swapped(uint32_t arg0) {
    return (((unsigned long)(arg0) << 16) | ((unsigned long)(arg0) >> 16));
}
pun_is_little_endian pass 4 lines
// glaurung: pun_is_little_endian @ 0x1130
int32_t pun_is_little_endian(void) {
    return 1;
}

gcc -O0

3/3
pun_byte_of_word pass 16 lines
// glaurung: pun_byte_of_word @ 0x1119
int32_t pun_byte_of_word(uint32_t arg0, int32_t arg1) {
    extern __attribute__((noreturn)) void __stack_chk_fail(void);
    long local_8;
    unsigned char local_c[4];
    long ret;
    // x86-64 prologue: save rbp, frame 32 bytes
    local_8 = (long)(0x28);
    *(int *)(&local_c[0]) = arg0;
    ret = ((((long)(arg1) < 0) || (((unsigned long)((unsigned int)(arg1)) != 3) && (3 <= (long)(arg1)))) ? 0xffffffff : (unsigned int)((unsigned char)(((unsigned int)((unsigned char)(*(char *)((&local_c[0] + (long)(arg1))))) & 255))));
    if ((local_8 != 0x28)) {
        __stack_chk_fail();
    }
    // x86-64 epilogue: restore rbp
    return ret;
}
pun_halves_swapped pass 20 lines
// glaurung: pun_halves_swapped @ 0x1176
uint32_t pun_halves_swapped(uint32_t arg0) {
    extern __attribute__((noreturn)) void __stack_chk_fail(void);
    unsigned short swap;
    long local_8;
    unsigned char local_c[4];
    long ret;
    // x86-64 prologue: save rbp, frame 32 bytes
    local_8 = (long)(0x28);
    *(int *)(&local_c[0]) = arg0;
    swap = *(short *)(&local_c[0]);
    *(short *)(&local_c[0]) = *(short *)((&local_c[0] + 2));
    *(short *)((&local_c[0] + 2)) = swap;
    ret = (unsigned long)((unsigned int)(*(int *)(&local_c[0])));
    if ((local_8 != 0x28)) {
        __stack_chk_fail();
    }
    // x86-64 epilogue: restore rbp
    return ret;
}
pun_is_little_endian pass 16 lines
// glaurung: pun_is_little_endian @ 0x11cb
int32_t pun_is_little_endian(void) {
    extern __attribute__((noreturn)) void __stack_chk_fail(void);
    long local_8;
    unsigned char local_c[4];
    long ret;
    // x86-64 prologue: save rbp, frame 16 bytes
    local_8 = (long)(0x28);
    *(int *)(&local_c[0]) = 1;
    ret = ((unsigned long)((unsigned char)(((unsigned int)((unsigned char)(*(char *)(&local_c[0]))) & 255))) == 1);
    if ((local_8 != 0x28)) {
        __stack_chk_fail();
    }
    // x86-64 epilogue: restore rbp
    return ret;
}

gcc -O2

3/3
pun_byte_of_word pass 14 lines
// glaurung: pun_byte_of_word @ 0x1120
int32_t pun_byte_of_word(uint32_t arg0, int32_t arg1) {
    extern __attribute__((noreturn)) void __stack_chk_fail(void);
    long local_10;
    unsigned char local_14[4];
    long ret;
    local_10 = (long)(0x28);
    *(int *)(&local_14[0]) = arg0;
    ret = (((unsigned long)(3) < (unsigned long)((unsigned long)((unsigned int)(arg1)))) ? 0xffffffff : (unsigned int)((unsigned char)(*(char *)((&local_14[0] + (long)(arg1))))));
    if ((local_10 != 0x28)) {
        __stack_chk_fail();
    }
    return ret;
}
pun_halves_swapped pass 4 lines
// glaurung: pun_halves_swapped @ 0x1170
uint32_t pun_halves_swapped(uint32_t arg0) {
    return (((unsigned long)(arg0) << 16) | ((unsigned long)(arg0) >> 16));
}
pun_is_little_endian pass 4 lines
// glaurung: pun_is_little_endian @ 0x1180
int32_t pun_is_little_endian(void) {
    return 1;
}

← 213 fixtures