w6c+selfhost: codegen for top-level mutable let
Third step toward writable globals. The C cgen and its selfhost
mirror now:
- emit DATAW <name>(SB),"<8 LE bytes>" for every top-level `let`
whose type lands in the scalar set (i8..i64/u8..u64/bool/rune/
int/uint/uintptr/ptr; floats and multi-word types deferred);
- drop the "no writable .data" silent-drop guard at the N_IDENT
store path, replacing it with a RIP-relative MOVQ for `=` and
a load→combine→store sequence for the compound ops; and
- route `&name` through LEAQ name(SB) instead of dropping it.
Type aliases resolve via aliaslookup so `type counter = i32; let c:
counter = 0;` still emits a DATAW slot. Non-literal initialisers
silently skip, which surfaces as a clean undefined-symbol error if
the binding is ever referenced.
The selfhost mirror lands in the same commit because test 990
diffs the C cgen against wwdump_ww -c on err.ww (which has
top-level `let nerrors: i32 = 0; ... nerrors += 1;`). Any drift
between the two cgens makes 990 fail. Bootstrap stays at a fixed
point: ww2 == ww3 == ww4 byte-identical.
This commit is contained in:
@@ -6678,6 +6678,13 @@ fn cgun(c: *cgen, n: *node) void = {
|
||||
emitline("(BP), AX\n");
|
||||
return;
|
||||
};
|
||||
// Top-level mutable let — RIP-relative LEAQ.
|
||||
if (isletvar(c, nm)) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), AX\n");
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
return;
|
||||
@@ -7253,7 +7260,52 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
if (lhs.kind == nkind.N_IDENT) {
|
||||
let nm: str = lhs.str;
|
||||
let off: i32 = localfind(c, nm);
|
||||
if (off == 0) { return; };
|
||||
if (off == 0) {
|
||||
// Top-level let target: RIP-relative store
|
||||
// for `=`, or load→combine→store for the
|
||||
// compound forms.
|
||||
if (!isletvar(c, nm)) { return; };
|
||||
cgexpr(c, n.rhs);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB)\n");
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), BX\n");
|
||||
let didcompound: bool = true;
|
||||
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, BX\n"); }
|
||||
else { if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); }
|
||||
else { if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); }
|
||||
else { if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); }
|
||||
else { if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); }
|
||||
else { if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tAX, BX\n"); }
|
||||
else { if (n.op == tkind.TK_LSHIFTEQ) {
|
||||
emitline("\tMOVQ\tAX, CX\n");
|
||||
emitline("\tSHLQ\tCX, BX\n");
|
||||
}
|
||||
else { if (n.op == tkind.TK_RSHIFTEQ) {
|
||||
emitline("\tMOVQ\tAX, CX\n");
|
||||
emitline("\tSHRQ\tCX, BX\n");
|
||||
}
|
||||
else {
|
||||
// Unsupported compound: store rhs
|
||||
// directly. Mirrors the local path's
|
||||
// fallback for TK_SLASHEQ etc.
|
||||
didcompound = false;
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB)\n");
|
||||
};};};};};};};};
|
||||
if (didcompound) {
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
// Detect str-typed local — assignment must store both
|
||||
// halves (AX=ptr at +0, BX=len at +8).
|
||||
let lcstr: bool = false;
|
||||
@@ -8326,6 +8378,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
|
||||
collectfnrets(c, file);
|
||||
fficollect(c, file);
|
||||
collectmods(c, file);
|
||||
collectlets(c, file);
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
if (d.kind == nkind.N_FNDECL) {
|
||||
@@ -8337,6 +8390,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
|
||||
};
|
||||
emitdatasection(c);
|
||||
emitdefconstants(c, file);
|
||||
emitletdataw(c, file);
|
||||
};
|
||||
|
||||
// MODULE: wcc
|
||||
@@ -8661,6 +8715,7 @@ type cgen = struct {
|
||||
structs: *structinfo,
|
||||
enums: *enumtype,
|
||||
mods: *modent, // non-exported decls → originating module
|
||||
lets: *letvar, // top-level mutable scalar `let` bindings
|
||||
fnname: str,
|
||||
fnret: *node, // declared return type of current fn (or nil)
|
||||
looptop: i32,
|
||||
@@ -8672,6 +8727,15 @@ type cgen = struct {
|
||||
deferbuf: **node, // stack of deferred exprs (LIFO at return)
|
||||
};
|
||||
|
||||
// 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.
|
||||
type letvar = struct {
|
||||
name: str,
|
||||
lvnext: *letvar,
|
||||
};
|
||||
|
||||
fn cgeninit(c: *cgen, a: *arena) void = {
|
||||
c.a = a;
|
||||
c.locals = nil;
|
||||
@@ -8889,6 +8953,157 @@ fn internstrlit(c: *cgen, bytes: str) str = {
|
||||
return lab;
|
||||
};
|
||||
|
||||
// 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.
|
||||
fn letscalarprim(nm: str) bool = {
|
||||
if (streq(nm, "bool")) { return true; };
|
||||
if (streq(nm, "rune")) { return true; };
|
||||
if (streq(nm, "i8")) { return true; };
|
||||
if (streq(nm, "i16")) { return true; };
|
||||
if (streq(nm, "i32")) { return true; };
|
||||
if (streq(nm, "i64")) { return true; };
|
||||
if (streq(nm, "u8")) { return true; };
|
||||
if (streq(nm, "u16")) { return true; };
|
||||
if (streq(nm, "u32")) { return true; };
|
||||
if (streq(nm, "u64")) { return true; };
|
||||
if (streq(nm, "int")) { return true; };
|
||||
if (streq(nm, "uint")) { return true; };
|
||||
if (streq(nm, "uintptr")) { return true; };
|
||||
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; };
|
||||
let t: *node = d.lhs;
|
||||
for (t != nil) {
|
||||
if (t.kind == nkind.N_TPTR) { return true; };
|
||||
if (t.kind != nkind.N_TNAME) { return false; };
|
||||
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.
|
||||
let next: *node = aliaslookup(c, nm);
|
||||
if (next == nil) { return false; };
|
||||
t = next;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
fn collectlets(c: *cgen, file: *node) void = {
|
||||
c.lets = nil;
|
||||
if (file == nil) { return; };
|
||||
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 lv: *letvar = amalloc(c.a, 32u64): *letvar;
|
||||
lv.name = nm;
|
||||
lv.lvnext = c.lets;
|
||||
c.lets = lv;
|
||||
};
|
||||
};
|
||||
};
|
||||
d = d.next;
|
||||
};
|
||||
};
|
||||
|
||||
fn isletvar(c: *cgen, name: str) bool = {
|
||||
let lv: *letvar = c.lets;
|
||||
for (lv != nil) {
|
||||
if (streq(lv.name, name)) { return true; };
|
||||
lv = lv.lvnext;
|
||||
};
|
||||
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.
|
||||
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 v: u64 = 0u64;
|
||||
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_INTLIT) { v = r.uval; ok = true; };
|
||||
if (r.kind == nkind.N_RUNELIT) { v = r.uval; ok = true; };
|
||||
if (r.kind == nkind.N_TRUE) { v = 1u64; ok = true; };
|
||||
if (r.kind == nkind.N_FALSE) { v = 0u64; ok = true; };
|
||||
if (r.kind == nkind.N_NIL) { v = 0u64; ok = true; };
|
||||
};
|
||||
};
|
||||
if (ok) {
|
||||
emitline("DATAW ");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB),\"");
|
||||
let i: i32 = 0;
|
||||
let n: u64 = v;
|
||||
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);
|
||||
};
|
||||
};
|
||||
};};
|
||||
i += 1;
|
||||
};
|
||||
emitline("\"\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
d = d.next;
|
||||
};
|
||||
};
|
||||
|
||||
// emitdefconstants — DATA directive per top-level int-literal `def`.
|
||||
// 8 bytes little-endian to match what the C cgen emits.
|
||||
fn emitdefconstants(c: *cgen, file: *node) void = {
|
||||
|
||||
@@ -319,6 +319,7 @@ type cgen = struct {
|
||||
structs: *structinfo,
|
||||
enums: *enumtype,
|
||||
mods: *modent, // non-exported decls → originating module
|
||||
lets: *letvar, // top-level mutable scalar `let` bindings
|
||||
fnname: str,
|
||||
fnret: *node, // declared return type of current fn (or nil)
|
||||
looptop: i32,
|
||||
@@ -330,6 +331,15 @@ type cgen = struct {
|
||||
deferbuf: **node, // stack of deferred exprs (LIFO at return)
|
||||
};
|
||||
|
||||
// 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.
|
||||
type letvar = struct {
|
||||
name: str,
|
||||
lvnext: *letvar,
|
||||
};
|
||||
|
||||
fn cgeninit(c: *cgen, a: *arena) void = {
|
||||
c.a = a;
|
||||
c.locals = nil;
|
||||
@@ -547,6 +557,157 @@ fn internstrlit(c: *cgen, bytes: str) str = {
|
||||
return lab;
|
||||
};
|
||||
|
||||
// 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.
|
||||
fn letscalarprim(nm: str) bool = {
|
||||
if (streq(nm, "bool")) { return true; };
|
||||
if (streq(nm, "rune")) { return true; };
|
||||
if (streq(nm, "i8")) { return true; };
|
||||
if (streq(nm, "i16")) { return true; };
|
||||
if (streq(nm, "i32")) { return true; };
|
||||
if (streq(nm, "i64")) { return true; };
|
||||
if (streq(nm, "u8")) { return true; };
|
||||
if (streq(nm, "u16")) { return true; };
|
||||
if (streq(nm, "u32")) { return true; };
|
||||
if (streq(nm, "u64")) { return true; };
|
||||
if (streq(nm, "int")) { return true; };
|
||||
if (streq(nm, "uint")) { return true; };
|
||||
if (streq(nm, "uintptr")) { return true; };
|
||||
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; };
|
||||
let t: *node = d.lhs;
|
||||
for (t != nil) {
|
||||
if (t.kind == nkind.N_TPTR) { return true; };
|
||||
if (t.kind != nkind.N_TNAME) { return false; };
|
||||
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.
|
||||
let next: *node = aliaslookup(c, nm);
|
||||
if (next == nil) { return false; };
|
||||
t = next;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
fn collectlets(c: *cgen, file: *node) void = {
|
||||
c.lets = nil;
|
||||
if (file == nil) { return; };
|
||||
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 lv: *letvar = amalloc(c.a, 32u64): *letvar;
|
||||
lv.name = nm;
|
||||
lv.lvnext = c.lets;
|
||||
c.lets = lv;
|
||||
};
|
||||
};
|
||||
};
|
||||
d = d.next;
|
||||
};
|
||||
};
|
||||
|
||||
fn isletvar(c: *cgen, name: str) bool = {
|
||||
let lv: *letvar = c.lets;
|
||||
for (lv != nil) {
|
||||
if (streq(lv.name, name)) { return true; };
|
||||
lv = lv.lvnext;
|
||||
};
|
||||
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.
|
||||
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 v: u64 = 0u64;
|
||||
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_INTLIT) { v = r.uval; ok = true; };
|
||||
if (r.kind == nkind.N_RUNELIT) { v = r.uval; ok = true; };
|
||||
if (r.kind == nkind.N_TRUE) { v = 1u64; ok = true; };
|
||||
if (r.kind == nkind.N_FALSE) { v = 0u64; ok = true; };
|
||||
if (r.kind == nkind.N_NIL) { v = 0u64; ok = true; };
|
||||
};
|
||||
};
|
||||
if (ok) {
|
||||
emitline("DATAW ");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB),\"");
|
||||
let i: i32 = 0;
|
||||
let n: u64 = v;
|
||||
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);
|
||||
};
|
||||
};
|
||||
};};
|
||||
i += 1;
|
||||
};
|
||||
emitline("\"\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
d = d.next;
|
||||
};
|
||||
};
|
||||
|
||||
// emitdefconstants — DATA directive per top-level int-literal `def`.
|
||||
// 8 bytes little-endian to match what the C cgen emits.
|
||||
fn emitdefconstants(c: *cgen, file: *node) void = {
|
||||
|
||||
@@ -287,6 +287,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
|
||||
collectfnrets(c, file);
|
||||
fficollect(c, file);
|
||||
collectmods(c, file);
|
||||
collectlets(c, file);
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
if (d.kind == nkind.N_FNDECL) {
|
||||
@@ -298,4 +299,5 @@ export fn cgfile(c: *cgen, file: *node) void = {
|
||||
};
|
||||
emitdatasection(c);
|
||||
emitdefconstants(c, file);
|
||||
emitletdataw(c, file);
|
||||
};
|
||||
|
||||
@@ -950,6 +950,13 @@ fn cgun(c: *cgen, n: *node) void = {
|
||||
emitline("(BP), AX\n");
|
||||
return;
|
||||
};
|
||||
// Top-level mutable let — RIP-relative LEAQ.
|
||||
if (isletvar(c, nm)) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), AX\n");
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
return;
|
||||
@@ -1525,7 +1532,52 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
if (lhs.kind == nkind.N_IDENT) {
|
||||
let nm: str = lhs.str;
|
||||
let off: i32 = localfind(c, nm);
|
||||
if (off == 0) { return; };
|
||||
if (off == 0) {
|
||||
// Top-level let target: RIP-relative store
|
||||
// for `=`, or load→combine→store for the
|
||||
// compound forms.
|
||||
if (!isletvar(c, nm)) { return; };
|
||||
cgexpr(c, n.rhs);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB)\n");
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), BX\n");
|
||||
let didcompound: bool = true;
|
||||
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, BX\n"); }
|
||||
else { if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); }
|
||||
else { if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); }
|
||||
else { if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); }
|
||||
else { if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); }
|
||||
else { if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tAX, BX\n"); }
|
||||
else { if (n.op == tkind.TK_LSHIFTEQ) {
|
||||
emitline("\tMOVQ\tAX, CX\n");
|
||||
emitline("\tSHLQ\tCX, BX\n");
|
||||
}
|
||||
else { if (n.op == tkind.TK_RSHIFTEQ) {
|
||||
emitline("\tMOVQ\tAX, CX\n");
|
||||
emitline("\tSHRQ\tCX, BX\n");
|
||||
}
|
||||
else {
|
||||
// Unsupported compound: store rhs
|
||||
// directly. Mirrors the local path's
|
||||
// fallback for TK_SLASHEQ etc.
|
||||
didcompound = false;
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB)\n");
|
||||
};};};};};};};};
|
||||
if (didcompound) {
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
// Detect str-typed local — assignment must store both
|
||||
// halves (AX=ptr at +0, BX=len at +8).
|
||||
let lcstr: bool = false;
|
||||
|
||||
@@ -6678,6 +6678,13 @@ fn cgun(c: *cgen, n: *node) void = {
|
||||
emitline("(BP), AX\n");
|
||||
return;
|
||||
};
|
||||
// Top-level mutable let — RIP-relative LEAQ.
|
||||
if (isletvar(c, nm)) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), AX\n");
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
return;
|
||||
@@ -7253,7 +7260,52 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
if (lhs.kind == nkind.N_IDENT) {
|
||||
let nm: str = lhs.str;
|
||||
let off: i32 = localfind(c, nm);
|
||||
if (off == 0) { return; };
|
||||
if (off == 0) {
|
||||
// Top-level let target: RIP-relative store
|
||||
// for `=`, or load→combine→store for the
|
||||
// compound forms.
|
||||
if (!isletvar(c, nm)) { return; };
|
||||
cgexpr(c, n.rhs);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB)\n");
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), BX\n");
|
||||
let didcompound: bool = true;
|
||||
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, BX\n"); }
|
||||
else { if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); }
|
||||
else { if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); }
|
||||
else { if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); }
|
||||
else { if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); }
|
||||
else { if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tAX, BX\n"); }
|
||||
else { if (n.op == tkind.TK_LSHIFTEQ) {
|
||||
emitline("\tMOVQ\tAX, CX\n");
|
||||
emitline("\tSHLQ\tCX, BX\n");
|
||||
}
|
||||
else { if (n.op == tkind.TK_RSHIFTEQ) {
|
||||
emitline("\tMOVQ\tAX, CX\n");
|
||||
emitline("\tSHRQ\tCX, BX\n");
|
||||
}
|
||||
else {
|
||||
// Unsupported compound: store rhs
|
||||
// directly. Mirrors the local path's
|
||||
// fallback for TK_SLASHEQ etc.
|
||||
didcompound = false;
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB)\n");
|
||||
};};};};};};};};
|
||||
if (didcompound) {
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
// Detect str-typed local — assignment must store both
|
||||
// halves (AX=ptr at +0, BX=len at +8).
|
||||
let lcstr: bool = false;
|
||||
@@ -8326,6 +8378,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
|
||||
collectfnrets(c, file);
|
||||
fficollect(c, file);
|
||||
collectmods(c, file);
|
||||
collectlets(c, file);
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
if (d.kind == nkind.N_FNDECL) {
|
||||
@@ -8337,6 +8390,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
|
||||
};
|
||||
emitdatasection(c);
|
||||
emitdefconstants(c, file);
|
||||
emitletdataw(c, file);
|
||||
};
|
||||
|
||||
// MODULE: wcc
|
||||
@@ -8661,6 +8715,7 @@ type cgen = struct {
|
||||
structs: *structinfo,
|
||||
enums: *enumtype,
|
||||
mods: *modent, // non-exported decls → originating module
|
||||
lets: *letvar, // top-level mutable scalar `let` bindings
|
||||
fnname: str,
|
||||
fnret: *node, // declared return type of current fn (or nil)
|
||||
looptop: i32,
|
||||
@@ -8672,6 +8727,15 @@ type cgen = struct {
|
||||
deferbuf: **node, // stack of deferred exprs (LIFO at return)
|
||||
};
|
||||
|
||||
// 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.
|
||||
type letvar = struct {
|
||||
name: str,
|
||||
lvnext: *letvar,
|
||||
};
|
||||
|
||||
fn cgeninit(c: *cgen, a: *arena) void = {
|
||||
c.a = a;
|
||||
c.locals = nil;
|
||||
@@ -8889,6 +8953,157 @@ fn internstrlit(c: *cgen, bytes: str) str = {
|
||||
return lab;
|
||||
};
|
||||
|
||||
// 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.
|
||||
fn letscalarprim(nm: str) bool = {
|
||||
if (streq(nm, "bool")) { return true; };
|
||||
if (streq(nm, "rune")) { return true; };
|
||||
if (streq(nm, "i8")) { return true; };
|
||||
if (streq(nm, "i16")) { return true; };
|
||||
if (streq(nm, "i32")) { return true; };
|
||||
if (streq(nm, "i64")) { return true; };
|
||||
if (streq(nm, "u8")) { return true; };
|
||||
if (streq(nm, "u16")) { return true; };
|
||||
if (streq(nm, "u32")) { return true; };
|
||||
if (streq(nm, "u64")) { return true; };
|
||||
if (streq(nm, "int")) { return true; };
|
||||
if (streq(nm, "uint")) { return true; };
|
||||
if (streq(nm, "uintptr")) { return true; };
|
||||
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; };
|
||||
let t: *node = d.lhs;
|
||||
for (t != nil) {
|
||||
if (t.kind == nkind.N_TPTR) { return true; };
|
||||
if (t.kind != nkind.N_TNAME) { return false; };
|
||||
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.
|
||||
let next: *node = aliaslookup(c, nm);
|
||||
if (next == nil) { return false; };
|
||||
t = next;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
fn collectlets(c: *cgen, file: *node) void = {
|
||||
c.lets = nil;
|
||||
if (file == nil) { return; };
|
||||
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 lv: *letvar = amalloc(c.a, 32u64): *letvar;
|
||||
lv.name = nm;
|
||||
lv.lvnext = c.lets;
|
||||
c.lets = lv;
|
||||
};
|
||||
};
|
||||
};
|
||||
d = d.next;
|
||||
};
|
||||
};
|
||||
|
||||
fn isletvar(c: *cgen, name: str) bool = {
|
||||
let lv: *letvar = c.lets;
|
||||
for (lv != nil) {
|
||||
if (streq(lv.name, name)) { return true; };
|
||||
lv = lv.lvnext;
|
||||
};
|
||||
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.
|
||||
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 v: u64 = 0u64;
|
||||
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_INTLIT) { v = r.uval; ok = true; };
|
||||
if (r.kind == nkind.N_RUNELIT) { v = r.uval; ok = true; };
|
||||
if (r.kind == nkind.N_TRUE) { v = 1u64; ok = true; };
|
||||
if (r.kind == nkind.N_FALSE) { v = 0u64; ok = true; };
|
||||
if (r.kind == nkind.N_NIL) { v = 0u64; ok = true; };
|
||||
};
|
||||
};
|
||||
if (ok) {
|
||||
emitline("DATAW ");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB),\"");
|
||||
let i: i32 = 0;
|
||||
let n: u64 = v;
|
||||
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);
|
||||
};
|
||||
};
|
||||
};};
|
||||
i += 1;
|
||||
};
|
||||
emitline("\"\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
d = d.next;
|
||||
};
|
||||
};
|
||||
|
||||
// emitdefconstants — DATA directive per top-level int-literal `def`.
|
||||
// 8 bytes little-endian to match what the C cgen emits.
|
||||
fn emitdefconstants(c: *cgen, file: *node) void = {
|
||||
|
||||
Reference in New Issue
Block a user