w6c+selfhost: slice globals — 24B DATAW + (AX,BX,CX) load
Extend top-level mutable `let` to cover slices. Same shape as the
str work, with one more 8-byte field and the address holder CX
overwritten by the cap as the last load step:
- emit_lets / emitletdataw: 24-byte zero DATAW for `let v: []u8;`
(and the trivial `nil` init); no slice-literal syntax exists
so the no-init path is the only supported shape;
- cgident: LEAQ name(SB), CX → MOVQ (CX), AX → MOVQ 8(CX), BX →
MOVQ 16(CX), CX, so the slice ABI triple lands in (AX, BX, CX);
- cgdot: .cap delta 16 wired alongside .ptr / .len through the
same &name(SB) base.
Slice reassignment (`v = some_slice;`) is still unsupported — slice
values don't yet flow as a full (AX, BX, CX) triple through general
expressions even for locals — so reads/`&` are the supported surface
today. Manual fill through `(&v): *u64` continues to work.
Tests 630 (10/10), 990, 994, 995 stay green; bootstrap fixed point
holds.
This commit is contained in:
@@ -398,9 +398,9 @@ struct LetVar {
|
|||||||
static LetVar *letvars;
|
static LetVar *letvars;
|
||||||
|
|
||||||
/* Slot size for a top-level `let` of type t, or 0 if the type isn't
|
/* 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/
|
* supported as a writable global yet. Floats (MOVSS/SD) and struct/
|
||||||
* struct/tagged unions are deferred. enums route through their
|
* tagged unions are deferred. enums route through their storage
|
||||||
* storage type. Keep this tight — extending it requires the matching
|
* type. Keep this tight — extending it requires the matching
|
||||||
* load/store code below. */
|
* load/store code below. */
|
||||||
static int
|
static int
|
||||||
let_emit_size(Type *t)
|
let_emit_size(Type *t)
|
||||||
@@ -417,6 +417,8 @@ let_emit_size(Type *t)
|
|||||||
return 8;
|
return 8;
|
||||||
case TY_STR:
|
case TY_STR:
|
||||||
return 16; /* {ptr, len}; literal-strlit init NYI. */
|
return 16; /* {ptr, len}; literal-strlit init NYI. */
|
||||||
|
case TY_SLICE:
|
||||||
|
return 24; /* {ptr, len, cap}; no init only. */
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -433,6 +435,16 @@ let_isstr(Type *t)
|
|||||||
return u && u->kind == TY_STR;
|
return u && u->kind == TY_STR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Is the unwrapped type a slice? Slice globals flow as the (AX, BX,
|
||||||
|
* CX) triple — same as the local ABI. */
|
||||||
|
static int
|
||||||
|
let_isslice(Type *t)
|
||||||
|
{
|
||||||
|
if (t == NULL) return 0;
|
||||||
|
Type *u = (t->kind == TY_NAMED) ? t->under : t;
|
||||||
|
return u && u->kind == TY_SLICE;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
decl_has_ffisym(Node *d)
|
decl_has_ffisym(Node *d)
|
||||||
{
|
{
|
||||||
@@ -764,13 +776,20 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
areg(D_BX));
|
areg(D_BX));
|
||||||
goto ident_done;
|
goto ident_done;
|
||||||
}
|
}
|
||||||
if (let_islet(n->str) && let_isstr(n->type)) {
|
if (let_islet(n->str)
|
||||||
/* Top-level str global: load both halves via
|
&& (let_isstr(n->type) || let_isslice(n->type))) {
|
||||||
* its address (the asm has no `name+8(SB)`
|
/* Top-level str/slice global: load each half
|
||||||
* operand form). */
|
* via its address (the asm has no `name+8(SB)`
|
||||||
|
* operand form). Slice has a third 8B (cap)
|
||||||
|
* — the address holder CX gets overwritten by
|
||||||
|
* the cap as the last step, after we no longer
|
||||||
|
* need it. */
|
||||||
|
int is_slice = let_isslice(n->type);
|
||||||
ins2(c, A_LEAQ, masym(c, n->str), areg(D_CX));
|
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, 0), areg(D_AX));
|
||||||
ins2(c, A_MOVQ, amem(D_CX, 8), areg(D_BX));
|
ins2(c, A_MOVQ, amem(D_CX, 8), areg(D_BX));
|
||||||
|
if (is_slice)
|
||||||
|
ins2(c, A_MOVQ, amem(D_CX, 16), areg(D_CX));
|
||||||
goto ident_done;
|
goto ident_done;
|
||||||
}
|
}
|
||||||
ins2(c, A_MOVQ, masym(c, n->str), areg(D_AX));
|
ins2(c, A_MOVQ, masym(c, n->str), areg(D_AX));
|
||||||
@@ -3662,7 +3681,10 @@ emit_data_row_zero(FILE *out, const char *dir, const char *name, int sz)
|
|||||||
* str lets (16B): emit 16 zero bytes when there is no init (or
|
* 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
|
* the init is `nil` / `""`). A non-empty strlit init would need a
|
||||||
* compile-time .data → .text relocation (asm doesn't support that
|
* compile-time .data → .text relocation (asm doesn't support that
|
||||||
* yet); we silently skip and let the link fail loudly. */
|
* yet); we silently skip and let the link fail loudly.
|
||||||
|
*
|
||||||
|
* Slice lets (24B): no-init only — the slot is zero. There's no
|
||||||
|
* literal slice syntax to honour, so this is the natural shape. */
|
||||||
static void
|
static void
|
||||||
emit_lets(Cg *c, FILE *out, Node *file)
|
emit_lets(Cg *c, FILE *out, Node *file)
|
||||||
{
|
{
|
||||||
@@ -3687,10 +3709,11 @@ emit_lets(Cg *c, FILE *out, Node *file)
|
|||||||
emit_data_row(out, "DATAW", mod_mangle(c, d->str), v);
|
emit_data_row(out, "DATAW", mod_mangle(c, d->str), v);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
/* Multi-word (str, 16B). Only zero-init shapes are
|
/* Multi-word (str=16, slice=24). Only zero-init shapes
|
||||||
* supported: no rhs, or `nil`, or `""` (which interns to
|
* are supported: no rhs, or `nil`, or `""` (which interns
|
||||||
* a strlit but we still emit a zero header — the program
|
* to a strlit but we still emit a zero header — the
|
||||||
* has to assign a real strlit at runtime to use it). */
|
* program has to assign a real strlit/slice at runtime
|
||||||
|
* to use it). */
|
||||||
if (d->rhs != NULL) {
|
if (d->rhs != NULL) {
|
||||||
Node *r = d->rhs;
|
Node *r = d->rhs;
|
||||||
while (r != NULL && r->kind == N_CAST) r = r->lhs;
|
while (r != NULL && r->kind == N_CAST) r = r->lhs;
|
||||||
|
|||||||
@@ -6130,17 +6130,25 @@ fn cgident(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
// Top-level mutable `let` — RIP-relative load from its DATAW
|
// Top-level mutable `let` — RIP-relative load from its DATAW
|
||||||
// slot. Mirrors C cgen's catch-all `MOVQ masym(s), AX` for
|
// slot. Mirrors C cgen's catch-all `MOVQ masym(s), AX` for
|
||||||
// scalar lets, plus the (LEAQ, MOVQ, MOVQ) sequence for str
|
// scalar lets, plus the (LEAQ, MOVQ, MOVQ[, MOVQ]) sequence
|
||||||
// globals so both ptr and len land in (AX, BX). Names that
|
// for str / slice globals so the ABI pair / triple lands in
|
||||||
// aren't lets either (typos, never-defined) drop through to
|
// (AX, BX[, CX]). Names that aren't lets either (typos,
|
||||||
// the silent return.
|
// never-defined) drop through to the silent return.
|
||||||
if (isletvar(c, nm)) {
|
if (isletvar(c, nm)) {
|
||||||
if (letvarisstr(c, nm)) {
|
let isstr: bool = letvarisstr(c, nm);
|
||||||
|
let issl: bool = letvarisslice(c, nm);
|
||||||
|
if (isstr || issl) {
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitsymname(c, nm);
|
emitsymname(c, nm);
|
||||||
emitline("(SB), CX\n");
|
emitline("(SB), CX\n");
|
||||||
emitline("\tMOVQ\t(CX), AX\n");
|
emitline("\tMOVQ\t(CX), AX\n");
|
||||||
emitline("\tMOVQ\t8(CX), BX\n");
|
emitline("\tMOVQ\t8(CX), BX\n");
|
||||||
|
if (issl) {
|
||||||
|
// Overwrites the address holder with the
|
||||||
|
// cap as the last step — CX is no longer
|
||||||
|
// needed once both ptr/len are loaded.
|
||||||
|
emitline("\tMOVQ\t16(CX), CX\n");
|
||||||
|
};
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
@@ -6608,17 +6616,22 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// Top-level str global field access — load .ptr / .len via
|
// Top-level str/slice global field access — load .ptr / .len
|
||||||
// &name(SB) into CX, then MOVQ delta(CX), AX. Without this
|
// (and .cap for slices) via &name(SB) into CX, then MOVQ
|
||||||
// the module-qualified fallback below would mis-emit
|
// delta(CX), AX. Without this the module-qualified fallback
|
||||||
// `MOVQ <field>(SB), AX`.
|
// below would mis-emit `MOVQ <field>(SB), AX`.
|
||||||
if (lhs != nil) {
|
if (lhs != nil) {
|
||||||
if (lhs.kind == nkind.N_IDENT) {
|
if (lhs.kind == nkind.N_IDENT) {
|
||||||
if (isletvar(c, lhs.str)) {
|
if (isletvar(c, lhs.str)) {
|
||||||
if (letvarisstr(c, lhs.str)) {
|
let isstr: bool = letvarisstr(c, lhs.str);
|
||||||
|
let issl: bool = letvarisslice(c, lhs.str);
|
||||||
|
if (isstr || issl) {
|
||||||
let delta: i32 = -1;
|
let delta: i32 = -1;
|
||||||
if (streq(fld, "ptr")) { delta = 0; };
|
if (streq(fld, "ptr")) { delta = 0; };
|
||||||
if (streq(fld, "len")) { delta = 8; };
|
if (streq(fld, "len")) { delta = 8; };
|
||||||
|
if (issl) {
|
||||||
|
if (streq(fld, "cap")) { delta = 16; };
|
||||||
|
};
|
||||||
if (delta >= 0) {
|
if (delta >= 0) {
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitsymname(c, lhs.str);
|
emitsymname(c, lhs.str);
|
||||||
@@ -9036,11 +9049,13 @@ fn letscalarprim(nm: str) bool = {
|
|||||||
// aliases so byte output matches C cgen, which resolves Type kinds.
|
// aliases so byte output matches C cgen, which resolves Type kinds.
|
||||||
// 8 → scalar (literal init supported)
|
// 8 → scalar (literal init supported)
|
||||||
// 16 → str (only zero-init / nil / "" supported)
|
// 16 → str (only zero-init / nil / "" supported)
|
||||||
|
// 24 → slice (only zero-init supported)
|
||||||
fn letemitsize(c: *cgen, d: *node) i32 = {
|
fn letemitsize(c: *cgen, d: *node) i32 = {
|
||||||
if (d == nil) { return 0; };
|
if (d == nil) { return 0; };
|
||||||
let t: *node = d.lhs;
|
let t: *node = d.lhs;
|
||||||
for (t != nil) {
|
for (t != nil) {
|
||||||
if (t.kind == nkind.N_TPTR) { return 8; };
|
if (t.kind == nkind.N_TPTR) { return 8; };
|
||||||
|
if (t.kind == nkind.N_TSLICE) { return 24; };
|
||||||
if (t.kind != nkind.N_TNAME) { return 0; };
|
if (t.kind != nkind.N_TNAME) { return 0; };
|
||||||
let nm: str = t.str;
|
let nm: str = t.str;
|
||||||
if (letscalarprim(nm)) { return 8; };
|
if (letscalarprim(nm)) { return 8; };
|
||||||
@@ -9106,6 +9121,24 @@ fn letvarisstr(c: *cgen, name: str) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// letvarisslice — is the named top-level let a slice global?
|
||||||
|
// Slice headers are 24 bytes; the ABI flows as (AX, BX, CX) so the
|
||||||
|
// load sequence ends with `MOVQ 16(CX), CX` (overwrites the
|
||||||
|
// address holder with the cap). Mirrors C cgen's `let_isslice`.
|
||||||
|
fn letvarisslice(c: *cgen, name: str) bool = {
|
||||||
|
let lv: *letvar = c.lets;
|
||||||
|
for (lv != nil) {
|
||||||
|
if (streq(lv.name, name)) {
|
||||||
|
let t: *node = lv.tnode;
|
||||||
|
if (t == nil) { return false; };
|
||||||
|
if (t.kind == nkind.N_TSLICE) { return true; };
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
lv = lv.lvnext;
|
||||||
|
};
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
// emitdatawbyte — write one byte of an asm string literal using
|
// emitdatawbyte — write one byte of an asm string literal using
|
||||||
// the same escape rules as emitdefconstants / emitdatasection.
|
// the same escape rules as emitdefconstants / emitdatasection.
|
||||||
fn emitdatawbyte(b: u8) void = {
|
fn emitdatawbyte(b: u8) void = {
|
||||||
@@ -9215,6 +9248,35 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
emitline("\"\n");
|
emitline("\"\n");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
if (sz == 24) {
|
||||||
|
// Slice: zero-init only (no slice-literal
|
||||||
|
// syntax to honour). Any rhs other than
|
||||||
|
// `nil` is skipped → undefined symbol at
|
||||||
|
// link.
|
||||||
|
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 (ok) {
|
||||||
|
emitline("DATAW ");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB),\"");
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < 24) {
|
||||||
|
emitdatawbyte(0u8);
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
emitline("\"\n");
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
d = d.next;
|
d = d.next;
|
||||||
|
|||||||
@@ -586,11 +586,13 @@ fn letscalarprim(nm: str) bool = {
|
|||||||
// aliases so byte output matches C cgen, which resolves Type kinds.
|
// aliases so byte output matches C cgen, which resolves Type kinds.
|
||||||
// 8 → scalar (literal init supported)
|
// 8 → scalar (literal init supported)
|
||||||
// 16 → str (only zero-init / nil / "" supported)
|
// 16 → str (only zero-init / nil / "" supported)
|
||||||
|
// 24 → slice (only zero-init supported)
|
||||||
fn letemitsize(c: *cgen, d: *node) i32 = {
|
fn letemitsize(c: *cgen, d: *node) i32 = {
|
||||||
if (d == nil) { return 0; };
|
if (d == nil) { return 0; };
|
||||||
let t: *node = d.lhs;
|
let t: *node = d.lhs;
|
||||||
for (t != nil) {
|
for (t != nil) {
|
||||||
if (t.kind == nkind.N_TPTR) { return 8; };
|
if (t.kind == nkind.N_TPTR) { return 8; };
|
||||||
|
if (t.kind == nkind.N_TSLICE) { return 24; };
|
||||||
if (t.kind != nkind.N_TNAME) { return 0; };
|
if (t.kind != nkind.N_TNAME) { return 0; };
|
||||||
let nm: str = t.str;
|
let nm: str = t.str;
|
||||||
if (letscalarprim(nm)) { return 8; };
|
if (letscalarprim(nm)) { return 8; };
|
||||||
@@ -656,6 +658,24 @@ fn letvarisstr(c: *cgen, name: str) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// letvarisslice — is the named top-level let a slice global?
|
||||||
|
// Slice headers are 24 bytes; the ABI flows as (AX, BX, CX) so the
|
||||||
|
// load sequence ends with `MOVQ 16(CX), CX` (overwrites the
|
||||||
|
// address holder with the cap). Mirrors C cgen's `let_isslice`.
|
||||||
|
fn letvarisslice(c: *cgen, name: str) bool = {
|
||||||
|
let lv: *letvar = c.lets;
|
||||||
|
for (lv != nil) {
|
||||||
|
if (streq(lv.name, name)) {
|
||||||
|
let t: *node = lv.tnode;
|
||||||
|
if (t == nil) { return false; };
|
||||||
|
if (t.kind == nkind.N_TSLICE) { return true; };
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
lv = lv.lvnext;
|
||||||
|
};
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
// emitdatawbyte — write one byte of an asm string literal using
|
// emitdatawbyte — write one byte of an asm string literal using
|
||||||
// the same escape rules as emitdefconstants / emitdatasection.
|
// the same escape rules as emitdefconstants / emitdatasection.
|
||||||
fn emitdatawbyte(b: u8) void = {
|
fn emitdatawbyte(b: u8) void = {
|
||||||
@@ -765,6 +785,35 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
emitline("\"\n");
|
emitline("\"\n");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
if (sz == 24) {
|
||||||
|
// Slice: zero-init only (no slice-literal
|
||||||
|
// syntax to honour). Any rhs other than
|
||||||
|
// `nil` is skipped → undefined symbol at
|
||||||
|
// link.
|
||||||
|
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 (ok) {
|
||||||
|
emitline("DATAW ");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB),\"");
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < 24) {
|
||||||
|
emitdatawbyte(0u8);
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
emitline("\"\n");
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
d = d.next;
|
d = d.next;
|
||||||
|
|||||||
@@ -402,17 +402,25 @@ fn cgident(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
// Top-level mutable `let` — RIP-relative load from its DATAW
|
// Top-level mutable `let` — RIP-relative load from its DATAW
|
||||||
// slot. Mirrors C cgen's catch-all `MOVQ masym(s), AX` for
|
// slot. Mirrors C cgen's catch-all `MOVQ masym(s), AX` for
|
||||||
// scalar lets, plus the (LEAQ, MOVQ, MOVQ) sequence for str
|
// scalar lets, plus the (LEAQ, MOVQ, MOVQ[, MOVQ]) sequence
|
||||||
// globals so both ptr and len land in (AX, BX). Names that
|
// for str / slice globals so the ABI pair / triple lands in
|
||||||
// aren't lets either (typos, never-defined) drop through to
|
// (AX, BX[, CX]). Names that aren't lets either (typos,
|
||||||
// the silent return.
|
// never-defined) drop through to the silent return.
|
||||||
if (isletvar(c, nm)) {
|
if (isletvar(c, nm)) {
|
||||||
if (letvarisstr(c, nm)) {
|
let isstr: bool = letvarisstr(c, nm);
|
||||||
|
let issl: bool = letvarisslice(c, nm);
|
||||||
|
if (isstr || issl) {
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitsymname(c, nm);
|
emitsymname(c, nm);
|
||||||
emitline("(SB), CX\n");
|
emitline("(SB), CX\n");
|
||||||
emitline("\tMOVQ\t(CX), AX\n");
|
emitline("\tMOVQ\t(CX), AX\n");
|
||||||
emitline("\tMOVQ\t8(CX), BX\n");
|
emitline("\tMOVQ\t8(CX), BX\n");
|
||||||
|
if (issl) {
|
||||||
|
// Overwrites the address holder with the
|
||||||
|
// cap as the last step — CX is no longer
|
||||||
|
// needed once both ptr/len are loaded.
|
||||||
|
emitline("\tMOVQ\t16(CX), CX\n");
|
||||||
|
};
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
@@ -880,17 +888,22 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// Top-level str global field access — load .ptr / .len via
|
// Top-level str/slice global field access — load .ptr / .len
|
||||||
// &name(SB) into CX, then MOVQ delta(CX), AX. Without this
|
// (and .cap for slices) via &name(SB) into CX, then MOVQ
|
||||||
// the module-qualified fallback below would mis-emit
|
// delta(CX), AX. Without this the module-qualified fallback
|
||||||
// `MOVQ <field>(SB), AX`.
|
// below would mis-emit `MOVQ <field>(SB), AX`.
|
||||||
if (lhs != nil) {
|
if (lhs != nil) {
|
||||||
if (lhs.kind == nkind.N_IDENT) {
|
if (lhs.kind == nkind.N_IDENT) {
|
||||||
if (isletvar(c, lhs.str)) {
|
if (isletvar(c, lhs.str)) {
|
||||||
if (letvarisstr(c, lhs.str)) {
|
let isstr: bool = letvarisstr(c, lhs.str);
|
||||||
|
let issl: bool = letvarisslice(c, lhs.str);
|
||||||
|
if (isstr || issl) {
|
||||||
let delta: i32 = -1;
|
let delta: i32 = -1;
|
||||||
if (streq(fld, "ptr")) { delta = 0; };
|
if (streq(fld, "ptr")) { delta = 0; };
|
||||||
if (streq(fld, "len")) { delta = 8; };
|
if (streq(fld, "len")) { delta = 8; };
|
||||||
|
if (issl) {
|
||||||
|
if (streq(fld, "cap")) { delta = 16; };
|
||||||
|
};
|
||||||
if (delta >= 0) {
|
if (delta >= 0) {
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitsymname(c, lhs.str);
|
emitsymname(c, lhs.str);
|
||||||
|
|||||||
@@ -6130,17 +6130,25 @@ fn cgident(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
// Top-level mutable `let` — RIP-relative load from its DATAW
|
// Top-level mutable `let` — RIP-relative load from its DATAW
|
||||||
// slot. Mirrors C cgen's catch-all `MOVQ masym(s), AX` for
|
// slot. Mirrors C cgen's catch-all `MOVQ masym(s), AX` for
|
||||||
// scalar lets, plus the (LEAQ, MOVQ, MOVQ) sequence for str
|
// scalar lets, plus the (LEAQ, MOVQ, MOVQ[, MOVQ]) sequence
|
||||||
// globals so both ptr and len land in (AX, BX). Names that
|
// for str / slice globals so the ABI pair / triple lands in
|
||||||
// aren't lets either (typos, never-defined) drop through to
|
// (AX, BX[, CX]). Names that aren't lets either (typos,
|
||||||
// the silent return.
|
// never-defined) drop through to the silent return.
|
||||||
if (isletvar(c, nm)) {
|
if (isletvar(c, nm)) {
|
||||||
if (letvarisstr(c, nm)) {
|
let isstr: bool = letvarisstr(c, nm);
|
||||||
|
let issl: bool = letvarisslice(c, nm);
|
||||||
|
if (isstr || issl) {
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitsymname(c, nm);
|
emitsymname(c, nm);
|
||||||
emitline("(SB), CX\n");
|
emitline("(SB), CX\n");
|
||||||
emitline("\tMOVQ\t(CX), AX\n");
|
emitline("\tMOVQ\t(CX), AX\n");
|
||||||
emitline("\tMOVQ\t8(CX), BX\n");
|
emitline("\tMOVQ\t8(CX), BX\n");
|
||||||
|
if (issl) {
|
||||||
|
// Overwrites the address holder with the
|
||||||
|
// cap as the last step — CX is no longer
|
||||||
|
// needed once both ptr/len are loaded.
|
||||||
|
emitline("\tMOVQ\t16(CX), CX\n");
|
||||||
|
};
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
@@ -6608,17 +6616,22 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// Top-level str global field access — load .ptr / .len via
|
// Top-level str/slice global field access — load .ptr / .len
|
||||||
// &name(SB) into CX, then MOVQ delta(CX), AX. Without this
|
// (and .cap for slices) via &name(SB) into CX, then MOVQ
|
||||||
// the module-qualified fallback below would mis-emit
|
// delta(CX), AX. Without this the module-qualified fallback
|
||||||
// `MOVQ <field>(SB), AX`.
|
// below would mis-emit `MOVQ <field>(SB), AX`.
|
||||||
if (lhs != nil) {
|
if (lhs != nil) {
|
||||||
if (lhs.kind == nkind.N_IDENT) {
|
if (lhs.kind == nkind.N_IDENT) {
|
||||||
if (isletvar(c, lhs.str)) {
|
if (isletvar(c, lhs.str)) {
|
||||||
if (letvarisstr(c, lhs.str)) {
|
let isstr: bool = letvarisstr(c, lhs.str);
|
||||||
|
let issl: bool = letvarisslice(c, lhs.str);
|
||||||
|
if (isstr || issl) {
|
||||||
let delta: i32 = -1;
|
let delta: i32 = -1;
|
||||||
if (streq(fld, "ptr")) { delta = 0; };
|
if (streq(fld, "ptr")) { delta = 0; };
|
||||||
if (streq(fld, "len")) { delta = 8; };
|
if (streq(fld, "len")) { delta = 8; };
|
||||||
|
if (issl) {
|
||||||
|
if (streq(fld, "cap")) { delta = 16; };
|
||||||
|
};
|
||||||
if (delta >= 0) {
|
if (delta >= 0) {
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitsymname(c, lhs.str);
|
emitsymname(c, lhs.str);
|
||||||
@@ -9036,11 +9049,13 @@ fn letscalarprim(nm: str) bool = {
|
|||||||
// aliases so byte output matches C cgen, which resolves Type kinds.
|
// aliases so byte output matches C cgen, which resolves Type kinds.
|
||||||
// 8 → scalar (literal init supported)
|
// 8 → scalar (literal init supported)
|
||||||
// 16 → str (only zero-init / nil / "" supported)
|
// 16 → str (only zero-init / nil / "" supported)
|
||||||
|
// 24 → slice (only zero-init supported)
|
||||||
fn letemitsize(c: *cgen, d: *node) i32 = {
|
fn letemitsize(c: *cgen, d: *node) i32 = {
|
||||||
if (d == nil) { return 0; };
|
if (d == nil) { return 0; };
|
||||||
let t: *node = d.lhs;
|
let t: *node = d.lhs;
|
||||||
for (t != nil) {
|
for (t != nil) {
|
||||||
if (t.kind == nkind.N_TPTR) { return 8; };
|
if (t.kind == nkind.N_TPTR) { return 8; };
|
||||||
|
if (t.kind == nkind.N_TSLICE) { return 24; };
|
||||||
if (t.kind != nkind.N_TNAME) { return 0; };
|
if (t.kind != nkind.N_TNAME) { return 0; };
|
||||||
let nm: str = t.str;
|
let nm: str = t.str;
|
||||||
if (letscalarprim(nm)) { return 8; };
|
if (letscalarprim(nm)) { return 8; };
|
||||||
@@ -9106,6 +9121,24 @@ fn letvarisstr(c: *cgen, name: str) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// letvarisslice — is the named top-level let a slice global?
|
||||||
|
// Slice headers are 24 bytes; the ABI flows as (AX, BX, CX) so the
|
||||||
|
// load sequence ends with `MOVQ 16(CX), CX` (overwrites the
|
||||||
|
// address holder with the cap). Mirrors C cgen's `let_isslice`.
|
||||||
|
fn letvarisslice(c: *cgen, name: str) bool = {
|
||||||
|
let lv: *letvar = c.lets;
|
||||||
|
for (lv != nil) {
|
||||||
|
if (streq(lv.name, name)) {
|
||||||
|
let t: *node = lv.tnode;
|
||||||
|
if (t == nil) { return false; };
|
||||||
|
if (t.kind == nkind.N_TSLICE) { return true; };
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
lv = lv.lvnext;
|
||||||
|
};
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
// emitdatawbyte — write one byte of an asm string literal using
|
// emitdatawbyte — write one byte of an asm string literal using
|
||||||
// the same escape rules as emitdefconstants / emitdatasection.
|
// the same escape rules as emitdefconstants / emitdatasection.
|
||||||
fn emitdatawbyte(b: u8) void = {
|
fn emitdatawbyte(b: u8) void = {
|
||||||
@@ -9215,6 +9248,35 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
emitline("\"\n");
|
emitline("\"\n");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
if (sz == 24) {
|
||||||
|
// Slice: zero-init only (no slice-literal
|
||||||
|
// syntax to honour). Any rhs other than
|
||||||
|
// `nil` is skipped → undefined symbol at
|
||||||
|
// link.
|
||||||
|
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 (ok) {
|
||||||
|
emitline("DATAW ");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB),\"");
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < 24) {
|
||||||
|
emitdatawbyte(0u8);
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
emitline("\"\n");
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
d = d.next;
|
d = d.next;
|
||||||
|
|||||||
@@ -113,6 +113,31 @@ static const struct fixture fixtures[] = {
|
|||||||
"};\n",
|
"};\n",
|
||||||
7,
|
7,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"slice-zeroinit",
|
||||||
|
/* Slice globals start as {nil, 0, 0}. .len and .cap both
|
||||||
|
* read zero; .ptr is nil but we don't dereference it. */
|
||||||
|
"let buf: []u8;\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
"\treturn (buf.len + buf.cap): i32;\n"
|
||||||
|
"};\n",
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slice-cap-via-raw-pointer",
|
||||||
|
/* Drive the slice header through a raw u64 pointer cast
|
||||||
|
* — the language has no slice-reassignment expression
|
||||||
|
* yet, so this is the only way to fill the header from
|
||||||
|
* ww source today. .cap reads back as the value we
|
||||||
|
* wrote. */
|
||||||
|
"let buf: []u8;\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
"\tlet p: *u64 = (&buf): *u64;\n"
|
||||||
|
"\tp[2] = 99u64;\n"
|
||||||
|
"\treturn buf.cap: i32;\n"
|
||||||
|
"};\n",
|
||||||
|
99,
|
||||||
|
},
|
||||||
{ NULL, NULL, 0 }
|
{ NULL, NULL, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user