cstage rejected a deref-less function-pointer call `f(21)` (f: *fn...) with "calling non-function" while wwstage accepted it and ran correctly -- a cs!=ww divergence. Hare auto-dereferences a pointer callee to its fn type before the call. Peel one pointer level after the named-type chase so cstage accepts a `*fn` callee, matching wwstage and Hare. One level only, deliberately: the #181-cgen indirect-call path lowers the callee VALUE as the target (CALL AX), which is the fn address for a single `*fn` but only the address-of the fn-ptr for `**fn`. A multi-level peel would accept `**fn` past what cgen can lower -- a silent both-stage miscompile (returns garbage). cstage stays loud on `**fn` (rule 7); wwstage's loop-accept of all levels (check.ww:3763) is the over-permissive side, filed for align-down plus the deeper cgen multi-level autoderef. Surfaced by the codegen miscompile hunt (finding C6a). Pinned by test/lang/fnptr_derefless_call_test.ww (deref-less + canonical (*f)(21), multi-arg, alias-chain, tuple-return; reverting the peel reds the deref-less rows at compile).
64 lines
2.6 KiB
Plaintext
64 lines
2.6 KiB
Plaintext
// 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.
|
|
//
|
|
// NOT covered here: a `**fn` deref-less call (`let pf: **fn(...) = &f;
|
|
// pf(21)`). The #181-cgen path uses the callee VALUE as the call target,
|
|
// so it autodereferences exactly ONE level; a `**fn` deref-less call
|
|
// drops a `MOVQ (AX),AX` and miscompiles on BOTH stages (byte-id-blind,
|
|
// returns garbage). cstage deliberately stays loud on `**fn` (one-level
|
|
// peel); multi-level fn-ptr autoderef is deferred (#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);
|
|
};
|