w6c: emit DATAR for let s: str = "literal" initialisers
Use the new DATAR mechanism so str-literal init on a top-level mutable `let` lands in .data and links cleanly. emit_lets, when it sees `let s: str = "lit"` (non-empty strlit), emits: DATAW s(SB),"<8 zero placeholder><8 LE bytes of len>" DATAR s+0(SB),<strlit_label>(SB) The linker patches the placeholder with the strlit's runtime VA at program load time, so `s.ptr` reads as the real pointer and `s.len` as the literal length. A new let_pre_intern pass scans top-level lets ahead of emit_data so the strlit gets a DATA row in the same .s file; running emit_lets after emit_data instead would have flipped the (DATA strlits, DATAW lets) section order in the .s and broken byte-identity with the wwstage cgen. The wwstage cgen still emits the zero-init shape for str lets, which only matters if the wwstage is asked to compile source that uses str-literal init. None of the selfhost combined sources do that today, so test 994 / 990 stay green. The selfhost mirror for DATAR + DATAW + this w6c branch is a follow-up. 630_let_global gains two fixtures: a length-readback and a first- byte readback through the patched ptr.
This commit is contained in:
@@ -3726,13 +3726,17 @@ emit_data_row_zero(FILE *out, const char *dir, const char *name, int sz)
|
||||
* 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.
|
||||
* str lets (16B): three init shapes are wired:
|
||||
* - no rhs / `nil` / `""` → 16 zero bytes
|
||||
* - `"literal"` (non-empty) → 8 zero placeholder + 8 LE len,
|
||||
* plus DATAR patching the ptr
|
||||
* half with the interned strlit's
|
||||
* runtime VA at link time.
|
||||
*
|
||||
* Slice lets (24B): no-init only — the slot is zero. There's no
|
||||
* literal slice syntax to honour, so this is the natural shape. */
|
||||
* literal slice syntax to honour, so this is the natural shape.
|
||||
*
|
||||
* Struct lets (size from Type.size): no-init only. */
|
||||
static void
|
||||
emit_lets(Cg *c, FILE *out, Node *file)
|
||||
{
|
||||
@@ -3757,23 +3761,39 @@ emit_lets(Cg *c, FILE *out, Node *file)
|
||||
emit_data_row(out, "DATAW", mod_mangle(c, d->str), v);
|
||||
continue;
|
||||
}
|
||||
/* Multi-word (str=16, slice=24, struct=N). 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 / slice / field
|
||||
* at runtime to use it). Struct literals as init are
|
||||
* skipped (no compile-time eval), so a non-default init
|
||||
* surfaces as an undefined-symbol link error. */
|
||||
if (d->rhs != NULL && !let_isstruct(d->type)) {
|
||||
Node *r = d->rhs;
|
||||
/* Strip leading casts on the rhs so a `nil: str` etc.
|
||||
* reads the same as a bare nil. */
|
||||
Node *r = NULL;
|
||||
if (d->rhs != NULL) {
|
||||
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;
|
||||
}
|
||||
if (d->rhs != NULL && let_isstruct(d->type))
|
||||
/* str literal init: bake the interned label's address
|
||||
* into the ptr half via a DATAR reloc, set the len half
|
||||
* inline. */
|
||||
if (sz == 16 && r != NULL && r->kind == N_STRLIT
|
||||
&& r->strlen > 0) {
|
||||
const char *lab = intern_strlit(c, r->str, r->strlen);
|
||||
const char *sym = mod_mangle(c, d->str);
|
||||
u64 v = r->strlen;
|
||||
/* 16-byte payload: 8 zero placeholder + LE len. */
|
||||
fprintf(out, "DATAW %s(SB),\"", sym);
|
||||
for (int i = 0; i < 8; i++) emit_data_byte(out, 0);
|
||||
for (int i = 0; i < 8; i++)
|
||||
emit_data_byte(out, (u8)((v >> (i * 8)) & 0xff));
|
||||
fputs("\"\n", out);
|
||||
fprintf(out, "DATAR %s+0(SB),%s(SB)\n", sym, lab);
|
||||
continue;
|
||||
}
|
||||
/* Otherwise: zero-init. str accepts nil / ""; struct
|
||||
* accepts no rhs at all; slice accepts nil. */
|
||||
if (r != NULL) {
|
||||
int is_struct = let_isstruct(d->type);
|
||||
int empty_str = (r->kind == N_STRLIT && r->strlen == 0);
|
||||
if (is_struct) continue;
|
||||
if (r->kind != N_NIL && !empty_str) continue;
|
||||
}
|
||||
emit_data_row_zero(out, "DATAW", mod_mangle(c, d->str), sz);
|
||||
}
|
||||
}
|
||||
@@ -3832,6 +3852,28 @@ sdef_collect(Cg *c, Node *file)
|
||||
}
|
||||
}
|
||||
|
||||
/* Pre-intern strlits referenced from top-level `let` initialisers
|
||||
* (e.g. `let g: str = "hello";`). Interning has to happen before
|
||||
* emit_data walks the strlit list, but we don't want to reorder
|
||||
* emit_data after emit_lets (the (DATA strlits, DATAW lets) section
|
||||
* order is part of the byte-identity contract with the selfhost
|
||||
* cgen). So this pass populates the strlit table; emit_lets later
|
||||
* just looks up the label. */
|
||||
static void
|
||||
let_pre_intern(Cg *c, Node *file)
|
||||
{
|
||||
if (file == NULL) return;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (d->kind != N_LET) continue;
|
||||
if (let_emit_size(d->type) != 16) continue;
|
||||
Node *r = d->rhs;
|
||||
while (r != NULL && r->kind == N_CAST) r = r->lhs;
|
||||
if (r == NULL || r->kind != N_STRLIT) continue;
|
||||
if (r->strlen == 0) continue;
|
||||
(void)intern_strlit(c, r->str, r->strlen);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
cg_file(Cg *c, FILE *out, Node *file)
|
||||
{
|
||||
@@ -3846,6 +3888,7 @@ cg_file(Cg *c, FILE *out, Node *file)
|
||||
if (d->kind != N_FNDECL) continue;
|
||||
cgfn(c, out, d);
|
||||
}
|
||||
let_pre_intern(c, file);
|
||||
emit_data(c, out);
|
||||
emit_defs(c, out, file);
|
||||
emit_lets(c, out, file);
|
||||
|
||||
@@ -113,6 +113,27 @@ static const struct fixture fixtures[] = {
|
||||
"};\n",
|
||||
7,
|
||||
},
|
||||
{
|
||||
"str-literal-init",
|
||||
/* `let s: str = "literal";` — the ptr half is patched at
|
||||
* link time by an R_X86_64_64 reloc against the strlit's
|
||||
* data label; .len comes baked into the DATAW payload. */
|
||||
"let g: str = \"hello world\";\n"
|
||||
"fn main() i32 = { return g.len: i32; };\n",
|
||||
11,
|
||||
},
|
||||
{
|
||||
"str-literal-via-fn",
|
||||
/* Confirm the patched ptr really points at the right bytes
|
||||
* by reading the first byte of g.ptr through a pointer
|
||||
* cast. 'h' == 104. */
|
||||
"let g: str = \"hello\";\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet p: *u8 = g.ptr;\n"
|
||||
"\treturn p[0]: i32;\n"
|
||||
"};\n",
|
||||
104,
|
||||
},
|
||||
{
|
||||
"slice-zeroinit",
|
||||
/* Slice globals start as {nil, 0, 0}. .len and .cap both
|
||||
|
||||
Reference in New Issue
Block a user