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:
164
cmd/w6c/cgen.c
164
cmd/w6c/cgen.c
@@ -397,13 +397,13 @@ struct LetVar {
|
|||||||
};
|
};
|
||||||
static LetVar *letvars;
|
static LetVar *letvars;
|
||||||
|
|
||||||
/* Subset of types we know how to store in 8 bytes of .data and load
|
/* Slot size for a top-level `let` of type t, or 0 if the type isn't
|
||||||
* back with a plain MOVQ. Floats need MOVSS/MOVSD; str/slice/struct/
|
* supported as a writable global yet. Floats (MOVSS/SD) and slice/
|
||||||
* tagged unions are multi-word; enums route through their storage
|
* struct/tagged unions are deferred. enums route through their
|
||||||
* type. Keep this tight — extending it requires the matching load/
|
* storage type. Keep this tight — extending it requires the matching
|
||||||
* store code below. */
|
* load/store code below. */
|
||||||
static int
|
static int
|
||||||
let_scalar_ok(Type *t)
|
let_emit_size(Type *t)
|
||||||
{
|
{
|
||||||
if (t == NULL) return 0;
|
if (t == NULL) return 0;
|
||||||
Type *u = (t->kind == TY_NAMED) ? t->under : t;
|
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_U8: case TY_U16: case TY_U32: case TY_U64:
|
||||||
case TY_INT: case TY_UINT: case TY_UINTPTR:
|
case TY_INT: case TY_UINT: case TY_UINTPTR:
|
||||||
case TY_PTR:
|
case TY_PTR:
|
||||||
return 1;
|
return 8;
|
||||||
|
case TY_STR:
|
||||||
|
return 16; /* {ptr, len}; literal-strlit init NYI. */
|
||||||
default:
|
default:
|
||||||
return 0;
|
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
|
static int
|
||||||
decl_has_ffisym(Node *d)
|
decl_has_ffisym(Node *d)
|
||||||
{
|
{
|
||||||
@@ -476,7 +489,7 @@ let_collect(Cg *c, Node *file)
|
|||||||
for (Node *d = file->list; d; d = d->next) {
|
for (Node *d = file->list; d; d = d->next) {
|
||||||
if (d->kind != N_LET) continue;
|
if (d->kind != N_LET) continue;
|
||||||
if (d->str == NULL || d->str[0] == '\0') 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);
|
LetVar *lv = amalloc(c->a, sizeof *lv);
|
||||||
lv->name = d->str;
|
lv->name = d->str;
|
||||||
lv->next = letvars;
|
lv->next = letvars;
|
||||||
@@ -751,6 +764,15 @@ 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)) {
|
||||||
|
/* 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));
|
ins2(c, A_MOVQ, masym(c, n->str), areg(D_AX));
|
||||||
}
|
}
|
||||||
ident_done:
|
ident_done:
|
||||||
@@ -1421,17 +1443,29 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
}
|
}
|
||||||
/* Plain `name = strexpr;` for a str-typed local. cgexpr leaves
|
/* Plain `name = strexpr;` for a str-typed local. cgexpr leaves
|
||||||
* (AX=ptr, BX=len); store both halves at off+0 and off+8.
|
* (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
|
if (n->lhs && n->lhs->kind == N_IDENT && n->op == TK_ASSIGN
|
||||||
&& n->lhs->type) {
|
&& n->lhs->type) {
|
||||||
Type *lt = n->lhs->type;
|
Type *lt = n->lhs->type;
|
||||||
Type *lu = (lt && lt->kind == TY_NAMED) ? lt->under : lt;
|
Type *lu = (lt && lt->kind == TY_NAMED) ? lt->under : lt;
|
||||||
if (lu && lu->kind == TY_STR) {
|
if (lu && lu->kind == TY_STR) {
|
||||||
int off = localfind(locals, n->lhs->str);
|
int off = localfind(locals, n->lhs->str);
|
||||||
if (off == 0) break;
|
if (off != 0) {
|
||||||
cgexpr(c, n->rhs, locals);
|
cgexpr(c, n->rhs, locals);
|
||||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
||||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off + 8));
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2429,6 +2463,20 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
}
|
}
|
||||||
goto dot_done;
|
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);
|
int delta = ptrfld ? 0 : (lenfld ? 8 : 16);
|
||||||
ins2(c, A_MOVQ, amem(D_BP, off + delta),
|
ins2(c, A_MOVQ, amem(D_BP, off + delta),
|
||||||
@@ -3570,50 +3618,88 @@ 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
|
/* Escape one byte for an asm string literal — the same rules
|
||||||
* the escape rules with emit_defs/emit_data so the .o bytes stay
|
* emit_data and emit_defs already use. */
|
||||||
* stable. */
|
static void
|
||||||
|
emit_data_byte(FILE *out, u8 b)
|
||||||
|
{
|
||||||
|
if (b == '"' || b == '\\')
|
||||||
|
fprintf(out, "\\%c", b);
|
||||||
|
else if (b < 0x20 || b >= 0x7f)
|
||||||
|
fprintf(out, "\\x%02x", b);
|
||||||
|
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
|
static void
|
||||||
emit_data_row(FILE *out, const char *dir, const char *name, u64 v)
|
emit_data_row(FILE *out, const char *dir, const char *name, u64 v)
|
||||||
{
|
{
|
||||||
fprintf(out, "%s %s(SB),\"", dir, name);
|
fprintf(out, "%s %s(SB),\"", dir, name);
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++)
|
||||||
unsigned b = (unsigned)((v >> (i * 8)) & 0xff);
|
emit_data_byte(out, (u8)((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);
|
fputs("\"\n", out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Emit DATAW directives for top-level mutable `let` decls. Only scalar
|
/* Emit `DIR NAME(SB),"<sz zero bytes>"`. Used for top-level str/
|
||||||
* types in `let_scalar_ok` are supported; the rest are silently
|
* slice/struct lets without a baked-in initialiser — the slot is
|
||||||
* skipped at cgen and link with an undefined-symbol error if used.
|
* pre-zeroed and the program writes the real value at runtime. */
|
||||||
* Initialisers must be integer/rune/bool/nil literals (or a `let`
|
static void
|
||||||
* with no init, which zero-initialises). */
|
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
|
static void
|
||||||
emit_lets(Cg *c, FILE *out, Node *file)
|
emit_lets(Cg *c, FILE *out, Node *file)
|
||||||
{
|
{
|
||||||
for (Node *d = file->list; d; d = d->next) {
|
for (Node *d = file->list; d; d = d->next) {
|
||||||
if (d->kind != N_LET) continue;
|
if (d->kind != N_LET) continue;
|
||||||
if (d->str == NULL || d->str[0] == '\0') continue;
|
if (d->str == NULL || d->str[0] == '\0') continue;
|
||||||
if (!let_scalar_ok(d->type)) continue;
|
int sz = let_emit_size(d->type);
|
||||||
u64 v = 0;
|
if (sz == 0) continue;
|
||||||
|
if (sz == 8) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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) {
|
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;
|
||||||
if (r == NULL) continue;
|
if (r == NULL) continue;
|
||||||
if (r->kind == N_INTLIT) v = r->uval;
|
int empty_str = (r->kind == N_STRLIT && r->strlen == 0);
|
||||||
else if (r->kind == N_RUNELIT) v = r->uval;
|
if (r->kind != N_NIL && !empty_str)
|
||||||
else if (r->kind == N_TRUE) v = 1;
|
continue;
|
||||||
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_row_zero(out, "DATAW", mod_mangle(c, d->str), sz);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6129,11 +6129,20 @@ fn cgident(c: *cgen, n: *node) void = {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// 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`. Names
|
// slot. Mirrors C cgen's catch-all `MOVQ masym(s), AX` for
|
||||||
// that aren't lets either (typos, never-defined) flow through
|
// scalar lets, plus the (LEAQ, MOVQ, MOVQ) sequence for str
|
||||||
// here too in C; the divergence today is bounded to scalar lets,
|
// globals so both ptr and len land in (AX, BX). Names that
|
||||||
// which is what `isletvar` gates on.
|
// aren't lets either (typos, never-defined) drop through to
|
||||||
|
// the silent return.
|
||||||
if (isletvar(c, nm)) {
|
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");
|
emitline("\tMOVQ\t");
|
||||||
emitsymname(c, nm);
|
emitsymname(c, nm);
|
||||||
emitline("(SB), AX\n");
|
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`
|
// Module-qualified value reference: `mod.name` where `mod`
|
||||||
// is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local.
|
// 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
|
// 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) {
|
if (off == 0) {
|
||||||
// Top-level let target: RIP-relative store
|
// Top-level let target: RIP-relative store
|
||||||
// for `=`, or load→combine→store for the
|
// 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; };
|
if (!isletvar(c, nm)) { return; };
|
||||||
cgexpr(c, n.rhs);
|
cgexpr(c, n.rhs);
|
||||||
if (n.op == tkind.TK_ASSIGN) {
|
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, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitsymname(c, nm);
|
emitsymname(c, nm);
|
||||||
emitline("(SB)\n");
|
emitline("(SB)\n");
|
||||||
@@ -8739,11 +8782,14 @@ type cgen = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar.
|
// Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar.
|
||||||
// Populated alongside modents; consulted by cgassign and the TK_AMP
|
// Populated alongside modents; consulted by cgassign, cgdot, cgident
|
||||||
// path so reads/writes hit a RIP-relative DATAW slot instead of being
|
// and the TK_AMP path so reads/writes hit a RIP-relative DATAW slot
|
||||||
// silently dropped. Only scalar (≤8B) types make the list.
|
// 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 {
|
type letvar = struct {
|
||||||
name: str,
|
name: str,
|
||||||
|
tnode: *node,
|
||||||
lvnext: *letvar,
|
lvnext: *letvar,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -8967,8 +9013,7 @@ fn internstrlit(c: *cgen, bytes: str) str = {
|
|||||||
// letscalarprim — recognise the bare type-name keywords whose values
|
// letscalarprim — recognise the bare type-name keywords whose values
|
||||||
// fit in an 8-byte .data slot and load back with a plain MOVQ. Float
|
// fit in an 8-byte .data slot and load back with a plain MOVQ. Float
|
||||||
// types deliberately excluded; they need MOVSS/MOVSD. Mirrors C
|
// types deliberately excluded; they need MOVSS/MOVSD. Mirrors C
|
||||||
// `let_scalar_ok` for the unwrapped TY_* enumeration. Pointer types
|
// `let_emit_size`'s 8-byte branch.
|
||||||
// (N_TPTR) handle separately at the callsite.
|
|
||||||
fn letscalarprim(nm: str) bool = {
|
fn letscalarprim(nm: str) bool = {
|
||||||
if (streq(nm, "bool")) { return true; };
|
if (streq(nm, "bool")) { return true; };
|
||||||
if (streq(nm, "rune")) { return true; };
|
if (streq(nm, "rune")) { return true; };
|
||||||
@@ -8986,24 +9031,25 @@ fn letscalarprim(nm: str) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// letscalarok — true iff the N_LET decl's declared type lands in the
|
// letemitsize — slot size in bytes for a top-level `let`, or 0 if
|
||||||
// scalar set, walking type aliases. Pointer types are always ok.
|
// the type isn't yet supported as a writable global. Walks type
|
||||||
fn letscalarok(c: *cgen, d: *node) bool = {
|
// aliases so byte output matches C cgen, which resolves Type kinds.
|
||||||
if (d == nil) { return false; };
|
// 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;
|
let t: *node = d.lhs;
|
||||||
for (t != nil) {
|
for (t != nil) {
|
||||||
if (t.kind == nkind.N_TPTR) { return true; };
|
if (t.kind == nkind.N_TPTR) { return 8; };
|
||||||
if (t.kind != nkind.N_TNAME) { return false; };
|
if (t.kind != nkind.N_TNAME) { return 0; };
|
||||||
let nm: str = t.str;
|
let nm: str = t.str;
|
||||||
if (letscalarprim(nm)) { return true; };
|
if (letscalarprim(nm)) { return 8; };
|
||||||
// Resolve a `type x = y;` alias and look again. C cgen
|
if (streq(nm, "str")) { return 16; };
|
||||||
// works on the resolved Type, so byte output diverges
|
|
||||||
// here without the walk.
|
|
||||||
let next: *node = aliaslookup(c, nm);
|
let next: *node = aliaslookup(c, nm);
|
||||||
if (next == nil) { return false; };
|
if (next == nil) { return 0; };
|
||||||
t = next;
|
t = next;
|
||||||
};
|
};
|
||||||
return false;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
fn collectlets(c: *cgen, file: *node) void = {
|
fn collectlets(c: *cgen, file: *node) void = {
|
||||||
@@ -9014,9 +9060,10 @@ fn collectlets(c: *cgen, file: *node) void = {
|
|||||||
if (d.kind == nkind.N_LET) {
|
if (d.kind == nkind.N_LET) {
|
||||||
let nm: str = d.str;
|
let nm: str = d.str;
|
||||||
if (nm.len > 0) {
|
if (nm.len > 0) {
|
||||||
if (letscalarok(c, d)) {
|
if (letemitsize(c, d) > 0) {
|
||||||
let lv: *letvar = amalloc(c.a, 32u64): *letvar;
|
let lv: *letvar = amalloc(c.a, 48u64): *letvar;
|
||||||
lv.name = nm;
|
lv.name = nm;
|
||||||
|
lv.tnode = d.lhs;
|
||||||
lv.lvnext = c.lets;
|
lv.lvnext = c.lets;
|
||||||
c.lets = lv;
|
c.lets = lv;
|
||||||
};
|
};
|
||||||
@@ -9035,18 +9082,79 @@ fn isletvar(c: *cgen, name: str) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// emitletdataw — DATAW directive per top-level scalar `let`. Same
|
// letvarisstr — is the named top-level let a str global? Resolves
|
||||||
// 8-byte LE byte encoding as emitdefconstants; only the directive
|
// aliases to mirror C cgen's `let_isstr`. Used by cgident/cgdot/
|
||||||
// keyword differs ("DATAW" vs "DATA"). Mirrors cmd/w6c/cgen.c
|
// cgassign to pick the (LEAQ, MOVQ, MOVQ) sequence over the bare
|
||||||
// emit_lets — w6a routes DATAW into a writable .data section, and
|
// MOVQ scalar load.
|
||||||
// w6l covers it with a second R+W PT_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 = {
|
fn emitletdataw(c: *cgen, file: *node) void = {
|
||||||
let d: *node = file.list;
|
let d: *node = file.list;
|
||||||
for (d != nil) {
|
for (d != nil) {
|
||||||
if (d.kind == nkind.N_LET) {
|
if (d.kind == nkind.N_LET) {
|
||||||
let nm: str = d.str;
|
let nm: str = d.str;
|
||||||
if (nm.len > 0) {
|
if (nm.len > 0) {
|
||||||
if (letscalarok(c, d)) {
|
let sz: i32 = letemitsize(c, d);
|
||||||
|
if (sz == 8) {
|
||||||
let v: u64 = 0u64;
|
let v: u64 = 0u64;
|
||||||
let ok: bool = true;
|
let ok: bool = true;
|
||||||
if (d.rhs != nil) {
|
if (d.rhs != nil) {
|
||||||
@@ -9073,37 +9181,35 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
for (i < 8) {
|
for (i < 8) {
|
||||||
let b: u8 = (n & 255u64): u8;
|
let b: u8 = (n & 255u64): u8;
|
||||||
n = n >> 8u64;
|
n = n >> 8u64;
|
||||||
if (b == 34u8) { emitline("\\\""); }
|
emitdatawbyte(b);
|
||||||
else { if (b == 92u8) { emitline("\\\\"); }
|
i += 1;
|
||||||
else {
|
};
|
||||||
if (b < 32u8) {
|
emitline("\"\n");
|
||||||
emitline("\\x");
|
};
|
||||||
let hi: u8 = b >> 4u8;
|
};
|
||||||
let lo: u8 = b & 15u8;
|
if (sz == 16) {
|
||||||
let bb: [2]u8;
|
let ok: bool = true;
|
||||||
if (hi < 10u8) { bb[0] = hi + 48u8; }
|
if (d.rhs != nil) {
|
||||||
else { bb[0] = (hi - 10u8) + 97u8; };
|
let r: *node = d.rhs;
|
||||||
if (lo < 10u8) { bb[1] = lo + 48u8; }
|
for (r != nil) {
|
||||||
else { bb[1] = (lo - 10u8) + 97u8; };
|
if (r.kind != nkind.N_CAST) { break; };
|
||||||
os.write(1, bb.ptr, 2u64);
|
r = r.lhs;
|
||||||
} else {
|
};
|
||||||
if (b >= 127u8) {
|
ok = false;
|
||||||
emitline("\\x");
|
if (r != nil) {
|
||||||
let hi: u8 = b >> 4u8;
|
if (r.kind == nkind.N_NIL) { ok = true; };
|
||||||
let lo: u8 = b & 15u8;
|
if (r.kind == nkind.N_STRLIT) {
|
||||||
let bb: [2]u8;
|
if (r.str.len == 0) { ok = true; };
|
||||||
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; };
|
if (ok) {
|
||||||
os.write(1, bb.ptr, 2u64);
|
emitline("DATAW ");
|
||||||
} else {
|
emitsymname(c, nm);
|
||||||
let bb: [1]u8;
|
emitline("(SB),\"");
|
||||||
bb[0] = b;
|
let i: i32 = 0;
|
||||||
os.write(1, bb.ptr, 1u64);
|
for (i < 16) {
|
||||||
};
|
emitdatawbyte(0u8);
|
||||||
};
|
|
||||||
};};
|
|
||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
emitline("\"\n");
|
emitline("\"\n");
|
||||||
|
|||||||
@@ -332,11 +332,14 @@ type cgen = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar.
|
// Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar.
|
||||||
// Populated alongside modents; consulted by cgassign and the TK_AMP
|
// Populated alongside modents; consulted by cgassign, cgdot, cgident
|
||||||
// path so reads/writes hit a RIP-relative DATAW slot instead of being
|
// and the TK_AMP path so reads/writes hit a RIP-relative DATAW slot
|
||||||
// silently dropped. Only scalar (≤8B) types make the list.
|
// 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 {
|
type letvar = struct {
|
||||||
name: str,
|
name: str,
|
||||||
|
tnode: *node,
|
||||||
lvnext: *letvar,
|
lvnext: *letvar,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -560,8 +563,7 @@ fn internstrlit(c: *cgen, bytes: str) str = {
|
|||||||
// letscalarprim — recognise the bare type-name keywords whose values
|
// letscalarprim — recognise the bare type-name keywords whose values
|
||||||
// fit in an 8-byte .data slot and load back with a plain MOVQ. Float
|
// fit in an 8-byte .data slot and load back with a plain MOVQ. Float
|
||||||
// types deliberately excluded; they need MOVSS/MOVSD. Mirrors C
|
// types deliberately excluded; they need MOVSS/MOVSD. Mirrors C
|
||||||
// `let_scalar_ok` for the unwrapped TY_* enumeration. Pointer types
|
// `let_emit_size`'s 8-byte branch.
|
||||||
// (N_TPTR) handle separately at the callsite.
|
|
||||||
fn letscalarprim(nm: str) bool = {
|
fn letscalarprim(nm: str) bool = {
|
||||||
if (streq(nm, "bool")) { return true; };
|
if (streq(nm, "bool")) { return true; };
|
||||||
if (streq(nm, "rune")) { return true; };
|
if (streq(nm, "rune")) { return true; };
|
||||||
@@ -579,24 +581,25 @@ fn letscalarprim(nm: str) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// letscalarok — true iff the N_LET decl's declared type lands in the
|
// letemitsize — slot size in bytes for a top-level `let`, or 0 if
|
||||||
// scalar set, walking type aliases. Pointer types are always ok.
|
// the type isn't yet supported as a writable global. Walks type
|
||||||
fn letscalarok(c: *cgen, d: *node) bool = {
|
// aliases so byte output matches C cgen, which resolves Type kinds.
|
||||||
if (d == nil) { return false; };
|
// 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;
|
let t: *node = d.lhs;
|
||||||
for (t != nil) {
|
for (t != nil) {
|
||||||
if (t.kind == nkind.N_TPTR) { return true; };
|
if (t.kind == nkind.N_TPTR) { return 8; };
|
||||||
if (t.kind != nkind.N_TNAME) { return false; };
|
if (t.kind != nkind.N_TNAME) { return 0; };
|
||||||
let nm: str = t.str;
|
let nm: str = t.str;
|
||||||
if (letscalarprim(nm)) { return true; };
|
if (letscalarprim(nm)) { return 8; };
|
||||||
// Resolve a `type x = y;` alias and look again. C cgen
|
if (streq(nm, "str")) { return 16; };
|
||||||
// works on the resolved Type, so byte output diverges
|
|
||||||
// here without the walk.
|
|
||||||
let next: *node = aliaslookup(c, nm);
|
let next: *node = aliaslookup(c, nm);
|
||||||
if (next == nil) { return false; };
|
if (next == nil) { return 0; };
|
||||||
t = next;
|
t = next;
|
||||||
};
|
};
|
||||||
return false;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
fn collectlets(c: *cgen, file: *node) void = {
|
fn collectlets(c: *cgen, file: *node) void = {
|
||||||
@@ -607,9 +610,10 @@ fn collectlets(c: *cgen, file: *node) void = {
|
|||||||
if (d.kind == nkind.N_LET) {
|
if (d.kind == nkind.N_LET) {
|
||||||
let nm: str = d.str;
|
let nm: str = d.str;
|
||||||
if (nm.len > 0) {
|
if (nm.len > 0) {
|
||||||
if (letscalarok(c, d)) {
|
if (letemitsize(c, d) > 0) {
|
||||||
let lv: *letvar = amalloc(c.a, 32u64): *letvar;
|
let lv: *letvar = amalloc(c.a, 48u64): *letvar;
|
||||||
lv.name = nm;
|
lv.name = nm;
|
||||||
|
lv.tnode = d.lhs;
|
||||||
lv.lvnext = c.lets;
|
lv.lvnext = c.lets;
|
||||||
c.lets = lv;
|
c.lets = lv;
|
||||||
};
|
};
|
||||||
@@ -628,18 +632,79 @@ fn isletvar(c: *cgen, name: str) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// emitletdataw — DATAW directive per top-level scalar `let`. Same
|
// letvarisstr — is the named top-level let a str global? Resolves
|
||||||
// 8-byte LE byte encoding as emitdefconstants; only the directive
|
// aliases to mirror C cgen's `let_isstr`. Used by cgident/cgdot/
|
||||||
// keyword differs ("DATAW" vs "DATA"). Mirrors cmd/w6c/cgen.c
|
// cgassign to pick the (LEAQ, MOVQ, MOVQ) sequence over the bare
|
||||||
// emit_lets — w6a routes DATAW into a writable .data section, and
|
// MOVQ scalar load.
|
||||||
// w6l covers it with a second R+W PT_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 = {
|
fn emitletdataw(c: *cgen, file: *node) void = {
|
||||||
let d: *node = file.list;
|
let d: *node = file.list;
|
||||||
for (d != nil) {
|
for (d != nil) {
|
||||||
if (d.kind == nkind.N_LET) {
|
if (d.kind == nkind.N_LET) {
|
||||||
let nm: str = d.str;
|
let nm: str = d.str;
|
||||||
if (nm.len > 0) {
|
if (nm.len > 0) {
|
||||||
if (letscalarok(c, d)) {
|
let sz: i32 = letemitsize(c, d);
|
||||||
|
if (sz == 8) {
|
||||||
let v: u64 = 0u64;
|
let v: u64 = 0u64;
|
||||||
let ok: bool = true;
|
let ok: bool = true;
|
||||||
if (d.rhs != nil) {
|
if (d.rhs != nil) {
|
||||||
@@ -666,37 +731,35 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
for (i < 8) {
|
for (i < 8) {
|
||||||
let b: u8 = (n & 255u64): u8;
|
let b: u8 = (n & 255u64): u8;
|
||||||
n = n >> 8u64;
|
n = n >> 8u64;
|
||||||
if (b == 34u8) { emitline("\\\""); }
|
emitdatawbyte(b);
|
||||||
else { if (b == 92u8) { emitline("\\\\"); }
|
i += 1;
|
||||||
else {
|
};
|
||||||
if (b < 32u8) {
|
emitline("\"\n");
|
||||||
emitline("\\x");
|
};
|
||||||
let hi: u8 = b >> 4u8;
|
};
|
||||||
let lo: u8 = b & 15u8;
|
if (sz == 16) {
|
||||||
let bb: [2]u8;
|
let ok: bool = true;
|
||||||
if (hi < 10u8) { bb[0] = hi + 48u8; }
|
if (d.rhs != nil) {
|
||||||
else { bb[0] = (hi - 10u8) + 97u8; };
|
let r: *node = d.rhs;
|
||||||
if (lo < 10u8) { bb[1] = lo + 48u8; }
|
for (r != nil) {
|
||||||
else { bb[1] = (lo - 10u8) + 97u8; };
|
if (r.kind != nkind.N_CAST) { break; };
|
||||||
os.write(1, bb.ptr, 2u64);
|
r = r.lhs;
|
||||||
} else {
|
};
|
||||||
if (b >= 127u8) {
|
ok = false;
|
||||||
emitline("\\x");
|
if (r != nil) {
|
||||||
let hi: u8 = b >> 4u8;
|
if (r.kind == nkind.N_NIL) { ok = true; };
|
||||||
let lo: u8 = b & 15u8;
|
if (r.kind == nkind.N_STRLIT) {
|
||||||
let bb: [2]u8;
|
if (r.str.len == 0) { ok = true; };
|
||||||
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; };
|
if (ok) {
|
||||||
os.write(1, bb.ptr, 2u64);
|
emitline("DATAW ");
|
||||||
} else {
|
emitsymname(c, nm);
|
||||||
let bb: [1]u8;
|
emitline("(SB),\"");
|
||||||
bb[0] = b;
|
let i: i32 = 0;
|
||||||
os.write(1, bb.ptr, 1u64);
|
for (i < 16) {
|
||||||
};
|
emitdatawbyte(0u8);
|
||||||
};
|
|
||||||
};};
|
|
||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
emitline("\"\n");
|
emitline("\"\n");
|
||||||
|
|||||||
@@ -401,11 +401,20 @@ fn cgident(c: *cgen, n: *node) void = {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// 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`. Names
|
// slot. Mirrors C cgen's catch-all `MOVQ masym(s), AX` for
|
||||||
// that aren't lets either (typos, never-defined) flow through
|
// scalar lets, plus the (LEAQ, MOVQ, MOVQ) sequence for str
|
||||||
// here too in C; the divergence today is bounded to scalar lets,
|
// globals so both ptr and len land in (AX, BX). Names that
|
||||||
// which is what `isletvar` gates on.
|
// aren't lets either (typos, never-defined) drop through to
|
||||||
|
// the silent return.
|
||||||
if (isletvar(c, nm)) {
|
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");
|
emitline("\tMOVQ\t");
|
||||||
emitsymname(c, nm);
|
emitsymname(c, nm);
|
||||||
emitline("(SB), AX\n");
|
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`
|
// Module-qualified value reference: `mod.name` where `mod`
|
||||||
// is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local.
|
// 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
|
// 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) {
|
if (off == 0) {
|
||||||
// Top-level let target: RIP-relative store
|
// Top-level let target: RIP-relative store
|
||||||
// for `=`, or load→combine→store for the
|
// 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; };
|
if (!isletvar(c, nm)) { return; };
|
||||||
cgexpr(c, n.rhs);
|
cgexpr(c, n.rhs);
|
||||||
if (n.op == tkind.TK_ASSIGN) {
|
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, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitsymname(c, nm);
|
emitsymname(c, nm);
|
||||||
emitline("(SB)\n");
|
emitline("(SB)\n");
|
||||||
|
|||||||
@@ -6129,11 +6129,20 @@ fn cgident(c: *cgen, n: *node) void = {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// 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`. Names
|
// slot. Mirrors C cgen's catch-all `MOVQ masym(s), AX` for
|
||||||
// that aren't lets either (typos, never-defined) flow through
|
// scalar lets, plus the (LEAQ, MOVQ, MOVQ) sequence for str
|
||||||
// here too in C; the divergence today is bounded to scalar lets,
|
// globals so both ptr and len land in (AX, BX). Names that
|
||||||
// which is what `isletvar` gates on.
|
// aren't lets either (typos, never-defined) drop through to
|
||||||
|
// the silent return.
|
||||||
if (isletvar(c, nm)) {
|
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");
|
emitline("\tMOVQ\t");
|
||||||
emitsymname(c, nm);
|
emitsymname(c, nm);
|
||||||
emitline("(SB), AX\n");
|
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`
|
// Module-qualified value reference: `mod.name` where `mod`
|
||||||
// is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local.
|
// 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
|
// 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) {
|
if (off == 0) {
|
||||||
// Top-level let target: RIP-relative store
|
// Top-level let target: RIP-relative store
|
||||||
// for `=`, or load→combine→store for the
|
// 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; };
|
if (!isletvar(c, nm)) { return; };
|
||||||
cgexpr(c, n.rhs);
|
cgexpr(c, n.rhs);
|
||||||
if (n.op == tkind.TK_ASSIGN) {
|
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, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitsymname(c, nm);
|
emitsymname(c, nm);
|
||||||
emitline("(SB)\n");
|
emitline("(SB)\n");
|
||||||
@@ -8739,11 +8782,14 @@ type cgen = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar.
|
// Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar.
|
||||||
// Populated alongside modents; consulted by cgassign and the TK_AMP
|
// Populated alongside modents; consulted by cgassign, cgdot, cgident
|
||||||
// path so reads/writes hit a RIP-relative DATAW slot instead of being
|
// and the TK_AMP path so reads/writes hit a RIP-relative DATAW slot
|
||||||
// silently dropped. Only scalar (≤8B) types make the list.
|
// 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 {
|
type letvar = struct {
|
||||||
name: str,
|
name: str,
|
||||||
|
tnode: *node,
|
||||||
lvnext: *letvar,
|
lvnext: *letvar,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -8967,8 +9013,7 @@ fn internstrlit(c: *cgen, bytes: str) str = {
|
|||||||
// letscalarprim — recognise the bare type-name keywords whose values
|
// letscalarprim — recognise the bare type-name keywords whose values
|
||||||
// fit in an 8-byte .data slot and load back with a plain MOVQ. Float
|
// fit in an 8-byte .data slot and load back with a plain MOVQ. Float
|
||||||
// types deliberately excluded; they need MOVSS/MOVSD. Mirrors C
|
// types deliberately excluded; they need MOVSS/MOVSD. Mirrors C
|
||||||
// `let_scalar_ok` for the unwrapped TY_* enumeration. Pointer types
|
// `let_emit_size`'s 8-byte branch.
|
||||||
// (N_TPTR) handle separately at the callsite.
|
|
||||||
fn letscalarprim(nm: str) bool = {
|
fn letscalarprim(nm: str) bool = {
|
||||||
if (streq(nm, "bool")) { return true; };
|
if (streq(nm, "bool")) { return true; };
|
||||||
if (streq(nm, "rune")) { return true; };
|
if (streq(nm, "rune")) { return true; };
|
||||||
@@ -8986,24 +9031,25 @@ fn letscalarprim(nm: str) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// letscalarok — true iff the N_LET decl's declared type lands in the
|
// letemitsize — slot size in bytes for a top-level `let`, or 0 if
|
||||||
// scalar set, walking type aliases. Pointer types are always ok.
|
// the type isn't yet supported as a writable global. Walks type
|
||||||
fn letscalarok(c: *cgen, d: *node) bool = {
|
// aliases so byte output matches C cgen, which resolves Type kinds.
|
||||||
if (d == nil) { return false; };
|
// 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;
|
let t: *node = d.lhs;
|
||||||
for (t != nil) {
|
for (t != nil) {
|
||||||
if (t.kind == nkind.N_TPTR) { return true; };
|
if (t.kind == nkind.N_TPTR) { return 8; };
|
||||||
if (t.kind != nkind.N_TNAME) { return false; };
|
if (t.kind != nkind.N_TNAME) { return 0; };
|
||||||
let nm: str = t.str;
|
let nm: str = t.str;
|
||||||
if (letscalarprim(nm)) { return true; };
|
if (letscalarprim(nm)) { return 8; };
|
||||||
// Resolve a `type x = y;` alias and look again. C cgen
|
if (streq(nm, "str")) { return 16; };
|
||||||
// works on the resolved Type, so byte output diverges
|
|
||||||
// here without the walk.
|
|
||||||
let next: *node = aliaslookup(c, nm);
|
let next: *node = aliaslookup(c, nm);
|
||||||
if (next == nil) { return false; };
|
if (next == nil) { return 0; };
|
||||||
t = next;
|
t = next;
|
||||||
};
|
};
|
||||||
return false;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
fn collectlets(c: *cgen, file: *node) void = {
|
fn collectlets(c: *cgen, file: *node) void = {
|
||||||
@@ -9014,9 +9060,10 @@ fn collectlets(c: *cgen, file: *node) void = {
|
|||||||
if (d.kind == nkind.N_LET) {
|
if (d.kind == nkind.N_LET) {
|
||||||
let nm: str = d.str;
|
let nm: str = d.str;
|
||||||
if (nm.len > 0) {
|
if (nm.len > 0) {
|
||||||
if (letscalarok(c, d)) {
|
if (letemitsize(c, d) > 0) {
|
||||||
let lv: *letvar = amalloc(c.a, 32u64): *letvar;
|
let lv: *letvar = amalloc(c.a, 48u64): *letvar;
|
||||||
lv.name = nm;
|
lv.name = nm;
|
||||||
|
lv.tnode = d.lhs;
|
||||||
lv.lvnext = c.lets;
|
lv.lvnext = c.lets;
|
||||||
c.lets = lv;
|
c.lets = lv;
|
||||||
};
|
};
|
||||||
@@ -9035,18 +9082,79 @@ fn isletvar(c: *cgen, name: str) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// emitletdataw — DATAW directive per top-level scalar `let`. Same
|
// letvarisstr — is the named top-level let a str global? Resolves
|
||||||
// 8-byte LE byte encoding as emitdefconstants; only the directive
|
// aliases to mirror C cgen's `let_isstr`. Used by cgident/cgdot/
|
||||||
// keyword differs ("DATAW" vs "DATA"). Mirrors cmd/w6c/cgen.c
|
// cgassign to pick the (LEAQ, MOVQ, MOVQ) sequence over the bare
|
||||||
// emit_lets — w6a routes DATAW into a writable .data section, and
|
// MOVQ scalar load.
|
||||||
// w6l covers it with a second R+W PT_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 = {
|
fn emitletdataw(c: *cgen, file: *node) void = {
|
||||||
let d: *node = file.list;
|
let d: *node = file.list;
|
||||||
for (d != nil) {
|
for (d != nil) {
|
||||||
if (d.kind == nkind.N_LET) {
|
if (d.kind == nkind.N_LET) {
|
||||||
let nm: str = d.str;
|
let nm: str = d.str;
|
||||||
if (nm.len > 0) {
|
if (nm.len > 0) {
|
||||||
if (letscalarok(c, d)) {
|
let sz: i32 = letemitsize(c, d);
|
||||||
|
if (sz == 8) {
|
||||||
let v: u64 = 0u64;
|
let v: u64 = 0u64;
|
||||||
let ok: bool = true;
|
let ok: bool = true;
|
||||||
if (d.rhs != nil) {
|
if (d.rhs != nil) {
|
||||||
@@ -9073,37 +9181,35 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
for (i < 8) {
|
for (i < 8) {
|
||||||
let b: u8 = (n & 255u64): u8;
|
let b: u8 = (n & 255u64): u8;
|
||||||
n = n >> 8u64;
|
n = n >> 8u64;
|
||||||
if (b == 34u8) { emitline("\\\""); }
|
emitdatawbyte(b);
|
||||||
else { if (b == 92u8) { emitline("\\\\"); }
|
i += 1;
|
||||||
else {
|
};
|
||||||
if (b < 32u8) {
|
emitline("\"\n");
|
||||||
emitline("\\x");
|
};
|
||||||
let hi: u8 = b >> 4u8;
|
};
|
||||||
let lo: u8 = b & 15u8;
|
if (sz == 16) {
|
||||||
let bb: [2]u8;
|
let ok: bool = true;
|
||||||
if (hi < 10u8) { bb[0] = hi + 48u8; }
|
if (d.rhs != nil) {
|
||||||
else { bb[0] = (hi - 10u8) + 97u8; };
|
let r: *node = d.rhs;
|
||||||
if (lo < 10u8) { bb[1] = lo + 48u8; }
|
for (r != nil) {
|
||||||
else { bb[1] = (lo - 10u8) + 97u8; };
|
if (r.kind != nkind.N_CAST) { break; };
|
||||||
os.write(1, bb.ptr, 2u64);
|
r = r.lhs;
|
||||||
} else {
|
};
|
||||||
if (b >= 127u8) {
|
ok = false;
|
||||||
emitline("\\x");
|
if (r != nil) {
|
||||||
let hi: u8 = b >> 4u8;
|
if (r.kind == nkind.N_NIL) { ok = true; };
|
||||||
let lo: u8 = b & 15u8;
|
if (r.kind == nkind.N_STRLIT) {
|
||||||
let bb: [2]u8;
|
if (r.str.len == 0) { ok = true; };
|
||||||
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; };
|
if (ok) {
|
||||||
os.write(1, bb.ptr, 2u64);
|
emitline("DATAW ");
|
||||||
} else {
|
emitsymname(c, nm);
|
||||||
let bb: [1]u8;
|
emitline("(SB),\"");
|
||||||
bb[0] = b;
|
let i: i32 = 0;
|
||||||
os.write(1, bb.ptr, 1u64);
|
for (i < 16) {
|
||||||
};
|
emitdatawbyte(0u8);
|
||||||
};
|
|
||||||
};};
|
|
||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
emitline("\"\n");
|
emitline("\"\n");
|
||||||
|
|||||||
@@ -91,6 +91,28 @@ static const struct fixture fixtures[] = {
|
|||||||
"fn main() i32 = { return val: i32; };\n",
|
"fn main() i32 = { return val: i32; };\n",
|
||||||
123,
|
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 }
|
{ NULL, NULL, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user