selfhost+test: cgdot LEAQ-of-fn for module-qualified N_DOT (#12)
wwstage cgdot lacked a TY_FN branch for module-qualified N_DOT
rvalues. `let p = mod1.ping` fell through to the MOVQ/LEAQ-narrow
fallback, loading 8 prologue bytes from the fn's first instruction
instead of taking its address. Cstage cgdot already handled this
case (wired during #9, f1440bf).
Mirror cstage: gate on fnretlookup(c, fld) before the localloadop
fallback; emit `LEAQ <module>.<name>(SB), AX` via emitfnname with
the hint from lhs.str.
Extend 706_fnlabel_mangle: pos.ww now stores mod1.ping/mod2.ping
into local fn-pointer slots and dispatches through them in addition
to the existing direct calls. Expected exit 56 → 112. Regression
shape: without the new branch, MOVQ leaf(SB) loads the prologue
bytes; indirect call jumps into garbage → SIGSEGV.
ww2 == ww3 == ww4 byte-identical at the new emit.
This commit is contained in:
@@ -10723,6 +10723,18 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// fallback the C cgen takes when bt is NULL/tyerr.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_IDENT) {
|
||||
// `let p = mod.fn` — fn rvalue via N_DOT. Mirror of
|
||||
// cstage cgdot's TY_FN branch (mafn with module hint).
|
||||
// Without this the MOVQ leaf(SB) fallback below would
|
||||
// load 8 bytes of fn-prologue code into AX instead of
|
||||
// the fn address.
|
||||
let frt: *node = fnretlookup(c, fld);
|
||||
if (frt != nil) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitfnname(c, fld, lhs.str);
|
||||
emitline("(SB), AX\n");
|
||||
return;
|
||||
};
|
||||
let mqop: str = localloadop(c, letvartnode(c, fld));
|
||||
if (streq(mqop, "MOVQ")) {
|
||||
emitline("\tMOVQ\t");
|
||||
|
||||
@@ -1707,6 +1707,18 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// fallback the C cgen takes when bt is NULL/tyerr.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_IDENT) {
|
||||
// `let p = mod.fn` — fn rvalue via N_DOT. Mirror of
|
||||
// cstage cgdot's TY_FN branch (mafn with module hint).
|
||||
// Without this the MOVQ leaf(SB) fallback below would
|
||||
// load 8 bytes of fn-prologue code into AX instead of
|
||||
// the fn address.
|
||||
let frt: *node = fnretlookup(c, fld);
|
||||
if (frt != nil) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitfnname(c, fld, lhs.str);
|
||||
emitline("(SB), AX\n");
|
||||
return;
|
||||
};
|
||||
let mqop: str = localloadop(c, letvartnode(c, fld));
|
||||
if (streq(mqop, "MOVQ")) {
|
||||
emitline("\tMOVQ\t");
|
||||
|
||||
@@ -10723,6 +10723,18 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// fallback the C cgen takes when bt is NULL/tyerr.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_IDENT) {
|
||||
// `let p = mod.fn` — fn rvalue via N_DOT. Mirror of
|
||||
// cstage cgdot's TY_FN branch (mafn with module hint).
|
||||
// Without this the MOVQ leaf(SB) fallback below would
|
||||
// load 8 bytes of fn-prologue code into AX instead of
|
||||
// the fn address.
|
||||
let frt: *node = fnretlookup(c, fld);
|
||||
if (frt != nil) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitfnname(c, fld, lhs.str);
|
||||
emitline("(SB), AX\n");
|
||||
return;
|
||||
};
|
||||
let mqop: str = localloadop(c, letvartnode(c, fld));
|
||||
if (streq(mqop, "MOVQ")) {
|
||||
emitline("\tMOVQ\t");
|
||||
|
||||
@@ -3,30 +3,33 @@
|
||||
* modules can each `export fn ping` (and each define a private
|
||||
* `fn helper`) without colliding at link time.
|
||||
*
|
||||
* The fixture in test/wcc/data/fnlabelmangle/ pins three of the four
|
||||
* label-emit sites #9 touches:
|
||||
* The fixture in test/wcc/data/fnlabelmangle/ pins all four
|
||||
* label-emit sites #9 / #12 touch:
|
||||
* mod{1,2}/mod{1,2}.ww
|
||||
* export fn ping = bareval + helper() + fpi();
|
||||
* fn helper — private, same-leaf across modules
|
||||
* fn fpi — `let h = helper; h();` pins LEAQ N_IDENT
|
||||
* pos.ww
|
||||
* return mod1.ping() + mod2.ping(); // CALL N_DOT × 2
|
||||
* let p1 = mod1.ping; let p2 = mod2.ping; // LEAQ N_DOT × 2
|
||||
* return mod1.ping() + mod2.ping() // CALL N_DOT × 2
|
||||
* + p1() + p2(); // through fn ptr
|
||||
*
|
||||
* mod1.ping = 3 + 11 + 11 = 25
|
||||
* mod2.ping = 5 + 13 + 13 = 31
|
||||
* total = 56
|
||||
* total = 25 + 31 + 25 + 31 = 112
|
||||
*
|
||||
* Pre-fix (cgen emitted bare `TEXT ping` for both modules' exports;
|
||||
* non-exported `helper` already mangled, so that half is incidental
|
||||
* regression coverage), the linker collapsed both `ping` symbols and
|
||||
* one dispatch landed on the wrong body. The bare-IDENT helper /
|
||||
* `let h = helper` callsites would also pick whichever module the
|
||||
* lookup found first, silently miscompiling fpi's intra-module call.
|
||||
* Pre-#9 (cgen emitted bare `TEXT ping` for both modules' exports),
|
||||
* the linker collapsed both `ping` symbols and one dispatch landed
|
||||
* on the wrong body. The bare-IDENT helper / `let h = helper` call-
|
||||
* sites would also pick whichever module the lookup found first,
|
||||
* silently miscompiling fpi's intra-module call.
|
||||
*
|
||||
* Pre-#12 wwstage cgdot's `let p = mod.ping` fell through to the
|
||||
* MOVQ leaf(SB) module-qualified-value fallback, loading 8 bytes
|
||||
* of fn-prologue into AX; `p()` then jumped into mid-prologue
|
||||
* garbage. Cstage cgdot already had a TY_FN LEAQ branch from #9.
|
||||
*
|
||||
* Coverage notes:
|
||||
* - LEAQ N_DOT for fn rvalue (`let p = mod1.ping`) is not exercised.
|
||||
* wwstage cgdot has no working LEAQ-of-fn N_DOT branch — a row
|
||||
* would diverge across stages. Tracked as a follow-up.
|
||||
* - The skip rule's three remaining cases (@symbol / main / empty-
|
||||
* module) are not pinned by a dedicated row because every passing
|
||||
* run of `make test` already exercises them: the bootstrap and
|
||||
@@ -70,10 +73,11 @@ run_pos(const char *driver, const char *fixdir, const char *tag)
|
||||
snprintf(bin, sizeof bin, "%s/pos", fixdir);
|
||||
int got = runwait(bin);
|
||||
unlink(bin);
|
||||
if (got != 56) {
|
||||
if (got != 112) {
|
||||
fprintf(stderr,
|
||||
"fnlabel_mangle[%s]: pos.ww exit=%d want=56 — "
|
||||
"fn labels likely collapsed at link\n", tag, got);
|
||||
"fnlabel_mangle[%s]: pos.ww exit=%d want=112 — "
|
||||
"fn labels likely collapsed at link, or LEAQ-of-fn "
|
||||
"N_DOT branch missing in cgdot\n", tag, got);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
// Two modules each `export fn ping` and each define a private
|
||||
// `helper` with the same leaf. Together with mod2 they pin three of
|
||||
// the four label-emit sites #9 touches:
|
||||
// `helper` with the same leaf. Together with mod2 they pin all four
|
||||
// label-emit sites #9 touches:
|
||||
// - CALL N_DOT : main calls mod1.ping / mod2.ping
|
||||
// - CALL N_IDENT : ping bare-calls helper inside its own module
|
||||
// - LEAQ N_IDENT : fpi takes `helper` by value, then calls it
|
||||
//
|
||||
// The LEAQ N_DOT path (`let p = mod1.ping`) is intentionally not
|
||||
// exercised here — wwstage cgdot has no working LEAQ-of-fn N_DOT
|
||||
// branch, so a row would diverge across stages.
|
||||
// - LEAQ N_DOT : main does `let p = mod1.ping` then `p()`
|
||||
// (pos.ww — wired via #12 wwstage cgdot fix)
|
||||
|
||||
fn helper() i32 = { return 11i32; };
|
||||
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
// Positive case: import both modules, dispatch through the explicit
|
||||
// module qualifier. Each module's ping = bare-value + helper() (CALL
|
||||
// N_IDENT) + fpi() (which exercises LEAQ N_IDENT via `let h = helper`).
|
||||
// `p1` / `p2` pin the LEAQ N_DOT path (`let p = mod.ping` for fn
|
||||
// rvalue) — cgdot must emit `LEAQ mod.ping(SB), AX`, not
|
||||
// `MOVQ ping(SB), AX`. Pre-#12 wwstage cgdot fell through to the
|
||||
// latter and `p1() / p2()` jumped into mid-prologue garbage.
|
||||
//
|
||||
// mod1.ping = 3 + 11 + 11 = 25
|
||||
// mod2.ping = 5 + 13 + 13 = 31
|
||||
// total = 56
|
||||
//
|
||||
// Pre-fix the four `helper` / `ping` symbols would collapse at link
|
||||
// or the hint-less lookup would silently grab the wrong module's
|
||||
// body — exit would land at 50/62/some other value, never 56.
|
||||
// p1() = mod1.ping = 25
|
||||
// p2() = mod2.ping = 31
|
||||
// total = 112
|
||||
|
||||
use mod1;
|
||||
use mod2;
|
||||
|
||||
fn main() i32 = {
|
||||
return mod1.ping() + mod2.ping();
|
||||
let p1: fn() i32 = mod1.ping;
|
||||
let p2: fn() i32 = mod2.ping;
|
||||
return mod1.ping() + mod2.ping() + p1() + p2();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user