code wiki / (root) / nx_fnptr_slot_probe.nx

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}