2814 lines
88 KiB
C
2814 lines
88 KiB
C
/*
|
|
* cgen.c — typed AST → Prog list, expressed as Plan 9-flavoured
|
|
* amd64 assembly text. This is the simplest thing that works:
|
|
*
|
|
* - Every function gets a stack frame sized for spilled locals + a
|
|
* 16-byte alignment pad.
|
|
* - Expressions are evaluated stack-machine style: result in AX,
|
|
* intermediate stuff pushed on the hardware stack via PUSHQ AX.
|
|
* - The first six integer args go in DI, SI, DX, CX, R8, R9
|
|
* (SysV amd64 ABI). We don't yet handle struct-by-value or
|
|
* floats; floats and slices are deferred.
|
|
*
|
|
* Calling our own functions: emit CALL <name>(SB), let w6a/w6l resolve.
|
|
* Calling C externs: same — extern symbols are just unresolved CALLs.
|
|
*/
|
|
#include "gc.h"
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
static const int sysv_argregs[] = { D_DI, D_SI, D_DX, D_CX, D_R8, D_R9 };
|
|
static const int sysv_fargregs[] = { D_X0, D_X1, D_X2, D_X3, D_X4, D_X5, D_X6, D_X7 };
|
|
|
|
/* per-fn cursor, reset before each cgfn: counts how many 8-byte
|
|
* stack-arg slots above BP have been claimed. */
|
|
int cg_stack_arg_cursor;
|
|
|
|
/* return type of the current function, set by cgfn before walking
|
|
* the body. Drives tagged-union return construction and the `?` /
|
|
* `!` propagation paths. */
|
|
static Type *cg_ret_type;
|
|
/* Pointer to the current function's frame size accumulator. cgexpr
|
|
* needs this to allocate scratch slots (e.g. match bindings) without
|
|
* threading it through every signature. */
|
|
static int *cg_frame;
|
|
|
|
/* Per-fn defer stack: pushed in registration order, popped (emitted)
|
|
* in reverse at each return. */
|
|
#define DEFER_MAX 32
|
|
static Node *defers[DEFER_MAX];
|
|
static int ndefers;
|
|
|
|
/* Loop stack: each `for` records the labels its `break`/`continue`
|
|
* target. The continue label is where the iterator step + cond test
|
|
* happens; the end label sits past the loop. */
|
|
#define LOOP_MAX 16
|
|
static const char *loop_cont[LOOP_MAX];
|
|
static const char *loop_brk[LOOP_MAX];
|
|
static int nloops;
|
|
|
|
static int
|
|
cg_isfloat(Type *t)
|
|
{
|
|
if (t == NULL) return 0;
|
|
if (t->kind == TY_NAMED) t = t->under;
|
|
if (t == NULL) return 0;
|
|
return t->kind == TY_F32 || t->kind == TY_F64
|
|
|| t->kind == TY_UNTYPED_FLOAT;
|
|
}
|
|
|
|
static int
|
|
node_isfloat(Node *n)
|
|
{
|
|
return n && cg_isfloat(n->type);
|
|
}
|
|
|
|
static int
|
|
type_isstr(Type *t)
|
|
{
|
|
if (t == NULL) return 0;
|
|
if (t->kind == TY_NAMED) t = t->under;
|
|
if (t == NULL) return 0;
|
|
return t->kind == TY_STR || t->kind == TY_UNTYPED_STR;
|
|
}
|
|
|
|
static int
|
|
node_isstr(Node *n)
|
|
{
|
|
return n && type_isstr(n->type);
|
|
}
|
|
|
|
static int
|
|
type_isslice(Type *t)
|
|
{
|
|
if (t == NULL) return 0;
|
|
if (t->kind == TY_NAMED) t = t->under;
|
|
return t && t->kind == TY_SLICE;
|
|
}
|
|
|
|
static int
|
|
node_isslice(Node *n)
|
|
{
|
|
return n && type_isslice(n->type);
|
|
}
|
|
|
|
static int
|
|
type_isf32(Type *t)
|
|
{
|
|
if (t == NULL) return 0;
|
|
if (t->kind == TY_NAMED) t = t->under;
|
|
return t && t->kind == TY_F32;
|
|
}
|
|
|
|
static int
|
|
node_isf32(Node *n)
|
|
{
|
|
return n && type_isf32(n->type);
|
|
}
|
|
|
|
/* struct ≤16B all-INTEGER: 1 or 2 eightbyte regs.
|
|
* Returns 0 if not a struct or too large. */
|
|
static int
|
|
struct_arg_size(Type *t)
|
|
{
|
|
if (t == NULL) return 0;
|
|
if (t->kind == TY_NAMED) t = t->under;
|
|
if (t == NULL || t->kind != TY_STRUCT) return 0;
|
|
return (int)t->size;
|
|
}
|
|
|
|
/* Tagged-union arg byte size: 16 (8B variants) or 24 (16B variants).
|
|
* Both fit in our 3-register classification. Returns 0 if not a
|
|
* tagged union or too large to pass in registers. */
|
|
static int
|
|
tagged_arg_size(Type *t)
|
|
{
|
|
if (t == NULL) return 0;
|
|
if (t->kind == TY_NAMED) t = t->under;
|
|
if (t == NULL || t->kind != TY_TAGGED) return 0;
|
|
if (t->size > 24) return 0;
|
|
return (int)t->size;
|
|
}
|
|
|
|
static int
|
|
node_istaggedarg(Node *n)
|
|
{
|
|
return n && tagged_arg_size(n->type) > 0;
|
|
}
|
|
|
|
static int
|
|
node_isstructarg(Node *n)
|
|
{
|
|
if (n == NULL) return 0;
|
|
int sz = struct_arg_size(n->type);
|
|
return sz > 0 && sz <= 16;
|
|
}
|
|
|
|
/* Pick the appropriate scalar SSE opcode (SS vs SD) for a node's
|
|
* float type. Untyped float defaults to SD. */
|
|
static int
|
|
op_for(Node *n, int sd_op, int ss_op)
|
|
{
|
|
return node_isf32(n) ? ss_op : sd_op;
|
|
}
|
|
|
|
/* Strict variant matcher. Returns 1 iff a value of `src` should be
|
|
* tagged as variant `vt` in a tagged-union dispatch:
|
|
* - untyped src: first variant whose type can hold it (type_assignable)
|
|
* - both NAMED: pointer-identical (same `type` declaration node)
|
|
* - one NAMED, the other not: no match (different nominal types)
|
|
* - otherwise: structural type_eq
|
|
* The pointer-identity rule is what keeps `(str | linerr)` distinguishable
|
|
* even though linerr unwraps to str. */
|
|
static int
|
|
cg_variant_match(Type *vt, Type *src)
|
|
{
|
|
if (vt == NULL || src == NULL) return 0;
|
|
if (type_isuntyped(src)) return type_assignable(vt, src);
|
|
if (vt->kind == TY_NAMED && src->kind == TY_NAMED) return vt == src;
|
|
if (vt->kind == TY_NAMED || src->kind == TY_NAMED) return 0;
|
|
return type_eq(vt, src);
|
|
}
|
|
|
|
/* Find the variant-tag index of `vt` inside the tagged-union type `t`.
|
|
* Returns -1 if `t` is not tagged or `vt` does not match a variant.
|
|
* Used by N_MATCH dispatch and by the let/assign/return tag synthesis. */
|
|
static int
|
|
cg_tag_for_variant(Type *t, Type *vt)
|
|
{
|
|
if (t == NULL || vt == NULL) return -1;
|
|
if (t->kind == TY_NAMED) t = t->under;
|
|
if (t == NULL || t->kind != TY_TAGGED) return -1;
|
|
int idx = 0;
|
|
for (Tparam *p = t->params; p; p = p->next, idx++) {
|
|
if (cg_variant_match(p->type, vt)) return idx;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
type_istagged(Type *t)
|
|
{
|
|
if (t == NULL) return 0;
|
|
if (t->kind == TY_NAMED) t = t->under;
|
|
return t && t->kind == TY_TAGGED;
|
|
}
|
|
|
|
/* FFI map: ww-side ident name → linker-side symbol name. Built from
|
|
* @symbol("real_name") attributes on fn declarations. */
|
|
typedef struct Ffi Ffi;
|
|
struct Ffi {
|
|
const char *ident;
|
|
const char *symbol;
|
|
Ffi *next;
|
|
};
|
|
static Ffi *ffi_map;
|
|
|
|
/* Def-as-string-literal map. `def NAME: str = "lit"` doesn't materialise
|
|
* as a real linker symbol; instead, references to NAME load the same
|
|
* (ptr, len) pair that the literal would. Avoids needing relocations
|
|
* inside DATA blocks for the ptr field of a str header. */
|
|
typedef struct Sdef Sdef;
|
|
struct Sdef {
|
|
const char *name;
|
|
const char *bytes;
|
|
u64 len;
|
|
Sdef *next;
|
|
};
|
|
static Sdef *sdefs;
|
|
|
|
/* Interned string literals — emitted as DATA directives after all
|
|
* function bodies, so the linker lays them out alongside .text. */
|
|
typedef struct Strlit Strlit;
|
|
struct Strlit {
|
|
const char *label;
|
|
const char *bytes;
|
|
u64 len;
|
|
Strlit *next;
|
|
};
|
|
static Strlit *strlits;
|
|
static int strlit_seq;
|
|
|
|
static const char *
|
|
intern_strlit(Cg *c, const char *bytes, u64 len)
|
|
{
|
|
for (Strlit *s = strlits; s; s = s->next)
|
|
if (s->len == len && memcmp(s->bytes, bytes, len) == 0)
|
|
return s->label;
|
|
Strlit *s = amalloc(c->a, sizeof *s);
|
|
s->label = aprintf(c->a, "_S_%d", strlit_seq++);
|
|
s->bytes = bytes;
|
|
s->len = len;
|
|
s->next = strlits;
|
|
strlits = s;
|
|
return s->label;
|
|
}
|
|
|
|
static void
|
|
emit_data(Cg *c, FILE *out)
|
|
{
|
|
for (Strlit *s = strlits; s; s = s->next) {
|
|
fprintf(out, "DATA %s(SB),\"", s->label);
|
|
for (u64 i = 0; i < s->len; i++) {
|
|
unsigned char b = (unsigned char)s->bytes[i];
|
|
switch (b) {
|
|
case '"': fputs("\\\"", out); break;
|
|
case '\\': fputs("\\\\", out); break;
|
|
case '\n': fputs("\\n", out); break;
|
|
case '\t': fputs("\\t", out); break;
|
|
case '\r': fputs("\\r", out); break;
|
|
default:
|
|
if (b < 0x20 || b >= 0x7f)
|
|
fprintf(out, "\\x%02x", b);
|
|
else
|
|
fputc(b, out);
|
|
}
|
|
}
|
|
/* Trailing NUL: lets `.ptr` be passed to libc / syscalls
|
|
* that expect a C string. The `len` field still excludes
|
|
* this byte, so iteration semantics are unchanged. */
|
|
fputs("\\x00", out);
|
|
fputs("\"\n", out);
|
|
}
|
|
(void)c;
|
|
}
|
|
|
|
static const char *
|
|
ffi_resolve(const char *ident)
|
|
{
|
|
for (Ffi *f = ffi_map; f; f = f->next)
|
|
if (strcmp(f->ident, ident) == 0) return f->symbol;
|
|
return ident;
|
|
}
|
|
|
|
static void
|
|
ffi_collect(Cg *c, Node *file)
|
|
{
|
|
ffi_map = NULL;
|
|
if (file == NULL) return;
|
|
for (Node *d = file->list; d; d = d->next) {
|
|
if (d->kind != N_FNDECL) continue;
|
|
for (Node *a = d->attr; a; a = a->next) {
|
|
if (a->kind != N_ATTR) continue;
|
|
if (strcmp(a->str, "symbol") != 0) continue;
|
|
if (a->list == NULL || a->list->kind != N_STRLIT) continue;
|
|
Ffi *f = amalloc(c->a, sizeof *f);
|
|
f->ident = d->str;
|
|
f->symbol = a->list->str;
|
|
f->next = ffi_map;
|
|
ffi_map = f;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Module-private symbol map. Mirrors selfhost/cmd/wcc/cgen.ww. Each
|
|
* non-exported, non-FFI top-level decl is mangled to <module>.<name>
|
|
* at emission time so two modules can each privately define the same
|
|
* helper without colliding at link time. */
|
|
typedef struct Mod Mod;
|
|
struct Mod {
|
|
const char *name;
|
|
const char *module;
|
|
Mod *next;
|
|
};
|
|
static Mod *mod_map;
|
|
|
|
static int
|
|
decl_has_ffisym(Node *d)
|
|
{
|
|
for (Node *a = d->attr; a; a = a->next) {
|
|
if (a->kind != N_ATTR) continue;
|
|
if (strcmp(a->str, "symbol") == 0) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
mod_collect(Cg *c, Node *file)
|
|
{
|
|
mod_map = NULL;
|
|
if (file == NULL) return;
|
|
for (Node *d = file->list; d; d = d->next) {
|
|
int track = (d->kind == N_FNDECL) || (d->kind == N_TYPEDECL)
|
|
|| (d->kind == N_DEF) || (d->kind == N_LET);
|
|
if (!track) continue;
|
|
if (d->export) continue;
|
|
if (d->module == NULL || d->module[0] == '\0') continue;
|
|
if (decl_has_ffisym(d)) continue;
|
|
/* `main` is the linker entry-point convention. Even when not
|
|
* marked `export`, it must keep its bare name so w6l can
|
|
* resolve `_start`'s `CALL main(SB)`. */
|
|
if (d->str && strcmp(d->str, "main") == 0) continue;
|
|
Mod *m = amalloc(c->a, sizeof *m);
|
|
m->name = d->str;
|
|
m->module = d->module;
|
|
m->next = mod_map;
|
|
mod_map = m;
|
|
}
|
|
}
|
|
|
|
/* Returns the originating module for a name, or NULL if the name
|
|
* isn't a registered private decl. */
|
|
static const char *
|
|
mod_lookup(const char *name)
|
|
{
|
|
for (Mod *m = mod_map; m; m = m->next)
|
|
if (strcmp(m->name, name) == 0) return m->module;
|
|
return NULL;
|
|
}
|
|
|
|
/* Mangle an AST identifier into its asm linker symbol:
|
|
* - @symbol("...") binding wins (return mapped name).
|
|
* - module-private decl → <module>.<name>.
|
|
* - else → name unchanged.
|
|
* Used at every CALL/MOVQ/LEAQ site that targets an AST name. Plain
|
|
* `asym(s)` still emits `s` verbatim — use it for strlit labels and
|
|
* hard-coded runtime symbols like "rt_streq". */
|
|
static const char *
|
|
mod_mangle(Cg *c, const char *ident)
|
|
{
|
|
const char *resolved = ffi_resolve(ident);
|
|
if (resolved != ident) return resolved;
|
|
const char *mod = mod_lookup(ident);
|
|
if (mod == NULL) return ident;
|
|
size_t mn = strlen(mod), in = strlen(ident);
|
|
char *buf = amalloc(c->a, mn + 1 + in + 1);
|
|
memcpy(buf, mod, mn);
|
|
buf[mn] = '.';
|
|
memcpy(buf + mn + 1, ident, in);
|
|
buf[mn + 1 + in] = '\0';
|
|
return buf;
|
|
}
|
|
|
|
/* Forward decl — masym below depends on asym defined further down. */
|
|
static Adr asym(const char *s);
|
|
|
|
static Adr
|
|
masym(Cg *c, const char *ident)
|
|
{
|
|
return asym(mod_mangle(c, ident));
|
|
}
|
|
|
|
void
|
|
cg_init(Cg *c, Arena *a)
|
|
{
|
|
memset(c, 0, sizeof *c);
|
|
c->a = a;
|
|
}
|
|
|
|
Prog *
|
|
newprog(Cg *c, int op)
|
|
{
|
|
Prog *p = amalloc(c->a, sizeof *p);
|
|
p->as = op;
|
|
return p;
|
|
}
|
|
|
|
void
|
|
emit(Cg *c, Prog *p)
|
|
{
|
|
if (c->head == NULL) c->head = p;
|
|
else c->tail->link = p;
|
|
c->tail = p;
|
|
}
|
|
|
|
static Adr
|
|
areg(int r)
|
|
{
|
|
Adr a = { 0 };
|
|
a.type = r;
|
|
return a;
|
|
}
|
|
|
|
static Adr
|
|
aimm(long long v)
|
|
{
|
|
Adr a = { 0 };
|
|
a.type = D_CONST;
|
|
a.offset = v;
|
|
return a;
|
|
}
|
|
|
|
static Adr
|
|
amem(int r, long long off)
|
|
{
|
|
Adr a = { 0 };
|
|
a.type = D_INDIR;
|
|
a.reg = r;
|
|
a.offset = off;
|
|
return a;
|
|
}
|
|
|
|
static Adr
|
|
asym(const char *s)
|
|
{
|
|
Adr a = { 0 };
|
|
a.type = D_EXTERN;
|
|
a.sym = s;
|
|
return a;
|
|
}
|
|
|
|
static Adr
|
|
abranch(const char *s)
|
|
{
|
|
Adr a = { 0 };
|
|
a.type = D_BRANCH;
|
|
a.sym = s;
|
|
return a;
|
|
}
|
|
|
|
static char *
|
|
mklabel(Cg *c, const char *prefix)
|
|
{
|
|
return aprintf(c->a, "%s_%s_%d", c->fnname ? c->fnname : "_", prefix,
|
|
c->labelseq++);
|
|
}
|
|
|
|
static void
|
|
ins2(Cg *c, int op, Adr from, Adr to)
|
|
{
|
|
Prog *p = newprog(c, op);
|
|
p->from = from;
|
|
p->to = to;
|
|
emit(c, p);
|
|
}
|
|
|
|
static void
|
|
ins1(Cg *c, int op, Adr to)
|
|
{
|
|
Prog *p = newprog(c, op);
|
|
p->to = to;
|
|
emit(c, p);
|
|
}
|
|
|
|
static void
|
|
ins0(Cg *c, int op)
|
|
{
|
|
emit(c, newprog(c, op));
|
|
}
|
|
|
|
static void
|
|
label(Cg *c, const char *s)
|
|
{
|
|
Prog *p = newprog(c, A_NOP);
|
|
p->label = s;
|
|
emit(c, p);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* per-fn local table: name → stack offset (positive = below FP) */
|
|
|
|
typedef struct Local Local;
|
|
struct Local {
|
|
const char *name;
|
|
int off; /* relative to BP; negative for locals */
|
|
Local *next;
|
|
};
|
|
|
|
static int
|
|
localoff(Cg *c, Local **head, const char *name, int size, int *frame)
|
|
{
|
|
for (Local *l = *head; l; l = l->next)
|
|
if (strcmp(l->name, name) == 0) return l->off;
|
|
int al = 8;
|
|
*frame = (*frame + size + al - 1) & ~(al - 1);
|
|
int off = -*frame;
|
|
Local *l = amalloc(c->a, sizeof *l);
|
|
l->name = name;
|
|
l->off = off;
|
|
l->next = *head;
|
|
*head = l;
|
|
return off;
|
|
}
|
|
|
|
static int
|
|
localfind(Local *head, const char *name)
|
|
{
|
|
for (Local *l = head; l; l = l->next)
|
|
if (strcmp(l->name, name) == 0) return l->off;
|
|
return 0; /* 0 = not found (caller must verify) */
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* expressions: result lands in AX. Returns 1 on success. */
|
|
|
|
static void cgexpr(Cg*, Node*, Local*);
|
|
static void cgstmt(Cg*, Node*, Local**, int*);
|
|
|
|
static void
|
|
cgexpr_int(Cg *c, long long v)
|
|
{
|
|
ins2(c, A_MOVQ, aimm(v), areg(D_AX));
|
|
}
|
|
|
|
static void
|
|
cgexpr(Cg *c, Node *n, Local *locals)
|
|
{
|
|
if (n == NULL) {
|
|
cgexpr_int(c, 0);
|
|
return;
|
|
}
|
|
switch (n->kind) {
|
|
case N_INTLIT:
|
|
case N_RUNELIT:
|
|
cgexpr_int(c, (long long)n->uval);
|
|
break;
|
|
case N_FLOATLIT: {
|
|
union { double d; u64 u; } x;
|
|
x.d = n->fval;
|
|
ins2(c, A_MOVQ, aimm((long long)x.u), areg(D_AX));
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
ins2(c, A_MOVSD, amem(D_SP, 0), areg(D_X0));
|
|
ins2(c, A_ADDQ, aimm(8), areg(D_SP));
|
|
break;
|
|
}
|
|
case N_STRLIT: {
|
|
/* result lives as the (ptr, len) pair: ptr in AX, len in BX.
|
|
* Call sites that pass a str arg pick these up directly. */
|
|
const char *lab = intern_strlit(c, n->str, n->strlen);
|
|
ins2(c, A_LEAQ, asym(lab), areg(D_AX));
|
|
ins2(c, A_MOVQ, aimm((long long)n->strlen), areg(D_BX));
|
|
break;
|
|
}
|
|
case N_TRUE: cgexpr_int(c, 1); break;
|
|
case N_FALSE:
|
|
case N_NIL: cgexpr_int(c, 0); break;
|
|
case N_IDENT: {
|
|
int off = localfind(locals, n->str);
|
|
if (off != 0) {
|
|
if (node_isfloat(n)) {
|
|
int op = op_for(n, A_MOVSD, A_MOVSS);
|
|
ins2(c, op, amem(D_BP, off), areg(D_X0));
|
|
} else if (node_isstr(n)) {
|
|
/* str values flow as (AX=ptr, BX=len) so they
|
|
* can be returned in AX:DX or pushed to the
|
|
* call-arg stack uniformly. */
|
|
ins2(c, A_MOVQ, amem(D_BP, off), areg(D_AX));
|
|
ins2(c, A_MOVQ, amem(D_BP, off + 8), areg(D_BX));
|
|
} else {
|
|
ins2(c, A_MOVQ, amem(D_BP, off), areg(D_AX));
|
|
}
|
|
} else {
|
|
/* Non-local: function symbols load by address (LEAQ),
|
|
* str-typed `def`s expand to (ptr, len) of the literal,
|
|
* other globals (def constants) load by value (MOVQ). */
|
|
Type *t = n->type;
|
|
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
|
if (u && u->kind == TY_FN) {
|
|
/* Take the address of a function. Apply
|
|
* @symbol resolution so taking the address
|
|
* of a body-less FFI binding yields the C
|
|
* symbol, not the ww-side ident. Hare emits
|
|
* the same `$symname` for both call and
|
|
* address-of via QBE; here we mirror that. */
|
|
ins2(c, A_LEAQ,
|
|
masym(c, n->str), areg(D_AX));
|
|
break;
|
|
}
|
|
for (Sdef *s = sdefs; s; s = s->next) {
|
|
if (strcmp(s->name, n->str) != 0) continue;
|
|
const char *lab = intern_strlit(c, s->bytes,
|
|
s->len);
|
|
ins2(c, A_LEAQ, asym(lab), areg(D_AX));
|
|
ins2(c, A_MOVQ, aimm((long long)s->len),
|
|
areg(D_BX));
|
|
goto ident_done;
|
|
}
|
|
ins2(c, A_MOVQ, masym(c, n->str), areg(D_AX));
|
|
}
|
|
ident_done:
|
|
break;
|
|
}
|
|
case N_UN:
|
|
cgexpr(c, n->lhs, locals);
|
|
switch (n->op) {
|
|
case TK_MINUS: ins1(c, A_NEGQ, areg(D_AX)); break;
|
|
case TK_TILDE: ins1(c, A_NOTQ, areg(D_AX)); break;
|
|
case TK_NOT: {
|
|
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
|
|
char *t = mklabel(c, "tt");
|
|
char *e = mklabel(c, "te");
|
|
ins1(c, A_JE, abranch(t));
|
|
ins2(c, A_MOVQ, aimm(0), areg(D_AX));
|
|
ins1(c, A_JMP, abranch(e));
|
|
label(c, t);
|
|
ins2(c, A_MOVQ, aimm(1), areg(D_AX));
|
|
label(c, e);
|
|
break;
|
|
}
|
|
case TK_AMP: {
|
|
/* address-of for an N_IDENT local */
|
|
if (n->lhs->kind == N_IDENT) {
|
|
int off = localfind(locals, n->lhs->str);
|
|
ins2(c, A_LEAQ, amem(D_BP, off), areg(D_AX));
|
|
}
|
|
break;
|
|
}
|
|
case TK_STAR: /* deref */
|
|
ins2(c, A_MOVQ, amem(D_AX, 0), areg(D_AX));
|
|
break;
|
|
default: break;
|
|
}
|
|
break;
|
|
case N_BIN: {
|
|
/* str == str / str != str — delegate to rt_streq, which
|
|
* does the byte-by-byte compare. */
|
|
if ((n->op == TK_EQ || n->op == TK_NEQ) &&
|
|
node_isstr(n->lhs) && node_isstr(n->rhs)) {
|
|
/* Push rhs (len, then ptr top) */
|
|
if (n->rhs->kind == N_IDENT) {
|
|
int off = localfind(locals, n->rhs->str);
|
|
ins2(c, A_MOVQ, amem(D_BP, off + 8), areg(D_AX));
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
ins2(c, A_MOVQ, amem(D_BP, off), areg(D_AX));
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
} else {
|
|
cgexpr(c, n->rhs, locals); /* AX=ptr, BX=len */
|
|
ins1(c, A_PUSHQ, areg(D_BX));
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
}
|
|
/* Push lhs */
|
|
if (n->lhs->kind == N_IDENT) {
|
|
int off = localfind(locals, n->lhs->str);
|
|
ins2(c, A_MOVQ, amem(D_BP, off + 8), areg(D_AX));
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
ins2(c, A_MOVQ, amem(D_BP, off), areg(D_AX));
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
} else {
|
|
cgexpr(c, n->lhs, locals);
|
|
ins1(c, A_PUSHQ, areg(D_BX));
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
}
|
|
ins1(c, A_POPQ, areg(D_DI));
|
|
ins1(c, A_POPQ, areg(D_SI));
|
|
ins1(c, A_POPQ, areg(D_DX));
|
|
ins1(c, A_POPQ, areg(D_CX));
|
|
ins1(c, A_CALL, asym("rt_streq"));
|
|
if (n->op == TK_NEQ)
|
|
ins2(c, A_XORQ, aimm(1), areg(D_AX));
|
|
break;
|
|
}
|
|
/* Float comparison: operands are float but the BIN node's
|
|
* type is bool, so node_isfloat(n) is false — we have to
|
|
* inspect n->lhs. UCOMISD/UCOMISS sets ZF/CF as if an
|
|
* unsigned compare, so the JA family is the right Jcc set
|
|
* regardless of how the operand types are signed. Plan 9's
|
|
* own w6c picks the same pattern (txt.c around AUCOMISD).
|
|
* NaN handling: UCOMI sets PF on unordered; we ignore it,
|
|
* which means NaN compares behave like Hare's default. */
|
|
if (n->lhs && node_isfloat(n->lhs) &&
|
|
(n->op == TK_EQ || n->op == TK_NEQ
|
|
|| n->op == TK_LT || n->op == TK_LE
|
|
|| n->op == TK_GT || n->op == TK_GE)) {
|
|
int isf32 = node_isf32(n->lhs);
|
|
int mov = isf32 ? A_MOVSS : A_MOVSD;
|
|
int ucomi = isf32 ? A_UCOMISS : A_UCOMISD;
|
|
cgexpr(c, n->rhs, locals); /* rhs → X0 */
|
|
ins2(c, A_SUBQ, aimm(8), areg(D_SP));
|
|
ins2(c, mov, areg(D_X0), amem(D_SP, 0));
|
|
cgexpr(c, n->lhs, locals); /* lhs → X0 */
|
|
ins2(c, mov, amem(D_SP, 0), areg(D_X1));
|
|
ins2(c, A_ADDQ, aimm(8), areg(D_SP));
|
|
ins2(c, ucomi, areg(D_X1), areg(D_X0));
|
|
int op = A_JE;
|
|
switch (n->op) {
|
|
case TK_EQ: op = A_JE; break;
|
|
case TK_NEQ: op = A_JNE; break;
|
|
case TK_LT: op = A_JB; break;
|
|
case TK_LE: op = A_JBE; break;
|
|
case TK_GT: op = A_JA; break;
|
|
case TK_GE: op = A_JAE; break;
|
|
default: break;
|
|
}
|
|
char *t = mklabel(c, "ct");
|
|
char *e = mklabel(c, "ce");
|
|
ins1(c, op, abranch(t));
|
|
ins2(c, A_MOVQ, aimm(0), areg(D_AX));
|
|
ins1(c, A_JMP, abranch(e));
|
|
label(c, t);
|
|
ins2(c, A_MOVQ, aimm(1), areg(D_AX));
|
|
label(c, e);
|
|
break;
|
|
}
|
|
if (node_isfloat(n)) {
|
|
int isf32 = node_isf32(n);
|
|
int mov = isf32 ? A_MOVSS : A_MOVSD;
|
|
cgexpr(c, n->rhs, locals); /* X0 */
|
|
ins2(c, A_SUBQ, aimm(8), areg(D_SP));
|
|
ins2(c, mov, areg(D_X0), amem(D_SP, 0));
|
|
cgexpr(c, n->lhs, locals); /* X0 */
|
|
ins2(c, mov, amem(D_SP, 0), areg(D_X1));
|
|
ins2(c, A_ADDQ, aimm(8), areg(D_SP));
|
|
switch (n->op) {
|
|
case TK_PLUS:
|
|
ins2(c, isf32 ? A_ADDSS : A_ADDSD, areg(D_X1), areg(D_X0));
|
|
break;
|
|
case TK_MINUS:
|
|
ins2(c, isf32 ? A_SUBSS : A_SUBSD, areg(D_X1), areg(D_X0));
|
|
break;
|
|
case TK_STAR:
|
|
ins2(c, isf32 ? A_MULSS : A_MULSD, areg(D_X1), areg(D_X0));
|
|
break;
|
|
case TK_SLASH:
|
|
ins2(c, isf32 ? A_DIVSS : A_DIVSD, areg(D_X1), areg(D_X0));
|
|
break;
|
|
default: break;
|
|
}
|
|
break;
|
|
}
|
|
cgexpr(c, n->rhs, locals);
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
cgexpr(c, n->lhs, locals);
|
|
ins1(c, A_POPQ, areg(D_BX));
|
|
switch (n->op) {
|
|
case TK_PLUS: ins2(c, A_ADDQ, areg(D_BX), areg(D_AX)); break;
|
|
case TK_MINUS: ins2(c, A_SUBQ, areg(D_BX), areg(D_AX)); break;
|
|
case TK_STAR: ins2(c, A_IMULQ, areg(D_BX), areg(D_AX)); break;
|
|
case TK_SLASH: {
|
|
/* Use DIV (unsigned) when either operand is an unsigned
|
|
* integer type — IDIV would sign-extend a u64 with high
|
|
* bit set into a negative i64 and produce wrong results
|
|
* (see strconv.u64toa with v = 1 << 63). */
|
|
int unsignd = (n->lhs && type_isunsigned(n->lhs->type))
|
|
|| (n->rhs && type_isunsigned(n->rhs->type));
|
|
ins2(c, A_MOVQ, aimm(0), areg(D_DX));
|
|
ins1(c, unsignd ? A_DIVQ : A_IDIVQ, areg(D_BX));
|
|
break;
|
|
}
|
|
case TK_PERCENT: {
|
|
int unsignd = (n->lhs && type_isunsigned(n->lhs->type))
|
|
|| (n->rhs && type_isunsigned(n->rhs->type));
|
|
ins2(c, A_MOVQ, aimm(0), areg(D_DX));
|
|
ins1(c, unsignd ? A_DIVQ : A_IDIVQ, areg(D_BX));
|
|
ins2(c, A_MOVQ, areg(D_DX), areg(D_AX));
|
|
break;
|
|
}
|
|
case TK_AMP: ins2(c, A_ANDQ, areg(D_BX), areg(D_AX)); break;
|
|
case TK_PIPE: ins2(c, A_ORQ, areg(D_BX), areg(D_AX)); break;
|
|
case TK_CARET: ins2(c, A_XORQ, areg(D_BX), areg(D_AX)); break;
|
|
case TK_LSHIFT: case TK_RSHIFT:
|
|
/* shift amount must be in CL */
|
|
ins2(c, A_MOVQ, areg(D_BX), areg(D_CX));
|
|
ins2(c, n->op == TK_LSHIFT ? A_SHLQ : A_SHRQ,
|
|
areg(D_CX), areg(D_AX));
|
|
break;
|
|
case TK_EQ: case TK_NEQ: case TK_LT: case TK_LE:
|
|
case TK_GT: case TK_GE: {
|
|
/* For ordered comparisons on unsigned operands we must
|
|
* use the JA/JAE/JB/JBE family — signed Jcc would treat
|
|
* a u64 with the high bit set as negative (e.g. the
|
|
* loop guard `n > 0` in strconv.u64toa with n=1<<63). */
|
|
int unsignd = (n->lhs && type_isunsigned(n->lhs->type))
|
|
|| (n->rhs && type_isunsigned(n->rhs->type));
|
|
ins2(c, A_CMPQ, areg(D_BX), areg(D_AX));
|
|
int op = A_JE;
|
|
switch (n->op) {
|
|
case TK_EQ: op = A_JE; break;
|
|
case TK_NEQ:op = A_JNE; break;
|
|
case TK_LT: op = unsignd ? A_JB : A_JL; break;
|
|
case TK_LE: op = unsignd ? A_JBE : A_JLE; break;
|
|
case TK_GT: op = unsignd ? A_JA : A_JG; break;
|
|
case TK_GE: op = unsignd ? A_JAE : A_JGE; break;
|
|
default: break;
|
|
}
|
|
char *t = mklabel(c, "ct");
|
|
char *e = mklabel(c, "ce");
|
|
ins1(c, op, abranch(t));
|
|
ins2(c, A_MOVQ, aimm(0), areg(D_AX));
|
|
ins1(c, A_JMP, abranch(e));
|
|
label(c, t);
|
|
ins2(c, A_MOVQ, aimm(1), areg(D_AX));
|
|
label(c, e);
|
|
break;
|
|
}
|
|
case TK_AND: case TK_OR: {
|
|
/* short-circuit not yet — eager evaluation. */
|
|
if (n->op == TK_AND)
|
|
ins2(c, A_ANDQ, areg(D_BX), areg(D_AX));
|
|
else
|
|
ins2(c, A_ORQ, areg(D_BX), areg(D_AX));
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
break;
|
|
}
|
|
case N_ASSIGN: {
|
|
/* p.x = v or p.x += v where p.x is a struct field
|
|
* (direct or via *struct). For compound ops we read-modify-
|
|
* write the field; for plain `=` we just write. */
|
|
if (n->lhs && n->lhs->kind == N_DOT && n->lhs->lhs &&
|
|
n->lhs->lhs->kind == N_IDENT) {
|
|
Node *base = n->lhs->lhs;
|
|
Type *bt = base->type;
|
|
Type *u = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
|
|
int via_ptr = 0;
|
|
if (u && u->kind == TY_PTR) {
|
|
via_ptr = 1;
|
|
u = u->sub;
|
|
if (u && u->kind == TY_NAMED) u = u->under;
|
|
}
|
|
/* slice/str pseudo-field write (.ptr/.len/.cap) */
|
|
if (u && (u->kind == TY_SLICE || u->kind == TY_STR)) {
|
|
const char *fld = n->lhs->str;
|
|
int delta = -1;
|
|
if (strcmp(fld, "ptr") == 0) delta = 0;
|
|
else if (strcmp(fld, "len") == 0) delta = 8;
|
|
else if (strcmp(fld, "cap") == 0) delta = 16;
|
|
if (delta < 0) goto after_dot_assign;
|
|
int boff = localfind(locals, base->str);
|
|
if (n->op != TK_ASSIGN) {
|
|
if (via_ptr) {
|
|
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX));
|
|
ins2(c, A_MOVQ, amem(D_BX, delta), areg(D_BX));
|
|
} else {
|
|
ins2(c, A_MOVQ, amem(D_BP, boff + delta), areg(D_BX));
|
|
}
|
|
ins1(c, A_PUSHQ, areg(D_BX));
|
|
}
|
|
cgexpr(c, n->rhs, locals);
|
|
if (n->op != TK_ASSIGN) {
|
|
ins1(c, A_POPQ, areg(D_BX));
|
|
switch (n->op) {
|
|
case TK_PLUSEQ: ins2(c, A_ADDQ, areg(D_BX), areg(D_AX)); break;
|
|
case TK_MINUSEQ:
|
|
/* old in BX, rhs in AX; want AX = old-rhs.
|
|
* SUBQ src,dst is dst -= src in Plan 9. */
|
|
ins2(c, A_SUBQ, areg(D_AX), areg(D_BX));
|
|
ins2(c, A_MOVQ, areg(D_BX), areg(D_AX));
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
if (via_ptr) {
|
|
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX));
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BX, delta));
|
|
} else {
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, boff + delta));
|
|
}
|
|
break;
|
|
}
|
|
after_dot_assign:
|
|
if (u && u->kind == TY_STRUCT) {
|
|
/* find field metadata */
|
|
Tfield *f = NULL;
|
|
for (Tfield *fl = u->fields; fl; fl = fl->next)
|
|
if (strcmp(fl->name, n->lhs->str) == 0)
|
|
{ f = fl; break; }
|
|
if (f == NULL) break;
|
|
/* Tagged-union field: synthesise tag and store
|
|
* value bytes. Compound ops on tagged fields are
|
|
* not meaningful, so only plain `=` is wired. */
|
|
Type *ft = f->type;
|
|
Type *fu = (ft && ft->kind == TY_NAMED)
|
|
? ft->under : ft;
|
|
if (fu && fu->kind == TY_TAGGED
|
|
&& n->op == TK_ASSIGN) {
|
|
int boff = localfind(locals, base->str);
|
|
int foff = (int)f->offset;
|
|
Type *vt = n->rhs ? n->rhs->type : NULL;
|
|
int tag = cg_tag_for_variant(fu, vt);
|
|
/* Resolve the slot's base address
|
|
* (either struct local or *struct). */
|
|
int addr_in_bx = via_ptr;
|
|
if (via_ptr)
|
|
ins2(c, A_MOVQ, amem(D_BP, boff),
|
|
areg(D_BX));
|
|
cgexpr(c, n->rhs, locals);
|
|
if (type_isstr(vt)) {
|
|
if (addr_in_bx) {
|
|
ins2(c, A_MOVQ, areg(D_AX),
|
|
amem(D_BX, foff + 8));
|
|
ins2(c, A_MOVQ, areg(D_BX),
|
|
amem(D_BX, foff + 16));
|
|
} else {
|
|
ins2(c, A_MOVQ, areg(D_AX),
|
|
amem(D_BP, boff + foff + 8));
|
|
ins2(c, A_MOVQ, areg(D_BX),
|
|
amem(D_BP, boff + foff + 16));
|
|
}
|
|
} else {
|
|
if (addr_in_bx)
|
|
ins2(c, A_MOVQ, areg(D_AX),
|
|
amem(D_BX, foff + 8));
|
|
else
|
|
ins2(c, A_MOVQ, areg(D_AX),
|
|
amem(D_BP, boff + foff + 8));
|
|
}
|
|
if (addr_in_bx)
|
|
ins2(c, A_MOVQ,
|
|
aimm(tag < 0 ? 0 : tag),
|
|
amem(D_BX, foff + 0));
|
|
else
|
|
ins2(c, A_MOVQ,
|
|
aimm(tag < 0 ? 0 : tag),
|
|
amem(D_BP, boff + foff + 0));
|
|
break;
|
|
}
|
|
int fsz = (int)(f->type ? f->type->size : 8);
|
|
int signed_field = f->type && (
|
|
f->type->kind == TY_I8 ||
|
|
f->type->kind == TY_I16 ||
|
|
f->type->kind == TY_I32);
|
|
int load_op = A_MOVQ, store_op = A_MOVQ;
|
|
if (fsz == 1) { load_op = A_MOVZBQ; store_op = A_MOVB; }
|
|
else if (fsz == 4) { load_op = signed_field ? A_MOVSXD : A_MOVL; store_op = A_MOVL; }
|
|
int boff = localfind(locals, base->str);
|
|
int foff = (int)f->offset;
|
|
/* str-typed field: rhs cgexpr leaves (AX=ptr, BX=len);
|
|
* store both halves at field+0 and field+8. The 8/16
|
|
* trailing-padding bytes are left untouched, which
|
|
* matches the let-init shape elsewhere in cgen. Only
|
|
* plain `=` is wired; compound on a str field is not
|
|
* meaningful. */
|
|
Type *str_fu = (f->type && f->type->kind == TY_NAMED)
|
|
? f->type->under : f->type;
|
|
if (n->op == TK_ASSIGN && str_fu && str_fu->kind == TY_STR) {
|
|
cgexpr(c, n->rhs, locals);
|
|
if (via_ptr) {
|
|
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_CX));
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_CX, foff + 0));
|
|
ins2(c, A_MOVQ, areg(D_BX), amem(D_CX, foff + 8));
|
|
} else {
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, boff + foff + 0));
|
|
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, boff + foff + 8));
|
|
}
|
|
break;
|
|
}
|
|
/* compound: load current value into BX */
|
|
if (n->op != TK_ASSIGN) {
|
|
if (via_ptr) {
|
|
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX));
|
|
ins2(c, load_op, amem(D_BX, foff), areg(D_BX));
|
|
} else {
|
|
ins2(c, load_op, amem(D_BP, boff + foff), areg(D_BX));
|
|
}
|
|
ins1(c, A_PUSHQ, areg(D_BX));
|
|
}
|
|
cgexpr(c, n->rhs, locals); /* AX = rhs */
|
|
if (n->op != TK_ASSIGN) {
|
|
ins1(c, A_POPQ, areg(D_BX));
|
|
switch (n->op) {
|
|
case TK_PLUSEQ: ins2(c, A_ADDQ, areg(D_BX), areg(D_AX)); break;
|
|
case TK_MINUSEQ:
|
|
/* old in BX, rhs in AX; want AX=old-rhs */
|
|
ins2(c, A_SUBQ, areg(D_AX), areg(D_BX));
|
|
ins2(c, A_MOVQ, areg(D_BX), areg(D_AX));
|
|
break;
|
|
default: break; /* others rare */
|
|
}
|
|
}
|
|
/* now store AX into target */
|
|
if (via_ptr) {
|
|
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX));
|
|
ins2(c, store_op, areg(D_AX), amem(D_BX, foff));
|
|
} else {
|
|
ins2(c, store_op, areg(D_AX), amem(D_BP, boff + foff));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
/* float assignment to a local */
|
|
if (n->lhs && n->lhs->kind == N_IDENT && node_isfloat(n)) {
|
|
cgexpr(c, n->rhs, locals); /* X0 */
|
|
int off = localfind(locals, n->lhs->str);
|
|
if (off != 0) {
|
|
int op = op_for(n, A_MOVSD, A_MOVSS);
|
|
ins2(c, op, areg(D_X0), amem(D_BP, off));
|
|
}
|
|
break;
|
|
}
|
|
/* arr[i] = v store. Base may be a simple ident (array/slice/
|
|
* ptr local) or a more complex expression like s.ptr where
|
|
* s: *[]u8. We compute the base address, scale the index by
|
|
* elem size, and store with the right size. */
|
|
if (n->lhs->kind == N_INDEX && n->lhs->lhs) {
|
|
Node *base = n->lhs->lhs;
|
|
Type *bt = base->type;
|
|
Type *u = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
|
|
int is_arr = u && u->kind == TY_ARRAY;
|
|
int is_sl = u && u->kind == TY_SLICE;
|
|
int is_ptr = u && u->kind == TY_PTR;
|
|
int esz = (u && u->sub) ? (int)u->sub->size : 1;
|
|
int elem_is_str = u && u->sub && type_isstr(u->sub);
|
|
if (is_arr || is_sl || is_ptr) {
|
|
cgexpr(c, n->rhs, locals); /* AX (and BX if str) */
|
|
/* str element: also stash len so we can store both */
|
|
if (elem_is_str)
|
|
ins1(c, A_PUSHQ, areg(D_BX));
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
cgexpr(c, n->lhs->rhs, locals); /* idx → AX */
|
|
if (esz > 1) {
|
|
ins2(c, A_MOVQ, aimm(esz), areg(D_CX));
|
|
ins2(c, A_IMULQ, areg(D_CX), areg(D_AX));
|
|
}
|
|
ins1(c, A_PUSHQ, areg(D_AX)); /* scaled idx */
|
|
/* base address → BX */
|
|
if (base->kind == N_IDENT && is_arr) {
|
|
int off = localfind(locals, base->str);
|
|
ins2(c, A_LEAQ, amem(D_BP, off), areg(D_BX));
|
|
} else if (base->kind == N_IDENT) {
|
|
int off = localfind(locals, base->str);
|
|
ins2(c, A_MOVQ, amem(D_BP, off), areg(D_BX));
|
|
} else {
|
|
cgexpr(c, base, locals);
|
|
ins2(c, A_MOVQ, areg(D_AX), areg(D_BX));
|
|
}
|
|
ins1(c, A_POPQ, areg(D_AX)); /* scaled idx */
|
|
ins2(c, A_ADDQ, areg(D_AX), areg(D_BX));
|
|
ins1(c, A_POPQ, areg(D_AX)); /* value (ptr if str) */
|
|
if (elem_is_str) {
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BX, 0));
|
|
ins1(c, A_POPQ, areg(D_CX));
|
|
ins2(c, A_MOVQ, areg(D_CX), amem(D_BX, 8));
|
|
break;
|
|
}
|
|
int store_op = A_MOVQ;
|
|
if (esz == 1) store_op = A_MOVB;
|
|
else if (esz == 4) store_op = A_MOVL;
|
|
ins2(c, store_op, areg(D_AX), amem(D_BX, 0));
|
|
break;
|
|
}
|
|
}
|
|
/* Plain `r = expr;` where r is a tagged-union local. Mirrors
|
|
* the let-init path: forward AX:DX[:CX] when rhs is itself
|
|
* tagged, else synthesise the tag from rhs's static type. */
|
|
if (n->lhs && n->lhs->kind == N_IDENT && n->op == TK_ASSIGN
|
|
&& n->lhs->type) {
|
|
Type *lt = n->lhs->type;
|
|
Type *lu = (lt && lt->kind == TY_NAMED) ? lt->under : lt;
|
|
if (lu && lu->kind == TY_TAGGED) {
|
|
int off = localfind(locals, n->lhs->str);
|
|
if (off == 0) break;
|
|
Type *rt = n->rhs ? n->rhs->type : NULL;
|
|
if (type_istagged(rt)) {
|
|
cgexpr(c, n->rhs, locals);
|
|
ins2(c, A_MOVQ, areg(D_AX),
|
|
amem(D_BP, off + 0));
|
|
ins2(c, A_MOVQ, areg(D_DX),
|
|
amem(D_BP, off + 8));
|
|
if (lu->size > 16)
|
|
ins2(c, A_MOVQ, areg(D_CX),
|
|
amem(D_BP, off + 16));
|
|
} else {
|
|
int tag = cg_tag_for_variant(lu, rt);
|
|
cgexpr(c, n->rhs, locals);
|
|
if (type_isstr(rt)) {
|
|
ins2(c, A_MOVQ, areg(D_AX),
|
|
amem(D_BP, off + 8));
|
|
ins2(c, A_MOVQ, areg(D_BX),
|
|
amem(D_BP, off + 16));
|
|
} else {
|
|
ins2(c, A_MOVQ, areg(D_AX),
|
|
amem(D_BP, off + 8));
|
|
}
|
|
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
|
amem(D_BP, off + 0));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
/* Deref-target assignment `*p = v;`. The size of the store is
|
|
* determined by the type *p points at; the pointer expression
|
|
* is evaluated after the value so we don't need to spill BX. */
|
|
if (n->lhs && n->lhs->kind == N_UN && n->lhs->op == TK_STAR
|
|
&& n->op == TK_ASSIGN) {
|
|
Type *pt = n->lhs->lhs ? n->lhs->lhs->type : NULL;
|
|
Type *pu = (pt && pt->kind == TY_NAMED) ? pt->under : pt;
|
|
Type *vt = (pu && pu->kind == TY_PTR) ? pu->sub : NULL;
|
|
if (vt && vt->kind == TY_NAMED) vt = vt->under;
|
|
cgexpr(c, n->rhs, locals); /* AX = value (BX too if str) */
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
if (vt && vt->kind == TY_STR) ins1(c, A_PUSHQ, areg(D_BX));
|
|
cgexpr(c, n->lhs->lhs, locals); /* AX = pointer */
|
|
ins2(c, A_MOVQ, areg(D_AX), areg(D_BX));
|
|
if (vt && vt->kind == TY_STR) {
|
|
ins1(c, A_POPQ, areg(D_CX)); /* len */
|
|
ins1(c, A_POPQ, areg(D_AX)); /* ptr */
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BX, 0));
|
|
ins2(c, A_MOVQ, areg(D_CX), amem(D_BX, 8));
|
|
} else {
|
|
ins1(c, A_POPQ, areg(D_AX));
|
|
int sz = vt ? (int)vt->size : 8;
|
|
int signed_field = vt && (vt->kind == TY_I8 ||
|
|
vt->kind == TY_I16 || vt->kind == TY_I32);
|
|
(void)signed_field;
|
|
int store_op = A_MOVQ;
|
|
if (sz == 1) store_op = A_MOVB;
|
|
else if (sz == 4) store_op = A_MOVL;
|
|
ins2(c, store_op, areg(D_AX), amem(D_BX, 0));
|
|
}
|
|
break;
|
|
}
|
|
/* Plain `name = strexpr;` for a str-typed local. cgexpr leaves
|
|
* (AX=ptr, BX=len); store both halves at off+0 and off+8.
|
|
* Mirrors the let-init shape so reassignment doesn't truncate. */
|
|
if (n->lhs && n->lhs->kind == N_IDENT && n->op == TK_ASSIGN
|
|
&& n->lhs->type) {
|
|
Type *lt = n->lhs->type;
|
|
Type *lu = (lt && lt->kind == TY_NAMED) ? lt->under : lt;
|
|
if (lu && lu->kind == TY_STR) {
|
|
int off = localfind(locals, n->lhs->str);
|
|
if (off == 0) break;
|
|
cgexpr(c, n->rhs, locals);
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
|
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off + 8));
|
|
break;
|
|
}
|
|
}
|
|
if (n->lhs->kind == N_IDENT) {
|
|
int off = localfind(locals, n->lhs->str);
|
|
/* No local match: drop the whole assignment, including
|
|
* the RHS evaluation. Top-level let mutation isn't
|
|
* supported (no writable .data) and we don't want to
|
|
* leak a dead `MOVQ $rhs, AX` into the output. */
|
|
if (off == 0) break;
|
|
cgexpr(c, n->rhs, locals);
|
|
if (n->op == TK_ASSIGN) {
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off));
|
|
break;
|
|
}
|
|
/* Compound: load → combine into BX → store. The two
|
|
* direct mem-form combines (ADDQ/SUBQ) are kept for
|
|
* the simple cases; the rest go through the generic
|
|
* register form. */
|
|
if (n->op == TK_PLUSEQ) {
|
|
ins2(c, A_ADDQ, areg(D_AX), amem(D_BP, off));
|
|
break;
|
|
}
|
|
if (n->op == TK_MINUSEQ) {
|
|
ins2(c, A_SUBQ, areg(D_AX), amem(D_BP, off));
|
|
break;
|
|
}
|
|
ins2(c, A_MOVQ, amem(D_BP, off), areg(D_BX));
|
|
switch (n->op) {
|
|
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;
|
|
case TK_SLASHEQ:
|
|
/* BX = BX / AX. IDIV uses DX:AX/RAX. Move
|
|
* BX→AX first, sign-extend via CQO would be
|
|
* cleanest; skip for now and fall back to
|
|
* MOVQ to avoid emitting wrong code. */
|
|
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off));
|
|
goto skip_assign_store;
|
|
default:
|
|
/* unknown: just store rhs (legacy fallback) */
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off));
|
|
goto skip_assign_store;
|
|
}
|
|
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off));
|
|
skip_assign_store: ;
|
|
}
|
|
break;
|
|
}
|
|
case N_CALL: {
|
|
/* Hare-style builtins: len(x) and append(s, v). */
|
|
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
|
|
strcmp(n->lhs->str, "len") == 0 && n->list) {
|
|
Node *a = n->list;
|
|
Type *at = a->type;
|
|
Type *u = (at && at->kind == TY_NAMED) ? at->under : at;
|
|
if (u && (u->kind == TY_SLICE || u->kind == TY_STR)
|
|
&& a->kind == N_IDENT) {
|
|
int off = localfind(locals, a->str);
|
|
ins2(c, A_MOVQ, amem(D_BP, off + 8), areg(D_AX));
|
|
} else if (u && u->kind == TY_ARRAY) {
|
|
ins2(c, A_MOVQ, aimm((long long)u->alen), areg(D_AX));
|
|
} else {
|
|
/* fall back: load via .len pseudo-field */
|
|
cgexpr(c, a, locals);
|
|
}
|
|
break;
|
|
}
|
|
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
|
|
strcmp(n->lhs->str, "free") == 0 && n->list &&
|
|
n->list->next == NULL) {
|
|
Node *p = n->list;
|
|
Type *pt = p->type;
|
|
Type *u = (pt && pt->kind == TY_NAMED) ? pt->under : pt;
|
|
if (u && u->kind == TY_PTR && u->sub) {
|
|
int sz = (int)u->sub->size;
|
|
if (sz == 0) sz = 8;
|
|
cgexpr(c, p, locals);
|
|
ins2(c, A_MOVQ, areg(D_AX), areg(D_DI));
|
|
ins2(c, A_MOVQ, aimm(sz), areg(D_SI));
|
|
ins1(c, A_CALL, asym(ffi_resolve("free")));
|
|
} else if (u && u->kind == TY_SLICE
|
|
&& p->kind == N_IDENT) {
|
|
int off = localfind(locals, p->str);
|
|
int esz = (int)(u->sub ? u->sub->size : 1);
|
|
ins2(c, A_MOVQ, amem(D_BP, off + 16), areg(D_AX));
|
|
if (esz > 1) {
|
|
ins2(c, A_MOVQ, aimm(esz), areg(D_BX));
|
|
ins2(c, A_IMULQ, areg(D_BX), areg(D_AX));
|
|
}
|
|
ins2(c, A_MOVQ, areg(D_AX), areg(D_SI));
|
|
ins2(c, A_MOVQ, amem(D_BP, off + 0), areg(D_DI));
|
|
ins1(c, A_CALL, asym(ffi_resolve("free")));
|
|
}
|
|
break;
|
|
}
|
|
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
|
|
strcmp(n->lhs->str, "alloc") == 0 && n->list) {
|
|
/* alloc(value): heap-init a fresh *T with the value's
|
|
* bytes. Size comes from the value's static type. */
|
|
Node *v = n->list;
|
|
Type *t = v->type;
|
|
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
|
Type *def = type_default(t);
|
|
int sz = def ? (int)def->size : 8;
|
|
if (sz == 0) sz = 8;
|
|
/* call os.alloc(sz) */
|
|
ins2(c, A_MOVQ, aimm(sz), areg(D_DI));
|
|
ins1(c, A_CALL, asym(ffi_resolve("alloc")));
|
|
ins1(c, A_PUSHQ, areg(D_AX)); /* save ptr */
|
|
if (v->kind == N_STRUCTLIT && u && u->kind == TY_STRUCT) {
|
|
for (Node *f = v->list; f; f = f->next) {
|
|
u64 foff = 0;
|
|
int fsz = 8;
|
|
for (Tfield *fl = u->fields; fl; fl = fl->next) {
|
|
if (strcmp(fl->name, f->str) == 0) {
|
|
foff = fl->offset;
|
|
fsz = (int)(fl->type ? fl->type->size : 8);
|
|
break;
|
|
}
|
|
}
|
|
cgexpr(c, f->lhs, locals); /* AX = field val */
|
|
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_BX));
|
|
int op = A_MOVQ;
|
|
if (fsz == 1) op = A_MOVB;
|
|
else if (fsz == 4) op = A_MOVL;
|
|
ins2(c, op, areg(D_AX), amem(D_BX, (int)foff));
|
|
}
|
|
} else {
|
|
cgexpr(c, v, locals); /* AX = value */
|
|
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_BX));
|
|
int op = A_MOVQ;
|
|
if (sz == 1) op = A_MOVB;
|
|
else if (sz == 4) op = A_MOVL;
|
|
ins2(c, op, areg(D_AX), amem(D_BX, 0));
|
|
}
|
|
ins1(c, A_POPQ, areg(D_AX)); /* return the ptr */
|
|
break;
|
|
}
|
|
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
|
|
strcmp(n->lhs->str, "append") == 0 && n->list &&
|
|
n->list->next) {
|
|
/* append(s, v) lowering — Hare's rt::ensure model.
|
|
* ; AX = value
|
|
* ; PUSHQ AX ; save
|
|
* ; ADDQ $1, sn_off+8(BP) ; s.len += 1
|
|
* ; LEAQ sn_off(BP), DI ; arg1 = &s
|
|
* ; MOVQ esz, SI ; arg2 = membsz
|
|
* ; CALL rt_ensure(SB) ; may realloc s.ptr
|
|
* ; MOVQ sn_off+8(BP), CX ; CX = new len
|
|
* ; SUBQ $1, CX ; slot index
|
|
* ; [IMULQ esz, CX] ; byte offset (esz>1)
|
|
* ; MOVQ sn_off(BP), BX ; reread s.ptr
|
|
* ; ADDQ CX, BX ; BX = target
|
|
* ; POPQ AX ; v
|
|
* ; MOV* AX, (BX) ; store (MOVB / MOVQ)
|
|
*
|
|
* Spread form `append(s, items...)` runs this same body
|
|
* in a counted loop over items. */
|
|
Node *sn = n->list;
|
|
Type *st = sn->type;
|
|
Type *su = (st && st->kind == TY_NAMED) ? st->under : st;
|
|
int esz = (su && su->sub) ? (int)su->sub->size : 1;
|
|
int sn_off = (sn->kind == N_IDENT)
|
|
? localfind(locals, sn->str) : 0;
|
|
int store_op = (esz == 1) ? A_MOVB : A_MOVQ;
|
|
for (Node *vn = sn->next; vn; vn = vn->next) {
|
|
if (vn->kind == N_SPREAD &&
|
|
vn->lhs && vn->lhs->kind == N_IDENT) {
|
|
int it_off = localfind(locals, vn->lhs->str);
|
|
int load_op = (esz == 1) ? A_MOVZBQ : A_MOVQ;
|
|
/* push counter (i) on stack */
|
|
ins2(c, A_SUBQ, aimm(8), areg(D_SP));
|
|
ins2(c, A_MOVQ, aimm(0), amem(D_SP, 0));
|
|
char *ll = mklabel(c, "spr_l");
|
|
char *le = mklabel(c, "spr_e");
|
|
label(c, ll);
|
|
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_CX));
|
|
ins2(c, A_MOVQ, amem(D_BP, it_off + 8), areg(D_DX));
|
|
ins2(c, A_CMPQ, areg(D_DX), areg(D_CX));
|
|
ins1(c, A_JGE, abranch(le));
|
|
/* AX = items.ptr[i] */
|
|
ins2(c, A_MOVQ, amem(D_BP, it_off), areg(D_BX));
|
|
if (esz > 1) {
|
|
ins2(c, A_MOVQ, aimm(esz), areg(D_AX));
|
|
ins2(c, A_IMULQ, areg(D_AX), areg(D_CX));
|
|
}
|
|
ins2(c, A_ADDQ, areg(D_CX), areg(D_BX));
|
|
ins2(c, load_op, amem(D_BX, 0), areg(D_AX));
|
|
/* ensure + store one element */
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
ins2(c, A_ADDQ, aimm(1), amem(D_BP, sn_off + 8));
|
|
ins2(c, A_LEAQ, amem(D_BP, sn_off), areg(D_DI));
|
|
ins2(c, A_MOVQ, aimm(esz), areg(D_SI));
|
|
ins1(c, A_CALL, masym(c, "rt_ensure"));
|
|
ins2(c, A_MOVQ, amem(D_BP, sn_off + 8), areg(D_CX));
|
|
ins2(c, A_SUBQ, aimm(1), areg(D_CX));
|
|
if (esz > 1) {
|
|
ins2(c, A_MOVQ, aimm(esz), areg(D_AX));
|
|
ins2(c, A_IMULQ, areg(D_AX), areg(D_CX));
|
|
}
|
|
ins2(c, A_MOVQ, amem(D_BP, sn_off), areg(D_BX));
|
|
ins2(c, A_ADDQ, areg(D_CX), areg(D_BX));
|
|
ins1(c, A_POPQ, areg(D_AX));
|
|
ins2(c, store_op, areg(D_AX), amem(D_BX, 0));
|
|
/* loop tail */
|
|
ins2(c, A_ADDQ, aimm(1), amem(D_SP, 0));
|
|
ins1(c, A_JMP, abranch(ll));
|
|
label(c, le);
|
|
ins2(c, A_ADDQ, aimm(8), areg(D_SP));
|
|
continue;
|
|
}
|
|
cgexpr(c, vn, locals); /* val → AX */
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
ins2(c, A_ADDQ, aimm(1), amem(D_BP, sn_off + 8));
|
|
ins2(c, A_LEAQ, amem(D_BP, sn_off), areg(D_DI));
|
|
ins2(c, A_MOVQ, aimm(esz), areg(D_SI));
|
|
ins1(c, A_CALL, masym(c, "rt_ensure"));
|
|
ins2(c, A_MOVQ, amem(D_BP, sn_off + 8), areg(D_CX));
|
|
ins2(c, A_SUBQ, aimm(1), areg(D_CX));
|
|
if (esz > 1) {
|
|
ins2(c, A_MOVQ, aimm(esz), areg(D_AX));
|
|
ins2(c, A_IMULQ, areg(D_AX), areg(D_CX));
|
|
}
|
|
ins2(c, A_MOVQ, amem(D_BP, sn_off), areg(D_BX));
|
|
ins2(c, A_ADDQ, areg(D_CX), areg(D_BX));
|
|
ins1(c, A_POPQ, areg(D_AX));
|
|
ins2(c, store_op, areg(D_AX), amem(D_BX, 0));
|
|
}
|
|
break;
|
|
}
|
|
/* up to 6 integer + 8 float args via SysV registers.
|
|
* str args occupy two integer eightbytes (ptr, len). */
|
|
int argcount = 0;
|
|
Node *args[16] = {0};
|
|
for (Node *a = n->list; a; a = a->next)
|
|
if (argcount < 16) args[argcount++] = a;
|
|
/* eval right-to-left, push to stack */
|
|
for (int i = argcount - 1; i >= 0; i--) {
|
|
if (node_isslice(args[i]) && args[i]->kind == N_IDENT) {
|
|
int off = localfind(locals, args[i]->str);
|
|
/* push cap, len, ptr (top) so pops give ptr,len,cap */
|
|
ins2(c, A_MOVQ, amem(D_BP, off + 16), areg(D_AX));
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
ins2(c, A_MOVQ, amem(D_BP, off + 8), areg(D_AX));
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
ins2(c, A_MOVQ, amem(D_BP, off + 0), areg(D_AX));
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
continue;
|
|
}
|
|
if (args[i]->kind == N_SLICE) {
|
|
Node *base = args[i]->lhs;
|
|
Node *lo = args[i]->rhs;
|
|
Node *hi = args[i]->cond;
|
|
Type *bt = base ? base->type : NULL;
|
|
Type *bu = (bt && bt->kind == TY_NAMED) ?
|
|
bt->under : bt;
|
|
/* base addr → push */
|
|
if (base->kind == N_IDENT) {
|
|
int boff = localfind(locals, base->str);
|
|
if (bu && bu->kind == TY_ARRAY) {
|
|
ins2(c, A_LEAQ, amem(D_BP, boff), areg(D_AX));
|
|
} else {
|
|
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_AX));
|
|
}
|
|
} else {
|
|
cgexpr(c, base, locals);
|
|
}
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
/* hi (default base length) → push */
|
|
if (hi) cgexpr(c, hi, locals);
|
|
else if (bu && bu->kind == TY_ARRAY)
|
|
cgexpr_int(c, (long long)bu->alen);
|
|
else if (base->kind == N_IDENT && bu &&
|
|
(bu->kind == TY_SLICE || bu->kind == TY_STR)) {
|
|
int boff = localfind(locals, base->str);
|
|
ins2(c, A_MOVQ, amem(D_BP, boff + 8), areg(D_AX));
|
|
} else {
|
|
cgexpr_int(c, 0);
|
|
}
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
/* lo (default 0) → AX */
|
|
if (lo) cgexpr(c, lo, locals);
|
|
else cgexpr_int(c, 0);
|
|
ins1(c, A_POPQ, areg(D_BX)); /* hi */
|
|
ins1(c, A_POPQ, areg(D_CX)); /* base */
|
|
/* len = hi - lo (DX) */
|
|
ins2(c, A_MOVQ, areg(D_BX), areg(D_DX));
|
|
ins2(c, A_SUBQ, areg(D_AX), areg(D_DX));
|
|
/* ptr = base + lo */
|
|
ins2(c, A_ADDQ, areg(D_AX), areg(D_CX));
|
|
/* push cap, len, ptr (top) */
|
|
ins1(c, A_PUSHQ, areg(D_DX)); /* cap */
|
|
ins1(c, A_PUSHQ, areg(D_DX)); /* len */
|
|
ins1(c, A_PUSHQ, areg(D_CX)); /* ptr */
|
|
continue;
|
|
}
|
|
if (node_isstructarg(args[i]) && args[i]->kind == N_IDENT) {
|
|
/* load qword(s) directly from the struct's slot */
|
|
int off = localfind(locals, args[i]->str);
|
|
int sz = struct_arg_size(args[i]->type);
|
|
if (sz > 8) {
|
|
ins2(c, A_MOVQ, amem(D_BP, off + 8), areg(D_AX));
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
}
|
|
ins2(c, A_MOVQ, amem(D_BP, off), areg(D_AX));
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
continue;
|
|
}
|
|
if (node_istaggedarg(args[i]) && args[i]->kind == N_IDENT) {
|
|
/* Tagged-union: push each 8B word from the slot.
|
|
* High word goes first so the popper drains them
|
|
* in low→high order into the arg-register class. */
|
|
int off = localfind(locals, args[i]->str);
|
|
int sz = tagged_arg_size(args[i]->type);
|
|
int nwords = sz / 8;
|
|
for (int k = nwords - 1; k >= 0; k--) {
|
|
ins2(c, A_MOVQ, amem(D_BP, off + k*8),
|
|
areg(D_AX));
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
}
|
|
continue;
|
|
}
|
|
cgexpr(c, args[i], locals);
|
|
if (node_isfloat(args[i])) {
|
|
ins2(c, A_SUBQ, aimm(8), areg(D_SP));
|
|
ins2(c, A_MOVSD, areg(D_X0), amem(D_SP, 0));
|
|
} else if (node_isstr(args[i])) {
|
|
ins1(c, A_PUSHQ, areg(D_BX)); /* len */
|
|
ins1(c, A_PUSHQ, areg(D_AX)); /* ptr — top */
|
|
} else {
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
}
|
|
}
|
|
/* pop forward into the right register class. Args that
|
|
* don't fit in regs stay on the stack and are reached by
|
|
* the callee via positive offsets from BP. The caller is
|
|
* responsible for cleaning them up after CALL. */
|
|
int ii = 0, fi = 0, stackslots = 0;
|
|
for (int i = 0; i < argcount; i++) {
|
|
if (node_isfloat(args[i])) {
|
|
if (fi < 8) {
|
|
ins2(c, A_MOVSD, amem(D_SP, 0),
|
|
areg(sysv_fargregs[fi]));
|
|
ins2(c, A_ADDQ, aimm(8), areg(D_SP));
|
|
fi++;
|
|
} else {
|
|
stackslots++; /* leave on stack */
|
|
}
|
|
} else if (node_isstr(args[i])) {
|
|
for (int k = 0; k < 2; k++) {
|
|
if (ii < 6)
|
|
ins1(c, A_POPQ, areg(sysv_argregs[ii++]));
|
|
else
|
|
stackslots++;
|
|
}
|
|
} else if (node_isslice(args[i])) {
|
|
for (int k = 0; k < 3; k++) {
|
|
if (ii < 6)
|
|
ins1(c, A_POPQ, areg(sysv_argregs[ii++]));
|
|
else
|
|
stackslots++;
|
|
}
|
|
} else if (node_isstructarg(args[i])) {
|
|
int sz = struct_arg_size(args[i]->type);
|
|
int eb = (sz > 8) ? 2 : 1;
|
|
for (int k = 0; k < eb; k++) {
|
|
if (ii < 6)
|
|
ins1(c, A_POPQ, areg(sysv_argregs[ii++]));
|
|
else
|
|
stackslots++;
|
|
}
|
|
} else if (node_istaggedarg(args[i])) {
|
|
int sz = tagged_arg_size(args[i]->type);
|
|
int eb = sz / 8;
|
|
for (int k = 0; k < eb; k++) {
|
|
if (ii < 6)
|
|
ins1(c, A_POPQ, areg(sysv_argregs[ii++]));
|
|
else
|
|
stackslots++;
|
|
}
|
|
} else {
|
|
if (ii < 6) {
|
|
ins1(c, A_POPQ, areg(sysv_argregs[ii]));
|
|
ii++;
|
|
} else {
|
|
stackslots++;
|
|
}
|
|
}
|
|
}
|
|
/* SysV: variadic callees require AL to hold the count of
|
|
* XMM regs used in the variable portion. We don't pass
|
|
* floats yet, so AL=0 covers every case we emit. */
|
|
Type *callee_t = n->lhs ? n->lhs->type : NULL;
|
|
Type *cu = (callee_t && callee_t->kind == TY_NAMED) ?
|
|
callee_t->under : callee_t;
|
|
if (cu && cu->kind == TY_FN && cu->variadic)
|
|
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
|
if (n->lhs->kind == N_IDENT) {
|
|
/* If the callee names a local variable holding a
|
|
* function pointer, load it and call indirect. Without
|
|
* this check `CALL fp(SB)` is emitted as if `fp` were
|
|
* a global symbol — the linker rightly fails. Hare /
|
|
* QBE handles this by treating any non-`$symbol` value
|
|
* as an indirect target; we get the same effect by
|
|
* reusing the cgexpr path. */
|
|
int loff = localfind(locals, n->lhs->str);
|
|
if (loff != 0) {
|
|
ins2(c, A_MOVQ,
|
|
amem(D_BP, loff), areg(D_AX));
|
|
ins1(c, A_CALL, areg(D_AX));
|
|
} else {
|
|
ins1(c, A_CALL,
|
|
masym(c, n->lhs->str));
|
|
}
|
|
} else if (n->lhs->kind == N_DOT && n->lhs->lhs &&
|
|
n->lhs->lhs->kind == N_IDENT) {
|
|
/* `m.fn()` is module-qualified iff the ident has no
|
|
* concrete type (SK_USE leaves it ty_err). For a real
|
|
* type — typically a struct or *struct holding a
|
|
* function pointer — we load the field and indirect. */
|
|
Type *bt = n->lhs->lhs->type;
|
|
if (bt == NULL || bt == ty_err) {
|
|
ins1(c, A_CALL, masym(c, n->lhs->str));
|
|
} else {
|
|
cgexpr(c, n->lhs, locals); /* AX = fn ptr */
|
|
ins1(c, A_CALL, areg(D_AX));
|
|
}
|
|
} else {
|
|
cgexpr(c, n->lhs, locals);
|
|
ins1(c, A_CALL, areg(D_AX));
|
|
}
|
|
/* SysV: caller cleans stack args. */
|
|
if (stackslots > 0)
|
|
ins2(c, A_ADDQ, aimm(stackslots * 8), areg(D_SP));
|
|
/* If callee returns a str (16B → AX:DX per SysV), shuffle
|
|
* len from DX into BX so str values stay in (AX, BX). */
|
|
if (node_isstr(n)) ins2(c, A_MOVQ, areg(D_DX), areg(D_BX));
|
|
break;
|
|
}
|
|
case N_MATCH: {
|
|
/* match on a tagged-union scrutinee. Read tag and value from
|
|
* the slot. Dispatch by the resolved variant index of each
|
|
* case's type pattern — case order is independent of variant
|
|
* declaration order. A case with no pattern (`case =>`) is a
|
|
* default arm; its body always runs.
|
|
*
|
|
* Slot layout: [+0]=tag, [+8]=value0, [+16]=value1. The third
|
|
* word is only meaningful for variants whose payload is >8B
|
|
* (e.g. str). Bindings sized 16B (str) copy two words. */
|
|
Node *s = n->lhs;
|
|
Type *st = s ? s->type : NULL;
|
|
Type *su = (st && st->kind == TY_NAMED) ? st->under : st;
|
|
int slot_size = (su && su->kind == TY_TAGGED) ? (int)su->size : 16;
|
|
int sl_off;
|
|
if (s->kind == N_IDENT) {
|
|
sl_off = localfind(locals, s->str);
|
|
} else if (s->kind == N_DOT && s->lhs && s->lhs->kind == N_IDENT
|
|
&& s->lhs->type) {
|
|
/* `match (p.field)` — point sl_off at the field's slot
|
|
* inside the parent struct. The slot layout (tag at +0,
|
|
* value words at +8/+16) is contiguous within the struct,
|
|
* so no spill is needed. */
|
|
Type *bt = s->lhs->type;
|
|
Type *bu = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
|
|
Tfield *f = NULL;
|
|
if (bu && bu->kind == TY_STRUCT) {
|
|
for (Tfield *fl = bu->fields; fl; fl = fl->next) {
|
|
if (strcmp(fl->name, s->str) == 0) {
|
|
f = fl; break;
|
|
}
|
|
}
|
|
}
|
|
if (f) {
|
|
int boff = localfind(locals, s->lhs->str);
|
|
sl_off = boff + (int)f->offset;
|
|
} else {
|
|
/* fall back to spill */
|
|
sl_off = localoff(c, &locals, "@match_spill",
|
|
slot_size, cg_frame);
|
|
cgexpr(c, s, locals);
|
|
ins2(c, A_MOVQ, areg(D_AX),
|
|
amem(D_BP, sl_off + 0));
|
|
}
|
|
} else {
|
|
/* Spill non-ident scrutinees (e.g. `match (foo()?)`) into
|
|
* a scratch slot so we can index out the tag/value. The
|
|
* call ABI for tagged returns is AX=tag, DX=value0,
|
|
* CX=value1 — copy each word into the slot. */
|
|
sl_off = localoff(c, &locals, "@match_spill", slot_size,
|
|
cg_frame);
|
|
cgexpr(c, s, locals);
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, sl_off + 0));
|
|
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, sl_off + 8));
|
|
if (slot_size > 16)
|
|
ins2(c, A_MOVQ, areg(D_CX),
|
|
amem(D_BP, sl_off + 16));
|
|
}
|
|
char *end = mklabel(c, "match_end");
|
|
for (Node *cs = n->list; cs; cs = cs->next) {
|
|
char *next = mklabel(c, "match_next");
|
|
if (cs->type != NULL) {
|
|
int tag = cg_tag_for_variant(su, cs->type);
|
|
ins2(c, A_MOVQ, amem(D_BP, sl_off + 0), areg(D_AX));
|
|
if (cs->list != NULL) {
|
|
/* Multi-pattern `case T1 | T2 | ... =>`:
|
|
* if the tag matches any of the alts,
|
|
* jump to body; otherwise to the next
|
|
* case. */
|
|
char *body = mklabel(c, "match_body");
|
|
ins2(c, A_CMPQ, aimm(tag < 0 ? 0 : tag),
|
|
areg(D_AX));
|
|
ins1(c, A_JE, abranch(body));
|
|
for (Node *alt = cs->list; alt;
|
|
alt = alt->next) {
|
|
int atag = cg_tag_for_variant(
|
|
su, alt->type);
|
|
ins2(c, A_CMPQ,
|
|
aimm(atag < 0 ? 0 : atag),
|
|
areg(D_AX));
|
|
ins1(c, A_JE, abranch(body));
|
|
}
|
|
ins1(c, A_JMP, abranch(next));
|
|
label(c, body);
|
|
} else {
|
|
ins2(c, A_CMPQ, aimm(tag < 0 ? 0 : tag),
|
|
areg(D_AX));
|
|
ins1(c, A_JNE, abranch(next));
|
|
}
|
|
}
|
|
if (cs->str && cs->str[0] && cs->type) {
|
|
Type *bt = cs->type;
|
|
Type *bu = (bt && bt->kind == TY_NAMED)
|
|
? bt->under : bt;
|
|
int bsz = (bu && bu->kind == TY_STR) ? 16 : 8;
|
|
int voff = localoff(c, &locals, cs->str, bsz,
|
|
cg_frame);
|
|
int nwords = (bsz + 7) / 8;
|
|
for (int w = 0; w < nwords; w++) {
|
|
ins2(c, A_MOVQ,
|
|
amem(D_BP, sl_off + 8 + 8*w),
|
|
areg(D_AX));
|
|
ins2(c, A_MOVQ, areg(D_AX),
|
|
amem(D_BP, voff + 8*w));
|
|
}
|
|
}
|
|
cgstmt(c, cs->body, &locals, cg_frame);
|
|
ins1(c, A_JMP, abranch(end));
|
|
label(c, next);
|
|
}
|
|
label(c, end);
|
|
break;
|
|
}
|
|
case N_TRYPROP: {
|
|
/* Evaluate tagged value: AX=tag, DX=value0[, CX=value1].
|
|
* If tag != 0, propagate as the current function's return.
|
|
* On success, unwrap to the success-variant ABI: ≤8B values
|
|
* land in AX; str values land in (AX=ptr, BX=len). */
|
|
cgexpr(c, n->lhs, locals);
|
|
Type *u = n->lhs ? n->lhs->type : NULL;
|
|
if (u && u->kind == TY_NAMED) u = u->under;
|
|
Type *first = (u && u->kind == TY_TAGGED && u->params)
|
|
? u->params->type : NULL;
|
|
int success_is_str = type_isstr(first);
|
|
char *cont = mklabel(c, "tryprop_ok");
|
|
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
|
|
ins1(c, A_JE, abranch(cont));
|
|
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
|
ins1(c, A_POPQ, areg(D_BP));
|
|
ins0(c, A_RET);
|
|
label(c, cont);
|
|
if (success_is_str)
|
|
ins2(c, A_MOVQ, areg(D_CX), areg(D_BX));
|
|
ins2(c, A_MOVQ, areg(D_DX), areg(D_AX));
|
|
break;
|
|
}
|
|
case N_TRYUNW: {
|
|
/* On error variant: exit(1) directly via the syscall. */
|
|
cgexpr(c, n->lhs, locals);
|
|
Type *u = n->lhs ? n->lhs->type : NULL;
|
|
if (u && u->kind == TY_NAMED) u = u->under;
|
|
Type *first = (u && u->kind == TY_TAGGED && u->params)
|
|
? u->params->type : NULL;
|
|
int success_is_str = type_isstr(first);
|
|
char *cont = mklabel(c, "tryunw_ok");
|
|
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
|
|
ins1(c, A_JE, abranch(cont));
|
|
ins2(c, A_MOVQ, aimm(1), areg(D_DI));
|
|
ins2(c, A_MOVQ, aimm(60), areg(D_AX));
|
|
ins0(c, A_SYSCALL);
|
|
label(c, cont);
|
|
if (success_is_str)
|
|
ins2(c, A_MOVQ, areg(D_CX), areg(D_BX));
|
|
ins2(c, A_MOVQ, areg(D_DX), areg(D_AX));
|
|
break;
|
|
}
|
|
case N_CAST: {
|
|
int from_f = node_isfloat(n->lhs);
|
|
int to_f = cg_isfloat(n->type);
|
|
int from_f32 = node_isf32(n->lhs);
|
|
int to_f32 = type_isf32(n->type);
|
|
cgexpr(c, n->lhs, locals); /* AX or X0 depending */
|
|
if (from_f && !to_f) {
|
|
int op = from_f32 ? A_CVTTSS2SI : A_CVTTSD2SI;
|
|
ins2(c, op, areg(D_X0), areg(D_AX));
|
|
} else if (!from_f && to_f) {
|
|
int op = to_f32 ? A_CVTSI2SS : A_CVTSI2SD;
|
|
ins2(c, op, areg(D_AX), areg(D_X0));
|
|
} else if (from_f && to_f && from_f32 != to_f32) {
|
|
int op = to_f32 ? A_CVTSD2SS : A_CVTSS2SD;
|
|
ins2(c, op, areg(D_X0), areg(D_X0));
|
|
}
|
|
break;
|
|
}
|
|
case N_DOT: {
|
|
/* slice/str pseudo-fields: .ptr (offset 0), .len (8), .cap (16).
|
|
* Arrays don't carry a header; .len uses the static size and
|
|
* .ptr is the address of the first element. */
|
|
Type *bt = n->lhs ? n->lhs->type : NULL;
|
|
Type *u = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
|
|
/* Module-qualified value reference: `mod.name`. The checker
|
|
* leaves SK_USE idents untyped (NULL/ty_err); detect that and
|
|
* look up the leaf in the flat (driver-concatenated) sym/def
|
|
* maps the same way a bare N_IDENT would. */
|
|
if (n->lhs && n->lhs->kind == N_IDENT
|
|
&& (bt == NULL || bt == ty_err)) {
|
|
Type *t = n->type;
|
|
Type *tu = (t && t->kind == TY_NAMED) ? t->under : t;
|
|
if (tu && tu->kind == TY_FN) {
|
|
ins2(c, A_LEAQ,
|
|
masym(c, n->str), areg(D_AX));
|
|
break;
|
|
}
|
|
for (Sdef *s = sdefs; s; s = s->next) {
|
|
if (strcmp(s->name, n->str) != 0) continue;
|
|
const char *lab = intern_strlit(c, s->bytes,
|
|
s->len);
|
|
ins2(c, A_LEAQ, asym(lab), areg(D_AX));
|
|
ins2(c, A_MOVQ, aimm((long long)s->len),
|
|
areg(D_BX));
|
|
goto dot_done;
|
|
}
|
|
ins2(c, A_MOVQ, masym(c, n->str), areg(D_AX));
|
|
goto dot_done;
|
|
}
|
|
int lenfld = (n->str && strcmp(n->str, "len") == 0);
|
|
int capfld = (n->str && strcmp(n->str, "cap") == 0);
|
|
int ptrfld = (n->str && strcmp(n->str, "ptr") == 0);
|
|
if (u && (u->kind == TY_SLICE || u->kind == TY_STR)
|
|
&& (lenfld || capfld || ptrfld)) {
|
|
if (n->lhs->kind == N_IDENT) {
|
|
int off = localfind(locals, n->lhs->str);
|
|
int delta = ptrfld ? 0 : (lenfld ? 8 : 16);
|
|
ins2(c, A_MOVQ, amem(D_BP, off + delta),
|
|
areg(D_AX));
|
|
} else {
|
|
/* Evaluate the str/slice expression — leaves
|
|
* (AX=ptr, BX=len) for str. .ptr returns AX,
|
|
* .len shuffles BX→AX. */
|
|
cgexpr(c, n->lhs, locals);
|
|
if (lenfld)
|
|
ins2(c, A_MOVQ, areg(D_BX),
|
|
areg(D_AX));
|
|
}
|
|
break;
|
|
}
|
|
if (u && u->kind == TY_ARRAY && n->lhs->kind == N_IDENT) {
|
|
int off = localfind(locals, n->lhs->str);
|
|
if (lenfld) {
|
|
ins2(c, A_MOVQ, aimm((long long)u->alen), areg(D_AX));
|
|
break;
|
|
}
|
|
if (ptrfld) {
|
|
ins2(c, A_LEAQ, amem(D_BP, off), areg(D_AX));
|
|
break;
|
|
}
|
|
}
|
|
/* tuple positional field access: t.0, t.1, ... */
|
|
if (u && u->kind == TY_TUPLE && n->lhs->kind == N_IDENT && n->str) {
|
|
int idx = 0;
|
|
for (const char *q = n->str; *q; q++) idx = idx * 10 + (*q - '0');
|
|
Tparam *tp = u->params;
|
|
int foff = 0;
|
|
while (idx > 0 && tp) {
|
|
if (tp->type) foff += (int)tp->type->size;
|
|
tp = tp->next;
|
|
idx--;
|
|
}
|
|
if (tp != NULL) {
|
|
int fsz = (int)(tp->type ? tp->type->size : 8);
|
|
int signed_field = tp->type && (
|
|
tp->type->kind == TY_I8 ||
|
|
tp->type->kind == TY_I16 ||
|
|
tp->type->kind == TY_I32);
|
|
int op = A_MOVQ;
|
|
if (fsz == 1) op = A_MOVZBQ;
|
|
else if (fsz == 4) op = signed_field ? A_MOVSXD : A_MOVL;
|
|
int off = localfind(locals, n->lhs->str);
|
|
ins2(c, op, amem(D_BP, off + foff), areg(D_AX));
|
|
}
|
|
break;
|
|
}
|
|
/* real struct field: load at struct_off + field_off */
|
|
if (u && u->kind == TY_STRUCT && n->lhs->kind == N_IDENT) {
|
|
int off = localfind(locals, n->lhs->str);
|
|
for (Tfield *f = u->fields; f; f = f->next) {
|
|
if (strcmp(f->name, n->str) != 0) continue;
|
|
/* str field: load (ptr, len) into (AX, BX) so the
|
|
* value flows through the str-rhs convention. */
|
|
Type *str_fu = (f->type && f->type->kind == TY_NAMED)
|
|
? f->type->under : f->type;
|
|
if (str_fu && str_fu->kind == TY_STR) {
|
|
ins2(c, A_MOVQ,
|
|
amem(D_BP, off + (int)f->offset + 0),
|
|
areg(D_AX));
|
|
ins2(c, A_MOVQ,
|
|
amem(D_BP, off + (int)f->offset + 8),
|
|
areg(D_BX));
|
|
break;
|
|
}
|
|
int fsz = (int)(f->type ? f->type->size : 8);
|
|
int signed_field = f->type && (
|
|
f->type->kind == TY_I8 ||
|
|
f->type->kind == TY_I16 ||
|
|
f->type->kind == TY_I32);
|
|
int op = A_MOVQ;
|
|
if (fsz == 1) op = A_MOVZBQ;
|
|
else if (fsz == 4) op = signed_field ? A_MOVSXD : A_MOVL;
|
|
ins2(c, op,
|
|
amem(D_BP, off + (int)f->offset),
|
|
areg(D_AX));
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
/* pointer-to-slice/str field: deref and read pseudo-field.
|
|
* Used by helpers like rt_appendu8(s: *[]u8, v: u8). */
|
|
if (u && u->kind == TY_PTR && u->sub) {
|
|
Type *inner = u->sub;
|
|
if (inner->kind == TY_NAMED) inner = inner->under;
|
|
if (inner && (inner->kind == TY_SLICE || inner->kind == TY_STR)
|
|
&& (lenfld || capfld || ptrfld)
|
|
&& n->lhs->kind == N_IDENT) {
|
|
int off = localfind(locals, n->lhs->str);
|
|
ins2(c, A_MOVQ, amem(D_BP, off), areg(D_BX));
|
|
int delta = ptrfld ? 0 : (lenfld ? 8 : 16);
|
|
ins2(c, A_MOVQ, amem(D_BX, delta), areg(D_AX));
|
|
break;
|
|
}
|
|
}
|
|
/* pointer-to-struct field: deref and load. Common pattern:
|
|
* fn move(p: *point) ... { p.x += dx; ... } */
|
|
if (u && u->kind == TY_PTR && u->sub) {
|
|
Type *inner = u->sub;
|
|
if (inner->kind == TY_NAMED) inner = inner->under;
|
|
if (inner && inner->kind == TY_STRUCT
|
|
&& n->lhs->kind == N_IDENT) {
|
|
int off = localfind(locals, n->lhs->str);
|
|
ins2(c, A_MOVQ, amem(D_BP, off), areg(D_BX));
|
|
for (Tfield *f = inner->fields; f; f = f->next) {
|
|
if (strcmp(f->name, n->str) != 0) continue;
|
|
/* str field through *struct: read len into a
|
|
* scratch first (it's at +8) so loading ptr
|
|
* into AX last leaves (AX=ptr, BX=len). We
|
|
* use CX as the scratch, then move CX→BX. */
|
|
Type *str_fu = (f->type && f->type->kind == TY_NAMED)
|
|
? f->type->under : f->type;
|
|
if (str_fu && str_fu->kind == TY_STR) {
|
|
ins2(c, A_MOVQ,
|
|
amem(D_BX, (int)f->offset + 8),
|
|
areg(D_CX));
|
|
ins2(c, A_MOVQ,
|
|
amem(D_BX, (int)f->offset + 0),
|
|
areg(D_AX));
|
|
ins2(c, A_MOVQ, areg(D_CX), areg(D_BX));
|
|
break;
|
|
}
|
|
int fsz = (int)(f->type ? f->type->size : 8);
|
|
int signed_field = f->type && (
|
|
f->type->kind == TY_I8 ||
|
|
f->type->kind == TY_I16 ||
|
|
f->type->kind == TY_I32);
|
|
int op = A_MOVQ;
|
|
if (fsz == 1) op = A_MOVZBQ;
|
|
else if (fsz == 4) op = signed_field ? A_MOVSXD : A_MOVL;
|
|
ins2(c, op,
|
|
amem(D_BX, (int)f->offset),
|
|
areg(D_AX));
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
/* Chained N_DOT through a *struct field. cgexpr lhs leaves
|
|
* AX = the inner *struct pointer; load the requested field
|
|
* with a single MOVQ. Without this, returning `o.p.val`
|
|
* silently leaves AX = o.p (the pointer) and the outer
|
|
* cast/use sees the pointer instead of the dereferenced
|
|
* field. (Surfaced building ww-w6l.) */
|
|
if (n->lhs->kind == N_DOT) {
|
|
Type *lt = n->lhs->type;
|
|
Type *lu = (lt && lt->kind == TY_NAMED) ? lt->under : lt;
|
|
if (lu && lu->kind == TY_PTR && lu->sub) {
|
|
Type *inner = lu->sub;
|
|
if (inner->kind == TY_NAMED) inner = inner->under;
|
|
if (inner && inner->kind == TY_STRUCT) {
|
|
for (Tfield *f = inner->fields; f; f = f->next) {
|
|
if (strcmp(f->name, n->str) != 0) continue;
|
|
cgexpr(c, n->lhs, locals); /* AX = inner ptr */
|
|
Type *ft = f->type;
|
|
Type *fu = (ft && ft->kind == TY_NAMED)
|
|
? ft->under : ft;
|
|
/* str field: load (ptr, len) into (AX, BX). */
|
|
if (fu && fu->kind == TY_STR) {
|
|
ins2(c, A_MOVQ,
|
|
amem(D_AX, (int)f->offset + 8),
|
|
areg(D_BX));
|
|
ins2(c, A_MOVQ,
|
|
amem(D_AX, (int)f->offset + 0),
|
|
areg(D_AX));
|
|
goto dot_done;
|
|
}
|
|
int fsz = (int)(ft ? ft->size : 8);
|
|
int signed_field = ft && (
|
|
ft->kind == TY_I8 ||
|
|
ft->kind == TY_I16 ||
|
|
ft->kind == TY_I32);
|
|
int op = A_MOVQ;
|
|
if (fsz == 1) op = A_MOVZBQ;
|
|
else if (fsz == 4) op = signed_field ? A_MOVSXD : A_MOVL;
|
|
ins2(c, op, amem(D_AX, (int)f->offset),
|
|
areg(D_AX));
|
|
goto dot_done;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* fall through to base evaluation; result placeholder */
|
|
cgexpr(c, n->lhs, locals);
|
|
dot_done:
|
|
break;
|
|
}
|
|
case N_INDEX: {
|
|
/* Scaled indexing for slice/array/str/ptr-to-T.
|
|
* Element size is 1 for u8/str, otherwise type's natural size. */
|
|
Type *bt = n->lhs ? n->lhs->type : NULL;
|
|
Type *u = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
|
|
int esz = 1;
|
|
if (u && u->sub) esz = (int)u->sub->size;
|
|
if (u && u->kind == TY_STR) esz = 1;
|
|
|
|
if (n->lhs->kind == N_IDENT && u) {
|
|
int off = localfind(locals, n->lhs->str);
|
|
cgexpr(c, n->rhs, locals); /* idx → AX */
|
|
if (esz > 1) {
|
|
ins2(c, A_MOVQ, aimm(esz), areg(D_CX));
|
|
ins2(c, A_IMULQ, areg(D_CX), areg(D_AX));
|
|
}
|
|
/* base address into BX */
|
|
if (u->kind == TY_ARRAY) {
|
|
ins2(c, A_LEAQ, amem(D_BP, off), areg(D_BX));
|
|
} else {
|
|
/* slice/str/ptr: ptr field is at off+0 */
|
|
ins2(c, A_MOVQ, amem(D_BP, off), areg(D_BX));
|
|
}
|
|
ins2(c, A_ADDQ, areg(D_AX), areg(D_BX));
|
|
/* str element: load (ptr, len) into (AX, BX) so the
|
|
* value flows through the str-rhs convention. */
|
|
if (u->sub && type_isstr(u->sub)) {
|
|
ins2(c, A_MOVQ, amem(D_BX, 8), areg(D_CX));
|
|
ins2(c, A_MOVQ, amem(D_BX, 0), areg(D_AX));
|
|
ins2(c, A_MOVQ, areg(D_CX), areg(D_BX));
|
|
break;
|
|
}
|
|
int signed_elem = u && u->sub && (
|
|
u->sub->kind == TY_I8 || u->sub->kind == TY_I16 ||
|
|
u->sub->kind == TY_I32);
|
|
int load_op = A_MOVQ;
|
|
if (esz == 1) load_op = A_MOVZBQ;
|
|
else if (esz == 4) load_op = signed_elem ? A_MOVSXD : A_MOVL;
|
|
ins2(c, load_op, amem(D_BX, 0), areg(D_AX));
|
|
break;
|
|
}
|
|
/* Fallback: evaluate base (treat as plain pointer) and
|
|
* dereference at base+idx. Pick the load opcode by element
|
|
* size — `b.data[i]` on a *u8 must read 1 byte, not 8.
|
|
*
|
|
* Scale the index in a register before pushing, because
|
|
* IMULQ on a memory operand isn't currently encoded by w6a
|
|
* (modrm bits use mod=3 register form). */
|
|
cgexpr(c, n->rhs, locals);
|
|
if (esz > 1) {
|
|
ins2(c, A_MOVQ, aimm(esz), areg(D_CX));
|
|
ins2(c, A_IMULQ, areg(D_CX), areg(D_AX));
|
|
}
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
cgexpr(c, n->lhs, locals);
|
|
ins1(c, A_POPQ, areg(D_BX));
|
|
ins2(c, A_ADDQ, areg(D_BX), areg(D_AX));
|
|
/* str element: load (ptr, len) into (AX, BX). */
|
|
if (u && u->sub && type_isstr(u->sub)) {
|
|
ins2(c, A_MOVQ, amem(D_AX, 8), areg(D_BX));
|
|
ins2(c, A_MOVQ, amem(D_AX, 0), areg(D_AX));
|
|
break;
|
|
}
|
|
{
|
|
int signed_elem = u && u->sub && (
|
|
u->sub->kind == TY_I8 || u->sub->kind == TY_I16 ||
|
|
u->sub->kind == TY_I32);
|
|
int load_op = A_MOVQ;
|
|
if (esz == 1) load_op = A_MOVZBQ;
|
|
else if (esz == 4)
|
|
load_op = signed_elem ? A_MOVSXD : A_MOVL;
|
|
ins2(c, load_op, amem(D_AX, 0), areg(D_AX));
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
cgexpr_int(c, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|
{
|
|
if (n == NULL) return;
|
|
switch (n->kind) {
|
|
case N_BLOCK:
|
|
for (Node *s = n->list; s; s = s->next)
|
|
cgstmt(c, s, locals, frame);
|
|
break;
|
|
case N_EXPRSTMT:
|
|
cgexpr(c, n->lhs, *locals);
|
|
break;
|
|
case N_LET: {
|
|
Type *lt = n->type;
|
|
Type *lu = (lt && lt->kind == TY_NAMED) ? lt->under : lt;
|
|
int sz = 8;
|
|
if (lu && lu->kind == TY_ARRAY) sz = (int)lu->size;
|
|
else if (lu && lu->kind == TY_SLICE) sz = 24;
|
|
else if (lu && lu->kind == TY_STR) sz = 16;
|
|
else if (lu && lu->kind == TY_STRUCT) sz = (int)lu->size;
|
|
else if (lu && lu->kind == TY_TUPLE) sz = (int)lu->size;
|
|
else if (lu && lu->kind == TY_TAGGED) sz = (int)lu->size;
|
|
int off = localoff(c, locals, n->str, sz, frame);
|
|
int isf = cg_isfloat(lt);
|
|
int isf32 = type_isf32(lt);
|
|
/* alloc([], n) initialiser for a slice local: allocate
|
|
* n*esize bytes, build the {ptr, 0, n} header in the slot.
|
|
* Element size comes from the declared slice type. */
|
|
if (n->rhs && lu && lu->kind == TY_SLICE && sz == 24
|
|
&& n->rhs->kind == N_CALL && n->rhs->lhs
|
|
&& n->rhs->lhs->kind == N_IDENT
|
|
&& strcmp(n->rhs->lhs->str, "alloc") == 0
|
|
&& n->rhs->list && n->rhs->list->kind == N_ARRLIT
|
|
&& n->rhs->list->list == NULL
|
|
&& n->rhs->list->next && n->rhs->list->next->next == NULL) {
|
|
Node *count = n->rhs->list->next;
|
|
int esz = (lu->sub) ? (int)lu->sub->size : 1;
|
|
cgexpr(c, count, *locals); /* AX = n */
|
|
ins1(c, A_PUSHQ, areg(D_AX)); /* save count */
|
|
if (esz > 1) {
|
|
ins2(c, A_MOVQ, aimm(esz), areg(D_BX));
|
|
ins2(c, A_IMULQ, areg(D_BX), areg(D_AX));
|
|
}
|
|
ins2(c, A_MOVQ, areg(D_AX), areg(D_DI));
|
|
ins1(c, A_CALL, asym(ffi_resolve("alloc")));
|
|
ins1(c, A_POPQ, areg(D_BX)); /* count */
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
|
ins2(c, A_MOVQ, aimm(0), amem(D_BP, off + 8));
|
|
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off + 16));
|
|
break;
|
|
}
|
|
/* str initialiser: cgexpr produces (AX=ptr, BX=len). */
|
|
if (n->rhs && type_isstr(lt) && sz == 16) {
|
|
cgexpr(c, n->rhs, *locals);
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
|
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off + 8));
|
|
break;
|
|
}
|
|
/* 2-tuple initialiser from a function call: SysV returns
|
|
* a 16-byte aggregate in (AX, DX). Store both into the
|
|
* tuple slot. */
|
|
if (n->rhs && lu && lu->kind == TY_TUPLE && sz == 16) {
|
|
cgexpr(c, n->rhs, *locals);
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
|
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off + 8));
|
|
break;
|
|
}
|
|
/* Tagged-union initialiser. Two shapes:
|
|
* 1) rhs already produces a tagged-union value (e.g. a fn
|
|
* call returning (T | E)). cgexpr leaves AX=tag,
|
|
* DX=value0[, CX=value1] — copy each into the slot.
|
|
* 2) rhs is a bare variant value (e.g. `let r: (i64|i32) = 7`).
|
|
* Synthesise the tag from rhs's static type and store it
|
|
* alongside the value. str-typed rhs flows as
|
|
* (AX=ptr, BX=len) so we store both halves. */
|
|
if (n->rhs && lu && lu->kind == TY_TAGGED) {
|
|
Type *rt = n->rhs->type;
|
|
if (type_istagged(rt)) {
|
|
cgexpr(c, n->rhs, *locals);
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
|
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off + 8));
|
|
if (lu->size > 16)
|
|
ins2(c, A_MOVQ, areg(D_CX),
|
|
amem(D_BP, off + 16));
|
|
} else {
|
|
int tag = cg_tag_for_variant(lu, rt);
|
|
cgexpr(c, n->rhs, *locals);
|
|
if (type_isstr(rt)) {
|
|
ins2(c, A_MOVQ, areg(D_AX),
|
|
amem(D_BP, off + 8));
|
|
ins2(c, A_MOVQ, areg(D_BX),
|
|
amem(D_BP, off + 16));
|
|
} else {
|
|
ins2(c, A_MOVQ, areg(D_AX),
|
|
amem(D_BP, off + 8));
|
|
}
|
|
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
|
amem(D_BP, off + 0));
|
|
}
|
|
break;
|
|
}
|
|
/* slice expression initialiser: build a {ptr, len, cap} header
|
|
* referring to the source. Element size assumed to be 1
|
|
* (u8) for now; real element-size scaling is a future TODO. */
|
|
if (n->rhs && n->rhs->kind == N_SLICE && lu
|
|
&& lu->kind == TY_SLICE) {
|
|
Node *base = n->rhs->lhs;
|
|
Node *lo = n->rhs->rhs;
|
|
Node *hi = n->rhs->cond;
|
|
Type *bt = base ? base->type : NULL;
|
|
Type *bu = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
|
|
/* load base address */
|
|
if (base->kind == N_IDENT) {
|
|
int boff = localfind(*locals, base->str);
|
|
if (bu && bu->kind == TY_ARRAY) {
|
|
ins2(c, A_LEAQ, amem(D_BP, boff), areg(D_AX));
|
|
} else {
|
|
/* slice/str/ptr: load .ptr */
|
|
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_AX));
|
|
}
|
|
} else {
|
|
cgexpr(c, base, *locals);
|
|
}
|
|
ins1(c, A_PUSHQ, areg(D_AX)); /* save base addr */
|
|
/* lo (default 0) */
|
|
if (lo) cgexpr(c, lo, *locals);
|
|
else cgexpr_int(c, 0);
|
|
ins1(c, A_PUSHQ, areg(D_AX)); /* save lo */
|
|
/* hi (default base length) */
|
|
if (hi) {
|
|
cgexpr(c, hi, *locals);
|
|
} else if (bu && bu->kind == TY_ARRAY) {
|
|
cgexpr_int(c, (long long)bu->alen);
|
|
} else if (base->kind == N_IDENT && bu &&
|
|
(bu->kind == TY_SLICE || bu->kind == TY_STR)) {
|
|
int boff = localfind(*locals, base->str);
|
|
ins2(c, A_MOVQ, amem(D_BP, boff + 8), areg(D_AX));
|
|
} else {
|
|
cgexpr_int(c, 0);
|
|
}
|
|
ins2(c, A_MOVQ, areg(D_AX), areg(D_BX)); /* BX = hi */
|
|
ins1(c, A_POPQ, areg(D_CX)); /* CX = lo */
|
|
ins1(c, A_POPQ, areg(D_AX)); /* AX = base */
|
|
ins2(c, A_ADDQ, areg(D_CX), areg(D_AX)); /* AX = base+lo */
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
|
ins2(c, A_SUBQ, areg(D_CX), areg(D_BX)); /* BX = hi-lo */
|
|
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off + 8));
|
|
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off + 16));
|
|
break;
|
|
}
|
|
/* struct literal initialiser: field-by-field store. */
|
|
if (n->rhs && n->rhs->kind == N_STRUCTLIT && lu
|
|
&& lu->kind == TY_STRUCT) {
|
|
for (Node *f = n->rhs->list; f; f = f->next) {
|
|
/* find offset of this field */
|
|
u64 foff = 0;
|
|
int fsz = 8;
|
|
Type *ft = NULL;
|
|
for (Tfield *fl = lu->fields; fl; fl = fl->next) {
|
|
if (strcmp(fl->name, f->str) == 0) {
|
|
foff = fl->offset;
|
|
fsz = (int)(fl->type ? fl->type->size : 8);
|
|
ft = fl->type;
|
|
break;
|
|
}
|
|
}
|
|
/* Tagged-union field: synthesise tag from
|
|
* f->lhs's static type and store value bytes
|
|
* (1 or 2 words for ≤8B/str variants). */
|
|
Type *fu = (ft && ft->kind == TY_NAMED)
|
|
? ft->under : ft;
|
|
if (fu && fu->kind == TY_TAGGED) {
|
|
Type *vt = f->lhs ? f->lhs->type : NULL;
|
|
int tag = cg_tag_for_variant(fu, vt);
|
|
cgexpr(c, f->lhs, *locals);
|
|
if (type_isstr(vt)) {
|
|
ins2(c, A_MOVQ, areg(D_AX),
|
|
amem(D_BP, off + (int)foff + 8));
|
|
ins2(c, A_MOVQ, areg(D_BX),
|
|
amem(D_BP, off + (int)foff + 16));
|
|
} else if (type_istagged(vt)) {
|
|
/* forwarding a tagged value */
|
|
ins2(c, A_MOVQ, areg(D_DX),
|
|
amem(D_BP, off + (int)foff + 8));
|
|
if (fu->size > 16)
|
|
ins2(c, A_MOVQ, areg(D_CX),
|
|
amem(D_BP, off + (int)foff + 16));
|
|
/* AX already holds the tag */
|
|
ins2(c, A_MOVQ, areg(D_AX),
|
|
amem(D_BP, off + (int)foff + 0));
|
|
continue;
|
|
} else {
|
|
ins2(c, A_MOVQ, areg(D_AX),
|
|
amem(D_BP, off + (int)foff + 8));
|
|
}
|
|
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
|
amem(D_BP, off + (int)foff + 0));
|
|
continue;
|
|
}
|
|
cgexpr(c, f->lhs, *locals);
|
|
int op = A_MOVQ;
|
|
if (fsz == 1) op = A_MOVB;
|
|
else if (fsz == 4) op = A_MOVL;
|
|
ins2(c, op, areg(D_AX),
|
|
amem(D_BP, off + (int)foff));
|
|
}
|
|
break;
|
|
}
|
|
if (n->rhs && sz == 8) {
|
|
cgexpr(c, n->rhs, *locals);
|
|
if (isf) {
|
|
int mov = isf32 ? A_MOVSS : A_MOVSD;
|
|
ins2(c, mov, areg(D_X0), amem(D_BP, off));
|
|
} else {
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off));
|
|
}
|
|
} else if (sz == 8) {
|
|
ins2(c, A_MOVQ, aimm(0), amem(D_BP, off));
|
|
}
|
|
/* arrays/slices left uninitialised — caller writes via index */
|
|
break;
|
|
}
|
|
case N_RETURN:
|
|
/* run all defers in reverse before the actual return */
|
|
for (int di = ndefers - 1; di >= 0; di--)
|
|
cgexpr(c, defers[di], *locals);
|
|
/* If the function returns a tagged union and the value is
|
|
* one of the variant types, wrap into (tag, value). If rhs
|
|
* already produces a tagged union (e.g. forwarding another
|
|
* fallible call), pass it through unchanged.
|
|
*
|
|
* Tagged-return ABI: AX=tag, DX=value0[, CX=value1]. CX is
|
|
* only meaningful when the union has a >8B variant (e.g.
|
|
* str, where ptr→DX and len→CX). */
|
|
if (n->lhs && cg_ret_type) {
|
|
Type *rt = cg_ret_type;
|
|
if (rt->kind == TY_NAMED) rt = rt->under;
|
|
if (rt && rt->kind == TY_TAGGED) {
|
|
Type *vt = n->lhs->type;
|
|
cgexpr(c, n->lhs, *locals);
|
|
if (!type_istagged(vt)) {
|
|
int tag = cg_tag_for_variant(rt, vt);
|
|
if (type_isstr(vt)) {
|
|
/* AX=ptr, BX=len → DX=ptr,
|
|
* CX=len, AX=tag. Move BX
|
|
* before AX since the tag
|
|
* MOVQ trashes AX. */
|
|
ins2(c, A_MOVQ, areg(D_BX),
|
|
areg(D_CX));
|
|
ins2(c, A_MOVQ, areg(D_AX),
|
|
areg(D_DX));
|
|
} else {
|
|
ins2(c, A_MOVQ, areg(D_AX),
|
|
areg(D_DX));
|
|
}
|
|
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
|
areg(D_AX));
|
|
}
|
|
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
|
ins1(c, A_POPQ, areg(D_BP));
|
|
ins0(c, A_RET);
|
|
break;
|
|
}
|
|
}
|
|
if (n->lhs && node_isstr(n->lhs)) {
|
|
cgexpr(c, n->lhs, *locals); /* AX=ptr, BX=len */
|
|
ins2(c, A_MOVQ, areg(D_BX), areg(D_DX));
|
|
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
|
ins1(c, A_POPQ, areg(D_BP));
|
|
ins0(c, A_RET);
|
|
break;
|
|
}
|
|
if (n->lhs && n->lhs->kind == N_TUPLE) {
|
|
/* small tuples (≤2 elems, ≤8B each) return in AX:DX */
|
|
Node *e0 = n->lhs->list;
|
|
Node *e1 = e0 ? e0->next : NULL;
|
|
if (e1 && e1->next == NULL) {
|
|
cgexpr(c, e1, *locals);
|
|
ins1(c, A_PUSHQ, areg(D_AX));
|
|
cgexpr(c, e0, *locals);
|
|
ins1(c, A_POPQ, areg(D_DX));
|
|
} else {
|
|
/* >2-tuple not yet implemented; fall back to first elem */
|
|
if (e0) cgexpr(c, e0, *locals);
|
|
else cgexpr_int(c, 0);
|
|
}
|
|
} else if (n->lhs) {
|
|
cgexpr(c, n->lhs, *locals);
|
|
} else {
|
|
cgexpr_int(c, 0);
|
|
}
|
|
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
|
ins1(c, A_POPQ, areg(D_BP));
|
|
ins0(c, A_RET);
|
|
break;
|
|
case N_IF: {
|
|
char *els = mklabel(c, "else");
|
|
char *end = mklabel(c, "end");
|
|
cgexpr(c, n->cond, *locals);
|
|
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
|
|
ins1(c, A_JE, abranch(n->els ? els : end));
|
|
cgstmt(c, n->body, locals, frame);
|
|
if (n->els) {
|
|
ins1(c, A_JMP, abranch(end));
|
|
label(c, els);
|
|
cgstmt(c, n->els, locals, frame);
|
|
}
|
|
label(c, end);
|
|
break;
|
|
}
|
|
case N_FORRANGE: {
|
|
/* Lower `for (let x .. s) body` (and its tuple-destructure
|
|
* cousin `for (let (a, b) .. s)`). The body is wrapped in a
|
|
* counted loop driven by stack-spilled `_i`/`_len`. Each
|
|
* iteration computes the element address `s.ptr + i*esz`
|
|
* and either loads the whole element into the named local
|
|
* or pulls each tuple field into its respective local. */
|
|
Node *slc = n->lhs;
|
|
Type *st = slc ? slc->type : NULL;
|
|
Type *u = (st && st->kind == TY_NAMED) ? st->under : st;
|
|
int esz = (u && u->sub) ? (int)u->sub->size : 1;
|
|
Type *etu = (u && u->sub && u->sub->kind == TY_NAMED)
|
|
? u->sub->under : (u ? u->sub : NULL);
|
|
int destruct = (n->list != NULL);
|
|
|
|
/* allocate temp slots: _i (8B), _len (8B) */
|
|
char *iname = aprintf(c->a, ".rgi_%d", c->labelseq++);
|
|
char *lname = aprintf(c->a, ".rgl_%d", c->labelseq++);
|
|
int ioff = localoff(c, locals, iname, 8, frame);
|
|
int loff = localoff(c, locals, lname, 8, frame);
|
|
|
|
/* allocate per-name slots */
|
|
struct { int off, sz, foff; int signed_field; } binds[8] = {0};
|
|
int nbinds = 0;
|
|
if (destruct) {
|
|
Tparam *tp = (etu && etu->kind == TY_TUPLE) ?
|
|
etu->params : NULL;
|
|
int field_off = 0;
|
|
for (Node *nm = n->list; nm && nbinds < 8; nm = nm->next) {
|
|
int fsz = tp && tp->type ? (int)tp->type->size : 8;
|
|
int slot_sz = (fsz < 8) ? 8 : fsz;
|
|
binds[nbinds].sz = fsz;
|
|
binds[nbinds].foff = field_off;
|
|
binds[nbinds].signed_field = tp && tp->type && (
|
|
tp->type->kind == TY_I8 ||
|
|
tp->type->kind == TY_I16 ||
|
|
tp->type->kind == TY_I32);
|
|
binds[nbinds].off = localoff(c, locals,
|
|
nm->str, slot_sz, frame);
|
|
field_off += fsz;
|
|
nbinds++;
|
|
if (tp) tp = tp->next;
|
|
}
|
|
} else {
|
|
int slot_sz = (esz < 8) ? 8 : esz;
|
|
binds[0].off = localoff(c, locals, n->str, slot_sz, frame);
|
|
binds[0].sz = esz;
|
|
binds[0].foff = 0;
|
|
binds[0].signed_field = u && u->sub && (
|
|
u->sub->kind == TY_I8 ||
|
|
u->sub->kind == TY_I16 ||
|
|
u->sub->kind == TY_I32);
|
|
nbinds = 1;
|
|
}
|
|
|
|
ins2(c, A_MOVQ, aimm(0), amem(D_BP, ioff));
|
|
if (u && (u->kind == TY_SLICE || u->kind == TY_STR)
|
|
&& slc->kind == N_IDENT) {
|
|
int boff = localfind(*locals, slc->str);
|
|
ins2(c, A_MOVQ, amem(D_BP, boff + 8), areg(D_AX));
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, loff));
|
|
} else if (u && u->kind == TY_ARRAY) {
|
|
ins2(c, A_MOVQ, aimm((long long)u->alen),
|
|
amem(D_BP, loff));
|
|
} else {
|
|
cgexpr(c, slc, *locals);
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, loff));
|
|
}
|
|
|
|
char *loop = mklabel(c, "rloop");
|
|
char *end = mklabel(c, "rend");
|
|
if (nloops < LOOP_MAX) {
|
|
loop_cont[nloops] = loop;
|
|
loop_brk[nloops] = end;
|
|
nloops++;
|
|
}
|
|
label(c, loop);
|
|
ins2(c, A_MOVQ, amem(D_BP, ioff), areg(D_AX));
|
|
ins2(c, A_MOVQ, amem(D_BP, loff), areg(D_BX));
|
|
ins2(c, A_CMPQ, areg(D_BX), areg(D_AX));
|
|
ins1(c, A_JGE, abranch(end));
|
|
/* compute element base: s.ptr + i*esz → BX */
|
|
if (esz > 1) {
|
|
ins2(c, A_MOVQ, aimm(esz), areg(D_CX));
|
|
ins2(c, A_IMULQ, areg(D_CX), areg(D_AX));
|
|
}
|
|
if (slc->kind == N_IDENT && u && u->kind == TY_ARRAY) {
|
|
int boff = localfind(*locals, slc->str);
|
|
ins2(c, A_LEAQ, amem(D_BP, boff), areg(D_BX));
|
|
} else if (slc->kind == N_IDENT) {
|
|
int boff = localfind(*locals, slc->str);
|
|
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX));
|
|
}
|
|
ins2(c, A_ADDQ, areg(D_AX), areg(D_BX));
|
|
/* load each binding from BX + foff into its slot */
|
|
for (int b = 0; b < nbinds; b++) {
|
|
int op = A_MOVQ;
|
|
if (binds[b].sz == 1) op = A_MOVZBQ;
|
|
else if (binds[b].sz == 4)
|
|
op = binds[b].signed_field ? A_MOVSXD : A_MOVL;
|
|
ins2(c, op, amem(D_BX, binds[b].foff), areg(D_AX));
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, binds[b].off));
|
|
}
|
|
cgstmt(c, n->body, locals, frame);
|
|
ins2(c, A_ADDQ, aimm(1), amem(D_BP, ioff));
|
|
ins1(c, A_JMP, abranch(loop));
|
|
label(c, end);
|
|
if (nloops > 0) nloops--;
|
|
break;
|
|
}
|
|
case N_FOR: {
|
|
char *loop = mklabel(c, "loop");
|
|
char *end = mklabel(c, "endloop");
|
|
if (n->lhs) cgstmt(c, n->lhs, locals, frame);
|
|
label(c, loop);
|
|
if (n->cond) {
|
|
cgexpr(c, n->cond, *locals);
|
|
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
|
|
ins1(c, A_JE, abranch(end));
|
|
}
|
|
if (nloops < LOOP_MAX) {
|
|
loop_cont[nloops] = loop;
|
|
loop_brk[nloops] = end;
|
|
nloops++;
|
|
}
|
|
cgstmt(c, n->body, locals, frame);
|
|
if (nloops > 0) nloops--;
|
|
if (n->rhs) cgexpr(c, n->rhs, *locals);
|
|
ins1(c, A_JMP, abranch(loop));
|
|
label(c, end);
|
|
break;
|
|
}
|
|
case N_MLET: {
|
|
/* eval rhs; first result in AX, second in DX. Bind both. */
|
|
cgexpr(c, n->rhs, *locals);
|
|
ins1(c, A_PUSHQ, areg(D_DX)); /* save 2nd while we store 1st */
|
|
Node *l0 = n->list;
|
|
Node *l1 = l0 ? l0->next : NULL;
|
|
if (l0) {
|
|
int off = localoff(c, locals, l0->str, 8, frame);
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off));
|
|
}
|
|
ins1(c, A_POPQ, areg(D_DX));
|
|
if (l1) {
|
|
int off = localoff(c, locals, l1->str, 8, frame);
|
|
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off));
|
|
}
|
|
break;
|
|
}
|
|
case N_MASSIGN: {
|
|
cgexpr(c, n->rhs, *locals);
|
|
ins1(c, A_PUSHQ, areg(D_DX));
|
|
Node *l0 = n->list;
|
|
Node *l1 = l0 ? l0->next : NULL;
|
|
if (l0 && l0->kind == N_IDENT) {
|
|
int off = localfind(*locals, l0->str);
|
|
if (off != 0)
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off));
|
|
}
|
|
ins1(c, A_POPQ, areg(D_DX));
|
|
if (l1 && l1->kind == N_IDENT) {
|
|
int off = localfind(*locals, l1->str);
|
|
if (off != 0)
|
|
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off));
|
|
}
|
|
break;
|
|
}
|
|
case N_DEFER:
|
|
if (ndefers < DEFER_MAX) {
|
|
defers[ndefers++] = n->lhs;
|
|
}
|
|
break;
|
|
case N_BREAK:
|
|
if (nloops > 0)
|
|
ins1(c, A_JMP, abranch(loop_brk[nloops - 1]));
|
|
break;
|
|
case N_CONTINUE:
|
|
if (nloops > 0)
|
|
ins1(c, A_JMP, abranch(loop_cont[nloops - 1]));
|
|
break;
|
|
case N_SWITCH: {
|
|
/* Lower to a chain of compares. Scrutinee lands in a fresh
|
|
* local slot so case bodies can spill through SP without
|
|
* losing it. Cases are tried top-to-bottom; a `case:` arm
|
|
* with no exprs is the default and runs after all named
|
|
* arms fail. */
|
|
char *swname = aprintf(c->a, ".sw_%d", c->labelseq++);
|
|
int sloff = localoff(c, locals, swname, 8, frame);
|
|
cgexpr(c, n->lhs, *locals); /* AX = scrutinee */
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, sloff));
|
|
char *end = mklabel(c, "swend");
|
|
Node *defcase = NULL;
|
|
for (Node *cs = n->list; cs; cs = cs->next) {
|
|
if (cs->list == NULL) {
|
|
defcase = cs; /* save for last */
|
|
continue;
|
|
}
|
|
char *body = mklabel(c, "swcase");
|
|
char *next = mklabel(c, "swnext");
|
|
for (Node *e = cs->list; e; e = e->next) {
|
|
cgexpr(c, e, *locals); /* AX = case-expr */
|
|
ins2(c, A_MOVQ, amem(D_BP, sloff), areg(D_BX));
|
|
ins2(c, A_CMPQ, areg(D_BX), areg(D_AX));
|
|
ins1(c, A_JE, abranch(body));
|
|
}
|
|
ins1(c, A_JMP, abranch(next));
|
|
label(c, body);
|
|
cgstmt(c, cs->body, locals, frame);
|
|
ins1(c, A_JMP, abranch(end));
|
|
label(c, next);
|
|
}
|
|
if (defcase)
|
|
cgstmt(c, defcase->body, locals, frame);
|
|
label(c, end);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
cgfn(Cg *c, FILE *out, Node *fn)
|
|
{
|
|
if (fn->body == NULL) return; /* extern decl, no body */
|
|
|
|
/* fresh per-fn state */
|
|
c->head = c->tail = NULL;
|
|
c->fnname = fn->str;
|
|
c->labelseq = 0;
|
|
cg_stack_arg_cursor = 0;
|
|
ndefers = 0;
|
|
nloops = 0;
|
|
cg_ret_type = fn->type ? fn->type->ret : NULL;
|
|
|
|
int frame = 0;
|
|
Local *locals = NULL;
|
|
cg_frame = &frame;
|
|
|
|
/* TEXT directive comes first; framesize is filled at the end. */
|
|
Prog *text = newprog(c, A_TEXT);
|
|
/* Mangle the label for non-exported, non-FFI decls; mod_mangle
|
|
* does the FFI/module lookup in one step. */
|
|
text->to = masym(c, fn->str);
|
|
text->from.offset = 0; /* framesize patched below */
|
|
emit(c, text);
|
|
|
|
/* prologue */
|
|
ins1(c, A_PUSHQ, areg(D_BP));
|
|
ins2(c, A_MOVQ, areg(D_SP), areg(D_BP));
|
|
Prog *subsp = newprog(c, A_SUBQ);
|
|
subsp->from = aimm(0);
|
|
subsp->to = areg(D_SP);
|
|
emit(c, subsp);
|
|
|
|
/* spill incoming arg registers to local slots. Slice params
|
|
* occupy 24 bytes; float params land in XMM0..7 (counted
|
|
* separately from integer DI/SI/DX/CX/R8/R9). */
|
|
int argi = 0, fargi = 0;
|
|
Tparam *tp = fn->type ? fn->type->params : NULL;
|
|
for (Node *p = fn->list; p; p = p->next) {
|
|
if (p->str == NULL || strcmp(p->str, "...") == 0) {
|
|
if (tp) tp = tp->next;
|
|
continue;
|
|
}
|
|
Type *pt = tp ? tp->type : NULL;
|
|
Type *pu = (pt && pt->kind == TY_NAMED) ? pt->under : pt;
|
|
int slice = (pu && pu->kind == TY_SLICE);
|
|
int is_str = type_isstr(pt);
|
|
int is_struct = pu && pu->kind == TY_STRUCT && pu->size <= 16;
|
|
int tagged_sz = tagged_arg_size(pt);
|
|
int is_tagged = tagged_sz > 0;
|
|
int isf = cg_isfloat(pt);
|
|
|
|
/* Args overflowing register classes live at positive offsets
|
|
* from BP (16 + i*8). We register them as Locals at those
|
|
* offsets, no spill needed. */
|
|
int struct_eb = is_struct ? ((pu->size > 8) ? 2 : 1) : 0;
|
|
int tagged_eb = is_tagged ? (tagged_sz / 8) : 0;
|
|
int eightbytes = slice ? 3 :
|
|
(is_str ? 2 :
|
|
(is_struct ? struct_eb :
|
|
(is_tagged ? tagged_eb : 1)));
|
|
int regs_left = isf ? (8 - fargi) : (6 - argi);
|
|
if (regs_left >= eightbytes) {
|
|
int sz = slice ? 24 : (is_str ? 16 :
|
|
(is_struct ? (int)pu->size :
|
|
(is_tagged ? tagged_sz : 8)));
|
|
int off = localoff(c, &locals, p->str, sz, &frame);
|
|
if (slice || is_str || is_struct || is_tagged) {
|
|
for (int k = 0; k < eightbytes; k++, argi++)
|
|
ins2(c, A_MOVQ,
|
|
areg(sysv_argregs[argi]),
|
|
amem(D_BP, off + k * 8));
|
|
} else if (isf) {
|
|
int mov = type_isf32(pt) ? A_MOVSS : A_MOVSD;
|
|
ins2(c, mov,
|
|
areg(sysv_fargregs[fargi]),
|
|
amem(D_BP, off));
|
|
fargi++;
|
|
} else {
|
|
ins2(c, A_MOVQ,
|
|
areg(sysv_argregs[argi]),
|
|
amem(D_BP, off));
|
|
argi++;
|
|
}
|
|
} else {
|
|
/* stack-spilled. Access in place via positive BP offset. */
|
|
static int stack_arg_off;
|
|
(void)stack_arg_off;
|
|
Local *l = amalloc(c->a, sizeof *l);
|
|
l->name = p->str;
|
|
/* spilled args layout: each takes 8B (ptr/len/etc); we
|
|
* only support the simple case of plain int/float here. */
|
|
extern int cg_stack_arg_cursor;
|
|
l->off = 16 + cg_stack_arg_cursor * 8;
|
|
cg_stack_arg_cursor += eightbytes;
|
|
l->next = locals;
|
|
locals = l;
|
|
}
|
|
if (tp) tp = tp->next;
|
|
}
|
|
|
|
cgstmt(c, fn->body, &locals, &frame);
|
|
|
|
/* implicit return for void functions */
|
|
if (c->tail->as != A_RET) {
|
|
for (int di = ndefers - 1; di >= 0; di--)
|
|
cgexpr(c, defers[di], locals);
|
|
ins2(c, A_MOVQ, aimm(0), areg(D_AX));
|
|
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
|
ins1(c, A_POPQ, areg(D_BP));
|
|
ins0(c, A_RET);
|
|
}
|
|
|
|
/* round frame to 16; patch SUBQ */
|
|
if (frame & 15) frame = (frame + 15) & ~15;
|
|
subsp->from.offset = frame;
|
|
text->from.offset = frame;
|
|
|
|
txt_emit(out, c->head);
|
|
}
|
|
|
|
/* Emit DATA directives for top-level `def` constants whose value is
|
|
* an integer/rune literal. The w6a side stores the bytes inside .text
|
|
* and accesses are RIP-relative.
|
|
*/
|
|
static void
|
|
emit_defs(Cg *c, FILE *out, Node *file)
|
|
{
|
|
for (Node *d = file->list; d; d = d->next) {
|
|
if (d->kind != N_DEF || d->rhs == NULL) continue;
|
|
u64 v = 0;
|
|
if (d->rhs->kind == N_INTLIT || d->rhs->kind == N_RUNELIT) {
|
|
v = d->rhs->uval;
|
|
} else if (d->rhs->kind == N_TRUE) {
|
|
v = 1;
|
|
} else {
|
|
continue; /* skip non-integer-literal defs */
|
|
}
|
|
fprintf(out, "DATA %s(SB),\"", mod_mangle(c, d->str));
|
|
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);
|
|
}
|
|
(void)c;
|
|
}
|
|
|
|
/* Collect str-typed `def`s so cgexpr N_IDENT can splice them inline.
|
|
* Walks past any leading cast on the rhs (e.g. `def x: error = "x": error;`
|
|
* shows up as N_CAST wrapping an N_STRLIT). */
|
|
static void
|
|
sdef_collect(Cg *c, Node *file)
|
|
{
|
|
(void)c;
|
|
sdefs = NULL;
|
|
for (Node *d = file->list; d; d = d->next) {
|
|
if (d->kind != N_DEF || d->rhs == NULL) continue;
|
|
Node *r = d->rhs;
|
|
while (r && r->kind == N_CAST) r = r->lhs;
|
|
if (r == NULL || r->kind != N_STRLIT) continue;
|
|
Sdef *s = amalloc(c->a, sizeof *s);
|
|
s->name = d->str;
|
|
s->bytes = r->str;
|
|
s->len = r->strlen;
|
|
s->next = sdefs;
|
|
sdefs = s;
|
|
}
|
|
}
|
|
|
|
void
|
|
cg_file(Cg *c, FILE *out, Node *file)
|
|
{
|
|
if (file == NULL || file->kind != N_FILE) return;
|
|
ffi_collect(c, file);
|
|
mod_collect(c, file);
|
|
sdef_collect(c, file);
|
|
strlits = NULL;
|
|
strlit_seq = 0;
|
|
for (Node *d = file->list; d; d = d->next) {
|
|
if (d->kind != N_FNDECL) continue;
|
|
cgfn(c, out, d);
|
|
}
|
|
emit_data(c, out);
|
|
emit_defs(c, out, file);
|
|
}
|
|
|
|
void peephole(Cg *c) { (void)c; }
|
|
void regalloc_init(Cg *c) { (void)c; }
|