CCTLib
Calling-context and data-object attribution library for Intel Pin
deadspy_xchg_tn.c
Go to the documentation of this file.
1 // Deadspy ISA edge case: LOCK XCHG.
2 //
3 // This is a TRUE-NEGATIVE test. XCHG r, m is a read-modify-write per
4 // Intel SDM: the memory operand is read into the register while the
5 // register is stored to memory (atomically -- xchg with memory always
6 // implies LOCK).
7 //
8 // Pin classifies XCHG's memory operand as BOTH read and written.
9 // Deadspy inserts the read callback before the write callback at
10 // IPOINT_BEFORE, so on each XCHG the read side sets the shadow to
11 // READ_ACTION before the write side checks it. Two consecutive XCHGs
12 // to the same address therefore produce NO dead write reports:
13 // xchg #1 read -> shadow cleared to READ (kills any prior "written")
14 // xchg #1 write -> shadow was READ, so nothing reported; -> WRITE
15 // xchg #2 read -> shadow cleared to READ (kills xchg #1's "written")
16 // xchg #2 write -> shadow was READ, so nothing reported; -> WRITE
17 //
18 // If deadspy ever loses the read-before-write ordering, or a Pin change
19 // reclassifies XCHG's memory operand as write-only, we'd start seeing
20 // ~8B * WORK_COUNT extra dead bytes here -- the point of the assertion
21 // in DeadspyIsa.XchgIsNotFalselyDead.
22 //
23 // Contrast: a plain MOV pair does NOT read memory before writing, so
24 // two consecutive MOVs to the same address DO produce dead writes.
25 // See deadspy_tp_simple / the sse16/avx32/etc _tp victims for that.
26 //
27 // History: this file was previously deadspy_xchg_tp.c with an inverted
28 // (true-positive) assertion. That assertion passed only because -O0
29 // compilation produced ~320K bytes of dead stack writes to unused local
30 // slots that dominated over the actual (zero) XCHG contribution. At
31 // -O2, GrandTotalDead was baseline (~12K), proving XCHG produces no
32 // dead writes -- exactly what x86 semantics say. Reclassified as _tn.
33 //
34 // Design notes: victim builds at -O0 (ISA_CFLAGS default). To keep the
35 // per-iteration stack traffic from unused locals out of the dead-count
36 // signal, all XCHG operands are file-scope static globals -- no per-
37 // iteration locals are declared in the loop body -- and the loop body
38 // is entirely inline asm with explicit register clobbers.
39 #include <stdint.h>
40 #define WORK_COUNT 20000
41 static volatile uint64_t sink;
42 static uint64_t buf[WORK_COUNT];
43 int main(int argc, char** argv) {
44  (void)argc; (void)argv;
45  // Two register variables that live for the whole loop; no per-iter
46  // stack slot chatter for the compiler to leak into deadspy's count.
47  register uint64_t v1 __asm__("r14") = 0x1111;
48  register uint64_t v2 __asm__("r15") = 0x2222;
49  for (int i = 0; i < WORK_COUNT; ++i) {
50  __asm__ __volatile__(
51  "xchgq %[v1], (%[p])\n\t" // atomic 8B RMW -- read-then-write
52  "xchgq %[v2], (%[p])\n\t" // atomic 8B RMW -- second read clears
53  // first write's shadow marker
54  : [v1] "+r"(v1), [v2] "+r"(v2)
55  : [p] "r"(&buf[i])
56  : "memory");
57  }
58  sink = buf[0] ^ v1 ^ v2;
59  return 0;
60 }
#define WORK_COUNT
int main(int argc, char **argv)
static uint64_t buf[WORK_COUNT]
static volatile uint64_t sink