w6c+selfhost: str globals — 16B DATAW + (LEAQ, MOVQ, MOVQ) sequences
Extend top-level mutable `let` to cover str. The cgen now:
- emits a 16-byte zero DATAW for `let s: str;` (and the trivial
`nil` / `""` inits); a non-empty strlit init is skipped because
a compile-time .data → .text reloc isn't supported yet, so the
user gets a clean undefined-symbol error at link;
- loads `s` as `(LEAQ s(SB), CX; MOVQ (CX), AX; MOVQ 8(CX), BX)`
so the (AX=ptr, BX=len) pair convention is preserved;
- stores via the same `&s` indirection for `s = expr;` and routes
the `.ptr` / `.len` pseudo-field N_DOT branch through it; and
- tracks the declared type on each LetVar so cgident / cgdot /
cgassign pick the right load/store shape.
Selfhost cgen mirrors all four paths byte-for-byte; test 990
(cgen-match on err.ww) and tests 994/995 (self-rebuild) stay
green. 630_let_global gains two new fixtures (`let msg: str;` +
runtime assign, plus reassign from a helper).
Slice and struct globals still NYI — same scope deferred.
This commit is contained in:
@@ -6129,11 +6129,20 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
// Top-level mutable `let` — RIP-relative load from its DATAW
|
||||
// slot. Mirrors C cgen's catch-all `MOVQ masym(s), AX`. Names
|
||||
// that aren't lets either (typos, never-defined) flow through
|
||||
// here too in C; the divergence today is bounded to scalar lets,
|
||||
// which is what `isletvar` gates on.
|
||||
// slot. Mirrors C cgen's catch-all `MOVQ masym(s), AX` for
|
||||
// scalar lets, plus the (LEAQ, MOVQ, MOVQ) sequence for str
|
||||
// globals so both ptr and len land in (AX, BX). Names that
|
||||
// aren't lets either (typos, never-defined) drop through to
|
||||
// the silent return.
|
||||
if (isletvar(c, nm)) {
|
||||
if (letvarisstr(c, nm)) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\t(CX), AX\n");
|
||||
emitline("\tMOVQ\t8(CX), BX\n");
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), AX\n");
|
||||
@@ -6599,6 +6608,30 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// Top-level str global field access — load .ptr / .len via
|
||||
// &name(SB) into CX, then MOVQ delta(CX), AX. Without this
|
||||
// the module-qualified fallback below would mis-emit
|
||||
// `MOVQ <field>(SB), AX`.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_IDENT) {
|
||||
if (isletvar(c, lhs.str)) {
|
||||
if (letvarisstr(c, lhs.str)) {
|
||||
let delta: i32 = -1;
|
||||
if (streq(fld, "ptr")) { delta = 0; };
|
||||
if (streq(fld, "len")) { delta = 8; };
|
||||
if (delta >= 0) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, lhs.str);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(delta: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Module-qualified value reference: `mod.name` where `mod`
|
||||
// is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local.
|
||||
// Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback
|
||||
@@ -7274,10 +7307,20 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
if (off == 0) {
|
||||
// Top-level let target: RIP-relative store
|
||||
// for `=`, or load→combine→store for the
|
||||
// compound forms.
|
||||
// compound forms. For a str global, take its
|
||||
// address into CX and store both halves; the
|
||||
// asm has no `name+8(SB)` operand form.
|
||||
if (!isletvar(c, nm)) { return; };
|
||||
cgexpr(c, n.rhs);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
if (letvarisstr(c, nm)) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\tAX, (CX)\n");
|
||||
emitline("\tMOVQ\tBX, 8(CX)\n");
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB)\n");
|
||||
@@ -8739,11 +8782,14 @@ type cgen = struct {
|
||||
};
|
||||
|
||||
// Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar.
|
||||
// Populated alongside modents; consulted by cgassign and the TK_AMP
|
||||
// path so reads/writes hit a RIP-relative DATAW slot instead of being
|
||||
// silently dropped. Only scalar (≤8B) types make the list.
|
||||
// Populated alongside modents; consulted by cgassign, cgdot, cgident
|
||||
// and the TK_AMP path so reads/writes hit a RIP-relative DATAW slot
|
||||
// instead of being silently dropped. tnode is the declared type AST
|
||||
// node — needed to distinguish scalar (8B) from str (16B) globals
|
||||
// when picking the load/store sequence.
|
||||
type letvar = struct {
|
||||
name: str,
|
||||
tnode: *node,
|
||||
lvnext: *letvar,
|
||||
};
|
||||
|
||||
@@ -8967,8 +9013,7 @@ fn internstrlit(c: *cgen, bytes: str) str = {
|
||||
// letscalarprim — recognise the bare type-name keywords whose values
|
||||
// fit in an 8-byte .data slot and load back with a plain MOVQ. Float
|
||||
// types deliberately excluded; they need MOVSS/MOVSD. Mirrors C
|
||||
// `let_scalar_ok` for the unwrapped TY_* enumeration. Pointer types
|
||||
// (N_TPTR) handle separately at the callsite.
|
||||
// `let_emit_size`'s 8-byte branch.
|
||||
fn letscalarprim(nm: str) bool = {
|
||||
if (streq(nm, "bool")) { return true; };
|
||||
if (streq(nm, "rune")) { return true; };
|
||||
@@ -8986,24 +9031,25 @@ fn letscalarprim(nm: str) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// letscalarok — true iff the N_LET decl's declared type lands in the
|
||||
// scalar set, walking type aliases. Pointer types are always ok.
|
||||
fn letscalarok(c: *cgen, d: *node) bool = {
|
||||
if (d == nil) { return false; };
|
||||
// letemitsize — slot size in bytes for a top-level `let`, or 0 if
|
||||
// the type isn't yet supported as a writable global. Walks type
|
||||
// aliases so byte output matches C cgen, which resolves Type kinds.
|
||||
// 8 → scalar (literal init supported)
|
||||
// 16 → str (only zero-init / nil / "" supported)
|
||||
fn letemitsize(c: *cgen, d: *node) i32 = {
|
||||
if (d == nil) { return 0; };
|
||||
let t: *node = d.lhs;
|
||||
for (t != nil) {
|
||||
if (t.kind == nkind.N_TPTR) { return true; };
|
||||
if (t.kind != nkind.N_TNAME) { return false; };
|
||||
if (t.kind == nkind.N_TPTR) { return 8; };
|
||||
if (t.kind != nkind.N_TNAME) { return 0; };
|
||||
let nm: str = t.str;
|
||||
if (letscalarprim(nm)) { return true; };
|
||||
// Resolve a `type x = y;` alias and look again. C cgen
|
||||
// works on the resolved Type, so byte output diverges
|
||||
// here without the walk.
|
||||
if (letscalarprim(nm)) { return 8; };
|
||||
if (streq(nm, "str")) { return 16; };
|
||||
let next: *node = aliaslookup(c, nm);
|
||||
if (next == nil) { return false; };
|
||||
if (next == nil) { return 0; };
|
||||
t = next;
|
||||
};
|
||||
return false;
|
||||
return 0;
|
||||
};
|
||||
|
||||
fn collectlets(c: *cgen, file: *node) void = {
|
||||
@@ -9014,9 +9060,10 @@ fn collectlets(c: *cgen, file: *node) void = {
|
||||
if (d.kind == nkind.N_LET) {
|
||||
let nm: str = d.str;
|
||||
if (nm.len > 0) {
|
||||
if (letscalarok(c, d)) {
|
||||
let lv: *letvar = amalloc(c.a, 32u64): *letvar;
|
||||
if (letemitsize(c, d) > 0) {
|
||||
let lv: *letvar = amalloc(c.a, 48u64): *letvar;
|
||||
lv.name = nm;
|
||||
lv.tnode = d.lhs;
|
||||
lv.lvnext = c.lets;
|
||||
c.lets = lv;
|
||||
};
|
||||
@@ -9035,18 +9082,79 @@ fn isletvar(c: *cgen, name: str) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// emitletdataw — DATAW directive per top-level scalar `let`. Same
|
||||
// 8-byte LE byte encoding as emitdefconstants; only the directive
|
||||
// keyword differs ("DATAW" vs "DATA"). Mirrors cmd/w6c/cgen.c
|
||||
// emit_lets — w6a routes DATAW into a writable .data section, and
|
||||
// w6l covers it with a second R+W PT_LOAD.
|
||||
// letvarisstr — is the named top-level let a str global? Resolves
|
||||
// aliases to mirror C cgen's `let_isstr`. Used by cgident/cgdot/
|
||||
// cgassign to pick the (LEAQ, MOVQ, MOVQ) sequence over the bare
|
||||
// MOVQ scalar load.
|
||||
fn letvarisstr(c: *cgen, name: str) bool = {
|
||||
let lv: *letvar = c.lets;
|
||||
for (lv != nil) {
|
||||
if (streq(lv.name, name)) {
|
||||
let t: *node = lv.tnode;
|
||||
for (t != nil) {
|
||||
if (t.kind != nkind.N_TNAME) { return false; };
|
||||
let nm: str = t.str;
|
||||
if (streq(nm, "str")) { return true; };
|
||||
let nx: *node = aliaslookup(c, nm);
|
||||
if (nx == nil) { return false; };
|
||||
t = nx;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
lv = lv.lvnext;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// emitdatawbyte — write one byte of an asm string literal using
|
||||
// the same escape rules as emitdefconstants / emitdatasection.
|
||||
fn emitdatawbyte(b: u8) void = {
|
||||
if (b == 34u8) { emitline("\\\""); return; };
|
||||
if (b == 92u8) { emitline("\\\\"); return; };
|
||||
if (b < 32u8) {
|
||||
emitline("\\x");
|
||||
let hi: u8 = b >> 4u8;
|
||||
let lo: u8 = b & 15u8;
|
||||
let bb: [2]u8;
|
||||
if (hi < 10u8) { bb[0] = hi + 48u8; }
|
||||
else { bb[0] = (hi - 10u8) + 97u8; };
|
||||
if (lo < 10u8) { bb[1] = lo + 48u8; }
|
||||
else { bb[1] = (lo - 10u8) + 97u8; };
|
||||
os.write(1, bb.ptr, 2u64);
|
||||
return;
|
||||
};
|
||||
if (b >= 127u8) {
|
||||
emitline("\\x");
|
||||
let hi: u8 = b >> 4u8;
|
||||
let lo: u8 = b & 15u8;
|
||||
let bb: [2]u8;
|
||||
if (hi < 10u8) { bb[0] = hi + 48u8; }
|
||||
else { bb[0] = (hi - 10u8) + 97u8; };
|
||||
if (lo < 10u8) { bb[1] = lo + 48u8; }
|
||||
else { bb[1] = (lo - 10u8) + 97u8; };
|
||||
os.write(1, bb.ptr, 2u64);
|
||||
return;
|
||||
};
|
||||
let bb: [1]u8;
|
||||
bb[0] = b;
|
||||
os.write(1, bb.ptr, 1u64);
|
||||
};
|
||||
|
||||
// emitletdataw — DATAW directive per top-level `let` global.
|
||||
// 8B scalar with int/rune/bool/nil literal init (or no init).
|
||||
// 16B str with no init (or `nil` / `""`) — zero header; the
|
||||
// program must assign a real strlit at runtime before
|
||||
// using .ptr / .len.
|
||||
// Non-literal scalar inits and non-empty strlit inits are skipped
|
||||
// so the link surfaces an undefined-symbol error.
|
||||
fn emitletdataw(c: *cgen, file: *node) void = {
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
if (d.kind == nkind.N_LET) {
|
||||
let nm: str = d.str;
|
||||
if (nm.len > 0) {
|
||||
if (letscalarok(c, d)) {
|
||||
let sz: i32 = letemitsize(c, d);
|
||||
if (sz == 8) {
|
||||
let v: u64 = 0u64;
|
||||
let ok: bool = true;
|
||||
if (d.rhs != nil) {
|
||||
@@ -9073,37 +9181,35 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
||||
for (i < 8) {
|
||||
let b: u8 = (n & 255u64): u8;
|
||||
n = n >> 8u64;
|
||||
if (b == 34u8) { emitline("\\\""); }
|
||||
else { if (b == 92u8) { emitline("\\\\"); }
|
||||
else {
|
||||
if (b < 32u8) {
|
||||
emitline("\\x");
|
||||
let hi: u8 = b >> 4u8;
|
||||
let lo: u8 = b & 15u8;
|
||||
let bb: [2]u8;
|
||||
if (hi < 10u8) { bb[0] = hi + 48u8; }
|
||||
else { bb[0] = (hi - 10u8) + 97u8; };
|
||||
if (lo < 10u8) { bb[1] = lo + 48u8; }
|
||||
else { bb[1] = (lo - 10u8) + 97u8; };
|
||||
os.write(1, bb.ptr, 2u64);
|
||||
} else {
|
||||
if (b >= 127u8) {
|
||||
emitline("\\x");
|
||||
let hi: u8 = b >> 4u8;
|
||||
let lo: u8 = b & 15u8;
|
||||
let bb: [2]u8;
|
||||
if (hi < 10u8) { bb[0] = hi + 48u8; }
|
||||
else { bb[0] = (hi - 10u8) + 97u8; };
|
||||
if (lo < 10u8) { bb[1] = lo + 48u8; }
|
||||
else { bb[1] = (lo - 10u8) + 97u8; };
|
||||
os.write(1, bb.ptr, 2u64);
|
||||
} else {
|
||||
let bb: [1]u8;
|
||||
bb[0] = b;
|
||||
os.write(1, bb.ptr, 1u64);
|
||||
};
|
||||
};
|
||||
};};
|
||||
emitdatawbyte(b);
|
||||
i += 1;
|
||||
};
|
||||
emitline("\"\n");
|
||||
};
|
||||
};
|
||||
if (sz == 16) {
|
||||
let ok: bool = true;
|
||||
if (d.rhs != nil) {
|
||||
let r: *node = d.rhs;
|
||||
for (r != nil) {
|
||||
if (r.kind != nkind.N_CAST) { break; };
|
||||
r = r.lhs;
|
||||
};
|
||||
ok = false;
|
||||
if (r != nil) {
|
||||
if (r.kind == nkind.N_NIL) { ok = true; };
|
||||
if (r.kind == nkind.N_STRLIT) {
|
||||
if (r.str.len == 0) { ok = true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
if (ok) {
|
||||
emitline("DATAW ");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB),\"");
|
||||
let i: i32 = 0;
|
||||
for (i < 16) {
|
||||
emitdatawbyte(0u8);
|
||||
i += 1;
|
||||
};
|
||||
emitline("\"\n");
|
||||
|
||||
@@ -332,11 +332,14 @@ type cgen = struct {
|
||||
};
|
||||
|
||||
// Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar.
|
||||
// Populated alongside modents; consulted by cgassign and the TK_AMP
|
||||
// path so reads/writes hit a RIP-relative DATAW slot instead of being
|
||||
// silently dropped. Only scalar (≤8B) types make the list.
|
||||
// Populated alongside modents; consulted by cgassign, cgdot, cgident
|
||||
// and the TK_AMP path so reads/writes hit a RIP-relative DATAW slot
|
||||
// instead of being silently dropped. tnode is the declared type AST
|
||||
// node — needed to distinguish scalar (8B) from str (16B) globals
|
||||
// when picking the load/store sequence.
|
||||
type letvar = struct {
|
||||
name: str,
|
||||
tnode: *node,
|
||||
lvnext: *letvar,
|
||||
};
|
||||
|
||||
@@ -560,8 +563,7 @@ fn internstrlit(c: *cgen, bytes: str) str = {
|
||||
// letscalarprim — recognise the bare type-name keywords whose values
|
||||
// fit in an 8-byte .data slot and load back with a plain MOVQ. Float
|
||||
// types deliberately excluded; they need MOVSS/MOVSD. Mirrors C
|
||||
// `let_scalar_ok` for the unwrapped TY_* enumeration. Pointer types
|
||||
// (N_TPTR) handle separately at the callsite.
|
||||
// `let_emit_size`'s 8-byte branch.
|
||||
fn letscalarprim(nm: str) bool = {
|
||||
if (streq(nm, "bool")) { return true; };
|
||||
if (streq(nm, "rune")) { return true; };
|
||||
@@ -579,24 +581,25 @@ fn letscalarprim(nm: str) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// letscalarok — true iff the N_LET decl's declared type lands in the
|
||||
// scalar set, walking type aliases. Pointer types are always ok.
|
||||
fn letscalarok(c: *cgen, d: *node) bool = {
|
||||
if (d == nil) { return false; };
|
||||
// letemitsize — slot size in bytes for a top-level `let`, or 0 if
|
||||
// the type isn't yet supported as a writable global. Walks type
|
||||
// aliases so byte output matches C cgen, which resolves Type kinds.
|
||||
// 8 → scalar (literal init supported)
|
||||
// 16 → str (only zero-init / nil / "" supported)
|
||||
fn letemitsize(c: *cgen, d: *node) i32 = {
|
||||
if (d == nil) { return 0; };
|
||||
let t: *node = d.lhs;
|
||||
for (t != nil) {
|
||||
if (t.kind == nkind.N_TPTR) { return true; };
|
||||
if (t.kind != nkind.N_TNAME) { return false; };
|
||||
if (t.kind == nkind.N_TPTR) { return 8; };
|
||||
if (t.kind != nkind.N_TNAME) { return 0; };
|
||||
let nm: str = t.str;
|
||||
if (letscalarprim(nm)) { return true; };
|
||||
// Resolve a `type x = y;` alias and look again. C cgen
|
||||
// works on the resolved Type, so byte output diverges
|
||||
// here without the walk.
|
||||
if (letscalarprim(nm)) { return 8; };
|
||||
if (streq(nm, "str")) { return 16; };
|
||||
let next: *node = aliaslookup(c, nm);
|
||||
if (next == nil) { return false; };
|
||||
if (next == nil) { return 0; };
|
||||
t = next;
|
||||
};
|
||||
return false;
|
||||
return 0;
|
||||
};
|
||||
|
||||
fn collectlets(c: *cgen, file: *node) void = {
|
||||
@@ -607,9 +610,10 @@ fn collectlets(c: *cgen, file: *node) void = {
|
||||
if (d.kind == nkind.N_LET) {
|
||||
let nm: str = d.str;
|
||||
if (nm.len > 0) {
|
||||
if (letscalarok(c, d)) {
|
||||
let lv: *letvar = amalloc(c.a, 32u64): *letvar;
|
||||
if (letemitsize(c, d) > 0) {
|
||||
let lv: *letvar = amalloc(c.a, 48u64): *letvar;
|
||||
lv.name = nm;
|
||||
lv.tnode = d.lhs;
|
||||
lv.lvnext = c.lets;
|
||||
c.lets = lv;
|
||||
};
|
||||
@@ -628,18 +632,79 @@ fn isletvar(c: *cgen, name: str) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// emitletdataw — DATAW directive per top-level scalar `let`. Same
|
||||
// 8-byte LE byte encoding as emitdefconstants; only the directive
|
||||
// keyword differs ("DATAW" vs "DATA"). Mirrors cmd/w6c/cgen.c
|
||||
// emit_lets — w6a routes DATAW into a writable .data section, and
|
||||
// w6l covers it with a second R+W PT_LOAD.
|
||||
// letvarisstr — is the named top-level let a str global? Resolves
|
||||
// aliases to mirror C cgen's `let_isstr`. Used by cgident/cgdot/
|
||||
// cgassign to pick the (LEAQ, MOVQ, MOVQ) sequence over the bare
|
||||
// MOVQ scalar load.
|
||||
fn letvarisstr(c: *cgen, name: str) bool = {
|
||||
let lv: *letvar = c.lets;
|
||||
for (lv != nil) {
|
||||
if (streq(lv.name, name)) {
|
||||
let t: *node = lv.tnode;
|
||||
for (t != nil) {
|
||||
if (t.kind != nkind.N_TNAME) { return false; };
|
||||
let nm: str = t.str;
|
||||
if (streq(nm, "str")) { return true; };
|
||||
let nx: *node = aliaslookup(c, nm);
|
||||
if (nx == nil) { return false; };
|
||||
t = nx;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
lv = lv.lvnext;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// emitdatawbyte — write one byte of an asm string literal using
|
||||
// the same escape rules as emitdefconstants / emitdatasection.
|
||||
fn emitdatawbyte(b: u8) void = {
|
||||
if (b == 34u8) { emitline("\\\""); return; };
|
||||
if (b == 92u8) { emitline("\\\\"); return; };
|
||||
if (b < 32u8) {
|
||||
emitline("\\x");
|
||||
let hi: u8 = b >> 4u8;
|
||||
let lo: u8 = b & 15u8;
|
||||
let bb: [2]u8;
|
||||
if (hi < 10u8) { bb[0] = hi + 48u8; }
|
||||
else { bb[0] = (hi - 10u8) + 97u8; };
|
||||
if (lo < 10u8) { bb[1] = lo + 48u8; }
|
||||
else { bb[1] = (lo - 10u8) + 97u8; };
|
||||
os.write(1, bb.ptr, 2u64);
|
||||
return;
|
||||
};
|
||||
if (b >= 127u8) {
|
||||
emitline("\\x");
|
||||
let hi: u8 = b >> 4u8;
|
||||
let lo: u8 = b & 15u8;
|
||||
let bb: [2]u8;
|
||||
if (hi < 10u8) { bb[0] = hi + 48u8; }
|
||||
else { bb[0] = (hi - 10u8) + 97u8; };
|
||||
if (lo < 10u8) { bb[1] = lo + 48u8; }
|
||||
else { bb[1] = (lo - 10u8) + 97u8; };
|
||||
os.write(1, bb.ptr, 2u64);
|
||||
return;
|
||||
};
|
||||
let bb: [1]u8;
|
||||
bb[0] = b;
|
||||
os.write(1, bb.ptr, 1u64);
|
||||
};
|
||||
|
||||
// emitletdataw — DATAW directive per top-level `let` global.
|
||||
// 8B scalar with int/rune/bool/nil literal init (or no init).
|
||||
// 16B str with no init (or `nil` / `""`) — zero header; the
|
||||
// program must assign a real strlit at runtime before
|
||||
// using .ptr / .len.
|
||||
// Non-literal scalar inits and non-empty strlit inits are skipped
|
||||
// so the link surfaces an undefined-symbol error.
|
||||
fn emitletdataw(c: *cgen, file: *node) void = {
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
if (d.kind == nkind.N_LET) {
|
||||
let nm: str = d.str;
|
||||
if (nm.len > 0) {
|
||||
if (letscalarok(c, d)) {
|
||||
let sz: i32 = letemitsize(c, d);
|
||||
if (sz == 8) {
|
||||
let v: u64 = 0u64;
|
||||
let ok: bool = true;
|
||||
if (d.rhs != nil) {
|
||||
@@ -666,37 +731,35 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
||||
for (i < 8) {
|
||||
let b: u8 = (n & 255u64): u8;
|
||||
n = n >> 8u64;
|
||||
if (b == 34u8) { emitline("\\\""); }
|
||||
else { if (b == 92u8) { emitline("\\\\"); }
|
||||
else {
|
||||
if (b < 32u8) {
|
||||
emitline("\\x");
|
||||
let hi: u8 = b >> 4u8;
|
||||
let lo: u8 = b & 15u8;
|
||||
let bb: [2]u8;
|
||||
if (hi < 10u8) { bb[0] = hi + 48u8; }
|
||||
else { bb[0] = (hi - 10u8) + 97u8; };
|
||||
if (lo < 10u8) { bb[1] = lo + 48u8; }
|
||||
else { bb[1] = (lo - 10u8) + 97u8; };
|
||||
os.write(1, bb.ptr, 2u64);
|
||||
} else {
|
||||
if (b >= 127u8) {
|
||||
emitline("\\x");
|
||||
let hi: u8 = b >> 4u8;
|
||||
let lo: u8 = b & 15u8;
|
||||
let bb: [2]u8;
|
||||
if (hi < 10u8) { bb[0] = hi + 48u8; }
|
||||
else { bb[0] = (hi - 10u8) + 97u8; };
|
||||
if (lo < 10u8) { bb[1] = lo + 48u8; }
|
||||
else { bb[1] = (lo - 10u8) + 97u8; };
|
||||
os.write(1, bb.ptr, 2u64);
|
||||
} else {
|
||||
let bb: [1]u8;
|
||||
bb[0] = b;
|
||||
os.write(1, bb.ptr, 1u64);
|
||||
};
|
||||
};
|
||||
};};
|
||||
emitdatawbyte(b);
|
||||
i += 1;
|
||||
};
|
||||
emitline("\"\n");
|
||||
};
|
||||
};
|
||||
if (sz == 16) {
|
||||
let ok: bool = true;
|
||||
if (d.rhs != nil) {
|
||||
let r: *node = d.rhs;
|
||||
for (r != nil) {
|
||||
if (r.kind != nkind.N_CAST) { break; };
|
||||
r = r.lhs;
|
||||
};
|
||||
ok = false;
|
||||
if (r != nil) {
|
||||
if (r.kind == nkind.N_NIL) { ok = true; };
|
||||
if (r.kind == nkind.N_STRLIT) {
|
||||
if (r.str.len == 0) { ok = true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
if (ok) {
|
||||
emitline("DATAW ");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB),\"");
|
||||
let i: i32 = 0;
|
||||
for (i < 16) {
|
||||
emitdatawbyte(0u8);
|
||||
i += 1;
|
||||
};
|
||||
emitline("\"\n");
|
||||
|
||||
@@ -401,11 +401,20 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
// Top-level mutable `let` — RIP-relative load from its DATAW
|
||||
// slot. Mirrors C cgen's catch-all `MOVQ masym(s), AX`. Names
|
||||
// that aren't lets either (typos, never-defined) flow through
|
||||
// here too in C; the divergence today is bounded to scalar lets,
|
||||
// which is what `isletvar` gates on.
|
||||
// slot. Mirrors C cgen's catch-all `MOVQ masym(s), AX` for
|
||||
// scalar lets, plus the (LEAQ, MOVQ, MOVQ) sequence for str
|
||||
// globals so both ptr and len land in (AX, BX). Names that
|
||||
// aren't lets either (typos, never-defined) drop through to
|
||||
// the silent return.
|
||||
if (isletvar(c, nm)) {
|
||||
if (letvarisstr(c, nm)) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\t(CX), AX\n");
|
||||
emitline("\tMOVQ\t8(CX), BX\n");
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), AX\n");
|
||||
@@ -871,6 +880,30 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// Top-level str global field access — load .ptr / .len via
|
||||
// &name(SB) into CX, then MOVQ delta(CX), AX. Without this
|
||||
// the module-qualified fallback below would mis-emit
|
||||
// `MOVQ <field>(SB), AX`.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_IDENT) {
|
||||
if (isletvar(c, lhs.str)) {
|
||||
if (letvarisstr(c, lhs.str)) {
|
||||
let delta: i32 = -1;
|
||||
if (streq(fld, "ptr")) { delta = 0; };
|
||||
if (streq(fld, "len")) { delta = 8; };
|
||||
if (delta >= 0) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, lhs.str);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(delta: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Module-qualified value reference: `mod.name` where `mod`
|
||||
// is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local.
|
||||
// Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback
|
||||
@@ -1546,10 +1579,20 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
if (off == 0) {
|
||||
// Top-level let target: RIP-relative store
|
||||
// for `=`, or load→combine→store for the
|
||||
// compound forms.
|
||||
// compound forms. For a str global, take its
|
||||
// address into CX and store both halves; the
|
||||
// asm has no `name+8(SB)` operand form.
|
||||
if (!isletvar(c, nm)) { return; };
|
||||
cgexpr(c, n.rhs);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
if (letvarisstr(c, nm)) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\tAX, (CX)\n");
|
||||
emitline("\tMOVQ\tBX, 8(CX)\n");
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB)\n");
|
||||
|
||||
@@ -6129,11 +6129,20 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
// Top-level mutable `let` — RIP-relative load from its DATAW
|
||||
// slot. Mirrors C cgen's catch-all `MOVQ masym(s), AX`. Names
|
||||
// that aren't lets either (typos, never-defined) flow through
|
||||
// here too in C; the divergence today is bounded to scalar lets,
|
||||
// which is what `isletvar` gates on.
|
||||
// slot. Mirrors C cgen's catch-all `MOVQ masym(s), AX` for
|
||||
// scalar lets, plus the (LEAQ, MOVQ, MOVQ) sequence for str
|
||||
// globals so both ptr and len land in (AX, BX). Names that
|
||||
// aren't lets either (typos, never-defined) drop through to
|
||||
// the silent return.
|
||||
if (isletvar(c, nm)) {
|
||||
if (letvarisstr(c, nm)) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\t(CX), AX\n");
|
||||
emitline("\tMOVQ\t8(CX), BX\n");
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), AX\n");
|
||||
@@ -6599,6 +6608,30 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// Top-level str global field access — load .ptr / .len via
|
||||
// &name(SB) into CX, then MOVQ delta(CX), AX. Without this
|
||||
// the module-qualified fallback below would mis-emit
|
||||
// `MOVQ <field>(SB), AX`.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_IDENT) {
|
||||
if (isletvar(c, lhs.str)) {
|
||||
if (letvarisstr(c, lhs.str)) {
|
||||
let delta: i32 = -1;
|
||||
if (streq(fld, "ptr")) { delta = 0; };
|
||||
if (streq(fld, "len")) { delta = 8; };
|
||||
if (delta >= 0) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, lhs.str);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(delta: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Module-qualified value reference: `mod.name` where `mod`
|
||||
// is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local.
|
||||
// Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback
|
||||
@@ -7274,10 +7307,20 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
if (off == 0) {
|
||||
// Top-level let target: RIP-relative store
|
||||
// for `=`, or load→combine→store for the
|
||||
// compound forms.
|
||||
// compound forms. For a str global, take its
|
||||
// address into CX and store both halves; the
|
||||
// asm has no `name+8(SB)` operand form.
|
||||
if (!isletvar(c, nm)) { return; };
|
||||
cgexpr(c, n.rhs);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
if (letvarisstr(c, nm)) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\tAX, (CX)\n");
|
||||
emitline("\tMOVQ\tBX, 8(CX)\n");
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB)\n");
|
||||
@@ -8739,11 +8782,14 @@ type cgen = struct {
|
||||
};
|
||||
|
||||
// Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar.
|
||||
// Populated alongside modents; consulted by cgassign and the TK_AMP
|
||||
// path so reads/writes hit a RIP-relative DATAW slot instead of being
|
||||
// silently dropped. Only scalar (≤8B) types make the list.
|
||||
// Populated alongside modents; consulted by cgassign, cgdot, cgident
|
||||
// and the TK_AMP path so reads/writes hit a RIP-relative DATAW slot
|
||||
// instead of being silently dropped. tnode is the declared type AST
|
||||
// node — needed to distinguish scalar (8B) from str (16B) globals
|
||||
// when picking the load/store sequence.
|
||||
type letvar = struct {
|
||||
name: str,
|
||||
tnode: *node,
|
||||
lvnext: *letvar,
|
||||
};
|
||||
|
||||
@@ -8967,8 +9013,7 @@ fn internstrlit(c: *cgen, bytes: str) str = {
|
||||
// letscalarprim — recognise the bare type-name keywords whose values
|
||||
// fit in an 8-byte .data slot and load back with a plain MOVQ. Float
|
||||
// types deliberately excluded; they need MOVSS/MOVSD. Mirrors C
|
||||
// `let_scalar_ok` for the unwrapped TY_* enumeration. Pointer types
|
||||
// (N_TPTR) handle separately at the callsite.
|
||||
// `let_emit_size`'s 8-byte branch.
|
||||
fn letscalarprim(nm: str) bool = {
|
||||
if (streq(nm, "bool")) { return true; };
|
||||
if (streq(nm, "rune")) { return true; };
|
||||
@@ -8986,24 +9031,25 @@ fn letscalarprim(nm: str) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// letscalarok — true iff the N_LET decl's declared type lands in the
|
||||
// scalar set, walking type aliases. Pointer types are always ok.
|
||||
fn letscalarok(c: *cgen, d: *node) bool = {
|
||||
if (d == nil) { return false; };
|
||||
// letemitsize — slot size in bytes for a top-level `let`, or 0 if
|
||||
// the type isn't yet supported as a writable global. Walks type
|
||||
// aliases so byte output matches C cgen, which resolves Type kinds.
|
||||
// 8 → scalar (literal init supported)
|
||||
// 16 → str (only zero-init / nil / "" supported)
|
||||
fn letemitsize(c: *cgen, d: *node) i32 = {
|
||||
if (d == nil) { return 0; };
|
||||
let t: *node = d.lhs;
|
||||
for (t != nil) {
|
||||
if (t.kind == nkind.N_TPTR) { return true; };
|
||||
if (t.kind != nkind.N_TNAME) { return false; };
|
||||
if (t.kind == nkind.N_TPTR) { return 8; };
|
||||
if (t.kind != nkind.N_TNAME) { return 0; };
|
||||
let nm: str = t.str;
|
||||
if (letscalarprim(nm)) { return true; };
|
||||
// Resolve a `type x = y;` alias and look again. C cgen
|
||||
// works on the resolved Type, so byte output diverges
|
||||
// here without the walk.
|
||||
if (letscalarprim(nm)) { return 8; };
|
||||
if (streq(nm, "str")) { return 16; };
|
||||
let next: *node = aliaslookup(c, nm);
|
||||
if (next == nil) { return false; };
|
||||
if (next == nil) { return 0; };
|
||||
t = next;
|
||||
};
|
||||
return false;
|
||||
return 0;
|
||||
};
|
||||
|
||||
fn collectlets(c: *cgen, file: *node) void = {
|
||||
@@ -9014,9 +9060,10 @@ fn collectlets(c: *cgen, file: *node) void = {
|
||||
if (d.kind == nkind.N_LET) {
|
||||
let nm: str = d.str;
|
||||
if (nm.len > 0) {
|
||||
if (letscalarok(c, d)) {
|
||||
let lv: *letvar = amalloc(c.a, 32u64): *letvar;
|
||||
if (letemitsize(c, d) > 0) {
|
||||
let lv: *letvar = amalloc(c.a, 48u64): *letvar;
|
||||
lv.name = nm;
|
||||
lv.tnode = d.lhs;
|
||||
lv.lvnext = c.lets;
|
||||
c.lets = lv;
|
||||
};
|
||||
@@ -9035,18 +9082,79 @@ fn isletvar(c: *cgen, name: str) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// emitletdataw — DATAW directive per top-level scalar `let`. Same
|
||||
// 8-byte LE byte encoding as emitdefconstants; only the directive
|
||||
// keyword differs ("DATAW" vs "DATA"). Mirrors cmd/w6c/cgen.c
|
||||
// emit_lets — w6a routes DATAW into a writable .data section, and
|
||||
// w6l covers it with a second R+W PT_LOAD.
|
||||
// letvarisstr — is the named top-level let a str global? Resolves
|
||||
// aliases to mirror C cgen's `let_isstr`. Used by cgident/cgdot/
|
||||
// cgassign to pick the (LEAQ, MOVQ, MOVQ) sequence over the bare
|
||||
// MOVQ scalar load.
|
||||
fn letvarisstr(c: *cgen, name: str) bool = {
|
||||
let lv: *letvar = c.lets;
|
||||
for (lv != nil) {
|
||||
if (streq(lv.name, name)) {
|
||||
let t: *node = lv.tnode;
|
||||
for (t != nil) {
|
||||
if (t.kind != nkind.N_TNAME) { return false; };
|
||||
let nm: str = t.str;
|
||||
if (streq(nm, "str")) { return true; };
|
||||
let nx: *node = aliaslookup(c, nm);
|
||||
if (nx == nil) { return false; };
|
||||
t = nx;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
lv = lv.lvnext;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// emitdatawbyte — write one byte of an asm string literal using
|
||||
// the same escape rules as emitdefconstants / emitdatasection.
|
||||
fn emitdatawbyte(b: u8) void = {
|
||||
if (b == 34u8) { emitline("\\\""); return; };
|
||||
if (b == 92u8) { emitline("\\\\"); return; };
|
||||
if (b < 32u8) {
|
||||
emitline("\\x");
|
||||
let hi: u8 = b >> 4u8;
|
||||
let lo: u8 = b & 15u8;
|
||||
let bb: [2]u8;
|
||||
if (hi < 10u8) { bb[0] = hi + 48u8; }
|
||||
else { bb[0] = (hi - 10u8) + 97u8; };
|
||||
if (lo < 10u8) { bb[1] = lo + 48u8; }
|
||||
else { bb[1] = (lo - 10u8) + 97u8; };
|
||||
os.write(1, bb.ptr, 2u64);
|
||||
return;
|
||||
};
|
||||
if (b >= 127u8) {
|
||||
emitline("\\x");
|
||||
let hi: u8 = b >> 4u8;
|
||||
let lo: u8 = b & 15u8;
|
||||
let bb: [2]u8;
|
||||
if (hi < 10u8) { bb[0] = hi + 48u8; }
|
||||
else { bb[0] = (hi - 10u8) + 97u8; };
|
||||
if (lo < 10u8) { bb[1] = lo + 48u8; }
|
||||
else { bb[1] = (lo - 10u8) + 97u8; };
|
||||
os.write(1, bb.ptr, 2u64);
|
||||
return;
|
||||
};
|
||||
let bb: [1]u8;
|
||||
bb[0] = b;
|
||||
os.write(1, bb.ptr, 1u64);
|
||||
};
|
||||
|
||||
// emitletdataw — DATAW directive per top-level `let` global.
|
||||
// 8B scalar with int/rune/bool/nil literal init (or no init).
|
||||
// 16B str with no init (or `nil` / `""`) — zero header; the
|
||||
// program must assign a real strlit at runtime before
|
||||
// using .ptr / .len.
|
||||
// Non-literal scalar inits and non-empty strlit inits are skipped
|
||||
// so the link surfaces an undefined-symbol error.
|
||||
fn emitletdataw(c: *cgen, file: *node) void = {
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
if (d.kind == nkind.N_LET) {
|
||||
let nm: str = d.str;
|
||||
if (nm.len > 0) {
|
||||
if (letscalarok(c, d)) {
|
||||
let sz: i32 = letemitsize(c, d);
|
||||
if (sz == 8) {
|
||||
let v: u64 = 0u64;
|
||||
let ok: bool = true;
|
||||
if (d.rhs != nil) {
|
||||
@@ -9073,37 +9181,35 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
||||
for (i < 8) {
|
||||
let b: u8 = (n & 255u64): u8;
|
||||
n = n >> 8u64;
|
||||
if (b == 34u8) { emitline("\\\""); }
|
||||
else { if (b == 92u8) { emitline("\\\\"); }
|
||||
else {
|
||||
if (b < 32u8) {
|
||||
emitline("\\x");
|
||||
let hi: u8 = b >> 4u8;
|
||||
let lo: u8 = b & 15u8;
|
||||
let bb: [2]u8;
|
||||
if (hi < 10u8) { bb[0] = hi + 48u8; }
|
||||
else { bb[0] = (hi - 10u8) + 97u8; };
|
||||
if (lo < 10u8) { bb[1] = lo + 48u8; }
|
||||
else { bb[1] = (lo - 10u8) + 97u8; };
|
||||
os.write(1, bb.ptr, 2u64);
|
||||
} else {
|
||||
if (b >= 127u8) {
|
||||
emitline("\\x");
|
||||
let hi: u8 = b >> 4u8;
|
||||
let lo: u8 = b & 15u8;
|
||||
let bb: [2]u8;
|
||||
if (hi < 10u8) { bb[0] = hi + 48u8; }
|
||||
else { bb[0] = (hi - 10u8) + 97u8; };
|
||||
if (lo < 10u8) { bb[1] = lo + 48u8; }
|
||||
else { bb[1] = (lo - 10u8) + 97u8; };
|
||||
os.write(1, bb.ptr, 2u64);
|
||||
} else {
|
||||
let bb: [1]u8;
|
||||
bb[0] = b;
|
||||
os.write(1, bb.ptr, 1u64);
|
||||
};
|
||||
};
|
||||
};};
|
||||
emitdatawbyte(b);
|
||||
i += 1;
|
||||
};
|
||||
emitline("\"\n");
|
||||
};
|
||||
};
|
||||
if (sz == 16) {
|
||||
let ok: bool = true;
|
||||
if (d.rhs != nil) {
|
||||
let r: *node = d.rhs;
|
||||
for (r != nil) {
|
||||
if (r.kind != nkind.N_CAST) { break; };
|
||||
r = r.lhs;
|
||||
};
|
||||
ok = false;
|
||||
if (r != nil) {
|
||||
if (r.kind == nkind.N_NIL) { ok = true; };
|
||||
if (r.kind == nkind.N_STRLIT) {
|
||||
if (r.str.len == 0) { ok = true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
if (ok) {
|
||||
emitline("DATAW ");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB),\"");
|
||||
let i: i32 = 0;
|
||||
for (i < 16) {
|
||||
emitdatawbyte(0u8);
|
||||
i += 1;
|
||||
};
|
||||
emitline("\"\n");
|
||||
|
||||
Reference in New Issue
Block a user