Fixture 189
effectful select
C · 5 functions · 4 lanes · 20 of 20 function-lanes behave identically
All 4 lanes recompile and return the same results as the original.
A call with a side effect, sitting inside a conditional select.
lazy_call_select and copy_prop::move_adjacent_effectful_scratch_values fold a diamond into a ternary. That is only sound if the folded call is evaluated exactly as often as the machine evaluated it. A fold that hoists the call above the condition, or that duplicates it into both arms, still produces plausible C with the right RETURN VALUE — the arithmetic is unchanged — and is caught only by counting the effect.
This is the same soundness class as the vector-transport bug in 188: one value with more than one consumer, where the transform assumed exactly one. There the second consumer read undefined lanes; here the effect count moves.
The count is kept in the caller's own buffer rather than a global, so it survives the harness rebuilding a single function against extern callees, and so the differential observes it directly.
se189_select_pure is the control: no call in either arm, so folding is entirely correct and must still happen. A fixture that only forbids folding would be satisfied by a decompiler that never folds anything.
#include <stdint.h>
/* A call with a side effect, sitting inside a conditional select.
*
* `lazy_call_select` and `copy_prop::move_adjacent_effectful_scratch_values`
* fold a diamond into a ternary. That is only sound if the folded call is
* evaluated exactly as often as the machine evaluated it. A fold that hoists
* the call above the condition, or that duplicates it into both arms, still
* produces plausible C with the right RETURN VALUE — the arithmetic is
* unchanged — and is caught only by counting the effect.
*
* This is the same soundness class as the vector-transport bug in `188`: one
* value with more than one consumer, where the transform assumed exactly one.
* There the second consumer read undefined lanes; here the effect count moves.
*
* The count is kept in the caller's own buffer rather than a global, so it
* survives the harness rebuilding a single function against extern callees,
* and so the differential observes it directly.
*
* `se189_select_pure` is the control: no call in either arm, so folding is
* entirely correct and must still happen. A fixture that only forbids folding
* would be satisfied by a decompiler that never folds anything. */
#define SE189_SLOT_SELECTED 1
#define SE189_SLOT_WITNESS 2
__attribute__((noinline)) int32_t se189_bump(int32_t *calls, int32_t value) {
if (calls == 0) {
return -1;
}
calls[0] += 1;
return value * 2 + 1;
}
__attribute__((noinline)) int32_t se189_select_call(int32_t *scratch, int32_t flag,
int32_t a, int32_t b) {
int32_t selected;
if (scratch == 0) {
return -1;
}
scratch[0] = 0;
/* Exactly one of these calls happens. If a fold evaluates both, or hoists
* one above the branch, scratch[0] becomes 2 and the differential fails
* even though the returned value is identical. */
selected = flag ? se189_bump(scratch, a) : se189_bump(scratch, b);
scratch[SE189_SLOT_SELECTED] = selected;
scratch[SE189_SLOT_WITNESS] = scratch[0];
return selected;
}
__attribute__((noinline)) int32_t se189_select_one_arm(int32_t *scratch, int32_t flag,
int32_t a, int32_t b) {
int32_t selected;
if (scratch == 0) {
return -1;
}
scratch[0] = 0;
/* Asymmetric: only the taken arm may call. Speculating the call so both
* arms share one evaluation changes the count on the `b` path. */
selected = flag ? se189_bump(scratch, a) : b;
scratch[SE189_SLOT_SELECTED] = selected;
scratch[SE189_SLOT_WITNESS] = scratch[0];
return selected;
}
__attribute__((noinline)) int32_t se189_nested_select(int32_t *scratch, int32_t first,
int32_t second, int32_t a,
int32_t b) {
int32_t selected;
if (scratch == 0) {
return -1;
}
scratch[0] = 0;
/* Nested diamonds: at most one call on any path through both conditions. */
selected = first ? (second ? se189_bump(scratch, a) : a) : (second ? b : se189_bump(scratch, b));
scratch[SE189_SLOT_SELECTED] = selected;
scratch[SE189_SLOT_WITNESS] = scratch[0];
return selected;
}
__attribute__((noinline)) int32_t se189_select_pure(int32_t *scratch, int32_t flag,
int32_t a, int32_t b) {
int32_t selected;
if (scratch == 0) {
return -1;
}
scratch[0] = 0;
/* CONTROL: no call in either arm, so this diamond SHOULD fold to a ternary.
* Present so the fixture cannot be satisfied by refusing to fold at all. */
selected = flag ? (a * 2 + 1) : (b * 2 + 1);
scratch[SE189_SLOT_SELECTED] = selected;
scratch[SE189_SLOT_WITNESS] = scratch[0];
return selected;
} 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
5/5se189_bump pass 10 lines
// glaurung: se189_bump @ 0x1110
int32_t se189_bump(int32_t * arg0, int32_t arg1) {
// x86-64 prologue: save rbp
if ((arg0 != 0)) {
*(int *)((long)arg0) = ((unsigned long)((unsigned int)(*(int *)((long)arg0))) + 1);
return (unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg1)) << 1))) + 1));
} else {
return (unsigned int)(-1);
}
} se189_nested_select pass 37 lines
// glaurung: se189_nested_select @ 0x1270
int32_t se189_nested_select(int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4) {
extern int se189_bump(int *, int);
int selected;
int local_28;
int local_2c;
int local_30;
int var1;
int var6;
// x86-64 prologue: save rbp, frame 48 bytes
if ((arg0 != 0)) {
*(int *)((long)arg0) = 0;
if (((unsigned long)((unsigned int)(arg1)) == 0)) {
if (((unsigned long)((unsigned int)(arg2)) == 0)) {
var1 = se189_bump((int *)(arg0), (unsigned long)((unsigned int)(arg4)));
local_30 = var1;
} else {
local_30 = arg4;
}
local_2c = local_30;
} else {
if (((unsigned long)((unsigned int)(arg2)) == 0)) {
local_28 = arg3;
} else {
var6 = se189_bump((int *)(arg0), (unsigned long)((unsigned int)(arg3)));
local_28 = var6;
}
local_2c = local_28;
}
selected = local_2c;
*(int *)(((long)arg0 + 0x4)) = selected;
*(int *)(((long)arg0 + 0x8)) = *(int *)((long)arg0);
return (unsigned int)(selected);
} else {
return (unsigned int)(-1);
}
} se189_select_call pass 25 lines
// glaurung: se189_select_call @ 0x1150
int32_t se189_select_call(int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
extern int se189_bump(int *, int);
int selected;
int local_24;
int var1;
int var3;
// x86-64 prologue: save rbp, frame 48 bytes
if ((arg0 != 0)) {
*(int *)((long)arg0) = 0;
if (((unsigned long)((unsigned int)(arg1)) == 0)) {
var1 = se189_bump((int *)(arg0), (unsigned long)((unsigned int)(arg3)));
local_24 = var1;
} else {
var3 = se189_bump((int *)(arg0), (unsigned long)((unsigned int)(arg2)));
local_24 = var3;
}
selected = local_24;
*(int *)(((long)arg0 + 0x4)) = selected;
*(int *)(((long)arg0 + 0x8)) = *(int *)((long)arg0);
return (unsigned int)(selected);
} else {
return (unsigned int)(-1);
}
} se189_select_one_arm pass 23 lines
// glaurung: se189_select_one_arm @ 0x11e0
int32_t se189_select_one_arm(int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
extern int se189_bump(int *, int);
int selected;
int local_24;
int var2;
// x86-64 prologue: save rbp, frame 48 bytes
if ((arg0 != 0)) {
*(int *)((long)arg0) = 0;
if (((unsigned long)((unsigned int)(arg1)) == 0)) {
local_24 = arg3;
} else {
var2 = se189_bump((int *)(arg0), (unsigned long)((unsigned int)(arg2)));
local_24 = var2;
}
selected = local_24;
*(int *)(((long)arg0 + 0x4)) = selected;
*(int *)(((long)arg0 + 0x8)) = *(int *)((long)arg0);
return (unsigned int)(selected);
} else {
return (unsigned int)(-1);
}
} se189_select_pure pass 16 lines
// glaurung: se189_select_pure @ 0x1340
int32_t se189_select_pure(int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
int selected;
int local_24;
// x86-64 prologue: save rbp
if ((arg0 != 0)) {
*(int *)((long)arg0) = 0;
local_24 = (((unsigned long)((unsigned int)(arg1)) == 0) ? ((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg3)) << 1))) + 1) : ((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg2)) << 1))) + 1));
selected = local_24;
*(int *)(((long)arg0 + 0x4)) = selected;
*(int *)(((long)arg0 + 0x8)) = *(int *)((long)arg0);
return (unsigned int)(selected);
} else {
return (unsigned int)(-1);
}
} clang -O2
5/5se189_bump pass 8 lines
// glaurung: se189_bump @ 0x1110
int32_t se189_bump(int32_t * arg0, int32_t arg1) {
if ((arg0 == 0)) {
return 0xffffffff;
}
*(int *)(((long)arg0)) = (*(int *)(((long)arg0)) + 1);
return (unsigned int)(((unsigned long)((unsigned int)((arg1 + arg1))) + 1));
} se189_nested_select pass 38 lines
// glaurung: se189_nested_select @ 0x11a0
int32_t se189_nested_select(int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4) {
extern int se189_bump(int *, int);
int selected;
long var0;
long var1;
int var2;
if ((arg0 == 0)) {
goto L_11cd;
}
var0 = (long)arg0;
*(int *)(((long)arg0)) = 0;
if (((unsigned long)((unsigned int)(arg1)) == 0)) {
goto L_11d3;
}
var1 = (unsigned long)((unsigned int)(arg3));
selected = (unsigned long)((unsigned int)(arg3));
if (((unsigned long)((unsigned int)(arg2)) == 0)) {
goto L_11c3;
}
L_11b9: ;
var2 = se189_bump((int *)(var0), (unsigned long)((unsigned int)(var1)));
selected = (unsigned long)((unsigned int)(var2));
L_11c3: ;
*(int *)((var0 + 0x4)) = selected;
*(int *)((var0 + 0x8)) = *(int *)((var0));
// x86-64 epilogue: tear down frame
return selected;
L_11cd: ;
return 0xffffffff;
L_11d3: ;
var1 = (unsigned long)((unsigned int)(arg4));
selected = (unsigned long)((unsigned int)(arg4));
if (((unsigned long)((unsigned int)(arg2)) != 0)) {
goto L_11c3;
}
goto L_11b9;
} se189_select_call pass 17 lines
// glaurung: se189_select_call @ 0x1130
int32_t se189_select_call(int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
extern int se189_bump(int *, int);
int selected;
int ret;
long var0;
if ((arg0 == 0)) {
return 0xffffffff;
}
var0 = (long)arg0;
*(int *)(((long)arg0)) = 0;
ret = se189_bump((int *)(arg0), (unsigned long)((unsigned int)((((unsigned long)((unsigned int)(arg1)) == 0) ? arg3 : arg2))));
*(int *)((var0 + 0x4)) = ret;
*(int *)((var0 + 0x8)) = *(int *)((var0));
// x86-64 epilogue: tear down frame
return ret;
} se189_select_one_arm pass 25 lines
// glaurung: se189_select_one_arm @ 0x1160
int32_t se189_select_one_arm(int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
extern int se189_bump(int *, int);
int selected;
long var0;
long var3;
int var4;
if ((arg0 == 0)) {
return 0xffffffff;
}
var0 = (long)arg0;
*(int *)(((long)arg0)) = 0;
if (((unsigned long)((unsigned int)(arg1)) == 0)) {
var3 = 0;
selected = (unsigned long)((unsigned int)(arg3));
} else {
var4 = se189_bump((int *)(var0), (unsigned long)((unsigned int)(arg2)));
var3 = (unsigned long)((unsigned int)(*(int *)((var0))));
selected = (unsigned long)((unsigned int)(var4));
}
*(int *)((var0 + 0x4)) = selected;
*(int *)((var0 + 0x8)) = var3;
// x86-64 epilogue: tear down frame
return selected;
} se189_select_pure pass 15 lines
// glaurung: se189_select_pure @ 0x11e0
int32_t se189_select_pure(int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
int selected;
long var0;
int var2;
if ((arg0 == 0)) {
return 0xffffffff;
}
var0 = (((unsigned long)((unsigned int)(arg1)) == 0) ? arg3 : arg2);
*(int *)(((long)arg0)) = 0;
var2 = ((unsigned int)((var0 + var0)) + 1);
*(int *)(((long)arg0 + 0x4)) = var2;
*(int *)(((long)arg0 + 0x8)) = 0;
return (unsigned int)(var2);
} gcc -O0
5/5se189_bump pass 10 lines
// glaurung: se189_bump @ 0x1119
int32_t se189_bump(int32_t * arg0, int32_t arg1) {
// x86-64 prologue: save rbp
if ((arg0 != 0)) {
*(int *)((long)arg0) = ((unsigned long)((unsigned int)(*(int *)((long)arg0))) + 1);
return (unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg1)) + (unsigned long)((unsigned int)(arg1))))) + 1));
} else {
return 0xffffffff;
}
} se189_nested_select pass 33 lines
// glaurung: se189_nested_select @ 0x1241
int32_t se189_nested_select(int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4) {
extern int se189_bump(int *, int);
int selected;
long var1;
int var4;
int var7;
// x86-64 prologue: save rbp, frame 48 bytes
if ((arg0 != 0)) {
*(int *)((long)arg0) = 0;
if (((unsigned long)((unsigned int)(arg1)) == 0)) {
if (((unsigned long)((unsigned int)(arg2)) != 0)) {
var1 = (unsigned long)((unsigned int)(arg4));
} else {
var4 = se189_bump((int *)(arg0), (unsigned long)((unsigned int)(arg4)));
var1 = (unsigned long)((unsigned int)(var4));
}
} else {
if (((unsigned long)((unsigned int)(arg2)) == 0)) {
var1 = (unsigned long)((unsigned int)(arg3));
} else {
var7 = se189_bump((int *)(arg0), (unsigned long)((unsigned int)(arg3)));
var1 = (unsigned long)((unsigned int)(var7));
}
}
selected = var1;
*(int *)((arg0 + 1)) = selected;
*(int *)((arg0 + 2)) = *(int *)((long)arg0);
return (unsigned int)(selected);
} else {
return 0xffffffff;
}
} se189_select_call pass 25 lines
// glaurung: se189_select_call @ 0x114f
int32_t se189_select_call(int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
extern int se189_bump(int *, int);
int selected;
int var3;
long var4;
int var7;
// x86-64 prologue: save rbp, frame 48 bytes
if ((arg0 != 0)) {
*(int *)((long)arg0) = 0;
if (((unsigned long)((unsigned int)(arg1)) == 0)) {
var3 = se189_bump((int *)(arg0), (unsigned long)((unsigned int)(arg3)));
var4 = (unsigned long)((unsigned int)(var3));
} else {
var7 = se189_bump((int *)(arg0), (unsigned long)((unsigned int)(arg2)));
var4 = (unsigned long)((unsigned int)(var7));
}
selected = var4;
*(int *)((arg0 + 1)) = selected;
*(int *)((arg0 + 2)) = *(int *)((long)arg0);
return (unsigned int)(selected);
} else {
return 0xffffffff;
}
} se189_select_one_arm pass 23 lines
// glaurung: se189_select_one_arm @ 0x11cf
int32_t se189_select_one_arm(int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
extern int se189_bump(int *, int);
int selected;
long var1;
int var4;
// x86-64 prologue: save rbp, frame 48 bytes
if ((arg0 != 0)) {
*(int *)((long)arg0) = 0;
if (((unsigned long)((unsigned int)(arg1)) == 0)) {
var1 = (unsigned long)((unsigned int)(arg3));
} else {
var4 = se189_bump((int *)(arg0), (unsigned long)((unsigned int)(arg2)));
var1 = (unsigned long)((unsigned int)(var4));
}
selected = var1;
*(int *)((arg0 + 1)) = selected;
*(int *)((arg0 + 2)) = *(int *)((long)arg0);
return (unsigned int)(selected);
} else {
return 0xffffffff;
}
} se189_select_pure pass 14 lines
// glaurung: se189_select_pure @ 0x12db
int32_t se189_select_pure(int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
int selected;
// x86-64 prologue: save rbp
if ((arg0 != 0)) {
*(int *)((long)arg0) = 0;
selected = (((unsigned long)((unsigned int)(arg1)) == 0) ? (unsigned long)((unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg3)) + (unsigned long)((unsigned int)(arg3))))) + 1))) : (unsigned long)((unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg2)) + (unsigned long)((unsigned int)(arg2))))) + 1))));
*(int *)((arg0 + 1)) = selected;
*(int *)((arg0 + 2)) = *(int *)((long)arg0);
return (unsigned int)(selected);
} else {
return 0xffffffff;
}
} gcc -O2
5/5se189_bump pass 8 lines
// glaurung: se189_bump @ 0x1120
int32_t se189_bump(int32_t * arg0, int32_t arg1) {
if ((arg0 == 0)) {
return 0xffffffff;
}
*(int *)(((long)arg0)) = (*(int *)(((long)arg0)) + 1);
return (unsigned int)(((arg1 + arg1) + 1));
} se189_nested_select pass 50 lines
// glaurung: se189_nested_select @ 0x11d0
int32_t se189_nested_select(int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4) {
extern int se189_bump(int *, int);
int selected;
long local_8;
int ret;
long var0;
long var1;
long var11;
long var3;
long var8;
if ((arg0 == 0)) {
return 0xffffffff;
}
local_8 = var0;
var1 = (long)arg0;
*(int *)(((long)arg0)) = 0;
if (((unsigned long)((unsigned int)(arg1)) != 0)) {
goto L_1200;
}
if (((unsigned long)((unsigned int)(arg2)) == 0)) {
goto L_1220;
}
var3 = 0;
ret = (unsigned long)((unsigned int)(arg4));
L_11f0: ;
*(int *)((var1 + 0x4)) = ret;
*(int *)((var1 + 0x8)) = var3;
// x86-64 epilogue: tear down frame
return ret;
L_1200: ;
ret = (unsigned long)((unsigned int)(arg3));
var3 = (unsigned long)((unsigned int)(arg2));
if (((unsigned long)((unsigned int)(arg2)) == 0)) {
goto L_11f0;
}
ret = se189_bump((int *)(arg0), (unsigned long)((unsigned int)(arg3)));
var8 = (unsigned long)((unsigned int)(*(int *)((var1))));
*(int *)((var1 + 0x4)) = ret;
*(int *)((var1 + 0x8)) = var8;
// x86-64 epilogue: tear down frame
return ret;
L_1220: ;
ret = se189_bump((int *)(arg0), (unsigned long)((unsigned int)(arg4)));
var11 = (unsigned long)((unsigned int)(*(int *)((var1))));
*(int *)((var1 + 0x4)) = ret;
*(int *)((var1 + 0x8)) = var11;
// x86-64 epilogue: tear down frame
return ret;
} se189_select_call pass 31 lines
// glaurung: se189_select_call @ 0x1140
int32_t se189_select_call(int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
extern int se189_bump(int *, int);
int selected;
long local_8;
int ret;
long var0;
long var1;
long var3;
long var6;
if ((arg0 == 0)) {
return 0xffffffff;
}
local_8 = var0;
var1 = (long)arg0;
*(int *)(((long)arg0)) = 0;
if (((unsigned long)((unsigned int)(arg1)) != 0)) {
ret = se189_bump((int *)(arg0), (unsigned long)((unsigned int)(arg2)));
var3 = (unsigned long)((unsigned int)(*(int *)((var1))));
*(int *)((var1 + 0x4)) = ret;
*(int *)((var1 + 0x8)) = var3;
// x86-64 epilogue: tear down frame
return ret;
}
ret = se189_bump((int *)(arg0), (unsigned long)((unsigned int)(arg3)));
var6 = (unsigned long)((unsigned int)(*(int *)((var1))));
*(int *)((var1 + 0x4)) = ret;
*(int *)((var1 + 0x8)) = var6;
// x86-64 epilogue: tear down frame
return ret;
} se189_select_one_arm pass 28 lines
// glaurung: se189_select_one_arm @ 0x1190
int32_t se189_select_one_arm(int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
extern int se189_bump(int *, int);
int selected;
long local_8;
long var0;
long var1;
long var3;
int var5;
local_8 = var0;
var1 = (long)arg0;
if ((arg0 == 0)) {
// x86-64 epilogue: tear down frame
return 0xffffffff;
}
*(int *)((var1)) = 0;
var3 = (unsigned long)((unsigned int)(arg1));
selected = (unsigned long)((unsigned int)(arg3));
if (((unsigned long)((unsigned int)(arg1)) != 0)) {
var5 = se189_bump((int *)(var1), (unsigned long)((unsigned int)(arg2)));
var3 = (unsigned long)((unsigned int)(*(int *)((var1))));
selected = (unsigned long)((unsigned int)(var5));
}
*(int *)((var1 + 0x4)) = selected;
*(int *)((var1 + 0x8)) = var3;
// x86-64 epilogue: tear down frame
return selected;
} se189_select_pure pass 18 lines
// glaurung: se189_select_pure @ 0x1240
int32_t se189_select_pure(int32_t * arg0, int32_t arg1, int32_t arg2, int32_t arg3) {
int selected;
if ((arg0 == 0)) {
return 0xffffffff;
}
*(int *)(((long)arg0)) = 0;
if (((unsigned long)((unsigned int)(arg1)) != 0)) {
selected = (unsigned long)((unsigned int)(((arg2 + arg2) + 1)));
*(int *)(((long)arg0 + 0x8)) = 0;
*(int *)(((long)arg0 + 0x4)) = selected;
return selected;
}
selected = (unsigned long)((unsigned int)(((arg3 + arg3) + 1)));
*(int *)(((long)arg0 + 0x8)) = 0;
*(int *)(((long)arg0 + 0x4)) = selected;
return selected;
}