Fixture 206
aarch64 wide dispatch
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-O2 (2/3), gcc-O2 (2/3).
A switch wide enough that every target lowers it to a JUMP TABLE rather than a comparison tree, so each architecture's table-dispatch recogniser is exercised by the same source.
WHY THIS IS NOT 04_switch_shapes OR 154_wide_switch. Those fixtures are about which arms are recovered and whether the exact case constants survive. This one is about whether the DISPATCH ITSELF is recognised, per architecture, and the two failure modes are completely different: an unrecognised table does not produce wrong arms, it produces no arms at all — the indirect branch contributes zero CFG successors and the case bodies never enter the graph. structure_accounting cannot see that (it accounts the region against the CFG, not against the program), and an execution differential cannot see it either, because a function whose arms are missing usually still returns the right value for the default case.
analysis::dispatch::DispatchTracker has recognisers for x86 (register and scaled-index forms) and for Thumb-2 tbb/tbh. It has NONE for AArch64: grep adrp under src/analysis/ and the hits are in aarch64_literals.rs and xrefs.rs, never in dispatch.rs. Every AArch64 br therefore reports Unresolved::UnknownBase. The aarch64 lane of this fixture is expected to fail on that, and it is the whole reason the fixture exists.
dense_dispatch is the measurement. sparse_dispatch is the control: its labels are far enough apart that no compiler builds a table for it, so it must pass on every architecture — if it fails, the defect is in ladder recovery and not in table recovery, and the two must not be confused.
#include <stdint.h>
/* A switch wide enough that every target lowers it to a JUMP TABLE rather than
* a comparison tree, so each architecture's table-dispatch recogniser is
* exercised by the same source.
*
* WHY THIS IS NOT `04_switch_shapes` OR `154_wide_switch`. Those fixtures are
* about which arms are recovered and whether the exact case constants survive.
* This one is about whether the DISPATCH ITSELF is recognised, per
* architecture, and the two failure modes are completely different: an
* unrecognised table does not produce wrong arms, it produces no arms at all —
* the indirect branch contributes zero CFG successors and the case bodies never
* enter the graph. `structure_accounting` cannot see that (it accounts the
* region against the CFG, not against the program), and an execution
* differential cannot see it either, because a function whose arms are missing
* usually still returns the right value for the default case.
*
* `analysis::dispatch::DispatchTracker` has recognisers for x86 (register and
* scaled-index forms) and for Thumb-2 `tbb`/`tbh`. It has NONE for AArch64:
* grep `adrp` under `src/analysis/` and the hits are in `aarch64_literals.rs`
* and `xrefs.rs`, never in `dispatch.rs`. Every AArch64 `br` therefore reports
* `Unresolved::UnknownBase`. The `aarch64` lane of this fixture is expected to
* fail on that, and it is the whole reason the fixture exists.
*
* `dense_dispatch` is the measurement. `sparse_dispatch` is the control: its
* labels are far enough apart that no compiler builds a table for it, so it
* must pass on every architecture — if it fails, the defect is in ladder
* recovery and not in table recovery, and the two must not be confused.
*/
__attribute__((noinline)) int32_t dense_dispatch(int32_t op, int32_t a,
int32_t b) {
switch (op) {
case 0: return a + b;
case 1: return a - b;
case 2: return a * 3 + b;
case 3: return a ^ b;
case 4: return a | b;
case 5: return a & b;
case 6: return (a >> 1) + b;
case 7: return (a << 1) - b;
case 8: return a + 100;
case 9: return b + 200;
case 10: return a - 300;
case 11: return b - 400;
case 12: return a * 5;
case 13: return b * 7;
case 14: return a + b + 11;
case 15: return a - b - 13;
default: return -1;
}
}
/* The control: labels chosen so the range is far wider than the arm count, so
* every compiler emits comparisons rather than a table. */
__attribute__((noinline)) int32_t sparse_dispatch(int32_t op, int32_t a,
int32_t b) {
switch (op) {
case 3: return a + b;
case 700: return a - b;
case 60000: return a * 3;
case 900000: return b * 3;
default: return -1;
}
}
/* A dense switch inside a loop, where one arm leaves the loop early. This is
* the shape whose ownership the region structurer gets wrong: the returning arm
* makes the function epilogue the immediate post-dominator of every conditional
* inside the loop, so a globally-computed join is outside the loop body. */
__attribute__((noinline)) int32_t dispatch_in_loop(const uint8_t *ops,
int32_t count) {
int32_t acc = 0;
if (ops == 0 || count < 0 || count > 64) {
return -1;
}
for (int32_t i = 0; i < count; i++) {
switch (ops[i] & 7) {
case 0: acc += 1; break;
case 1: acc += 2; break;
case 2: acc += 4; break;
case 3: return acc; /* the returning arm */
case 4: acc -= 1; break;
case 5: acc -= 2; break;
case 6: acc ^= 0x55; break;
default: acc = 0; break;
}
}
return acc;
} 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
2/3dense_dispatch pass 39 lines
// glaurung: dense_dispatch @ 0x1100
int32_t dense_dispatch(int32_t arg0, int32_t arg1, int32_t arg2) {
switch ((unsigned long)((unsigned int)(arg0))) {
case 0:
return (unsigned int)(((unsigned long)((unsigned int)(arg2)) + arg1));
case 1:
return (unsigned int)((arg1 - (unsigned long)((unsigned int)(arg2))));
case 2:
return (unsigned int)(((unsigned long)((unsigned int)(arg2)) + (unsigned long)((unsigned int)((arg1 + (arg1 * 2))))));
case 3:
return (unsigned int)(((unsigned long)((unsigned int)(arg2)) ^ arg1));
case 4:
return (unsigned int)(((unsigned long)((unsigned int)(arg2)) | arg1));
case 5:
return (unsigned int)(((unsigned long)((unsigned int)(arg2)) & arg1));
case 6:
return (unsigned int)(((unsigned long)((unsigned int)(((int)(arg1) >> 1))) + (unsigned long)((unsigned int)(arg2))));
case 7:
return (unsigned int)(((unsigned long)((unsigned int)((arg1 + arg1))) - (unsigned long)((unsigned int)(arg2))));
case 8:
return (unsigned int)((arg1 + 100));
case 9:
return (unsigned int)(((unsigned long)((unsigned int)(arg2)) + 200));
case 10:
return (unsigned int)((arg1 - 300));
case 11:
return (unsigned int)(((unsigned long)((unsigned int)(arg2)) - 400));
case 12:
return (unsigned int)((arg1 + (arg1 * 4)));
case 13:
return (unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg2)) * 8))) - (unsigned long)((unsigned int)(arg2))));
case 14:
return (unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg2)) + arg1))) + 11));
case 15:
return (unsigned int)(((unsigned long)((unsigned int)((arg1 - (unsigned long)((unsigned int)(arg2))))) - 13));
default:
return 0xffffffff;
}
} dispatch_in_loop fail 42 lines
// glaurung: dispatch_in_loop @ 0x11c0
int32_t dispatch_in_loop(const uint8_t * arg0, int32_t arg1) {
int i;
int acc;
long ret;
long var1;
long var10;
long var12;
long var17;
long var4;
long var8;
ret = 0xffffffff;
if ((arg0 == 0)) {
return ret;
}
ret = 0xffffffff;
if (((unsigned long)(64) < (unsigned long)((unsigned long)((unsigned int)(arg1))))) {
return ret;
}
if (((unsigned long)((unsigned int)(arg1)) == 0)) {
return 0;
}
var1 = (unsigned long)((unsigned int)(arg1));
var4 = 0x2040;
var8 = 0;
goto L_11fd;
var12 = (unsigned long)((unsigned int)(var10));
L_11f2: ;
i = (var8 + 1);
var8 = (unsigned long)((unsigned int)(i));
ret = (unsigned long)((unsigned int)(var12));
if ((var1 == i)) {
return ret;
}
L_11fd: ;
var17 = (unsigned long)((unsigned int)(((unsigned int)((unsigned char)(*(char *)(((long)arg0 + var8)))) & 7)));
var12 = 0;
if (((unsigned long)(6) < (unsigned long)((unsigned long)((unsigned int)(var17))))) {
goto L_11f2;
}
/* unrecovered indirect jump through ((long)((int)(*(int *)((var4 + var17 * 4)))) + var4) */
} sparse_dispatch pass 21 lines
// glaurung: sparse_dispatch @ 0x1180
int32_t sparse_dispatch(int32_t arg0, int32_t arg1, int32_t arg2) {
long ret;
ret = 0xffffffff;
if (((((unsigned long)((unsigned int)(arg0)) == 0xea5f) | ((long)(arg0) < 0xea5f)) != 0)) {
if (((unsigned long)((unsigned int)(arg0)) == 3)) {
return (unsigned int)((arg2 + arg1));
}
if (((unsigned long)((unsigned int)(arg0)) != 700)) {
return ret;
}
return (unsigned int)((arg1 - arg2));
}
if (((unsigned long)((unsigned int)(arg0)) == 0xea60)) {
return (unsigned int)((arg1 + (arg1 * 2)));
}
if (((unsigned long)((unsigned int)(arg0)) != 0xdbba0)) {
return ret;
}
return (unsigned int)((arg2 + (arg2 * 2)));
} gcc -O2
2/3dense_dispatch pass 39 lines
// glaurung: dense_dispatch @ 0x1110
int32_t dense_dispatch(int32_t arg0, int32_t arg1, int32_t arg2) {
switch ((unsigned long)((unsigned int)(arg0))) {
case 0:
return (unsigned int)((arg1 + arg2));
case 1:
return (unsigned int)(((unsigned long)((unsigned int)(arg1)) - arg2));
case 2:
return (unsigned int)(((unsigned long)((unsigned int)((arg1 + (arg1 * 2)))) + arg2));
case 3:
return (unsigned int)(((unsigned long)((unsigned int)(arg1)) ^ arg2));
case 4:
return (unsigned int)(((unsigned long)((unsigned int)(arg1)) | arg2));
case 5:
return (unsigned int)(((unsigned long)((unsigned int)(arg1)) & arg2));
case 6:
return (unsigned int)(((unsigned long)((unsigned int)(((int)(arg1) >> 1))) + arg2));
case 7:
return (unsigned int)(((unsigned long)((unsigned int)((arg1 + arg1))) - arg2));
case 8:
return (unsigned int)((arg1 + 100));
case 9:
return (unsigned int)((arg2 + 200));
case 10:
return (unsigned int)((arg1 - 300));
case 11:
return (unsigned int)((arg2 - 400));
case 12:
return (unsigned int)((arg1 + (arg1 * 4)));
case 13:
return (unsigned int)(((unsigned long)((unsigned int)((arg2 * 8))) - arg2));
case 14:
return (unsigned int)(((arg1 + arg2) + 11));
case 15:
return (unsigned int)(((unsigned long)((unsigned int)((arg1 - arg2))) - 13));
default:
return (unsigned int)(-1);
}
} dispatch_in_loop fail 48 lines
// glaurung: dispatch_in_loop @ 0x1230
int32_t dispatch_in_loop(const uint8_t * arg0, int32_t arg1) {
int acc;
int i;
long var1;
long var11;
long var15;
long var5;
long var6;
long var8;
if ((arg0 == 0)) {
goto L_12db;
}
goto L_123d;
L_1044: ;
var1 = 0;
goto L_1288;
L_123d: ;
if (((unsigned long)(64) < (unsigned long)((unsigned long)((unsigned int)(arg1))))) {
goto L_12db;
}
if (((unsigned long)((unsigned int)(arg1)) == 0)) {
goto L_12d6;
}
var5 = 0x2040;
var6 = (long)((((long)arg0 + (unsigned long)((unsigned int)((arg1 - 1)))) + 1));
var8 = (long)arg0;
L_1260: ;
var11 = (unsigned long)((unsigned int)(((unsigned int)((unsigned char)(*(char *)((var8)))) & 7)));
if (((unsigned long)(6) < (unsigned long)((unsigned long)((unsigned char)((var11 & 255)))))) {
goto L_1044;
}
/* unrecovered indirect jump through ((long)((int)(*(int *)((var5 + ((unsigned int)((unsigned char)((var11 & 255))) * 4))))) + var5) */
L_1288: ;
var8 = (var8 + 1);
var15 = var1;
if ((var8 != var6)) {
goto L_1260;
}
L_1291: ;
return (unsigned int)(var15);
L_12d6: ;
var15 = 0;
goto L_1291;
L_12db: ;
var15 = 0xffffffff;
goto L_1291;
} sparse_dispatch pass 20 lines
// glaurung: sparse_dispatch @ 0x11e0
int32_t sparse_dispatch(int32_t arg0, int32_t arg1, int32_t arg2) {
long ret;
long t33;
t33 = ((unsigned long)((unsigned int)(arg0)) - 0xea60);
if (((unsigned long)((unsigned int)(arg0)) == 0xea60)) {
return (unsigned int)((arg1 + (arg1 * 2)));
}
if (((((unsigned long)((unsigned int)(arg0)) == 0xea60) | ((long)(arg0) < 0xea60)) == 0)) {
return (((unsigned long)((unsigned int)(arg0)) == 0xdbba0) ? (unsigned long)((unsigned int)((arg2 + (arg2 * 2)))) : 0xffffffff);
}
ret = (unsigned long)((unsigned int)((arg2 + arg1)));
if (((unsigned long)((unsigned int)(arg0)) == 3)) {
return ret;
}
if (((unsigned long)((unsigned int)(arg0)) != 700)) {
return (unsigned int)(-1);
}
return (unsigned int)(((unsigned long)((unsigned int)(arg1)) - arg2));
} clang -O0
3/3dense_dispatch pass 41 lines
// glaurung: dense_dispatch @ 0x1100
int32_t dense_dispatch(int32_t arg0, int32_t arg1, int32_t arg2) {
// x86-64 prologue: save rbp
switch ((unsigned int)(arg0)) {
case 0:
return (unsigned int)(((unsigned int)(arg1) + arg2));
case 1:
return (unsigned int)(((unsigned int)(arg1) - arg2));
case 2:
return (unsigned int)(((arg1 * 3) + arg2));
case 3:
return (unsigned int)(((unsigned int)(arg1) ^ arg2));
case 4:
return (unsigned int)(((unsigned int)(arg1) | arg2));
case 5:
return (unsigned int)(((unsigned int)(arg1) & arg2));
case 6:
return (unsigned int)(((unsigned int)(((int)(arg1) >> 1)) + arg2));
case 7:
return (unsigned int)(((unsigned int)(((unsigned long)((unsigned int)(arg1)) << 1)) - arg2));
case 8:
return (unsigned int)(((unsigned int)(arg1) + 100));
case 9:
return (unsigned int)(((unsigned int)(arg2) + 200));
case 10:
return (unsigned int)(((unsigned int)(arg1) - 300));
case 11:
return (unsigned int)(((unsigned int)(arg2) - 400));
case 12:
return (unsigned int)((arg1 * 5));
case 13:
return (unsigned int)((arg2 * 7));
case 14:
return (unsigned int)(((unsigned int)(((unsigned long)((unsigned int)(arg1)) + arg2)) + 11));
case 15:
return (unsigned int)(((unsigned int)(((unsigned long)((unsigned int)(arg1)) - arg2)) - 13));
default:
return (unsigned int)(-1);
}
// x86-64 epilogue: restore rbp
} dispatch_in_loop pass 63 lines
// glaurung: dispatch_in_loop @ 0x12d0
int32_t dispatch_in_loop(const uint8_t * arg0, int32_t arg1) {
int acc;
int i;
long local_28;
int local_4;
long var5;
// x86-64 prologue: save rbp
acc = 0;
if ((arg0 == 0)) {
local_4 = -1;
// x86-64 epilogue: restore rbp
return (unsigned int)(local_4);
}
if (((long)(arg1) < 0)) {
local_4 = -1;
// x86-64 epilogue: restore rbp
return (unsigned int)(local_4);
}
if (((((unsigned long)((unsigned int)(arg1)) == 64) | ((long)(arg1) < 64)) == 0)) {
local_4 = -1;
// x86-64 epilogue: restore rbp
return (unsigned int)(local_4);
}
i = 0;
while ((i < arg1)) {
var5 = (unsigned long)((unsigned int)(((unsigned int)((unsigned char)(arg0[i])) & 7)));
local_28 = (unsigned int)(var5);
if (((((unsigned long)((unsigned long)((unsigned int)(var5))) < (unsigned long)(6)) | ((unsigned long)((unsigned int)((var5 - 6))) == 0)) == 0)) {
acc = 0;
L_13b8: ;
i = ((unsigned int)(i) + 1);
} else {
switch (local_28) {
case 0:
acc = ((unsigned int)(acc) + 1);
goto L_13b8;
case 1:
acc = ((unsigned int)(acc) + 2);
goto L_13b8;
case 2:
acc = ((unsigned int)(acc) + 4);
goto L_13b8;
case 3:
local_4 = acc;
// x86-64 epilogue: restore rbp
return (unsigned int)(local_4);
case 4:
acc = ((unsigned int)(acc) - 1);
goto L_13b8;
case 5:
acc = ((unsigned int)(acc) - 2);
goto L_13b8;
case 6:
acc = ((unsigned int)(acc) ^ 85);
goto L_13b8;
}
}
}
local_4 = acc;
// x86-64 epilogue: restore rbp
return (unsigned int)(local_4);
} sparse_dispatch pass 21 lines
// glaurung: sparse_dispatch @ 0x1230
int32_t sparse_dispatch(int32_t arg0, int32_t arg1, int32_t arg2) {
// x86-64 prologue: save rbp
if (((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg0)) - 3))) == 0)) {
return (unsigned int)(((unsigned long)((unsigned int)(arg1)) + arg2));
} else {
if (((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg0)) - 700))) == 0)) {
return (unsigned int)(((unsigned long)((unsigned int)(arg1)) - arg2));
} else {
if (((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg0)) - 0xea60))) == 0)) {
return (unsigned int)((arg1 * 3));
} else {
if (((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg0)) - 0xdbba0))) == 0)) {
return (unsigned int)((arg2 * 3));
} else {
return (unsigned int)(-1);
}
}
}
}
} gcc -O0
3/3dense_dispatch pass 41 lines
// glaurung: dense_dispatch @ 0x10f9
int32_t dense_dispatch(int32_t arg0, int32_t arg1, int32_t arg2) {
// x86-64 prologue: save rbp
switch ((unsigned long)((unsigned int)(arg0))) {
case 0:
return (unsigned int)(((unsigned long)((unsigned int)(arg2)) + (unsigned long)((unsigned int)(arg1))));
case 1:
return (unsigned int)(((unsigned long)((unsigned int)(arg1)) - arg2));
case 2:
return (unsigned int)(((unsigned long)((unsigned int)(arg2)) + (unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg1)) + (unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg1)) + (unsigned long)((unsigned int)(arg1))))))))));
case 3:
return (unsigned int)(((unsigned long)((unsigned int)(arg1)) ^ arg2));
case 4:
return (unsigned int)(((unsigned long)((unsigned int)(arg1)) | arg2));
case 5:
return (unsigned int)(((unsigned long)((unsigned int)(arg1)) & arg2));
case 6:
return (unsigned int)(((unsigned long)((unsigned int)(arg2)) + (unsigned long)((unsigned int)(((int)(arg1) >> 1)))));
case 7:
return (unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg1)) + (unsigned long)((unsigned int)(arg1))))) - arg2));
case 8:
return (unsigned int)(((unsigned long)((unsigned int)(arg1)) + 100));
case 9:
return (unsigned int)(((unsigned long)((unsigned int)(arg2)) + 200));
case 10:
return (unsigned int)(((unsigned long)((unsigned int)(arg1)) - 300));
case 11:
return (unsigned int)(((unsigned long)((unsigned int)(arg2)) - 400));
case 12:
return (unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg1)) << 2))) + (unsigned long)((unsigned int)(arg1))));
case 13:
return (unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg2)) << 3))) - (unsigned long)((unsigned int)(arg2))));
case 14:
return (unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg2)) + (unsigned long)((unsigned int)(arg1))))) + 11));
case 15:
return (unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg1)) - arg2))) - 13));
default:
return 0xffffffff;
}
// x86-64 epilogue: restore rbp
} dispatch_in_loop pass 52 lines
// glaurung: dispatch_in_loop @ 0x1268
int32_t dispatch_in_loop(const uint8_t * arg0, int32_t arg1) {
int acc;
int i;
// x86-64 prologue: save rbp
acc = 0;
if ((arg0 == 0)) {
// x86-64 epilogue: restore rbp
return 0xffffffff;
}
if (((long)(arg1) < 0)) {
// x86-64 epilogue: restore rbp
return 0xffffffff;
}
if (((((unsigned long)((unsigned int)(arg1)) == 64) | ((long)(arg1) < 64)) == 0)) {
// x86-64 epilogue: restore rbp
return 0xffffffff;
}
i = 0;
while ((i < arg1)) {
switch ((unsigned long)((unsigned int)(((unsigned int)((unsigned char)(((unsigned int)((unsigned char)(arg0[i])) & 255))) & 7)))) {
case 0:
acc = (acc + 1);
goto L_1313;
case 1:
acc = (acc + 2);
goto L_1313;
case 2:
acc = (acc + 4);
goto L_1313;
case 3:
// x86-64 epilogue: restore rbp
return (unsigned int)(acc);
case 4:
acc = (acc - 1);
goto L_1313;
case 5:
acc = (acc - 2);
goto L_1313;
case 6:
acc = (acc ^ 85);
goto L_1313;
default:
acc = 0;
L_1313: ;
i = (i + 1);
break;
}
}
// x86-64 epilogue: restore rbp
return (unsigned int)(acc);
} sparse_dispatch pass 18 lines
// glaurung: sparse_dispatch @ 0x11f3
int32_t sparse_dispatch(int32_t arg0, int32_t arg1, int32_t arg2) {
// x86-64 prologue: save rbp
switch (arg0) {
case 900000:
return (unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg2)) + (unsigned long)((unsigned int)(arg2))))) + (unsigned long)((unsigned int)(arg2))));
case 60000:
return (unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg1)) + (unsigned long)((unsigned int)(arg1))))) + (unsigned long)((unsigned int)(arg1))));
case 3:
return (unsigned int)(((unsigned long)((unsigned int)(arg2)) + (unsigned long)((unsigned int)(arg1))));
case 700:
return (unsigned int)(((unsigned long)((unsigned int)(arg1)) - arg2));
default:
// x86-64 epilogue: restore rbp
return 0xffffffff;
}
// x86-64 epilogue: restore rbp
}