diff --git a/bend2/comp.ts b/bend2/comp.ts index e64e16c065..00987bb8ee 100644 --- a/bend2/comp.ts +++ b/bend2/comp.ts @@ -51,47 +51,7 @@ type HTerm = Bend.HTerm; type Name = Bend.Name; -type File = { - book: Bend.Book; - js: boolean; - bangs: Set; - sites: Map; - hot: Set; - stat: Set; - own: Set; - lend: Set; - segs: Seg[]; - spins: Seg[]; - spun: Map; - clos: Set; - tabs: Map; - tails: Map>; - img: string[]; - lits: Map; - consts: Map>; - ids: Map; - taken: Set; - teles: Map; - steps: Map; - opens: Map; - srcs: Map | null>; - fids: Set; - effs: Map; - loops: Map; - flats: Map; - funs: Map; - brws: Map; - nodes: Map; - lays: Map; - holds: Map; - lay_ids: Map; - memo: { - opens: Map | Of<"Let">, Open>; - uses: Map>; - folds: Map; - spines: Map; - }; -}; +type File = ReturnType; type Scope = { seg: Seg; @@ -685,12 +645,10 @@ function term_strip(t: HTerm): HTerm { } function term_open(t: Of<"Lam"> | Of<"Let">): Open { - return memo(FL.memo.opens, t, open_of); -} - -function open_of(t: Of<"Lam"> | Of<"Let">): Open { - const ps = (t.$ === "Lam" ? [t.k] : t.k).map(probe); - return { ps, b: t.$ === "Lam" ? t.f(ps[0]) : t.f(ps) }; + return memo(FL.memo.opens, t, () => { + const ps = (t.$ === "Lam" ? [t.k] : t.k).map(probe); + return { ps, b: t.$ === "Lam" ? t.f(ps[0]) : t.f(ps) }; + }); } function let_open(ps: Of<"Var">[], vs: HTerm[], b: HTerm): Of<"Let"> { @@ -707,38 +665,36 @@ function let_live(t: Of<"Let">): boolean[] { } function term_spine(tm: HTerm): Spine { - return memo(FL.memo.spines, tm, spine_of); -} - -function spine_of(tm: HTerm): Spine { - const apps: Of<"App">[] = []; - let h = tm; - let c = term_force(tm); - while (c.$ === "Ann" || c.$ === "App") { - if (c.$ === "App") { - apps.unshift(c); - h = c.f; - } - c = term_force(c.$ === "App" ? c.f : c.x); - } - const tld = c.$ === "Ref" ? FL.book.tlds[c.k] : undefined; - const T = tld?.$ === "Def" ? tld.T : ty_ann(h); - const qs = T === null || apps.length === 0 ? [] : tele_unbind(T).doms; - const live = apps.map((_, i) => i >= qs.length || dom_live(qs[i])); - const all = apps.map((a) => a.x); - const args = all.filter((_, i) => live[i]); - const def = c.$ === "Ref" && intr_of(c.k) === undefined - && fun_runs(tld) ? c.k : null; - const need = def === null ? 0 : fun_of(def).lays.length; - const a = apps[live.lastIndexOf(true)]; - const m = { h, t: c, all, args, tld, k: null, xs: args }; - if (def !== null && args.length === need) { - return { ...m, k: def, b: (c as Of<"Ref">).b }; - } - if (args.length > need && (def !== null || c.$ !== "Ref")) { - return { ...m, k: CLO_APPLY, xs: [a.f, a.x] }; - } - return m; + return memo(FL.memo.spines, tm, () => { + const apps: Of<"App">[] = []; + let h = tm; + let c = term_force(tm); + while (c.$ === "Ann" || c.$ === "App") { + if (c.$ === "App") { + apps.unshift(c); + h = c.f; + } + c = term_force(c.$ === "App" ? c.f : c.x); + } + const tld = c.$ === "Ref" ? FL.book.tlds[c.k] : undefined; + const T = tld?.$ === "Def" ? tld.T : ty_ann(h); + const qs = T === null || apps.length === 0 ? [] : tele_unbind(T).doms; + const live = apps.map((_, i) => i >= qs.length || dom_live(qs[i])); + const all = apps.map((a) => a.x); + const args = all.filter((_, i) => live[i]); + const def = c.$ === "Ref" && !intr_of(c.k) + && fun_runs(tld) ? c.k : null; + const need = def === null ? 0 : fun_of(def).lays.length; + const a = apps[live.lastIndexOf(true)]; + const m = { h, t: c, all, args, tld, k: null, xs: args }; + if (def !== null && args.length === need) { + return { ...m, k: def, b: (c as Of<"Ref">).b }; + } + if (args.length > need && (def !== null || c.$ !== "Ref")) { + return { ...m, k: CLO_APPLY, xs: [a.f, a.x] }; + } + return m; + }); } function term_eta(t: HTerm, T: HTerm, n: number): HTerm { @@ -812,16 +768,14 @@ function term_use(u: Bend.PMap, p: Of<"Var">): number { const ADD = (a: number, b: number) => a + b; function term_uses(tm: HTerm): Bend.PMap { - return memo(FL.memo.uses, tm, uses_of); -} - -function uses_of(tm: HTerm): Bend.PMap { - const t = term_force(tm); - let u = t.$ !== "Var" || t.i === DUMMY.i ? USE0 : Bend.pmap_set(USE0, t.i, 1); - for (const x of term_kids(t)) { - u = Bend.pmap_union(u, term_uses(x), t.$ === "Mat" ? Math.max : ADD); - } - return u; + return memo(FL.memo.uses, tm, () => { + const t = term_force(tm); + let u = t.$ !== "Var" || t.i === DUMMY.i ? USE0 : Bend.pmap_set(USE0, t.i, 1); + for (const x of term_kids(t)) { + u = Bend.pmap_union(u, term_uses(x), t.$ === "Mat" ? Math.max : ADD); + } + return u; + }); } function rest_use(rest: HTerm[], p: Of<"Var">): number { @@ -955,7 +909,7 @@ function ty_srcs(T: HTerm): void { } else if (t?.$ === "Lam") { ty_srcs(t.f(DUMMY)); } else if (t?.$ === "ADT") { - if (WORDS[t.k] === undefined && t.k !== "Array") { + if (!WORDS[t.k] && t.k !== "Array") { src_add(t.k); } t.x.forEach(ty_srcs); @@ -1424,53 +1378,53 @@ export function io_run(book: Bend.Book, args: string[]): number { // passed on from a lent root (k~i(), + sites: new Map(), + hot: new Set(), + stat: new Set(), + own: new Set(), + lend: new Set(), + segs: [] as Seg[], + spins: [] as Seg[], + spun: new Map(), + clos: new Set(), + tabs: new Map(), + tails: new Map>(), + img: [] as string[], + lits: new Map(), + consts: new Map>(), + ids: new Map(), taken: new Set("FID_EXIT FID_ENTER FID_T CID_T $pc".split(" ")), - teles: new Map(), - steps: new Map(), - opens: new Map(), - srcs: new Map(), - fids: new Set(), - effs: new Map(), - loops: new Map(), - flats: new Map(), - funs: new Map(), - brws: new Map(), - nodes: new Map(), - lays: new Map(), - holds: new Map(), - lay_ids: new Map(), + teles: new Map(), + steps: new Map(), + opens: new Map(), + srcs: new Map | null>(), + fids: new Set(), + effs: new Map(), + loops: new Map(), + flats: new Map(), + funs: new Map(), + brws: new Map(), + nodes: new Map(), + lays: new Map(), + holds: new Map(), + lay_ids: new Map(), memo: { - opens: new Map(), - uses: new Map(), - folds: new Map(), - spines: new Map(), + opens: new Map | Of<"Let">, Open>(), + uses: new Map>(), + folds: new Map(), + spines: new Map(), }, }; } // A scope is where the emitter writes; a nested arm, closure or fork copies -// it. The book's facts and output live in FL. +// it. function scope_new(): Scope { return { seg: seg_new("", BOX, []), @@ -1520,7 +1474,7 @@ function file_book(roots: Name[]): void { if (s.b) { FL.bangs.add(s.k); } - if (intr_of(s.k) === undefined) { + if (!intr_of(s.k)) { refs.add(s.k); FL.sites.set(s.k, (FL.sites.get(s.k) ?? 0) + 1); } @@ -2120,7 +2074,7 @@ function anf(t: HTerm, ty: HTerm | null = null): HTerm { } case "Lam": { const all = T && Bend.tele_open(FL.book, T); - return all === null || all.q.$ !== "None" ? s + return all?.q.$ !== "None" ? s : Bend.Ann(go(s.f(DUMMY), top, all.B(DUMMY)), all.B(DUMMY)); } default: { @@ -2165,8 +2119,8 @@ function emit_hold(sc: Scope, exprs: string[], k: string, ks?: Kind[]): string[] { return exprs.map((ex, i) => { const al = name_local(sc, k); - const ty = FL.js ? "const" : lay_c(ks?.[i] ?? "w64"); - file_push(sc, `${ty} ${al} = ${ex};`); + file_push(sc, + `${FL.js ? "const" : lay_c(ks?.[i] ?? "w64")} ${al} = ${ex};`); return al; }); } @@ -2311,8 +2265,8 @@ function emit_native(sc: Scope, k: Name, ers: HTerm[], f: Fun): string { const [sl, vals] = emit_open(sc, k, f); const seg = sl.seg; seg.fid = name; - const dst = val_new(seg.ret.ks.map((_, j) => `o[${j}]`), seg.ret); - emit_body(sl, f.h!, FL.book.tlds[k].T, ers, vals, dst); + emit_body(sl, f.h!, FL.book.tlds[k].T, ers, vals, + val_new(seg.ret.ks.map((_, j) => `o[${j}]`), seg.ret)); FUEL = fuel; const body = [...seg_text(["u32 wpoll = 0;", ...seg.ks.flatMap((k, i) => arr_q(k) ? [`Term h${i} = r${i};`] : []), @@ -2382,9 +2336,9 @@ function emit_ctr(sc: Scope, x: Of<"Ctr">, ty: HTerm | null, const flds = ctr_flds(x.k, x.x); const ctr = FL.book.ctrs[x.k]; const word = WORDS[adt.k]; - if (word !== undefined || adt.k === "Array") { + if (word || adt.k === "Array") { const vs = emit_each(sc, flds, [], ctr_doms(ctr, adt.x)); - if (word === undefined) { + if (!word) { return val_new([x.k === "ALeaf" ? arr_new(sc, "0", vs[0], lay_el(adt.x[0])) : `blk_node(e, ${val_own(sc, vs[0])[0]}, ${val_own(sc, vs[1])[0]})`], @@ -2407,7 +2361,7 @@ function emit_ctr(sc: Scope, x: Of<"Ctr">, ty: HTerm | null, const pos = at ?? lay_of(adt); const seen = memo(FL.consts, pos, () => new Map()); const got = seen.get(x); - if (got !== undefined) { + if (got) { return got; } const lay = pos === BOX ? lay_node(x.k) : pos; @@ -2432,7 +2386,7 @@ function emit_fold(t: HTerm): HTerm | null { return s; } const m = term_spine(s); - if (m.t.$ !== "Ref" || intr_of(m.t.k) === undefined) { + if (m.t.$ !== "Ref" || !intr_of(m.t.k)) { const b = emit_unfold(m); if (b === null) { return null; @@ -2532,7 +2486,7 @@ function emit_expr(sc: Scope, tm: HTerm, ty0: HTerm | null, } const g = m.t as Of<"Ref">; const intr = intr_of(g.k); - if (intr !== undefined) { + if (intr) { return emit_intr(sc, intr, m, ty); } if (m.tld?.$ === "ADT") { @@ -2689,7 +2643,7 @@ function emit_fork(sc: Scope, x: Of<"Let">, ers: HTerm[]): void { (pos.set(p, depth), depth += b.val.ws.length, b.val.ws)); emit_chain(sc, [["seq", () => { const fr = [...ws, seg_ref(sc, seg_fid(kn))]; - file_push(sc, `WL_ROOM(${fr.length});`); + file_push(sc, `WL_ROOM(${fr.length}, ${depth - ws.length});`); fr.forEach((w, j) => file_push(sc, `STK(${j}) = ${w};`)); file_push(sc, `WL_PUSHN(${fr.length});`); }], ...fork ? [] : [["", () => { @@ -2720,7 +2674,7 @@ function emit_fork(sc: Scope, x: Of<"Let">, ers: HTerm[]): void { function emit_row(sc: Scope, t: HTerm, ty: HTerm | null): string | null { const k = ty_adt(ty)?.k ?? ""; - if (ty !== null && WORDS[k] === undefined) { + if (ty !== null && !WORDS[k]) { return null; } let s = term_strip(t); @@ -2748,7 +2702,7 @@ function emit_row(sc: Scope, t: HTerm, ty: HTerm | null): string | null { function emit_tab(sc: Scope, cells: HTerm[] | null, ty: HTerm, s: string): string | null { const ls = cells?.map((t) => emit_row(sc, t, ty)); - if (ls === undefined || ls.includes(null)) { + if (!ls || ls.includes(null)) { return null; } const key = (FL.js ? ls : Function("f32_bits", `return [${ls}]`)( @@ -2842,7 +2796,7 @@ function emit_chain(sc: Scope, arms: [string, () => void][]): void { const ID_WORDS = /\/\/.*|\/\*[^]*?\*\/|"(?:\\[^]|[^"\\\n])*"|'(?:\\[^]|[^'\\\n])*'|`(?:\\[^]|[^`\\])*`|(? void): string { - const replace = (t: string, p: string | undefined, k: string | undefined, at: number): string => { + return src.replace(ID_WORDS, (t, p, k, at) => { // '#' starts a private JS name, but C's ## may paste an ID macro. if (!p || !k || (FL.js && src[at - 1] === "#")) { return t; @@ -2856,12 +2810,7 @@ function c_ids(src: string, m = "", use?: (p: string, k: Name) => void): string if (q === undefined) die(`${p}(${k}) names no constructor or def`); return FL.js ? JSON.stringify(p === "CID" && q in FL.book.ctrs ? js_tag(q) : q) : name_id(p + "_", q); - }; - if (use) { - for (const hit of src.matchAll(ID_WORDS)) replace(hit[0], hit[1], hit[2], hit.index); - return src; - } - return src.replace(ID_WORDS, replace); + }); } function effect_src(k: Name, tld: Bend.Def & { i: string[] }): void { @@ -2879,8 +2828,7 @@ function effect_src(k: Name, tld: Bend.Def & { i: string[] }): void { const src = FL.js ? raw : raw.replace(/\\[ \t]*\r?\n/g, ""); FL.effs.set(path, [m, src]); if (!FL.js) c_ids(src, m, (p, q) => { - const d = FL.book.tlds[q]; - if (p === "FID" && fun_runs(d)) { + if (p === "FID" && fun_runs(FL.book.tlds[q])) { src_add(q); FL.fids.add(q); } @@ -2898,8 +2846,7 @@ function effect_src(k: Name, tld: Bend.Def & { i: string[] }): void { export function compile_book(book: Bend.Book): string { FL = file_new(book, false); - // a pure main's descriptor names constructors of the types it prints, - // so their datatypes are roots too + // the datatypes of the constructors a pure main prints are roots too const show = show_main(); const names = (show ?? []).filter((c) => typeof c === "string"); file_book(["main", ...RUNTIME_ADTS, @@ -3055,7 +3002,7 @@ function js_call(sc: Scope, k: Name, args: HTerm[], tail = false): string { return tail ? `run_tail(${f}, ${x})` : `${f}(${x})`; } const intr = intr_of(k, true); - if (intr !== undefined) { + if (intr) { return tpl(intr.JS!, exprs.map((e) => ATOM.test(e) || STRLIT.test(e) ? e : emit_hold(sc, [e], "x")[0])); } @@ -3078,9 +3025,8 @@ function js_open(sc: Scope, x: Of<"Let">): HTerm { : js_var(sc, emit_hold(sc, [js_expr(sc, v)], x.k[j])[0]))); } -// Constructor tags name the constructor within its declaring file, not -// the namespace a loading book gives that file. Matches stay type-local; -// foreign effect identities and public def names keep their namespaces. +// A constructor's tag drops its namespace, as matches stay type-local; +// effect ids and public def names keep theirs. function js_tag(k: Name): string { return k.slice(k.indexOf(":") + 1); } @@ -3125,7 +3071,7 @@ function js_expr(sc: Scope, tm: HTerm, ty0: HTerm | null = null): string { } const exprs = ctr_flds(x.k, x.x).map((f) => js_expr(sc, f)); const native = OPTIMIZED[adt.k]; - if (native !== undefined) { + if (native) { return tpl(native[x.k].intr, exprs); } const fs = ctr_live(FL.book.ctrs[x.k]); @@ -3225,7 +3171,7 @@ function js_match(sc: Scope, x: HTerm, ty: HTerm | null, args: string[]): void { const native = OPTIMIZED[adt.k]; const is = (c: Name) => `${s}.$ === "${js_tag(c)}"`; lv = mat_ctrs(x, adt).map(([k, h]) => - k === "" || k === "_" ? [k && mat_ops(is), h, [s]] : native === undefined + k === "" || k === "_" ? [k && mat_ops(is), h, [s]] : !native ? [is(k), h, ctr_live(FL.book.ctrs[k]).map(([, f]) => `${s}["${f}"]`)] : [tpl(native[k].cond ?? "", [s]), h, (native[k].elim ?? []).map((e) => tpl(e, [s]))]); @@ -3235,7 +3181,7 @@ function js_match(sc: Scope, x: HTerm, ty: HTerm | null, args: string[]): void { } function js_def(sc: Scope, k: Name, def: Bend.Def): void { - if (intr_of(k, true) !== undefined) { + if (intr_of(k, true)) { return; } FUEL = FOLD_FUEL; @@ -3294,7 +3240,7 @@ export function js_lib(book: Bend.Book, mod = false): string { FL = file_new(book, true); const outs = !mod ? null : [...new Set(FL.book.order)].filter((k) => { const t = FL.book.tlds[k]; - return done_live(t) && t.b !== true && t.x === 0 + return done_live(t) && !t.b && t.x === 0 && io_base(FL.book, t.T) === null; }); file_book(outs ?? ["main"]); @@ -3447,9 +3393,8 @@ using namespace metal; #endif #define FAR static __attribute__((noinline)) -// The device inlines every spin into its kernel once per caller, so a long -// spin is a call there; short ones gain from inlining. The host lets clang -// decide. +// The device inlines a spin once per caller, so a long one is a call there. +// The host lets clang decide. #if DEVICE #define DFAR FAR #define LOCK(l) @@ -3491,8 +3436,9 @@ using namespace metal; STK(wi) = e.mem[(A) + wi]; \ } \ WL_PUSHN(N); -#define WL_ROOM(N) \ - if (DEVICE && sp + (N) * CUBE >= e.mem + STAT_OFF + CUBE) { \ +#define WL_ROOM(N, D) \ + if (DEVICE && sp + (N) * CUBE >= e.mem + STAT_OFF + CUBE \ + && !(sp = lane_spill(e, sp, N, D))) { \ err_post(e.mem, ERR_DEEP); \ return 0; \ } @@ -4470,6 +4416,27 @@ static const WlFn wl_tab[] = { WL_TABLE }; FAR Term work_loop(Env e, DEV Term* sp, Term t, u32 seq); #endif +// A full lane moves the words below its def's to the heap, under an exit +// frame that moves them back (#1393). +FAR DEV Term* lane_spill(Env e, DEV Term* sp, u32 n, u32 d) { + DEV Term* lo = e.alc + (STAK_OFF - ALC_OFF); + u64 k = (sp - lo) / CUBE - d; + if (d + n + 3 > STAK_LEN) { + return 0; + } + u64 b = heap_alloc(e, cls_fit(STAK_LEN)); + for (u64 i = 0; i < k + d; i += 1) { + e.mem[b + i] = lo[i * CUBE]; + } + sp = lo; + STK(0) = term_tsk(FID_EXIT, b); + STK(1) = k; + STK(2) = FID_EXIT; + WL_PUSHN(3); + WL_ARGS(b + k, d) + return sp; +} + static Term work_loop(Env e, DEV Term* sp, Term t, u32 seq) { WL_BANK u32 rn = 0; @@ -4545,6 +4512,12 @@ ${segs} Term cont = STK(0); u32 idx = (u32)STK(1); u32 wf = (u32)term_aux(cont); + if (DEVICE && wf == FID_EXIT) { + WL_ARGS(term_loc(cont), idx) + heap_free(e, cls_fit(STAK_LEN), term_loc(cont)); + WL_TAKE(rv) + WL_RETN(n); + } if (cont != TERM_HOLE && fid_resw(wf)) { u64 wa = term_loc(cont); u32 wn = fid_arity(wf); @@ -4996,7 +4969,7 @@ static void gpu_note(const char* path) { static void gpu_kernel(u32 pass, u32 at); -// Dispatch at runs pass {0, 0, 1, 3, 2}[at] on TURN_ROWS(at) groups. +// Dispatch at runs on TURN_ROWS(at) groups. static void gpu_run(void) { for (u32 i = 0; i < 5; i += 1) { gpu_kernel((u32[]){ 0, 0, 1, 3, 2 }[i], i); diff --git a/tests/compile/bang_lane_spill.bend b/tests/compile/bang_lane_spill.bend new file mode 100644 index 0000000000..97b4c58af5 --- /dev/null +++ b/tests/compile/bang_lane_spill.bend @@ -0,0 +1,70 @@ +# A bang's root and a fork kid run on a lane's STAK_LEN-word stack on the +# device. A call that finds it full moves the words below its def's own to a +# heap block under an exit frame, whose return moves them back, so non-tail +# recursion under ! goes as deep on the device as on the host (#1393, #1394). +# Before, 1023 List.append frames faulted, and so did a 365-fork spine beside +# a 3-fork one. walk's first spill comes at its second fork call, where the +# def still holds two words on the stack, and they must come back with it. +import Base + +def gen(n: Nat, +k: U32, acc: List<&2, U32>) -> List<&2, U32>: + match n: + case 0n: + acc + case 1n+p: + gen(p, (k + 1 : U32), k <> acc) + +def mix(xs: List<&2, U32>, +acc: U32) -> U32: + match xs: + case Nil{}: + acc + case Con{h, t}: + mix(t, ((acc * 31 : U32) + h : U32)) + +def deep(+n: Nat) -> U32: + mix(List.append(&2, U32, gen(n, 0, []), [7]), 0) + +def spine(n: Nat, +i: U32) -> U32: + match n: + case 0n: + 0 + case 1n+p: + a b = spine(p, (i + 1 : U32)) mix(List.append(&2, U32, gen(800n, i, []), gen(64n, i, [])), i) + (a + b : U32) + +type Q is Data: + Q{a: U32, b: U32, c: U32, d: U32, e: U32} + +def leaf(+k: U32) -> Q: + Q{k, (k * 3 : U32), (k * 5 : U32), (k * 7 : U32), (k + 9 : U32)} + +def sum(q: Q) -> U32: + match q: + case Q{a, b, c, d, e}: + (((((a + b : U32) * 3 : U32) + c : U32) + ((d + e : U32) * 5 : U32) : U32) * 7 : U32) + +def walk(n: Nat, +k: U32) -> U32: + match n: + case 0n: + k + case 1n+p: + q b = leaf(k) walk(p, (k + 1 : U32)) + (((sum(q) + b : U32) * 31 : U32) + k : U32) + +def two(+n: Nat) -> U32: + a b = spine(n, 0) spine(3n, 0) + (a + b : U32) + +def pair(+n: Nat) -> U32: + a b = walk(n, 1) walk(3n, 0) + (a + b : U32) + +def main() -> IO(Unit): + do IO: + IO.print(U32.show(deep!(5000n))) + IO.print(U32.show(two!(365n))) + IO.print(U32.show(pair!(600n))) + +#|1543298507 +#|2063974529 +#|1878402805