// fnptr_derefless_call_test — a deref-less call through a `*fn` pointer // (C6a, task #7). `f(21)` where `f: *fn(...)...` autodereferences one // pointer level to the fn before the call, exactly like the canonical // `(*f)(21)`. wwstage already accepted this (it mirrors harec's // check_autodereference, ref/harec/src/check.c:1566); cstage's N_CALL // checker arm peeled only TY_NAMED and rejected the `*fn` callee // ("calling non-function"), the under-permissive cs!=ww side. The fix // peels ONE pointer level in cmd/wcc/check.c so BOTH stages accept it and // emit byte-identical asm (rule-10) — cgen lowers a bare fn-ptr value as // the call target (#181), identical to the deref form. // // REVERT MANIFESTATION: revert the cstage checker peel and the // deref-less rows below no longer COMPILE under cstage ("calling // non-function"), so this whole file fails to build in the cstage `ww // test` / byte-id legs — a hard red, not a silent value drift. // // The #181-cgen path uses the callee VALUE as the call target, so it // autodereferences exactly ONE level: a `*fn` callee is the fn address, but // a `**fn` callee is only the *address of* the fn-ptr. So a deref-less // `**fn` call (`let pf: **fn(...) = &f; pf(21)`) drops a `MOVQ (AX),AX` and // is a hard reject in BOTH stages — cstage at cmd/wcc/check.c:1947 (one-level // peel), wwstage aligned DOWN at selfhost/cmd/wcc/check.ww (task #14, was the // SILENT-miscompile side). That loud reject is pinned in // test/wcc/data/fnptr_pp_derefless_reject. The LEGAL one-explicit-deref // `(*pf)(21)` (which yields a `*fn`, then autoderefs the remaining single // level) is covered by derefless_pp_one_explicit_deref below — the shape the // one-level peel must NOT over-reject. Multi-level fn-ptr autoderef is a // deferred FEATURE (#181); the explicit `(**pf)(21)` is correct (cgexpr emits // both loads). package fnptr_derefless_call_test; fn add1(x: i32) i32 = { return x + 21; }; fn many(a: i32, b: i32, p: *i32) i32 = { return a + b + *p; }; fn pair(x: i32) (i32, i32) = { return (x, x + 1); }; @test fn derefless_minimal() void = { // the bug repro: `f(21)` through a `*fn` callee, no explicit deref. let f: *fn(x: i32) i32 = &add1; assert(f(21) == 42); }; @test fn deref_form_same_value() void = { // the canonical `(*f)(21)` must agree — both spellings call the same fn. let f: *fn(x: i32) i32 = &add1; assert((*f)(21) == 42); assert(f(21) == (*f)(21)); }; @test fn derefless_multi_arg() void = { // multiple args, scalar + ptr mix, through a deref-less callee. let z: i32 = 5; let f: *fn(a: i32, b: i32, p: *i32) i32 = &many; assert(f(3, 7, &z) == 15); }; @test fn derefless_alias_chain() void = { // the fn-ptr value survives a plain copy and still calls deref-less. let f: *fn(x: i32) i32 = &add1; let g: *fn(x: i32) i32 = f; assert(g(21) == 42); }; @test fn derefless_tuple_return() void = { // multi-register return ABI through the deref-less call shape. let f: *fn(x: i32) (i32, i32) = &pair; let a, b = f(7); assert(a + b == 15); }; @test fn derefless_pp_one_explicit_deref() void = { // #14 positive guard: a `**fn` with ONE explicit deref `(*pf)` yields a // `*fn`, whose call autodereferences the remaining single level — legal, // the shape the one-level peel must NOT over-reject. Its deref-less twin // `pf(21)` is the loud reject pinned in fnptr_pp_derefless_reject. let f: *fn(x: i32) i32 = &add1; let pf: **fn(x: i32) i32 = &f; assert((*pf)(21) == 42); };