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:
134
cmd/w6c/cgen.c
134
cmd/w6c/cgen.c
@@ -397,13 +397,13 @@ struct LetVar {
|
||||
};
|
||||
static LetVar *letvars;
|
||||
|
||||
/* Subset of types we know how to store in 8 bytes of .data and load
|
||||
* back with a plain MOVQ. Floats need MOVSS/MOVSD; str/slice/struct/
|
||||
* tagged unions are multi-word; enums route through their storage
|
||||
* type. Keep this tight — extending it requires the matching load/
|
||||
* store code below. */
|
||||
/* Slot size for a top-level `let` of type t, or 0 if the type isn't
|
||||
* supported as a writable global yet. Floats (MOVSS/SD) and slice/
|
||||
* struct/tagged unions are deferred. enums route through their
|
||||
* storage type. Keep this tight — extending it requires the matching
|
||||
* load/store code below. */
|
||||
static int
|
||||
let_scalar_ok(Type *t)
|
||||
let_emit_size(Type *t)
|
||||
{
|
||||
if (t == NULL) return 0;
|
||||
Type *u = (t->kind == TY_NAMED) ? t->under : t;
|
||||
@@ -414,12 +414,25 @@ let_scalar_ok(Type *t)
|
||||
case TY_U8: case TY_U16: case TY_U32: case TY_U64:
|
||||
case TY_INT: case TY_UINT: case TY_UINTPTR:
|
||||
case TY_PTR:
|
||||
return 1;
|
||||
return 8;
|
||||
case TY_STR:
|
||||
return 16; /* {ptr, len}; literal-strlit init NYI. */
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Is the unwrapped type a str? Used by the load/store paths so the
|
||||
* (AX, BX) pair convention is preserved for str globals, mirroring
|
||||
* what we already do for str locals. */
|
||||
static int
|
||||
let_isstr(Type *t)
|
||||
{
|
||||
if (t == NULL) return 0;
|
||||
Type *u = (t->kind == TY_NAMED) ? t->under : t;
|
||||
return u && u->kind == TY_STR;
|
||||
}
|
||||
|
||||
static int
|
||||
decl_has_ffisym(Node *d)
|
||||
{
|
||||
@@ -476,7 +489,7 @@ let_collect(Cg *c, Node *file)
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (d->kind != N_LET) continue;
|
||||
if (d->str == NULL || d->str[0] == '\0') continue;
|
||||
if (!let_scalar_ok(d->type)) continue;
|
||||
if (let_emit_size(d->type) == 0) continue;
|
||||
LetVar *lv = amalloc(c->a, sizeof *lv);
|
||||
lv->name = d->str;
|
||||
lv->next = letvars;
|
||||
@@ -751,6 +764,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
areg(D_BX));
|
||||
goto ident_done;
|
||||
}
|
||||
if (let_islet(n->str) && let_isstr(n->type)) {
|
||||
/* Top-level str global: load both halves via
|
||||
* its address (the asm has no `name+8(SB)`
|
||||
* operand form). */
|
||||
ins2(c, A_LEAQ, masym(c, n->str), areg(D_CX));
|
||||
ins2(c, A_MOVQ, amem(D_CX, 0), areg(D_AX));
|
||||
ins2(c, A_MOVQ, amem(D_CX, 8), areg(D_BX));
|
||||
goto ident_done;
|
||||
}
|
||||
ins2(c, A_MOVQ, masym(c, n->str), areg(D_AX));
|
||||
}
|
||||
ident_done:
|
||||
@@ -1421,19 +1443,31 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
/* Plain `name = strexpr;` for a str-typed local. cgexpr leaves
|
||||
* (AX=ptr, BX=len); store both halves at off+0 and off+8.
|
||||
* Mirrors the let-init shape so reassignment doesn't truncate. */
|
||||
* Mirrors the let-init shape so reassignment doesn't truncate.
|
||||
* Top-level str globals follow the same shape but go through
|
||||
* &name(SB) since the asm has no `name+8(SB)` operand form. */
|
||||
if (n->lhs && n->lhs->kind == N_IDENT && n->op == TK_ASSIGN
|
||||
&& n->lhs->type) {
|
||||
Type *lt = n->lhs->type;
|
||||
Type *lu = (lt && lt->kind == TY_NAMED) ? lt->under : lt;
|
||||
if (lu && lu->kind == TY_STR) {
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
if (off == 0) break;
|
||||
if (off != 0) {
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off + 8));
|
||||
break;
|
||||
}
|
||||
if (let_islet(n->lhs->str)) {
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins2(c, A_LEAQ, masym(c, n->lhs->str),
|
||||
areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_CX, 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_CX, 8));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (n->lhs->kind == N_IDENT) {
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
@@ -2429,6 +2463,20 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
goto dot_done;
|
||||
}
|
||||
/* Top-level str/slice `let` — load
|
||||
* the field through &name(SB). Same
|
||||
* pattern as the bare N_IDENT load. */
|
||||
if (let_islet(n->lhs->str)) {
|
||||
int delta = ptrfld ? 0
|
||||
: (lenfld ? 8 : 16);
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, n->lhs->str),
|
||||
areg(D_CX));
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_CX, delta),
|
||||
areg(D_AX));
|
||||
goto dot_done;
|
||||
}
|
||||
}
|
||||
int delta = ptrfld ? 0 : (lenfld ? 8 : 16);
|
||||
ins2(c, A_MOVQ, amem(D_BP, off + delta),
|
||||
@@ -3570,15 +3618,11 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
||||
txt_emit(out, c->head);
|
||||
}
|
||||
|
||||
/* Emit a single 8-byte DATA/DATAW row for a scalar value. Shares
|
||||
* the escape rules with emit_defs/emit_data so the .o bytes stay
|
||||
* stable. */
|
||||
/* Escape one byte for an asm string literal — the same rules
|
||||
* emit_data and emit_defs already use. */
|
||||
static void
|
||||
emit_data_row(FILE *out, const char *dir, const char *name, u64 v)
|
||||
emit_data_byte(FILE *out, u8 b)
|
||||
{
|
||||
fprintf(out, "%s %s(SB),\"", dir, name);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
unsigned b = (unsigned)((v >> (i * 8)) & 0xff);
|
||||
if (b == '"' || b == '\\')
|
||||
fprintf(out, "\\%c", b);
|
||||
else if (b < 0x20 || b >= 0x7f)
|
||||
@@ -3586,21 +3630,48 @@ emit_data_row(FILE *out, const char *dir, const char *name, u64 v)
|
||||
else
|
||||
fputc(b, out);
|
||||
}
|
||||
|
||||
/* Emit `DIR NAME(SB),"<8 LE bytes of v>"`. Used for scalar `def`
|
||||
* constants (DATA) and scalar `let` globals (DATAW). */
|
||||
static void
|
||||
emit_data_row(FILE *out, const char *dir, const char *name, u64 v)
|
||||
{
|
||||
fprintf(out, "%s %s(SB),\"", dir, name);
|
||||
for (int i = 0; i < 8; i++)
|
||||
emit_data_byte(out, (u8)((v >> (i * 8)) & 0xff));
|
||||
fputs("\"\n", out);
|
||||
}
|
||||
|
||||
/* Emit DATAW directives for top-level mutable `let` decls. Only scalar
|
||||
* types in `let_scalar_ok` are supported; the rest are silently
|
||||
* skipped at cgen and link with an undefined-symbol error if used.
|
||||
* Initialisers must be integer/rune/bool/nil literals (or a `let`
|
||||
* with no init, which zero-initialises). */
|
||||
/* Emit `DIR NAME(SB),"<sz zero bytes>"`. Used for top-level str/
|
||||
* slice/struct lets without a baked-in initialiser — the slot is
|
||||
* pre-zeroed and the program writes the real value at runtime. */
|
||||
static void
|
||||
emit_data_row_zero(FILE *out, const char *dir, const char *name, int sz)
|
||||
{
|
||||
fprintf(out, "%s %s(SB),\"", dir, name);
|
||||
for (int i = 0; i < sz; i++)
|
||||
emit_data_byte(out, 0);
|
||||
fputs("\"\n", out);
|
||||
}
|
||||
|
||||
/* Emit DATAW directives for top-level mutable `let` decls.
|
||||
*
|
||||
* Scalar lets (8B): emit the literal value, or 0 if no init.
|
||||
* Non-literal init: skip — undefined symbol surfaces at link time.
|
||||
*
|
||||
* str lets (16B): emit 16 zero bytes when there is no init (or
|
||||
* the init is `nil` / `""`). A non-empty strlit init would need a
|
||||
* compile-time .data → .text relocation (asm doesn't support that
|
||||
* yet); we silently skip and let the link fail loudly. */
|
||||
static void
|
||||
emit_lets(Cg *c, FILE *out, Node *file)
|
||||
{
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (d->kind != N_LET) continue;
|
||||
if (d->str == NULL || d->str[0] == '\0') continue;
|
||||
if (!let_scalar_ok(d->type)) continue;
|
||||
int sz = let_emit_size(d->type);
|
||||
if (sz == 0) continue;
|
||||
if (sz == 8) {
|
||||
u64 v = 0;
|
||||
if (d->rhs != NULL) {
|
||||
Node *r = d->rhs;
|
||||
@@ -3611,9 +3682,24 @@ emit_lets(Cg *c, FILE *out, Node *file)
|
||||
else if (r->kind == N_TRUE) v = 1;
|
||||
else if (r->kind == N_FALSE) v = 0;
|
||||
else if (r->kind == N_NIL) v = 0;
|
||||
else continue; /* non-literal init: skip */
|
||||
else continue;
|
||||
}
|
||||
emit_data_row(out, "DATAW", mod_mangle(c, d->str), v);
|
||||
continue;
|
||||
}
|
||||
/* Multi-word (str, 16B). Only zero-init shapes are
|
||||
* supported: no rhs, or `nil`, or `""` (which interns to
|
||||
* a strlit but we still emit a zero header — the program
|
||||
* has to assign a real strlit at runtime to use it). */
|
||||
if (d->rhs != NULL) {
|
||||
Node *r = d->rhs;
|
||||
while (r != NULL && r->kind == N_CAST) r = r->lhs;
|
||||
if (r == NULL) continue;
|
||||
int empty_str = (r->kind == N_STRLIT && r->strlen == 0);
|
||||
if (r->kind != N_NIL && !empty_str)
|
||||
continue;
|
||||
}
|
||||
emit_data_row_zero(out, "DATAW", mod_mangle(c, d->str), sz);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -91,6 +91,28 @@ static const struct fixture fixtures[] = {
|
||||
"fn main() i32 = { return val: i32; };\n",
|
||||
123,
|
||||
},
|
||||
{
|
||||
"str-zeroinit",
|
||||
/* `let msg: str;` zero-inits the {ptr,len} slot. Assigning
|
||||
* a strlit at runtime updates both halves; .len then reads
|
||||
* the stored length. */
|
||||
"let msg: str;\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tmsg = \"hello\";\n"
|
||||
"\treturn msg.len: i32;\n"
|
||||
"};\n",
|
||||
5,
|
||||
},
|
||||
{
|
||||
"str-reassign",
|
||||
"let msg: str;\n"
|
||||
"fn set(s: str) void = { msg = s; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tset(\"abcdefg\");\n"
|
||||
"\treturn msg.len: i32;\n"
|
||||
"};\n",
|
||||
7,
|
||||
},
|
||||
{ NULL, NULL, 0 }
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user