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:
6
Makefile
6
Makefile
@@ -214,7 +214,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
|||||||
$(BIN)/test_data_link \
|
$(BIN)/test_data_link \
|
||||||
$(BIN)/test_arch \
|
$(BIN)/test_arch \
|
||||||
$(BIN)/test_e2e $(BIN)/test_ffi $(BIN)/test_dyn $(BIN)/test_stdlib \
|
$(BIN)/test_e2e $(BIN)/test_ffi $(BIN)/test_dyn $(BIN)/test_stdlib \
|
||||||
$(BIN)/test_at_test \
|
$(BIN)/test_at_test $(BIN)/test_let_global \
|
||||||
$(BIN)/test_selfhost $(BIN)/test_w6a_ww $(BIN)/test_w6l_ww \
|
$(BIN)/test_selfhost $(BIN)/test_w6a_ww $(BIN)/test_w6l_ww \
|
||||||
$(BIN)/test_w6c_ww $(BIN)/test_ww_ww $(BIN)/test_self_rebuild \
|
$(BIN)/test_w6c_ww $(BIN)/test_ww_ww $(BIN)/test_self_rebuild \
|
||||||
$(BIN)/test_dyn_ww $(BIN)/test_selfcheck $(BIN)/test_at_test_ww
|
$(BIN)/test_dyn_ww $(BIN)/test_selfcheck $(BIN)/test_at_test_ww
|
||||||
@@ -268,6 +268,10 @@ $(BIN)/test_at_test: test/wcc/910_at_test.c $(BIN)/ww $(BIN)/w6c $(BIN)/w6a \
|
|||||||
$(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
$(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
$(BIN)/test_let_global: test/wcc/630_let_global.c $(BIN)/ww $(BIN)/w6c \
|
||||||
|
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
$(BIN)/test_selfhost: test/wcc/990_selfhost.c $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
$(BIN)/test_selfhost: test/wcc/990_selfhost.c $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||||
$(BIN)/ww $(BIN)/wwdump $(BIN)/wwdump_ww $(LIB)/libwwrt.a | $(BIN)
|
$(BIN)/ww $(BIN)/wwdump $(BIN)/wwdump_ww $(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|||||||
178
cmd/w6c/cgen.c
178
cmd/w6c/cgen.c
@@ -386,6 +386,40 @@ struct Mod {
|
|||||||
};
|
};
|
||||||
static Mod *mod_map;
|
static Mod *mod_map;
|
||||||
|
|
||||||
|
/* Top-level `let` map. Populated alongside mod_map; consulted by the
|
||||||
|
* N_IDENT store path and the &-of path to route reads/writes through
|
||||||
|
* a RIP-relative reference rather than dropping them as the (pre-
|
||||||
|
* writable-.data) compiler did. emit_lets emits a DATAW for each. */
|
||||||
|
typedef struct LetVar LetVar;
|
||||||
|
struct LetVar {
|
||||||
|
const char *name;
|
||||||
|
LetVar *next;
|
||||||
|
};
|
||||||
|
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. */
|
||||||
|
static int
|
||||||
|
let_scalar_ok(Type *t)
|
||||||
|
{
|
||||||
|
if (t == NULL) return 0;
|
||||||
|
Type *u = (t->kind == TY_NAMED) ? t->under : t;
|
||||||
|
if (u == NULL) return 0;
|
||||||
|
switch (u->kind) {
|
||||||
|
case TY_BOOL: case TY_RUNE:
|
||||||
|
case TY_I8: case TY_I16: case TY_I32: case TY_I64:
|
||||||
|
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;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
decl_has_ffisym(Node *d)
|
decl_has_ffisym(Node *d)
|
||||||
{
|
{
|
||||||
@@ -430,6 +464,35 @@ mod_lookup(const char *name)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Collect every top-level `let` whose declared type we can store
|
||||||
|
* in a single .data slot. Names not in this map fall through to
|
||||||
|
* the old "drop assignment" path; with a clear link-time
|
||||||
|
* undefined-symbol error on any read. */
|
||||||
|
static void
|
||||||
|
let_collect(Cg *c, Node *file)
|
||||||
|
{
|
||||||
|
letvars = NULL;
|
||||||
|
if (file == NULL) return;
|
||||||
|
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;
|
||||||
|
LetVar *lv = amalloc(c->a, sizeof *lv);
|
||||||
|
lv->name = d->str;
|
||||||
|
lv->next = letvars;
|
||||||
|
letvars = lv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
let_islet(const char *name)
|
||||||
|
{
|
||||||
|
if (name == NULL) return 0;
|
||||||
|
for (LetVar *lv = letvars; lv; lv = lv->next)
|
||||||
|
if (strcmp(lv->name, name) == 0) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Mangle an AST identifier into its asm linker symbol:
|
/* Mangle an AST identifier into its asm linker symbol:
|
||||||
* - @symbol("...") binding wins (return mapped name).
|
* - @symbol("...") binding wins (return mapped name).
|
||||||
* - module-private decl → <module>.<name>.
|
* - module-private decl → <module>.<name>.
|
||||||
@@ -733,10 +796,17 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TK_AMP: {
|
case TK_AMP: {
|
||||||
/* address-of for an N_IDENT local */
|
/* address-of for an N_IDENT: local frame slot first,
|
||||||
|
* else a top-level mutable let (RIP-relative LEAQ).
|
||||||
|
* Anything else (e.g. & on an undefined name) silently
|
||||||
|
* drops, matching the pre-existing behaviour. */
|
||||||
if (n->lhs->kind == N_IDENT) {
|
if (n->lhs->kind == N_IDENT) {
|
||||||
int off = localfind(locals, n->lhs->str);
|
int off = localfind(locals, n->lhs->str);
|
||||||
ins2(c, A_LEAQ, amem(D_BP, off), areg(D_AX));
|
if (off != 0) {
|
||||||
|
ins2(c, A_LEAQ, amem(D_BP, off), areg(D_AX));
|
||||||
|
} else if (let_islet(n->lhs->str)) {
|
||||||
|
ins2(c, A_LEAQ, masym(c, n->lhs->str), areg(D_AX));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1367,11 +1437,56 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
}
|
}
|
||||||
if (n->lhs->kind == N_IDENT) {
|
if (n->lhs->kind == N_IDENT) {
|
||||||
int off = localfind(locals, n->lhs->str);
|
int off = localfind(locals, n->lhs->str);
|
||||||
/* No local match: drop the whole assignment, including
|
if (off == 0) {
|
||||||
* the RHS evaluation. Top-level let mutation isn't
|
/* Top-level let target — RIP-relative store
|
||||||
* supported (no writable .data) and we don't want to
|
* (or load→combine→store for compound). Names
|
||||||
* leak a dead `MOVQ $rhs, AX` into the output. */
|
* we don't recognise as scalar lets fall through
|
||||||
if (off == 0) break;
|
* to the existing drop behaviour, which produces
|
||||||
|
* a clean link-time undefined-symbol error if
|
||||||
|
* the binding was ever supposed to exist. */
|
||||||
|
if (!let_islet(n->lhs->str)) break;
|
||||||
|
cgexpr(c, n->rhs, locals);
|
||||||
|
if (n->op == TK_ASSIGN) {
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX),
|
||||||
|
masym(c, n->lhs->str));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* Compound: BX = load; combine with AX; store
|
||||||
|
* BX. The asm has no RIP-relative ADDQ/SUBQ
|
||||||
|
* mem-form, so we use the explicit load→
|
||||||
|
* combine→store sequence uniformly. */
|
||||||
|
ins2(c, A_MOVQ, masym(c, n->lhs->str),
|
||||||
|
areg(D_BX));
|
||||||
|
int did_compound = 1;
|
||||||
|
switch (n->op) {
|
||||||
|
case TK_PLUSEQ: ins2(c, A_ADDQ, areg(D_AX), areg(D_BX)); break;
|
||||||
|
case TK_MINUSEQ: ins2(c, A_SUBQ, areg(D_AX), areg(D_BX)); break;
|
||||||
|
case TK_STAREQ: ins2(c, A_IMULQ, areg(D_AX), areg(D_BX)); break;
|
||||||
|
case TK_AMPEQ: ins2(c, A_ANDQ, areg(D_AX), areg(D_BX)); break;
|
||||||
|
case TK_PIPEEQ: ins2(c, A_ORQ, areg(D_AX), areg(D_BX)); break;
|
||||||
|
case TK_CARETEQ: ins2(c, A_XORQ, areg(D_AX), areg(D_BX)); break;
|
||||||
|
case TK_LSHIFTEQ:
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX), areg(D_CX));
|
||||||
|
ins2(c, A_SHLQ, areg(D_CX), areg(D_BX));
|
||||||
|
break;
|
||||||
|
case TK_RSHIFTEQ:
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX), areg(D_CX));
|
||||||
|
ins2(c, A_SHRQ, areg(D_CX), areg(D_BX));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* Unsupported compound op: store rhs
|
||||||
|
* directly. Mirrors the local path's
|
||||||
|
* fallback for TK_SLASHEQ etc. */
|
||||||
|
did_compound = 0;
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX),
|
||||||
|
masym(c, n->lhs->str));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (did_compound)
|
||||||
|
ins2(c, A_MOVQ, areg(D_BX),
|
||||||
|
masym(c, n->lhs->str));
|
||||||
|
break;
|
||||||
|
}
|
||||||
cgexpr(c, n->rhs, locals);
|
cgexpr(c, n->rhs, locals);
|
||||||
if (n->op == TK_ASSIGN) {
|
if (n->op == TK_ASSIGN) {
|
||||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off));
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off));
|
||||||
@@ -3455,6 +3570,53 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
|||||||
txt_emit(out, c->head);
|
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. */
|
||||||
|
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++) {
|
||||||
|
unsigned b = (unsigned)((v >> (i * 8)) & 0xff);
|
||||||
|
if (b == '"' || b == '\\')
|
||||||
|
fprintf(out, "\\%c", b);
|
||||||
|
else if (b < 0x20 || b >= 0x7f)
|
||||||
|
fprintf(out, "\\x%02x", b);
|
||||||
|
else
|
||||||
|
fputc(b, out);
|
||||||
|
}
|
||||||
|
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). */
|
||||||
|
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;
|
||||||
|
u64 v = 0;
|
||||||
|
if (d->rhs != NULL) {
|
||||||
|
Node *r = d->rhs;
|
||||||
|
while (r != NULL && r->kind == N_CAST) r = r->lhs;
|
||||||
|
if (r == NULL) continue;
|
||||||
|
if (r->kind == N_INTLIT) v = r->uval;
|
||||||
|
else if (r->kind == N_RUNELIT) v = r->uval;
|
||||||
|
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 */
|
||||||
|
}
|
||||||
|
emit_data_row(out, "DATAW", mod_mangle(c, d->str), v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Emit DATA directives for top-level `def` constants whose value is
|
/* Emit DATA directives for top-level `def` constants whose value is
|
||||||
* an integer/rune literal. The w6a side stores the bytes inside .text
|
* an integer/rune literal. The w6a side stores the bytes inside .text
|
||||||
* and accesses are RIP-relative.
|
* and accesses are RIP-relative.
|
||||||
@@ -3516,6 +3678,7 @@ cg_file(Cg *c, FILE *out, Node *file)
|
|||||||
ffi_collect(c, file);
|
ffi_collect(c, file);
|
||||||
mod_collect(c, file);
|
mod_collect(c, file);
|
||||||
sdef_collect(c, file);
|
sdef_collect(c, file);
|
||||||
|
let_collect(c, file);
|
||||||
strlits = NULL;
|
strlits = NULL;
|
||||||
strlit_seq = 0;
|
strlit_seq = 0;
|
||||||
for (Node *d = file->list; d; d = d->next) {
|
for (Node *d = file->list; d; d = d->next) {
|
||||||
@@ -3524,6 +3687,7 @@ cg_file(Cg *c, FILE *out, Node *file)
|
|||||||
}
|
}
|
||||||
emit_data(c, out);
|
emit_data(c, out);
|
||||||
emit_defs(c, out, file);
|
emit_defs(c, out, file);
|
||||||
|
emit_lets(c, out, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
void peephole(Cg *c) { (void)c; }
|
void peephole(Cg *c) { (void)c; }
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ anames(int op)
|
|||||||
case A_NOP: return "NOP";
|
case A_NOP: return "NOP";
|
||||||
case A_TEXT: return "TEXT";
|
case A_TEXT: return "TEXT";
|
||||||
case A_DATA: return "DATA";
|
case A_DATA: return "DATA";
|
||||||
|
case A_DATAW: return "DATAW";
|
||||||
case A_GLOBL: return "GLOBL";
|
case A_GLOBL: return "GLOBL";
|
||||||
case A_END: return "END";
|
case A_END: return "END";
|
||||||
case A_MOVQ: return "MOVQ";
|
case A_MOVQ: return "MOVQ";
|
||||||
|
|||||||
@@ -6678,6 +6678,13 @@ fn cgun(c: *cgen, n: *node) void = {
|
|||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
// Top-level mutable let — RIP-relative LEAQ.
|
||||||
|
if (isletvar(c, nm)) {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB), AX\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -7253,7 +7260,52 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
if (lhs.kind == nkind.N_IDENT) {
|
if (lhs.kind == nkind.N_IDENT) {
|
||||||
let nm: str = lhs.str;
|
let nm: str = lhs.str;
|
||||||
let off: i32 = localfind(c, nm);
|
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
|
// Detect str-typed local — assignment must store both
|
||||||
// halves (AX=ptr at +0, BX=len at +8).
|
// halves (AX=ptr at +0, BX=len at +8).
|
||||||
let lcstr: bool = false;
|
let lcstr: bool = false;
|
||||||
@@ -8326,6 +8378,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
|
|||||||
collectfnrets(c, file);
|
collectfnrets(c, file);
|
||||||
fficollect(c, file);
|
fficollect(c, file);
|
||||||
collectmods(c, file);
|
collectmods(c, file);
|
||||||
|
collectlets(c, file);
|
||||||
let d: *node = file.list;
|
let d: *node = file.list;
|
||||||
for (d != nil) {
|
for (d != nil) {
|
||||||
if (d.kind == nkind.N_FNDECL) {
|
if (d.kind == nkind.N_FNDECL) {
|
||||||
@@ -8337,6 +8390,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
|
|||||||
};
|
};
|
||||||
emitdatasection(c);
|
emitdatasection(c);
|
||||||
emitdefconstants(c, file);
|
emitdefconstants(c, file);
|
||||||
|
emitletdataw(c, file);
|
||||||
};
|
};
|
||||||
|
|
||||||
// MODULE: wcc
|
// MODULE: wcc
|
||||||
@@ -8661,6 +8715,7 @@ type cgen = struct {
|
|||||||
structs: *structinfo,
|
structs: *structinfo,
|
||||||
enums: *enumtype,
|
enums: *enumtype,
|
||||||
mods: *modent, // non-exported decls → originating module
|
mods: *modent, // non-exported decls → originating module
|
||||||
|
lets: *letvar, // top-level mutable scalar `let` bindings
|
||||||
fnname: str,
|
fnname: str,
|
||||||
fnret: *node, // declared return type of current fn (or nil)
|
fnret: *node, // declared return type of current fn (or nil)
|
||||||
looptop: i32,
|
looptop: i32,
|
||||||
@@ -8672,6 +8727,15 @@ type cgen = struct {
|
|||||||
deferbuf: **node, // stack of deferred exprs (LIFO at return)
|
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 = {
|
fn cgeninit(c: *cgen, a: *arena) void = {
|
||||||
c.a = a;
|
c.a = a;
|
||||||
c.locals = nil;
|
c.locals = nil;
|
||||||
@@ -8889,6 +8953,157 @@ fn internstrlit(c: *cgen, bytes: str) str = {
|
|||||||
return lab;
|
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`.
|
// emitdefconstants — DATA directive per top-level int-literal `def`.
|
||||||
// 8 bytes little-endian to match what the C cgen emits.
|
// 8 bytes little-endian to match what the C cgen emits.
|
||||||
fn emitdefconstants(c: *cgen, file: *node) void = {
|
fn emitdefconstants(c: *cgen, file: *node) void = {
|
||||||
|
|||||||
@@ -319,6 +319,7 @@ type cgen = struct {
|
|||||||
structs: *structinfo,
|
structs: *structinfo,
|
||||||
enums: *enumtype,
|
enums: *enumtype,
|
||||||
mods: *modent, // non-exported decls → originating module
|
mods: *modent, // non-exported decls → originating module
|
||||||
|
lets: *letvar, // top-level mutable scalar `let` bindings
|
||||||
fnname: str,
|
fnname: str,
|
||||||
fnret: *node, // declared return type of current fn (or nil)
|
fnret: *node, // declared return type of current fn (or nil)
|
||||||
looptop: i32,
|
looptop: i32,
|
||||||
@@ -330,6 +331,15 @@ type cgen = struct {
|
|||||||
deferbuf: **node, // stack of deferred exprs (LIFO at return)
|
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 = {
|
fn cgeninit(c: *cgen, a: *arena) void = {
|
||||||
c.a = a;
|
c.a = a;
|
||||||
c.locals = nil;
|
c.locals = nil;
|
||||||
@@ -547,6 +557,157 @@ fn internstrlit(c: *cgen, bytes: str) str = {
|
|||||||
return lab;
|
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`.
|
// emitdefconstants — DATA directive per top-level int-literal `def`.
|
||||||
// 8 bytes little-endian to match what the C cgen emits.
|
// 8 bytes little-endian to match what the C cgen emits.
|
||||||
fn emitdefconstants(c: *cgen, file: *node) void = {
|
fn emitdefconstants(c: *cgen, file: *node) void = {
|
||||||
|
|||||||
@@ -287,6 +287,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
|
|||||||
collectfnrets(c, file);
|
collectfnrets(c, file);
|
||||||
fficollect(c, file);
|
fficollect(c, file);
|
||||||
collectmods(c, file);
|
collectmods(c, file);
|
||||||
|
collectlets(c, file);
|
||||||
let d: *node = file.list;
|
let d: *node = file.list;
|
||||||
for (d != nil) {
|
for (d != nil) {
|
||||||
if (d.kind == nkind.N_FNDECL) {
|
if (d.kind == nkind.N_FNDECL) {
|
||||||
@@ -298,4 +299,5 @@ export fn cgfile(c: *cgen, file: *node) void = {
|
|||||||
};
|
};
|
||||||
emitdatasection(c);
|
emitdatasection(c);
|
||||||
emitdefconstants(c, file);
|
emitdefconstants(c, file);
|
||||||
|
emitletdataw(c, file);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -950,6 +950,13 @@ fn cgun(c: *cgen, n: *node) void = {
|
|||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
// Top-level mutable let — RIP-relative LEAQ.
|
||||||
|
if (isletvar(c, nm)) {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB), AX\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -1525,7 +1532,52 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
if (lhs.kind == nkind.N_IDENT) {
|
if (lhs.kind == nkind.N_IDENT) {
|
||||||
let nm: str = lhs.str;
|
let nm: str = lhs.str;
|
||||||
let off: i32 = localfind(c, nm);
|
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
|
// Detect str-typed local — assignment must store both
|
||||||
// halves (AX=ptr at +0, BX=len at +8).
|
// halves (AX=ptr at +0, BX=len at +8).
|
||||||
let lcstr: bool = false;
|
let lcstr: bool = false;
|
||||||
|
|||||||
@@ -6678,6 +6678,13 @@ fn cgun(c: *cgen, n: *node) void = {
|
|||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
// Top-level mutable let — RIP-relative LEAQ.
|
||||||
|
if (isletvar(c, nm)) {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB), AX\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -7253,7 +7260,52 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
if (lhs.kind == nkind.N_IDENT) {
|
if (lhs.kind == nkind.N_IDENT) {
|
||||||
let nm: str = lhs.str;
|
let nm: str = lhs.str;
|
||||||
let off: i32 = localfind(c, nm);
|
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
|
// Detect str-typed local — assignment must store both
|
||||||
// halves (AX=ptr at +0, BX=len at +8).
|
// halves (AX=ptr at +0, BX=len at +8).
|
||||||
let lcstr: bool = false;
|
let lcstr: bool = false;
|
||||||
@@ -8326,6 +8378,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
|
|||||||
collectfnrets(c, file);
|
collectfnrets(c, file);
|
||||||
fficollect(c, file);
|
fficollect(c, file);
|
||||||
collectmods(c, file);
|
collectmods(c, file);
|
||||||
|
collectlets(c, file);
|
||||||
let d: *node = file.list;
|
let d: *node = file.list;
|
||||||
for (d != nil) {
|
for (d != nil) {
|
||||||
if (d.kind == nkind.N_FNDECL) {
|
if (d.kind == nkind.N_FNDECL) {
|
||||||
@@ -8337,6 +8390,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
|
|||||||
};
|
};
|
||||||
emitdatasection(c);
|
emitdatasection(c);
|
||||||
emitdefconstants(c, file);
|
emitdefconstants(c, file);
|
||||||
|
emitletdataw(c, file);
|
||||||
};
|
};
|
||||||
|
|
||||||
// MODULE: wcc
|
// MODULE: wcc
|
||||||
@@ -8661,6 +8715,7 @@ type cgen = struct {
|
|||||||
structs: *structinfo,
|
structs: *structinfo,
|
||||||
enums: *enumtype,
|
enums: *enumtype,
|
||||||
mods: *modent, // non-exported decls → originating module
|
mods: *modent, // non-exported decls → originating module
|
||||||
|
lets: *letvar, // top-level mutable scalar `let` bindings
|
||||||
fnname: str,
|
fnname: str,
|
||||||
fnret: *node, // declared return type of current fn (or nil)
|
fnret: *node, // declared return type of current fn (or nil)
|
||||||
looptop: i32,
|
looptop: i32,
|
||||||
@@ -8672,6 +8727,15 @@ type cgen = struct {
|
|||||||
deferbuf: **node, // stack of deferred exprs (LIFO at return)
|
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 = {
|
fn cgeninit(c: *cgen, a: *arena) void = {
|
||||||
c.a = a;
|
c.a = a;
|
||||||
c.locals = nil;
|
c.locals = nil;
|
||||||
@@ -8889,6 +8953,157 @@ fn internstrlit(c: *cgen, bytes: str) str = {
|
|||||||
return lab;
|
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`.
|
// emitdefconstants — DATA directive per top-level int-literal `def`.
|
||||||
// 8 bytes little-endian to match what the C cgen emits.
|
// 8 bytes little-endian to match what the C cgen emits.
|
||||||
fn emitdefconstants(c: *cgen, file: *node) void = {
|
fn emitdefconstants(c: *cgen, file: *node) void = {
|
||||||
|
|||||||
118
test/wcc/630_let_global.c
Normal file
118
test/wcc/630_let_global.c
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* 630_let_global — top-level mutable `let` end-to-end through the
|
||||||
|
* full Cstage pipeline (w6c → w6a → w6l). Each fixture is a tiny
|
||||||
|
* .ww program that exercises one path: literal-init read, plain
|
||||||
|
* assignment, compound assignment, and address-of.
|
||||||
|
*
|
||||||
|
* Exit code = the value the runtime feeds to `exit(2)`. main's
|
||||||
|
* return value flows into DI via rt/start.s and ends up as the
|
||||||
|
* shell's $?.
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_exit(const char *cmd)
|
||||||
|
{
|
||||||
|
int rc = system(cmd);
|
||||||
|
if (rc == -1) return -1;
|
||||||
|
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
build_and_run(const char *bin, const char *prog)
|
||||||
|
{
|
||||||
|
/* `ww run` builds with the driver (w6c → w6a → w6l + libwwrt.a)
|
||||||
|
* and executes the result. The exit code propagates back as the
|
||||||
|
* shell's $? so we just compare against the fixture's want. */
|
||||||
|
char src[64], cmd[1024];
|
||||||
|
snprintf(src, sizeof src, "/tmp/wwt_lg_%d.ww", getpid());
|
||||||
|
|
||||||
|
FILE *f = fopen(src, "wb");
|
||||||
|
if (!f) return -1;
|
||||||
|
fputs(prog, f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
snprintf(cmd, sizeof cmd, "%s/ww run %s", bin, src);
|
||||||
|
int rc = run_exit(cmd);
|
||||||
|
unlink(src);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct fixture {
|
||||||
|
const char *label;
|
||||||
|
const char *prog;
|
||||||
|
int want;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct fixture fixtures[] = {
|
||||||
|
{
|
||||||
|
"read",
|
||||||
|
"let counter: i32 = 42;\n"
|
||||||
|
"fn main() i32 = { return counter; };\n",
|
||||||
|
42,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"assign",
|
||||||
|
"let counter: i32 = 0;\n"
|
||||||
|
"fn main() i32 = { counter = 99; return counter; };\n",
|
||||||
|
99,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"compound",
|
||||||
|
"let counter: i32 = 1;\n"
|
||||||
|
"fn inc() void = { counter += 1; };\n"
|
||||||
|
"fn main() i32 = { inc(); inc(); inc(); return counter; };\n",
|
||||||
|
4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zero-init",
|
||||||
|
/* No initialiser → zero-filled DATAW slot. */
|
||||||
|
"let counter: i32;\n"
|
||||||
|
"fn main() i32 = { counter = 7; return counter; };\n",
|
||||||
|
7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"addr-of",
|
||||||
|
"let counter: i32 = 11;\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
"\tlet p: *i32 = &counter;\n"
|
||||||
|
"\t*p = 55;\n"
|
||||||
|
"\treturn counter;\n"
|
||||||
|
"};\n",
|
||||||
|
55,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"u64-read",
|
||||||
|
"let val: u64 = 123u64;\n"
|
||||||
|
"fn main() i32 = { return val: i32; };\n",
|
||||||
|
123,
|
||||||
|
},
|
||||||
|
{ NULL, NULL, 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
main(void)
|
||||||
|
{
|
||||||
|
const char *bin = getenv("BIN");
|
||||||
|
if (!bin) bin = "out/bin";
|
||||||
|
|
||||||
|
int fail = 0, ran = 0;
|
||||||
|
for (int i = 0; fixtures[i].label; i++, ran++) {
|
||||||
|
int got = build_and_run(bin, fixtures[i].prog);
|
||||||
|
if (got != fixtures[i].want) {
|
||||||
|
fprintf(stderr, "let_global[%s]: exit=%d, want %d\n",
|
||||||
|
fixtures[i].label, got, fixtures[i].want);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fail) {
|
||||||
|
fprintf(stderr, "let_global: %d/%d fixtures failed\n", fail, ran);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("let_global: %d/%d ok\n", ran, ran);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user