examples: lisp — drive wwstage by default; retire stale workarounds
Makefile sets WW_W6C=$(BIN)/w6c_ww so `make`, `make test`, and `make demo` all use the ww-built backend. With the wwstage cgen fixes in selfhost/cmd/wcc/ the demo no longer needs to dodge: - bug #1+#2 (global addressing): symbol interner indexes sym_off / sym_len / sym_blob directly. No `let blob = sym_blob;` aliasing. - bug #3 (chained non-pointer sub-struct field): not retired here (lexer.cur is still flattened) but the cgen now handles the shape; un-flattening is cosmetic. - bug #4 (f64 through every boundary): vfloat writes p.fval = v directly; promote_v's FLOAT branch is one assign; to_f64 reads v.ival as f64 / v.fval directly. Drops fbuf, FVAL_OFF, copybytes. - bug #7 (xs[i].field): builtins write xs[0].kind / xs[0].car directly; no `let p = xs[0];` first. lisp_test still 101/101. CLAUDE.md marks each historical bug as retired or still load- bearing; #6 (slice-len in tagged-union return) and #8 (f64 compound assign) are the remaining shapes to avoid.
This commit is contained in:
@@ -263,22 +263,10 @@ export fn vint(v: i64) *value = {
|
||||
return p;
|
||||
};
|
||||
|
||||
// f64 → struct-field via a *T pointer is mis-lowered (stores AX instead
|
||||
// of MOVSD from X0). MOVSD into a top-level f64 global works, so route
|
||||
// the bits through a scratch global and copy them as bytes.
|
||||
def FVAL_OFF: u64 = 16u64;
|
||||
let fbuf: f64 = 0.0;
|
||||
|
||||
fn vfloat(v: f64) *value = {
|
||||
fbuf = v;
|
||||
let p: *value = arena_alloc(VALUE_SZ): *value;
|
||||
p.kind = valkind.FLOAT;
|
||||
let raw: *u8 = p: *u8;
|
||||
let dst: *u8 = raw + FVAL_OFF;
|
||||
let srcp: *f64 = &fbuf;
|
||||
let src: *u8 = srcp: *u8;
|
||||
let i: u64 = 0u64;
|
||||
for (i < 8u64) { dst[i] = src[i]; i += 1u64; };
|
||||
p.fval = v;
|
||||
return p;
|
||||
};
|
||||
|
||||
@@ -371,29 +359,20 @@ let SID_LET: i32 = 0;
|
||||
let SID_BEGIN: i32 = 0;
|
||||
let SID_SETBANG: i32 = 0;
|
||||
|
||||
// streqid / intern / symname all alias the global *T pointers into
|
||||
// locals before indexing. The wwstage cgen mis-lowers `global[i]` when
|
||||
// `global` is a pointer-typed top-level let: it reads BP+0 instead of
|
||||
// `LEAQ global(SB)`. Through a local the address path is correct.
|
||||
|
||||
fn streqid(a: str, off: i32, len: i32) bool = {
|
||||
if (a.len != len) { return false; };
|
||||
let blob: *u8 = sym_blob;
|
||||
let i: i32 = 0;
|
||||
for (i < len) {
|
||||
if (a[i] != blob[off + i]) { return false; };
|
||||
if (a[i] != sym_blob[off + i]) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
export fn intern(s: str) i32 = {
|
||||
let offt: *i32 = sym_off;
|
||||
let lent: *i32 = sym_len;
|
||||
let blob: *u8 = sym_blob;
|
||||
let i: i32 = 0;
|
||||
for (i < sym_count) {
|
||||
if (streqid(s, offt[i], lent[i])) { return i; };
|
||||
if (streqid(s, sym_off[i], sym_len[i])) { return i; };
|
||||
i += 1;
|
||||
};
|
||||
os.assert(sym_count < SYM_CAP, "sym table full");
|
||||
@@ -401,11 +380,11 @@ export fn intern(s: str) i32 = {
|
||||
let off: i32 = sym_blobuse;
|
||||
let j: i32 = 0;
|
||||
for (j < s.len) {
|
||||
blob[off + j] = s[j];
|
||||
sym_blob[off + j] = s[j];
|
||||
j += 1;
|
||||
};
|
||||
offt[sym_count] = off;
|
||||
lent[sym_count] = s.len;
|
||||
sym_off[sym_count] = off;
|
||||
sym_len[sym_count] = s.len;
|
||||
let id: i32 = sym_count;
|
||||
sym_count += 1;
|
||||
sym_blobuse += s.len;
|
||||
@@ -413,11 +392,9 @@ export fn intern(s: str) i32 = {
|
||||
};
|
||||
|
||||
fn symname(id: i32) str = {
|
||||
let offt: *i32 = sym_off;
|
||||
let lent: *i32 = sym_len;
|
||||
let r: str;
|
||||
r.ptr = sym_blob + offt[id];
|
||||
r.len = lent[id];
|
||||
r.ptr = sym_blob + sym_off[id];
|
||||
r.len = sym_len[id];
|
||||
return r;
|
||||
};
|
||||
|
||||
@@ -883,15 +860,7 @@ fn promote_v(v: *value) *value = {
|
||||
n.car = p.car;
|
||||
n.cdr = p.cdr;
|
||||
n.envp = p.envp;
|
||||
if (p.kind == valkind.FLOAT) {
|
||||
// f64 byte copy — `n.fval = p.fval` through *value lowers
|
||||
// to an integer-reg store at the f64 offset, same trap
|
||||
// vfloat sidesteps via the `fbuf` scratch global.
|
||||
let psrc: *u8 = (p: *u8) + FVAL_OFF;
|
||||
let ndst: *u8 = (n: *u8) + FVAL_OFF;
|
||||
let i: u64 = 0u64;
|
||||
for (i < 8u64) { ndst[i] = psrc[i]; i += 1u64; };
|
||||
};
|
||||
if (p.kind == valkind.FLOAT) { n.fval = p.fval; };
|
||||
if (p.kind == valkind.STR) {
|
||||
// Deep-copy the bytes into perm. vstr put them in trans, and
|
||||
// the trans reset that ends every top-level form would drop
|
||||
@@ -983,35 +952,9 @@ fn any_float(xs: []*value) bool = {
|
||||
// X0 — so the caller sees garbage. An out-pointer sidesteps the
|
||||
// boundary entirely.
|
||||
|
||||
// f64 movement through the wwstage cgen is fragile: only MOVSD into a
|
||||
// top-level global emits the right MOVSD. Field-of-pointer stores,
|
||||
// struct-literal stores, and function-arg passing for f64 all run
|
||||
// through integer registers and lose the value. We treat every
|
||||
// "f64 stored at address A" as a byte-copy from a known-good source.
|
||||
//
|
||||
// copybytes(d, s) — pure 8-byte memcpy.
|
||||
// to_f64 — INT branch routes through `fbuf` (global f64 store works
|
||||
// for the CVTSI2SD-produced X0), then byte-copies; FLOAT
|
||||
// branch is a direct bit-copy from `v.fval`.
|
||||
|
||||
fn copybytes(d: *u8, s: *u8) void = {
|
||||
let i: u64 = 0u64;
|
||||
for (i < 8u64) { d[i] = s[i]; i += 1u64; };
|
||||
};
|
||||
|
||||
fn to_f64(v: *value, out: *f64) bool = {
|
||||
let dst: *u8 = out: *u8;
|
||||
if (v.kind == valkind.INT) {
|
||||
fbuf = v.ival: f64;
|
||||
let sp: *f64 = &fbuf;
|
||||
copybytes(dst, sp: *u8);
|
||||
return true;
|
||||
};
|
||||
if (v.kind == valkind.FLOAT) {
|
||||
let raw: *u8 = v: *u8;
|
||||
copybytes(dst, raw + FVAL_OFF);
|
||||
return true;
|
||||
};
|
||||
if (v.kind == valkind.INT) { *out = v.ival: f64; return true; };
|
||||
if (v.kind == valkind.FLOAT) { *out = v.fval; return true; };
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -1149,22 +1092,16 @@ fn b_cons(xs: []*value) (*value | rterror) = {
|
||||
return vcons(xs[0], xs[1]);
|
||||
};
|
||||
|
||||
// Field access on a slice element (xs[i].kind) is mis-lowered by the
|
||||
// wwstage cgen: it emits only the slice index, drops the trailing
|
||||
// field load. Workaround: bind xs[i] to a local *value first.
|
||||
|
||||
fn b_car(xs: []*value) (*value | rterror) = {
|
||||
if (xs.len != 1) { return "car: need 1 arg": rterror; };
|
||||
let p: *value = xs[0];
|
||||
if (p.kind != valkind.CONS) { return "car: not a pair": rterror; };
|
||||
return p.car;
|
||||
if (xs[0].kind != valkind.CONS) { return "car: not a pair": rterror; };
|
||||
return xs[0].car;
|
||||
};
|
||||
|
||||
fn b_cdr(xs: []*value) (*value | rterror) = {
|
||||
if (xs.len != 1) { return "cdr: need 1 arg": rterror; };
|
||||
let p: *value = xs[0];
|
||||
if (p.kind != valkind.CONS) { return "cdr: not a pair": rterror; };
|
||||
return p.cdr;
|
||||
if (xs[0].kind != valkind.CONS) { return "cdr: not a pair": rterror; };
|
||||
return xs[0].cdr;
|
||||
};
|
||||
|
||||
fn b_list(xs: []*value) (*value | rterror) = {
|
||||
@@ -1179,47 +1116,40 @@ fn b_list(xs: []*value) (*value | rterror) = {
|
||||
|
||||
fn b_nullp(xs: []*value) (*value | rterror) = {
|
||||
if (xs.len != 1) { return "null?: need 1 arg": rterror; };
|
||||
let p: *value = xs[0];
|
||||
return vbool(p.kind == valkind.NIL);
|
||||
return vbool(xs[0].kind == valkind.NIL);
|
||||
};
|
||||
|
||||
fn b_pairp(xs: []*value) (*value | rterror) = {
|
||||
if (xs.len != 1) { return "pair?: need 1 arg": rterror; };
|
||||
let p: *value = xs[0];
|
||||
return vbool(p.kind == valkind.CONS);
|
||||
return vbool(xs[0].kind == valkind.CONS);
|
||||
};
|
||||
|
||||
fn b_nump(xs: []*value) (*value | rterror) = {
|
||||
if (xs.len != 1) { return "number?: need 1 arg": rterror; };
|
||||
let p: *value = xs[0];
|
||||
let k: valkind = p.kind;
|
||||
let k: valkind = xs[0].kind;
|
||||
return vbool(k == valkind.INT || k == valkind.FLOAT);
|
||||
};
|
||||
|
||||
fn b_symp(xs: []*value) (*value | rterror) = {
|
||||
if (xs.len != 1) { return "symbol?: need 1 arg": rterror; };
|
||||
let p: *value = xs[0];
|
||||
return vbool(p.kind == valkind.SYM);
|
||||
return vbool(xs[0].kind == valkind.SYM);
|
||||
};
|
||||
|
||||
fn b_eqp(xs: []*value) (*value | rterror) = {
|
||||
if (xs.len != 2) { return "eq?: need 2 args": rterror; };
|
||||
let a: *value = xs[0];
|
||||
let b: *value = xs[1];
|
||||
if (a.kind != b.kind) { return vbool(false); };
|
||||
if (a.kind == valkind.NIL) { return vbool(true); };
|
||||
if (a.kind == valkind.BOOL) { return vbool(a.bval == b.bval); };
|
||||
if (a.kind == valkind.INT) { return vbool(a.ival == b.ival); };
|
||||
if (a.kind == valkind.SYM) { return vbool(a.sid == b.sid); };
|
||||
if (xs[0].kind != xs[1].kind) { return vbool(false); };
|
||||
if (xs[0].kind == valkind.NIL) { return vbool(true); };
|
||||
if (xs[0].kind == valkind.BOOL) { return vbool(xs[0].bval == xs[1].bval); };
|
||||
if (xs[0].kind == valkind.INT) { return vbool(xs[0].ival == xs[1].ival); };
|
||||
if (xs[0].kind == valkind.SYM) { return vbool(xs[0].sid == xs[1].sid); };
|
||||
// Reference equality for everything else — matches eq? semantics
|
||||
// in classic Lisps.
|
||||
return vbool(a == b);
|
||||
return vbool(xs[0] == xs[1]);
|
||||
};
|
||||
|
||||
fn b_not(xs: []*value) (*value | rterror) = {
|
||||
if (xs.len != 1) { return "not: need 1 arg": rterror; };
|
||||
let p: *value = xs[0];
|
||||
return vbool(!truthy(p));
|
||||
return vbool(!truthy(xs[0]));
|
||||
};
|
||||
|
||||
fn b_print(xs: []*value) (*value | rterror) = {
|
||||
|
||||
Reference in New Issue
Block a user