selfhost/cmd/wcc: collapse chained-dot offset-emit onto tinfo.fields + delete dotinnerstructptr (#70, #12)
Both chained-*struct-field sites (cgdot read, cgassign store) read the inner- struct layout off the stamped inner-dot tinfo (peel NAMED->under, TY_PTR->.sub ->NAMED->under->TY_STRUCT) and walk tinfo.fields for offset + per-field type, replacing dotinnerstructptr's structinfo re-walk + structlookup. Type dispatch (str/slice/float, load/store op) keys on tfield.type_ via the existing *tinfo predicates; ft.slotsize reproduces the old fieldsize exactly (== size for all kinds reached here except struct/array). cstage parity: cgen.c:1653-1655 (read fl->offset), 2514/2541 (store f->offset). Behavior-preserving: a strict per-level gate (every chain dot must peel through a *struct, root must be N_IDENT && localfindnode != nil) reproduces dotinnerstructptr's EXACT locals-only-with-all-*struct-intermediates firing set -- a by-value intermediate dot bails to the identical pre-existing generic path. asm is byte-identical (990-997 hold; both the strict gate and the looser superset tested 134/134, confirming the corpus has no by-value-intermediate chain). Deliberately NOT widened past the old set: global-root and by-value- intermediate chained dots stay on their old paths -- the global case is a pre-existing SHARED base-eval defect (cstage mis-evals the global base as (BP)->segfault; wwstage no-stores), filed #27 to fix in both stages together. dotinnerstructptr deleted (0 callers). Offset now sources from tinfo.fields (natural layout) vs old structinfo (slot-padded); they coincide for flat structs (all reached here), with nested-by-value-struct divergence pre-existing and left to #57/#6/#7 territory.
This commit is contained in:
@@ -10549,7 +10549,7 @@ fn dynamicgrow(m: *state, need: i32) void = {
|
||||
// - pushargsrev: per-call arg pushing
|
||||
// - type predicates: isstr*/isslice*/istagged*/nodeis* families
|
||||
// - field ops: fieldloadop, fieldstoreop
|
||||
// - index helpers: indexbaseesz, dotinnerstructptr, elemsizeof
|
||||
// - index helpers: indexbaseesz, elemsizeof
|
||||
// - slot sizing: structlookup, primsize, slotsize, fieldsize,
|
||||
// registerstruct, collectstructs
|
||||
// - rhs helpers: taggedvariantindex
|
||||
@@ -11493,56 +11493,6 @@ fn indexbaseesz(c: *cgen, base: *node) i32 = {
|
||||
return 8;
|
||||
};
|
||||
|
||||
// dotinnerstructptr — for an nkind.N_DOT whose lhs is a chain of dots
|
||||
// or an nkind.N_IDENT, walk the chain and return the nkind.N_TNAME tnode of the
|
||||
// struct that the chain dereferences to (i.e., for `r.sym` where
|
||||
// .sym is *lsym, return nkind.N_TNAME("lsym")). Returns nil if the chain
|
||||
// doesn't resolve to a *struct.
|
||||
//
|
||||
// Used by the chained-DOT cgen path so `r.sym.val` knows the outer
|
||||
// is a field of `lsym`.
|
||||
fn dotinnerstructptr(c: *cgen, n: *node) *node = {
|
||||
if (n == nil) { return nil; };
|
||||
if (n.kind != nkind.N_DOT) { return nil; };
|
||||
let base: *node = n.lhs;
|
||||
let fld: str = n.str;
|
||||
if (base == nil) { return nil; };
|
||||
|
||||
// Resolve base's struct tnode.
|
||||
let baset: *node = nil;
|
||||
if (base.kind == nkind.N_IDENT) {
|
||||
let lc: *local = localfindnode(c, base.str);
|
||||
if (lc == nil) { return nil; };
|
||||
let tn: *node = lc.tnode;
|
||||
if (tn == nil) { return nil; };
|
||||
// base could be either struct-by-value (nkind.N_TNAME) or *struct (nkind.N_TPTR).
|
||||
if (tn.kind == nkind.N_TNAME) { baset = tn; };
|
||||
if (tn.kind == nkind.N_TPTR) { baset = tn.lhs; };
|
||||
} else { if (base.kind == nkind.N_DOT) {
|
||||
baset = dotinnerstructptr(c, base);
|
||||
};};
|
||||
if (baset == nil) { return nil; };
|
||||
if (baset.kind != nkind.N_TNAME) { return nil; };
|
||||
|
||||
// Look up the struct, find the field, return the field's *struct.
|
||||
let si: *structinfo = structlookup(c, baset.str);
|
||||
if (si == nil) { return nil; };
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fld)) {
|
||||
let ft: *node = fi.tnode;
|
||||
if (ft == nil) { return nil; };
|
||||
if (ft.kind != nkind.N_TPTR) { return nil; };
|
||||
let inner: *node = ft.lhs;
|
||||
if (inner == nil) { return nil; };
|
||||
if (inner.kind != nkind.N_TNAME) { return nil; };
|
||||
return inner;
|
||||
};
|
||||
fi = fi.finext;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// elemsizeof — given the type node of an indexable (`*T`, `[]T`,
|
||||
// `[N]T`, `str`), return the byte size of one element (1 for u8/i8/
|
||||
// bool/str-byte, 8 otherwise — same shape as C cgen's esz fallback).
|
||||
@@ -15908,22 +15858,54 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// the field. (Showed up porting w6l/pass.ww.)
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_DOT) {
|
||||
let innert: *node = dotinnerstructptr(c, lhs);
|
||||
if (innert != nil) {
|
||||
let sname: str = innert.str;
|
||||
let si: *structinfo = structlookup(c, sname);
|
||||
if (si != nil) {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fld)) {
|
||||
// #70 (#12): inner-struct layout via the stamped lhs.type_
|
||||
// (peel *→struct) + tinfo.fields, replacing dotinnerstructptr's
|
||||
// structinfo walk. Gate is strict-equal to the deleted helper:
|
||||
// fire only when the chain root is a LOCAL ident AND every dot
|
||||
// in the chain resolves through a *struct (dotinnerstructptr
|
||||
// recursed per level on a *struct field and bailed on a by-
|
||||
// value-struct intermediate). Reproducing that exactly avoids
|
||||
// an untested widening past cstage; a deliberate widen, if ever
|
||||
// wanted, is a future task with its own probe. Global-root
|
||||
// chains stay in their pre-existing shared base-eval breakage
|
||||
// (filed #27), untouched here.
|
||||
let croot: *node = lhs;
|
||||
let allptr: bool = true;
|
||||
for (croot != nil && croot.kind == nkind.N_DOT) {
|
||||
let ct: *tinfo = croot.type_: *tinfo;
|
||||
for (ct != nil && ct.kind == tykind.TY_NAMED) { ct = ct.under; };
|
||||
let okp: bool = false;
|
||||
if (ct != nil) { if (ct.kind == tykind.TY_PTR) {
|
||||
let cs: *tinfo = ct.sub;
|
||||
for (cs != nil && cs.kind == tykind.TY_NAMED) { cs = cs.under; };
|
||||
if (cs != nil) { if (cs.kind == tykind.TY_STRUCT) { okp = true; }; };
|
||||
}; };
|
||||
if (!okp) { allptr = false; };
|
||||
croot = croot.lhs;
|
||||
};
|
||||
let it: *tinfo = nil;
|
||||
if (allptr) { if (croot != nil) { if (croot.kind == nkind.N_IDENT) {
|
||||
if (localfindnode(c, croot.str) != nil) {
|
||||
it = lhs.type_: *tinfo;
|
||||
};
|
||||
}; }; };
|
||||
for (it != nil && it.kind == tykind.TY_NAMED) { it = it.under; };
|
||||
if (it != nil) { if (it.kind == tykind.TY_PTR) {
|
||||
let st: *tinfo = it.sub;
|
||||
for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; };
|
||||
if (st != nil) { if (st.kind == tykind.TY_STRUCT) {
|
||||
let tf: *tfield = st.fields;
|
||||
for (tf != nil) {
|
||||
if (streq(tf.name, fld)) {
|
||||
let ft: *tinfo = tf.type_;
|
||||
cgexpr(c, lhs); // AX = ptr to inner struct
|
||||
// str field: load both halves.
|
||||
if (isstrtype(c, fi.tnode)) {
|
||||
if (typeisstr(ft)) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg((fi.foff + 8): i64, "AX");
|
||||
emitdispreg((tf.offset + 8u64): i64, "AX");
|
||||
emitline(", BX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(fi.foff: i64, "AX");
|
||||
emitdispreg(tf.offset: i64, "AX");
|
||||
emitline(", AX\n");
|
||||
return;
|
||||
};
|
||||
@@ -15931,41 +15913,41 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// into (AX, BX, CX). AX is the *struct
|
||||
// base, so load .ptr (which targets
|
||||
// AX) LAST.
|
||||
if (isslicetype(c, fi.tnode)) {
|
||||
if (typeisslice(ft)) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg((fi.foff + 8): i64, "AX");
|
||||
emitdispreg((tf.offset + 8u64): i64, "AX");
|
||||
emitline(", BX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg((fi.foff + 16): i64, "AX");
|
||||
emitdispreg((tf.offset + 16u64): i64, "AX");
|
||||
emitline(", CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(fi.foff: i64, "AX");
|
||||
emitdispreg(tf.offset: i64, "AX");
|
||||
emitline(", AX\n");
|
||||
return;
|
||||
};
|
||||
// f64/f32 chained field: route through X0.
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
if (typeisfloat(ft)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
||||
if (typeisf32(ft)) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitdispreg(fi.foff: i64, "AX");
|
||||
emitdispreg(tf.offset: i64, "AX");
|
||||
emitline(", X0\n");
|
||||
return;
|
||||
};
|
||||
let lop: str = fieldloadop(c, fi);
|
||||
let lop: str = loadopsz(typeissigned(ft), ft.slotsize: i32);
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitdispreg(fi.foff: i64, "AX");
|
||||
emitdispreg(tf.offset: i64, "AX");
|
||||
emitline(", AX\n");
|
||||
return;
|
||||
};
|
||||
fi = fi.finext;
|
||||
tf = tf.tnext;
|
||||
};
|
||||
};
|
||||
};
|
||||
}; };
|
||||
}; };
|
||||
};
|
||||
};
|
||||
// Chained `(ident).f1.f2` read where f1 is a struct-by-value
|
||||
@@ -18737,16 +18719,48 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
let fld: str = lhs.str;
|
||||
if (base != nil) {
|
||||
if (base.kind == nkind.N_DOT) {
|
||||
let innert: *node = dotinnerstructptr(c, base);
|
||||
if (innert != nil) {
|
||||
let sname: str = innert.str;
|
||||
let si: *structinfo = structlookup(c, sname);
|
||||
if (si != nil) {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fld)) {
|
||||
// #70 (#12): inner-struct layout via the stamped
|
||||
// base.type_ (peel *→struct) + tinfo.fields,
|
||||
// replacing dotinnerstructptr's structinfo walk.
|
||||
// Gate is strict-equal to the deleted helper: fire
|
||||
// only when the chain root is a LOCAL ident AND every
|
||||
// dot resolves through a *struct (dotinnerstructptr
|
||||
// recursed per level on a *struct field, bailing on a
|
||||
// by-value-struct intermediate). Reproducing that
|
||||
// exactly avoids an untested widening past cstage.
|
||||
// Global-root chains stay in their pre-existing shared
|
||||
// base-eval breakage (filed #27).
|
||||
let croot: *node = base;
|
||||
let allptr: bool = true;
|
||||
for (croot != nil && croot.kind == nkind.N_DOT) {
|
||||
let ct: *tinfo = croot.type_: *tinfo;
|
||||
for (ct != nil && ct.kind == tykind.TY_NAMED) { ct = ct.under; };
|
||||
let okp: bool = false;
|
||||
if (ct != nil) { if (ct.kind == tykind.TY_PTR) {
|
||||
let cs: *tinfo = ct.sub;
|
||||
for (cs != nil && cs.kind == tykind.TY_NAMED) { cs = cs.under; };
|
||||
if (cs != nil) { if (cs.kind == tykind.TY_STRUCT) { okp = true; }; };
|
||||
}; };
|
||||
if (!okp) { allptr = false; };
|
||||
croot = croot.lhs;
|
||||
};
|
||||
let it: *tinfo = nil;
|
||||
if (allptr) { if (croot != nil) { if (croot.kind == nkind.N_IDENT) {
|
||||
if (localfindnode(c, croot.str) != nil) {
|
||||
it = base.type_: *tinfo;
|
||||
};
|
||||
}; }; };
|
||||
for (it != nil && it.kind == tykind.TY_NAMED) { it = it.under; };
|
||||
if (it != nil) { if (it.kind == tykind.TY_PTR) {
|
||||
let st: *tinfo = it.sub;
|
||||
for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; };
|
||||
if (st != nil) { if (st.kind == tykind.TY_STRUCT) {
|
||||
let tf: *tfield = st.fields;
|
||||
for (tf != nil) {
|
||||
if (streq(tf.name, fld)) {
|
||||
let ft: *tinfo = tf.type_;
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
if (isstrtype(c, fi.tnode)) {
|
||||
if (typeisstr(ft)) {
|
||||
// str rhs: AX=ptr, BX=len.
|
||||
// Stash both, then load
|
||||
// the struct ptr into CX
|
||||
@@ -18759,19 +18773,19 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "CX");
|
||||
emitdispreg(tf.offset: i64, "CX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg((fi.foff + 8): i64, "CX");
|
||||
emitdispreg((tf.offset + 8u64): i64, "CX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
// f64/f32 chained plain `=`: cgexpr rhs left value in
|
||||
// X0. Spill to stack so cgexpr(base) can use AX, then
|
||||
// reload and MOVSD/MOVSS into the slot.
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
if (typeisfloat(ft)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
||||
if (typeisf32(ft)) { mov = "MOVSS"; };
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tSUBQ\t$8, SP\n");
|
||||
emitline("\t");
|
||||
@@ -18786,7 +18800,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitdispreg(tf.offset: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
@@ -18795,19 +18809,19 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
cgexpr(c, base);
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
let sop: str = fieldstoreop(c, fi);
|
||||
let sop: str = tnodestoreop(c, n.rhs, ft.slotsize: i32);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitdispreg(tf.offset: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
};
|
||||
fi = fi.finext;
|
||||
tf = tf.tnext;
|
||||
};
|
||||
};
|
||||
};
|
||||
}; };
|
||||
}; };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2015,22 +2015,54 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// the field. (Showed up porting w6l/pass.ww.)
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_DOT) {
|
||||
let innert: *node = dotinnerstructptr(c, lhs);
|
||||
if (innert != nil) {
|
||||
let sname: str = innert.str;
|
||||
let si: *structinfo = structlookup(c, sname);
|
||||
if (si != nil) {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fld)) {
|
||||
// #70 (#12): inner-struct layout via the stamped lhs.type_
|
||||
// (peel *→struct) + tinfo.fields, replacing dotinnerstructptr's
|
||||
// structinfo walk. Gate is strict-equal to the deleted helper:
|
||||
// fire only when the chain root is a LOCAL ident AND every dot
|
||||
// in the chain resolves through a *struct (dotinnerstructptr
|
||||
// recursed per level on a *struct field and bailed on a by-
|
||||
// value-struct intermediate). Reproducing that exactly avoids
|
||||
// an untested widening past cstage; a deliberate widen, if ever
|
||||
// wanted, is a future task with its own probe. Global-root
|
||||
// chains stay in their pre-existing shared base-eval breakage
|
||||
// (filed #27), untouched here.
|
||||
let croot: *node = lhs;
|
||||
let allptr: bool = true;
|
||||
for (croot != nil && croot.kind == nkind.N_DOT) {
|
||||
let ct: *tinfo = croot.type_: *tinfo;
|
||||
for (ct != nil && ct.kind == tykind.TY_NAMED) { ct = ct.under; };
|
||||
let okp: bool = false;
|
||||
if (ct != nil) { if (ct.kind == tykind.TY_PTR) {
|
||||
let cs: *tinfo = ct.sub;
|
||||
for (cs != nil && cs.kind == tykind.TY_NAMED) { cs = cs.under; };
|
||||
if (cs != nil) { if (cs.kind == tykind.TY_STRUCT) { okp = true; }; };
|
||||
}; };
|
||||
if (!okp) { allptr = false; };
|
||||
croot = croot.lhs;
|
||||
};
|
||||
let it: *tinfo = nil;
|
||||
if (allptr) { if (croot != nil) { if (croot.kind == nkind.N_IDENT) {
|
||||
if (localfindnode(c, croot.str) != nil) {
|
||||
it = lhs.type_: *tinfo;
|
||||
};
|
||||
}; }; };
|
||||
for (it != nil && it.kind == tykind.TY_NAMED) { it = it.under; };
|
||||
if (it != nil) { if (it.kind == tykind.TY_PTR) {
|
||||
let st: *tinfo = it.sub;
|
||||
for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; };
|
||||
if (st != nil) { if (st.kind == tykind.TY_STRUCT) {
|
||||
let tf: *tfield = st.fields;
|
||||
for (tf != nil) {
|
||||
if (streq(tf.name, fld)) {
|
||||
let ft: *tinfo = tf.type_;
|
||||
cgexpr(c, lhs); // AX = ptr to inner struct
|
||||
// str field: load both halves.
|
||||
if (isstrtype(c, fi.tnode)) {
|
||||
if (typeisstr(ft)) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg((fi.foff + 8): i64, "AX");
|
||||
emitdispreg((tf.offset + 8u64): i64, "AX");
|
||||
emitline(", BX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(fi.foff: i64, "AX");
|
||||
emitdispreg(tf.offset: i64, "AX");
|
||||
emitline(", AX\n");
|
||||
return;
|
||||
};
|
||||
@@ -2038,41 +2070,41 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// into (AX, BX, CX). AX is the *struct
|
||||
// base, so load .ptr (which targets
|
||||
// AX) LAST.
|
||||
if (isslicetype(c, fi.tnode)) {
|
||||
if (typeisslice(ft)) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg((fi.foff + 8): i64, "AX");
|
||||
emitdispreg((tf.offset + 8u64): i64, "AX");
|
||||
emitline(", BX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg((fi.foff + 16): i64, "AX");
|
||||
emitdispreg((tf.offset + 16u64): i64, "AX");
|
||||
emitline(", CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(fi.foff: i64, "AX");
|
||||
emitdispreg(tf.offset: i64, "AX");
|
||||
emitline(", AX\n");
|
||||
return;
|
||||
};
|
||||
// f64/f32 chained field: route through X0.
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
if (typeisfloat(ft)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
||||
if (typeisf32(ft)) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitdispreg(fi.foff: i64, "AX");
|
||||
emitdispreg(tf.offset: i64, "AX");
|
||||
emitline(", X0\n");
|
||||
return;
|
||||
};
|
||||
let lop: str = fieldloadop(c, fi);
|
||||
let lop: str = loadopsz(typeissigned(ft), ft.slotsize: i32);
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitdispreg(fi.foff: i64, "AX");
|
||||
emitdispreg(tf.offset: i64, "AX");
|
||||
emitline(", AX\n");
|
||||
return;
|
||||
};
|
||||
fi = fi.finext;
|
||||
tf = tf.tnext;
|
||||
};
|
||||
};
|
||||
};
|
||||
}; };
|
||||
}; };
|
||||
};
|
||||
};
|
||||
// Chained `(ident).f1.f2` read where f1 is a struct-by-value
|
||||
@@ -4844,16 +4876,48 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
let fld: str = lhs.str;
|
||||
if (base != nil) {
|
||||
if (base.kind == nkind.N_DOT) {
|
||||
let innert: *node = dotinnerstructptr(c, base);
|
||||
if (innert != nil) {
|
||||
let sname: str = innert.str;
|
||||
let si: *structinfo = structlookup(c, sname);
|
||||
if (si != nil) {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fld)) {
|
||||
// #70 (#12): inner-struct layout via the stamped
|
||||
// base.type_ (peel *→struct) + tinfo.fields,
|
||||
// replacing dotinnerstructptr's structinfo walk.
|
||||
// Gate is strict-equal to the deleted helper: fire
|
||||
// only when the chain root is a LOCAL ident AND every
|
||||
// dot resolves through a *struct (dotinnerstructptr
|
||||
// recursed per level on a *struct field, bailing on a
|
||||
// by-value-struct intermediate). Reproducing that
|
||||
// exactly avoids an untested widening past cstage.
|
||||
// Global-root chains stay in their pre-existing shared
|
||||
// base-eval breakage (filed #27).
|
||||
let croot: *node = base;
|
||||
let allptr: bool = true;
|
||||
for (croot != nil && croot.kind == nkind.N_DOT) {
|
||||
let ct: *tinfo = croot.type_: *tinfo;
|
||||
for (ct != nil && ct.kind == tykind.TY_NAMED) { ct = ct.under; };
|
||||
let okp: bool = false;
|
||||
if (ct != nil) { if (ct.kind == tykind.TY_PTR) {
|
||||
let cs: *tinfo = ct.sub;
|
||||
for (cs != nil && cs.kind == tykind.TY_NAMED) { cs = cs.under; };
|
||||
if (cs != nil) { if (cs.kind == tykind.TY_STRUCT) { okp = true; }; };
|
||||
}; };
|
||||
if (!okp) { allptr = false; };
|
||||
croot = croot.lhs;
|
||||
};
|
||||
let it: *tinfo = nil;
|
||||
if (allptr) { if (croot != nil) { if (croot.kind == nkind.N_IDENT) {
|
||||
if (localfindnode(c, croot.str) != nil) {
|
||||
it = base.type_: *tinfo;
|
||||
};
|
||||
}; }; };
|
||||
for (it != nil && it.kind == tykind.TY_NAMED) { it = it.under; };
|
||||
if (it != nil) { if (it.kind == tykind.TY_PTR) {
|
||||
let st: *tinfo = it.sub;
|
||||
for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; };
|
||||
if (st != nil) { if (st.kind == tykind.TY_STRUCT) {
|
||||
let tf: *tfield = st.fields;
|
||||
for (tf != nil) {
|
||||
if (streq(tf.name, fld)) {
|
||||
let ft: *tinfo = tf.type_;
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
if (isstrtype(c, fi.tnode)) {
|
||||
if (typeisstr(ft)) {
|
||||
// str rhs: AX=ptr, BX=len.
|
||||
// Stash both, then load
|
||||
// the struct ptr into CX
|
||||
@@ -4866,19 +4930,19 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "CX");
|
||||
emitdispreg(tf.offset: i64, "CX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg((fi.foff + 8): i64, "CX");
|
||||
emitdispreg((tf.offset + 8u64): i64, "CX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
// f64/f32 chained plain `=`: cgexpr rhs left value in
|
||||
// X0. Spill to stack so cgexpr(base) can use AX, then
|
||||
// reload and MOVSD/MOVSS into the slot.
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
if (typeisfloat(ft)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
||||
if (typeisf32(ft)) { mov = "MOVSS"; };
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tSUBQ\t$8, SP\n");
|
||||
emitline("\t");
|
||||
@@ -4893,7 +4957,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitdispreg(tf.offset: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
@@ -4902,19 +4966,19 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
cgexpr(c, base);
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
let sop: str = fieldstoreop(c, fi);
|
||||
let sop: str = tnodestoreop(c, n.rhs, ft.slotsize: i32);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitdispreg(tf.offset: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
};
|
||||
fi = fi.finext;
|
||||
tf = tf.tnext;
|
||||
};
|
||||
};
|
||||
};
|
||||
}; };
|
||||
}; };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// - pushargsrev: per-call arg pushing
|
||||
// - type predicates: isstr*/isslice*/istagged*/nodeis* families
|
||||
// - field ops: fieldloadop, fieldstoreop
|
||||
// - index helpers: indexbaseesz, dotinnerstructptr, elemsizeof
|
||||
// - index helpers: indexbaseesz, elemsizeof
|
||||
// - slot sizing: structlookup, primsize, slotsize, fieldsize,
|
||||
// registerstruct, collectstructs
|
||||
// - rhs helpers: taggedvariantindex
|
||||
@@ -948,56 +948,6 @@ fn indexbaseesz(c: *cgen, base: *node) i32 = {
|
||||
return 8;
|
||||
};
|
||||
|
||||
// dotinnerstructptr — for an nkind.N_DOT whose lhs is a chain of dots
|
||||
// or an nkind.N_IDENT, walk the chain and return the nkind.N_TNAME tnode of the
|
||||
// struct that the chain dereferences to (i.e., for `r.sym` where
|
||||
// .sym is *lsym, return nkind.N_TNAME("lsym")). Returns nil if the chain
|
||||
// doesn't resolve to a *struct.
|
||||
//
|
||||
// Used by the chained-DOT cgen path so `r.sym.val` knows the outer
|
||||
// is a field of `lsym`.
|
||||
fn dotinnerstructptr(c: *cgen, n: *node) *node = {
|
||||
if (n == nil) { return nil; };
|
||||
if (n.kind != nkind.N_DOT) { return nil; };
|
||||
let base: *node = n.lhs;
|
||||
let fld: str = n.str;
|
||||
if (base == nil) { return nil; };
|
||||
|
||||
// Resolve base's struct tnode.
|
||||
let baset: *node = nil;
|
||||
if (base.kind == nkind.N_IDENT) {
|
||||
let lc: *local = localfindnode(c, base.str);
|
||||
if (lc == nil) { return nil; };
|
||||
let tn: *node = lc.tnode;
|
||||
if (tn == nil) { return nil; };
|
||||
// base could be either struct-by-value (nkind.N_TNAME) or *struct (nkind.N_TPTR).
|
||||
if (tn.kind == nkind.N_TNAME) { baset = tn; };
|
||||
if (tn.kind == nkind.N_TPTR) { baset = tn.lhs; };
|
||||
} else { if (base.kind == nkind.N_DOT) {
|
||||
baset = dotinnerstructptr(c, base);
|
||||
};};
|
||||
if (baset == nil) { return nil; };
|
||||
if (baset.kind != nkind.N_TNAME) { return nil; };
|
||||
|
||||
// Look up the struct, find the field, return the field's *struct.
|
||||
let si: *structinfo = structlookup(c, baset.str);
|
||||
if (si == nil) { return nil; };
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fld)) {
|
||||
let ft: *node = fi.tnode;
|
||||
if (ft == nil) { return nil; };
|
||||
if (ft.kind != nkind.N_TPTR) { return nil; };
|
||||
let inner: *node = ft.lhs;
|
||||
if (inner == nil) { return nil; };
|
||||
if (inner.kind != nkind.N_TNAME) { return nil; };
|
||||
return inner;
|
||||
};
|
||||
fi = fi.finext;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// elemsizeof — given the type node of an indexable (`*T`, `[]T`,
|
||||
// `[N]T`, `str`), return the byte size of one element (1 for u8/i8/
|
||||
// bool/str-byte, 8 otherwise — same shape as C cgen's esz fallback).
|
||||
|
||||
@@ -10549,7 +10549,7 @@ fn dynamicgrow(m: *state, need: i32) void = {
|
||||
// - pushargsrev: per-call arg pushing
|
||||
// - type predicates: isstr*/isslice*/istagged*/nodeis* families
|
||||
// - field ops: fieldloadop, fieldstoreop
|
||||
// - index helpers: indexbaseesz, dotinnerstructptr, elemsizeof
|
||||
// - index helpers: indexbaseesz, elemsizeof
|
||||
// - slot sizing: structlookup, primsize, slotsize, fieldsize,
|
||||
// registerstruct, collectstructs
|
||||
// - rhs helpers: taggedvariantindex
|
||||
@@ -11493,56 +11493,6 @@ fn indexbaseesz(c: *cgen, base: *node) i32 = {
|
||||
return 8;
|
||||
};
|
||||
|
||||
// dotinnerstructptr — for an nkind.N_DOT whose lhs is a chain of dots
|
||||
// or an nkind.N_IDENT, walk the chain and return the nkind.N_TNAME tnode of the
|
||||
// struct that the chain dereferences to (i.e., for `r.sym` where
|
||||
// .sym is *lsym, return nkind.N_TNAME("lsym")). Returns nil if the chain
|
||||
// doesn't resolve to a *struct.
|
||||
//
|
||||
// Used by the chained-DOT cgen path so `r.sym.val` knows the outer
|
||||
// is a field of `lsym`.
|
||||
fn dotinnerstructptr(c: *cgen, n: *node) *node = {
|
||||
if (n == nil) { return nil; };
|
||||
if (n.kind != nkind.N_DOT) { return nil; };
|
||||
let base: *node = n.lhs;
|
||||
let fld: str = n.str;
|
||||
if (base == nil) { return nil; };
|
||||
|
||||
// Resolve base's struct tnode.
|
||||
let baset: *node = nil;
|
||||
if (base.kind == nkind.N_IDENT) {
|
||||
let lc: *local = localfindnode(c, base.str);
|
||||
if (lc == nil) { return nil; };
|
||||
let tn: *node = lc.tnode;
|
||||
if (tn == nil) { return nil; };
|
||||
// base could be either struct-by-value (nkind.N_TNAME) or *struct (nkind.N_TPTR).
|
||||
if (tn.kind == nkind.N_TNAME) { baset = tn; };
|
||||
if (tn.kind == nkind.N_TPTR) { baset = tn.lhs; };
|
||||
} else { if (base.kind == nkind.N_DOT) {
|
||||
baset = dotinnerstructptr(c, base);
|
||||
};};
|
||||
if (baset == nil) { return nil; };
|
||||
if (baset.kind != nkind.N_TNAME) { return nil; };
|
||||
|
||||
// Look up the struct, find the field, return the field's *struct.
|
||||
let si: *structinfo = structlookup(c, baset.str);
|
||||
if (si == nil) { return nil; };
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fld)) {
|
||||
let ft: *node = fi.tnode;
|
||||
if (ft == nil) { return nil; };
|
||||
if (ft.kind != nkind.N_TPTR) { return nil; };
|
||||
let inner: *node = ft.lhs;
|
||||
if (inner == nil) { return nil; };
|
||||
if (inner.kind != nkind.N_TNAME) { return nil; };
|
||||
return inner;
|
||||
};
|
||||
fi = fi.finext;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// elemsizeof — given the type node of an indexable (`*T`, `[]T`,
|
||||
// `[N]T`, `str`), return the byte size of one element (1 for u8/i8/
|
||||
// bool/str-byte, 8 otherwise — same shape as C cgen's esz fallback).
|
||||
@@ -15908,22 +15858,54 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// the field. (Showed up porting w6l/pass.ww.)
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_DOT) {
|
||||
let innert: *node = dotinnerstructptr(c, lhs);
|
||||
if (innert != nil) {
|
||||
let sname: str = innert.str;
|
||||
let si: *structinfo = structlookup(c, sname);
|
||||
if (si != nil) {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fld)) {
|
||||
// #70 (#12): inner-struct layout via the stamped lhs.type_
|
||||
// (peel *→struct) + tinfo.fields, replacing dotinnerstructptr's
|
||||
// structinfo walk. Gate is strict-equal to the deleted helper:
|
||||
// fire only when the chain root is a LOCAL ident AND every dot
|
||||
// in the chain resolves through a *struct (dotinnerstructptr
|
||||
// recursed per level on a *struct field and bailed on a by-
|
||||
// value-struct intermediate). Reproducing that exactly avoids
|
||||
// an untested widening past cstage; a deliberate widen, if ever
|
||||
// wanted, is a future task with its own probe. Global-root
|
||||
// chains stay in their pre-existing shared base-eval breakage
|
||||
// (filed #27), untouched here.
|
||||
let croot: *node = lhs;
|
||||
let allptr: bool = true;
|
||||
for (croot != nil && croot.kind == nkind.N_DOT) {
|
||||
let ct: *tinfo = croot.type_: *tinfo;
|
||||
for (ct != nil && ct.kind == tykind.TY_NAMED) { ct = ct.under; };
|
||||
let okp: bool = false;
|
||||
if (ct != nil) { if (ct.kind == tykind.TY_PTR) {
|
||||
let cs: *tinfo = ct.sub;
|
||||
for (cs != nil && cs.kind == tykind.TY_NAMED) { cs = cs.under; };
|
||||
if (cs != nil) { if (cs.kind == tykind.TY_STRUCT) { okp = true; }; };
|
||||
}; };
|
||||
if (!okp) { allptr = false; };
|
||||
croot = croot.lhs;
|
||||
};
|
||||
let it: *tinfo = nil;
|
||||
if (allptr) { if (croot != nil) { if (croot.kind == nkind.N_IDENT) {
|
||||
if (localfindnode(c, croot.str) != nil) {
|
||||
it = lhs.type_: *tinfo;
|
||||
};
|
||||
}; }; };
|
||||
for (it != nil && it.kind == tykind.TY_NAMED) { it = it.under; };
|
||||
if (it != nil) { if (it.kind == tykind.TY_PTR) {
|
||||
let st: *tinfo = it.sub;
|
||||
for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; };
|
||||
if (st != nil) { if (st.kind == tykind.TY_STRUCT) {
|
||||
let tf: *tfield = st.fields;
|
||||
for (tf != nil) {
|
||||
if (streq(tf.name, fld)) {
|
||||
let ft: *tinfo = tf.type_;
|
||||
cgexpr(c, lhs); // AX = ptr to inner struct
|
||||
// str field: load both halves.
|
||||
if (isstrtype(c, fi.tnode)) {
|
||||
if (typeisstr(ft)) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg((fi.foff + 8): i64, "AX");
|
||||
emitdispreg((tf.offset + 8u64): i64, "AX");
|
||||
emitline(", BX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(fi.foff: i64, "AX");
|
||||
emitdispreg(tf.offset: i64, "AX");
|
||||
emitline(", AX\n");
|
||||
return;
|
||||
};
|
||||
@@ -15931,41 +15913,41 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// into (AX, BX, CX). AX is the *struct
|
||||
// base, so load .ptr (which targets
|
||||
// AX) LAST.
|
||||
if (isslicetype(c, fi.tnode)) {
|
||||
if (typeisslice(ft)) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg((fi.foff + 8): i64, "AX");
|
||||
emitdispreg((tf.offset + 8u64): i64, "AX");
|
||||
emitline(", BX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg((fi.foff + 16): i64, "AX");
|
||||
emitdispreg((tf.offset + 16u64): i64, "AX");
|
||||
emitline(", CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(fi.foff: i64, "AX");
|
||||
emitdispreg(tf.offset: i64, "AX");
|
||||
emitline(", AX\n");
|
||||
return;
|
||||
};
|
||||
// f64/f32 chained field: route through X0.
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
if (typeisfloat(ft)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
||||
if (typeisf32(ft)) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitdispreg(fi.foff: i64, "AX");
|
||||
emitdispreg(tf.offset: i64, "AX");
|
||||
emitline(", X0\n");
|
||||
return;
|
||||
};
|
||||
let lop: str = fieldloadop(c, fi);
|
||||
let lop: str = loadopsz(typeissigned(ft), ft.slotsize: i32);
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitdispreg(fi.foff: i64, "AX");
|
||||
emitdispreg(tf.offset: i64, "AX");
|
||||
emitline(", AX\n");
|
||||
return;
|
||||
};
|
||||
fi = fi.finext;
|
||||
tf = tf.tnext;
|
||||
};
|
||||
};
|
||||
};
|
||||
}; };
|
||||
}; };
|
||||
};
|
||||
};
|
||||
// Chained `(ident).f1.f2` read where f1 is a struct-by-value
|
||||
@@ -18737,16 +18719,48 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
let fld: str = lhs.str;
|
||||
if (base != nil) {
|
||||
if (base.kind == nkind.N_DOT) {
|
||||
let innert: *node = dotinnerstructptr(c, base);
|
||||
if (innert != nil) {
|
||||
let sname: str = innert.str;
|
||||
let si: *structinfo = structlookup(c, sname);
|
||||
if (si != nil) {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fld)) {
|
||||
// #70 (#12): inner-struct layout via the stamped
|
||||
// base.type_ (peel *→struct) + tinfo.fields,
|
||||
// replacing dotinnerstructptr's structinfo walk.
|
||||
// Gate is strict-equal to the deleted helper: fire
|
||||
// only when the chain root is a LOCAL ident AND every
|
||||
// dot resolves through a *struct (dotinnerstructptr
|
||||
// recursed per level on a *struct field, bailing on a
|
||||
// by-value-struct intermediate). Reproducing that
|
||||
// exactly avoids an untested widening past cstage.
|
||||
// Global-root chains stay in their pre-existing shared
|
||||
// base-eval breakage (filed #27).
|
||||
let croot: *node = base;
|
||||
let allptr: bool = true;
|
||||
for (croot != nil && croot.kind == nkind.N_DOT) {
|
||||
let ct: *tinfo = croot.type_: *tinfo;
|
||||
for (ct != nil && ct.kind == tykind.TY_NAMED) { ct = ct.under; };
|
||||
let okp: bool = false;
|
||||
if (ct != nil) { if (ct.kind == tykind.TY_PTR) {
|
||||
let cs: *tinfo = ct.sub;
|
||||
for (cs != nil && cs.kind == tykind.TY_NAMED) { cs = cs.under; };
|
||||
if (cs != nil) { if (cs.kind == tykind.TY_STRUCT) { okp = true; }; };
|
||||
}; };
|
||||
if (!okp) { allptr = false; };
|
||||
croot = croot.lhs;
|
||||
};
|
||||
let it: *tinfo = nil;
|
||||
if (allptr) { if (croot != nil) { if (croot.kind == nkind.N_IDENT) {
|
||||
if (localfindnode(c, croot.str) != nil) {
|
||||
it = base.type_: *tinfo;
|
||||
};
|
||||
}; }; };
|
||||
for (it != nil && it.kind == tykind.TY_NAMED) { it = it.under; };
|
||||
if (it != nil) { if (it.kind == tykind.TY_PTR) {
|
||||
let st: *tinfo = it.sub;
|
||||
for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; };
|
||||
if (st != nil) { if (st.kind == tykind.TY_STRUCT) {
|
||||
let tf: *tfield = st.fields;
|
||||
for (tf != nil) {
|
||||
if (streq(tf.name, fld)) {
|
||||
let ft: *tinfo = tf.type_;
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
if (isstrtype(c, fi.tnode)) {
|
||||
if (typeisstr(ft)) {
|
||||
// str rhs: AX=ptr, BX=len.
|
||||
// Stash both, then load
|
||||
// the struct ptr into CX
|
||||
@@ -18759,19 +18773,19 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "CX");
|
||||
emitdispreg(tf.offset: i64, "CX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg((fi.foff + 8): i64, "CX");
|
||||
emitdispreg((tf.offset + 8u64): i64, "CX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
// f64/f32 chained plain `=`: cgexpr rhs left value in
|
||||
// X0. Spill to stack so cgexpr(base) can use AX, then
|
||||
// reload and MOVSD/MOVSS into the slot.
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
if (typeisfloat(ft)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
||||
if (typeisf32(ft)) { mov = "MOVSS"; };
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tSUBQ\t$8, SP\n");
|
||||
emitline("\t");
|
||||
@@ -18786,7 +18800,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitdispreg(tf.offset: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
@@ -18795,19 +18809,19 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
cgexpr(c, base);
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
let sop: str = fieldstoreop(c, fi);
|
||||
let sop: str = tnodestoreop(c, n.rhs, ft.slotsize: i32);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitdispreg(tf.offset: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
};
|
||||
fi = fi.finext;
|
||||
tf = tf.tnext;
|
||||
};
|
||||
};
|
||||
};
|
||||
}; };
|
||||
}; };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user