nx_fnptr_slot_probe.nx source
↩ module page · 82 lines · 3602 B
1// nx_fnptr_slot_probe.nx -- DECISIVE probe: does a struct with MORE THAN ONE function-pointer field
2// dispatch to the right slot?
3//
4// The arity probe showed every struct-field fn-ptr call returning garbage while the same callees
5// invoked directly (or through a let-copied local) return the right answer. Two hypotheses survive:
6// H1 the indirect call-site mangles ARGUMENTS (arity/register related), or
7// H2 the FIELD OFFSETS of fn-ptr-typed struct members collapse, so every vtable slot aliases and
8// a call lands in whichever function was assigned last.
9// H2 would be far more serious: it silently breaks every multi-method vtable in the tree.
10//
11// This probe discriminates them with ZERO argument passing. Five nullary functions, each returning a
12// unique constant, stored in five separate fields. If each slot returns its own constant, offsets are
13// fine and the fault is in argument passing. If they all return the SAME constant, the fields alias.
14// license_tier: ORIGINAL
15import "syscalls.nx"
16
17func q_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
18func q_num(v: i64) -> i64 { let bb: *u8 = sys_mmap(28); var m: i64 = v; if m < 0 { m = 0 - m; sys_write(1, "-" as *u8, 1) }; let t: *u8 = sys_mmap(28); var k: i64 = 0; if m == 0 { t[0] = (48 as u8); k = 1 }; while m > 0 { t[k] = ((48 + (m % 10)) as u8); m = m / 10; k = k + 1 }; var i: i64 = 0; while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }; sys_write(1, bb, k); return 0 }
19
20func ret11() -> i64 { return 11 }
21func ret22() -> i64 { return 22 }
22func ret33() -> i64 { return 33 }
23func ret44() -> i64 { return 44 }
24func ret55() -> i64 { return 55 }
25
26struct Slots {
27 head: i64,
28 a: func() -> i64,
29 b: func() -> i64,
30 c: func() -> i64,
31 d: func() -> i64,
32 e: func() -> i64,
33 tail: i64,
34}
35const SLOTS_BYTES: i64 = 56
36
37func main() -> i64 {
38 let s: *Slots = sys_mmap(SLOTS_BYTES) as *Slots
39 s.head = 0x1111
40 s.a = ret11
41 s.b = ret22
42 s.c = ret33
43 s.d = ret44
44 s.e = ret55
45 s.tail = 0x2222
46
47 let ra: i64 = s.a()
48 let rb: i64 = s.b()
49 let rc: i64 = s.c()
50 let rd: i64 = s.d()
51 let re: i64 = s.e()
52
53 q_puts("slot a got=" as *u8); q_num(ra); q_puts(" want=11\n" as *u8)
54 q_puts("slot b got=" as *u8); q_num(rb); q_puts(" want=22\n" as *u8)
55 q_puts("slot c got=" as *u8); q_num(rc); q_puts(" want=33\n" as *u8)
56 q_puts("slot d got=" as *u8); q_num(rd); q_puts(" want=44\n" as *u8)
57 q_puts("slot e got=" as *u8); q_num(re); q_puts(" want=55\n" as *u8)
58
59 // guard fields: if fn-ptr fields are sized 0, `tail` overlaps them and head/tail read wrong too
60 q_puts("head got=" as *u8); q_num(s.head); q_puts(" want=4369\n" as *u8)
61 q_puts("tail got=" as *u8); q_num(s.tail); q_puts(" want=8738\n" as *u8)
62
63 // let-copy control at nullary arity
64 let fa: func() -> i64 = s.a
65 let fe: func() -> i64 = s.e
66 q_puts("letcopy a got=" as *u8); q_num(fa()); q_puts(" want=11\n" as *u8)
67 q_puts("letcopy e got=" as *u8); q_num(fe()); q_puts(" want=55\n" as *u8)
68
69 var bad: i64 = 0
70 if ra != 11 { bad = bad + 1 }
71 if rb != 22 { bad = bad + 1 }
72 if rc != 33 { bad = bad + 1 }
73 if rd != 44 { bad = bad + 1 }
74 if re != 55 { bad = bad + 1 }
75 if s.head != 0x1111 { bad = bad + 1 }
76 if s.tail != 0x2222 { bad = bad + 1 }
77 q_puts("FNPTR-SLOT-PROBE wrong=" as *u8); q_num(bad); q_puts("\n" as *u8)
78 if bad == 0 { q_puts("FNPTR-SLOT-PROBE verdict=GREEN\n" as *u8); sys_exit(0); return 0 }
79 q_puts("FNPTR-SLOT-PROBE verdict=RED\n" as *u8)
80 sys_exit(1)
81 return 1
82}