w6c+wwstage: chained-base array-field address via dotbaseaddr — close the family (#253)

cg_dotbase_addr / dotbaseaddr rejected a non-ident inner, so a chained
base (`o.p.m[i]` / `o.i.m[i]` / `o.a.b.m[i]`) fell to cgexpr(base) which
auto-derefs the array field's first 8 bytes AS a pointer -> garbage base
-> segfault (base64 fillobuf `s.enc.encmap[...]` blocker). Extend the one
helper per stage to accept a chained inner: a new cg_dotchain_addr /
dotchainaddr recovers the container base via the dot-chain spine (recurse
to &x, deref when x is a *struct, sum field offsets), keeping the same
no-AX/no-stack spill contract. dotbaseaddr then takes the pointer VALUE of
inner when viaptr, else its ADDRESS, and adds the field offset. One fix
closes every op (index r/w, addr-of, slice, compound) since all route
through the helper. Symmetric cs==ww byte-id.

test/949: +22 rows. Chained-PTR (rd/wr/addr/slice x2/compound), deeper
(value+ptr leaf links, triple-pointer exercising the internal deref),
non-u8 esz stride (i32 addr+slice), and single-level controls — all
byte-id. The chained VALUE-container arm (`o.i.m`) is run-only (byteid=0):
it needs a value nested-struct instance, which trips THREE orthogonal
pre-existing cs!=ww emission divergences (bare-let zero-init policy,
global DATAW byte count, i32 element-load opcode in the index fallback)
unrelated to #253. Run correctness proves the segfault is gone for that
cell; byte-id there awaits the separate wwstage value-nested-struct fix.
This commit is contained in:
2026-06-02 03:44:49 +09:00
parent 7e3271bf01
commit 585ec50676
5 changed files with 713 additions and 107 deletions

View File

@@ -20190,8 +20190,84 @@ fn cgslicehdr(c: *cgen, base: str) void = {
if (streq(base, "AX")) { emitmovqload(0i64, base, "AX"); };
};
// dotchainaddr — emit the ADDRESS of a dot/ident lvalue chain into
// `dstreg`, dereferencing pointer links mid-chain. Returns true on
// success, false if a link isn't a struct / ptr-to-struct it can
// resolve. Recursion mirrors the cstage read spine: for `x.f`, recurse
// to &x, deref if x is a *struct (so dstreg holds the pointee base),
// then add f's offset. Touches ONLY dstreg (no AX, no stack) — same
// spill contract as dotbaseaddr. The chained-base arm of dotbaseaddr
// (#253) is its sole caller. Cstage twin: cmd/w6c/cgen.c
// `cg_dotchain_addr`.
fn dotchainaddr(c: *cgen, n: *node, dstreg: str) bool = {
if (n == nil) { return false; };
if (n.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, n.str);
if (lc != nil) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), ");
emitline(dstreg);
emitline("\n");
return true;
};
emitline("\tLEAQ\t");
emitsymname(c, n.str);
emitline("(SB), ");
emitline(dstreg);
emitline("\n");
return true;
};
if (n.kind != nkind.N_DOT) { return false; };
let x: *node = n.lhs;
if (x == nil) { return false; };
let xu: *tinfo = x.type_: *tinfo;
for (xu != nil && xu.kind == tykind.TY_NAMED) { xu = xu.under; };
if (xu == nil) { return false; };
let xviaptr: bool = false;
let st: *tinfo = nil;
if (xu.kind == tykind.TY_PTR) {
let p: *tinfo = xu.sub;
for (p != nil && p.kind == tykind.TY_NAMED) { p = p.under; };
if (p != nil) { if (p.kind == tykind.TY_STRUCT) {
st = p;
xviaptr = true;
}; };
} else { if (xu.kind == tykind.TY_STRUCT) {
st = xu;
}; };
if (st == nil) { return false; };
let f: *tfield = st.fields;
let foff: i64 = -1;
for (f != nil) {
if (streq(f.name, n.str)) {
foff = f.offset: i64;
break;
};
f = f.tnext;
};
if (foff < 0) { return false; };
if (!dotchainaddr(c, x, dstreg)) { return false; };
if (xviaptr) {
emitline("\tMOVQ\t(");
emitline(dstreg);
emitline("), ");
emitline(dstreg);
emitline("\n");
};
if (foff != 0) {
emitline("\tADDQ\t$");
emitint(foff);
emitline(", ");
emitline(dstreg);
emitline("\n");
};
return true;
};
// dotbaseaddr — emit `&(inner.field)` into `dstreg` when `base` is an
// N_DOT with N_IDENT inner. Returns true if emitted; callers fall back
// N_DOT with N_IDENT inner OR a chained N_DOT inner (#253: `o.p.m` /
// `o.i.m` / `o.a.b.m`). Returns true if emitted; callers fall back
// to `cgexpr(c, base); MOVQ AX, dstreg` on false. Cstage twin:
// cmd/w6c/cgen.c `cg_dotbase_addr`.
//
@@ -20200,39 +20276,49 @@ fn cgslicehdr(c: *cgen, base: str) void = {
// an LHS or index-base shape (`d.fld[i] = v` / `d.fld[i]` read / `d.fld
// [i] OP= v`), the caller wants the field's ADDRESS — this helper
// supplies it inline. Reusable primitive of the inverse template
// `arr[i].field = v` (cstage cgen.c arr[i].field address-eval). Chained
// N_DOT (`a.b.c.field[i]`) deferred — not in #135 scope.
// `arr[i].field = v` (cstage cgen.c arr[i].field address-eval).
//
// #253: a chained inner (`inner` is itself an N_DOT) routes through
// dotchainaddr to recover the container base — the pointer VALUE of
// inner when inner is a *struct (viaptr), else the ADDRESS of inner —
// then adds the field offset. Closes the array-field-base-address
// family across every op (index r/w, addr-of, slice, compound).
fn dotbaseaddr(c: *cgen, base: *node, dstreg: str) bool = {
if (base == nil) { return false; };
if (base.kind != nkind.N_DOT) { return false; };
let inner: *node = base.lhs;
if (inner == nil) { return false; };
if (inner.kind != nkind.N_IDENT) { return false; };
let chained: bool = (inner.kind == nkind.N_DOT);
if (inner.kind != nkind.N_IDENT && !chained) { return false; };
// #128b: module-qualified `mod.arr` where arr is an imported
// top-level `let X: [N]T`. The checker leaves SK_USE module-
// idents without a localfindnode entry; detect via letvartnode
// resolving to N_TARRAY and emit LEAQ X(SB). Without this, the
// cgindex fallback's cgexpr(base) auto-MOVQs the symbol's first
// 8 bytes as if it were a pointer-var — wrong shape (cstage
// sister fix in cg_dotbase_addr).
let lc: *local = localfindnode(c, inner.str);
// sister fix in cg_dotbase_addr). N_IDENT-inner only — a chained
// inner has a valid stamped type_ and routes through dotchainaddr.
let lc: *local = nil;
let isglobal: bool = false;
if (lc == nil) {
let gt: *node = letvartnode(c, base.str);
if (gt != nil && gt.kind == nkind.N_TARRAY) {
emitline("\tLEAQ\t");
emitsymname(c, base.str);
emitline("(SB), ");
emitline(dstreg);
emitline("\n");
return true;
if (!chained) {
lc = localfindnode(c, inner.str);
if (lc == nil) {
let gt: *node = letvartnode(c, base.str);
if (gt != nil && gt.kind == nkind.N_TARRAY) {
emitline("\tLEAQ\t");
emitsymname(c, base.str);
emitline("(SB), ");
emitline(dstreg);
emitline("\n");
return true;
};
// #249 (sibling of #135): inner is a module-GLOBAL struct value
// (let/def), not a local — lc is nil but inner.type_ is a valid
// struct. Resolve the field below and emit a global base (LEAQ
// name(SB)). A non-struct inner (e.g. an SK_USE module qualifier,
// type ty_err) falls through the struct gate to `return false`.
isglobal = true;
};
// #249 (sibling of #135): inner is a module-GLOBAL struct value
// (let/def), not a local — lc is nil but inner.type_ is a valid
// struct. Resolve the field below and emit a global base (LEAQ
// name(SB)). A non-struct inner (e.g. an SK_USE module qualifier,
// type ty_err) falls through the struct gate to `return false`.
isglobal = true;
};
let bu: *tinfo = inner.type_: *tinfo;
for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
@@ -20269,6 +20355,27 @@ fn dotbaseaddr(c: *cgen, base: *node, dstreg: str) bool = {
for (ft != nil && ft.kind == tykind.TY_NAMED) { ft = ft.under; };
if (ft == nil) { return false; };
if (ft.kind != tykind.TY_ARRAY) { return false; };
// #253: chained inner — compute the container base via the dot-chain
// spine (pointer VALUE of inner when viaptr, else its ADDRESS), then
// add the field offset. dotchainaddr keeps the spill contract.
if (chained) {
if (!dotchainaddr(c, inner, dstreg)) { return false; };
if (viaptr) {
emitline("\tMOVQ\t(");
emitline(dstreg);
emitline("), ");
emitline(dstreg);
emitline("\n");
};
if (foff != 0) {
emitline("\tADDQ\t$");
emitint(foff);
emitline(", ");
emitline(dstreg);
emitline("\n");
};
return true;
};
let innoff: i64 = 0;
if (lc != nil) { innoff = lc.off: i64; };
if (viaptr) {

View File

@@ -892,8 +892,84 @@ fn cgslicehdr(c: *cgen, base: str) void = {
if (streq(base, "AX")) { emitmovqload(0i64, base, "AX"); };
};
// dotchainaddr — emit the ADDRESS of a dot/ident lvalue chain into
// `dstreg`, dereferencing pointer links mid-chain. Returns true on
// success, false if a link isn't a struct / ptr-to-struct it can
// resolve. Recursion mirrors the cstage read spine: for `x.f`, recurse
// to &x, deref if x is a *struct (so dstreg holds the pointee base),
// then add f's offset. Touches ONLY dstreg (no AX, no stack) — same
// spill contract as dotbaseaddr. The chained-base arm of dotbaseaddr
// (#253) is its sole caller. Cstage twin: cmd/w6c/cgen.c
// `cg_dotchain_addr`.
fn dotchainaddr(c: *cgen, n: *node, dstreg: str) bool = {
if (n == nil) { return false; };
if (n.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, n.str);
if (lc != nil) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), ");
emitline(dstreg);
emitline("\n");
return true;
};
emitline("\tLEAQ\t");
emitsymname(c, n.str);
emitline("(SB), ");
emitline(dstreg);
emitline("\n");
return true;
};
if (n.kind != nkind.N_DOT) { return false; };
let x: *node = n.lhs;
if (x == nil) { return false; };
let xu: *tinfo = x.type_: *tinfo;
for (xu != nil && xu.kind == tykind.TY_NAMED) { xu = xu.under; };
if (xu == nil) { return false; };
let xviaptr: bool = false;
let st: *tinfo = nil;
if (xu.kind == tykind.TY_PTR) {
let p: *tinfo = xu.sub;
for (p != nil && p.kind == tykind.TY_NAMED) { p = p.under; };
if (p != nil) { if (p.kind == tykind.TY_STRUCT) {
st = p;
xviaptr = true;
}; };
} else { if (xu.kind == tykind.TY_STRUCT) {
st = xu;
}; };
if (st == nil) { return false; };
let f: *tfield = st.fields;
let foff: i64 = -1;
for (f != nil) {
if (streq(f.name, n.str)) {
foff = f.offset: i64;
break;
};
f = f.tnext;
};
if (foff < 0) { return false; };
if (!dotchainaddr(c, x, dstreg)) { return false; };
if (xviaptr) {
emitline("\tMOVQ\t(");
emitline(dstreg);
emitline("), ");
emitline(dstreg);
emitline("\n");
};
if (foff != 0) {
emitline("\tADDQ\t$");
emitint(foff);
emitline(", ");
emitline(dstreg);
emitline("\n");
};
return true;
};
// dotbaseaddr — emit `&(inner.field)` into `dstreg` when `base` is an
// N_DOT with N_IDENT inner. Returns true if emitted; callers fall back
// N_DOT with N_IDENT inner OR a chained N_DOT inner (#253: `o.p.m` /
// `o.i.m` / `o.a.b.m`). Returns true if emitted; callers fall back
// to `cgexpr(c, base); MOVQ AX, dstreg` on false. Cstage twin:
// cmd/w6c/cgen.c `cg_dotbase_addr`.
//
@@ -902,39 +978,49 @@ fn cgslicehdr(c: *cgen, base: str) void = {
// an LHS or index-base shape (`d.fld[i] = v` / `d.fld[i]` read / `d.fld
// [i] OP= v`), the caller wants the field's ADDRESS — this helper
// supplies it inline. Reusable primitive of the inverse template
// `arr[i].field = v` (cstage cgen.c arr[i].field address-eval). Chained
// N_DOT (`a.b.c.field[i]`) deferred — not in #135 scope.
// `arr[i].field = v` (cstage cgen.c arr[i].field address-eval).
//
// #253: a chained inner (`inner` is itself an N_DOT) routes through
// dotchainaddr to recover the container base — the pointer VALUE of
// inner when inner is a *struct (viaptr), else the ADDRESS of inner —
// then adds the field offset. Closes the array-field-base-address
// family across every op (index r/w, addr-of, slice, compound).
fn dotbaseaddr(c: *cgen, base: *node, dstreg: str) bool = {
if (base == nil) { return false; };
if (base.kind != nkind.N_DOT) { return false; };
let inner: *node = base.lhs;
if (inner == nil) { return false; };
if (inner.kind != nkind.N_IDENT) { return false; };
let chained: bool = (inner.kind == nkind.N_DOT);
if (inner.kind != nkind.N_IDENT && !chained) { return false; };
// #128b: module-qualified `mod.arr` where arr is an imported
// top-level `let X: [N]T`. The checker leaves SK_USE module-
// idents without a localfindnode entry; detect via letvartnode
// resolving to N_TARRAY and emit LEAQ X(SB). Without this, the
// cgindex fallback's cgexpr(base) auto-MOVQs the symbol's first
// 8 bytes as if it were a pointer-var — wrong shape (cstage
// sister fix in cg_dotbase_addr).
let lc: *local = localfindnode(c, inner.str);
// sister fix in cg_dotbase_addr). N_IDENT-inner only — a chained
// inner has a valid stamped type_ and routes through dotchainaddr.
let lc: *local = nil;
let isglobal: bool = false;
if (lc == nil) {
let gt: *node = letvartnode(c, base.str);
if (gt != nil && gt.kind == nkind.N_TARRAY) {
emitline("\tLEAQ\t");
emitsymname(c, base.str);
emitline("(SB), ");
emitline(dstreg);
emitline("\n");
return true;
if (!chained) {
lc = localfindnode(c, inner.str);
if (lc == nil) {
let gt: *node = letvartnode(c, base.str);
if (gt != nil && gt.kind == nkind.N_TARRAY) {
emitline("\tLEAQ\t");
emitsymname(c, base.str);
emitline("(SB), ");
emitline(dstreg);
emitline("\n");
return true;
};
// #249 (sibling of #135): inner is a module-GLOBAL struct value
// (let/def), not a local — lc is nil but inner.type_ is a valid
// struct. Resolve the field below and emit a global base (LEAQ
// name(SB)). A non-struct inner (e.g. an SK_USE module qualifier,
// type ty_err) falls through the struct gate to `return false`.
isglobal = true;
};
// #249 (sibling of #135): inner is a module-GLOBAL struct value
// (let/def), not a local — lc is nil but inner.type_ is a valid
// struct. Resolve the field below and emit a global base (LEAQ
// name(SB)). A non-struct inner (e.g. an SK_USE module qualifier,
// type ty_err) falls through the struct gate to `return false`.
isglobal = true;
};
let bu: *tinfo = inner.type_: *tinfo;
for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
@@ -971,6 +1057,27 @@ fn dotbaseaddr(c: *cgen, base: *node, dstreg: str) bool = {
for (ft != nil && ft.kind == tykind.TY_NAMED) { ft = ft.under; };
if (ft == nil) { return false; };
if (ft.kind != tykind.TY_ARRAY) { return false; };
// #253: chained inner — compute the container base via the dot-chain
// spine (pointer VALUE of inner when viaptr, else its ADDRESS), then
// add the field offset. dotchainaddr keeps the spill contract.
if (chained) {
if (!dotchainaddr(c, inner, dstreg)) { return false; };
if (viaptr) {
emitline("\tMOVQ\t(");
emitline(dstreg);
emitline("), ");
emitline(dstreg);
emitline("\n");
};
if (foff != 0) {
emitline("\tADDQ\t$");
emitint(foff);
emitline(", ");
emitline(dstreg);
emitline("\n");
};
return true;
};
let innoff: i64 = 0;
if (lc != nil) { innoff = lc.off: i64; };
if (viaptr) {

View File

@@ -20190,8 +20190,84 @@ fn cgslicehdr(c: *cgen, base: str) void = {
if (streq(base, "AX")) { emitmovqload(0i64, base, "AX"); };
};
// dotchainaddr — emit the ADDRESS of a dot/ident lvalue chain into
// `dstreg`, dereferencing pointer links mid-chain. Returns true on
// success, false if a link isn't a struct / ptr-to-struct it can
// resolve. Recursion mirrors the cstage read spine: for `x.f`, recurse
// to &x, deref if x is a *struct (so dstreg holds the pointee base),
// then add f's offset. Touches ONLY dstreg (no AX, no stack) — same
// spill contract as dotbaseaddr. The chained-base arm of dotbaseaddr
// (#253) is its sole caller. Cstage twin: cmd/w6c/cgen.c
// `cg_dotchain_addr`.
fn dotchainaddr(c: *cgen, n: *node, dstreg: str) bool = {
if (n == nil) { return false; };
if (n.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, n.str);
if (lc != nil) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), ");
emitline(dstreg);
emitline("\n");
return true;
};
emitline("\tLEAQ\t");
emitsymname(c, n.str);
emitline("(SB), ");
emitline(dstreg);
emitline("\n");
return true;
};
if (n.kind != nkind.N_DOT) { return false; };
let x: *node = n.lhs;
if (x == nil) { return false; };
let xu: *tinfo = x.type_: *tinfo;
for (xu != nil && xu.kind == tykind.TY_NAMED) { xu = xu.under; };
if (xu == nil) { return false; };
let xviaptr: bool = false;
let st: *tinfo = nil;
if (xu.kind == tykind.TY_PTR) {
let p: *tinfo = xu.sub;
for (p != nil && p.kind == tykind.TY_NAMED) { p = p.under; };
if (p != nil) { if (p.kind == tykind.TY_STRUCT) {
st = p;
xviaptr = true;
}; };
} else { if (xu.kind == tykind.TY_STRUCT) {
st = xu;
}; };
if (st == nil) { return false; };
let f: *tfield = st.fields;
let foff: i64 = -1;
for (f != nil) {
if (streq(f.name, n.str)) {
foff = f.offset: i64;
break;
};
f = f.tnext;
};
if (foff < 0) { return false; };
if (!dotchainaddr(c, x, dstreg)) { return false; };
if (xviaptr) {
emitline("\tMOVQ\t(");
emitline(dstreg);
emitline("), ");
emitline(dstreg);
emitline("\n");
};
if (foff != 0) {
emitline("\tADDQ\t$");
emitint(foff);
emitline(", ");
emitline(dstreg);
emitline("\n");
};
return true;
};
// dotbaseaddr — emit `&(inner.field)` into `dstreg` when `base` is an
// N_DOT with N_IDENT inner. Returns true if emitted; callers fall back
// N_DOT with N_IDENT inner OR a chained N_DOT inner (#253: `o.p.m` /
// `o.i.m` / `o.a.b.m`). Returns true if emitted; callers fall back
// to `cgexpr(c, base); MOVQ AX, dstreg` on false. Cstage twin:
// cmd/w6c/cgen.c `cg_dotbase_addr`.
//
@@ -20200,39 +20276,49 @@ fn cgslicehdr(c: *cgen, base: str) void = {
// an LHS or index-base shape (`d.fld[i] = v` / `d.fld[i]` read / `d.fld
// [i] OP= v`), the caller wants the field's ADDRESS — this helper
// supplies it inline. Reusable primitive of the inverse template
// `arr[i].field = v` (cstage cgen.c arr[i].field address-eval). Chained
// N_DOT (`a.b.c.field[i]`) deferred — not in #135 scope.
// `arr[i].field = v` (cstage cgen.c arr[i].field address-eval).
//
// #253: a chained inner (`inner` is itself an N_DOT) routes through
// dotchainaddr to recover the container base — the pointer VALUE of
// inner when inner is a *struct (viaptr), else the ADDRESS of inner —
// then adds the field offset. Closes the array-field-base-address
// family across every op (index r/w, addr-of, slice, compound).
fn dotbaseaddr(c: *cgen, base: *node, dstreg: str) bool = {
if (base == nil) { return false; };
if (base.kind != nkind.N_DOT) { return false; };
let inner: *node = base.lhs;
if (inner == nil) { return false; };
if (inner.kind != nkind.N_IDENT) { return false; };
let chained: bool = (inner.kind == nkind.N_DOT);
if (inner.kind != nkind.N_IDENT && !chained) { return false; };
// #128b: module-qualified `mod.arr` where arr is an imported
// top-level `let X: [N]T`. The checker leaves SK_USE module-
// idents without a localfindnode entry; detect via letvartnode
// resolving to N_TARRAY and emit LEAQ X(SB). Without this, the
// cgindex fallback's cgexpr(base) auto-MOVQs the symbol's first
// 8 bytes as if it were a pointer-var — wrong shape (cstage
// sister fix in cg_dotbase_addr).
let lc: *local = localfindnode(c, inner.str);
// sister fix in cg_dotbase_addr). N_IDENT-inner only — a chained
// inner has a valid stamped type_ and routes through dotchainaddr.
let lc: *local = nil;
let isglobal: bool = false;
if (lc == nil) {
let gt: *node = letvartnode(c, base.str);
if (gt != nil && gt.kind == nkind.N_TARRAY) {
emitline("\tLEAQ\t");
emitsymname(c, base.str);
emitline("(SB), ");
emitline(dstreg);
emitline("\n");
return true;
if (!chained) {
lc = localfindnode(c, inner.str);
if (lc == nil) {
let gt: *node = letvartnode(c, base.str);
if (gt != nil && gt.kind == nkind.N_TARRAY) {
emitline("\tLEAQ\t");
emitsymname(c, base.str);
emitline("(SB), ");
emitline(dstreg);
emitline("\n");
return true;
};
// #249 (sibling of #135): inner is a module-GLOBAL struct value
// (let/def), not a local — lc is nil but inner.type_ is a valid
// struct. Resolve the field below and emit a global base (LEAQ
// name(SB)). A non-struct inner (e.g. an SK_USE module qualifier,
// type ty_err) falls through the struct gate to `return false`.
isglobal = true;
};
// #249 (sibling of #135): inner is a module-GLOBAL struct value
// (let/def), not a local — lc is nil but inner.type_ is a valid
// struct. Resolve the field below and emit a global base (LEAQ
// name(SB)). A non-struct inner (e.g. an SK_USE module qualifier,
// type ty_err) falls through the struct gate to `return false`.
isglobal = true;
};
let bu: *tinfo = inner.type_: *tinfo;
for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
@@ -20269,6 +20355,27 @@ fn dotbaseaddr(c: *cgen, base: *node, dstreg: str) bool = {
for (ft != nil && ft.kind == tykind.TY_NAMED) { ft = ft.under; };
if (ft == nil) { return false; };
if (ft.kind != tykind.TY_ARRAY) { return false; };
// #253: chained inner — compute the container base via the dot-chain
// spine (pointer VALUE of inner when viaptr, else its ADDRESS), then
// add the field offset. dotchainaddr keeps the spill contract.
if (chained) {
if (!dotchainaddr(c, inner, dstreg)) { return false; };
if (viaptr) {
emitline("\tMOVQ\t(");
emitline(dstreg);
emitline("), ");
emitline(dstreg);
emitline("\n");
};
if (foff != 0) {
emitline("\tADDQ\t$");
emitint(foff);
emitline(", ");
emitline(dstreg);
emitline("\n");
};
return true;
};
let innoff: i64 = 0;
if (lc != nil) { innoff = lc.off: i64; };
if (viaptr) {