diff options
| author | 2026-10-14 10:05:40 +0000 | |
|---|---|---|
| committer | 2026-10-14 10:05:40 +0000 | |
| commit | ce920b6ff47f32bc06dd27124e1b882f82f3d6ae (patch) | |
| tree | 89f3dac0aa670294053320599a742d670c0ec5a4 /src/compiler | |
| parent | todo: tick the parity runner; zc0 follow-ups it found (diff) | |
split compiler.zc into files by phase
src/compiler/compiler.zc is now twelve files under src/compiler/, listed in
order in src/compiler/parts. cat of the parts in order is byte-identical to
the old file, so every compiler and image except the language server's is
unchanged. no function was edited.
consumers read the list: the makefile, bootstrap-check, genmap (which still
writes one compiler.zc into a generated map directory), tinyvm.bat,
test.sh, test_win.ps1 and the lsp drivers. the language server embeds each
part and serves them joined as the one document tinyvm:/compiler.zc.
Diffstat (limited to 'src/compiler')
| -rw-r--r-- | src/compiler/analyzer.zc | 1236 | ||||
| -rw-r--r-- | src/compiler/base.zc | 1384 | ||||
| -rw-r--r-- | src/compiler/builtins.zc | 1423 | ||||
| -rw-r--r-- | src/compiler/check.zc | 115 | ||||
| -rw-r--r-- | src/compiler/compiler.zc | 10458 | ||||
| -rw-r--r-- | src/compiler/decl.zc | 1413 | ||||
| -rw-r--r-- | src/compiler/expr.zc | 1912 | ||||
| -rw-r--r-- | src/compiler/image.zc | 95 | ||||
| -rw-r--r-- | src/compiler/lex.zc | 652 | ||||
| -rw-r--r-- | src/compiler/ops.zc | 218 | ||||
| -rw-r--r-- | src/compiler/parts | 12 | ||||
| -rw-r--r-- | src/compiler/setup.zc | 191 | ||||
| -rw-r--r-- | src/compiler/stmt.zc | 816 | ||||
| -rw-r--r-- | src/compiler/types.zc | 1003 |
14 files changed, 10470 insertions, 10458 deletions
diff --git a/src/compiler/analyzer.zc b/src/compiler/analyzer.zc new file mode 100644 index 0000000..505ac09 --- /dev/null +++ b/src/compiler/analyzer.zc @@ -0,0 +1,1236 @@ +// ---- function effects and temporary borrows ---- + +word effect_record(int symbol) { + if (symbol < 0) return 0; + if (!effect_symbols) effect_symbols = chk_resize(0, 0, symcap); + if (!effect_symbols[symbol]) effect_symbols[symbol] = chk_resize(0, 0, 50); + return effect_symbols[symbol]; +} +// trusted runtime contracts apply only to built-ins, never a user function with the same name. +int effect_contract(int symbol, int parameter) { + word n; + if (symbol < 0 || symbol >= ubase) return EF_UNKNOWN; + n = sname[symbol]; + if (n == s_put_s || rt.eq_w(n, "rt.put_bs") || rt.eq_w(n, "rt.len_w") || rt.eq_w(n, "rt.len_s") || rt.eq_w(n, "rt.eq_w") || rt.eq_w(n, "rt.eq_s") || rt.eq_w(n, "rt.at_s") || rt.eq_w(n, "rt.put_n") || rt.eq_w(n, "rt.put_c")) return EF_READ; + if (n == s_free || n == s_recycle) return EF_CONSUME; + if (rt.eq_w(n, "rt.copy_w")) { if (!parameter) return EF_WRITE; return EF_READ; } + if (rt.eq_w(n, "rt.fill_w")) { if (!parameter) return EF_WRITE; return EF_READ; } + return EF_UNKNOWN; +} +int effect_get(int symbol, int parameter) { + word r; + if (parameter < 0 || parameter >= 24) return EF_UNKNOWN; + if (symbol < 0 || symbol >= nsym) return EF_UNKNOWN; + if (symbol < ubase) return effect_contract(symbol, parameter); + r = effect_record(symbol); + if (r[0] != 1 || (sflag[symbol] & F_EXT)) { + if (parameter < 12 && (((ssig[symbol] >> 6) >> (parameter + parameter)) & 3) == 1) return EF_READ | EF_WRITE | EF_ALIAS; + return EF_UNKNOWN; + } + return r[2 + parameter]; +} +int effect_origin(int c) { + int i = xbor; + if (c >= C_VAR) i = c - C_VAR; + if (i < lbase || i >= nloc) return -1; + if (local_origin && local_origin[i] >= 0) return local_origin[i]; + if (lrskind[i] == RS_PARAM && i - lbase < 24) return i - lbase; + return -1; +} +word effect_mask(int c) { + int i; + if (c >= C_VAR) { + i = c - C_VAR; + if (local_origin_mask && local_origin_mask[i]) return local_origin_mask[i]; + if (i >= lbase && lrskind[i] == RS_PARAM && i - lbase < 24) return 1 << (i - lbase); + } + return effect_value; +} +int effect_note_mask(word mask, int flags) { + int i = 0; + while (mask && i < 24) { if (mask & 1) effect_note(i, flags); mask = mask >> 1; i++; } + return 0; +} +int effect_args_mask(int callee, int parameter, word mask) { + int i = 0; + while (mask && i < 24) { if (mask & 1) effect_arg(callee, parameter, i); mask = mask >> 1; i++; } + return 0; +} +int effect_note(int origin, int flags) { + if (effect_current && origin >= 0 && origin < 24) { effect_current[2 + origin] = effect_current[2 + origin] | flags; effect_current[26 + origin] = effect_current[26 + origin] | flags; } + return 0; +} +int effect_arg(int callee, int parameter, int origin) { + word e; + if (!effect_current || origin < 0) return 0; + e = talloc(6); e[0] = effect_edges; e[1] = effect_current; e[2] = origin; e[3] = callee; e[4] = parameter; e[5] = 0; + effect_edges = e; return 0; +} +int effect_require_mode(int callee, int parameter, int place, int mode) { + word e; + if (!fchk || !live || dry) return 0; + e = talloc(7); e[6] = mode; + e[5] = effect_floor; + e[0] = effect_requirements; e[1] = callee; e[2] = parameter; e[3] = place; e[4] = curinc; + effect_requirements = e; return 0; +} +int effect_require(int callee, int parameter, int place) { return effect_require_mode(callee, parameter, place, 0); } +// argument checks for a generic call use the instantiated body's effects. +int effect_specialize(int original, int instance, word requirements, word edges) { + word e = effect_requirements; + while (e && e != requirements) { if (e[1] == original) e[1] = instance; e = e[0]; } + e = effect_edges; + while (e && e != edges) { if (e[3] == original) e[3] = instance; e = e[0]; } + return 0; +} +int effect_finish(int from) { + word e, r, next; int changed = 1, flags, prior, save = tpack(), inc = curinc, i = 0; + if (chkabort) return 0; + // direct body effects are immutable inputs; recompute callee effects so an earlier nested unit cannot leave stale unknowns. + while (effect_symbols && i < nsym) { r = effect_symbols[i]; if (r) rt.copy_w(r + 2, r + 26, 24); i++; } + while (changed) { + changed = 0; e = effect_edges; + while (e) { + r = e[1]; flags = effect_get(e[3], e[4]) & ~(EF_ALIAS | EF_IDENTITY); prior = r[2 + e[2]]; + r[2 + e[2]] = prior | flags; if (prior != r[2 + e[2]]) changed = 1; + e = e[0]; + } + } + e = effect_requirements; effect_requirements = 0; + while (e) { + next = e[0]; + if (e[5] < from) { e[0] = effect_requirements; effect_requirements = e; e = next; continue; } + flags = effect_get(e[1], e[2]); + if ((!e[6] && (flags & (EF_RETAIN | EF_CONSUME | EF_UNKNOWN))) || (e[6] && !(flags & EF_UNKNOWN) && (flags & (EF_RETAIN | EF_CONSUME)))) { + tunpack(e[3]); curinc = e[4]; + if (e[6]) serr("this callee retains or consumes a borrowed owner; declare its parameter .d to transfer ownership"); + else if (flags & EF_UNKNOWN) serr("a temporary argument requires a proved non-escaping borrow; bind it to a name first or provide an explicit parameter contract"); + else serr("a temporary argument requires a proved non-escaping borrow; this callee retains or consumes it, so transfer ownership through a .d parameter"); + panic = 0; + } + e = next; + } + tunpack(save); curinc = inc; return 0; +} +int borrow_begin() { return 0; } +int borrow_prologue() { borrow_jump = hole(); op(O_JMP); borrow_entry = here; return 0; } +int borrow_initializer() { + word p = borrow_slots; + if (borrow_jump < 0) return 0; + if (!p) { setlit(borrow_jump, borrow_entry); return 0; } + setlit(borrow_jump, here); + while (p) { lit(0); laddr(p[1]); op(O_ST); p = p[0]; } + jmp(borrow_entry); return 0; +} +int emit_borrow_hold() { + word p; + if (!infn) { cerr("temporary borrow outside a function"); return 0; } + p = talloc(2); p[0] = borrow_slots; p[1] = fslots; fslots++; borrow_slots = p; + lit(0); op(O_PICK); laddr(p[1]); op(O_ST); borrow_pending++; return 0; +} +// release only temporaries introduced by this full expression. zero slots cover untaken branches. +int borrow_cleanup_since(word stop) { + word p = borrow_slots; int skip; + while (p && p != stop) { + laddr(p[1]); op(O_LD); skip = hole(); op(O_JZ); + laddr(p[1]); op(O_LD); callname(s_recycle); op(O_DROP); + lit(0); laddr(p[1]); op(O_ST); setlit(skip, here); p = p[0]; + } + return 0; +} +int borrow_cleanup() { return borrow_cleanup_since(0); } +int borrow_condition() { + word before = borrow_slots; int c = expr(); rval(); plainuse(c); ftest(); borrow_cleanup_since(before); return c; +} + +// ---- locals and the analyzer's view of them ---- + +// value tracking is for check mode only; it never changes emitted code. +int vf_symbols() { + if (!svkind) { svkind = chk_resize(0, 0, symcap); svvalue = chk_resize(0, 0, symcap); } + return 0; +} +int vf_at(int r, int kind, word value) { + if (!cmode || dry || chkind != 3 || chbase != rsbase || r < 0 || r >= rsn) return 0; + rsrec[r * RS_W + 9] = kind; rsrec[r * RS_W + 10] = value; return 0; +} +char* vf_string(char* text) { + word p; int n; + if (!cmode || dry) return 0; + n = rt.len_w(text) + 1; p = talloc(n); rt.copy_w(p, text, n); return p; +} +int vf_forget() { + if (!cmode || dry) return 0; + if (lvkind) rt.fill_w(lvkind + lbase, 0, nloc - lbase); + if (svkind) rt.fill_w(svkind, 0, nsym); + xvk = 0; return 0; +} +// a call or indirect write can change globals, escaped locals, and string data. +int vf_effect() { + int i = lbase; + if (!cmode || dry) return 0; + while (i < nloc) { if (lvescaped[i] || lvkind[i] >= 3) lvkind[i] = 0; i++; } + if (svkind) rt.fill_w(svkind, 0, nsym); + xvk = 0; return 0; +} +word vf_i2f(word v) { return asm { v @ #1216 }; } +word vf_f2i(word v) { return asm { v @ #1218 }; } +word vf_negf(word v) { return asm { 0.0 v @ #1204 }; } +int vf_coerce(word t, int convert) { + if (!xvk || !t) return 0; + if (xvk >= 3) { if (t != tword && t != tbstr && t[0] != TY_PTR) xvk = 0; return 0; } + if (isfl(t)) { + if (xvk == 1) { if (convert) xvv = vf_i2f(xvv); xvk = 2; } + if (t == tf32 && convert) xvv = rt.zs2f(rt.zf2s(xvv)); + } else { + if (xvk == 2 && convert && isnum(t)) xvv = vf_f2i(xvv); + xvk = 1; xvv = cnorm(t, xvv); + } + return 0; +} +int vf_store(int i) { + if (!cmode || dry || !live) return 0; + lvkind[i] = xvk; lvvalue[i] = xvv; return 0; +} + +// these warnings add no ownership rules. facts are discarded at control-flow +// joins and after unknown effects, so a possible failure is never called definite. +word af_new(int length, int kind, int place) { + word f; + if (dry || stubgen) return 0; + f = talloc(15); rt.fill_w(f, 0, 15); f[0] = length; f[1] = 1; f[2] = kind; + f[3] = place; f[4] = 0; f[5] = -1; + return f; +} +// owner-relative projections retain provenance and the array generation at the time of borrowing. +word af_base(word f) { while (f && f[6]) f = f[6]; return f; } +word af_project(word f, int offset, int extent, int place, int elements) { + word p, base = af_base(f); int start = offset; + if (!f || !base) return 0; + if (f[6] && f[7] >= 0 && offset >= 0) start = f[7] + offset; + p = af_new(extent, base[2], base[3]); + if (!p) return 0; + p[1] = base[1]; p[4] = base[4]; p[5] = base[5]; p[6] = base; p[7] = start; + p[8] = elements; p[9] = base[10]; p[12] = place; + if (base[2] == 3) p[2] = 2; + if (elements) { + p[0] = -1; + if (offset >= 0 && f[0] >= 0) { p[0] = f[0] - offset; p[13] = f[13] - offset; p[14] = 1; } + } + return p; +} +int af_mutate(word f, int place) { + word base = af_base(f); + if (!base || dry || !live) return 0; + af_mutation_tick++; base[10] = af_mutation_tick; base[11] = place; base[0] = -1; return 0; +} +int af_projection_access(word f, int place) { + word base; int saved; + if (!f || !f[6] || !f[8]) return 0; + base = af_base(f); + if (base[10] < 0 || base[10] == f[9] || dry || !live) return 0; + saved = tpack(); tunpack(place); + if (fchk) { if (panic) { tunpack(saved); return 0; } errat(); } + else if (!af_warning(place)) { tunpack(saved); return 0; } + rt.put_s("element borrow was invalidated by array mutation on line "); rt.put_n(base[11] & 0xFFFFFF); + rt.put_s("; borrow created on line "); rt.put_n(f[12] & 0xFFFFFF); + if (fchk) errend(); else af_warning_end(); tunpack(saved); return 0; +} + +int af_forget() { + vf_forget(); + int i = lbase; + while (i < nloc) { lafact[i] = 0; i++; } + xaf = 0; return 0; +} +// an unknown callee may change a passed allocation's length or lifetime. +int af_escape(word f) { + if (f) { word base = af_base(f); base[0] = -1; base[1] = 0; base[10] = -1; f[0] = -1; f[1] = 0; } + return 0; +} +int af_store(int i) { + if (!dry) lafact[i] = xaf; + return 0; +} +int af_expired(word f) { + word base; + if (!f) return 0; + if (f[1] == 2) return 1; + if (!f[6] || !f[8]) return 0; + base = af_base(f); return base[10] >= 0 && base[10] != f[9]; +} +int af_snapshot(word f) { + if (f && f[6] && af_expired(f)) { f[1] = 2; f[4] = af_base(f)[11]; } + return 0; +} +int af_record(int off, word f) { + int at; + if (!f || !cmode || dry || chkind != 3 || chbase != rsbase || !rsn) return 0; + af_snapshot(f); + at = (rsn - 1) * RS_W; + if (rsrec[at] != off) return 0; + rt.copy_w(rsrec + at + 4, f, 5); return 0; +} +// keep declaration previews as well as the facts at each use site. +int af_declaration(int off, word f) { + int i = rsn; + if (!f || !cmode || dry || chkind != 3 || chbase != rsbase) return 0; + while (i) { i--; if (rsrec[i * RS_W] == off) { rt.copy_w(rsrec + i * RS_W + 4, f, 5); return 0; } } + return 0; +} +int af_warning(int place) { + int sv; + if (dry || panic || chkabort || stubgen || !live) return 0; + sv = tpack(); tunpack(place); + if (cmode) dg_open(2); + else { rt.put_s("zc: line "); rt.put_n(tline); rt.put_s(": warning: "); } + tunpack(sv); return 1; +} +int af_warning_end() { if (cmode) dg_close(); else rt.put_c(10); return 0; } +int af_access(word f, int place) { + af_projection_access(f, place); if (f && f[6]) { word base = af_base(f); f[1] = base[1]; f[4] = base[4]; } + if (!f || f[1] != 2 || !af_warning(place)) return 0; + rt.put_s("use after free: this storage's lifetime ended on line "); + rt.put_n(f[4] & 0xFFFFFF); af_warning_end(); return 0; +} +int af_use(word f, int place, int c) { + if (fchk && c >= C_VAR && lstate[lroot(c - C_VAR)] == S_DEAD) return 0; + return af_access(f, place); +} +int af_index(word f, int place) { + int lower = 0; + if (f && f[6]) { word base = af_base(f); if (base[1] == 0 || base[10] < 0) return 0; if (f[14]) lower = f[13]; } + if (!f || f[1] == 2 || f[0] < 0 || !xconst || (xcval >= lower && xcval < f[0])) return 0; + if (!af_warning(place)) return 0; + rt.put_s("out-of-bounds access: index "); rt.put_n(xcval); + rt.put_s(" is outside length "); rt.put_n(f[0]); rt.put_s(" (storage declared or allocated on line "); rt.put_n(f[3] & 0xFFFFFF); rt.put_c(')'); af_warning_end(); return 0; +} +// freeing an alias ends the same allocation as freeing its original name. +int af_end(word f, int place) { + if (f && !dry && live) { f[1] = 2; f[4] = place; } + return 0; +} +int af_close(int save) { + int i = save; word f; + if (dry) return 0; + while (i < nloc) { + f = laaddr[i]; if (f && f[2] == 1) af_end(f, tpack()); + f = lafact[i]; + if (f && fchk && lstate[i] == S_M && lown[i] && !local_captured[i]) af_end(f, tpack()); + i++; + } + return 0; +} +// the analyzer's model of a local. locals are numbered in declaration order +// and every fact about one lives in a parallel array indexed by that number +// (see the data region). for ownership the ones that matter are: +// lstate what the local holds right now: +// S_PLAIN nothing tracked (a number, or untracked memory) +// S_M owns an object; the compiler frees it when the block ends +// S_U owns one that has to leave the block (return it, make +// it the block's value, or assign it to the enclosing block) +// S_D was handed in to be given away, and hasn't been yet +// S_DEAD the object is gone (given away); any use is an error +// S_CHILD the object belongs to a container, lowner says which local +// S_BORROW a .m parameter: usable but not freeable or storable +// lhome the block depth that owns the object (fdepth where it was declared) +// lown 1 once the local has owned an object, lused the line of its last use +// ltag the tag written on the declaration, plus the flag bits (float, +// byte string, o3 register, hint bits) +// fchk says the function being compiled is analyzed at all (unchecked turns +// it off) and live says the code just emitted is reachable. ownership rules +// return when fchk is off; definite storage warnings also inspect unchecked code. +// findloc looks a name up among the current function's locals, innermost first. +// name heads and shadow links keep declaration and use lookup independent of +// the number of earlier locals. dropping a scope restores the previous heads. +int local_bucket(int n, int cap) { return ((n ^ (n >> 16)) * 0x9E37) & (cap - 1); } +int local_key(int n) { + int k = local_bucket(n, lkeycap); + while (lkeys[k] && lkeys[k] != n) k = (k + 1) & (lkeycap - 1); + return k; +} +int local_bind(int n, int i) { + int k, j, at, cap; word keys, heads; + if (lkeyn + lkeyn >= lkeycap) { + keys = lkeys; heads = lheads; cap = lkeycap * 2; + lkeys = chk_resize(0, 0, cap); lheads = chk_resize(0, 0, cap); + j = 0; + while (j < lkeycap) { + if (keys[j]) { at = local_bucket(keys[j], cap); while (lkeys[at]) at = (at + 1) & (cap - 1); lkeys[at] = keys[j]; lheads[at] = heads[j]; } + j++; + } + lkeycap = cap; + } + k = local_key(n); + if (!lkeys[k]) { lkeys[k] = n; lheads[k] = -1; lkeyn++; } + lprev[i] = lheads[k]; lheads[k] = i; return 0; +} +int local_drop(int save) { + int i = nloc, k; + while (i > save) { i--; k = local_key(lname[i]); lheads[k] = lprev[i]; } + nloc = save; return 0; +} +int findloc(int n) { + int k = local_key(n), i; + if (!lkeys[k]) return -1; + i = lheads[k]; + while (i >= nloc) i = lprev[i]; + if (i >= lbase) return i; + return -1; +} +// declare a local: take the next slot in the frame (fslots) and reset every +// per-local fact so a reused index never inherits the old variable's state +int newloc(int n) { + int i = nloc; + if (nloc >= loccap) reserve_locals(nloc + 1); + nloc++; + lname[i] = n; local_bind(n, i); lslot[i] = fslots; lhome[i] = fdepth; lstate[i] = S_PLAIN; lsmark(0, n); + if (!local_origin) local_origin = chk_resize(0, 0, loccap); local_origin[i] = -1; + if (if_local) if_local[i] = 0; + if (!local_origin_mask) local_origin_mask = chk_resize(0, 0, loccap); local_origin_mask[i] = 0; + if (!local_cell) local_cell = chk_resize(0, 0, loccap); local_cell[i] = 0; + if (!local_captured) local_captured = chk_resize(0, 0, loccap); local_captured[i] = 0; + if (!local_ended) local_ended = chk_resize(0, 0, loccap); local_ended[i] = 0; + if (!local_end_info) local_end_info = chk_resize(0, 0, loccap); local_end_info[i] = 0; + lpos[i] = 0; + lreadpos[i] = 0; + lowner[i] = 0; + lrsoff[i] = 0 - 1; lrskind[i] = RS_LOCAL; + lused[i] = 0; + larr[i] = 0; + lown[i] = 0; + ltype[i] = tword; + ltag[i] = 0; + o3cache[i] = 0; + lafact[i] = 0; laaddr[i] = 0; + if (cmode) { + if (!lvkind) { lvkind = chk_resize(0, 0, loccap); lvvalue = chk_resize(0, 0, loccap); lvescaped = chk_resize(0, 0, loccap); } + lvkind[i] = 0; lvvalue[i] = 0; lvescaped[i] = 0; + } + return i; +} +// a local's known lower bound, and the array whose length it holds. -1 is unknown. +// the table is dropped as scopes close, so a reused slot does not keep the old fact. +word getlo(int i) { + int k = 0; + while (k < nfact) { + if (floc[k] == i) return flo[k]; + k++; + } + return -1; +} +// the length of the recorded fact for line i, or -1 when there is none. +word getln(int i) { + int k = 0; + while (k < nfact) { + if (floc[k] == i) return flen[k]; + k++; + } + return -1; +} +// record the range lo to lo+ln for line i. +int putfact(int i, int lo, int ln) { + int k = 0; + while (k < nfact) { if (floc[k] == i) { flo[k] = lo; flen[k] = ln; return 0; } k++; } + if (lo < 0 && ln < 0) return 0; + if (nfact >= factcap) { + floc = chk_resize(floc, nfact, factcap * 2); flo = chk_resize(flo, nfact, factcap * 2); flen = chk_resize(flen, nfact, factcap * 2); + factcap = factcap * 2; + } + floc[nfact] = i; flo[nfact] = lo; flen[nfact] = ln; nfact++; + return 0; +} +// drop the recorded facts from line from onward. +int fdrop(int from) { + int k = 0, w = 0; + while (k < nfact) { + if (floc[k] < from) { + if (w != k) { floc[w] = floc[k]; flo[w] = flo[k]; flen[w] = flen[k]; } + w++; + } + k++; + } + nfact = w; + return 0; +} +// a loop fact stores three full local indices: the +// induction variable, its bound and the array the bound is the length of. a +// loop "for (i = 0; i < len(a); i++)" records (i, bound, a) in lpfact, which +// is how proven() knows that a[i] needs no bounds check inside it. +int pfact(int iv, int bd, int ar) { word p = talloc(3); p[0] = iv; p[1] = bd; p[2] = ar; return p; } +// the induction variable of a loop proof. +int p_iv(int w) { return w[0]; } +// the array's bound in a loop proof. +int p_bd(int w) { return w[1]; } +// the array a loop proof refers to. +int p_ar(int w) { return w[2]; } +// a store to the induction variable, the bound, or the array ends the proof +int bpoison(int i) { + int k = lsp, w; + while (k > loopbase) { + k--; + w = lpfact[k]; + if (w >= 0 && (p_iv(w) == i || p_bd(w) == i || p_ar(w) == i)) lpfact[k] = -1; + } + return 0; +} +// note a store to a local: a constant or a length it now holds. +int note_store(int i) { + if (if_local && !dry) { + if (!if_local[i]) if_local[i] = chk_resize(0, 0, 1); + if (if_value) if_link(if_local[i], if_value); + else if (xty == tword) *if_local[i] = -1; + if_join(if_local[i], xty); + } + int lo = -1, ln = -1; + if (xconst && xcval >= 0) lo = xcval; + if (xlenloc >= 0) ln = xlenloc; + putfact(i, lo, ln); + bpoison(i); + return 0; +} +// i >= 0 and i < len(a), proved by the loop test and a step of +1 +int proven(int a, int ix) { + int k = lsp, w; + if (ltype[a][0] != TY_DYN || !ltype[a][3] || tbytes(ltype[a][4])) return 0; + while (k > loopbase) { + k--; + w = lpfact[k]; + if (w >= 0 && p_iv(w) == ix && p_ar(w) == a) return 1; + } + return 0; +} +// follow child links up to the local that really owns the object +int lroot(int i) { + while (lstate[i] == S_CHILD) i = lowner[i]; + return i; +} +// lend a local owner to global s for the rest of the current block. +int lend_add(int s, int i) { + if (nlend * 3 + 3 > lendcap) { if (!lendcap) lendcap = 48; else lendcap = lendcap * 2; lendrec = chk_resize(lendrec, nlend * 3, lendcap); } + lendrec[nlend * 3] = s; lendrec[nlend * 3 + 1] = i; lendrec[nlend * 3 + 2] = fdepth; nlend++; + return 0; +} +// remove entry k, keeping the others in order. +int lend_remove(int k) { + if (k + 1 < nlend) rt.copy_w(lendrec + k * 3, lendrec + k * 3 + 3, (nlend - k - 1) * 3); + nlend--; + return 0; +} +// an assignment to global s in the block that lent it ends the lend. +int lend_clear(int s) { + int k = nlend; + while (k > 0) { k--; if (lendrec[k * 3] == s && lendrec[k * 3 + 2] == fdepth && lendrec[k * 3 + 1] >= lbase) lend_remove(k); } + return 0; +} +// report local i still lent to the global of entry k. +int lend_err(int k, char* msg) { + int i = lendrec[k * 3 + 1]; + if (!fchk || !live || panic || dry) return 0; + errat(); rt.put_s(lname[i]); rt.put_s(": is lent to global "); rt.put_s(sname[lendrec[k * 3]]); rt.put_s(msg); errend(); + return 0; +} +// local i is about to be given away or released: no global may still borrow it. +int lend_check(int i) { + int k = 0; + while (k < nlend) { + if (lendrec[k * 3 + 1] == i && i >= lbase) { lend_err(k, "; assign the global again before giving the local away"); lend_remove(k); } + else k++; + } + return 0; +} +// leaving every block at depth dep or deeper: a lend made there must have ended. +int lend_exit(int dep) { + int k = 0; + while (k < nlend) { + if (lendrec[k * 3 + 2] >= dep && lendrec[k * 3 + 1] >= lbase) lend_err(k, "; assign the global again before the block that lent it ends"); + k++; + } + return 0; +} +// forget the lends made at depth dep or deeper, or by locals from index from onward. +int lend_drop(int dep, int from) { + int k = 0; + while (k < nlend) { + if ((lendrec[k * 3 + 2] >= dep && lendrec[k * 3 + 1] >= lbase) || lendrec[k * 3 + 1] >= from) lend_remove(k); + else k++; + } + return 0; +} +// retire existing aliases together with their owner. dead aliases do not keep later loop snapshots live. +int end_owner(int i) { + int j = lbase; word info = talloc(3); + lend_check(i); + info[0] = lname[i]; info[1] = lpos[i]; info[2] = tpack(); + local_ended[i] = info[2]; local_end_info[i] = info; lstate[i] = S_DEAD; + while (j < nloc) { + if (j != i && isalias(j) && lroot(j) == i) { local_ended[j] = info[2]; local_end_info[j] = info; lstate[j] = S_DEAD; } + j++; + } + return 0; +} +// true when a local has an ownership state the analyzer follows. +word tracked(int i) { return lstate[i] != S_PLAIN; } +// a child link with nothing owned: a pointer computed from some other variable +word isalias(int i) { return lstate[i] == S_CHILD && !lown[i]; } +// record the last-use line and saturating reference count for diagnostics. +int luse(int i) { + lused[i] = tline; + if ((ltag[i] >> 56) < 100) ltag[i] = ltag[i] + LCNT1; + return 0; +} +// touch records a use of a tracked local and rejects dead objects. a d object +// read in an inner block moves there, where the block owns it as m. +int touch(int i) { + int r; + luse(i); + ltag[i] = ltag[i] | LREAD; + if (!fchk) return 0; + r = lroot(i); + luse(r); + if (lstate[r] == S_DEAD) { cerrn(i, "used after it was given away"); return 0; } + // a d object that shows up in a deeper block is given to that block + if (lstate[r] == S_D && lhome[r] < fdepth) { lhome[r] = fdepth; lstate[r] = S_M; lown[r] = 1; } + return 0; +} +// an expression of class c used as a plain value (a lend, an operand). a new +// object can't be used that way, since nothing would own it. +int plainuse(int c) { + if (c == C_FRESH) cerr("a new object can't be used as a plain value; bind it to a name first"); + if (c >= C_VAR) touch(c - C_VAR); + return C_PLAIN; +} +// the size class of an n word object, for a constant-size new +int szclass(int n) { + int k = 3; + while (rt.pow2[k] < n + rt.HDR) k++; + return k; +} +// freeing an owned local. one that holds a typed object with no pointer +// fields goes back through recycle_leaf, which knows its class +int freevar(int i) { + if (local_captured && local_captured[i]) { lit(0); laddr(lslot[i]); op(O_ST); return 0; } + vaddr(i); op(O_LD); + if (lleaf[i] && !o3on && !dry && lookup(s_recycle_leaf) >= 0) { + lit(lleaf[i]); + lit(szclass(lnsz[i])); + callname(s_recycle_leaf); + } + else callname(s_recycle); op(O_DROP); lit(0); laddr(lslot[i]); op(O_ST); return 0; +} +// leaving every block deeper than or equal to dep: free what is owned, and +// complain about what should have left already. final means the block really +// ends here rather than an early exit. +int exit_vars(int dep, int final) { + int i = nloc, st; + if (!fchk || !live) return 0; + lend_exit(dep); + while (i > lbase) { + i--; + if (lhome[i] < dep) continue; + st = lstate[i]; + if (st == S_M) { + if (!local_captured || !local_captured[i]) ev_put(i, EV_FREE, -1); freevar(i); + } else if (st == S_U) { + cerrn(i, "u object must leave this block (block value, assignment to the enclosing block, or return)"); + } + else if (st == S_D) cerrn(i, "d object was never given away"); + } + return 0; +} +// an m object has to be used somewhere in the block that owns it, on any path +// block ends. +int unused_check(int from) { + while (from < nloc) { + if (fchk && lown[from] && !lused[from] && !(lrskind[from] == RS_PARAM && rt.eq_w(lname[from], "_"))) serrn(lname[from], "owned object is never used"); + from++; + } + return 0; +} +// what a block's own variables show once it ends (check mode only) +int hint_name(int i, char* msg, int kind) { + if (!hint_begin(lpos[i], kind)) return 0; + rt.put_s(lname[i]); rt.put_s(msg); + dg_close(); + return 0; +} +// report the locals declared from token from onward that were never used. +int local_hints(int from) { + int i = from, f; + if (!cmode || dry || panic || chkabort || stubgen) return 0; + while (i < nloc) { + f = ltag[i]; + if (lrskind[i] == RS_PARAM && lname[i] != s_this && !rt.eq_w(lname[i], "_") && !(f & LREAD)) { + if (hint_begin(lpos[i], HINT_SMALL)) { + rt.put_s("unused parameter `"); rt.put_s(lname[i]); rt.put_s("`"); dg_close(); + } + } else if ((f & LDECL) && !larr[i] && !rt.eq_w(lname[i], "_")) { // _ discards its value + if (!(f & LREAD)) hint_name(i, " is never read", HINT_SMALL); + else if ((f & LCONST) && !(f & LASG) && !(ltype[i] && treadonly(ltype[i]))) hint_name(i, " is never reassigned; make it a const", HINT_ALL); + else if (fchk && lstate[i] == S_M && lown[i] && lnsz[i] > 0 && lnsz[i] <= 64) hint_name(i, " never leaves this function; a stack buffer (let name[n]) avoids the heap", HINT_SMALL); + else if (fchk && lstate[i] == S_M && lown[i] && lused[i] && tline - lused[i] >= 4) hint_name(i, " is last used several lines above the end of its block; close the block sooner so it is freed sooner", HINT_ALL); + } + i++; + } + return 0; +} +// block_close runs the analyzer's end-of-block work in order: hints for the +// block's variables, o3 registers go back to the pool, an m object that was +// never used is an error, exit_vars frees what is owned and complains about +// what should have left, aliases whose owner dies are marked dead, then the +// block's locals are dropped. unused_check comes first because an unused m +// object is an error, not a free. +int block_close(int save) { + int i = save, k; + af_close(save); + local_hints(save); + if (o3on) { + while (i < nloc) { + k = (ltag[i] >> 8) & 7; + if ((ltag[i] & 16) && k && no3free < 8) { + if (getflt(i)) k = 0 - k; + o3free[no3free] = k; + no3free++; + } + i++; + } + } + i = lbase; + unused_check(save); + exit_vars(fdepth, 1); + lend_drop(fdepth, save); + alias_end(fdepth, save); + while (i < save) { if (lhome[i] >= fdepth) { lstate[i] = S_DEAD; lhome[i] = fdepth - 1; } i++; } + local_drop(save); + fdrop(save); + td_end(fdepth); + return 0; +} + +// an alias in an outer block whose owner is about to disappear dies with it +int alias_end(int dep, int save) { + int i, r; + if (!fchk) return 0; + i = lbase; + while (i < save) { + if (isalias(i)) { + r = lroot(i); + if (r >= save || lhome[r] >= dep || lstate[r] == S_DEAD) lstate[i] = S_DEAD; + } + i++; + } + return 0; +} +// snapshots: [lbase] [nloc] then state, owner, home per local +// save the states of all current locals; returns the snapshot offset +int snap_push() { + int b = snaptop, i = lbase; word p; + af_forget(); + reserve_R_SNAP(b + 2 + 3 * (nloc - lbase)); + p = R_SNAP + b; + p[0] = lbase; p[1] = nloc; + p = p + 2; + while (i < nloc) { p[0] = lstate[i]; p[1] = lowner[i]; p[2] = lhome[i]; p = p + 3; i++; } + snaptop = p - R_SNAP; + return b; +} +// drop the snapshot at base b. +int snap_pop(int b) { snaptop = b; return 0; } +// restore the local states saved in snapshot b. +int snap_restore(int b) { + word p = R_SNAP + b; int i = p[0], e = p[1]; + af_forget(); + if (e > nloc) e = nloc; + p = p + 2; + while (i < e) { lstate[i] = p[0]; lowner[i] = p[1]; lhome[i] = p[2]; p = p + 3; i++; } + return 0; +} +// true for a plain or dead local state, which merge to dead. +int empty(int s) { return s == S_PLAIN || s == S_DEAD; } +// compare the current states with a snapshot: -1 if they agree, else the +// first local that differs. plain and dead agree and merge to dead. +int snap_cmp(int b) { + word p = R_SNAP + b; int i = p[0], e = p[1], s; + if (e > nloc) e = nloc; + p = p + 2; + while (i < e) { + s = lstate[i]; + if (empty(s) && empty(p[0])) { if (p[0] == S_DEAD) lstate[i] = S_DEAD; } + else if (s != p[0] || lhome[i] != p[2] || (s == S_CHILD && lowner[i] != p[1])) return i; + p = p + 3; i++; + } + return -1; +} +// the state names used in join and loop messages +int statename(int s) { + if (s == S_PLAIN) return "empty"; + if (s == S_M) return "m"; + if (s == S_U) return "u"; + if (s == S_D) return "d"; + if (s == S_DEAD) return "given away"; + if (s == S_CHILD) return "owned by a container"; + return "borrowed"; +} +// report that local i ends two paths in different states +int merge_err(int i, int sa, int what) { + if (!fchk || panic || dry) return 0; + errat(); rt.put_s(lname[i]); rt.put_s(": "); rt.put_s(what); + rt.put_s(" (one path leaves it "); + rt.put_s(statename(sa)); + rt.put_s(", another "); + rt.put_s(statename(lstate[i])); + rt.put_s(")"); + errend(); + return 0; +} + +// ---- snapshots and comparing paths ---- + +// to analyze an if, the compiler snapshots the locals' states before the +// branch, compiles the then path, restores the snapshot for the else path, and +// then compares or merges the two. a snapshot is [lbase, nloc] followed by +// state, owner and home for each local; the stack of them is at R_SNAP. +// ---- joins ---- + +// where control flow comes back together (after an if, a loop, a switch or +// ?:) every variable has to agree across the paths that reach the join. one +// exception: a variable that became d, or was given away, on some path is dead +// after the join, and the paths where it's still alive free it on the way in +// (fixups). u objects and borrows don't get this treatment: a u object still +// has to leave, and a borrow never changes. + +int extop; word[JOIN_PATHS] jps_initial; word jps = jps_initial; int jpscap = JOIN_PATHS, njps; // the paths being joined: [lbase, nloc, triples] +// accessors on a path record p (a snapshot or an exit record): the state, +// owner and home of local i on that path. locals declared after the snapshot +// read as plain. +word jst(word p, int i) { + if (i >= p[1]) return S_PLAIN; + return p[2 + 3 * (i - p[0])]; +} +// the owner recorded in a jump's state for local i. +word jow(word p, int i) { return p[3 + 3 * (i - p[0])]; } +// the jump's marker for local i, in the same record as jow. +word jhm(word p, int i) { return p[4 + 3 * (i - p[0])]; } +// the states at a break, with the jump to patch: [site] then a snapshot +word exit_rec(int site) { + word p; int i = lbase; word q; + reserve_R_EXIT(extop + 3 + 3 * (nloc - lbase)); p = R_EXIT + extop; + p[0] = site; p[1] = lbase; p[2] = nloc; + q = p + 3; + while (i < nloc) { q[0] = lstate[i]; q[1] = lowner[i]; q[2] = lhome[i]; q = q + 3; i++; } + extop = q - R_EXIT; + return p; +} +// jps is the set of paths about to be joined. jadd adds one, jsame compares +// what two paths say about a local, and join folds them all into the current +// states (reporting any local that disagrees) +int jadd(int p) { + if (njps >= jpscap) { jps = chk_resize(jps, njps, jpscap * 2); jpscap = jpscap * 2; } + jps[njps] = p; + njps++; + return 0; +} +// true when the states of two jumps agree for local i. +word jsame(int p, int q, int i) { + int s = jst(p, i), t = jst(q, i); + if (empty(s) && empty(t)) return 1; + return s == t && jhm(p, i) == jhm(q, i) && (s != S_CHILD || jow(p, i) == jow(q, i)); +} +// join the paths in jps over the locals [lbase, n0) into the current states +int join(int n0, int what) { + int i = lbase, j, p, s, s0, dead, okdead, bad; + af_forget(); + if (!njps) return 0; + while (i < n0) { + p = jps[0]; s0 = jst(p, i); + dead = 0; okdead = 1; bad = -1; + j = 0; + while (j < njps) { + s = jst(jps[j], i); + if (s == S_DEAD || s == S_D) dead = 1; + if (s == S_U || s == S_BORROW) okdead = 0; + if (!jsame(p, jps[j], i)) bad = j; + j++; + } + lstate[i] = s0; lowner[i] = jow(p, i); lhome[i] = jhm(p, i); + if (bad < 0) { + if (empty(s0)) { + j = 0; + while (j < njps) { + if (jst(jps[j], i) == S_DEAD) lstate[i] = S_DEAD; + j++; + } + } + } else if (dead && okdead) lstate[i] = S_DEAD; + else if (fchk && !panic && !dry) { + errat(); rt.put_s(lname[i]); rt.put_s(": "); rt.put_s(what); + rt.put_s(" (one path leaves it "); + rt.put_s(statename(s0)); + rt.put_s(", another "); + rt.put_s(statename(jst(jps[bad], i))); + rt.put_s(")"); + errend(); + } + i++; + } + return 0; +} +// on path p: count (and with doit, emit) the frees for what the join made dead but p still owns +int fixups(int p, int n0, int doit) { + int i = lbase, n = 0, s; + if (!fchk) return 0; + while (i < n0) { + s = jst(p, i); + if (lstate[i] == S_DEAD && (s == S_M || s == S_D)) { n++; if (doit) freevar(i); } + i++; + } + return n; +} +// a jump back to the top of a loop: whatever the loop owns has to be as it was +int backedge(int s0) { + word p = R_SNAP + s0; int i = p[0], e = p[1], s; + if (!fchk || !live) return 0; + if (e > nloc) e = nloc; + while (i < e) { + s = lstate[i]; + if (empty(s) && empty(jst(p, i))) { if (jst(p, i) == S_DEAD) lstate[i] = S_DEAD; } + else if (s != jst(p, i) || lhome[i] != jhm(p, i) || (s == S_CHILD && lowner[i] != jow(p, i))) { + if (s == S_DEAD || s == S_D) { + cerrn(i, "is given away inside a loop that comes back around to it; give it away on a path that leaves the loop"); + } + else if (!panic && !dry) { + errat(); + rt.put_s(lname[i]); + rt.put_s(": changes state inside a loop; each pass has to leave it as it found it (it went from "); + rt.put_s(statename(jst(p, i))); rt.put_s(" to "); rt.put_s(statename(s)); rt.put_s(")"); errend(); + } + } + i++; + } + return 0; +} +// joins two paths that end at the same point, like the branches of if/else: +// path a jumped (through the hole at ja) and path b falls through to here +// (the then path's snapshot is sa; livea / livebv say whether each path can +// reach the join at all, since a path that returned is not part of it) +int join2(int sa, int livea, int ja, int livebv, int n0, int what) { + int sb, nt, cj; + sb = snap_push(); + njps = 0; if (livea) jadd(R_SNAP + sa); if (livebv) jadd(R_SNAP + sb); + join(n0, what); + if (livebv) fixups(R_SNAP + sb, n0, 1); + nt = 0; if (livea) nt = fixups(R_SNAP + sa, n0, 0); + if (nt) { cj = hole(); op(O_JMP); setlit(ja, here); fixups(R_SNAP + sa, n0, 1); setlit(cj, here); } + else setlit(ja, here); + live = livea || livebv; + return 0; +} + +// the rules below are the heart of the analyzer. each is a function the parser +// calls at one kind of place where a value moves: recast (u*x), argument +// passing, assignment, return, block values, and discarding. the argument +// c they take is an expression class: C_PLAIN (an ordinary word), C_FRESH (a +// new object nobody owns yet) or C_VAR + n (a bare local, number n), which is +// how the analyzer knows the thing being moved is a variable it tracks. +// ---- analyzer rules for the places values flow ---- + +// recast: x as u / x as d / x as m +int recast(int c, int t) { + int i, st; + if (c == C_FRESH && t == S_M) return C_PLAIN; // m*f(): the unnamed result is let go on purpose, so it can be discarded + if (!fchk) return c; + if (c < C_VAR) { cerr("only a named owned object can be recast; this expression has no owner"); return c; } + i = c - C_VAR; + st = lstate[i]; + luse(i); + if (st == S_CHILD) cerrn(i, "belongs to a container; recast the container instead"); + else if (st == S_BORROW) cerrn(i, "is borrowed and can't be recast"); + else if (st == S_PLAIN) cerrn(i, "isn't an owned object"); + else if (st == S_DEAD) cerrn(i, "used after it was given away"); + else if (t == S_M && st != S_M) cerrn(i, "can't be recast back to m"); + else lstate[i] = t; + return c; +} +// an argument for a parameter tagged t (0 plain, 1 m, 2 d) +int argcheck(int c, int t) { + int i, st; + if (fchk && t == 2 && xaf && xaf[2] == 5) { cerr("a borrowed temporary cannot transfer ownership"); return 0; } + if (fchk && t == 2 && xaf && xaf[2] == 4 && (isptr(xty) || isagg(xty))) { cerr("an array literal cannot transfer ownership to a d parameter"); return 0; } + if (!fchk) return 0; + if (t == 2 && c < C_VAR && c != C_FRESH && xbor >= 0 && tracked(lroot(xbor))) { cerr("a borrowed projection cannot transfer ownership; transfer its owner"); return 0; } + if (c == C_FRESH) { + if (t != 2) { + cerr("a new object can only go straight to a d parameter; bind it to a name first"); + } else { + xgive = 1; + } + return 0; + } + if (c < C_VAR || !tracked(c - C_VAR)) return 0; + i = c - C_VAR; + if (t == 2 && isalias(i)) { cerr("a borrowed projection cannot transfer ownership; transfer its owner"); return 0; } + st = lstate[i]; + if (lstate[lroot(i)] == S_DEAD) { cerrn(i, "used after it was given away"); return 0; } + if (t <= 1) { // untagged parameters are m: a lend + if (st == S_D) { + cerrn(i, "is d; pass it to a d parameter (lending it would lose it)"); + return 0; + } + touch(i); + return 0; + } + if (st == S_D || ((st == S_M || st == S_U) && lown[i])) { end_owner(i); luse(i); xgive = 1; ev_put(i, EV_DOWN, -1); return 0; } + if (st == S_M) cerrn(i, "is m; write d*x to give it away"); + else if (st == S_U) cerrn(i, "is u; write d*x to give it away instead of returning it"); + else if (st == S_CHILD) cerrn(i, "belongs to a container and can't be given away alone"); + else cerrn(i, "is borrowed and can't be given away"); + return 0; +} +// child, owner on the stack (owner on top). write owner into the child's +// header and leave the child. a shared child keeps the owner it has. a null +// child's address is out of range and the store does not land. +// o3 code can't call own(), so this is own() inlined: skip the store when the +// child is shared (flag bit 8) and otherwise write the owner into the child's +// header word p[-2] +int o3own() { + int sk, st; + lit(1); op(O_PICK); lit(4); op(O_SUB); op(O_LD); lit(8); op(16); + sk = hole(); op(O_JZ); + op(O_DROP); + st = hole(); op(O_JMP); + setlit(sk, here); + lit(1); op(O_PICK); lit(2); op(O_SUB); op(O_SWAP); op(O_SWAP); op(O_ST); + setlit(st, here); + return 0; +} +// storing a child into container local p: emit the call that records the +// owner in the child's header. a growable array's elements live in its items +// object, so that is the owner there. +int emitown(int p) { + vaddr(p); op(O_LD); + // a T[?]'s elements live in its items object + if (ltype[p][0] == TY_DYN) { + lit(2); + op(O_ADD); + op(O_LD); + } + if (o3on && !mdbg) o3own(); + else callname(s_own); + if (o3on && hasptr(xty) && !xstamp) { lit(xty); callname(s_fatten); } + return 0; +} +// a store of class rc into an lvalue of kind k (local or container index i) +int assigncheck(int k, int i, int rc) { + int st, j, r, js, bor, origin = effect_origin(rc); + if (k == LV_LOCAL && local_origin) { local_origin[i] = origin; local_origin_mask[i] = local_origin_mask[i] | effect_mask(rc); } + if (k == LV_ELEM) { effect_note_mask(effect_mask(C_VAR + i), EF_WRITE); } + if (k != LV_LOCAL && isptr(xty)) effect_note_mask(effect_mask(rc), EF_RETAIN); + else if (k != LV_LOCAL && xty == tword) effect_note_mask(effect_mask(rc), EF_UNKNOWN); + if (fchk && xaf && xaf[2] == 5 && (isptr(xty) || xty == tword)) cerr("a borrowed temporary cannot escape its expression into a variable or container"); + bor = xbor; xbor = -1; + if (!fchk) return C_PLAIN; + if (k != LV_LOCAL && xaf && xaf[2] == 4 && (isptr(xty) || isagg(xty))) { cerr("an array literal cannot escape into a global or container"); return C_PLAIN; } + j = -1; + if (rc >= C_VAR && tracked(rc - C_VAR)) j = rc - C_VAR; + if (j >= 0 && lstate[lroot(j)] == S_DEAD) { + cerrn(j, "used after it was given away"); + return C_PLAIN; + } + if (k == LV_LOCAL) { + st = lstate[i]; + if (isalias(i)) st = S_PLAIN; // a borrow isn't an owner; overwriting it drops the alias + if (j < 0 && rc != C_FRESH) { + if (!empty(st)) cerrn(i, "still owns an object; overwriting it would leak it"); + else if (bor >= 0 && bor != i && (tracked(bor) || larr[bor])) { lstate[i] = S_CHILD; lowner[i] = bor; lown[i] = 0; } + else lstate[i] = S_PLAIN; + return C_PLAIN; + } + if (!empty(st)) { + cerrn(i, "still owns an object; overwriting it would leak it"); + return C_PLAIN; + } + if (rc == C_FRESH) { + lstate[i] = S_M; + lused[i] = 0; + lown[i] = 1; + local_ended[i] = 0; local_end_info[i] = 0; + if (ltag[i] & 15) lstate[i] = ltag[i] & 15; + return C_PLAIN; + } + js = lstate[j]; + if (js == S_BORROW || isalias(j)) { lstate[i] = S_CHILD; lowner[i] = lroot(j); lown[i] = 0; touch(j); return C_PLAIN; } + // replace a released owner by moving a same-scope owner into its empty slot. + // fresh declarations still cannot copy owners; the source and its aliases expire. + if (st == S_DEAD && lown[i] && js == S_M && lown[j] && lhome[j] >= lhome[i] + && !local_captured[i] && !local_captured[j]) { + lstate[i] = S_M; lused[i] = 0; lown[i] = 1; + lnsz[i] = lnsz[j]; lleaf[i] = lleaf[j]; + local_ended[i] = 0; local_end_info[i] = 0; + end_owner(j); luse(j); ev_put(j, EV_MOVE, i); + if (ltag[i] & 15) lstate[i] = ltag[i] & 15; + return C_PLAIN; + } + if (js == S_U && lhome[i] == lhome[j] - 1) { // handing a u object up one block + lstate[i] = S_M; + lused[i] = 0; + lown[i] = 1; + end_owner(j); + luse(j); ev_put(j, EV_UP, i); + if (ltag[i] & 15) lstate[i] = ltag[i] & 15; + return C_PLAIN; + } + if (js == S_U) { + cerrn(j, "is u; it can only be handed to a variable of the enclosing block, or returned"); + } + else cerrn(j, "is an owned object and can't be copied into another variable"); + return C_PLAIN; + } + if (k == LV_ELEM) { + r = lroot(i); + st = lstate[r]; + touch(i); + if (rc == C_FRESH) { emitown(i); return C_PLAIN; } + if (j < 0) return C_PLAIN; + js = lstate[j]; + if (js == S_CHILD) cerrn(j, "already belongs to a container"); + else if (js == S_BORROW) cerrn(j, "is borrowed and can't be stored"); + else if (st == S_BORROW) { + if (js == S_U) { + cerrn(j, "is u; it has to leave through the block above, not into a borrowed object"); + } + else { end_owner(j); luse(j); ev_put(j, EV_STORE, i); emitown(i); } // storing it gives it away + } else if (js != st) cerrn(j, "its tag doesn't match its container's (both have to be m, u or d)"); + else if (lhome[j] < lhome[r]) cerrn(j, "lives in an outer block than the container it's stored into"); + else { lstate[j] = S_CHILD; lowner[j] = i; luse(j); ev_put(j, EV_STORE, i); emitown(i); } + return C_PLAIN; + } + // a global, or memory the analyzer doesn't track: the collector takes it + // an owner (d, u, or an m local that owns its object) is transferred as if by u*x + if (k == LV_GLOBAL) lend_clear(i); + if (j >= 0 && k == LV_GLOBAL && xlend == j && lstate[j] == S_M && lown[j]) { xlend = -1; touch(j); lend_add(i, j); return C_PLAIN; } + xlend = -1; + if (j >= 0) { + if (lstate[j] == S_D || lstate[j] == S_U || (lstate[j] == S_M && lown[j])) { ev_put(j, EV_GLOBAL, -1); end_owner(j); luse(j); } + else cerrn(j, "is not an owner, so it can't be stored in a global or in untracked memory (the collector takes it over)"); + } + return C_PLAIN; +} +// return c: an owned object (d, or u after u*x) goes up with the return, and the +// function's return is owned: declared with .u, or inferred from this return when +// the function has no .u (its callers compiled after this point see it). an m +// object is lent, not given, so returning it is an error, and so is a plain word +// from an owned function that was called before its body. +int retcheck(int c) { + int i; + if (isptr(xty) || xty == tword) { + effect_note_mask(effect_mask(c), EF_ALIAS); + if (c >= C_VAR && lrskind[c - C_VAR] == RS_PARAM) effect_note(effect_origin(c), EF_IDENTITY); + } + if (fchk && xaf && xaf[2] == 5 && (isptr(xty) || xty == tword)) cerr("a borrowed temporary cannot escape its expression through return"); + if (!fchk) return 0; + if (c < C_VAR && c != C_FRESH && xbor >= 0 && (isptr(xty) || xty == tword)) { + int root = lroot(xbor); + if (fret) { cerr("a borrowed projection cannot be returned as an owned result"); return 0; } + if (lown[root] && (lstate[root] == S_M || lstate[root] == S_U || lstate[root] == S_D)) { cerr("a borrow of a local owner cannot escape through return; return the owner"); return 0; } + } + if (c >= C_VAR && isalias(c - C_VAR)) { + int root = lroot(c - C_VAR); + if (fret) cerr("a borrowed projection cannot be returned as an owned result"); + else if (lown[root]) cerr("a borrow of a local owner cannot escape through return; return the owner"); + return 0; + } + if (c >= C_VAR && lstate[c - C_VAR] == S_BORROW) { + if (fret) cerr("a borrowed parameter cannot be returned as an owned result"); + return 0; + } + if ((isptr(xty) || isagg(xty)) && ((xaf && xaf[2] == 4) || (xbor >= 0 && lafact[xbor] && lafact[xbor][2] == 4))) { cerr("an array literal cannot escape its function through return"); return 0; } + i = -1; + if (c >= C_VAR && tracked(c - C_VAR)) i = c - C_VAR; + if (i >= 0 && lstate[lroot(i)] == S_DEAD) { cerrn(i, "used after it was given away"); return 0; } + if (i >= 0 && (lstate[i] == S_D || (lstate[i] == S_M && lown[i])) && (fret || finfer)) lstate[i] = S_U; // returning an owned object (a d object, or a local that owns one) gives it up, as u*x does + if (!fret && finfer && (c == C_FRESH || (i >= 0 && lstate[i] == S_U))) { + if (sflag[finsym] & F_EARLY) serrn(sname[finsym], "is called before its body, so its owned return can't be known there; declare the return type with .u"); + ssig[finsym] = ssig[finsym] | 1; // the return is owned: callers read it from the signature + sflag[finsym] = sflag[finsym] | F_RETOWN; + if (stype[finsym]) { word ft = stype[finsym]; stype[finsym] = tfn(ft[4], ft[3], ft + 7, ssig[finsym]); } + fret = 1; finfer = 0; + } + if (fret) { + if (i >= 0) { + if (lstate[i] == S_U) { end_owner(i); luse(i); ev_put(i, EV_RETURN, -1); } + else cerrn(i, "has to be u to be returned"); + } + return 0; + } + if (c == C_FRESH) cerr("returns a new object; give the function a .u return type"); + else if (i >= 0) cerrn(i, "is owned and can't be returned from a function whose return type isn't .u"); + return 0; +} +// the last expression of a block expression +int handup(int c) { + int i; + if (!fchk) return C_PLAIN; + if (c == C_FRESH) return C_FRESH; + if (c < C_VAR || !tracked(c - C_VAR)) return C_PLAIN; + i = c - C_VAR; + if (lstate[i] == S_U && lhome[i] == fdepth) { lstate[i] = S_DEAD; luse(i); ev_put(i, EV_VALUE, -1); return C_FRESH; } + if (lstate[i] == S_U) cerrn(i, "only a u object of this block can be its value"); + else cerrn(i, "has to be u to be handed up as the block's value"); + return C_PLAIN; +} +// throw away an expression result, which is an error for a fresh object. +int discard(int c) { + if (c == C_FRESH) cerr("a new object (or a u result) can't be thrown away"); + else if (c >= C_VAR) touch(c - C_VAR); + xbor = -1; + return 0; +} + +// ---- completion ---- + +// copy local i (kind: 1 local, 2 parameter, 3 captured from the function around a closure) into the probe, +// unless a nearer local has its name +int probe_add(int i, int kind) { + int k = 0; + while (k < probe_n) { if (probe_name[k] == lname[i]) return 0; k++; } + if (!probe_name || probe_n == probe_cap) { + if (!probe_cap) probe_cap = 32; else probe_cap = probe_cap * 2; + probe_name = chk_resize(probe_name, probe_n, probe_cap); probe_kind = chk_resize(probe_kind, probe_n, probe_cap); + probe_state = chk_resize(probe_state, probe_n, probe_cap); probe_type = chk_resize(probe_type, probe_n, probe_cap); probe_decl = chk_resize(probe_decl, probe_n, probe_cap); + } + probe_name[probe_n] = lname[i]; probe_kind[probe_n] = kind; + probe_state[probe_n] = lstate[lroot(i)] == S_DEAD || af_expired(lafact[i]); + if (lafact[i] && lafact[i][1] == 2) probe_state[probe_n] = 1; + probe_type[probe_n] = ltype[i]; probe_decl[probe_n] = lrsoff[i]; + probe_n++; + return 0; +} +// what is in scope right now: this function's locals, then those of the functions around a closure +int probe_snap() { + int i = nloc, k = nclo; + probe_n = 0; probe_has = 1; + while (i > lbase) { + i--; + if (lname[i][0] != '<') { if (lrskind[i] == RS_PARAM) probe_add(i, 2); else probe_add(i, 1); } + } + while (k) { + k--; + i = clo_hi[k]; + while (i > clo_lo[k]) { i--; if (lname[i][0] != '<') probe_add(i, 3); } + } + return 0; +} +// a statement of the document starts here: remember what is in scope, or stop when the place has been passed. +// statements of a method or generic compiled for a call belong to another function and are left alone +int probe_stmt() { + if (!cmode || dry || ginst || chkabort || incdepth != 0 || ctxsp > nclo) return 0; + probe_snap(); + if (toff > probe_off) { chkabort = 1; return 0; } + return 0; +} +// the place asked about is a member name, and t is the type before the dot +int probe_member(word t) { + probe_mem = t; probe_has = 2; chkabort = 1; + return 0; +} + diff --git a/src/compiler/base.zc b/src/compiler/base.zc new file mode 100644 index 0000000..17ec80b --- /dev/null +++ b/src/compiler/base.zc @@ -0,0 +1,1384 @@ +namespace run; +include rt; + +// ============================================================================ +// compiler.zc: the resident zc compiler. it lives in the genome next to the +// runtime, compiles source straight to vm words in memory, and is visible to +// the programs it compiles: every function and global here can be called or +// patched by name, and parse words (immediate fns) run inside it. +// ============================================================================ + +// how this file is organized, in the order a source file travels through it: +// +// 1 lexer bytes -> tokens (nextch, lex1, next/peek). a token is a +// kind (tk), a value (tv: number or interned name) and a +// place (line, offset, length). sources nest: stdin, a +// token list for macros, a string for eval. +// 2 symbols every name is interned once in a string pool; symbols +// (functions, globals, constants, keywords, types) hang +// off the interned name and shadow one another. +// 3 types records in the type area, hash-consed, so comparing two +// types is comparing two words. typing is optional: an +// untyped program is all "word". +// 4 analyzer the ownership/lifetime pass. it runs while the parser +// emits code, tracking for every local whether it owns an +// object in this block (m), moves one up and out (u), or +// moves one down into a lower scope (d). borrowing is +// tracked separately. the pass inserts frees, and joins +// merge that knowledge at +// if/else, loops and switches. +// 5 expressions precedence climbing over the operator tables, which a +// program can extend at compile time. +// 6 statements blocks, control flow, switch, asm blocks, quote/grab. +// 7 functions signatures, prologues, closures, methods, generics, +// fattening (typed new) and the top level. +// 8 setup the keyword table, built-in names, entry point. +// 9 check mode the same compiler run over an in-memory document for the +// language server: errors and hints become records. +// +// the compiler emits vm words directly into memory at "here" (there is no +// intermediate tree). a forward reference emits a placeholder literal and +// remembers the site on a patch chain; defining the name patches the chain. +// the compiler is itself written in zc and lives in the genome next to rt.zc, +// so a program being compiled can call any function in this file, and +// immediate fns (parse words) run in the middle of compilation and can read +// tokens, emit code, or add operators. a few conventions that recur: +// - "plain word": most values are untyped 64-bit words. a pointer is just a +// word that happens to be a heap address. +// - a code word is lit(v) = 2v+1 (odd) or op(k) = 2k (even). see lit and op. +// - names ending in an s_ prefix are interned spellings the parser compares +// against by pointer, which is why intern exists. +// - serr reports and carries on; die reports and skips input to the next +// ; or }. nothing in here raises or unwinds. +// - a backtick outside a comment or a literal ends the source (srcend). zc0, +// the c bootstrap, reads the same way, so a backtick in a comment is fine. +// +// ---- compiler data region (@map-cc) ---- + +// src/tools/genmap computes these addresses from the compiler table sizes for each +// build. later regions (the type area, local types, exit records) are computed +// from these names. + +// @map-cc +const IHN = 0x8000; +const R_IH = 0x90100; +const R_POOL = 0x98100; +const R_POOLEND = 0xD8100; +const NS = 0x4000; +const R_SYM = 0xD8200; +const sname_BASE = 0xD8200; +const skind_BASE = 0xDC200; +const sval_BASE = 0xE0200; +const ssig_BASE = 0xE4200; +const sflag_BASE = 0xE8200; +const sfix_BASE = 0xEC200; +const snext_BASE = 0xF0200; +const NL = 0x200; +const lname_BASE = 0xF4200; +const lslot_BASE = 0xF4400; +const lhome_BASE = 0xF4600; +const lstate_BASE = 0xF4800; +const lowner_BASE = 0xF4A00; +const lused_BASE = 0xF4C00; +const larr_BASE = 0xF4E00; +const lown_BASE = 0xF5000; +const NF = 0x8000; +const fsite_BASE = 0xF5200; +const fnext_BASE = 0xFD200; +const R_SNAP_BASE = 0x105200; +const R_QBUF_BASE = 0x109200; +const R_SRC_BASE = 0x10D200; +const R_SEQ_BASE = 0x10D600; +const bname_BASE = 0x10DE00; +const bprec_BASE = 0x10DE40; +const bseq_BASE = 0x10DE80; +const blen_BASE = 0x10DEC0; +const uname_BASE = 0x10DF00; +const useq_BASE = 0x10DF20; +const ulen_BASE = 0x10DF40; +const aname_BASE = 0x10DF60; +const abin_BASE = 0x10DFA0; +const kops_BASE = 0x10E000; +const R_CTX_BASE = 0x10E200; +const R_LOOP_BASE = 0x10EA00; +const R_ASM = 0x10EE00; +const ASM_LABELS = 0x40; +const ASM_REFS = 0x100; +const ASM_WORDS = 0x400; +const EXIT_WORDS = 0xA00; +const ENUM_MEMBERS = 0x80; +const TAGGED_ENUMS = 0x10; +const STRUCT_METHODS = 0x20; +const GENERATED_WORDS = 0x2000; +const RANGE_FACTS = 0x20; +const JOIN_PATHS = 0x40; +const O3_REGIONS = 0xA; +// @end-map + +// table bases start in the generated map; growth copies columns without changing indices. + +// interned spelling of each symbol, indexed by symbol id; use lookup(name) to find an id. +char** sname = sname_BASE; +// kind of each symbol: K_FN, K_GVAR, K_GARR, K_CONST, K_KW, K_ENH, K_TYPE or K_DOC. +word skind = skind_BASE; +// value of each symbol, indexed by symbol id: a function code address, variable storage address, constant value, enhancement slot or type record, according to skind. +word sval = sval_BASE; +// packed function signature for each symbol; includes the owned-return bit, parameter count and parameter lifetime tags. +word ssig = ssig_BASE; +// definition and compilation flags for each symbol, such as F_DEF, F_DECL, F_GEN and F_STATIC. +word sflag = sflag_BASE; +// head of unresolved code references for each symbol; references are patched when the symbol is defined. +word sfix = sfix_BASE; +// previous symbol with the same interned name, stored as id plus one; zero ends its shadow chain. +word snext = snext_BASE; +// retained source metadata for functions that can be transformed at runtime. +word fnmeta; +int syntax_host_floor, syntax_host_from; word syntax_host_symbols; +// interned name of each currently active local variable or parameter. +char** lname = lname_BASE; +// frame slot of each local; laddr(lslot[i]) emits its address relative to the current frame. +word lslot = lslot_BASE; +// scope depth where each local was declared; the lifetime analyzer uses it when values move between blocks. +word lhome = lhome_BASE; +// ownership state of each local: plain, m, u, d, dead, child or borrowed. +word lstate = lstate_BASE; +// owner local of a borrowed or container-owned local; lroot(i) follows this chain. +word lowner = lowner_BASE; +// source line of each local's last tracked use; zero means it has not been used. +word lused = lused_BASE; +// nonzero for locals stored in place as fixed arrays or structs; their expression value is their address. +word larr = larr_BASE; +// nonzero when a local is responsible for releasing its owned object. +word lown = lown_BASE; +// code location to patch for each unresolved reference record. +word fsite = fsite_BASE; +// next unresolved reference record, stored as index plus one; zero ends a patch chain. +word fnext = fnext_BASE; +int symcap = NS, loccap = NL, fixcap = NF; + +const word R_TY = rt.ZFP - 16384, R_TYEND = rt.ZFP; +// interned text and type records never move: only their index vectors grow. +char** namehash = R_IH; word poolend = R_POOLEND; +word poolblocks, typeblocks; +int hashcap = IHN, namecount, poolchunk = R_POOLEND - R_POOL; +word typelist = R_TY, tyend = R_TYEND; +int typecap = 1024; + +// growing scratch regions retain offsets used by nested parser frames. +word R_SNAP = R_SNAP_BASE; int R_SNAP_cap = R_QBUF_BASE - R_SNAP_BASE; +word R_QBUF = R_QBUF_BASE; int R_QBUF_cap = R_SRC_BASE - R_QBUF_BASE; +word R_SRC = R_SRC_BASE; int R_SRC_cap = R_SEQ_BASE - R_SRC_BASE; +word R_SEQ = R_SEQ_BASE; int R_SEQ_cap = bname_BASE - R_SEQ_BASE; +word R_CTX = R_CTX_BASE; int R_CTX_cap = R_LOOP_BASE - R_CTX_BASE; +word R_LOOP = R_LOOP_BASE; int R_LOOP_cap = R_ASM - R_LOOP_BASE; +word[60] lpfact_initial; word lpfact = lpfact_initial; int loopfacts_cap = 60; + +// operator columns grow independently of their initial map reservations. +char** bname = bname_BASE; +word bprec = bprec_BASE; +word bseq = bseq_BASE; +word blen = blen_BASE; +char** uname = uname_BASE; +word useq = useq_BASE; +word ulen = ulen_BASE; +char** aname = aname_BASE; +word abin = abin_BASE; +word kops = kops_BASE; +int bincap = bprec_BASE - bname_BASE, uncap = useq_BASE - uname_BASE, asgcap = abin_BASE - aname_BASE, kopcap = R_CTX_BASE - kops_BASE; +// ---- token kinds, symbol kinds and flags ---- + +const int T_EOF = 0, T_NUM = 1, T_ID = 2, T_STR = 3, T_OP = 4, T_FLT = 5, T_BSTR = 6; +const int K_FN = 1, K_GVAR = 2, K_GARR = 3, K_CONST = 4, K_KW = 5, K_ENH = 6, K_TYPE = 7, K_DOC = 8; +const int F_DEF = 1, F_SIG = 2, F_UNTYPED = 4, F_STMT = 8, F_EXPR = 16, F_TOP = 32, F_FALLBACK = 64, F_GEN = 128, F_PRE = 256, F_O3 = 512, F_EXT = 1024, F_TPL = 2048, F_STATIC = 4096, F_DECL = 8192, F_RETOWN = 16384, F_EARLY = 32768; +// variable states for the lifetime analyzer +const int S_PLAIN = 0, S_M = 1, S_U = 2, S_D = 3, S_DEAD = 4, S_CHILD = 5, S_BORROW = 6; +// expression classes: plain word, a new object, or a bare variable (C_VAR + index) +const int C_PLAIN = 0, C_FRESH = 1, C_VAR = 2; +// lvalue kinds +const int LV_NONE = 0, LV_LOCAL = 1, LV_GLOBAL = 2, LV_ELEM = 3, LV_OTHER = 4; +// opcodes +const int O_Q = 0, O_LD = 1, O_ST = 2, O_PICK = 3, O_DROP = 4, O_SWAP = 5, O_CALL = 6, O_JMP = 7; +const int O_JZ = 8, O_RET = 9, O_ADD = 13, O_SUB = 14, O_LT = 20; +const int XOP0 = 22; // first enhancement opcode +const int OP_FLIT = 600, OP_FADD = 601, OP_FSUB = 602, OP_FMUL = 603, OP_FDIV = 604; +const int OP_FEQ = 605, OP_FLT = 606, OP_FLE = 607, OP_I2F = 608, OP_F2I = 609; +const int SEQWORD = -4; // in an operator sequence: the next word is a full-width literal +const int SEQREF = -2; // in an operator sequence: the next word names a symbol + +// ---- state ---- + +int chk_heap; +int nenh; word pooltop; int nsym, ubase, vislo, nfixu, fixfree; +char* module_name; word module_imports; +int nbin, nun, nasg, nkop, seqtop; +char* mnem = "q@!$_~cjzr,?.+-*&|^><%"; +int check_on = 1; // set to 0 to switch the analyzer off for what follows +int tk; word tv; int tglue, tline, line = 1, ch, chkind; word chptr, chbase; int qdone, isp = -1, pendot; +// where the current token sits in its source (chars from the start, -1 for a token list), how long it is, and where the one before it ended +int toff = -1, toklen, tpend = -1, tpline; +int ntok; // tokens taken so far, so a parse word that reads nothing can be caught +int qst, qesc, qp; // srcend's reading state: normal, line comment, block comment, string, char literal +char[1100] tbuf_initial; char* tbuf = tbuf_initial; int tbufcap = 1100; +// doc comments: the comment lines directly above a declaration (no blank line +// between them or before the declaration) are its documentation. the lexer +// collects them in dbuf, dlast is the line of the last one, and dline the +// line of the token that follows them. only when docs are on. +int docs_on; // docs on; keeps them: a symbol "doc name" holds a byte string +char[2100] dbuf_initial; char* dbuf = dbuf_initial; int docbufcap = 2100; int dn, dlast, dline, docpending, dsect; +char[2100] dsnap_initial; char* dsnap = dsnap_initial; int docsnapcap = 2100; int dsn, dnm; char[300] dnmb_initial; char* dnmb = dnmb_initial; int docnamecap = 300; +int pk_tk, pk_tv, pq_n; word[4] pq_tk; word[4] pq_tv; word[4] pq_gl; word[4] pq_ln; // up to 4 tokens of lookahead +int here, hdelta, hlimit, ptop; +int code_chunk = rt.CODE_END; +word image_buffer, image_words; int image_active, image_org, image_capacity, image_size; +int nloc, lbase, fdepth, fslots, fchk, fret, live, bxdepth, epichain, fframe, infn; +// a global that borrows a local owner through g = m*x. each entry is the global's +// symbol, the local, and the block depth of the store; the lend ends when the same +// global is assigned again in that block, and is an error if the block can end first. +word lendrec; int nlend, lendcap, xlend = -1; +int finsym, finfer; // the function being compiled, and whether its owned return is still to be inferred (no .u) +int fn_return_type; // a consistent return type inferred from the function body +// effects come from the real compiler's uses, stores, returns and call edges; unknown bodies stay conservative. +const int EF_READ = 1, EF_WRITE = 2, EF_RETAIN = 4, EF_CONSUME = 8, EF_UNKNOWN = 16, EF_ALIAS = 32, EF_IDENTITY = 64; +word effect_symbols, effect_edges, effect_requirements, effect_current; int effect_floor, effect_argument_base; +word local_origin, local_origin_mask, local_cell, local_captured, local_ended, local_end_info; word effect_value, argorigin_mask; int capture_enabled; word[1] capture_dummy; +int borrow_pending, borrow_depth, borrow_jump = -1, borrow_entry; word borrow_slots; +int argorigin = -1, argtemporary, argowner = -1; word argowner_fact; +int stubgen; // 1 while an extern's generated stub is compiled: the stub gets no hints +word xprojection; int af_mutation_tick; +int xbor = -1; // local a pointer value is borrowed from, or -1 +int mdbg, mdbg_flags; // memdebug: compile checks in, and the flags start() turns on +int lsp, loopbase, ctxsp, snaptop; +int lv, lvk, lvi, kw_expr, xcls, fty; +// interned spellings the parser compares against + +int s_lp, s_rp, s_lb, s_rb, s_lk, s_rk, s_comma, s_semi, s_eq, s_arrow, s_inc, s_dec; +int s_star, s_slash, s_amp, s_colon, s_hash, s_minus, s_andand, s_oror; +int s_dot, s_fn, s_var, s_const, s_else, s_as, s_unchecked, s_immediate, s_m, s_d, s_u; +int s_q, s_case, s_default, s_fovf, s_start, s_main, s_free, s_recycle, s_own, s_qnew, s_enhance, s_micro, s_vm; +int s_frame_floor, s_frame_top, s_frame_node, s_frame_busy, s_frame_guard, s_frame_grow, s_frame_leave; +int s_copy_w; +int s_do, s_while, s_enum, s_include, s_memdebug, s_scrub, s_alloc, s_check, s_memset, s_static; + +// ---- types: kinds, tables and parser state (see the types section) ---- + +const int TY_WORD = 0, TY_INT = 1, TY_CHAR = 2, TY_BOOL = 3, TY_VOID = 4, TY_PTR = 5, TY_ARR = 6; +// dyn: T[?], a = element, n = 1 if fixed +const int TY_FN = 7, TY_STRUCT = 8, TY_VAR = 9, TY_GEN = 10, TY_DYN = 11, TY_FLOAT = 12, TY_BSTR = 13; +const int NTL = 1024; // the first NTL words list every record +// type per local, declared tag per local, type per symbol + +const word LTYPE_BASE = R_ASM + ASM_WORDS, LTAG_BASE = LTYPE_BASE + NL, STYPE_BASE = LTAG_BASE + NL; +word ltype = LTYPE_BASE, ltag = LTAG_BASE, stype = STYPE_BASE; +int tytop, ntl; word tword, tint, tchar, tbool, tvoid, tfloat, tf32, tbstr; +int ckst; char* ckn; // extern: the zc struct a by-value parameter names (ckind), and its name +int tc_on; // the optional typechecker, off unless a program says typecheck on; +// type, literal 0, float, byte string, dry runs (typeof), unique giveaway + +word xty; int xzero, xfloat, xstr, dry, xgive; +// the type facts of annotate mode (see type facts below): the tables, and where the declaration being read starts + +const int NIF = 431, IFP = 24, IFW = 80, IFN = 4096, IFT = 32768; +int ifon; word ifs, ifr; int nif; word ifx; int nifx; word ift; int nift; word ifcur; int ifkp, ifkk, ifcall, ifgk, ifstrict; +word if_edges, if_value, if_local; +int ifrcap = NIF, ifxcap = IFN, iftcap = IFT; +int fldst, fldi; // the one struct a bare address's field name resolved to +char*[8] tpn; int ntp; // type parameters in scope +// reading a return type: unknown names are type parameters declared after the function's name +int tvauto; +// the typedefs of the blocks being compiled: three words each (the symbol, the block depth and the +// compile context it was made in), newest last. a block's end unlinks them from their names. +word tdstk; int ntd, tdcap; +word readonly_types; +int dty, dtag; // result of dtype() +int s_typedef; +int s_lt, s_gt, s_shr, s_le, s_ge, s_eqeq, s_ne, s_plus, s_struct, s_type, s_typecheck, s_docs, s_small, s_std, s_icon, s_on, s_off, s_new, s_new_t; +int s_o3, s_lfa, s_outside, s_new_raw, s_new_oz, s_oob; +int s_ld8, s_st8, s_ld16, s_st16, s_ld32, s_st32; +// ---- o3 region state ---- + +// each promoted slot maps to a register; pending regions use 67-word records and a descriptor buffer. +int o3on, o3nxt, o3one; word[32] o3sl; word[32] o3rg; int no3sl; word[O3_REGIONS * 67] o3pend_initial; word o3pend = o3pend_initial; int o3pendcap = O3_REGIONS, no3pend; word[80] o3dsc; int o3fs, o3na, o3fn0; +// o3free holds registers released by closed scopes; o3cache holds the items pointer and length slots for fixed arrays. +word[8] o3free; int no3free, o3nreg; word[NL] o3cache_initial; word o3cache = o3cache_initial; int argloc, s_hitems, s_hlen, s_fatten; +// operator names used by numeric type checks. +word[12] numops; int nnumops, s_fixed, s_dlen, s_dcap, s_dnew, s_dat, s_dpush, s_dpop, s_dresize, xconst, xcval; +int xlenloc, arrloc, fortest, forstep, ft_iv, ft_bd, ft_ir, ft_nops, fs_ok, xstamp; +// local lower bound / len(array); one proof record per live loop +int nfact, factcap = RANGE_FACTS; word[RANGE_FACTS] floc_initial, flo_initial, flen_initial; word floc = floc_initial, flo = flo_initial, flen = flen_initial; +int s_len_s, s_at_s, s_slice_s, s_cat_s, s_eq_s, s_lt_s, s_le_s, s_put_bs, s_put_s, s_n_to_s, s_new_s; +int mdg; word[8] mdn; // the type parameters of the generic struct whose methods are being compiled +word[8] gbind; int ngbind; // explicit type arguments for the next call: f<int>(...) +// generic functions. the template (type parameters stay variables) is compiled once, as before, +// and its tokens are kept; a call whose type arguments are all known gets an instance, the same +// tokens compiled again with the parameters bound to those types, so new(T) and sizeof(T) +// inside it mean the real type. instances are compiled after the item that asked for them. +int grec; word gbuf; int gn, gcap; // the tokens of the top-level item being read +int gnew, gnew_np; word[8] gnew_nm; // a generic function was just defined +int fn_method_compile, fnpatchseq; +word gtab; int gt_n, gtcap = 64, gicap = 128; word gt_sym, gt_tok, gt_np, gt_module, gt_imports, gt_source; int gt_nm; // the columns are allocated at the first generic function +int gi_n; word gi_t, gi_sym, gi_ar, gi_done; +int ginst; word[8] gnm; word[8] gib; int gng, ginsn; // compiling an instance: the names and what they stand for +int s_this; +const int NCLO = 8; /* initial closure nesting capacity; grows when more closures are compiled */ +const int NCAP = 32; /* initial capture capacity per closure; grows when more variables are captured */ + +// ---- closure compilation state ---- + +// closures being compiled, innermost last: the enclosing function's locals +// (lo .. hi), how many variables the body has captured so far (names and the +// enclosing local each came from), and the body's own hidden env parameter + +int nclo, closure_cap = NCLO, capture_width = NCAP; +word[NCLO] clo_lo_initial; word clo_lo = clo_lo_initial; +word[NCLO] clo_hi_initial; word clo_hi = clo_hi_initial; +word[NCLO] clo_n_initial; word clo_n = clo_n_initial; +word[NCLO] clo_env_initial; word clo_env = clo_env_initial; +word[NCLO] clo_ret_initial; word clo_ret = clo_ret_initial; +char*[256] clo_name_initial; char** clo_name = clo_name_initial; +word[256] clo_loc_initial; word clo_loc = clo_loc_initial; +word[256] clo_via_initial; word clo_via = clo_via_initial; + + +// ---- errors ---- + +// the compiler reports up to 20 errors before it stops. a syntax error +// (die) skips the token it happened on and sets panic, which skips ahead to +// the next ; or } (or the next top-level item) and keeps quiet until then. +// other errors (serr) just report: the parse is still in step. +// +// check mode (cmode) is for the language server, which runs the real compiler +// over a document it holds in memory. nothing is printed and nothing halts: +// each error is written into dgbuf as a record [severity, line, offset, +// length, chars...] and giveup raises chkabort, which makes the lexer return +// end of file so every parsing loop winds down by itself. +int nerr, panic; +// bits kept in a local's ltag for the hints: assigned after its declaration, +// read, declared with let, initialized with a literal; the size of the object +// a new gave it (bits 16 to 39); and how many times it was used (from bit 56) +const int LASG = 0x1000, LREAD = 0x2000, LDECL = 0x4000, LCONST = 0x8000, LCNT1 = 0x100000000000000; +const int NEWTYPED = 0x800000; +word[NL] lpos_initial; word lpos = lpos_initial; word[NL] lreadpos_initial; word lreadpos = lreadpos_initial; word[NL] lnsz_initial; word lnsz = lnsz_initial; word[NL] lleaf_initial; word lleaf = lleaf_initial; word[NL] lrsoff_initial; word lrsoff = lrsoff_initial; word[NL] lrskind_initial; word lrskind = lrskind_initial; int newsz, newleaf, nomagic, callpos; +word[NL] lptr_initial; word lptr = lptr_initial; // annotate mode: the local was indexed or dereferenced, so it holds an address +// storage facts are shared by aliases. they describe one allocation, not a +// variable slot: [length, lifetime, kind, allocation place, end place, owner]. +// lifetime is 0 unknown, 1 live, 2 ended; kind is 1 stack, 2 heap, 3 array handle. +word[NL] lafact_initial; word lafact = lafact_initial; +word[NL] laaddr_initial; word laaddr = laaddr_initial; word xaf, argfact; + +// value previews are separate from optimization facts. each reference keeps +// the value proved at that position: 1 integer, 2 float bits, 3 string, 4 bytes. +word lvkind, lvvalue, lvescaped, svkind, svvalue; int xvk; word xvv; +// local name lookup keeps a head for each spelling and a shadow link per slot. +word[NL] lprev_initial; word lprev = lprev_initial; +word[256] lkeys_initial; word lkeys = lkeys_initial; +word[256] lheads_initial; word lheads = lheads_initial; int lkeycap = 256, lkeyn; +int s_new_k, s_recycle_leaf; +// dgprev: the next record sits at the end of the previous token +// chk_hook, when set, is called after each top-level item of the document in check mode: the language +// server streams diagnostics and drops a check whose source has been replaced +word chk_hook; +int cmode, chkabort, chkfuel; word dgbuf, dggrow; int dgcap, dgpos, dgn, dgopen, dgprev, dgmode; +// expand a diagnostic in progress, keeping its output offset within the buffer. +int dg_expand() { + dgbuf = chk_resize(dgbuf, dgpos + 5 + rt.outn, dgcap * 2); + dgcap = dgcap * 2; rt.outbuf = dgbuf + dgpos + 5; rt.outcap = dgcap - dgpos - 5; + return 0; +} +// diagnostic records for check mode. dg_open starts a record [severity, line, +// offset, length, text length, text...] at the current token (or just past the +// previous one when dgprev is set) and redirects the error text into it with +// outmode 2; dg_close seals it with the length of the text and restores output. +// severity 1 is an error, 2 a warning, and 4 a hint. +// completion: the language server asks what is in scope at a place in the document. probe_off is that place +// (a byte offset; -1 when nobody asks). the check keeps a copy of the locals at the last statement that starts at or +// before it, with whether the lifetime analysis has used each one up, and, when the place is a member name after +// a dot, what is before the dot. the check stops as soon as it has what was asked for +int probe_off = -1, probe_has, probe_n, probe_cap, probe_static; +char** probe_name; word probe_kind, probe_state, probe_type, probe_decl, probe_mem; +char* curinc; int incdepth, dgdrop; int incplace; // how many includes deep the compile is; a record to throw away; the place of the outermost include +int dg_close() { + if (!dgopen) return 0; + if (!dgdrop) { dgbuf[dgpos + 4] = rt.outn; dgpos = dgpos + 5 + rt.outn; dgn++; } + dgdrop = 0; + dgopen = 0; rt.outmode = dgmode; rt.outgrow = dggrow; + return 0; +} +// open a check-mode diagnostic record of the given severity. +int dg_open(int sev) { + dg_close(); + if (dgpos + 205 > dgcap) { + int cap = dgcap; while (cap < dgpos + 205) cap = cap * 2; + dgbuf = chk_resize(dgbuf, dgpos, cap); dgcap = cap; + } + if (dgprev && tpend >= 0) { + dgbuf[dgpos + 1] = tpline; + dgbuf[dgpos + 2] = tpend - 1; + dgbuf[dgpos + 3] = 1; + } + else { dgbuf[dgpos + 1] = tline; dgbuf[dgpos + 2] = toff; dgbuf[dgpos + 3] = toklen; } + dgbuf[dgpos] = sev; + // inside an included file the document has no place for the finding: hints and warnings about a library are + // left out, and an error is shown on the include that brought the file in, with where it really is + if (incdepth > 0) { + dgbuf[dgpos + 1] = incplace & 0xFFFFFF; dgbuf[dgpos + 2] = ((incplace >> 40) & 0x1FFFFF) - 1; dgbuf[dgpos + 3] = (incplace >> 24) & 0xFFFF; + if (sev != 1) dgdrop = 1; + } + rt.outbuf = dgbuf + dgpos + 5; rt.outn = 0; rt.outcap = dgcap - dgpos - 5; + dggrow = rt.outgrow; rt.outgrow = dg_expand; + rt.outmode = 2; dgopen = 1; + if (incdepth > 0 && sev == 1) { rt.put_s("in "); rt.put_s(curinc); rt.put_s(", line "); rt.put_n(tline); rt.put_s(": "); } + return 0; +} +word[256] dgseen_initial; word dgseen = dgseen_initial; int dgseen_cap = 256, dgseen_n; +// start and stop a check run. chk_begin points the diagnostics at a buffer +// the caller owns; chk_end flushes any open record and restores output. +int chk_begin(int buf, int cap) { + cmode = 1; chkabort = 0; chkfuel = 0; dgbuf = buf; dgcap = cap; dgpos = 0; dgn = 0; dgopen = 0; + nerr = 0; panic = 0; dgmode = rt.outmode; rt.fill_w(dgseen, 0, dgseen_cap); dgseen_n = 0; + return 0; +} +// end check mode and put the output back the way it was. +int chk_end() { + dg_close(); + cmode = 0; + rt.outmode = dgmode; + return 0; +} +// a hint is a finding that isn't an error: the program still compiles, and +// only check mode reports it. hint_begin(place) opens the record at a packed +// place (see tpack) and says whether to write the text; dg_close ends it. +// one hint per source position. a growing set keeps large documents linear +// instead of rescanning every previous diagnostic for each local. +int dg_hash(int key, int cap) { return ((key ^ (key >> 21)) * 0x9E37) & (cap - 1); } +int dg_dup() { + int key = ((toff + 1) << 24) | tline, i, k, cap; word old; + if (dgseen_n + dgseen_n >= dgseen_cap) { + old = dgseen; cap = dgseen_cap * 2; dgseen = chk_resize(0, 0, cap); + i = 0; + while (i < dgseen_cap) { + if (old[i]) { k = dg_hash(old[i], cap); while (dgseen[k]) k = (k + 1) & (cap - 1); dgseen[k] = old[i]; } + i++; + } + dgseen_cap = cap; + } + i = dg_hash(key, dgseen_cap); + while (dgseen[i]) { if (dgseen[i] == key) return 1; i = (i + 1) & (dgseen_cap - 1); } + dgseen[i] = key; dgseen_n++; return 0; +} +// hints on; hints small; hints off; set how many hints the code after it gets: +// on uses informational ranges, small uses compact hint markers, and off +// suppresses hints. each directive is kept with its byte +// offset, so a hint is judged by where its code is, not where it was reported. +const int HINT_SMALL = 1, HINT_ALL = 2; +word hl_off, hl_lev; int hl_n, hl_cap = 16; +int hint_set(int level, int at) { + if (!cmode || dry || isp != 0 || chkind != 3) return 0; + if (hl_n && hl_off[hl_n - 1] >= at) return 0; + if (!hl_off) { hl_cap = 16; hl_off = chk_resize(0, 0, hl_cap); hl_lev = chk_resize(0, 0, hl_cap); } + if (hl_n >= hl_cap) { hl_off = chk_resize(hl_off, hl_n, hl_cap * 2); hl_lev = chk_resize(hl_lev, hl_n, hl_cap * 2); hl_cap = hl_cap * 2; } + hl_off[hl_n] = at; hl_lev[hl_n] = level; hl_n++; + return 0; +} +// the hint level at byte offset o: 2 all, 1 small, 0 off +int hint_level(int o) { + int k = hl_n; + while (k) { k--; if (hl_off[k] <= o) return hl_lev[k]; } + return 2; +} +// start a check-mode hint at token position p, or say no when hints are off or +// this kind (HINT_SMALL or HINT_ALL) is not wanted there. +int hint_begin(int p, int kind) { + int sv, ok; + if (!cmode || dry || panic || chkabort || stubgen) return 0; + int level = hint_level(((p >> 40) & 0x1FFFFF) - 1); + if (!level) return 0; + sv = tpack(); tunpack(p); + ok = !dg_dup(); + if (ok) { if (level == 1) dg_open(4); else dg_open(3); ok = dgopen; } + tunpack(sv); + return ok; +} +// error plumbing. errat prints the "zc: line N:" prefix (or opens a record in +// check mode) and counts the error; errend ends the line and gives up after +// 20; giveup prints the total and halts (check mode: sets chkabort, which +// makes the lexer return end of file so the parse unwinds). fatal reports and +// stops at once; serr just reports; die reports and also skips input to the +// next ; or } (panic) so one mistake does not become a screen of errors. +int errat() { + if (cmode) { + dg_open(1); + } else { + rt.put_s("zc: line "); + rt.put_n(tline); + rt.put_s(": "); + } + nerr++; + return 0; +} +// finish an error line; the compiler stops after 20 errors. +int errend() { + if (cmode) { dg_close(); if (nerr >= 20) chkabort = 1; return 0; } + rt.put_c(10); + if (nerr >= 20) { rt.put_s("zc: too many errors, stopping\n"); rt.halt(); } + return 0; +} +// stop compiling after errors and report how many there were. +int giveup() { + if (cmode) { dg_close(); chkabort = 1; return 0; } + rt.put_s("zc: "); rt.put_n(nerr); if (nerr == 1) rt.put_s(" error\n"); else rt.put_s(" errors\n"); rt.halt(); + return 0; +} +// report a fatal error and stop, or record it in check mode. +int fatal(char* msg) { + errat(); rt.put_s(msg); + if (cmode) { dg_close(); chkabort = 1; return 0; } + rt.put_c(10); rt.halt(); + return 0; +} +// report a syntax error at the current token, once per recovery. +int serr(char* msg) { + if (!panic && !dry) { + errat(); + rt.put_s(msg); + errend(); + } + return 0; +} +// report an error that names something, then the message. +int serrn(char* name, char* msg) { + if (!panic && !dry) { + errat(); + rt.put_s(name); + rt.put_s(": "); + rt.put_s(msg); + errend(); + } + return 0; +} +// report an error and skip to the next top-level item. +int die(char* msg) { + serr(msg); + panic = 1; + if (tk == T_EOF) giveup(); + next(); + return 0; +} +// what the parser is looking at, for the "found" part of a message +int tokshow() { + if (tk == T_EOF) { rt.put_s("the end of the file"); return 0; } + if (tk == T_NUM) { rt.put_s("the number "); rt.put_n(tv); return 0; } + if (tk == T_STR) { rt.put_s("a string"); return 0; } + if (tk == T_BSTR) { rt.put_s("a byte string"); return 0; } + if (tk == T_FLT) { rt.put_s("a float"); return 0; } + rt.put_s("'"); rt.put_s(tv); rt.put_s("'"); + return 0; +} +// like die, and says what was found instead +int diefound(char* msg) { + if (!panic && !dry) { + errat(); + rt.put_s(msg); rt.put_s(", found "); tokshow(); + errend(); + } + panic = 1; + if (tk == T_EOF) giveup(); + next(); + return 0; +} +// die with a name shown before the message. +int dien(char* name, char* msg) { + serrn(name, msg); + panic = 1; + if (tk == T_EOF) giveup(); + next(); + return 0; +} +// four classification bits live next to each interned spelling. this works +// for names in later pool chunks too, without indexing by a fixed pool offset. +const int LSW = 1; +word lsets; +int lsmark(int set, int n) { + if (lsets) n[-2] = n[-2] | (1 << set); + return 0; +} +word lsget(int set, int n) { + if (!lsets) return 0; + return (n[-2] >> set) & 1; +} +// name resolution for the language server. while a check runs over the document +// at rsbase, every name a use or a declaration resolves to is written into rsrec +// as a record: the byte offset, kind (RS_LOCAL, RS_PARAM, RS_SYM or RS_FIELD), +// declaration or symbol, and storage facts at that position. a local names its declaration by offset +// (-1 when it has none), a symbol names its kind and flags (see rs_sym). the +// compiler's own scopes decide, so the highlighter and hover see locals and +// parameters first, then globals and built-ins, the way the compiler does. +const int RS_LOCAL = 1, RS_PARAM = 2, RS_SYM = 3, RS_FIELD = 4, RS_CAP = 32768, RS_W = 13; +int lt_on, lt_n, ltcap = 256, ltxtcap = 2048; +word lt_off, lt_at, lt_len, lt_type, ltxt; int ltxn; +int ht_n, htcap = 256; word ht_start, ht_end, ht_type; +int fieldn, fieldcap = 128; word fieldtypes, fieldoffs; char** fieldnames; +int field_note(word t, char* n, int off) { + if (!lt_on || isp != 0 || dry) return 0; + if (!fieldtypes || fieldn == fieldcap) { + if (fieldtypes) fieldcap = fieldcap * 2; + fieldtypes = chk_resize(fieldtypes, fieldn, fieldcap); + fieldnames = chk_resize(fieldnames, fieldn, fieldcap); + fieldoffs = chk_resize(fieldoffs, fieldn, fieldcap); + } + fieldtypes[fieldn] = t; fieldnames[fieldn] = n; fieldoffs[fieldn] = off; fieldn++; + return 0; +} +int field_decl(word t, char* n) { + int k = 0; + while (k < fieldn) { if (fieldtypes[k] == t && fieldnames[k] == n) return fieldoffs[k]; k++; } + return 0 - 1; +} +// where each symbol was defined, for go to definition: sdoff is the byte offset of its name + 1 +// (0 when unknown, as for built-ins) and sdsrc the include path whose text it is in (0 for the document) +word sdoff, sdsrc; +// includes: incdir is the directory of the file being compiled (0 for the program on stdin), increoot the +// workspace folder an editor passes in, inc_hook a function that gives the text of a file the editor +// sent (a host with no files of its own), incres the path an include was found at +char* incdir, increoot, incres, std_buf, icon_buf; char*(char*.m) inc_hook; int std_done, icon_done; +word rsrec, rstmp; word[32] pnoffs; word[32] pnplaces; int rsn, rscap, rsbase, nameoff; +// ownership at a reference: record word 11 holds the local's state (S_* + 1, plus 16 when +// it owns its object) and word 12 the declaration offset (+ 1) of the owner it borrows from. +// transfers are logged as events, EV_W words each: the local's declaration offset, what +// happened (EV_*), its line, and the target's declaration offset (+ 1) when there is one. +const int EV_RETURN = 1, EV_VALUE = 2, EV_UP = 3, EV_DOWN = 4, EV_STORE = 5, EV_GLOBAL = 6, EV_FREE = 7, EV_CAPTURE = 8, EV_MOVE = 9, EV_W = 4; +word evrec; int evn, evcap = 64; +// resize compiler tables. these buffers are collector-managed: m* relinquishes +// scope cleanup, and their local or global pointers keep them visible to gc. +word chk_resize(word old, int count, int capacity) { + word p = m*rt.new(capacity); + p[-4] = p[-4] | rt.F_INTERNAL; + rt.fill_w(p, 0, capacity); + if (old && count) rt.copy_w(p, old, count); + return p; +} +// lexer and documentation buffers grow before storing the next character. +int token_char(int n, int c) { + if (n + 2 > tbufcap) { tbuf = chk_resize(tbuf, n, tbufcap * 2); tbufcap = tbufcap * 2; } + tbuf[n] = c; return n + 1; +} +int doc_char(int c) { + if (dn + 2 > docbufcap) { dbuf = chk_resize(dbuf, dn, docbufcap * 2); docbufcap = docbufcap * 2; } + dbuf[dn] = c; dn++; return 0; +} +int doc_name_room(int need) { + if (need <= docnamecap) return 0; + while (docnamecap < need) docnamecap = docnamecap * 2; + dnmb = chk_resize(dnmb, 0, docnamecap); return 0; +} +// reserve compiler columns at execution time. indices remain valid after a copy. +int reserve_symbols(int need) { + int cap = symcap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + sname = chk_resize(sname, nsym, cap); + skind = chk_resize(skind, nsym, cap); + sval = chk_resize(sval, nsym, cap); + ssig = chk_resize(ssig, nsym, cap); + sflag = chk_resize(sflag, nsym, cap); + sfix = chk_resize(sfix, nsym, cap); + snext = chk_resize(snext, nsym, cap); + stype = chk_resize(stype, nsym, cap); + if (effect_symbols) effect_symbols = chk_resize(effect_symbols, nsym, cap); + if (fnmeta) fnmeta = chk_resize(fnmeta, nsym, cap); + if (ifs) ifs = chk_resize(ifs, symcap, cap); + if (svkind) { svkind = chk_resize(svkind, nsym, cap); svvalue = chk_resize(svvalue, nsym, cap); } + if (sdoff) { sdoff = chk_resize(sdoff, nsym, cap); sdsrc = chk_resize(sdsrc, nsym, cap); } + symcap = cap; return cap; +} +int reserve_locals(int need) { + int cap = loccap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + lname = chk_resize(lname, nloc, cap); + lslot = chk_resize(lslot, nloc, cap); + lhome = chk_resize(lhome, nloc, cap); + lstate = chk_resize(lstate, nloc, cap); + lowner = chk_resize(lowner, nloc, cap); + lused = chk_resize(lused, nloc, cap); + larr = chk_resize(larr, nloc, cap); + lown = chk_resize(lown, nloc, cap); + ltype = chk_resize(ltype, nloc, cap); + ltag = chk_resize(ltag, nloc, cap); + lpos = chk_resize(lpos, nloc, cap); + lreadpos = chk_resize(lreadpos, nloc, cap); + lnsz = chk_resize(lnsz, nloc, cap); + lleaf = chk_resize(lleaf, nloc, cap); + lrsoff = chk_resize(lrsoff, nloc, cap); + lrskind = chk_resize(lrskind, nloc, cap); + lptr = chk_resize(lptr, nloc, cap); + if (if_local) if_local = chk_resize(if_local, nloc, cap); + if (local_origin) local_origin = chk_resize(local_origin, nloc, cap); + if (local_origin_mask) local_origin_mask = chk_resize(local_origin_mask, nloc, cap); + if (local_cell) local_cell = chk_resize(local_cell, nloc, cap); + if (local_captured) local_captured = chk_resize(local_captured, nloc, cap); + if (local_ended) local_ended = chk_resize(local_ended, nloc, cap); + if (local_end_info) local_end_info = chk_resize(local_end_info, nloc, cap); + lafact = chk_resize(lafact, nloc, cap); laaddr = chk_resize(laaddr, nloc, cap); lprev = chk_resize(lprev, nloc, cap); + if (lvkind) { lvkind = chk_resize(lvkind, nloc, cap); lvvalue = chk_resize(lvvalue, nloc, cap); lvescaped = chk_resize(lvescaped, nloc, cap); } + o3cache = chk_resize(o3cache, nloc, cap); + loccap = cap; return cap; +} +int reserve_fixes(int need) { + int cap = fixcap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + fsite = chk_resize(fsite, fixcap, cap); + fnext = chk_resize(fnext, fixcap, cap); + fixcap = cap; return cap; +} +const word R_EXIT_BASE = STYPE_BASE + NS, R_EXITEND = R_EXIT_BASE + EXIT_WORDS; +word R_EXIT = R_EXIT_BASE; int R_EXIT_cap = EXIT_WORDS; +int reserve_R_SNAP(int need) { + int cap = R_SNAP_cap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + R_SNAP = chk_resize(R_SNAP, R_SNAP_cap, cap); R_SNAP_cap = cap; + return cap; +} +int reserve_R_QBUF(int need) { + int cap = R_QBUF_cap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + R_QBUF = chk_resize(R_QBUF, R_QBUF_cap, cap); R_QBUF_cap = cap; + return cap; +} +int reserve_R_SRC(int need) { + int cap = R_SRC_cap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + R_SRC = chk_resize(R_SRC, R_SRC_cap, cap); R_SRC_cap = cap; + return cap; +} +int reserve_R_SEQ(int need) { + int cap = R_SEQ_cap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + R_SEQ = chk_resize(R_SEQ, R_SEQ_cap, cap); R_SEQ_cap = cap; + return cap; +} +int reserve_R_CTX(int need) { + int cap = R_CTX_cap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + R_CTX = chk_resize(R_CTX, R_CTX_cap, cap); R_CTX_cap = cap; + return cap; +} +int reserve_R_LOOP(int need) { + int cap = R_LOOP_cap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + R_LOOP = chk_resize(R_LOOP, R_LOOP_cap, cap); R_LOOP_cap = cap; + if (cap / 8 > loopfacts_cap) { lpfact = chk_resize(lpfact, loopfacts_cap, cap / 8); loopfacts_cap = cap / 8; } + return cap; +} +int reserve_R_EXIT(int need) { + int cap = R_EXIT_cap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + R_EXIT = chk_resize(R_EXIT, R_EXIT_cap, cap); R_EXIT_cap = cap; + return cap; +} +int reserve_binary(int need) { + int cap = bincap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + bname = chk_resize(bname, nbin, cap); + bprec = chk_resize(bprec, nbin, cap); + bseq = chk_resize(bseq, nbin, cap); + blen = chk_resize(blen, nbin, cap); + bincap = cap; return cap; +} +int reserve_unary(int need) { + int cap = uncap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + uname = chk_resize(uname, nun, cap); + useq = chk_resize(useq, nun, cap); + ulen = chk_resize(ulen, nun, cap); + uncap = cap; return cap; +} +int reserve_assignment(int need) { + int cap = asgcap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + aname = chk_resize(aname, nasg, cap); + abin = chk_resize(abin, nasg, cap); + asgcap = cap; return cap; +} +int reserve_operators(int need) { + int cap = kopcap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + kops = chk_resize(kops, nkop, cap); + kopcap = cap; return cap; +} +// write a record for the name at byte offset off. ref is what a local names (its +// declaration's offset, or -1); sym is the symbol a RS_SYM record names, or -1. +int rs_put(int off, int kind, int ref, int sym) { + int at; + if (!rsrec || !cmode || dry || chkind != 3 || chbase != rsbase || off < 0) return 0; + if (rsn >= rscap) { + rsrec = chk_resize(rsrec, rsn * RS_W, rscap * 2 * RS_W); + rstmp = chk_resize(0, 0, rscap * 2 * RS_W); + rscap = rscap * 2; + } + at = rsn * RS_W; + rsrec[at] = off; rsrec[at + 1] = kind; rsrec[at + 2] = ref; rsrec[at + 3] = sym; + rt.fill_w(rsrec + at + 4, 0, RS_W - 4); + rsn++; + return 0; +} +// the ownership of local i at the record for the name at off +int os_at(int off, int i) { + int at, r, k = rsn; + if (!rsn || !cmode || dry || i < 0 || chkind != 3 || chbase != rsbase) return 0; + while (k && rsrec[(k - 1) * RS_W] != off) k--; + if (!k) return 0; + at = (k - 1) * RS_W; + rsrec[at + 11] = lstate[i] + 1; + if (lown[i]) rsrec[at + 11] = rsrec[at + 11] + 16; + if (lstate[i] == S_CHILD) { r = lroot(i); if (lrsoff[r] >= 0) rsrec[at + 12] = lrsoff[r] + 1; } + return 0; +} +// log a transfer of local i; the last few events are searched so a loop body is not logged twice +int ev_put(int i, int kind, int target) { + int k = evn, n = 0, t = 0; + if (!rsrec || !cmode || dry || chkind != 3 || chbase != rsbase || i < 0 || lrsoff[i] < 0) return 0; + if (target >= 0 && lrsoff[target] >= 0) t = lrsoff[target] + 1; + while (k && n < 8) { + k--; n++; + if (evrec[k * EV_W] == lrsoff[i] && evrec[k * EV_W + 1] == kind && evrec[k * EV_W + 2] == tline) return 0; + } + if (!evrec) { evcap = 64; evrec = chk_resize(0, 0, evcap * EV_W); } + if (evn >= evcap) { evrec = chk_resize(evrec, evn * EV_W, evcap * 2 * EV_W); evcap = evcap * 2; } + evrec[evn * EV_W] = lrsoff[i]; evrec[evn * EV_W + 1] = kind; evrec[evn * EV_W + 2] = tline; evrec[evn * EV_W + 3] = t; + evn++; return 0; +} +// write a record for the name token that is current. +int rs_note(int kind, int ref) { return rs_put(toff, kind, ref, 0 - 1); } +// the flags of every symbol record, read again once the check has defined the +// whole document (a function used before its definition is defined by now). +int rs_fix() { + int i = 0; + while (i < rsn) { + if (rsrec[i * RS_W + 1] == RS_SYM && rsrec[i * RS_W + 3] >= 0) { + int s = rsrec[i * RS_W + 3]; rsrec[i * RS_W + 2] = rs_sym(s); + if (skind[s] == K_CONST) vf_at(i, 1, sval[s]); + } + i++; + } + return 0; +} +// what a symbol is, as a record keeps it: its kind, plus 16 for a symbol the +// document declares (not a built-in) and 32 when that symbol has a definition +int rs_sym(int s) { + int v = skind[s]; + if (s >= ubase) v = v | 16; + if (sflag[s] & F_DEF) v = v | 32; + return v; +} +// copies record a of from to record b of to (the merge sort moves records this way). +int rs_move(word from, int a, word to, int b) { + rt.copy_w(to + b * RS_W, from + a * RS_W, RS_W); + return 0; +} +// sort the records by offset: a bottom-up merge sort through rstmp, so a long +// document costs n log n, whatever order its names come in. +int rs_sort() { + int w = 1, lo, mid, hi, i, j, k, n = rsn; + if (n < 2) return 0; + while (w < n) { + lo = 0; + while (lo < n) { + mid = lo + w; if (mid > n) mid = n; + hi = lo + w * 2; if (hi > n) hi = n; + i = lo; j = mid; k = lo; + while (i < mid && j < hi) { + if (rsrec[j * RS_W] < rsrec[i * RS_W]) { rs_move(rsrec, j, rstmp, k); j++; } + else { rs_move(rsrec, i, rstmp, k); i++; } + k++; + } + while (i < mid) { rs_move(rsrec, i, rstmp, k); i++; k++; } + while (j < hi) { rs_move(rsrec, j, rstmp, k); j++; k++; } + lo = hi; + } + i = 0; + while (i < n * RS_W) { rsrec[i] = rstmp[i]; i++; } + w = w * 2; + } + return 0; +} +// analyzer errors only count in checked, reachable code +int cerr(char* msg) { + if (fchk && live) serr(msg); + return 0; +} +// report an analyzer error about local i, named in the message. +int cerrn(int i, char* msg) { + int root = i, cause = 0; word info = 0; + if (fchk && live) { + if (rt.eq_w(msg, "used after it was given away")) { + root = lroot(i); if (local_end_info) info = local_end_info[i]; + if (!info && local_end_info) info = local_end_info[root]; + if (info) cause = info[2]; else if (local_ended) cause = local_ended[root]; + } + if (cause && !panic && !dry) { + errat(); rt.put_s(lname[i]); rt.put_s(": "); rt.put_s(msg); + rt.put_s("; owner "); if (info) rt.put_s(info[0]); else rt.put_s(lname[root]); + rt.put_s(" was transferred on line "); rt.put_n(cause & 0xFFFFFF); + if (info && info[1]) { rt.put_s(" (declared on line "); rt.put_n(info[1] & 0xFFFFFF); rt.put_c(')'); } + errend(); + } else serrn(lname[i], msg); + } + return 0; +} + +// ---- interning ---- + +// the pool: an open addressing hash table (R_IH) of pointers into a string +// pool (R_POOL). each spelling has two spare words: its symbol-chain head at +// p[-1] and editor classification bits at p[-2]. equal text returns the same +// stable handle, which lookup can use directly. +int hashs(char* s) { + int h = 0; + while (*s) { h = h * 31 + *s; s++; } + return h & (hashcap - 1); +} +// rehash the index without moving strings or their symbol-chain heads. +int reserve_names(int need) { + int cap = hashcap, previous = hashcap, i = 0, h; char* p; char** old; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + old = namehash; + namehash = chk_resize(0, 0, cap); + hashcap = cap; + while (i < previous) { + p = old[i]; + if (p) { h = hashs(p); while (namehash[h]) h = (h + 1) & (hashcap - 1); namehash[h] = p; } + i++; + } + return cap; +} +// return the interned copy of a spelling. new chunks preserve every old handle. +char* intern(char* s) { + int h; word p; int n; + if ((namecount + 1) * 2 >= hashcap) reserve_names(hashcap * 2); + h = hashs(s); + while (1) { + p = namehash[h]; + if (!p) break; + if (p[0] == s[0] && rt.eq_w(p, s)) return p; + h = (h + 1) & (hashcap - 1); + } + n = rt.len_w(s); + if (pooltop + n + 3 > poolend) { + if (poolchunk < n + 3) poolchunk = n + 3; + else if (poolchunk < 0x20000000) poolchunk = poolchunk * 2; + p = chk_resize(0, 0, poolchunk + 1); p[0] = poolblocks; poolblocks = p; + pooltop = p + 1; poolend = pooltop + poolchunk; + } + pooltop[0] = 0; pooltop[1] = 0; + p = pooltop + 2; + rt.copy_w(p, s, n + 1); + pooltop = pooltop + n + 3; + namehash[h] = p; namecount++; + return p; +} + +// ---- symbols ---- + +// each interned name keeps the index (+1) of its newest symbol in p[-1], and +// each symbol links to the one it shadows, so lookup is one step. +// remember where symbol s is defined, when the prescan made it before its definition was read +int def_note(int s, int off) { + if (cmode && !dry && !ginst && sdoff && off >= 0 && !sdoff[s]) { sdoff[s] = off + 1; sdsrc[s] = curinc; } + return 0; +} +int addsym(char* name, int kind, int val) { + int i = nsym; + if (module_name && kind != K_KW) name = module_symbol(name); + if (nsym >= symcap) reserve_symbols(nsym + 1); + if (!fnmeta) fnmeta = chk_resize(0, 0, symcap); + nsym++; + sname[i] = name; + skind[i] = kind; + sval[i] = val; + ssig[i] = 0; + sflag[i] = F_DEF; + sfix[i] = 0; + fnmeta[i] = 0; + stype[i] = 0; + snext[i] = name[-1]; + name[-1] = i + 1; + if (cmode && !dry && !sdoff) { sdoff = chk_resize(0, 0, symcap); sdsrc = chk_resize(0, 0, symcap); } + if (sdoff) { + sdoff[i] = 0; sdsrc[i] = 0; + if (cmode && !dry && !ginst && nameoff >= 0) { sdoff[i] = nameoff + 1; sdsrc[i] = curinc; } + } + return i; +} +// while an image builds, names below vislo are hidden, except keywords +int lookup_raw(char* name) { + int i = name[-1] - 1, resident; + if (syntax_host_floor && i >= syntax_host_floor && i < syntax_host_from) { + resident = i; + while (resident >= syntax_host_floor) resident = snext[resident] - 1; + if (resident >= 0) return resident; + if (skind[i] == K_GVAR || skind[i] == K_GARR) serr("a syntax handler cannot access emitted program globals"); + } + while (i >= 0) { + if (i >= vislo || skind[i] == K_KW || skind[i] == K_TYPE) return i; + i = snext[i] - 1; + } + return -1; +} +// qualify a declaration within its file namespace; generated method names retain their prefix. +char* module_symbol(char* name) { + int i = 0, j; char* b; char* q; + while (name[i]) { if (name[i] == '.') return name; i++; } + if (!module_name) return name; + if (((name[0] == 'd' && name[1] == 'o' && name[2] == 'c') || (name[0] == 's' && name[1] == 'i' && name[2] == 'g')) && name[3] == 32) { + q = methname(module_name, name + 4); b = chk_resize(0, 0, rt.len_w(q) + 5); + rt.copy_w(b, name, 4); + j = 0; while (q[j]) { b[j + 4] = q[j]; j++; } b[j + 4] = 0; + return intern(b); + } + return methname(module_name, name); +} +int lookup(char* name) { + int s, i = 0; + if (module_name) { s = lookup_raw(module_symbol(name)); if (s >= 0) return s; } + s = lookup_raw(name); if (s >= 0) return s; + while (name[i]) { if (name[i] == '.') return -1; i++; } + return lookup_raw(methname("rt", name)); +} +// modules loaded from source are tracked independently of each file's imports. +word included_modules; +int module_loaded(char* name) { + word p = included_modules; + while (p) { if (rt.eq_w(p[1], name)) return 1; p = p[0]; } + return 0; +} +int module_known(char* name) { + return module_loaded(name) || rt.eq_w(name, "rt") || rt.eq_w(name, "run") || rt.eq_w(name, "core") || rt.eq_w(name, "lsp") || rt.eq_w(name, "std") || rt.eq_w(name, "icon"); +} +int module_imported(char* name) { + word p = module_imports; + if (module_name && rt.eq_w(name, module_name)) return 1; + while (p) { if (rt.eq_w(p[1], name)) return 1; p = p[0]; } + return 0; +} +// modules reported missing in this unit, so their names are not reported again as undefined +word missing_imports; +// true when name is qualified by a module this unit was already told to include. +int missing_import_name(char* name) { + int i = 0; word p = missing_imports; + while (name[i] && name[i] != '.') i++; + if (!name[i]) return 0; + while (p) { + char* module = p[1]; int k = 0; + while (k < i && module[k] == name[k]) k++; + if (k == i && !module[k]) return 1; + p = p[0]; + } + return 0; +} +// source references require their module's explicit import; compiler-generated calls use lookup directly. +int source_lookup(char* name) { + int i = 0, s; char* owner; + while (name[i] && name[i] != '.') i++; + if (name[i]) { + owner = chk_resize(0, 0, i + 1); rt.copy_w(owner, name, i); owner[i] = 0; + if ((module_known(owner) || module_loaded(owner)) && !module_imported(owner)) { + errat(); rt.put_s("module access requires `include "); rt.put_s(owner); rt.put_s(";`"); errend(); + word link = chk_resize(0, 0, 2); link[0] = missing_imports; link[1] = owner; missing_imports = link; + // the unit already fails; resolving the name anyway keeps later diagnostics about the real code + } + } + if (module_name) { s = lookup_raw(module_symbol(name)); if (s >= 0) return s; } + return lookup_raw(name); +} +// drop the symbols above index to, which ends a scope. +int popsyms(int to) { + while (nsym > to) { nsym--; sname[nsym][-1] = snext[nsym]; } + while (ntd && tdstk[ntd * 3 - 3] >= to) ntd--; // local typedefs made above to went with them + return 0; +} +// look a name up from a plain string. +int lookups(char* s) { return lookup(intern(s)); } + +// patch nodes +int fixnew(int site, int link) { + int i = fixfree; + if (i) fixfree = fnext[i]; + else { nfixu++; i = nfixu; if (i >= fixcap) reserve_fixes(i + 1); } + fsite[i] = site; fnext[i] = link; + return i; +} +// patch every lit in a chain to point at v, and recycle the nodes +int fixall(int head, int v) { + int n; + while (head) { + setlit(fsite[head], v); + n = fnext[head]; + fnext[head] = fixfree; + fixfree = head; + head = n; + } + return 0; +} + +// ---- code emission ---- + +// code goes straight into memory at "here". hdelta shifts where it is written +// (an image build emits at one address and stores at another, see nested +// compiles), and dry turns emission off entirely, which typeof and similar +// queries use to type an expression without producing code. +// reserve contiguous words before recording an address or writing a data block. +// new runtime segments are linked by jumps; earlier code and globals stay put. +int reserve_code(int need) { + word p; int cap, top; + if (dry) return 0; + if (need < 0 || need > 0x7fffffff - 8) fatal("bad code reservation"); + if (image_active) { + if (need <= hlimit - here) return hlimit - here; + cap = image_capacity; + while (cap < here - image_org + need) { + if (cap > (0x7fffffff - image_org) / 2) fatal("image too big"); + cap = cap * 2; + } + image_buffer = chk_resize(image_buffer, here - image_org, cap); + image_capacity = cap; hdelta = image_buffer - image_org; hlimit = image_org + cap; + return hlimit - here; + } + if (need <= hlimit - here - 2) return hlimit - here - 2; + if (here + 1 >= hlimit) fatal("no room for a code link"); + cap = code_chunk; + if (cap < need + 5) cap = need + 5; + p = chk_resize(0, 0, cap); top = p + p[-5] - rt.HDR; + if (rt.syscall(9, 9, top, 0) < top) fatal("code address reservation failed"); + p[0] = rt.code_blocks; p[1] = p + 3; p[2] = top; rt.code_blocks = p; + *here = (p + 3) * 2 + 1; here[1] = O_JMP * 2; + here = p + 3; hlimit = top; + if (cap < 0x20000000) code_chunk = cap * 2; + return hlimit - here - 2; +} +// a region handed to an optimizer must fit in one contiguous segment. +int code_contiguous(int lo, int hi) { + word p = rt.code_blocks; + if (image_active || (lo >= 0 && hi <= rt.CODE_END)) return 1; + while (p) { if (lo >= p[1] && hi <= p[2] - 2) return 1; p = p[0]; } + return 0; +} +int emit(int w) { + if (dry) return 0; + if (w == OP_FLIT * 2) reserve_code(2); else reserve_code(1); + *(here + hdelta) = w; + here++; + return 0; +} +// a vm code word with the low bit set pushes the value in its upper bits +// (v + v + 1); an even word is an opcode (k + k). jmp pushes a target and jumps. +int lit(int v) { + if ((v >> 62) != 0 && (v >> 62) != -1) { + reserve_code(5); + emit((v >> 32) * 2 + 1); emit(0x100000000 * 2 + 1); op(15); + emit((v & 0xFFFFFFFF) * 2 + 1); op(17); + } else emit(v + v + 1); + return 0; +} +// emit one vm opcode. +int op(int k) { emit(k + k); return 0; } +// fill the literal placeholder at address at with value v. +int setlit(int at, int v) { + if (dry) return 0; + if ((v >> 62) != 0 && (v >> 62) != -1) fatal("literal patch too large"); + *(at + hdelta) = v + v + 1; + return 0; +} +// emit a jump to the address t. +int jmp(int t) { + lit(t); + op(O_JMP); + return 0; +} +// a lit that will be patched later; returns its address +int hole() { + int a; + reserve_code(1); a = here; + lit(rt.MW - 1); + return a; +} +// forward references. calling a function that is not defined yet creates a +// symbol with F_DEF clear whose value is the place of its first use (for the +// "never defined" error). each call site emits a placeholder literal and adds +// itself to the symbol's chain (sfix); defining the function runs fixall to +// patch every site. MW-1 is a jump target that halts, so an unpatched call +// stops the machine instead of running garbage. +int fwdfn_at(char* name, int p) { + int s = addsym(name, K_FN, p); + sflag[s] = 0; + return s; +} +// the place of the first use (line, and offset above bit 24), until it's defined +int fwdfn(char* name) { + return fwdfn_at(name, tpack()); +} +// emit a reference to function s, patched later if it is not defined yet. +int fnref(int s) { + if (syntax_host_floor && s >= syntax_host_floor && s < syntax_host_from) { + s = syntax_host_function(s); + if (s < 0) { lit(rt.MW - 1); return 0; } + } + if (sflag[s] & F_DEF) { lit(sval[s]); return 0; } + if (dry) return 0; + reserve_code(1); sfix[s] = fixnew(here, sfix[s]); + lit(rt.MW - 1); // a jump to MW-1 halts if it ever runs unpatched + return 0; +} +// emit a call to the function with this name, making a forward slot if it is not defined. +int callname(char* name) { + int s = lookup(name); + if (s < 0) { name = methname("rt", name); s = lookup(name); } + if (s < 0) s = fwdfn(name); + fnref(s); + op(O_CALL); + return 0; +} +// the address of local slot k: the frame pointer lives in the word ZFP, and a +// local is fp + k. (o3 locals live in cpu registers instead; see vaddr.) +int laddr(int k) { + lit(rt.ZFP); + op(O_LD); + if (k) { lit(k); op(O_ADD); } + return 0; +} +// an o3 variable's address: lfa(k) is fp + k as one enhancement, which the +// jit specializes on the literal k +word vaddr(int i) { + int s; + if (ltag[i] & 16) { + s = lookup(s_lfa); + if (s >= 0 && skind[s] == K_ENH) { + lit(lslot[i]); + op(XOP0 + sval[s]); + return 0; + } + } + laddr(lslot[i]); if (local_cell && local_cell[i]) op(O_LD); return 0; +} +// registers for an o3 region's variables: the cpu has 7. a variable takes the +// one its region freed last (when a scope closed), or the next unused one +// ---- o3: the optimizing region compiler's bookkeeping ---- + +// code inside an o3 region (or a function marked o3) keeps hot locals in the +// cpu's registers. the compiler does not change what it emits for them; it +// records which frame slot maps to which register and hands the finished +// region to the core through install(). the core compiles the whole region +// with those slots held in registers and writes them back to the frame when +// control leaves it. there are 7 registers, so locals past that stay in +// memory. +int o3reset() { + no3free = 0; + o3nreg = 1; + no3sl = 0; + return 0; +} +// a freed register keeps its class. a float home is a d register and an int +// home is an x register, and one var index cannot be both, so a number that +// was a float is only handed out again as a float. +int o3take(int fp) { + int i = 0, k, w = -1; + while (i < no3free) { + k = o3free[i]; + if (fp && k < 0) w = i; + if (!fp && k > 0) w = i; + i++; + } + if (w >= 0) { + k = o3free[w]; + no3free--; + o3free[w] = o3free[no3free]; + if (k < 0) return 0 - k; + return k; + } + if (o3nreg < 8) { o3nreg++; return o3nreg - 1; } + return 0; +} +// true when a type is a plain number that o3 can keep in a register. +int o3plain(word t) { + if (!t) return 0; + if (t[0] == TY_INT || t[0] == TY_CHAR || t[0] == TY_BOOL || t[0] == TY_FLOAT) return 1; + return 0; +} +// move a local into an o3 register when the region can keep it there. +int o3promote(int i) { + int r, fp; + ltag[i] = ltag[i] - (ltag[i] & 1792); + if (!o3on || larr[i] || no3sl >= 32) return 0; + fp = isfl(ltype[i]); + r = o3take(fp); + if (!r) return 0; + ltag[i] = ltag[i] + r * 256; + o3sl[no3sl] = lslot[i]; + if (o3plain(ltype[i])) r = r + 16; // 16 marks a plain number: the core need not write it back before a call + if (fp) r = 0 - r; // the install word is negative when the home is a d register + o3rg[no3sl] = r; no3sl++; + return 0; +} +// a fixed T[?] can't change its items or its length, so o3 code keeps both in +// two hidden variables, loaded when the array variable is set (or when the +// region starts), and indexes through them +int o3mkcache(int i) { + int k; + if (!o3on || ltype[i][0] != TY_DYN || !ltype[i][3] || tbytes(ltype[i][4])) return 0; + k = newloc(s_hitems); fslots++; ltag[k] = 16; o3promote(k); + newloc(s_hlen); fslots++; ltag[k + 1] = 16; o3promote(k + 1); + o3cache[i] = k; + return 0; +} +// reload the o3 register cache of a local from its memory slot. +int o3refresh(int i) { + int k = o3cache[i]; + if (!k) return 0; + vaddr(i); op(O_LD); lit(2); op(O_ADD); op(O_LD); vaddr(k); op(O_ST); + vaddr(i); op(O_LD); op(O_LD); vaddr(k + 1); op(O_ST); + return 0; +} +// hands a region to the cpu: install kind 2 is [-, 2, lo, hi, n, slot, register, ..., fslots, argc, fnstart] +// fslots, argc and the prologue address sit in o3fs, o3na, o3fn0. a bare o3 block leaves the prologue at -1, so a function that starts at address 0 still binds. +int o3region(int lo, int hi, word sl, word rg, int n) { + int i = 0; + if (hdelta || lo >= hi || !code_contiguous(lo, hi)) return 0; + o3dsc[0] = 0; o3dsc[1] = 2; o3dsc[2] = lo; o3dsc[3] = hi; o3dsc[4] = n; + while (i < n) { o3dsc[5 + i + i] = sl[i]; o3dsc[6 + i + i] = rg[i]; i++; } + o3dsc[5 + n + n] = o3fs; o3dsc[6 + n + n] = o3na; o3dsc[7 + n + n] = o3fn0; + rt.install(o3dsc); + return 0; +} +// o3 blocks go to the cpu once their function is done, so every jump in them is final +int o3flush() { + int i = 0; word p; + o3fs = 0; o3na = 0; o3fn0 = -1; /* -1 is not a function; a prologue at address 0 is */ + while (i < no3pend) { p = o3pend + i * 67; o3region(p[0], p[1], p + 3, p + 35, p[2]); i++; } + no3pend = 0; + return 0; +} +// a string literal: the characters (one per word, then a 0) are emitted into +// the code stream behind a jump, and the literal's value is their address +int strlit(char* s) { + int j = hole(), a; + op(O_JMP); + reserve_code(rt.len_w(s) + 1); a = here; + while (*s) { emit(*s); s++; } + emit(0); + setlit(j, here); + lit(a); + return 0; +} + diff --git a/src/compiler/builtins.zc b/src/compiler/builtins.zc new file mode 100644 index 0000000..53d8597 --- /dev/null +++ b/src/compiler/builtins.zc @@ -0,0 +1,1423 @@ +// memdebug on; checks and interior pinning. memdebug scrub; also poisons frees. +// memdebug off; stops both. at the top level, start() applies it before main. +// ---- statements that only configure the compiler ---- + +int kw_memdebug() { + int f = 0; + stmtonly(); + next(); + if (is_id(s_on)) { mdbg = 1; f = 5; } + else if (is_id(s_scrub)) { mdbg = 1; f = 7; } + else if (is_id(s_off)) { mdbg = 0; f = 0; } + else die("memdebug on, memdebug scrub or memdebug off"); + next(); + expect(s_semi); + if (infn) { lit(f); callname(s_memset); op(O_DROP); } + else mdbg_flags = f; + return 0; +} +// enum name { A, B = 5, C }; the names are global constants, the name is an int type +// the type of an enum: an int record that the typechecker keeps apart from the +// plain ints. t[6] bit 4 marks it; the members carry it in stype +word tenum(char* n) { word t = tnew(TY_INT, 1, n, 0); t[6] = 16; return t; } +// true for a type made by an enum declaration. +word isen(word t) { return t[0] == TY_INT && (t[6] & 16); } +// enum name { A, B = 5 }: each member is a constant, plain (A) and scoped (name.A) +// member records for tagged enums and for plain enums (pm_en is -1 for a plain +// member). a member's payload fields are at most 8. +// the text a tagged enum or an extern stub is generated into +char[GENERATED_WORDS] ebuf_initial; char* ebuf = ebuf_initial; int ebcap = GENERATED_WORDS, ebn; +char*[ENUM_MEMBERS] pm_name_initial, pm_en_initial; word[ENUM_MEMBERS] pm_tag_initial, pm_nf_initial, pm_val_initial, pm_exp_initial; +char** pm_name = pm_name_initial, pm_en = pm_en_initial; word pm_tag = pm_tag_initial, pm_nf = pm_nf_initial, pm_val = pm_val_initial, pm_exp = pm_exp_initial; int pmcap = ENUM_MEMBERS, npm; +char*[ENUM_MEMBERS * 8] pm_fnm_initial; word[ENUM_MEMBERS * 8] pm_ft_initial, pm_off_initial; +char** pm_fnm = pm_fnm_initial; word pm_ft = pm_ft_initial, pm_off = pm_off_initial; +word[TAGGED_ENUMS] pe_st_initial, pe_first_initial, pe_n_initial; char*[TAGGED_ENUMS] pe_name_initial; +word pe_st = pe_st_initial, pe_first = pe_first_initial, pe_n = pe_n_initial; char** pe_name = pe_name_initial; int pecap = TAGGED_ENUMS, npe; +// grow whole member rows, keeping their payload-field indices stable. +int reserve_enum_members(int need) { + int cap = pmcap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + pm_name = chk_resize(pm_name, npm, cap); pm_en = chk_resize(pm_en, npm, cap); + pm_tag = chk_resize(pm_tag, npm, cap); pm_nf = chk_resize(pm_nf, npm, cap); + pm_val = chk_resize(pm_val, npm, cap); pm_exp = chk_resize(pm_exp, npm, cap); + pm_fnm = chk_resize(pm_fnm, npm * 8, cap * 8); pm_ft = chk_resize(pm_ft, npm * 8, cap * 8); pm_off = chk_resize(pm_off, npm * 8, cap * 8); + pmcap = cap; return cap; +} +// grow the tagged-enum index independently of its member rows. +int reserve_tagged_enums(int need) { + int cap = pecap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + pe_st = chk_resize(pe_st, npe, cap); pe_first = chk_resize(pe_first, npe, cap); + pe_n = chk_resize(pe_n, npe, cap); pe_name = chk_resize(pe_name, npe, cap); + pecap = cap; return cap; +} +// a field's c-like spelling in a generated struct +char* ftext(int t) { if (t == tint) return "int"; if (t == tfloat) return "float"; return "let"; } +// the member named n of a tagged enum, or -1 +int pm_find(char* n) { + int gi = npm - 1; + while (gi >= 0) { if (pm_name[gi] == n && pm_en[gi] >= 0) return gi; gi--; } + return -1; +} +// the tagged enum a type is a pointer to, plus one; 0 for any other type +int ptagged(word t) { + int e = 0; + if (!t || t[0] != TY_PTR) return 0; + while (e < npe) { if (t[4] == pe_st[e]) return e + 1; e++; } + return 0; +} +// enum name { A, B = 5 } is a plain enum: each member is a constant, plain (A) +// and scoped (name.A). a member with a block of fields makes it a tagged enum +// (see enum_tagged): enum name { circle { int r; }, dot }. +int kw_enum() { + char* n; char* en = 0; int first = npm, cnt = 0, tagged = 0, gi, nf, v = 0, et = 0, sk, ft; char* fname; + stmtonly(); + next(); + if (tk == T_ID) { en = tv; next(); } + expect(s_lb); + while (!is_op(s_rb)) { + if (tk == T_EOF) { die("missing }"); break; } + reserve_enum_members(npm + 1); + gi = npm; npm++; + n = name(); + pm_name[gi] = n; pm_tag[gi] = cnt; pm_nf[gi] = 0; pm_en[gi] = -1; pm_val[gi] = 0; pm_exp[gi] = 0; + if (is_op(s_lb)) { + tagged = 1; next(); + while (!is_op(s_rb)) { + if (tk == T_EOF) { die("missing }"); break; } + if (pm_nf[gi] >= 8) { fatal("too many fields in a member"); break; } + if (is_id(s_var)) { next(); ft = tword; } + else { rettype(); ft = dty; if (dtag) serr("a payload field has no tag"); } + if (ft != tint && ft != tfloat && ft != tword) serr("a payload field is int, float or let"); + fname = name(); + nf = gi * 8 + pm_nf[gi]; + pm_fnm[nf] = fname; pm_ft[nf] = ft; pm_nf[gi]++; + expect(s_semi); + } + expect(s_rb); + } else if (accept(s_eq)) { pm_val[gi] = cexpr(); pm_exp[gi] = 1; } + cnt++; + if (!accept(s_comma)) break; + if (is_op(s_rb)) break; + } + expect(s_rb); + expect(s_semi); + if (tagged) return enum_tagged(en, first, cnt); + if (en) { et = tenum(en); addsym(en, K_TYPE, et); } + for (gi = first; gi < npm; gi++) { + if (pm_exp[gi]) v = pm_val[gi]; + sk = addsym(pm_name[gi], K_CONST, v); + stype[sk] = et; + if (en) { sk = addsym(methname(en, pm_name[gi]), K_CONST, v); stype[sk] = et; } + v++; + } + return 0; +} +// a tagged enum: one struct, with the tag as word 0 and the fields of every +// member after it, each named member_field. a member's constructor is a static +// function on the enum, en.member(fields...), that allocates the struct, sets +// the tag and the member's fields, and returns it owned. the struct is generated +// as source and compiled here, so its methods are ordinary zc. +int enum_tagged(char* en, int first, int cnt) { + int gi, j, nf, w = 1; word buf, oldimports = module_imports; int sk; + if (!en) { serr("a tagged enum needs a name"); return 0; } + reserve_tagged_enums(npe + 1); + if (cnt > 63) { fatal("a tagged enum has at most 63 members"); return 0; } + for (gi = first; gi < npm; gi++) if (pm_exp[gi]) serr("a tagged enum's members take no explicit values"); + ebn = 0; ebuf[0] = 0; + ebs("struct "); ebs(en); ebs(" { int tag; "); + for (gi = first; gi < npm; gi++) { + for (j = 0; j < pm_nf[gi]; j++) { + nf = gi * 8 + j; + pm_off[nf] = w; w++; + ebs(ftext(pm_ft[nf])); ebs(" "); ebs(pm_name[gi]); ebs("_"); ebs(pm_fnm[nf]); ebs("; "); + } + } + for (gi = first; gi < npm; gi++) { + ebs("static "); ebs(en); ebs("*.u "); ebs(pm_name[gi]); ebs("("); + for (j = 0; j < pm_nf[gi]; j++) { + nf = gi * 8 + j; + if (j) ebs(", "); + if (pm_ft[nf] == tint) ebs("int "); else if (pm_ft[nf] == tfloat) ebs("float "); + ebs(pm_fnm[nf]); + } + ebs(") { "); ebs(en); ebs("* o = rt.new("); ebs(en); ebs("); o.tag = "); ebn10(pm_tag[gi]); ebs("; "); + for (j = 0; j < pm_nf[gi]; j++) { + nf = gi * 8 + j; + ebs("o."); ebs(pm_name[gi]); ebs("_"); ebs(pm_fnm[nf]); ebs(" = "); ebs(pm_fnm[nf]); ebs("; "); + } + ebs("return u*o; } "); + } + ebs("}"); + buf = rt.new(ebn + 14); + rt.copy_w(buf, "include rt; ", 12); + rt.copy_w(buf + 12, ebuf, ebn + 1); + src_push(2, buf, 0, 0); + next(); + stubgen = 1; + while (tk != T_EOF) topitem(); + stubgen = 0; + src_pop(); + module_imports = oldimports; + rt.free(d*buf); + sk = lookup(en); + if (sk < 0 || skind[sk] != K_TYPE) { serr("the tagged enum didn't make its struct"); return 0; } + pe_st[npe] = sval[sk]; pe_first[npe] = first; pe_n[npe] = cnt; pe_name[npe] = en; + for (gi = first; gi < npm; gi++) { + pm_en[gi] = npe; + sk = addsym(pm_name[gi], K_CONST, pm_tag[gi]); + stype[sk] = 0; + } + npe++; + return 0; +} +// a switch over a tagged enum that leaves out a member, with no default, is a +// warning, on the line of the switch (sl). e is the enum, cov the bit set of +// the tags the switch covers. +int cover_warn(int e, int cov, int sl) { + int k, gi, first = 1; + if (dry || panic || cmode) return 0; + for (k = 0; k < pe_n[e]; k++) { + if (k < 63 && ((cov >> k) & 1)) continue; + if (first) { rt.put_s("zc: line "); rt.put_n(sl); rt.put_s(": warning: this switch doesn't cover "); first = 0; } + else rt.put_s(", "); + gi = pe_first[e] + k; + rt.put_s(pm_name[gi]); + } + if (!first) rt.put_c(10); + return 0; +} +// ---- include ---- + +// the text of an embedded library, one character to a word and ending in 0, as readfile gives a file. +// it is unpacked from the byte string the first time it is asked for. +char* bstr_text(bstr p) { + int n = rt.len_s(p), i = 0; char* b = m*rt.new(n + 2); + while (i < n) { b[i] = rt.at_s(p, i); i++; } + b[n] = 0; + return b; +} +// the embedded std module. +char* std_text() { + if (!std_buf) std_buf = bstr_text(std_source()); + return std_buf; +} +// the embedded icon module. +char* icon_text() { + if (!icon_buf) icon_buf = bstr_text(icon_source()); + return icon_buf; +} +// a path with its . and .. segments and doubled slashes taken out: /a/b/../c/./d.zc is /a/c/d.zc +char*.u inc_norm(char*.m p) { + int n = rt.len_w(p), i = 0, k = 0, s, e, j, isabs = 0; char* o; + o = rt.new(n + 4); + if (p[0] == '/') isabs = 1; + while (i < n) { + while (i < n && p[i] == '/') i++; + s = i; + while (i < n && p[i] != '/') i++; + e = i; + if (e == s) continue; + if (e - s == 1 && p[s] == '.') continue; + if (e - s == 2 && p[s] == '.' && p[s + 1] == '.') { + j = k; + while (j > 0 && o[j - 1] != '/') j--; + // the last segment is o[j..k); a leading .. of a relative path stays + if (k > 0 && !(k - j == 2 && o[j] == '.' && o[j + 1] == '.')) { + if (j > 0) k = j - 1; else k = 0; + continue; + } + if (k == 0 && isabs) continue; + } + if (k > 0 || isabs) { o[k] = '/'; k++; } + j = s; while (j < e) { o[k] = p[j]; k++; j++; } + } + if (k == 0 && isabs) { o[0] = '/'; k = 1; } + o[k] = 0; + return o; +} +// dir, a slash and path +char*.u inc_cat(char*.m dir, char*.m path) { + int a = rt.len_w(dir), b = rt.len_w(path); char* o; + o = rt.new(a + b + 3); + rt.copy_w(o, dir, a); + o[a] = '/'; + rt.copy_w(o + a + 1, path, b); + o[a + b + 1] = 0; + return o; +} +// the directory part of a path, without the last slash, or 0 when there is none +char*.u inc_dir(char*.m p) { + int n = rt.len_w(p), k; char* o; + k = n; + while (k > 0 && p[k - 1] != '/') k--; + if (k == 0) return 0; + if (k > 1) k--; + o = rt.new(k + 2); + rt.copy_w(o, p, k); + o[k] = 0; + return o; +} +// the text of the file at path: the editor's copy if it sent one, else the file +char*.u inc_try(char*.m path) { + if (inc_hook) { + char* text = inc_hook(path); + if (text) { + int n = rt.len_w(text); char* copy = rt.new(n + 1); + rt.copy_w(copy, text, n + 1); return copy; + } + } + char* copy = rt.readfile(path); + return copy; +} +// find an include: next to the file that has it, then in the workspace folder, then as written (relative to the +// working directory). incres is where it was found +char*.u inc_open(char*.m path) { + incres = inc_norm(path); + if (path[0] == '/' || path[1] == ':') { + char* c = inc_norm(path), b = inc_try(c); + if (b) incres = c; + return b; + } + if (incdir) { + char* combined = inc_cat(incdir, path); char* c = inc_norm(combined), b = inc_try(c); + if (b) { incres = c; return b; } + } + if (increoot) { + char* combined = inc_cat(increoot, path); char* c = inc_norm(combined), b = inc_try(c); + if (b) { incres = c; return b; } + } + return inc_try(path); +} +// compile the text buf as if it were written here, signatures first. name is what hovers and definitions call it +// and dir the directory its own includes are found from +int inc_compile(char* buf, char* name, char*.d dir) { + char* oldname = curinc; word olddir = incdir; + char* oldmodule = module_name; word oldimports = module_imports; + module_imports = 0; module_name = 0; + prescan(2, buf); + src_push(2, buf, 0, 0); + curinc = name; incdir = dir; incdepth++; + next(); + while (tk != T_EOF) topitem(); + curinc = oldname; incdir = olddir; incdepth--; + src_pop(); + module_name = oldmodule; module_imports = oldimports; + return 0; +} +// include "path"; reads the file and compiles it here, signatures first +// include std; does the same with the standard library, which is part of the compiler +int kw_include() { + int pp, sv; char* path; char* buf; + stmtonly(); + next(); + if (tk == T_ID) { + while (1) { + pp = tpack(); path = tv; + if (tk != T_ID) { die("expected a module name in include"); return 0; } + next(); + if (!module_imported(path)) { + word link = chk_resize(0, 0, 2); link[0] = module_imports; link[1] = path; module_imports = link; + } + if (path == s_std && !std_done) { + std_done = 1; if (incdepth == 0) incplace = pp; + inc_compile(std_text(), "tinyvm:std.zc", 0); + } + if (path == s_icon && !icon_done) { + icon_done = 1; if (incdepth == 0) incplace = pp; + inc_compile(icon_text(), "tinyvm:icon.zc", 0); + } + if (!module_known(path) && !module_loaded(path)) { + char* filename = inc_cat("src/lib", path); int length = rt.len_w(filename); + char* full = chk_resize(0, 0, length + 4); rt.copy_w(full, filename, length); rt.copy_w(full + length, ".zc", 4); + buf = m*inc_open(full); + if (!buf) { sv = tpack(); tunpack(pp); serr("can't read that module include"); tunpack(sv); return 0; } + word link = chk_resize(0, 0, 2); link[0] = included_modules; link[1] = path; included_modules = link; + if (incdepth == 0) incplace = pp; + inc_compile(buf, incres, inc_dir(incres)); + } + if (!accept(s_comma)) break; + } + expect(s_semi); return 0; + } + if (tk != T_STR) { die("include takes a path in quotes, or std"); return 0; } + pp = tpack(); + path = tv; + next(); + expect(s_semi); + buf = m*inc_open(path); + if (!buf) { + sv = tpack(); tunpack(pp); + serr("can't read that include"); + tunpack(sv); + return 0; + } + if (incdepth == 0) incplace = pp; + inc_compile(buf, incres, inc_dir(incres)); + return 0; +} +// embed "path" as [type] name; declares name with the bytes of the file at path, read +// when the unit is compiled. the path is found the way an include's is. with no type, or +// bstr, name is a byte string of the file; char* gives one character to a word; a number +// type takes the bytes little-endian (a float or f32 needs exactly its size); T[] and +// T[n] lay the bytes out as the array's elements, packed for the sized types, one byte +// to an element for char[]. at the top level name is a global, in a function a local. +int kw_embed() { + int pp, off, n, t = 0, i, w, count, k, a, j, v, sz, skip = -1; char* path; word data; + stmtonly(); + next(); + pp = tpack(); + if (tk != T_STR) { die("embed takes a path in quotes: embed \"file\" as name;"); return 0; } + path = tv; next(); + if (!is_id(s_as)) { die("expected as: embed \"file\" as [type] name;"); return 0; } + next(); + peekat(0); + if (!(tk == T_ID && pk_tk == T_OP && pk_tv == s_semi)) t = ptype(); + off = toff; n = name(); + expect(s_semi); + data = m*embed_open(path); + if (!data) { embed_fail(pp, "can't read the file named by embed"); data = chk_resize(0, 0, 1); } + sz = data[0]; + if (!t) t = tbstr; + // in a function the data sits in the code stream, so the code jumps over it + if (infn && (t == tbstr || t == tptr(tchar) || t[0] == TY_ARR)) { reserve_code(2); skip = hole(); op(O_JMP); } + if (t == tbstr || t == tptr(tchar)) { + if (t == tbstr) { a = embed_bstr(data); } + else { reserve_code(sz + 1); a = here; i = 0; while (i < sz) { emit(data[1 + i]); i++; } emit(0); } + if (skip >= 0) setlit(skip, here); + return embed_bind(n, off, t, a, 0); + } + if (t[0] == TY_ARR) { + w = embed_width(t[4]); + if (!w) { embed_fail(pp, "embed declares a bstr, a char*, a number, or an array of numbers"); if (skip >= 0) setlit(skip, here); return embed_bind(n, off, tword, 0, 2); } + if (sz % w) { embed_fail(pp, "the embedded file's size is not a whole number of array elements"); if (skip >= 0) setlit(skip, here); return embed_bind(n, off, tword, 0, 2); } + count = sz / w; + if (t[3] >= 0 && count > t[3]) { embed_fail(pp, "the embedded file is larger than the array it is declared as"); if (skip >= 0) setlit(skip, here); return embed_bind(n, off, tword, 0, 2); } + if (t[3] < 0) t = tarr(t[4], count); + k = tsz(t); reserve_code(k); a = here; i = 0; + if (t[4][0] == TY_CHAR) { while (i < k) { if (i < sz) emit(data[1 + i]); else emit(0); i++; } } + else { + while (i < k) { + v = 0; j = 0; + while (j < 8) { if (i * 8 + j < sz) v = v | (data[1 + i * 8 + j] << (j * 8)); j++; } + emit(v); i++; + } + } + if (skip >= 0) setlit(skip, here); + return embed_bind(n, off, t, a, 1); + } + w = embed_width(t); + if (!w) { embed_fail(pp, "embed declares a bstr, a char*, a number, or an array of numbers"); return embed_bind(n, off, tword, 0, 2); } + if (sz > w) { embed_fail(pp, "the embedded file is larger than the type it is declared as"); return embed_bind(n, off, tword, 0, 2); } + if (isfl(t) && sz != w) { embed_fail(pp, "an embedded float takes exactly as many bytes as its type"); return embed_bind(n, off, tword, 0, 2); } + v = 0; i = 0; + while (i < sz) { v = v | (data[1 + i] << (i * 8)); i++; } + if (isfl(t) && w == 4) v = rt.s2f(v); + else v = cnorm(t, v); + return embed_bind(n, off, t, v, 2); +} +// report msg at the embed declaration that starts at the token packed in pp. the +// caller still declares the name, as a plain 0, so its uses are not reported too. +int embed_fail(int pp, char* msg) { + int sv = tpack(); + tunpack(pp); serr(msg); tunpack(sv); + return 0; +} +// the bytes one element of type t takes in an embedded file: the packed size of a +// sized type or f32, 1 for char, 8 for the other number types, 0 when t is not a number. +int embed_width(word t) { + if (tbytes(t)) return tbytes(t); + if (t[0] == TY_CHAR) return 1; + if (t[0] == TY_INT || t[0] == TY_WORD || t[0] == TY_BOOL || t[0] == TY_FLOAT) return 8; + return 0; +} +// the bytes of block data ([n, b0, b1, ...]) as byte string storage in the code stream. +int embed_bstr(word data) { + int n = data[0], a, i = 0, w, k; + reserve_code((n >> 3) + 2); a = here; + emit(n); + while (i < n) { + w = 0; k = 0; + while (k < 8 && i < n) { w = w | (data[1 + i] << (k * 8)); k++; i++; } + emit(w); + } + if (!(n & 7)) emit(0); + return a; +} +// declare name of type t. kind 0: a is the address of the data, which becomes the value; +// kind 1: a is an array's storage; kind 2: a is the value itself. a global keeps the +// value in its slot; a local is initialized from it, and a local array copies the storage. +int embed_bind(int n, int off, word t, int a, int kind) { + int s, i; + if (!infn) { + if (kind == 1) { s = addsym(n, K_GARR, a); } + else { reserve_code(1); s = addsym(n, K_GVAR, here); emit(a); } + rs_put(off, RS_SYM, rs_sym(s), s); stype[s] = t; + lt_note(off, t); + return 0; + } + i = newloc(n); lrsoff[i] = off; rs_note(RS_LOCAL, off); + ltag[i] = LDECL; lpos[i] = tpack(); lnsz[i] = 0; lleaf[i] = 0; ltype[i] = t; + if (kind == 1) { + larr[i] = 1; fslots = fslots + tsz(t); capture_cell(i, 0); + vaddr(i); lit(a); lit(tsz(t)); callname(s_copy_w); op(O_DROP); + } else { + fslots++; if (isfl(t)) setflt(i, 1); capture_cell(i, 0); + lit(a); vaddr(i); op(O_ST); + setstr(i, t == tbstr); + } + lt_note(off, t); + return 0; +} +// the bytes of an embedded file as [n, b0, b1, ...], found next to the including file, +// then in the workspace folder, then as written, like an include. 0 when none opens. +word.u embed_open(char*.m path) { + if (path[0] != '/' && incdir) { char* combined = inc_cat(incdir, path); char* c = inc_norm(combined); word b = rt.readbytes(c); if (b) return b; } + if (path[0] != '/' && increoot) { char* combined = inc_cat(increoot, path); char* c = inc_norm(combined); word b = rt.readbytes(c); if (b) return b; } + word b = rt.readbytes(path); + return b; +} +// file namespace and imports are restored when an included source ends. +int kw_namespace() { + next(); + if (is_op(s_semi)) module_name = 0; + else { + module_name = tv; name(); + if (!module_known(module_name)) { word link = chk_resize(0, 0, 2); link[0] = included_modules; link[1] = module_name; included_modules = link; } + } + module_imports = 0; expect(s_semi); return 0; +} +// typecheck on; / typecheck off; +int kw_typecheck() { + next(); + if (is_id(s_on)) { + tc_on = 1; + } else if (is_id(s_off)) tc_on = 0; else die("typecheck on or typecheck off"); + next(); expect(s_semi); + return 0; +} +// docs on; keeps the doc comments of what follows in the image, docs off; stops +int kw_docs() { + next(); + if (is_id(s_on)) { + docs_on = 1; + } else if (is_id(s_off)) docs_on = 0; else die("docs on or docs off"); + next(); expect(s_semi); + return 0; +} +// hints on; / hints small; / hints off; from here to the end of the document (or the next such line) +int kw_hints() { + int level = 2, at = toff; + next(); + if (is_id(s_on)) level = 2; else if (is_id(s_small)) level = 1; else if (is_id(s_off)) level = 0; + else { die("hints on, hints small or hints off"); return 0; } + hint_set(level, at); + next(); expect(s_semi); + return 0; +} +// the doc symbol of a name: the symbol "doc name" that holds its byte string, +// or -1. the chain is walked by hand because lookup hides what is below vislo +int docsym(char* nm) { + int i, k = 0; char* p = "doc "; + doc_name_room(rt.len_w(nm) + 5); + while (p[k]) { dnmb[k] = p[k]; k++; } + i = 0; while (nm[i]) { dnmb[k] = nm[i]; k++; i++; } + dnmb[k] = 0; + nm = intern(dnmb); + i = nm[-1] - 1; + while (i >= 0) { + if (skind[i] == K_DOC) return i; + i = snext[i] - 1; + } + return -1; +} +// the packed documentation of a name (a byte string) or 0 +bstr docofs(char* nm) { + int s = docsym(nm); + if (s < 0) return 0; + return sval[s]; +} +// the documentation of a function, by address +bstr docof(int f) { + int i = nsym; + while (i) { + i--; + if (skind[i] == K_FN && sval[i] == f && (sflag[i] & F_DEF)) return docofs(sname[i]); + } + return 0; +} +// documentation uses stable packed objects. symbols keep them alive until an +// image copies their bytes after its code; ordinary docofs calls work too. +bstr doc_pack(char* text, int length) { + int i = 0, k, c, w, nw = (length + 7) >> 3; word p = chk_resize(0, 0, nw + 2); + p[-4] = p[-4] | rt.F_SHARE | rt.F_INTERNAL; p[0] = length; + while (i < length) { + w = 0; k = 0; + while (k < 8 && i < length) { c = text[i]; if (c > 255) c = 63; w = w | (c << (k * 8)); k++; i++; } + p[1 + ((i - 1) >> 3)] = w; + } + return p; +} +// section headings are organizational text, never documentation for a symbol. +int doc_section(char* text, int length) { + int start = 0, end, a, b; + while (start < length) { + end = start; while (end < length && text[end] != 10) end++; + a = start; b = end; + while (a < b && (text[a] == 32 || text[a] == 9 || text[a] == 13)) a++; + while (b > a && (text[b - 1] == 32 || text[b - 1] == 9 || text[b - 1] == 13)) b--; + if (b - a >= 4 && text[a] == '-' && text[a + 1] == '-' && text[b - 1] == '-' && text[b - 2] == '-') return 1; + start = end + 1; + } + return 0; +} + +// attach the doc comment above a declaration at name n. +int doc_attach(char* n) { + int i = 0, w, k, c, b, nw; + if (!dsn || !n || dry || ginst || doc_section(dsnap, dsn)) return 0; + b = doc_pack(dsnap, dsn); + doc_name_room(rt.len_w(n) + 5); + i = 0; while ("doc "[i]) { dnmb[i] = "doc "[i]; i++; } + k = 0; while (n[k]) { dnmb[i] = n[k]; i++; k++; } + dnmb[i] = 0; + addsym(intern(dnmb), K_DOC, b); + dsn = 0; + return 0; +} +// the signature of a function as source text, "int put_c(int ch)", kept in the +// image as the symbol "sig name" when docs are on, so the language server can +// show it for any genome function, with or without a doc comment. the text is +// printed through put_c with the output sent to sgb (the same redirect the +// check uses for a parse word that prints) +char[400] sgb_initial; char* sgb = sgb_initial; int sigcap = 400; +int sig_print(char* n, int rty, int np, char** pn, word pty, int typed) { + int i = 0; + if (typed) { tprint(rty); rt.put_c(' '); } else rt.put_s("fn "); + rt.put_s(n); rt.put_c('('); + while (i < np) { + if (i) rt.put_s(", "); + if (typed) { tprint(pty[i]); rt.put_c(' '); } + rt.put_s(pn[i]); i++; + } + rt.put_c(')'); return 0; +} +// attach a complete signature, measured before capture. +int sig_attach(char* n, int rty, int np, char** pn, word pty, int typed) { + int i = 0, k, b, nw, sm, sb, sn, sc, sl, w, c; + if (!docs_on || dry || ginst || ntp || panic) return 0; + sm = rt.outmode; sb = rt.outbuf; sn = rt.outn; sc = rt.outcap; + rt.outmode = 3; rt.outn = 0; sig_print(n, rty, np, pn, pty, typed); + if (rt.outn > sigcap) { while (rt.outn > sigcap) sigcap = sigcap * 2; sgb = chk_resize(sgb, 0, sigcap); } + rt.outmode = 2; rt.outbuf = sgb; rt.outn = 0; rt.outcap = sigcap; + sig_print(n, rty, np, pn, pty, typed); + sl = rt.outn; + rt.outmode = sm; rt.outbuf = sb; rt.outn = sn; rt.outcap = sc; + b = doc_pack(sgb, sl); + doc_name_room(rt.len_w(n) + 5); + i = 0; while ("sig "[i]) { dnmb[i] = "sig "[i]; i++; } + k = 0; while (n[k]) { dnmb[i] = n[k]; i++; k++; } + dnmb[i] = 0; + addsym(intern(dnmb), K_DOC, b); + return 0; +} +// the field of a struct type, or -1 +// ---- built-ins that look like functions ---- + +// sizeof and typeof take a type or an expression; the expression form is +// compiled with dry set so it contributes only its type. len, cap, push, pop +// and resize work on growable arrays (T[?] handles in rt.zc); arrarg parses +// their first argument and remembers which local it was, so the o3 and +// bounds-proof machinery can see it. +int tfindfield(word t, char* n) { + int i = 0; + tresolve(t); + while (i < t[3]) { + if (t[4][i * 3] == n) return i; + i++; + } + return -1; +} +// sizeof(T or expr), typeof(T or expr): the expression form is compiled dry, so it never runs +int typearg() { + int t, sb, c, ob; + expect(s_lp); + if (tstart() && !is_op(s_dot)) t = ptype(); + else { + sb = snap_push(); ob = xbor; dry++; + c = expr(); rval(); + t = xty; + dry--; xbor = ob; snap_restore(sb); snap_pop(sb); + } + expect(s_rp); + return t; +} +// len(a), cap(a), push(a, x), pop(a), resize(a, n): growable arrays. +// len of a T[n] is just n. +word arrvalue(int c) { + argloc = -1; arrloc = -1; + if (lv && lvk == LV_LOCAL && xty[0] == TY_DYN) { + arrloc = lvi; + if (o3on && o3cache[lvi]) argloc = lvi; + } + effect_value = effect_mask(c); rval(); plainuse(c); af_access(xaf, tpack()); + return xty; +} +// parse an array argument and retain the receiver's mutation and bounds facts. +word arrarg() { + int c; + expect(s_lp); c = expr(); + return arrvalue(c); +} +// report an error when a fixed array is asked to grow or shrink. +int nogrow(word t) { + require_mutable(t); + if (t[0] == TY_ARR || (t[0] == TY_DYN && t[3])) serr("a fixed array can't grow or shrink"); + return 0; +} +// compile len(a): the length of an array. +int array_len(word t) { + int a = arrloc; + xcls = C_PLAIN; xaf = 0; xbor = -1; effect_value = 0; + expect(s_rp); + if (xstr) { + callname(s_len_s); + xty = tint; + xstr = 0; + xfloat = 0; + xlenloc = -1; + xstamp = 0; + return 0; + } + if (t[0] == TY_ARR) { op(O_DROP); lit(t[3]); } + else { tcneed(t[0] == TY_DYN || t == tword, "len needs an array", t); if (argloc >= 0) { op(O_DROP); vaddr(o3cache[argloc] + 1); op(O_LD); } else if (o3on && t[0] == TY_DYN) op(O_LD); else callname(s_dlen); } + xty = tint; + xlenloc = -1; xstamp = 0; + if (a >= 0 && t[0] == TY_DYN && t[3]) xlenloc = a; // the whole expression is len of a fixed array + return 0; +} +int kw_len() { next(); return array_len(arrarg()); } +// compile cap(a): the capacity of a growable array. +int array_cap(word t) { + xcls = C_PLAIN; xaf = 0; xbor = -1; effect_value = 0; + expect(s_rp); + if (t[0] == TY_ARR) { op(O_DROP); lit(t[3]); } + else { tcneed(t[0] == TY_DYN || t == tword, "cap needs an array", t); callname(s_dcap); } + xty = tint; + return 0; +} +int kw_cap() { next(); return array_cap(arrarg()); } +// compile push(a, v): add an element to the end of a growable array. +int array_push(word t) { + int c; + tcneed(t[0] == TY_DYN || t == tword, "push needs a T[?]", t); + effect_note_mask(effect_value, EF_WRITE); af_mutate(xaf, tpack()); nogrow(t); + if (t[0] == TY_DYN && isagg(t[4])) serr("push takes scalar or reference elements; use resize for aggregates"); + c = expr(); rval(); if (t[0] == TY_DYN) fcoerce(t[4]); + if (isptr(xty) || xty == tword) effect_note_mask(effect_mask(c), EF_RETAIN); argcheck(c, 2); + if (t[0] == TY_DYN && !compat(t[4], xty)) tcerr("push", t[4], xty); + if (o3on && t[0] == TY_DYN && hasptr(t[4])) { lit(t[4]); callname(s_fatten); } + expect(s_rp); + if (t[0] == TY_DYN && tbytes(t[4])) { // packed: an f32 goes in as its 32 bits, the rest are cut by the store + if (isfl(t[4])) { if (!xfloat) op(OP_I2F); xenh("zf2s"); } + callname(intern("dpushb")); xty = tint; xfloat = 0; xcls = C_PLAIN; + return 0; + } + if (o3on && t[0] == TY_DYN && !hasptr(t[4]) && tsz(t[4]) == 1) { pushfast(); xty = tint; xfloat = 0; xcls = C_PLAIN; return 0; } + callname(s_dpush); xty = tint; xfloat = 0; xcls = C_PLAIN; + return 0; +} +int kw_push() { word t; next(); t = arrarg(); expect(s_comma); return array_push(t); } +// pop in o3 code, inline: a non-empty array gives up its last word, an empty one is dpop's report +int popfast() { + int k, e; + lit(0); op(O_PICK); op(O_LD); + k = hole(); op(O_JZ); + lit(0); op(O_PICK); op(O_LD); lit(1); op(O_SUB); lit(1); op(O_PICK); op(O_ST); + lit(0); op(O_PICK); op(O_LD); + lit(1); op(O_PICK); lit(2); op(O_ADD); op(O_LD); + op(O_ADD); lit(0); op(O_PICK); op(O_LD); op(O_SWAP); lit(0); op(O_SWAP); op(O_ST); + op(O_SWAP); op(O_DROP); + e = hole(); op(O_JMP); + setlit(k, here); + callname(s_dpop); + setlit(e, here); + return 0; +} +// push of a plain one-word element in o3 code, inline: when len < cap the word goes +// straight into the items and len moves up, otherwise dpush grows the array +int pushfast() { + int k, e; + lit(1); op(O_PICK); op(O_LD); + lit(2); op(O_PICK); lit(1); op(O_ADD); op(O_LD); + op(O_LT); + k = hole(); op(O_JZ); + lit(1); op(O_PICK); lit(2); op(O_ADD); op(O_LD); + lit(2); op(O_PICK); op(O_LD); op(O_ADD); + op(O_ST); + lit(0); op(O_PICK); op(O_LD); lit(1); op(O_ADD); lit(1); op(O_PICK); op(O_ST); + op(O_DROP); lit(0); + e = hole(); op(O_JMP); + setlit(k, here); + callname(s_dpush); + setlit(e, here); + return 0; +} +// compile pop(a): remove and return the last element of a growable array. +int array_pop(word t) { + xcls = C_PLAIN; + tcneed(t[0] == TY_DYN || t == tword, "pop needs a T[?]", t); + effect_note_mask(effect_value, EF_WRITE); af_mutate(xaf, tpack()); + nogrow(t); + if (t[0] == TY_DYN && isagg(t[4])) serr("pop takes scalar or reference elements; access the last aggregate before resizing"); + expect(s_rp); + if (t[0] == TY_DYN && tbytes(t[4])) { // packed: read zero extended, then sign extend or widen + callname(intern("dpopb")); + xty = t[4]; xfloat = 0; + xaf = 0; xbor = -1; effect_value = 0; + pkfix(xty); + if (isfl(xty)) xfloat = 1; + return 0; + } + if (o3on && t[0] == TY_DYN && !hasptr(t[4]) && tsz(t[4]) == 1) popfast(); else callname(s_dpop); + xty = telem(t); + if (t[0] == TY_DYN) xty = t[4]; + xfloat = 0; + if (isfl(xty)) xfloat = 1; + xaf = 0; xbor = -1; effect_value = 0; + if (isref(xty)) { + xcls = C_FRESH; xaf = af_new(-1, 2, tpack()); newsz = -1; newleaf = 0; return C_FRESH; + } + return 0; +} +int kw_pop() { next(); return array_pop(arrarg()); } +// compile resize(a, n): set the length of a growable array. +int array_resize(word t) { + int c; + tcneed(t[0] == TY_DYN || t == tword, "resize needs a T[?]", t); + effect_note_mask(effect_value, EF_WRITE); af_mutate(xaf, tpack()); + nogrow(t); + c = expr(); + rval(); + tcneed(isnum(xty) || isfl(xty) || xty == tword, "resize needs a numeric length", xty); + fcoerce(tint); + plainuse(c); + expect(s_rp); + if (t[0] == TY_DYN && tbytes(t[4])) callname(intern("dresizeb")); else callname(s_dresize); + xty = tint; xcls = C_PLAIN; xfloat = 0; + return 0; +} +int kw_resize() { word t; next(); t = arrarg(); expect(s_comma); return array_resize(t); } +// array receiver calls share the free-call lowering and ownership checks. +int array_method(int n) { + return n == intern("len") || n == intern("cap") || n == intern("push") || n == intern("pop") || n == intern("resize"); +} +int array_member(int c, int n) { + word t = arrvalue(c); + expect(s_lp); + if (n == intern("len")) return array_len(t); + if (n == intern("cap")) return array_cap(t); + if (n == intern("push")) return array_push(t); + if (n == intern("pop")) return array_pop(t); + return array_resize(t); +} +// compile sizeof(T) or sizeof(e): the size of a type or expression in words. +int kw_sizeof() { + int t; + next(); + t = typearg(); + lit(tsz(t)); + xty = tint; + return 0; +} +// compile typeof(e): the type of an expression, as a value. +int kw_typeof() { + int t; + next(); + t = typearg(); + lit(t); + xty = tword; + return 0; +} +// ---- c abi: extern and cfunc ---- + +// extern double pow(double, double); or with parameter names, and an +// optional "= "cname"" to give the zc function another name than the c symbol. +// the declaration is read with c's spelling of types and turned into an +// ordinary zc function, so a call to it is a call like any other. the function +// fills a block of words that sits in the code stream (the symbol's name, the +// signature, a cache slot, then room for the arguments) and stores its arguments there, +// and gate op 14 makes the native call (see the c abi section of core.c). +// a signature is 4 bits per kind: the result, then each argument: +// 0 void 1 long 2 double 3 byte pointer 4 int 5 float 6 word pointer +// 7 unsigned int 8 short 9 unsigned short 10 char 11 unsigned char +// 12 str, a zc byte string (b"..."), given to c as the address of its bytes +// a double or float is a zc float. any pointer is a byte address (what +// addr_b(s + 1) gives for a byte string), except "word *", which is a word +// address (a zc object or array). native results come back the same way. +// append the string s to the text being generated for an extern stub. +int eb_char(int c) { + if (ebn + 2 > ebcap) { ebcap = ebcap * 2; ebuf = chk_resize(ebuf, ebn, ebcap); } + ebuf[ebn] = c; ebn++; ebuf[ebn] = 0; return 0; +} +int ebs(char* s) { + while (*s) { eb_char(*s); s++; } + ebuf[ebn] = 0; + return 0; +} +// append the decimal digits of n to the generated text. +int ebn10(int n) { + if (n >= 10) ebn10(n / 10); + eb_char(48 + n % 10); + ebuf[ebn] = 0; + return 0; +} +// true for a c type keyword, like long, char or double. +int ctypeword(int w) { + return rt.eq_w(w, "unsigned") || rt.eq_w(w, "signed") || rt.eq_w(w, "long") || rt.eq_w(w, "short") || rt.eq_w(w, "int") || rt.eq_w(w, "char") || rt.eq_w(w, "double") || rt.eq_w(w, "float") || rt.eq_w(w, "void") || rt.eq_w(w, "const") || rt.eq_w(w, "volatile"); +} +// a c type at the current token: its signature kind, or -1 after an error. +// skipname also eats a parameter name that follows. +// a plain zc struct a native call takes by value: every field is one word (a long, +// a double or a pointer), so word g of the struct is eightbyte g. returns its +// word count, or -1 after an error. +int cstruct_ok(word st, char* nm) { + int i = 0, n; word ft; + tresolve(st); + n = st[1]; + if (st[0] != TY_STRUCT || st[5] || n < 1 || n > 15 || st[3] != n) { serrn(nm, "can't be passed by value to a native call: it needs one word fields, at most 15 words"); return 0 - 1; } + while (i < n) { + ft = st[4][i * 3 + 1]; + if (tbytes(ft) || tsz(ft) != 1 || ft[0] == TY_STRUCT || ft[0] == TY_ARR || st[4][i * 3 + 2] != i) { serrn(nm, "can't be passed by value to a native call: every field has to be one word (long, double or a pointer)"); return 0 - 1; } + i++; + } + return n; +} +// the native signature kind of the c type at the current token, or -1 after an error. +int ckind(int skipname) { + char* w; int uns = 0, lng = 0, shrt = 0, chr = 0, ints = 0, dbl = 0, flt = 0, vd = 0, ptr = 0, seen = 0, k = -1, sk, nw; word other = 0; char* sw = 0; + ckst = 0; ckn = 0; + while (tk == T_ID) { + w = tv; + if (rt.eq_w(w, "const") || rt.eq_w(w, "volatile") || rt.eq_w(w, "restrict")) { next(); continue; } + if (rt.eq_w(w, "struct") || rt.eq_w(w, "enum") || rt.eq_w(w, "union")) { + next(); + if (tk == T_ID) { if (rt.eq_w(w, "struct")) sw = tv; next(); } + other = 1; seen = 1; + continue; + } + if (seen && !ctypeword(w)) break; + if (rt.eq_w(w, "unsigned")) uns = 1; + else if (rt.eq_w(w, "signed")) ints = 1; + else if (rt.eq_w(w, "long")) lng++; + else if (rt.eq_w(w, "short")) shrt = 1; + else if (rt.eq_w(w, "char")) chr = 1; + else if (rt.eq_w(w, "int")) ints = 1; + else if (rt.eq_w(w, "double") || rt.eq_w(w, "f64")) dbl = 1; + else if (rt.eq_w(w, "float") || rt.eq_w(w, "f32")) flt = 1; + else if (rt.eq_w(w, "void")) vd = 1; + else other = w; + seen = 1; + next(); + } + if (!seen) { die("expected a c type"); return 0 - 1; } + while (is_op(s_star)) { + ptr = 1; next(); + while (is_id(intern("const"))) next(); + } + if (skipname && tk == T_ID) next(); + if (ptr) { + if (other && other != 1 && rt.eq_w(other, "word")) return 6; + return 3; + } + if (dbl) return 2; + if (flt) return 5; + if (vd) return 0; + if (chr) { if (uns) return 11; return 10; } + if (shrt) { if (uns) return 9; return 8; } + if (lng) return 1; + if (ints || uns) { if (uns) return 7; return 4; } + if (other == 1) { + if (!sw) { serr("a struct has to be passed by pointer"); return 0 - 1; } + sk = lookup(sw); + if (sk < 0 || skind[sk] != K_TYPE || sval[sk][0] != TY_STRUCT) { serrn(sw, "isn't a zc struct: a native call takes a zc struct by value, declared with word fields"); return 0 - 1; } + nw = cstruct_ok(sval[sk], sw); + if (nw < 0) return 0 - 1; + ckst = sval[sk]; ckn = sw; + if (nw <= 2) return 13; // in registers when they fit (sysv: 16 bytes or less) + return 14; // in memory: on the stack, word after word + } + if (rt.eq_w(other, "str")) return 12; + if (rt.eq_w(other, "size_t") || rt.eq_w(other, "ssize_t") || rt.eq_w(other, "intptr_t") || rt.eq_w(other, "uintptr_t") || rt.eq_w(other, "ptrdiff_t") || rt.eq_w(other, "int64_t") || rt.eq_w(other, "uint64_t") || rt.eq_w(other, "off_t") || rt.eq_w(other, "time_t") || rt.eq_w(other, "word")) return 1; + if (rt.eq_w(other, "int32_t") || rt.eq_w(other, "i32")) return 4; + if (rt.eq_w(other, "uint32_t") || rt.eq_w(other, "u32")) return 7; + if (rt.eq_w(other, "int16_t") || rt.eq_w(other, "i16")) return 8; + if (rt.eq_w(other, "uint16_t") || rt.eq_w(other, "u16")) return 9; + if (rt.eq_w(other, "int8_t") || rt.eq_w(other, "i8")) return 10; + if (rt.eq_w(other, "uint8_t") || rt.eq_w(other, "u8") || rt.eq_w(other, "byte") || rt.eq_w(other, "bool") || rt.eq_w(other, "_Bool")) return 11; + serrn(other, "isn't a c type extern knows (use long, int, double, a pointer, ...)"); + return 0 - 1; +} +// true for a native kind that travels in a float register. +int ckindfloat(int k) { return k == 2 || k == 5; } +// parse an extern declaration: a c function with its signature, and a zc stub for it. +int kw_extern() { + int rk; word[16] ks; word[16] kst; char*[16] ksn; int np = 0, k; char* fname; char* cname; int i, a, j, n, w, sig, bad = 0; char* buf; word oldimports = module_imports; int pos, len, g; word st, rst; int rn, rsd, sret = 0; + stmtonly(); + next(); + rk = ckind(0); + // a struct result: the stub makes it (new) and passes it as the first item; the + // call block's word 2 says how many words it has and which of them are floats + if (rk >= 13) { + sret = 1; rst = ckst; rn = ckn; + if (rst[1] > 7) { serr("a native call can return at most 7 words"); rk = 0 - 1; } + } + fname = name(); + cname = fname; + expect(s_lp); + if (!is_op(s_rp)) { + while (1) { + k = ckind(1); + if (k < 0) { bad = 1; break; } + if (k == 0 && np == 0 && is_op(s_rp)) break; // (void) + if (k == 0) { serr("a parameter can't be void"); bad = 1; break; } + if (np >= 15) { serr("a native call takes at most 15 arguments"); bad = 1; break; } + ks[np] = k; kst[np] = ckst; ksn[np] = ckn; np++; + if (!accept(s_comma)) break; + } + } + expect(s_rp); + if (accept(s_eq)) { + if (tk != T_STR) { die("expected the c name as a string"); return 0; } + cname = tv; + next(); + } + expect(s_semi); + if (bad || rk < 0 || panic) return 0; + if (sret && np > 14) { serr("a native call that returns a struct takes at most 14 arguments"); return 0; } + rsd = 0; + if (sret) { + rsd = rst[1]; g = 0; + while (g < rst[1]) { + if (rst[4][g * 3 + 1] == tfloat) rsd = rsd | (2 << (4 + 4 * g)); else rsd = rsd | (1 << (4 + 4 * g)); + g++; + } + } + // the block, then the name as packed bytes, then the block's own address + // (the word before the stub's entry, which is how a call site finds it): + // [0] the name's byte address, [1] the signature, [2] a lookup cache the + // host fills in, [3..17] the arguments, [18..] the name + // the arguments' nibbles: one per argument. a struct is a group: code 13 (in + // registers if they fit) or 14 (in memory), its length in eightbytes, then one + // class nibble per eightbyte (2 for a float word, 1 otherwise). the group is one + // stack item: the struct's address + sig = rk; + pos = 1; i = 0; + while (i < np) { + if (ks[i] < 13) { + sig = sig | (ks[i] << (4 * pos)); pos++; + } else { + st = kst[i]; len = st[1]; + if (pos + 1 + len > 15) { serr("the arguments of this native call are too many words"); return 0; } + sig = sig | (ks[i] << (4 * pos)); pos++; + sig = sig | (len << (4 * pos)); pos++; + g = 0; + while (g < len) { + if (st[4][g * 3 + 1] == tfloat) sig = sig | (2 << (4 * pos)); else sig = sig | (1 << (4 * pos)); + pos++; g++; + } + } + i++; + } + j = hole(); + op(O_JMP); + reserve_code(20 + (rt.len_w(cname) >> 3)); a = here; + emit((a + 18) << 3); + emit(sig); + emit(rsd << 32); // the cache's half is the host's; this half is the result's descriptor + i = 3; + while (i < 18) { emit(0); i++; } + n = 0; + while (cname[n]) n++; + i = 0; + while (i < n) { + w = 0; k = 0; + while (k < 8 && i < n) { w = w | (cname[i] << (k * 8)); k++; i++; } + emit(w); + } + if (!(n & 7)) emit(0); + emit(a); + setlit(j, here); + // the stub, compiled as if it had been written in the source. it stays + // for the places that take the function's address; a direct call to it + // compiles to the native-call opcode instead (see call) + ebn = 0; ebuf[0] = 0; + if (sret) { ebs(rn); ebs("*.u "); } + else if (rk == 5) ebs("f32 "); else if (ckindfloat(rk)) ebs("float "); else ebs("int "); + ebs(fname); ebs("("); + i = 0; + while (i < np) { + if (i) ebs(", "); + if (ks[i] >= 13) { ebs(ksn[i]); ebs(" p"); } else if (ks[i] == 5) ebs("f32 p"); else if (ckindfloat(ks[i])) ebs("float p"); else ebs("int p"); + ebn10(i); + i++; + } + ebs(") { let b = "); ebn10(a); ebs(";"); + // the native descriptor borrows an integer address, not ownership of r. + // clear that scratch address after the call so it cannot keep the result rooted. + if (sret) { ebs(" "); ebs(rn); ebs("* r = rt.new("); ebs(rn); ebs("); b[3] = core.addr_b(r) >> 3;"); } + i = 0; + while (i < np) { ebs(" b["); ebn10(i + 3 + sret); ebs("] = p"); ebn10(i); ebs(";"); i++; } + if (sret) ebs(" core.syscall(14, b, 0, 0); b[3] = 0; return r; }"); + else { + ebs(" return core.syscall(14, b, 0, 0)"); + if (rk == 5) ebs(" as f32"); else if (ckindfloat(rk)) ebs(" as float"); + ebs("; }"); + } + buf = rt.new(ebn + 19); + rt.copy_w(buf, "include rt, core; ", 18); + rt.copy_w(buf + 18, ebuf, ebn + 1); + src_push(2, buf, 0, 0); + next(); + stubgen = 1; // the stub is ours, not the document's: its locals get no hints + while (tk != T_EOF) topitem(); + stubgen = 0; + src_pop(); + module_imports = oldimports; + rt.free(buf); + k = lookup(fname); + if (k >= 0 && skind[k] == K_FN && (sflag[k] & F_DEF) && !sret) sflag[k] = sflag[k] | F_EXT; // a struct result is called as its stub + return 0; +} +// the c kind of a zc type, for cfunc +int ckindof(word t) { + if (t == tfloat) return 2; + if (t == tf32) return 5; + if (t[0] == TY_INT && t[5]) { // i8 u8 i16 u16 i32 u32 pass and come back at their width + if (t[5] == 1) { if (t[6] & 1) return 10; return 11; } + if (t[5] == 2) { if (t[6] & 1) return 8; return 9; } + if (t[6] & 1) return 4; + return 7; + } + if (t == tvoid) return 0; + if (t[0] == TY_PTR || t[0] == TY_ARR || t[0] == TY_DYN) return 6; + return 1; +} +// cfunc(f): a native function pointer that runs the zc function f, with the +// c signature its declared types give (float is a double, f32 a c float, i8..u32 their width, a pointer is a word +// address, anything else a long). the pointer can be given to c code, which +// may call it while a native call from zc is running. +int kw_cfunc() { + int s; word t; int sig = 1, i, nm; + next(); + expect(s_lp); + if (tk != T_ID) { die("cfunc needs the name of a function"); return 0; } + s = source_lookup(tv); + if (s < 0 || skind[s] != K_FN) { serrn(tv, "isn't a function"); return 0; } + t = sigof(sval[s]); + if (!t) t = stype[s]; + if (t && t[0] == TY_FN) { + sig = ckindof(t[4]); + i = 0; + if (t[3] > 14) serrn(tv, "has more than 14 parameters"); + while (i < t[3] && i < 14) { sig = sig | (ckindof(t[7 + i]) << (4 * (i + 1))); i++; } + } else { + i = (ssig[s] >> 1) & 31; + while (i > 0) { i--; sig = sig | (1 << (4 * (i + 1))); } + } + expr(); rval(); + lit(sig); + callname(intern("cbmake")); + expect(s_rp); + xty = tint; xfloat = 0; + return 0; +} +// intern the built-in types and the spellings the parser compares against. +// the s_ names are plain globals holding interned handles, so a check like +// is_id(s_fn) is one pointer compare. +int types_init() { + word f32p; + readonly_types = 0; + tytop = R_TY + NTL; tyend = R_TYEND; typeblocks = 0; ntl = 0; fieldn = 0; + if (R_TY + typecap > R_TYEND && typelist == R_TY) typelist = chk_resize(0, 0, typecap); + tword = tnew(TY_WORD, 1, intern("word"), 0); + tint = tnew(TY_INT, 1, intern("int"), 0); + tchar = tnew(TY_CHAR, 1, intern("char"), 0); + tbool = tnew(TY_BOOL, 1, intern("bool"), 0); + tvoid = tnew(TY_VOID, 0, intern("void"), 0); + tfloat = tnew(TY_FLOAT, 1, intern("float"), 0); + tbstr = tnew(TY_BSTR, 1, intern("bstr"), 0); addsym(tbstr[2], K_TYPE, tbstr); + addsym(tword[2], K_TYPE, tword); addsym(tint[2], K_TYPE, tint); addsym(tchar[2], K_TYPE, tchar); + addsym(tbool[2], K_TYPE, tbool); addsym(tvoid[2], K_TYPE, tvoid); addsym(tfloat[2], K_TYPE, tfloat); + addsym(intern("f64"), K_TYPE, tfloat); + addsym(intern("string"), K_TYPE, tptr(tchar)); // string is char*, as if by typedef char* string; + // f32: single precision. a variable holds the rounded value as a double; an element + // of an array or pointer of f32 is packed two to a word, like the sized integers + f32p = tnew(TY_FLOAT, 1, intern("f32"), 0); + tf32 = tnew(TY_FLOAT, 1, f32p[2], 0); + tf32[5] = 4; tf32[4] = f32p; f32p[5] = 4; f32p[6] = 2; + addsym(tf32[2], K_TYPE, tf32); + tsized("i8", 1, 1); addsym(intern("byte"), K_TYPE, tsized("u8", 1, 0)); + tsized("i16", 2, 1); tsized("u16", 2, 0); tsized("i32", 4, 1); tsized("u32", 4, 0); + s_lt = intern("<"); + s_gt = intern(">"); + s_shr = intern(">>"); + s_le = intern("<="); + s_ge = intern(">="); + s_eqeq = intern("=="); s_ne = intern("!="); s_plus = intern("+"); + s_struct = intern("struct"); s_type = intern("type"); s_typedef = intern("typedef"); s_typecheck = intern("typecheck"); s_docs = intern("docs"); s_std = intern("std"); s_icon = intern("icon"); s_small = intern("small"); + s_on = intern("on"); + s_off = intern("off"); + s_new = intern("rt.new"); + s_new_t = intern("rt.new_t"); + s_new_k = intern("rt.new_k"); + s_recycle_leaf = intern("rt.recycle_leaf"); + s_hitems = intern("#items"); s_hlen = intern("#len"); s_fatten = intern("run.fatten"); + s_this = intern("this"); + s_o3 = intern("o3"); + s_lfa = intern("rt.lfa"); + s_outside = intern("rt.outside"); + s_new_raw = intern("rt.new_raw"); + s_oob = intern("rt.oob"); + s_new_oz = intern("rt.new_oz"); + s_ld8 = intern("rt.ld8"); s_st8 = intern("rt.st8"); s_ld16 = intern("rt.ld16"); s_st16 = intern("rt.st16"); + s_ld32 = intern("rt.ld32"); s_st32 = intern("rt.st32"); + s_fixed = intern("fixed"); + s_dlen = intern("rt.dlen"); + s_dcap = intern("rt.dcap"); + s_dnew = intern("rt.dnew"); + s_dat = intern("rt.dat"); + s_len_s = intern("rt.len_s"); + s_at_s = intern("rt.at_s"); + s_slice_s = intern("rt.slice_s"); + s_cat_s = intern("rt.cat_s"); + s_eq_s = intern("rt.eq_s"); + s_lt_s = intern("rt.lt_s"); + s_le_s = intern("rt.le_s"); + s_put_bs = intern("rt.put_bs"); + s_put_s = intern("rt.put_s"); + s_n_to_s = intern("rt.n_to_s"); s_new_s = intern("rt.new_s"); + s_dpush = intern("rt.dpush"); s_dpop = intern("rt.dpop"); s_dresize = intern("rt.dresize"); + numops[0] = intern("*"); numops[1] = intern("/"); numops[2] = intern("%"); numops[3] = intern("&"); + numops[4] = intern("|"); + numops[5] = intern("^"); + numops[6] = intern("<<"); + numops[7] = s_shr; + nnumops = 8; + return 0; +} +// true for an operator that works on numbers. +int isnumop(int o) { + int i = 0; + while (i < nnumops) { + if (numops[i] == o) return 1; + i++; + } + return 0; +} +// the type of a binary operation, checking it when the typechecker is on +// the result type of binary operator b, which is also where the typechecker +// complains: pointer plus int is that pointer, pointer minus pointer is an int, +// word on either side wins, and a float on either side makes arithmetic float. +word bintype(int b, word lt, word rhs_type) { + int o = bname[b]; + lt = decay(lt); rhs_type = decay(rhs_type); + if (blen[b] < 0) { tcneed(!isagg(lt) && !isagg(rhs_type), "&& and || need scalars", lt); return tbool; } + if (isfl(lt) || isfl(rhs_type)) { + if (o == s_plus || o == s_minus || o == s_star || o == s_slash) { + if (lt[0] == TY_PTR || rhs_type[0] == TY_PTR) { tcerr("+", lt, rhs_type); return tword; } + return tfloat; + } + if (o == s_eqeq || o == s_ne || o == s_lt || o == s_gt || o == s_le || o == s_ge) return tbool; + } + if (o == s_eqeq || o == s_ne) { + if (!compat(lt, rhs_type) && !compat(rhs_type, lt)) tcerr("comparison", lt, rhs_type); + return tbool; + } + if (o == s_lt || o == s_gt || o == s_le || o == s_ge) { + if (!(lt == tword || rhs_type == tword || (isnum(lt) && isnum(rhs_type)) || (lt[0] == TY_PTR && rhs_type[0] == TY_PTR))) { + tcerr("comparison", lt, rhs_type); + } + return tbool; + } + if (o == s_plus) { + if (lt[0] == TY_PTR && (isnum(rhs_type) || rhs_type == tword)) return lt; + if (rhs_type[0] == TY_PTR && isnum(lt)) return rhs_type; + if (lt == tword || rhs_type == tword) return tword; + if (isnum(lt) && isnum(rhs_type)) return tint; + tcerr("+", lt, rhs_type); return tword; + } + if (o == s_minus) { + if (lt[0] == TY_PTR && rhs_type[0] == TY_PTR) return tint; + if (lt[0] == TY_PTR && (isnum(rhs_type) || rhs_type == tword)) return lt; + if (lt == tword || rhs_type == tword) return tword; + if (isnum(lt) && isnum(rhs_type)) return tint; + tcerr("-", lt, rhs_type); return tword; + } + if (isnumop(o)) { + if (lt == tword || rhs_type == tword) return tword; + if (isnum(lt) && isnum(rhs_type)) return tint; + if (isnum(lt)) lt = rhs_type; + tcneed(0, "arithmetic needs numbers", lt); return tword; + } + return tword; // operators a program added +} + +// ---- reflection ---- + +// for programs (and parse words) looking at types at runtime +word tkind(word t) { return t[0]; } +// the size of a type in words. +word tsize(int t) { return tsz(t); } +// the name of a type. +char* tname(word t) { return t[2]; } +// the length of an array type. +word tlen(word t) { return t[3]; } +// the number of fields of a struct type. +word tnfields(word t) { tresolve(t); return t[3]; } +// ---- fattening ---- + +// typed new stamps the header, in o3 the same as outside it. fatten is for a +// value that still has new_raw's blank type word when it reaches code that +// isn't o3: a store into a normal local or a global, an argument to a +// function that isn't o3, the call site where non-o3 code calls an o3 +// function whose result has pointers, and the end of an o3 block. a child +// store of a value new(T) just allocated, or that an o3 function returned, +// does not call fatten: the header is already that type. a call or return +// from o3 to o3 does not fatten. fatten(p, t) stamps the type, zeroes the +// slack, and follows every typed pointer. it returns p. +// a type whose objects can never hold a pointer: numbers, chars, bools, and +// arrays and plain structs of them. word, generics and anything unknown say no +// two questions about a type's shape, both answered structurally: +// isleaf can an object of this type never hold a heap pointer? the +// allocator uses it to pick the recycle_leaf fast path (no children +// to walk, so the block goes straight back on its size-class list). +// hasptr can it hold one? fatten uses it to decide whether stamping a +// type has to follow pointers. +word isleaf(word t) { + int k = t[0], i, n; + if (k == TY_INT || k == TY_CHAR || k == TY_BOOL || k == TY_FLOAT) return 1; + if (k == TY_ARR) return isleaf(t[4]); + if (k == TY_STRUCT && !t[5]) { + n = tnfields(t); + i = 0; + while (i < n) { + if (!isleaf(tftype(t, i))) return 0; + i++; + } + return 1; + } + return 0; +} +// true when a value of type t holds a pointer that fattening must follow. +word hasptr(word t) { + int k = t[0], i, n; + if (k == TY_PTR || k == TY_DYN || k == TY_BSTR) return 1; + if (k == TY_ARR) return hasptr(t[4]); + if (k == TY_STRUCT && !t[5]) { + n = tnfields(t); + i = 0; + while (i < n) { + if (hasptr(tftype(t, i))) return 1; + i++; + } + } + return 0; +} +// stamp p with its type t, then the pointers it holds. +int fatten(int p, int t) { fat1(p, t, 32); return p; } +// stamp p with type t if it has no type yet, then follow its pointers. dl is a +// depth limit so a cycle through growable arrays ends. a raw header holds +// -(n + 1) for an object of n words, which tells how many elements to type. +int fat1(word p, word t, int dl) { + word e; int n, k, i, es; + if (dl <= 0 || !t || !rt.isobj(p)) return 0; + if (t[0] == TY_DYN) { // a handle is always fat; what it holds may not be + if (hasptr(t[4])) { + es = tsz(t[4]); + n = p[0]; + i = 0; + while (i < n) { fatin(p[2] + i * es, t[4], dl - 1); i++; } + } + return 0; + } + if (t[0] != TY_PTR || p[-1] >= 0) return 0; + e = t[4]; n = 0 - p[-1] - 1; es = tsz(e); + if (es < 1) es = 1; + if (e == tword || e[0] == TY_VAR) p[-1] = 0; + else if (n > es) p[-1] = tarr(e, 0); + else p[-1] = e; + if (rt.osize(p) > n) rt.fill_w(p + n, 0, rt.osize(p) - n); + if (hasptr(e)) { k = n / es; i = 0; while (i < k) { fatin(p + i * es, e, dl - 1); i++; } } + return 0; +} +// fatten the value stored at address a, which has type t: a pointer or handle +// is fattened itself, an array or struct is walked element by element +word fatin(word a, word t, int dl) { + int k = t[0], i, n, o, ft, es; + if (k == TY_PTR || k == TY_DYN) return fat1(a[0], t, dl); + if (k == TY_ARR) { + if (!hasptr(t[4])) return 0; + n = t[3]; + es = tsz(t[4]); + i = 0; + while (i < n) { fatin(a + i * es, t[4], dl); i++; } + return 0; + } + if (k == TY_STRUCT && !t[5]) { + n = tnfields(t); + i = 0; + while (i < n) { + o = tfoff(t, i); + ft = tftype(t, i); + if (o >= 0) fatin(a + o, ft, dl); + i++; + } + } + return 0; +} +// reflection accessors over type records (see the layout under types). they +// resolve a generic instance first, so asking about vec<int> works. +char* tfname(word t, int i) { tresolve(t); return t[4][i * 3]; } +// the type of field i of a struct. +word tftype(word t, int i) { tresolve(t); return t[4][i * 3 + 1]; } +// the offset of field i of a struct. +word tfoff(word t, int i) { tresolve(t); return t[4][i * 3 + 2]; } +// the return type of a function type. +word tret(word t) { return t[4]; } +// the number of parameters of a function type. +word tnparams(word t) { return t[3]; } +// the type of parameter i of a function type. +word tparam(word t, int i) { return t[7 + i]; } +// the tag of parameter i: 0 untagged, 1 .m, 2 .d. +word tparamtag(word t, int i) { return ((t[5] >> 6) >> (i + i)) & 3; } // 0 untagged (m), 1 .m, 2 .d +// true when a function type returns an owned object (.u). +word tretu(word t) { return t[5] & 1; } +// the number of type arguments a generic struct instance has. +word tnargs(word t) { + if (t[0] != TY_STRUCT || !t[5]) return 0; + return t[6][0]; +} +// type argument i of a generic struct instance. +word targ(word t, int i) { return t[6][1 + i]; } +// the type of a function, by address. untyped functions get one made from their signature +// ---- function types by address ---- + +word sigof(int f) { + int i = nsym, s = -1; word[24] ws; int n, k; + while (i) { i--; if (skind[i] == K_FN && sval[i] == f && (sflag[i] & F_DEF)) { s = i; break; } } + if (s < 0) return 0; + if (stype[s]) return stype[s]; + n = (ssig[s] >> 1) & 31; k = 0; + while (k < n) { ws[k] = tword; k++; } + return tfn(tword, n, ws, ssig[s]); +} +// the function type of the function named nm, or 0 when there is none. +word sigofs(char* nm) { + int s = lookups(nm); + if (s < 0 || skind[s] != K_FN) return 0; + return sigof(sval[s]); +} + diff --git a/src/compiler/check.zc b/src/compiler/check.zc new file mode 100644 index 0000000..a22bcdd --- /dev/null +++ b/src/compiler/check.zc @@ -0,0 +1,115 @@ +// ---- check mode: the language server ---- + +// the server runs this compiler, unchanged, over a document it holds in +// memory. chk_unit compiles it as a whole unit, in a machine set up the way +// start() sets one up (the genome's own names, the default operators and +// keywords), so the document's parse words, operators and types change what +// the rest of it means exactly as they would in a real build. the symbols +// the document made stay in the tables afterwards for the highlighter. +// chk_mark is the last function of the compiler proper, and LSP_SERVER (in +// src/lsp/server.zc) the first constant of the server: the server's names are +// hidden from the document, the runtime's and the compiler's are not. +int chk_mark() { return 0; } +// reset check mode for a new document. +int chk_reset() { + statement_hooks = 0; included_modules = 0; + if_local = 0; if_edges = 0; if_value = 0; + rt.code_blocks = 0; code_chunk = rt.CODE_END; + isp = -1; + docs_on = 1; dn = 0; dline = 0; docpending = 0; dsect = 0; dsn = 0; dnm = 0; + pq_n = 0; + pendot = 0; + qdone = 0; + ch = 0; + chkind = 0; + line = 1; + tk = 0; + tv = 0; + tglue = 0; + tline = 1; + toff = 0 - 1; toklen = 0; tpend = 0 - 1; tpline = 1; + infn = 0; + fdepth = 0; + fslots = 0; + fchk = 0; + fret = 0; + live = 0; + bxdepth = 0; + epichain = 0; + fframe = 0; + xbor = 0 - 1; xaf = 0; argfact = 0; xvk = 0; + if (svkind) rt.fill_w(svkind, 0, symcap); + mdbg = 0; mdbg_flags = 0; lv = 0; lvk = 0; lvi = 0; kw_expr = 0; xcls = 0; fty = 0; + check_on = 1; tc_on = ifstrict; dry = 0; ntp = 0; tvauto = 0; ngbind = 0; xgive = 0; xconst = 0; + o3on = 0; o3nxt = 0; no3pend = 0; no3sl = 0; no3free = 0; o3nreg = 0; o3na = 0; o3fn0 = 0; + nfact = 0; fortest = 0; forstep = 0; argloc = 0; arrloc = 0; njps = 0; extop = 0; nnumops = 0; + nenh = 0; panic = 0; nerr = 0; dgprev = 0; nclo = 0; newsz = 0 - 1; nomagic = 0; callpos = 0; + grec = 0; gnew = 0 - 1; gt_n = 0; gi_n = 0; ginst = 0; gtab = 0; + return 0; +} +// src: the document, 4 chars to a word and a 0 at the end. buf, cap: where +// the records go. sets: 4 * LSW words for the name sets. lim: code may grow up +// to here. every block the caller holds is a heap object reachable from its +// globals, so the check's garbage is collected and nothing is cut off. +// check one document in check mode and fill the records it made. +int chk_unit(int src, int buf, int cap, int sets, int lim) { + chk_reset(); + module_name = 0; module_imports = 0; + chk_begin(buf, cap); + lsets = sets; + if (sets) rt.fill_w(sets, 0, 4 * LSW); + if (!chk_heap) { rt.heap_init(); chk_heap = 1; } else rt.gc(); // the caller's blocks are reachable from its globals, the rest goes + rsn = 0; evn = 0; hl_n = 0; literal_doc_n = 0; if (literal_docs) rt.fill_w(literal_docs, 0, literal_doc_cap); curinc = 0; std_done = 0; icon_done = 0; incdepth = 0; dgdrop = 0; probe_has = 0; probe_n = 0; probe_mem = 0; probe_static = 0; nameoff = 0 - 1; rsbase = src; + if (!rsrec) { rscap = RS_CAP; rsrec = rt.new(RS_CAP * RS_W); rstmp = rt.new(RS_CAP * RS_W); } + ifs = 0; ifx = 0; nifx = 0; nift = 0; nif = 0; ifcur = 0; ifkk = 0; ifgk = 0; ifcall = 0 - 1; + if (ifon) { + ifrcap = NIF; ifxcap = IFN; iftcap = IFT; + ifs = rt.new(symcap); ifr = chk_resize(0, 0, ifrcap); ifx = chk_resize(0, 0, ifxcap * 4); ift = chk_resize(0, 0, iftcap); + if_local = chk_resize(0, 0, loccap); if_edges = 0; if_value = 0; + rt.fill_w(ifs, 0, symcap); + } + zc_init(); + blob_load(rt.GORG[5], 1); + zc_tables(); + types_init(); + here = 0; hdelta = 0; hlimit = lim; ptop = 0; + ubase = nsym; vislo = 0; + prescan(3, src); + src_push(3, src, 0, 0); + next(); + unit_top(); + rs_fix(); + unit_end(ubase); + if (ifx) if_emit(); + src_pop(); + rs_sort(); + chk_end(); + return 0; +} +// walk a document's tokens with the lexer as the check left it. hl_begin +// loads the first token into tk, tv, toff and toklen; next() moves on; hl_end +// restores the previous state. +// the syntax highlighter's view of the lexer. it re-lexes the document after +// the check, so the tokens it sees are cut exactly where the compiler's were +// (parse words that changed the operators included). +int hl_begin(int src) { + chkabort = 0; isp = -1; pq_n = 0; pendot = 0; qdone = 0; panic = 0; + dry++; + src_push(3, src, 0, 0); + next(); + return 0; +} +// end a highlight pass: leave the source and the dry run. +int hl_end() { + src_pop(); + dry--; + isp = -1; + pq_n = 0; + return 0; +} +// the source of the std and icon libraries, embedded when the compiler is built, so +// `include std;` and `include icon;` need no file at run time. +embed "src/lib/std.zc" as bstr std_embedded; +embed "src/lib/icon.zc" as bstr icon_embedded; +bstr std_source() { return std_embedded; } +bstr icon_source() { return icon_embedded; } diff --git a/src/compiler/compiler.zc b/src/compiler/compiler.zc deleted file mode 100644 index ac504d7..0000000 --- a/src/compiler/compiler.zc +++ /dev/null @@ -1,10458 +0,0 @@ -namespace run; -include rt; - -// ============================================================================ -// compiler.zc: the resident zc compiler. it lives in the genome next to the -// runtime, compiles source straight to vm words in memory, and is visible to -// the programs it compiles: every function and global here can be called or -// patched by name, and parse words (immediate fns) run inside it. -// ============================================================================ - -// how this file is organized, in the order a source file travels through it: -// -// 1 lexer bytes -> tokens (nextch, lex1, next/peek). a token is a -// kind (tk), a value (tv: number or interned name) and a -// place (line, offset, length). sources nest: stdin, a -// token list for macros, a string for eval. -// 2 symbols every name is interned once in a string pool; symbols -// (functions, globals, constants, keywords, types) hang -// off the interned name and shadow one another. -// 3 types records in the type area, hash-consed, so comparing two -// types is comparing two words. typing is optional: an -// untyped program is all "word". -// 4 analyzer the ownership/lifetime pass. it runs while the parser -// emits code, tracking for every local whether it owns an -// object in this block (m), moves one up and out (u), or -// moves one down into a lower scope (d). borrowing is -// tracked separately. the pass inserts frees, and joins -// merge that knowledge at -// if/else, loops and switches. -// 5 expressions precedence climbing over the operator tables, which a -// program can extend at compile time. -// 6 statements blocks, control flow, switch, asm blocks, quote/grab. -// 7 functions signatures, prologues, closures, methods, generics, -// fattening (typed new) and the top level. -// 8 setup the keyword table, built-in names, entry point. -// 9 check mode the same compiler run over an in-memory document for the -// language server: errors and hints become records. -// -// the compiler emits vm words directly into memory at "here" (there is no -// intermediate tree). a forward reference emits a placeholder literal and -// remembers the site on a patch chain; defining the name patches the chain. -// the compiler is itself written in zc and lives in the genome next to rt.zc, -// so a program being compiled can call any function in this file, and -// immediate fns (parse words) run in the middle of compilation and can read -// tokens, emit code, or add operators. a few conventions that recur: -// - "plain word": most values are untyped 64-bit words. a pointer is just a -// word that happens to be a heap address. -// - a code word is lit(v) = 2v+1 (odd) or op(k) = 2k (even). see lit and op. -// - names ending in an s_ prefix are interned spellings the parser compares -// against by pointer, which is why intern exists. -// - serr reports and carries on; die reports and skips input to the next -// ; or }. nothing in here raises or unwinds. -// - a backtick outside a comment or a literal ends the source (srcend). zc0, -// the c bootstrap, reads the same way, so a backtick in a comment is fine. -// -// ---- compiler data region (@map-cc) ---- - -// src/tools/genmap computes these addresses from the compiler table sizes for each -// build. later regions (the type area, local types, exit records) are computed -// from these names. - -// @map-cc -const IHN = 0x8000; -const R_IH = 0x90100; -const R_POOL = 0x98100; -const R_POOLEND = 0xD8100; -const NS = 0x4000; -const R_SYM = 0xD8200; -const sname_BASE = 0xD8200; -const skind_BASE = 0xDC200; -const sval_BASE = 0xE0200; -const ssig_BASE = 0xE4200; -const sflag_BASE = 0xE8200; -const sfix_BASE = 0xEC200; -const snext_BASE = 0xF0200; -const NL = 0x200; -const lname_BASE = 0xF4200; -const lslot_BASE = 0xF4400; -const lhome_BASE = 0xF4600; -const lstate_BASE = 0xF4800; -const lowner_BASE = 0xF4A00; -const lused_BASE = 0xF4C00; -const larr_BASE = 0xF4E00; -const lown_BASE = 0xF5000; -const NF = 0x8000; -const fsite_BASE = 0xF5200; -const fnext_BASE = 0xFD200; -const R_SNAP_BASE = 0x105200; -const R_QBUF_BASE = 0x109200; -const R_SRC_BASE = 0x10D200; -const R_SEQ_BASE = 0x10D600; -const bname_BASE = 0x10DE00; -const bprec_BASE = 0x10DE40; -const bseq_BASE = 0x10DE80; -const blen_BASE = 0x10DEC0; -const uname_BASE = 0x10DF00; -const useq_BASE = 0x10DF20; -const ulen_BASE = 0x10DF40; -const aname_BASE = 0x10DF60; -const abin_BASE = 0x10DFA0; -const kops_BASE = 0x10E000; -const R_CTX_BASE = 0x10E200; -const R_LOOP_BASE = 0x10EA00; -const R_ASM = 0x10EE00; -const ASM_LABELS = 0x40; -const ASM_REFS = 0x100; -const ASM_WORDS = 0x400; -const EXIT_WORDS = 0xA00; -const ENUM_MEMBERS = 0x80; -const TAGGED_ENUMS = 0x10; -const STRUCT_METHODS = 0x20; -const GENERATED_WORDS = 0x2000; -const RANGE_FACTS = 0x20; -const JOIN_PATHS = 0x40; -const O3_REGIONS = 0xA; -// @end-map - -// table bases start in the generated map; growth copies columns without changing indices. - -// interned spelling of each symbol, indexed by symbol id; use lookup(name) to find an id. -char** sname = sname_BASE; -// kind of each symbol: K_FN, K_GVAR, K_GARR, K_CONST, K_KW, K_ENH, K_TYPE or K_DOC. -word skind = skind_BASE; -// value of each symbol, indexed by symbol id: a function code address, variable storage address, constant value, enhancement slot or type record, according to skind. -word sval = sval_BASE; -// packed function signature for each symbol; includes the owned-return bit, parameter count and parameter lifetime tags. -word ssig = ssig_BASE; -// definition and compilation flags for each symbol, such as F_DEF, F_DECL, F_GEN and F_STATIC. -word sflag = sflag_BASE; -// head of unresolved code references for each symbol; references are patched when the symbol is defined. -word sfix = sfix_BASE; -// previous symbol with the same interned name, stored as id plus one; zero ends its shadow chain. -word snext = snext_BASE; -// retained source metadata for functions that can be transformed at runtime. -word fnmeta; -int syntax_host_floor, syntax_host_from; word syntax_host_symbols; -// interned name of each currently active local variable or parameter. -char** lname = lname_BASE; -// frame slot of each local; laddr(lslot[i]) emits its address relative to the current frame. -word lslot = lslot_BASE; -// scope depth where each local was declared; the lifetime analyzer uses it when values move between blocks. -word lhome = lhome_BASE; -// ownership state of each local: plain, m, u, d, dead, child or borrowed. -word lstate = lstate_BASE; -// owner local of a borrowed or container-owned local; lroot(i) follows this chain. -word lowner = lowner_BASE; -// source line of each local's last tracked use; zero means it has not been used. -word lused = lused_BASE; -// nonzero for locals stored in place as fixed arrays or structs; their expression value is their address. -word larr = larr_BASE; -// nonzero when a local is responsible for releasing its owned object. -word lown = lown_BASE; -// code location to patch for each unresolved reference record. -word fsite = fsite_BASE; -// next unresolved reference record, stored as index plus one; zero ends a patch chain. -word fnext = fnext_BASE; -int symcap = NS, loccap = NL, fixcap = NF; - -const word R_TY = rt.ZFP - 16384, R_TYEND = rt.ZFP; -// interned text and type records never move: only their index vectors grow. -char** namehash = R_IH; word poolend = R_POOLEND; -word poolblocks, typeblocks; -int hashcap = IHN, namecount, poolchunk = R_POOLEND - R_POOL; -word typelist = R_TY, tyend = R_TYEND; -int typecap = 1024; - -// growing scratch regions retain offsets used by nested parser frames. -word R_SNAP = R_SNAP_BASE; int R_SNAP_cap = R_QBUF_BASE - R_SNAP_BASE; -word R_QBUF = R_QBUF_BASE; int R_QBUF_cap = R_SRC_BASE - R_QBUF_BASE; -word R_SRC = R_SRC_BASE; int R_SRC_cap = R_SEQ_BASE - R_SRC_BASE; -word R_SEQ = R_SEQ_BASE; int R_SEQ_cap = bname_BASE - R_SEQ_BASE; -word R_CTX = R_CTX_BASE; int R_CTX_cap = R_LOOP_BASE - R_CTX_BASE; -word R_LOOP = R_LOOP_BASE; int R_LOOP_cap = R_ASM - R_LOOP_BASE; -word[60] lpfact_initial; word lpfact = lpfact_initial; int loopfacts_cap = 60; - -// operator columns grow independently of their initial map reservations. -char** bname = bname_BASE; -word bprec = bprec_BASE; -word bseq = bseq_BASE; -word blen = blen_BASE; -char** uname = uname_BASE; -word useq = useq_BASE; -word ulen = ulen_BASE; -char** aname = aname_BASE; -word abin = abin_BASE; -word kops = kops_BASE; -int bincap = bprec_BASE - bname_BASE, uncap = useq_BASE - uname_BASE, asgcap = abin_BASE - aname_BASE, kopcap = R_CTX_BASE - kops_BASE; -// ---- token kinds, symbol kinds and flags ---- - -const int T_EOF = 0, T_NUM = 1, T_ID = 2, T_STR = 3, T_OP = 4, T_FLT = 5, T_BSTR = 6; -const int K_FN = 1, K_GVAR = 2, K_GARR = 3, K_CONST = 4, K_KW = 5, K_ENH = 6, K_TYPE = 7, K_DOC = 8; -const int F_DEF = 1, F_SIG = 2, F_UNTYPED = 4, F_STMT = 8, F_EXPR = 16, F_TOP = 32, F_FALLBACK = 64, F_GEN = 128, F_PRE = 256, F_O3 = 512, F_EXT = 1024, F_TPL = 2048, F_STATIC = 4096, F_DECL = 8192, F_RETOWN = 16384, F_EARLY = 32768; -// variable states for the lifetime analyzer -const int S_PLAIN = 0, S_M = 1, S_U = 2, S_D = 3, S_DEAD = 4, S_CHILD = 5, S_BORROW = 6; -// expression classes: plain word, a new object, or a bare variable (C_VAR + index) -const int C_PLAIN = 0, C_FRESH = 1, C_VAR = 2; -// lvalue kinds -const int LV_NONE = 0, LV_LOCAL = 1, LV_GLOBAL = 2, LV_ELEM = 3, LV_OTHER = 4; -// opcodes -const int O_Q = 0, O_LD = 1, O_ST = 2, O_PICK = 3, O_DROP = 4, O_SWAP = 5, O_CALL = 6, O_JMP = 7; -const int O_JZ = 8, O_RET = 9, O_ADD = 13, O_SUB = 14, O_LT = 20; -const int XOP0 = 22; // first enhancement opcode -const int OP_FLIT = 600, OP_FADD = 601, OP_FSUB = 602, OP_FMUL = 603, OP_FDIV = 604; -const int OP_FEQ = 605, OP_FLT = 606, OP_FLE = 607, OP_I2F = 608, OP_F2I = 609; -const int SEQWORD = -4; // in an operator sequence: the next word is a full-width literal -const int SEQREF = -2; // in an operator sequence: the next word names a symbol - -// ---- state ---- - -int chk_heap; -int nenh; word pooltop; int nsym, ubase, vislo, nfixu, fixfree; -char* module_name; word module_imports; -int nbin, nun, nasg, nkop, seqtop; -char* mnem = "q@!$_~cjzr,?.+-*&|^><%"; -int check_on = 1; // set to 0 to switch the analyzer off for what follows -int tk; word tv; int tglue, tline, line = 1, ch, chkind; word chptr, chbase; int qdone, isp = -1, pendot; -// where the current token sits in its source (chars from the start, -1 for a token list), how long it is, and where the one before it ended -int toff = -1, toklen, tpend = -1, tpline; -int ntok; // tokens taken so far, so a parse word that reads nothing can be caught -int qst, qesc, qp; // srcend's reading state: normal, line comment, block comment, string, char literal -char[1100] tbuf_initial; char* tbuf = tbuf_initial; int tbufcap = 1100; -// doc comments: the comment lines directly above a declaration (no blank line -// between them or before the declaration) are its documentation. the lexer -// collects them in dbuf, dlast is the line of the last one, and dline the -// line of the token that follows them. only when docs are on. -int docs_on; // docs on; keeps them: a symbol "doc name" holds a byte string -char[2100] dbuf_initial; char* dbuf = dbuf_initial; int docbufcap = 2100; int dn, dlast, dline, docpending, dsect; -char[2100] dsnap_initial; char* dsnap = dsnap_initial; int docsnapcap = 2100; int dsn, dnm; char[300] dnmb_initial; char* dnmb = dnmb_initial; int docnamecap = 300; -int pk_tk, pk_tv, pq_n; word[4] pq_tk; word[4] pq_tv; word[4] pq_gl; word[4] pq_ln; // up to 4 tokens of lookahead -int here, hdelta, hlimit, ptop; -int code_chunk = rt.CODE_END; -word image_buffer, image_words; int image_active, image_org, image_capacity, image_size; -int nloc, lbase, fdepth, fslots, fchk, fret, live, bxdepth, epichain, fframe, infn; -// a global that borrows a local owner through g = m*x. each entry is the global's -// symbol, the local, and the block depth of the store; the lend ends when the same -// global is assigned again in that block, and is an error if the block can end first. -word lendrec; int nlend, lendcap, xlend = -1; -int finsym, finfer; // the function being compiled, and whether its owned return is still to be inferred (no .u) -int fn_return_type; // a consistent return type inferred from the function body -// effects come from the real compiler's uses, stores, returns and call edges; unknown bodies stay conservative. -const int EF_READ = 1, EF_WRITE = 2, EF_RETAIN = 4, EF_CONSUME = 8, EF_UNKNOWN = 16, EF_ALIAS = 32, EF_IDENTITY = 64; -word effect_symbols, effect_edges, effect_requirements, effect_current; int effect_floor, effect_argument_base; -word local_origin, local_origin_mask, local_cell, local_captured, local_ended, local_end_info; word effect_value, argorigin_mask; int capture_enabled; word[1] capture_dummy; -int borrow_pending, borrow_depth, borrow_jump = -1, borrow_entry; word borrow_slots; -int argorigin = -1, argtemporary, argowner = -1; word argowner_fact; -int stubgen; // 1 while an extern's generated stub is compiled: the stub gets no hints -word xprojection; int af_mutation_tick; -int xbor = -1; // local a pointer value is borrowed from, or -1 -int mdbg, mdbg_flags; // memdebug: compile checks in, and the flags start() turns on -int lsp, loopbase, ctxsp, snaptop; -int lv, lvk, lvi, kw_expr, xcls, fty; -// interned spellings the parser compares against - -int s_lp, s_rp, s_lb, s_rb, s_lk, s_rk, s_comma, s_semi, s_eq, s_arrow, s_inc, s_dec; -int s_star, s_slash, s_amp, s_colon, s_hash, s_minus, s_andand, s_oror; -int s_dot, s_fn, s_var, s_const, s_else, s_as, s_unchecked, s_immediate, s_m, s_d, s_u; -int s_q, s_case, s_default, s_fovf, s_start, s_main, s_free, s_recycle, s_own, s_qnew, s_enhance, s_micro, s_vm; -int s_frame_floor, s_frame_top, s_frame_node, s_frame_busy, s_frame_guard, s_frame_grow, s_frame_leave; -int s_copy_w; -int s_do, s_while, s_enum, s_include, s_memdebug, s_scrub, s_alloc, s_check, s_memset, s_static; - -// ---- types: kinds, tables and parser state (see the types section) ---- - -const int TY_WORD = 0, TY_INT = 1, TY_CHAR = 2, TY_BOOL = 3, TY_VOID = 4, TY_PTR = 5, TY_ARR = 6; -// dyn: T[?], a = element, n = 1 if fixed -const int TY_FN = 7, TY_STRUCT = 8, TY_VAR = 9, TY_GEN = 10, TY_DYN = 11, TY_FLOAT = 12, TY_BSTR = 13; -const int NTL = 1024; // the first NTL words list every record -// type per local, declared tag per local, type per symbol - -const word LTYPE_BASE = R_ASM + ASM_WORDS, LTAG_BASE = LTYPE_BASE + NL, STYPE_BASE = LTAG_BASE + NL; -word ltype = LTYPE_BASE, ltag = LTAG_BASE, stype = STYPE_BASE; -int tytop, ntl; word tword, tint, tchar, tbool, tvoid, tfloat, tf32, tbstr; -int ckst; char* ckn; // extern: the zc struct a by-value parameter names (ckind), and its name -int tc_on; // the optional typechecker, off unless a program says typecheck on; -// type, literal 0, float, byte string, dry runs (typeof), unique giveaway - -word xty; int xzero, xfloat, xstr, dry, xgive; -// the type facts of annotate mode (see type facts below): the tables, and where the declaration being read starts - -const int NIF = 431, IFP = 24, IFW = 80, IFN = 4096, IFT = 32768; -int ifon; word ifs, ifr; int nif; word ifx; int nifx; word ift; int nift; word ifcur; int ifkp, ifkk, ifcall, ifgk, ifstrict; -word if_edges, if_value, if_local; -int ifrcap = NIF, ifxcap = IFN, iftcap = IFT; -int fldst, fldi; // the one struct a bare address's field name resolved to -char*[8] tpn; int ntp; // type parameters in scope -// reading a return type: unknown names are type parameters declared after the function's name -int tvauto; -// the typedefs of the blocks being compiled: three words each (the symbol, the block depth and the -// compile context it was made in), newest last. a block's end unlinks them from their names. -word tdstk; int ntd, tdcap; -word readonly_types; -int dty, dtag; // result of dtype() -int s_typedef; -int s_lt, s_gt, s_shr, s_le, s_ge, s_eqeq, s_ne, s_plus, s_struct, s_type, s_typecheck, s_docs, s_small, s_std, s_icon, s_on, s_off, s_new, s_new_t; -int s_o3, s_lfa, s_outside, s_new_raw, s_new_oz, s_oob; -int s_ld8, s_st8, s_ld16, s_st16, s_ld32, s_st32; -// ---- o3 region state ---- - -// each promoted slot maps to a register; pending regions use 67-word records and a descriptor buffer. -int o3on, o3nxt, o3one; word[32] o3sl; word[32] o3rg; int no3sl; word[O3_REGIONS * 67] o3pend_initial; word o3pend = o3pend_initial; int o3pendcap = O3_REGIONS, no3pend; word[80] o3dsc; int o3fs, o3na, o3fn0; -// o3free holds registers released by closed scopes; o3cache holds the items pointer and length slots for fixed arrays. -word[8] o3free; int no3free, o3nreg; word[NL] o3cache_initial; word o3cache = o3cache_initial; int argloc, s_hitems, s_hlen, s_fatten; -// operator names used by numeric type checks. -word[12] numops; int nnumops, s_fixed, s_dlen, s_dcap, s_dnew, s_dat, s_dpush, s_dpop, s_dresize, xconst, xcval; -int xlenloc, arrloc, fortest, forstep, ft_iv, ft_bd, ft_ir, ft_nops, fs_ok, xstamp; -// local lower bound / len(array); one proof record per live loop -int nfact, factcap = RANGE_FACTS; word[RANGE_FACTS] floc_initial, flo_initial, flen_initial; word floc = floc_initial, flo = flo_initial, flen = flen_initial; -int s_len_s, s_at_s, s_slice_s, s_cat_s, s_eq_s, s_lt_s, s_le_s, s_put_bs, s_put_s, s_n_to_s, s_new_s; -int mdg; word[8] mdn; // the type parameters of the generic struct whose methods are being compiled -word[8] gbind; int ngbind; // explicit type arguments for the next call: f<int>(...) -// generic functions. the template (type parameters stay variables) is compiled once, as before, -// and its tokens are kept; a call whose type arguments are all known gets an instance, the same -// tokens compiled again with the parameters bound to those types, so new(T) and sizeof(T) -// inside it mean the real type. instances are compiled after the item that asked for them. -int grec; word gbuf; int gn, gcap; // the tokens of the top-level item being read -int gnew, gnew_np; word[8] gnew_nm; // a generic function was just defined -int fn_method_compile, fnpatchseq; -word gtab; int gt_n, gtcap = 64, gicap = 128; word gt_sym, gt_tok, gt_np, gt_module, gt_imports, gt_source; int gt_nm; // the columns are allocated at the first generic function -int gi_n; word gi_t, gi_sym, gi_ar, gi_done; -int ginst; word[8] gnm; word[8] gib; int gng, ginsn; // compiling an instance: the names and what they stand for -int s_this; -const int NCLO = 8; /* initial closure nesting capacity; grows when more closures are compiled */ -const int NCAP = 32; /* initial capture capacity per closure; grows when more variables are captured */ - -// ---- closure compilation state ---- - -// closures being compiled, innermost last: the enclosing function's locals -// (lo .. hi), how many variables the body has captured so far (names and the -// enclosing local each came from), and the body's own hidden env parameter - -int nclo, closure_cap = NCLO, capture_width = NCAP; -word[NCLO] clo_lo_initial; word clo_lo = clo_lo_initial; -word[NCLO] clo_hi_initial; word clo_hi = clo_hi_initial; -word[NCLO] clo_n_initial; word clo_n = clo_n_initial; -word[NCLO] clo_env_initial; word clo_env = clo_env_initial; -word[NCLO] clo_ret_initial; word clo_ret = clo_ret_initial; -char*[256] clo_name_initial; char** clo_name = clo_name_initial; -word[256] clo_loc_initial; word clo_loc = clo_loc_initial; -word[256] clo_via_initial; word clo_via = clo_via_initial; - - -// ---- errors ---- - -// the compiler reports up to 20 errors before it stops. a syntax error -// (die) skips the token it happened on and sets panic, which skips ahead to -// the next ; or } (or the next top-level item) and keeps quiet until then. -// other errors (serr) just report: the parse is still in step. -// -// check mode (cmode) is for the language server, which runs the real compiler -// over a document it holds in memory. nothing is printed and nothing halts: -// each error is written into dgbuf as a record [severity, line, offset, -// length, chars...] and giveup raises chkabort, which makes the lexer return -// end of file so every parsing loop winds down by itself. -int nerr, panic; -// bits kept in a local's ltag for the hints: assigned after its declaration, -// read, declared with let, initialized with a literal; the size of the object -// a new gave it (bits 16 to 39); and how many times it was used (from bit 56) -const int LASG = 0x1000, LREAD = 0x2000, LDECL = 0x4000, LCONST = 0x8000, LCNT1 = 0x100000000000000; -const int NEWTYPED = 0x800000; -word[NL] lpos_initial; word lpos = lpos_initial; word[NL] lreadpos_initial; word lreadpos = lreadpos_initial; word[NL] lnsz_initial; word lnsz = lnsz_initial; word[NL] lleaf_initial; word lleaf = lleaf_initial; word[NL] lrsoff_initial; word lrsoff = lrsoff_initial; word[NL] lrskind_initial; word lrskind = lrskind_initial; int newsz, newleaf, nomagic, callpos; -word[NL] lptr_initial; word lptr = lptr_initial; // annotate mode: the local was indexed or dereferenced, so it holds an address -// storage facts are shared by aliases. they describe one allocation, not a -// variable slot: [length, lifetime, kind, allocation place, end place, owner]. -// lifetime is 0 unknown, 1 live, 2 ended; kind is 1 stack, 2 heap, 3 array handle. -word[NL] lafact_initial; word lafact = lafact_initial; -word[NL] laaddr_initial; word laaddr = laaddr_initial; word xaf, argfact; - -// value previews are separate from optimization facts. each reference keeps -// the value proved at that position: 1 integer, 2 float bits, 3 string, 4 bytes. -word lvkind, lvvalue, lvescaped, svkind, svvalue; int xvk; word xvv; -// local name lookup keeps a head for each spelling and a shadow link per slot. -word[NL] lprev_initial; word lprev = lprev_initial; -word[256] lkeys_initial; word lkeys = lkeys_initial; -word[256] lheads_initial; word lheads = lheads_initial; int lkeycap = 256, lkeyn; -int s_new_k, s_recycle_leaf; -// dgprev: the next record sits at the end of the previous token -// chk_hook, when set, is called after each top-level item of the document in check mode: the language -// server streams diagnostics and drops a check whose source has been replaced -word chk_hook; -int cmode, chkabort, chkfuel; word dgbuf, dggrow; int dgcap, dgpos, dgn, dgopen, dgprev, dgmode; -// expand a diagnostic in progress, keeping its output offset within the buffer. -int dg_expand() { - dgbuf = chk_resize(dgbuf, dgpos + 5 + rt.outn, dgcap * 2); - dgcap = dgcap * 2; rt.outbuf = dgbuf + dgpos + 5; rt.outcap = dgcap - dgpos - 5; - return 0; -} -// diagnostic records for check mode. dg_open starts a record [severity, line, -// offset, length, text length, text...] at the current token (or just past the -// previous one when dgprev is set) and redirects the error text into it with -// outmode 2; dg_close seals it with the length of the text and restores output. -// severity 1 is an error, 2 a warning, and 4 a hint. -// completion: the language server asks what is in scope at a place in the document. probe_off is that place -// (a byte offset; -1 when nobody asks). the check keeps a copy of the locals at the last statement that starts at or -// before it, with whether the lifetime analysis has used each one up, and, when the place is a member name after -// a dot, what is before the dot. the check stops as soon as it has what was asked for -int probe_off = -1, probe_has, probe_n, probe_cap, probe_static; -char** probe_name; word probe_kind, probe_state, probe_type, probe_decl, probe_mem; -char* curinc; int incdepth, dgdrop; int incplace; // how many includes deep the compile is; a record to throw away; the place of the outermost include -int dg_close() { - if (!dgopen) return 0; - if (!dgdrop) { dgbuf[dgpos + 4] = rt.outn; dgpos = dgpos + 5 + rt.outn; dgn++; } - dgdrop = 0; - dgopen = 0; rt.outmode = dgmode; rt.outgrow = dggrow; - return 0; -} -// open a check-mode diagnostic record of the given severity. -int dg_open(int sev) { - dg_close(); - if (dgpos + 205 > dgcap) { - int cap = dgcap; while (cap < dgpos + 205) cap = cap * 2; - dgbuf = chk_resize(dgbuf, dgpos, cap); dgcap = cap; - } - if (dgprev && tpend >= 0) { - dgbuf[dgpos + 1] = tpline; - dgbuf[dgpos + 2] = tpend - 1; - dgbuf[dgpos + 3] = 1; - } - else { dgbuf[dgpos + 1] = tline; dgbuf[dgpos + 2] = toff; dgbuf[dgpos + 3] = toklen; } - dgbuf[dgpos] = sev; - // inside an included file the document has no place for the finding: hints and warnings about a library are - // left out, and an error is shown on the include that brought the file in, with where it really is - if (incdepth > 0) { - dgbuf[dgpos + 1] = incplace & 0xFFFFFF; dgbuf[dgpos + 2] = ((incplace >> 40) & 0x1FFFFF) - 1; dgbuf[dgpos + 3] = (incplace >> 24) & 0xFFFF; - if (sev != 1) dgdrop = 1; - } - rt.outbuf = dgbuf + dgpos + 5; rt.outn = 0; rt.outcap = dgcap - dgpos - 5; - dggrow = rt.outgrow; rt.outgrow = dg_expand; - rt.outmode = 2; dgopen = 1; - if (incdepth > 0 && sev == 1) { rt.put_s("in "); rt.put_s(curinc); rt.put_s(", line "); rt.put_n(tline); rt.put_s(": "); } - return 0; -} -word[256] dgseen_initial; word dgseen = dgseen_initial; int dgseen_cap = 256, dgseen_n; -// start and stop a check run. chk_begin points the diagnostics at a buffer -// the caller owns; chk_end flushes any open record and restores output. -int chk_begin(int buf, int cap) { - cmode = 1; chkabort = 0; chkfuel = 0; dgbuf = buf; dgcap = cap; dgpos = 0; dgn = 0; dgopen = 0; - nerr = 0; panic = 0; dgmode = rt.outmode; rt.fill_w(dgseen, 0, dgseen_cap); dgseen_n = 0; - return 0; -} -// end check mode and put the output back the way it was. -int chk_end() { - dg_close(); - cmode = 0; - rt.outmode = dgmode; - return 0; -} -// a hint is a finding that isn't an error: the program still compiles, and -// only check mode reports it. hint_begin(place) opens the record at a packed -// place (see tpack) and says whether to write the text; dg_close ends it. -// one hint per source position. a growing set keeps large documents linear -// instead of rescanning every previous diagnostic for each local. -int dg_hash(int key, int cap) { return ((key ^ (key >> 21)) * 0x9E37) & (cap - 1); } -int dg_dup() { - int key = ((toff + 1) << 24) | tline, i, k, cap; word old; - if (dgseen_n + dgseen_n >= dgseen_cap) { - old = dgseen; cap = dgseen_cap * 2; dgseen = chk_resize(0, 0, cap); - i = 0; - while (i < dgseen_cap) { - if (old[i]) { k = dg_hash(old[i], cap); while (dgseen[k]) k = (k + 1) & (cap - 1); dgseen[k] = old[i]; } - i++; - } - dgseen_cap = cap; - } - i = dg_hash(key, dgseen_cap); - while (dgseen[i]) { if (dgseen[i] == key) return 1; i = (i + 1) & (dgseen_cap - 1); } - dgseen[i] = key; dgseen_n++; return 0; -} -// hints on; hints small; hints off; set how many hints the code after it gets: -// on uses informational ranges, small uses compact hint markers, and off -// suppresses hints. each directive is kept with its byte -// offset, so a hint is judged by where its code is, not where it was reported. -const int HINT_SMALL = 1, HINT_ALL = 2; -word hl_off, hl_lev; int hl_n, hl_cap = 16; -int hint_set(int level, int at) { - if (!cmode || dry || isp != 0 || chkind != 3) return 0; - if (hl_n && hl_off[hl_n - 1] >= at) return 0; - if (!hl_off) { hl_cap = 16; hl_off = chk_resize(0, 0, hl_cap); hl_lev = chk_resize(0, 0, hl_cap); } - if (hl_n >= hl_cap) { hl_off = chk_resize(hl_off, hl_n, hl_cap * 2); hl_lev = chk_resize(hl_lev, hl_n, hl_cap * 2); hl_cap = hl_cap * 2; } - hl_off[hl_n] = at; hl_lev[hl_n] = level; hl_n++; - return 0; -} -// the hint level at byte offset o: 2 all, 1 small, 0 off -int hint_level(int o) { - int k = hl_n; - while (k) { k--; if (hl_off[k] <= o) return hl_lev[k]; } - return 2; -} -// start a check-mode hint at token position p, or say no when hints are off or -// this kind (HINT_SMALL or HINT_ALL) is not wanted there. -int hint_begin(int p, int kind) { - int sv, ok; - if (!cmode || dry || panic || chkabort || stubgen) return 0; - int level = hint_level(((p >> 40) & 0x1FFFFF) - 1); - if (!level) return 0; - sv = tpack(); tunpack(p); - ok = !dg_dup(); - if (ok) { if (level == 1) dg_open(4); else dg_open(3); ok = dgopen; } - tunpack(sv); - return ok; -} -// error plumbing. errat prints the "zc: line N:" prefix (or opens a record in -// check mode) and counts the error; errend ends the line and gives up after -// 20; giveup prints the total and halts (check mode: sets chkabort, which -// makes the lexer return end of file so the parse unwinds). fatal reports and -// stops at once; serr just reports; die reports and also skips input to the -// next ; or } (panic) so one mistake does not become a screen of errors. -int errat() { - if (cmode) { - dg_open(1); - } else { - rt.put_s("zc: line "); - rt.put_n(tline); - rt.put_s(": "); - } - nerr++; - return 0; -} -// finish an error line; the compiler stops after 20 errors. -int errend() { - if (cmode) { dg_close(); if (nerr >= 20) chkabort = 1; return 0; } - rt.put_c(10); - if (nerr >= 20) { rt.put_s("zc: too many errors, stopping\n"); rt.halt(); } - return 0; -} -// stop compiling after errors and report how many there were. -int giveup() { - if (cmode) { dg_close(); chkabort = 1; return 0; } - rt.put_s("zc: "); rt.put_n(nerr); if (nerr == 1) rt.put_s(" error\n"); else rt.put_s(" errors\n"); rt.halt(); - return 0; -} -// report a fatal error and stop, or record it in check mode. -int fatal(char* msg) { - errat(); rt.put_s(msg); - if (cmode) { dg_close(); chkabort = 1; return 0; } - rt.put_c(10); rt.halt(); - return 0; -} -// report a syntax error at the current token, once per recovery. -int serr(char* msg) { - if (!panic && !dry) { - errat(); - rt.put_s(msg); - errend(); - } - return 0; -} -// report an error that names something, then the message. -int serrn(char* name, char* msg) { - if (!panic && !dry) { - errat(); - rt.put_s(name); - rt.put_s(": "); - rt.put_s(msg); - errend(); - } - return 0; -} -// report an error and skip to the next top-level item. -int die(char* msg) { - serr(msg); - panic = 1; - if (tk == T_EOF) giveup(); - next(); - return 0; -} -// what the parser is looking at, for the "found" part of a message -int tokshow() { - if (tk == T_EOF) { rt.put_s("the end of the file"); return 0; } - if (tk == T_NUM) { rt.put_s("the number "); rt.put_n(tv); return 0; } - if (tk == T_STR) { rt.put_s("a string"); return 0; } - if (tk == T_BSTR) { rt.put_s("a byte string"); return 0; } - if (tk == T_FLT) { rt.put_s("a float"); return 0; } - rt.put_s("'"); rt.put_s(tv); rt.put_s("'"); - return 0; -} -// like die, and says what was found instead -int diefound(char* msg) { - if (!panic && !dry) { - errat(); - rt.put_s(msg); rt.put_s(", found "); tokshow(); - errend(); - } - panic = 1; - if (tk == T_EOF) giveup(); - next(); - return 0; -} -// die with a name shown before the message. -int dien(char* name, char* msg) { - serrn(name, msg); - panic = 1; - if (tk == T_EOF) giveup(); - next(); - return 0; -} -// four classification bits live next to each interned spelling. this works -// for names in later pool chunks too, without indexing by a fixed pool offset. -const int LSW = 1; -word lsets; -int lsmark(int set, int n) { - if (lsets) n[-2] = n[-2] | (1 << set); - return 0; -} -word lsget(int set, int n) { - if (!lsets) return 0; - return (n[-2] >> set) & 1; -} -// name resolution for the language server. while a check runs over the document -// at rsbase, every name a use or a declaration resolves to is written into rsrec -// as a record: the byte offset, kind (RS_LOCAL, RS_PARAM, RS_SYM or RS_FIELD), -// declaration or symbol, and storage facts at that position. a local names its declaration by offset -// (-1 when it has none), a symbol names its kind and flags (see rs_sym). the -// compiler's own scopes decide, so the highlighter and hover see locals and -// parameters first, then globals and built-ins, the way the compiler does. -const int RS_LOCAL = 1, RS_PARAM = 2, RS_SYM = 3, RS_FIELD = 4, RS_CAP = 32768, RS_W = 13; -int lt_on, lt_n, ltcap = 256, ltxtcap = 2048; -word lt_off, lt_at, lt_len, lt_type, ltxt; int ltxn; -int ht_n, htcap = 256; word ht_start, ht_end, ht_type; -int fieldn, fieldcap = 128; word fieldtypes, fieldoffs; char** fieldnames; -int field_note(word t, char* n, int off) { - if (!lt_on || isp != 0 || dry) return 0; - if (!fieldtypes || fieldn == fieldcap) { - if (fieldtypes) fieldcap = fieldcap * 2; - fieldtypes = chk_resize(fieldtypes, fieldn, fieldcap); - fieldnames = chk_resize(fieldnames, fieldn, fieldcap); - fieldoffs = chk_resize(fieldoffs, fieldn, fieldcap); - } - fieldtypes[fieldn] = t; fieldnames[fieldn] = n; fieldoffs[fieldn] = off; fieldn++; - return 0; -} -int field_decl(word t, char* n) { - int k = 0; - while (k < fieldn) { if (fieldtypes[k] == t && fieldnames[k] == n) return fieldoffs[k]; k++; } - return 0 - 1; -} -// where each symbol was defined, for go to definition: sdoff is the byte offset of its name + 1 -// (0 when unknown, as for built-ins) and sdsrc the include path whose text it is in (0 for the document) -word sdoff, sdsrc; -// includes: incdir is the directory of the file being compiled (0 for the program on stdin), increoot the -// workspace folder an editor passes in, inc_hook a function that gives the text of a file the editor -// sent (a host with no files of its own), incres the path an include was found at -char* incdir, increoot, incres, std_buf, icon_buf; char*(char*.m) inc_hook; int std_done, icon_done; -word rsrec, rstmp; word[32] pnoffs; word[32] pnplaces; int rsn, rscap, rsbase, nameoff; -// ownership at a reference: record word 11 holds the local's state (S_* + 1, plus 16 when -// it owns its object) and word 12 the declaration offset (+ 1) of the owner it borrows from. -// transfers are logged as events, EV_W words each: the local's declaration offset, what -// happened (EV_*), its line, and the target's declaration offset (+ 1) when there is one. -const int EV_RETURN = 1, EV_VALUE = 2, EV_UP = 3, EV_DOWN = 4, EV_STORE = 5, EV_GLOBAL = 6, EV_FREE = 7, EV_CAPTURE = 8, EV_MOVE = 9, EV_W = 4; -word evrec; int evn, evcap = 64; -// resize compiler tables. these buffers are collector-managed: m* relinquishes -// scope cleanup, and their local or global pointers keep them visible to gc. -word chk_resize(word old, int count, int capacity) { - word p = m*rt.new(capacity); - p[-4] = p[-4] | rt.F_INTERNAL; - rt.fill_w(p, 0, capacity); - if (old && count) rt.copy_w(p, old, count); - return p; -} -// lexer and documentation buffers grow before storing the next character. -int token_char(int n, int c) { - if (n + 2 > tbufcap) { tbuf = chk_resize(tbuf, n, tbufcap * 2); tbufcap = tbufcap * 2; } - tbuf[n] = c; return n + 1; -} -int doc_char(int c) { - if (dn + 2 > docbufcap) { dbuf = chk_resize(dbuf, dn, docbufcap * 2); docbufcap = docbufcap * 2; } - dbuf[dn] = c; dn++; return 0; -} -int doc_name_room(int need) { - if (need <= docnamecap) return 0; - while (docnamecap < need) docnamecap = docnamecap * 2; - dnmb = chk_resize(dnmb, 0, docnamecap); return 0; -} -// reserve compiler columns at execution time. indices remain valid after a copy. -int reserve_symbols(int need) { - int cap = symcap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - sname = chk_resize(sname, nsym, cap); - skind = chk_resize(skind, nsym, cap); - sval = chk_resize(sval, nsym, cap); - ssig = chk_resize(ssig, nsym, cap); - sflag = chk_resize(sflag, nsym, cap); - sfix = chk_resize(sfix, nsym, cap); - snext = chk_resize(snext, nsym, cap); - stype = chk_resize(stype, nsym, cap); - if (effect_symbols) effect_symbols = chk_resize(effect_symbols, nsym, cap); - if (fnmeta) fnmeta = chk_resize(fnmeta, nsym, cap); - if (ifs) ifs = chk_resize(ifs, symcap, cap); - if (svkind) { svkind = chk_resize(svkind, nsym, cap); svvalue = chk_resize(svvalue, nsym, cap); } - if (sdoff) { sdoff = chk_resize(sdoff, nsym, cap); sdsrc = chk_resize(sdsrc, nsym, cap); } - symcap = cap; return cap; -} -int reserve_locals(int need) { - int cap = loccap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - lname = chk_resize(lname, nloc, cap); - lslot = chk_resize(lslot, nloc, cap); - lhome = chk_resize(lhome, nloc, cap); - lstate = chk_resize(lstate, nloc, cap); - lowner = chk_resize(lowner, nloc, cap); - lused = chk_resize(lused, nloc, cap); - larr = chk_resize(larr, nloc, cap); - lown = chk_resize(lown, nloc, cap); - ltype = chk_resize(ltype, nloc, cap); - ltag = chk_resize(ltag, nloc, cap); - lpos = chk_resize(lpos, nloc, cap); - lreadpos = chk_resize(lreadpos, nloc, cap); - lnsz = chk_resize(lnsz, nloc, cap); - lleaf = chk_resize(lleaf, nloc, cap); - lrsoff = chk_resize(lrsoff, nloc, cap); - lrskind = chk_resize(lrskind, nloc, cap); - lptr = chk_resize(lptr, nloc, cap); - if (if_local) if_local = chk_resize(if_local, nloc, cap); - if (local_origin) local_origin = chk_resize(local_origin, nloc, cap); - if (local_origin_mask) local_origin_mask = chk_resize(local_origin_mask, nloc, cap); - if (local_cell) local_cell = chk_resize(local_cell, nloc, cap); - if (local_captured) local_captured = chk_resize(local_captured, nloc, cap); - if (local_ended) local_ended = chk_resize(local_ended, nloc, cap); - if (local_end_info) local_end_info = chk_resize(local_end_info, nloc, cap); - lafact = chk_resize(lafact, nloc, cap); laaddr = chk_resize(laaddr, nloc, cap); lprev = chk_resize(lprev, nloc, cap); - if (lvkind) { lvkind = chk_resize(lvkind, nloc, cap); lvvalue = chk_resize(lvvalue, nloc, cap); lvescaped = chk_resize(lvescaped, nloc, cap); } - o3cache = chk_resize(o3cache, nloc, cap); - loccap = cap; return cap; -} -int reserve_fixes(int need) { - int cap = fixcap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - fsite = chk_resize(fsite, fixcap, cap); - fnext = chk_resize(fnext, fixcap, cap); - fixcap = cap; return cap; -} -const word R_EXIT_BASE = STYPE_BASE + NS, R_EXITEND = R_EXIT_BASE + EXIT_WORDS; -word R_EXIT = R_EXIT_BASE; int R_EXIT_cap = EXIT_WORDS; -int reserve_R_SNAP(int need) { - int cap = R_SNAP_cap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - R_SNAP = chk_resize(R_SNAP, R_SNAP_cap, cap); R_SNAP_cap = cap; - return cap; -} -int reserve_R_QBUF(int need) { - int cap = R_QBUF_cap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - R_QBUF = chk_resize(R_QBUF, R_QBUF_cap, cap); R_QBUF_cap = cap; - return cap; -} -int reserve_R_SRC(int need) { - int cap = R_SRC_cap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - R_SRC = chk_resize(R_SRC, R_SRC_cap, cap); R_SRC_cap = cap; - return cap; -} -int reserve_R_SEQ(int need) { - int cap = R_SEQ_cap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - R_SEQ = chk_resize(R_SEQ, R_SEQ_cap, cap); R_SEQ_cap = cap; - return cap; -} -int reserve_R_CTX(int need) { - int cap = R_CTX_cap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - R_CTX = chk_resize(R_CTX, R_CTX_cap, cap); R_CTX_cap = cap; - return cap; -} -int reserve_R_LOOP(int need) { - int cap = R_LOOP_cap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - R_LOOP = chk_resize(R_LOOP, R_LOOP_cap, cap); R_LOOP_cap = cap; - if (cap / 8 > loopfacts_cap) { lpfact = chk_resize(lpfact, loopfacts_cap, cap / 8); loopfacts_cap = cap / 8; } - return cap; -} -int reserve_R_EXIT(int need) { - int cap = R_EXIT_cap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - R_EXIT = chk_resize(R_EXIT, R_EXIT_cap, cap); R_EXIT_cap = cap; - return cap; -} -int reserve_binary(int need) { - int cap = bincap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - bname = chk_resize(bname, nbin, cap); - bprec = chk_resize(bprec, nbin, cap); - bseq = chk_resize(bseq, nbin, cap); - blen = chk_resize(blen, nbin, cap); - bincap = cap; return cap; -} -int reserve_unary(int need) { - int cap = uncap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - uname = chk_resize(uname, nun, cap); - useq = chk_resize(useq, nun, cap); - ulen = chk_resize(ulen, nun, cap); - uncap = cap; return cap; -} -int reserve_assignment(int need) { - int cap = asgcap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - aname = chk_resize(aname, nasg, cap); - abin = chk_resize(abin, nasg, cap); - asgcap = cap; return cap; -} -int reserve_operators(int need) { - int cap = kopcap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - kops = chk_resize(kops, nkop, cap); - kopcap = cap; return cap; -} -// write a record for the name at byte offset off. ref is what a local names (its -// declaration's offset, or -1); sym is the symbol a RS_SYM record names, or -1. -int rs_put(int off, int kind, int ref, int sym) { - int at; - if (!rsrec || !cmode || dry || chkind != 3 || chbase != rsbase || off < 0) return 0; - if (rsn >= rscap) { - rsrec = chk_resize(rsrec, rsn * RS_W, rscap * 2 * RS_W); - rstmp = chk_resize(0, 0, rscap * 2 * RS_W); - rscap = rscap * 2; - } - at = rsn * RS_W; - rsrec[at] = off; rsrec[at + 1] = kind; rsrec[at + 2] = ref; rsrec[at + 3] = sym; - rt.fill_w(rsrec + at + 4, 0, RS_W - 4); - rsn++; - return 0; -} -// the ownership of local i at the record for the name at off -int os_at(int off, int i) { - int at, r, k = rsn; - if (!rsn || !cmode || dry || i < 0 || chkind != 3 || chbase != rsbase) return 0; - while (k && rsrec[(k - 1) * RS_W] != off) k--; - if (!k) return 0; - at = (k - 1) * RS_W; - rsrec[at + 11] = lstate[i] + 1; - if (lown[i]) rsrec[at + 11] = rsrec[at + 11] + 16; - if (lstate[i] == S_CHILD) { r = lroot(i); if (lrsoff[r] >= 0) rsrec[at + 12] = lrsoff[r] + 1; } - return 0; -} -// log a transfer of local i; the last few events are searched so a loop body is not logged twice -int ev_put(int i, int kind, int target) { - int k = evn, n = 0, t = 0; - if (!rsrec || !cmode || dry || chkind != 3 || chbase != rsbase || i < 0 || lrsoff[i] < 0) return 0; - if (target >= 0 && lrsoff[target] >= 0) t = lrsoff[target] + 1; - while (k && n < 8) { - k--; n++; - if (evrec[k * EV_W] == lrsoff[i] && evrec[k * EV_W + 1] == kind && evrec[k * EV_W + 2] == tline) return 0; - } - if (!evrec) { evcap = 64; evrec = chk_resize(0, 0, evcap * EV_W); } - if (evn >= evcap) { evrec = chk_resize(evrec, evn * EV_W, evcap * 2 * EV_W); evcap = evcap * 2; } - evrec[evn * EV_W] = lrsoff[i]; evrec[evn * EV_W + 1] = kind; evrec[evn * EV_W + 2] = tline; evrec[evn * EV_W + 3] = t; - evn++; return 0; -} -// write a record for the name token that is current. -int rs_note(int kind, int ref) { return rs_put(toff, kind, ref, 0 - 1); } -// the flags of every symbol record, read again once the check has defined the -// whole document (a function used before its definition is defined by now). -int rs_fix() { - int i = 0; - while (i < rsn) { - if (rsrec[i * RS_W + 1] == RS_SYM && rsrec[i * RS_W + 3] >= 0) { - int s = rsrec[i * RS_W + 3]; rsrec[i * RS_W + 2] = rs_sym(s); - if (skind[s] == K_CONST) vf_at(i, 1, sval[s]); - } - i++; - } - return 0; -} -// what a symbol is, as a record keeps it: its kind, plus 16 for a symbol the -// document declares (not a built-in) and 32 when that symbol has a definition -int rs_sym(int s) { - int v = skind[s]; - if (s >= ubase) v = v | 16; - if (sflag[s] & F_DEF) v = v | 32; - return v; -} -// copies record a of from to record b of to (the merge sort moves records this way). -int rs_move(word from, int a, word to, int b) { - rt.copy_w(to + b * RS_W, from + a * RS_W, RS_W); - return 0; -} -// sort the records by offset: a bottom-up merge sort through rstmp, so a long -// document costs n log n, whatever order its names come in. -int rs_sort() { - int w = 1, lo, mid, hi, i, j, k, n = rsn; - if (n < 2) return 0; - while (w < n) { - lo = 0; - while (lo < n) { - mid = lo + w; if (mid > n) mid = n; - hi = lo + w * 2; if (hi > n) hi = n; - i = lo; j = mid; k = lo; - while (i < mid && j < hi) { - if (rsrec[j * RS_W] < rsrec[i * RS_W]) { rs_move(rsrec, j, rstmp, k); j++; } - else { rs_move(rsrec, i, rstmp, k); i++; } - k++; - } - while (i < mid) { rs_move(rsrec, i, rstmp, k); i++; k++; } - while (j < hi) { rs_move(rsrec, j, rstmp, k); j++; k++; } - lo = hi; - } - i = 0; - while (i < n * RS_W) { rsrec[i] = rstmp[i]; i++; } - w = w * 2; - } - return 0; -} -// analyzer errors only count in checked, reachable code -int cerr(char* msg) { - if (fchk && live) serr(msg); - return 0; -} -// report an analyzer error about local i, named in the message. -int cerrn(int i, char* msg) { - int root = i, cause = 0; word info = 0; - if (fchk && live) { - if (rt.eq_w(msg, "used after it was given away")) { - root = lroot(i); if (local_end_info) info = local_end_info[i]; - if (!info && local_end_info) info = local_end_info[root]; - if (info) cause = info[2]; else if (local_ended) cause = local_ended[root]; - } - if (cause && !panic && !dry) { - errat(); rt.put_s(lname[i]); rt.put_s(": "); rt.put_s(msg); - rt.put_s("; owner "); if (info) rt.put_s(info[0]); else rt.put_s(lname[root]); - rt.put_s(" was transferred on line "); rt.put_n(cause & 0xFFFFFF); - if (info && info[1]) { rt.put_s(" (declared on line "); rt.put_n(info[1] & 0xFFFFFF); rt.put_c(')'); } - errend(); - } else serrn(lname[i], msg); - } - return 0; -} - -// ---- interning ---- - -// the pool: an open addressing hash table (R_IH) of pointers into a string -// pool (R_POOL). each spelling has two spare words: its symbol-chain head at -// p[-1] and editor classification bits at p[-2]. equal text returns the same -// stable handle, which lookup can use directly. -int hashs(char* s) { - int h = 0; - while (*s) { h = h * 31 + *s; s++; } - return h & (hashcap - 1); -} -// rehash the index without moving strings or their symbol-chain heads. -int reserve_names(int need) { - int cap = hashcap, previous = hashcap, i = 0, h; char* p; char** old; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - old = namehash; - namehash = chk_resize(0, 0, cap); - hashcap = cap; - while (i < previous) { - p = old[i]; - if (p) { h = hashs(p); while (namehash[h]) h = (h + 1) & (hashcap - 1); namehash[h] = p; } - i++; - } - return cap; -} -// return the interned copy of a spelling. new chunks preserve every old handle. -char* intern(char* s) { - int h; word p; int n; - if ((namecount + 1) * 2 >= hashcap) reserve_names(hashcap * 2); - h = hashs(s); - while (1) { - p = namehash[h]; - if (!p) break; - if (p[0] == s[0] && rt.eq_w(p, s)) return p; - h = (h + 1) & (hashcap - 1); - } - n = rt.len_w(s); - if (pooltop + n + 3 > poolend) { - if (poolchunk < n + 3) poolchunk = n + 3; - else if (poolchunk < 0x20000000) poolchunk = poolchunk * 2; - p = chk_resize(0, 0, poolchunk + 1); p[0] = poolblocks; poolblocks = p; - pooltop = p + 1; poolend = pooltop + poolchunk; - } - pooltop[0] = 0; pooltop[1] = 0; - p = pooltop + 2; - rt.copy_w(p, s, n + 1); - pooltop = pooltop + n + 3; - namehash[h] = p; namecount++; - return p; -} - -// ---- symbols ---- - -// each interned name keeps the index (+1) of its newest symbol in p[-1], and -// each symbol links to the one it shadows, so lookup is one step. -// remember where symbol s is defined, when the prescan made it before its definition was read -int def_note(int s, int off) { - if (cmode && !dry && !ginst && sdoff && off >= 0 && !sdoff[s]) { sdoff[s] = off + 1; sdsrc[s] = curinc; } - return 0; -} -int addsym(char* name, int kind, int val) { - int i = nsym; - if (module_name && kind != K_KW) name = module_symbol(name); - if (nsym >= symcap) reserve_symbols(nsym + 1); - if (!fnmeta) fnmeta = chk_resize(0, 0, symcap); - nsym++; - sname[i] = name; - skind[i] = kind; - sval[i] = val; - ssig[i] = 0; - sflag[i] = F_DEF; - sfix[i] = 0; - fnmeta[i] = 0; - stype[i] = 0; - snext[i] = name[-1]; - name[-1] = i + 1; - if (cmode && !dry && !sdoff) { sdoff = chk_resize(0, 0, symcap); sdsrc = chk_resize(0, 0, symcap); } - if (sdoff) { - sdoff[i] = 0; sdsrc[i] = 0; - if (cmode && !dry && !ginst && nameoff >= 0) { sdoff[i] = nameoff + 1; sdsrc[i] = curinc; } - } - return i; -} -// while an image builds, names below vislo are hidden, except keywords -int lookup_raw(char* name) { - int i = name[-1] - 1, resident; - if (syntax_host_floor && i >= syntax_host_floor && i < syntax_host_from) { - resident = i; - while (resident >= syntax_host_floor) resident = snext[resident] - 1; - if (resident >= 0) return resident; - if (skind[i] == K_GVAR || skind[i] == K_GARR) serr("a syntax handler cannot access emitted program globals"); - } - while (i >= 0) { - if (i >= vislo || skind[i] == K_KW || skind[i] == K_TYPE) return i; - i = snext[i] - 1; - } - return -1; -} -// qualify a declaration within its file namespace; generated method names retain their prefix. -char* module_symbol(char* name) { - int i = 0, j; char* b; char* q; - while (name[i]) { if (name[i] == '.') return name; i++; } - if (!module_name) return name; - if (((name[0] == 'd' && name[1] == 'o' && name[2] == 'c') || (name[0] == 's' && name[1] == 'i' && name[2] == 'g')) && name[3] == 32) { - q = methname(module_name, name + 4); b = chk_resize(0, 0, rt.len_w(q) + 5); - rt.copy_w(b, name, 4); - j = 0; while (q[j]) { b[j + 4] = q[j]; j++; } b[j + 4] = 0; - return intern(b); - } - return methname(module_name, name); -} -int lookup(char* name) { - int s, i = 0; - if (module_name) { s = lookup_raw(module_symbol(name)); if (s >= 0) return s; } - s = lookup_raw(name); if (s >= 0) return s; - while (name[i]) { if (name[i] == '.') return -1; i++; } - return lookup_raw(methname("rt", name)); -} -// modules loaded from source are tracked independently of each file's imports. -word included_modules; -int module_loaded(char* name) { - word p = included_modules; - while (p) { if (rt.eq_w(p[1], name)) return 1; p = p[0]; } - return 0; -} -int module_known(char* name) { - return module_loaded(name) || rt.eq_w(name, "rt") || rt.eq_w(name, "run") || rt.eq_w(name, "core") || rt.eq_w(name, "lsp") || rt.eq_w(name, "std") || rt.eq_w(name, "icon"); -} -int module_imported(char* name) { - word p = module_imports; - if (module_name && rt.eq_w(name, module_name)) return 1; - while (p) { if (rt.eq_w(p[1], name)) return 1; p = p[0]; } - return 0; -} -// modules reported missing in this unit, so their names are not reported again as undefined -word missing_imports; -// true when name is qualified by a module this unit was already told to include. -int missing_import_name(char* name) { - int i = 0; word p = missing_imports; - while (name[i] && name[i] != '.') i++; - if (!name[i]) return 0; - while (p) { - char* module = p[1]; int k = 0; - while (k < i && module[k] == name[k]) k++; - if (k == i && !module[k]) return 1; - p = p[0]; - } - return 0; -} -// source references require their module's explicit import; compiler-generated calls use lookup directly. -int source_lookup(char* name) { - int i = 0, s; char* owner; - while (name[i] && name[i] != '.') i++; - if (name[i]) { - owner = chk_resize(0, 0, i + 1); rt.copy_w(owner, name, i); owner[i] = 0; - if ((module_known(owner) || module_loaded(owner)) && !module_imported(owner)) { - errat(); rt.put_s("module access requires `include "); rt.put_s(owner); rt.put_s(";`"); errend(); - word link = chk_resize(0, 0, 2); link[0] = missing_imports; link[1] = owner; missing_imports = link; - // the unit already fails; resolving the name anyway keeps later diagnostics about the real code - } - } - if (module_name) { s = lookup_raw(module_symbol(name)); if (s >= 0) return s; } - return lookup_raw(name); -} -// drop the symbols above index to, which ends a scope. -int popsyms(int to) { - while (nsym > to) { nsym--; sname[nsym][-1] = snext[nsym]; } - while (ntd && tdstk[ntd * 3 - 3] >= to) ntd--; // local typedefs made above to went with them - return 0; -} -// look a name up from a plain string. -int lookups(char* s) { return lookup(intern(s)); } - -// patch nodes -int fixnew(int site, int link) { - int i = fixfree; - if (i) fixfree = fnext[i]; - else { nfixu++; i = nfixu; if (i >= fixcap) reserve_fixes(i + 1); } - fsite[i] = site; fnext[i] = link; - return i; -} -// patch every lit in a chain to point at v, and recycle the nodes -int fixall(int head, int v) { - int n; - while (head) { - setlit(fsite[head], v); - n = fnext[head]; - fnext[head] = fixfree; - fixfree = head; - head = n; - } - return 0; -} - -// ---- code emission ---- - -// code goes straight into memory at "here". hdelta shifts where it is written -// (an image build emits at one address and stores at another, see nested -// compiles), and dry turns emission off entirely, which typeof and similar -// queries use to type an expression without producing code. -// reserve contiguous words before recording an address or writing a data block. -// new runtime segments are linked by jumps; earlier code and globals stay put. -int reserve_code(int need) { - word p; int cap, top; - if (dry) return 0; - if (need < 0 || need > 0x7fffffff - 8) fatal("bad code reservation"); - if (image_active) { - if (need <= hlimit - here) return hlimit - here; - cap = image_capacity; - while (cap < here - image_org + need) { - if (cap > (0x7fffffff - image_org) / 2) fatal("image too big"); - cap = cap * 2; - } - image_buffer = chk_resize(image_buffer, here - image_org, cap); - image_capacity = cap; hdelta = image_buffer - image_org; hlimit = image_org + cap; - return hlimit - here; - } - if (need <= hlimit - here - 2) return hlimit - here - 2; - if (here + 1 >= hlimit) fatal("no room for a code link"); - cap = code_chunk; - if (cap < need + 5) cap = need + 5; - p = chk_resize(0, 0, cap); top = p + p[-5] - rt.HDR; - if (rt.syscall(9, 9, top, 0) < top) fatal("code address reservation failed"); - p[0] = rt.code_blocks; p[1] = p + 3; p[2] = top; rt.code_blocks = p; - *here = (p + 3) * 2 + 1; here[1] = O_JMP * 2; - here = p + 3; hlimit = top; - if (cap < 0x20000000) code_chunk = cap * 2; - return hlimit - here - 2; -} -// a region handed to an optimizer must fit in one contiguous segment. -int code_contiguous(int lo, int hi) { - word p = rt.code_blocks; - if (image_active || (lo >= 0 && hi <= rt.CODE_END)) return 1; - while (p) { if (lo >= p[1] && hi <= p[2] - 2) return 1; p = p[0]; } - return 0; -} -int emit(int w) { - if (dry) return 0; - if (w == OP_FLIT * 2) reserve_code(2); else reserve_code(1); - *(here + hdelta) = w; - here++; - return 0; -} -// a vm code word with the low bit set pushes the value in its upper bits -// (v + v + 1); an even word is an opcode (k + k). jmp pushes a target and jumps. -int lit(int v) { - if ((v >> 62) != 0 && (v >> 62) != -1) { - reserve_code(5); - emit((v >> 32) * 2 + 1); emit(0x100000000 * 2 + 1); op(15); - emit((v & 0xFFFFFFFF) * 2 + 1); op(17); - } else emit(v + v + 1); - return 0; -} -// emit one vm opcode. -int op(int k) { emit(k + k); return 0; } -// fill the literal placeholder at address at with value v. -int setlit(int at, int v) { - if (dry) return 0; - if ((v >> 62) != 0 && (v >> 62) != -1) fatal("literal patch too large"); - *(at + hdelta) = v + v + 1; - return 0; -} -// emit a jump to the address t. -int jmp(int t) { - lit(t); - op(O_JMP); - return 0; -} -// a lit that will be patched later; returns its address -int hole() { - int a; - reserve_code(1); a = here; - lit(rt.MW - 1); - return a; -} -// forward references. calling a function that is not defined yet creates a -// symbol with F_DEF clear whose value is the place of its first use (for the -// "never defined" error). each call site emits a placeholder literal and adds -// itself to the symbol's chain (sfix); defining the function runs fixall to -// patch every site. MW-1 is a jump target that halts, so an unpatched call -// stops the machine instead of running garbage. -int fwdfn_at(char* name, int p) { - int s = addsym(name, K_FN, p); - sflag[s] = 0; - return s; -} -// the place of the first use (line, and offset above bit 24), until it's defined -int fwdfn(char* name) { - return fwdfn_at(name, tpack()); -} -// emit a reference to function s, patched later if it is not defined yet. -int fnref(int s) { - if (syntax_host_floor && s >= syntax_host_floor && s < syntax_host_from) { - s = syntax_host_function(s); - if (s < 0) { lit(rt.MW - 1); return 0; } - } - if (sflag[s] & F_DEF) { lit(sval[s]); return 0; } - if (dry) return 0; - reserve_code(1); sfix[s] = fixnew(here, sfix[s]); - lit(rt.MW - 1); // a jump to MW-1 halts if it ever runs unpatched - return 0; -} -// emit a call to the function with this name, making a forward slot if it is not defined. -int callname(char* name) { - int s = lookup(name); - if (s < 0) { name = methname("rt", name); s = lookup(name); } - if (s < 0) s = fwdfn(name); - fnref(s); - op(O_CALL); - return 0; -} -// the address of local slot k: the frame pointer lives in the word ZFP, and a -// local is fp + k. (o3 locals live in cpu registers instead; see vaddr.) -int laddr(int k) { - lit(rt.ZFP); - op(O_LD); - if (k) { lit(k); op(O_ADD); } - return 0; -} -// an o3 variable's address: lfa(k) is fp + k as one enhancement, which the -// jit specializes on the literal k -word vaddr(int i) { - int s; - if (ltag[i] & 16) { - s = lookup(s_lfa); - if (s >= 0 && skind[s] == K_ENH) { - lit(lslot[i]); - op(XOP0 + sval[s]); - return 0; - } - } - laddr(lslot[i]); if (local_cell && local_cell[i]) op(O_LD); return 0; -} -// registers for an o3 region's variables: the cpu has 7. a variable takes the -// one its region freed last (when a scope closed), or the next unused one -// ---- o3: the optimizing region compiler's bookkeeping ---- - -// code inside an o3 region (or a function marked o3) keeps hot locals in the -// cpu's registers. the compiler does not change what it emits for them; it -// records which frame slot maps to which register and hands the finished -// region to the core through install(). the core compiles the whole region -// with those slots held in registers and writes them back to the frame when -// control leaves it. there are 7 registers, so locals past that stay in -// memory. -int o3reset() { - no3free = 0; - o3nreg = 1; - no3sl = 0; - return 0; -} -// a freed register keeps its class. a float home is a d register and an int -// home is an x register, and one var index cannot be both, so a number that -// was a float is only handed out again as a float. -int o3take(int fp) { - int i = 0, k, w = -1; - while (i < no3free) { - k = o3free[i]; - if (fp && k < 0) w = i; - if (!fp && k > 0) w = i; - i++; - } - if (w >= 0) { - k = o3free[w]; - no3free--; - o3free[w] = o3free[no3free]; - if (k < 0) return 0 - k; - return k; - } - if (o3nreg < 8) { o3nreg++; return o3nreg - 1; } - return 0; -} -// true when a type is a plain number that o3 can keep in a register. -int o3plain(word t) { - if (!t) return 0; - if (t[0] == TY_INT || t[0] == TY_CHAR || t[0] == TY_BOOL || t[0] == TY_FLOAT) return 1; - return 0; -} -// move a local into an o3 register when the region can keep it there. -int o3promote(int i) { - int r, fp; - ltag[i] = ltag[i] - (ltag[i] & 1792); - if (!o3on || larr[i] || no3sl >= 32) return 0; - fp = isfl(ltype[i]); - r = o3take(fp); - if (!r) return 0; - ltag[i] = ltag[i] + r * 256; - o3sl[no3sl] = lslot[i]; - if (o3plain(ltype[i])) r = r + 16; // 16 marks a plain number: the core need not write it back before a call - if (fp) r = 0 - r; // the install word is negative when the home is a d register - o3rg[no3sl] = r; no3sl++; - return 0; -} -// a fixed T[?] can't change its items or its length, so o3 code keeps both in -// two hidden variables, loaded when the array variable is set (or when the -// region starts), and indexes through them -int o3mkcache(int i) { - int k; - if (!o3on || ltype[i][0] != TY_DYN || !ltype[i][3] || tbytes(ltype[i][4])) return 0; - k = newloc(s_hitems); fslots++; ltag[k] = 16; o3promote(k); - newloc(s_hlen); fslots++; ltag[k + 1] = 16; o3promote(k + 1); - o3cache[i] = k; - return 0; -} -// reload the o3 register cache of a local from its memory slot. -int o3refresh(int i) { - int k = o3cache[i]; - if (!k) return 0; - vaddr(i); op(O_LD); lit(2); op(O_ADD); op(O_LD); vaddr(k); op(O_ST); - vaddr(i); op(O_LD); op(O_LD); vaddr(k + 1); op(O_ST); - return 0; -} -// hands a region to the cpu: install kind 2 is [-, 2, lo, hi, n, slot, register, ..., fslots, argc, fnstart] -// fslots, argc and the prologue address sit in o3fs, o3na, o3fn0. a bare o3 block leaves the prologue at -1, so a function that starts at address 0 still binds. -int o3region(int lo, int hi, word sl, word rg, int n) { - int i = 0; - if (hdelta || lo >= hi || !code_contiguous(lo, hi)) return 0; - o3dsc[0] = 0; o3dsc[1] = 2; o3dsc[2] = lo; o3dsc[3] = hi; o3dsc[4] = n; - while (i < n) { o3dsc[5 + i + i] = sl[i]; o3dsc[6 + i + i] = rg[i]; i++; } - o3dsc[5 + n + n] = o3fs; o3dsc[6 + n + n] = o3na; o3dsc[7 + n + n] = o3fn0; - rt.install(o3dsc); - return 0; -} -// o3 blocks go to the cpu once their function is done, so every jump in them is final -int o3flush() { - int i = 0; word p; - o3fs = 0; o3na = 0; o3fn0 = -1; /* -1 is not a function; a prologue at address 0 is */ - while (i < no3pend) { p = o3pend + i * 67; o3region(p[0], p[1], p + 3, p + 35, p[2]); i++; } - no3pend = 0; - return 0; -} -// a string literal: the characters (one per word, then a 0) are emitted into -// the code stream behind a jump, and the literal's value is their address -int strlit(char* s) { - int j = hole(), a; - op(O_JMP); - reserve_code(rt.len_w(s) + 1); a = here; - while (*s) { emit(*s); s++; } - emit(0); - setlit(j, here); - lit(a); - return 0; -} - -// ---- input sources ---- - -// frame: 0 kind (0 stdin, 1 token list, 2 string) 1 object 2 index 3 end -// 4 transparent 5..7 saved token 8..10 saved char state 11 owned 12 saved line -// 13 saved token line 14 saved lookahead count 15..30 saved lookahead 31 saved char buffer -// a backtick ends the source: it is how a program tells the compiler where its -// own text stops and the program's input begins. but only a backtick the -// lexer would read as a token counts. srcend is fed every character of a -// stdin source in order and follows just enough of the lexer's rules to know -// whether it is inside a // comment, a /* */ comment, a string or a char -// literal (a backslash escapes the next character in the last two). it -// returns 1 for a backtick outside all of them. readsrc and nextch both use -// it, so a backtick in a comment or a string is just a character. -int srcend(int c) { - if (qst == 0) { - if (c == 96) return 1; - if (c == 34) qst = 3; - else if (c == 39) qst = 4; - else if (c == '*' && qp == '/') { qst = 2; qp = 0; return 0; } - else if (c == '/' && qp == '/') { qst = 1; qp = 0; return 0; } - qp = c; - return 0; - } - if (qst == 1) { - if (c == 10) { qst = 0; qp = 0; } - return 0; - } - if (qst == 2) { - if (c == '/' && qp == '*') { qst = 0; qp = 0; return 0; } - qp = c; - return 0; - } - if (qesc) { qesc = 0; return 0; } - if (c == 92) { qesc = 1; return 0; } - if ((qst == 3 && c == 34) || (qst == 4 && c == 39)) { qst = 0; qp = 0; } - return 0; -} -// the character level of the lexer: ch is the current char (-1 at the end), -// and chkind says where it comes from (0 stdin, 3 a byte string, anything -// else a zero-terminated char* string). a stdin source ends at a backtick (see srcend). -int nextch() { - if (chkind == 3) { // the usual case: the unit readsrc packed - ch = (chbase[chptr >> 2] >> ((chptr & 3) * 8)) & 255; - if (ch) chptr++; else ch = -1; - } else if (chkind == 0) { - if (qdone || rt.eof()) { ch = -1; return 0; } - ch = rt.get_c() & 255; - // a read that waited for a slow pipe and found its end returns -1: that is the end, not a 0xFF byte - if (ch == 255 && rt.eof()) { qdone = 1; ch = -1; return 0; } - if (srcend(ch)) { qdone = 1; ch = -1; return 0; } // a backtick ends the source - } else { - ch = *chptr & 255; - if (ch) chptr++; else ch = -1; - } - if (ch == 10) line++; - return 0; -} -// the place of a token travels with its line: line in the low 24 bits, then -// the length (16 bits), the offset + 1 (21 bits) and its glue (2 bits, so a -// replayed token list still tells u*x from a product) -int tpack() { return tline | (toklen << 24) | ((toff + 1) << 40) | ((tglue & 3) << 61); } -// restore the token position that tpack saved in a word. -int tunpack(int x) { - tline = x & 0xFFFFFF; - toklen = (x >> 24) & 0xFFFF; - toff = ((x >> 40) & 0x1FFFFF) - 1; - tglue = (x >> 61) & 3; - return 0; -} -// chars consumed so far in the source being read: the index of the char in ch -// (or of the end, once the source is used up). a stdin source reports -1. -int cpos() { - if (chkind == 3) { if (ch < 0) return chptr; return chptr - 1; } - if (chkind == 2) { if (ch < 0) return chptr - chbase; return chptr - chbase - 1; } - return 0 - 1; -} -// push a new input source. the current token, char state and lookahead are -// saved into a 32 word frame so src_pop can put them back exactly. kind 0 is -// stdin, 1 a token list (obj[0] is the count, then triples of kind, value and -// packed place: this is how macros, quote/grab and feed replay code), 2 a -// char* string and 3 a byte string. trans means "transparent": when a token -// list runs out the reader pops it and carries on in the source below, which -// is what lets a fed list splice into the text around it. owned means the -// frame frees obj when it pops. -int src_push(int kind, word obj, int trans, int owned) { - word f; int i; - isp++; - reserve_R_SRC((isp + 1) * 32); - f = R_SRC + isp * 32; - f[0] = kind; f[1] = obj; f[2] = 0; f[3] = 0; f[4] = trans; f[11] = owned; - f[5] = tk; f[6] = tv; f[7] = tglue; f[8] = ch; f[9] = chkind; f[10] = chptr; - f[12] = line; f[13] = tpack(); f[14] = pq_n; f[31] = chbase; - i = 0; - while (i < pq_n) { - f[15 + i * 4] = pq_tk[i]; - f[16 + i * 4] = pq_tv[i]; - f[17 + i * 4] = pq_gl[i]; - f[18 + i * 4] = pq_ln[i]; - i++; - } - pq_n = 0; - if (kind == 1) f[3] = obj[0]; - if (kind == 0) { chkind = 0; qdone = 0; qst = 0; qp = 0; qesc = 0; line = 1; nextch(); } - if (kind == 2) { chkind = 2; chptr = obj; chbase = obj; line = 1; nextch(); } - if (kind == 3) { chkind = 3; chbase = obj; chptr = 0; line = 1; nextch(); } - return 0; -} -// leave the current source and restore the one beneath it -int src_pop() { - word f = R_SRC + isp * 32; int i = 0; - tk = f[5]; tv = f[6]; tglue = f[7]; ch = f[8]; chkind = f[9]; chptr = f[10]; - line = f[12]; tunpack(f[13]); pq_n = f[14]; chbase = f[31]; - while (i < pq_n) { - pq_tk[i] = f[15 + i * 4]; - pq_tv[i] = f[16 + i * 4]; - pq_gl[i] = f[17 + i * 4]; - pq_ln[i] = f[18 + i * 4]; - i++; - } - isp--; - if (f[11]) rt.free(f[1]); - f[1] = 0; // a dead source pointer would pin a quarantined block - return 0; -} -// the lexer's character classes. an operator is built one character at a time -// for as long as the text so far is still a prefix of some known operator -// (opprefix), so a program that adds an operator with add_binop changes how -// the next source is split into tokens without any other bookkeeping. -// the operator characters are + - * / % = < > ! & | ^ ~ ? : . @ # $ . those below -// 64 are a bit mask indexed from 33, the rest are four compares -int isopch(int c) { - if (c < 33 || c > 126) return 0; - if (c < 64) return (0x7a00763d >> (c - 33)) & 1; - return c == 64 || c == 94 || c == 124 || c == 126; -} -// is buf[0..n) followed by c the start of some operator the lexer knows? -int opprefix(int n, int c) { - int i = 0; word k; int j; - while (i < nkop) { - k = kops[i]; - j = 0; - while (j < n && k[j] == tbuf[j]) j++; - if (j == n && k[n] == c) return 1; - i++; - } - return 0; -} -// true for a decimal digit. -int isdig(int c) { return c >= '0' && c <= '9'; } -// true for a letter or an underscore. -int isal(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; } -// a letter, digit or _ : the characters after the first one of a name -int isidc(int c) { - if (c >= 'a') return c <= 'z'; - if (c >= 'A') return c <= 'Z' || c == '_'; - return c >= '0' && c <= '9'; -} -// the value of a hex digit, or a value past 15 when c is not one. -int hexv(int c) { - if (isdig(c)) return c - '0'; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - return -1; -} -// one possibly escaped character of a string or char literal -int escch() { - int c = ch; - nextch(); - if (c != 92) return c; - c = ch; - nextch(); - if (c == 'n') return 10; - if (c == 't') return 9; - if (c == 'r') return 13; - if (c == '0') return 0; - if (c == 'e') return 27; - return c; -} -// finish lexing an operator whose first n characters are in tbuf -int oplex(int n) { - while (ch >= 0 && isopch(ch) && opprefix(n, ch)) { n = token_char(n, ch); nextch(); } - tbuf[n] = 0; - tk = T_OP; tv = intern(tbuf); - return 0; -} -// per-local type flags kept in ltag. these are tiny accessors; the whole bit -// layout is: 0..3 the u/m/d tag, 4 o3, 5 float, 6 byte string, 8..10 the o3 -// register, 12.. the hint bits (see LASG and friends). -// bit 5 of ltag: this local holds a float. the low bits are the u/m/d tag and bit 4 is o3. -word getflt(int i) { return (ltag[i] >> 5) & 1; } -// mark local i as holding a float, or clear the mark. -int setflt(int i, int f) { - if (f) ltag[i] = ltag[i] | 32; - else ltag[i] = ltag[i] - (ltag[i] & 32); - return 0; -} -// bit 6 of ltag: this local holds a byte string. bit 5 is the float. -word getstr(int i) { return (ltag[i] >> 6) & 1; } -// mark local i as holding a string, or clear the mark. -int setstr(int i, int f) { - if (f) ltag[i] = ltag[i] | 64; - else ltag[i] = ltag[i] - (ltag[i] & 64); - return 0; -} -// b"...": length word, then bytes packed 8 per word, little-endian, and a 0 -// after the last byte. a jump skips the data. the address pushed is the length. -int bstrdata(char* s) { - int a, n = 0, i = 0, w, k; - while (s[n]) n++; - reserve_code((n >> 3) + 2); a = here; - emit(n); - while (i < n) { - w = 0; k = 0; - while (k < 8 && i < n) { w = w | (s[i] << (k * 8)); k++; i++; } - emit(w); - } - if (!(n & 7)) emit(0); - return a; -} -int bstrlit(char* s) { - int j = hole(), a; - op(O_JMP); a = bstrdata(s); setlit(j, here); lit(a); return 0; -} -// ---- float literals and float code ---- - -// a float is one word holding the ieee bits. the lexer collects the literal's -// text in tbuf and fbits asks the core (opcode 611) to convert it, so the -// compiler needs no float parser of its own. float operations are single vm -// opcodes (OP_FADD ...). an int operand is converted on the fly, and a -// comparison leaves a plain 0/1 behind. xfloat says whether the expression -// just compiled is a float. -int fbits() { return asm { tbuf @ #1222 }; } -// collect an exponent (e+3) onto the literal in tbuf; n is the length so far -int takeexp(int n) { - if (ch != 'e' && ch != 'E') return n; - n = token_char(n, ch); - nextch(); - if (ch == '+' || ch == '-') { n = token_char(n, ch); nextch(); } - if (!isdig(ch)) { die("bad float"); return n; } - while (isdig(ch)) { n = token_char(n, ch); nextch(); } - return n; -} -// the literal in tbuf[0..n) is a float: hand it back as a T_FLT token -int floatok(int n) { - tbuf[n] = 0; - tk = T_FLT; - tv = fbits(); - return 0; -} -// the binary operators that have a float form -int isfop(int o) { - if (o == s_plus || o == s_minus || o == s_star || o == s_slash) return 1; - if (o == s_lt || o == s_gt || o == s_le || o == s_ge || o == s_eqeq || o == s_ne) return 1; - return 0; -} -// emit float arithmetic or comparison o for operands that are floats (lf, rf). -// an int left operand sits under the right one, so it is converted with -// swap / i2f / swap. > and >= are < and <= with the operands swapped, and != -// is == followed by not. -int femit(int o, int lf, int rf) { - if (!lf) { op(O_SWAP); op(OP_I2F); op(O_SWAP); } - if (!rf) op(OP_I2F); - if (o == s_plus) { op(OP_FADD); xfloat = 1; return 0; } - if (o == s_minus) { op(OP_FSUB); xfloat = 1; return 0; } - if (o == s_star) { op(OP_FMUL); xfloat = 1; return 0; } - if (o == s_slash) { op(OP_FDIV); xfloat = 1; return 0; } - if (o == s_eqeq) { op(OP_FEQ); xfloat = 0; return 0; } - if (o == s_ne) { op(OP_FEQ); lit(1); op(18); xfloat = 0; return 0; } - if (o == s_lt) { op(OP_FLT); xfloat = 0; return 0; } - if (o == s_gt) { op(O_SWAP); op(OP_FLT); xfloat = 0; return 0; } - if (o == s_le) { op(OP_FLE); xfloat = 0; return 0; } - if (o == s_ge) { op(O_SWAP); op(OP_FLE); xfloat = 0; return 0; } - xfloat = 0; - return 0; -} -// true when a type is a float, the same as isfl. -word isfloat(int t) { return isfl(t); } -// float, f32, and f32's packed twin: everything computed as a double -word isfl(word t) { return t && t[0] == TY_FLOAT; } -// ±0 is false, like 0.0. every other float, including NaN, is true. the vm's -// jz stays an integer test, so a float is compared with +0 first. -int ftest() { - if (!xfloat) return 0; - op(OP_FLIT); emit(0); op(OP_FEQ); lit(1); op(18); - xfloat = 0; xty = tbool; - return 0; -} -// a float stored into an int becomes an int, and an int stored into a float -// becomes a float. word keeps the bits and drops the float mark. -int fcoerce(word dst) { - if (dst && treadonly(xty) && isref(xty) && !treadonly(dst) && dst[0] != TY_VAR) serr("const storage cannot be converted to a mutable reference"); - vf_coerce(dst, 1); - if (!dst || dst[0] == TY_VAR) return 0; - if (isfl(dst)) { - if (!xfloat) { op(OP_I2F); xfloat = 1; xty = tfloat; } - // an f32 variable holds a value rounded to single precision; a packed - // element is rounded when pkst stores it - if (dst == tf32 && xty != tf32 && !ispk(xty)) { xenh("zf2s"); xenh("zs2f"); xty = tf32; } - return 0; - } - if (!xfloat) { - if (tbytes(dst) && !samesz(dst, xty)) tnorm(dst); - return 0; - } - if (isnum(dst)) { op(OP_F2I); xfloat = 0; xty = dst; if (tbytes(dst)) tnorm(dst); return 0; } - if (dst == tword) xfloat = 0; - return 0; -} -// sized integers. a value stored into a variable, field, parameter or array -// element of type t is cut to size first: unsigned types are masked and signed -// ones are masked and then sign-extended (xor the sign bit, subtract it). -// samesz says a value of type b is already in range for type a. -word samesz(word a, word b) { return tbytes(a) && tbytes(b) == tbytes(a) && (a[6] & 1) == (b[6] & 1); } -// narrow the integer on the stack to the width of its type. -int tnorm(word t) { - int b = tbytes(t), s; - if (!b || t[0] != TY_INT) return 0; - lit((1 << (8 * b)) - 1); op(16); - if (t[6] & 1) { s = 1 << (8 * b - 1); lit(s); op(18); lit(s); op(O_SUB); } - return 0; -} -// [byte address] -> [value]: load a packed element of type t (the twin) -int pkld(word t) { - int b = t[5]; - if (b == 1) op(612); else if (b == 2) op(614); else op(616); - pkfix(t); - return 0; -} -// a zero extended packed element on the stack becomes the value of type t -int pkfix(word t) { - int b = t[5], s; - if (t[0] == TY_FLOAT) { xenh("zs2f"); return 0; } // f32: the bits widen to a double - if (t[6] & 1) { s = 1 << (8 * b - 1); lit(s); op(18); lit(s); op(O_SUB); } - return 0; -} -// [value byte-address] -> []: store a packed element of type t -int pkst(word t) { - int b = t[5]; - if (t[0] == TY_FLOAT) { op(O_SWAP); xenh("zf2s"); op(O_SWAP); op(617); return 0; } // f32: round the double to single bits - if (b == 1) op(613); else if (b == 2) op(615); else op(617); - return 0; -} -// an integer address and a field name: the name has to belong to one struct. -// field lookup on a bare address, for untyped code such as p.x where p is a -// plain word. if exactly one struct in the type area has a field called n, that -// is the one meant; with two or more it is ambiguous (the caller reports it). -int fieldscan(int n) { - int i = 0; word t; int k, hit = 0; - fldst = 0; fldi = -1; - while (i < ntl) { - t = typelist[i]; - if (t[0] == TY_STRUCT && t[3] >= 0) { - k = 0; - while (k < t[3]) { - if (t[4][k * 3] == n) { - if (hit) return 2; - hit = 1; fldst = t; fldi = k; - } - k++; - } - } - i++; - } - return hit; -} -// a // comment, with ch on its second slash. one that starts a line joins the -// doc comment being collected, if the line above was part of it, or starts a -// new one; a comment after code on the same line is not documentation. -int doccomment(int nl) { - int cl = line, start; - nextch(); - if (ch == ':') { while (ch >= 0 && ch != 10) nextch(); dn = 0; dsect = 1; dlast = cl; docpending = 1; return 0; } - if (!nl) { while (ch >= 0 && ch != 10) nextch(); return 0; } - if (dlast != cl - 1) { dn = 0; dsect = 0; } - if (ch == 32) nextch(); - start = dn; - while (ch >= 0 && ch != 10) { doc_char(ch); nextch(); } - while (dn > start && (dbuf[dn - 1] == 32 || dbuf[dn - 1] == 9 || dbuf[dn - 1] == 13)) dn--; - // a comment block with a section heading is section text: it documents nothing - if (dn - start >= 4 && dbuf[start] == '-' && dbuf[start + 1] == '-' && dbuf[dn - 1] == '-' && dbuf[dn - 2] == '-') dsect = 1; - if (dsect) { dn = 0; dlast = cl; docpending = 1; return 0; } - doc_char(10); - dlast = cl; docpending = 1; - return 0; -} -// block documentation can end on the declaration's line. remove the optional -// leading star on each line, while leaving markdown inside the comment intact. -int docblock(int nl) { - int cl = line, bol = 1, c, adj = dsect && dlast == cl - 1; - if (!nl || (dn && dlast != cl - 1)) dn = 0; - if (!adj) dsect = 0; - nextch(); - if (ch == '*') { nextch(); if (ch == '/') { nextch(); return 0; } } - while (ch >= 0) { - if (bol) { - while (ch == 32 || ch == 9) nextch(); - if (ch == '*') { nextch(); if (ch == '/') { cl = line; nextch(); break; } if (ch == 32) nextch(); } - bol = 0; - } - c = ch; nextch(); - if (c == '*' && ch == '/') { cl = line; nextch(); break; } - doc_char(c); - if (c == 10) bol = 1; - } - while (dn && (dbuf[dn - 1] == 32 || dbuf[dn - 1] == 9 || dbuf[dn - 1] == 10 || dbuf[dn - 1] == 13)) dn--; - doc_char(10); dlast = cl; docpending = 1; - if (adj) dn = 0; - return 0; -} -// the lexer proper: skip white space and comments, then read one token into -// tk / tv with its place in tline / toff. numbers (decimal, hex, floats), -// names, char and string literals (b"..." is a byte string), and operators -// all start here. tglue is 1 when the token touches the one before it with no -// white space, 0 when it does not, and 3 for a char literal (so the magic -// number hint can leave 'a' alone). asm blocks use it to read -5 as one -// literal. -// commented numeric literals are recorded by source offset, including lookahead. -word literal_docs; int literal_doc_n, literal_doc_cap = 32, token_block_doc; -int literal_documented(int off) { - int key = off + 1, i; - if (!literal_doc_n || key <= 0) return 0; - i = dg_hash(key, literal_doc_cap); - while (literal_docs[i]) { if (literal_docs[i] == key) return 1; i = (i + 1) & (literal_doc_cap - 1); } - return 0; -} -int literal_doc_note(int off) { - int key = off + 1, i, cap, k; word old; - if (literal_documented(off)) return 0; - if (!literal_docs) literal_docs = chk_resize(0, 0, literal_doc_cap); - if ((literal_doc_n + 1) * 2 >= literal_doc_cap) { - old = literal_docs; cap = literal_doc_cap * 2; literal_docs = chk_resize(0, 0, cap); - i = 0; - while (i < literal_doc_cap) { if (old[i]) { k = dg_hash(old[i], cap); while (literal_docs[k]) k = (k + 1) & (cap - 1); literal_docs[k] = old[i]; } i++; } - literal_doc_cap = cap; - } - i = dg_hash(key, literal_doc_cap); while (literal_docs[i]) i = (i + 1) & (literal_doc_cap - 1); - literal_docs[i] = key; literal_doc_n++; return 0; -} -int lex1() { - int n = 0, v, dg, nl; - token_block_doc = 0; - if (pendot) { - pendot = 0; tglue = 1; tline = line; toff = cpos(); - tbuf[0] = '.'; tbuf[1] = 0; tk = T_OP; tv = intern(tbuf); - return 0; - } - tglue = 1; - nl = tline != line; - while (1) { - while (ch == 32 || ch == 9 || ch == 10 || ch == 13) { if (ch == 10) nl = 1; nextch(); tglue = 0; } - if (ch != '/') break; - nextch(); - if (ch == '/') { - if (docs_on) doccomment(nl); else while (ch >= 0 && ch != 10) nextch(); - tglue = 0; continue; - } - if (ch == '*') { - token_block_doc = 1; - if (docs_on) { docblock(nl); tglue = 0; continue; } - nextch(); - while (ch >= 0) { if (ch == '*') { nextch(); if (ch == '/') break; } else nextch(); } - nextch(); tglue = 0; continue; - } - tline = line; toff = cpos() - 1; tbuf[0] = '/'; oplex(1); return 0; - } - tline = line; toff = cpos(); - if (docpending) { - dline = 0; - if (dn && (tline == dlast || tline == dlast + 1)) dline = tline; - docpending = 0; - } - if (ch < 0) { tk = T_EOF; tv = 0; return 0; } - if (isdig(ch)) { - v = 0; n = 0; - if (ch == '0') { - nextch(); - if (ch == 'x' || ch == 'X') { - nextch(); - while ((dg = hexv(ch)) >= 0) { v = v * 16 + dg; nextch(); } - tk = T_NUM; tv = v; return 0; - } - tbuf[0] = '0'; n = 1; - } - while (isdig(ch)) { n = token_char(n, ch); v = v * 10 + ch - '0'; nextch(); } - if (ch == 'e' || ch == 'E') { n = takeexp(n); return floatok(n); } - if (ch == '.') { - nextch(); - if (isdig(ch) || ch == 'e' || ch == 'E') { - n = token_char(n, '.'); - while (isdig(ch)) { n = token_char(n, ch); nextch(); } - return floatok(takeexp(n)); - } - if (isal(ch)) { pendot = 1; tk = T_NUM; tv = v; return 0; } // 1.field - n = token_char(n, '.'); - return floatok(n); - } - tk = T_NUM; tv = v; return 0; - } - if (isal(ch)) { - while (isidc(ch)) { n = token_char(n, ch); nextch(); } - tbuf[n] = 0; - if (n == 1 && tbuf[0] == 'b' && ch == 34) { - n = 0; nextch(); - while (ch >= 0 && ch != 34) { v = escch(); n = token_char(n, v); } - nextch(); - tbuf[n] = 0; - tk = T_BSTR; tv = intern(tbuf); return 0; - } - if (ch == '.' && module_known(tbuf) && findloc(intern(tbuf)) < 0) { - n = token_char(n, '.'); nextch(); - while (isidc(ch)) { n = token_char(n, ch); nextch(); } - tbuf[n] = 0; - } - tk = T_ID; tv = intern(tbuf); return 0; - } - if (ch == 39) { - nextch(); - tv = escch(); - if (ch != 39) die("bad character literal"); - nextch(); - tk = T_NUM; - tglue = 3; - return 0; - } - if (ch == 34) { - nextch(); - while (ch >= 0 && ch != 34) { v = escch(); n = token_char(n, v); } - nextch(); - tbuf[n] = 0; - tk = T_STR; tv = intern(tbuf); return 0; - } - if (ch == '.') { - nextch(); - if (isdig(ch)) { - n = 0; tbuf[0] = '.'; n = 1; - while (isdig(ch)) { n = token_char(n, ch); nextch(); } - return floatok(takeexp(n)); - } - tbuf[0] = '.'; - if (isopch(ch) && opprefix(1, ch)) return oplex(1); - tbuf[1] = 0; - tk = T_OP; tv = intern(tbuf); - return 0; - } - tbuf[0] = ch; - nextch(); - if (isopch(tbuf[0])) return oplex(1); - tbuf[1] = 0; - tk = T_OP; tv = intern(tbuf); - return 0; -} -// lex1 plus the token's length, so the language server knows its extent -int lex() { - lex1(); - if (cmode && tk == T_NUM && token_block_doc && chkind == 3 && chbase == rsbase && toff >= 0) { - literal_doc_note(toff); - } - if (toff < 0) toklen = 0; else toklen = cpos() - toff; - return 0; -} -// one token of lookahead: peek() fills pk_*, next() takes it first -int next() { - int i = 1; - ntok++; - if (toff >= 0) { tpend = toff + toklen; tpline = tline; } - if (pq_n) { - tk = pq_tk[0]; tv = pq_tv[0]; tglue = pq_gl[0]; tunpack(pq_ln[0]); - while (i < pq_n) { - pq_tk[i - 1] = pq_tk[i]; - pq_tv[i - 1] = pq_tv[i]; - pq_gl[i - 1] = pq_gl[i]; - pq_ln[i - 1] = pq_ln[i]; - i++; - } - pq_n--; - if (grec) grec_add(); - return 0; - } - next0(); - if (grec) grec_add(); - return 0; -} -// the k-th token after the current one (k from 0 to 3), into pk_tk and pk_tv -int peekat(int k) { - int a, b, c, e; - while (pq_n <= k) { - a = tk; b = tv; c = tglue; e = tpack(); - next0(); - pq_tk[pq_n] = tk; pq_tv[pq_n] = tv; pq_gl[pq_n] = tglue; pq_ln[pq_n] = tpack(); pq_n++; - tk = a; tv = b; tglue = c; tunpack(e); - } - pk_tk = pq_tk[k]; pk_tv = pq_tv[k]; - return 0; -} -// look at the next token without consuming it. -int peek() { return peekat(0); } -// the lowest level of "next token": from a token list if one is the current -// source, otherwise from the lexer. in check mode after an abort it returns -// end of file forever, which unwinds every parsing loop without any special -// case in them. -int next0() { - word f, q; int i; - if (chkabort) { tk = T_EOF; tv = 0; chkfuel++; if (chkfuel > 3000000) rt.halt(); return 0; } - f = R_SRC + isp * 32; - if (f[0] != 1) { lex(); return 0; } - i = f[2]; - if (i < f[3]) { - q = f[1]; - tk = q[1 + i * 3]; - tv = q[2 + i * 3]; - tunpack(q[3 + i * 3]); - f[2] = i + 1; - return 0; - } - if (!f[4]) { tk = T_EOF; tv = 0; return 0; } - src_pop(); // a fed list ran out: continue with the token that followed it - return 0; -} -// token tests. s is an interned spelling, so these are pointer compares. -int is_op(int s) { return tk == T_OP && tv == s; } -// true when the current token is the name s. -int is_id(int s) { return tk == T_ID && tv == s; } -// consume the current token if it is the operator or name s, and say whether it did. -int accept(int s) { - if ((tk == T_OP || tk == T_ID) && tv == s) { next(); return 1; } - return 0; -} -// accept(s) consumes the token if it is s. expect reports "expected s" at the -// end of the previous token (dgprev) when it is missing, and goes into panic. -int expect(int s) { - if (!accept(s)) { - if (!panic && !dry) { - dgprev = 1; - errat(); - dgprev = 0; - rt.put_s("expected '"); rt.put_s(s); rt.put_s("', found "); tokshow(); - errend(); - } - panic = 1; - if (tk == T_EOF) giveup(); - } - return 0; -} -// read an identifier and return its interned handle -int name() { - int n = tv; - if (tk != T_ID) diefound("expected a name"); - nameoff = toff; - next(); - return n; -} - -// ---- operator tables ---- - -// a sequence is written like asm: one-char mnemonics, numbers, and names -// (resolved when the operator is used, so image builds stay self-contained) -int seqasm(char* s) { - int start = seqtop, n, neg, v, k, c; - while (1) { - while (*s == 32) s++; - if (!*s) break; - n = 0; - while (s[n] && s[n] != 32) { n = token_char(n, s[n]); } - tbuf[n] = 0; - s = s + n; - reserve_R_SEQ(seqtop + 2); - neg = tbuf[0] == '-' && isdig(tbuf[1]); - if (isdig(tbuf[neg])) { - v = 0; k = neg; - while (tbuf[k]) { v = v * 10 + tbuf[k] - '0'; k++; } - if (neg) v = 0 - v; - if ((v >> 62) != 0 && (v >> 62) != -1) { R_SEQ[seqtop] = SEQWORD; R_SEQ[seqtop + 1] = v; seqtop = seqtop + 2; } - else { R_SEQ[seqtop] = v + v + 1; seqtop++; } - } else if (n == 1 && (c = mnemidx(tbuf[0])) >= 0) { - R_SEQ[seqtop] = c + c; seqtop++; - } else { - R_SEQ[seqtop] = SEQREF; R_SEQ[seqtop + 1] = intern(tbuf); seqtop = seqtop + 2; - } - } - return start; -} -// the index of an assembler mnemonic in the table, or -1 when there is none. -int mnemidx(int c) { - int i = 0; - while (mnem[i]) { - if (mnem[i] == c) return i; - i++; - } - return -1; -} -// emit n words of a stored sequence, resolving its references. -int emitseq(int a, int n) { - int e = a + n, s; - while (a < e) { - if (R_SEQ[a] == SEQWORD) { lit(R_SEQ[a + 1]); a = a + 2; } - else if (R_SEQ[a] == SEQREF) { - s = lookup(R_SEQ[a + 1]); - if (s < 0) s = fwdfn(R_SEQ[a + 1]); - if (skind[s] == K_FN) { - fnref(s); - } else if (skind[s] == K_ENH) op(XOP0 + sval[s]); else lit(sval[s]); - a = a + 2; - } else { emit(R_SEQ[a]); a++; } - } - return 0; -} -// add an operator name to the table of known operators, once. -int addkop(int p) { - int i = 0; - while (i < nkop) { - if (kops[i] == p) return 0; - i++; - } - reserve_operators(nkop + 1); - kops[nkop] = p; - nkop++; - return 0; -} -// find a binary operator by its name. -int binfind(int p) { - int i = nbin; - while (i) { - i--; - if (bname[i] == p) return i; - } - return -1; -} -// find a unary operator by its name. -int unfind(int p) { - int i = nun; - while (i) { - i--; - if (uname[i] == p) return i; - } - return -1; -} -// find an assignment operator by its name. -int asgfind(int p) { - int i = nasg; - while (i) { - i--; - if (aname[i] == p) return i; - } - return -1; -} -// add or replace a binary operator. code is a sequence string, or 0 for the -// built-in && (prec 2) and || (prec 1) behaviour, chosen by -1 / -2 in prec. -int add_binop(char* nm, int prec, char* code) { - int p = intern(nm), i = binfind(p), a, n; - if (i < 0) { reserve_binary(nbin + 1); i = nbin; nbin++; } - bname[i] = p; bprec[i] = prec; - // && (-1) and || (-2) short-circuit: they bind below |, and && above ||, as in c - if (prec < 0) { bprec[i] = 3 + prec; blen[i] = prec; bseq[i] = 0; addkop(p); return i; } - bseq[i] = seqasm(code); blen[i] = seqtop - bseq[i]; - addkop(p); - // and its compound assignment, x op= y - n = rt.len_w(nm); - while (1002 + n > tbufcap) { tbuf = chk_resize(tbuf, 0, tbufcap * 2); tbufcap = tbufcap * 2; } - rt.copy_w(tbuf + 1000, nm, n); - tbuf[1000 + n] = '='; tbuf[1001 + n] = 0; - a = intern(tbuf + 1000); - if (prec >= 8 || prec == 5 || prec == 4 || prec == 3) { - if (asgfind(a) < 0) { reserve_assignment(nasg + 1); aname[nasg] = a; nasg++; } - abin[asgfind(a)] = i; - addkop(a); - } - return i; -} -// register a unary operator and the sequence it expands to. -int add_unop(char* nm, char* code) { - int p = intern(nm), i = unfind(p); - if (i < 0) { reserve_unary(nun + 1); i = nun; nun++; } - uname[i] = p; useq[i] = seqasm(code); ulen[i] = seqtop - useq[i]; - addkop(p); - return i; -} -// register a keyword with its handler and flags. -int add_kw(char* nm, int handler, int flags) { - int s = addsym(intern(nm), K_KW, handler); - sflag[s] = F_DEF | flags; - return s; -} -// register a library function as a receiver method. its first parameter -// determines the receiver type; remaining parameters follow the member name. -word receiver_methods, closure_expected; -int add_method(char* member, char* function_name) { - int s = lookup_raw(intern(function_name)); word link; - if (s < 0 || skind[s] != K_FN || !stype[s] || !stype[s][3]) { - serr("a receiver method requires a function with at least one typed parameter"); return -1; - } - link = chk_resize(0, 0, 3); - link[0] = receiver_methods; link[1] = intern(member); link[2] = s; receiver_methods = link; - return s; -} -int receiver_method(word receiver_type, int member) { - word link = receiver_methods, ft, actual; word[8] bs; int found = -1, s; - while (link) { - s = link[2]; - if (s < nsym && link[1] == member) { - ft = stype[s]; actual = receiver_type; - if (actual[0] == TY_ARR && ft[7][0] == TY_DYN && ft[7][3]) actual = tdyn(actual[4], 1); - rt.fill_w(bs, 0, 8); - if (unify(ft[7], actual, bs)) { if (found >= 0 && found != s) return -2; found = s; } - } - link = link[0]; - } - return found; -} -// run.method("member", function) registers a receiver extension while parsing -// the importing source, including when its code is emitted into an image. -int kw_method() { - char* member; int s; - stmtonly(); next(); expect(s_lp); - if (tk != T_STR) { die("a receiver method name must be a character string"); return 0; } - member = tv; next(); expect(s_comma); - s = -1; if (tk == T_ID) s = source_lookup(tv); - if (s < 0 || skind[s] != K_FN) serr("a receiver extension requires a function name"); - next(); expect(s_rp); expect(s_semi); - if (s >= 0) add_method(member, sname[s]); - return 0; -} -// publish a reference for shallow copies while preserving its declared type. -// scalar values are unchanged; in-place aggregates require explicit copying. -int kw_share_value() { - int c; word t; - next(); expect(s_lp); c = expr(); rval(); plainuse(c); t = xty; expect(s_rp); - if (isagg(t)) serr("sharing an aggregate requires sharing its pointer fields explicitly"); - else if (isptr(t) || t[0] == TY_DYN || t[0] == TY_FN || t == tword) callname(intern("rt.share")); - xbor = -1; xaf = 0; effect_value = 0; xty = t; xconst = 0; xvk = 0; - return C_PLAIN; -} - -word statement_hooks; - -// source libraries can register syntax without emitting a runtime registration call. -int kw_syntax() { - char* keyword; int handler, entry = -1; - stmtonly(); next(); expect(s_lp); - if (tk != T_STR) { die("a syntax keyword must be a character string"); return 0; } - keyword = tv; next(); expect(s_comma); handler = -1; - if (tk == T_ID) handler = source_lookup(tv); - if (handler < 0 || skind[handler] != K_FN) serr("a syntax extension requires a function name"); - next(); expect(s_rp); expect(s_semi); - if (handler >= 0) { - if (hdelta && handler >= ubase) entry = syntax_resident(handler); else entry = sval[handler]; - if (entry >= 0) add_kw(keyword, entry, F_TOP | F_STMT); - } - return 0; -} -int kw_statement_hook() { - int handler = -1; word link; - stmtonly(); next(); expect(s_lp); if (tk == T_ID) handler = source_lookup(tv); - if (handler < 0 || skind[handler] != K_FN) serr("a statement hook requires a function name"); - next(); expect(s_rp); expect(s_semi); - if (handler >= 0) { link = chk_resize(0, 0, 3); link[0] = statement_hooks; link[1] = handler; link[2] = 1; statement_hooks = link; } - return 0; -} -// optional statement callbacks installed by source extensions at include time. -// callbacks execute at ordinary vm call boundaries and must guard reentrancy. -int add_statement_hook(int callback) { - word link = chk_resize(0, 0, 3); link[0] = statement_hooks; link[1] = callback; link[2] = 0; statement_hooks = link; - return 0; -} -int emit_statement_hooks() { - word link = statement_hooks; - if (dry || o3on || cmode || incdepth || !infn) return 0; - while (link) { if (link[2]) fnref(link[1]); else lit(link[1]); op(O_CALL); op(O_DROP); link = link[0]; } - return 0; -} - -// ---- function effects and temporary borrows ---- - -word effect_record(int symbol) { - if (symbol < 0) return 0; - if (!effect_symbols) effect_symbols = chk_resize(0, 0, symcap); - if (!effect_symbols[symbol]) effect_symbols[symbol] = chk_resize(0, 0, 50); - return effect_symbols[symbol]; -} -// trusted runtime contracts apply only to built-ins, never a user function with the same name. -int effect_contract(int symbol, int parameter) { - word n; - if (symbol < 0 || symbol >= ubase) return EF_UNKNOWN; - n = sname[symbol]; - if (n == s_put_s || rt.eq_w(n, "rt.put_bs") || rt.eq_w(n, "rt.len_w") || rt.eq_w(n, "rt.len_s") || rt.eq_w(n, "rt.eq_w") || rt.eq_w(n, "rt.eq_s") || rt.eq_w(n, "rt.at_s") || rt.eq_w(n, "rt.put_n") || rt.eq_w(n, "rt.put_c")) return EF_READ; - if (n == s_free || n == s_recycle) return EF_CONSUME; - if (rt.eq_w(n, "rt.copy_w")) { if (!parameter) return EF_WRITE; return EF_READ; } - if (rt.eq_w(n, "rt.fill_w")) { if (!parameter) return EF_WRITE; return EF_READ; } - return EF_UNKNOWN; -} -int effect_get(int symbol, int parameter) { - word r; - if (parameter < 0 || parameter >= 24) return EF_UNKNOWN; - if (symbol < 0 || symbol >= nsym) return EF_UNKNOWN; - if (symbol < ubase) return effect_contract(symbol, parameter); - r = effect_record(symbol); - if (r[0] != 1 || (sflag[symbol] & F_EXT)) { - if (parameter < 12 && (((ssig[symbol] >> 6) >> (parameter + parameter)) & 3) == 1) return EF_READ | EF_WRITE | EF_ALIAS; - return EF_UNKNOWN; - } - return r[2 + parameter]; -} -int effect_origin(int c) { - int i = xbor; - if (c >= C_VAR) i = c - C_VAR; - if (i < lbase || i >= nloc) return -1; - if (local_origin && local_origin[i] >= 0) return local_origin[i]; - if (lrskind[i] == RS_PARAM && i - lbase < 24) return i - lbase; - return -1; -} -word effect_mask(int c) { - int i; - if (c >= C_VAR) { - i = c - C_VAR; - if (local_origin_mask && local_origin_mask[i]) return local_origin_mask[i]; - if (i >= lbase && lrskind[i] == RS_PARAM && i - lbase < 24) return 1 << (i - lbase); - } - return effect_value; -} -int effect_note_mask(word mask, int flags) { - int i = 0; - while (mask && i < 24) { if (mask & 1) effect_note(i, flags); mask = mask >> 1; i++; } - return 0; -} -int effect_args_mask(int callee, int parameter, word mask) { - int i = 0; - while (mask && i < 24) { if (mask & 1) effect_arg(callee, parameter, i); mask = mask >> 1; i++; } - return 0; -} -int effect_note(int origin, int flags) { - if (effect_current && origin >= 0 && origin < 24) { effect_current[2 + origin] = effect_current[2 + origin] | flags; effect_current[26 + origin] = effect_current[26 + origin] | flags; } - return 0; -} -int effect_arg(int callee, int parameter, int origin) { - word e; - if (!effect_current || origin < 0) return 0; - e = talloc(6); e[0] = effect_edges; e[1] = effect_current; e[2] = origin; e[3] = callee; e[4] = parameter; e[5] = 0; - effect_edges = e; return 0; -} -int effect_require_mode(int callee, int parameter, int place, int mode) { - word e; - if (!fchk || !live || dry) return 0; - e = talloc(7); e[6] = mode; - e[5] = effect_floor; - e[0] = effect_requirements; e[1] = callee; e[2] = parameter; e[3] = place; e[4] = curinc; - effect_requirements = e; return 0; -} -int effect_require(int callee, int parameter, int place) { return effect_require_mode(callee, parameter, place, 0); } -// argument checks for a generic call use the instantiated body's effects. -int effect_specialize(int original, int instance, word requirements, word edges) { - word e = effect_requirements; - while (e && e != requirements) { if (e[1] == original) e[1] = instance; e = e[0]; } - e = effect_edges; - while (e && e != edges) { if (e[3] == original) e[3] = instance; e = e[0]; } - return 0; -} -int effect_finish(int from) { - word e, r, next; int changed = 1, flags, prior, save = tpack(), inc = curinc, i = 0; - if (chkabort) return 0; - // direct body effects are immutable inputs; recompute callee effects so an earlier nested unit cannot leave stale unknowns. - while (effect_symbols && i < nsym) { r = effect_symbols[i]; if (r) rt.copy_w(r + 2, r + 26, 24); i++; } - while (changed) { - changed = 0; e = effect_edges; - while (e) { - r = e[1]; flags = effect_get(e[3], e[4]) & ~(EF_ALIAS | EF_IDENTITY); prior = r[2 + e[2]]; - r[2 + e[2]] = prior | flags; if (prior != r[2 + e[2]]) changed = 1; - e = e[0]; - } - } - e = effect_requirements; effect_requirements = 0; - while (e) { - next = e[0]; - if (e[5] < from) { e[0] = effect_requirements; effect_requirements = e; e = next; continue; } - flags = effect_get(e[1], e[2]); - if ((!e[6] && (flags & (EF_RETAIN | EF_CONSUME | EF_UNKNOWN))) || (e[6] && !(flags & EF_UNKNOWN) && (flags & (EF_RETAIN | EF_CONSUME)))) { - tunpack(e[3]); curinc = e[4]; - if (e[6]) serr("this callee retains or consumes a borrowed owner; declare its parameter .d to transfer ownership"); - else if (flags & EF_UNKNOWN) serr("a temporary argument requires a proved non-escaping borrow; bind it to a name first or provide an explicit parameter contract"); - else serr("a temporary argument requires a proved non-escaping borrow; this callee retains or consumes it, so transfer ownership through a .d parameter"); - panic = 0; - } - e = next; - } - tunpack(save); curinc = inc; return 0; -} -int borrow_begin() { return 0; } -int borrow_prologue() { borrow_jump = hole(); op(O_JMP); borrow_entry = here; return 0; } -int borrow_initializer() { - word p = borrow_slots; - if (borrow_jump < 0) return 0; - if (!p) { setlit(borrow_jump, borrow_entry); return 0; } - setlit(borrow_jump, here); - while (p) { lit(0); laddr(p[1]); op(O_ST); p = p[0]; } - jmp(borrow_entry); return 0; -} -int emit_borrow_hold() { - word p; - if (!infn) { cerr("temporary borrow outside a function"); return 0; } - p = talloc(2); p[0] = borrow_slots; p[1] = fslots; fslots++; borrow_slots = p; - lit(0); op(O_PICK); laddr(p[1]); op(O_ST); borrow_pending++; return 0; -} -// release only temporaries introduced by this full expression. zero slots cover untaken branches. -int borrow_cleanup_since(word stop) { - word p = borrow_slots; int skip; - while (p && p != stop) { - laddr(p[1]); op(O_LD); skip = hole(); op(O_JZ); - laddr(p[1]); op(O_LD); callname(s_recycle); op(O_DROP); - lit(0); laddr(p[1]); op(O_ST); setlit(skip, here); p = p[0]; - } - return 0; -} -int borrow_cleanup() { return borrow_cleanup_since(0); } -int borrow_condition() { - word before = borrow_slots; int c = expr(); rval(); plainuse(c); ftest(); borrow_cleanup_since(before); return c; -} - -// ---- locals and the analyzer's view of them ---- - -// value tracking is for check mode only; it never changes emitted code. -int vf_symbols() { - if (!svkind) { svkind = chk_resize(0, 0, symcap); svvalue = chk_resize(0, 0, symcap); } - return 0; -} -int vf_at(int r, int kind, word value) { - if (!cmode || dry || chkind != 3 || chbase != rsbase || r < 0 || r >= rsn) return 0; - rsrec[r * RS_W + 9] = kind; rsrec[r * RS_W + 10] = value; return 0; -} -char* vf_string(char* text) { - word p; int n; - if (!cmode || dry) return 0; - n = rt.len_w(text) + 1; p = talloc(n); rt.copy_w(p, text, n); return p; -} -int vf_forget() { - if (!cmode || dry) return 0; - if (lvkind) rt.fill_w(lvkind + lbase, 0, nloc - lbase); - if (svkind) rt.fill_w(svkind, 0, nsym); - xvk = 0; return 0; -} -// a call or indirect write can change globals, escaped locals, and string data. -int vf_effect() { - int i = lbase; - if (!cmode || dry) return 0; - while (i < nloc) { if (lvescaped[i] || lvkind[i] >= 3) lvkind[i] = 0; i++; } - if (svkind) rt.fill_w(svkind, 0, nsym); - xvk = 0; return 0; -} -word vf_i2f(word v) { return asm { v @ #1216 }; } -word vf_f2i(word v) { return asm { v @ #1218 }; } -word vf_negf(word v) { return asm { 0.0 v @ #1204 }; } -int vf_coerce(word t, int convert) { - if (!xvk || !t) return 0; - if (xvk >= 3) { if (t != tword && t != tbstr && t[0] != TY_PTR) xvk = 0; return 0; } - if (isfl(t)) { - if (xvk == 1) { if (convert) xvv = vf_i2f(xvv); xvk = 2; } - if (t == tf32 && convert) xvv = rt.zs2f(rt.zf2s(xvv)); - } else { - if (xvk == 2 && convert && isnum(t)) xvv = vf_f2i(xvv); - xvk = 1; xvv = cnorm(t, xvv); - } - return 0; -} -int vf_store(int i) { - if (!cmode || dry || !live) return 0; - lvkind[i] = xvk; lvvalue[i] = xvv; return 0; -} - -// these warnings add no ownership rules. facts are discarded at control-flow -// joins and after unknown effects, so a possible failure is never called definite. -word af_new(int length, int kind, int place) { - word f; - if (dry || stubgen) return 0; - f = talloc(15); rt.fill_w(f, 0, 15); f[0] = length; f[1] = 1; f[2] = kind; - f[3] = place; f[4] = 0; f[5] = -1; - return f; -} -// owner-relative projections retain provenance and the array generation at the time of borrowing. -word af_base(word f) { while (f && f[6]) f = f[6]; return f; } -word af_project(word f, int offset, int extent, int place, int elements) { - word p, base = af_base(f); int start = offset; - if (!f || !base) return 0; - if (f[6] && f[7] >= 0 && offset >= 0) start = f[7] + offset; - p = af_new(extent, base[2], base[3]); - if (!p) return 0; - p[1] = base[1]; p[4] = base[4]; p[5] = base[5]; p[6] = base; p[7] = start; - p[8] = elements; p[9] = base[10]; p[12] = place; - if (base[2] == 3) p[2] = 2; - if (elements) { - p[0] = -1; - if (offset >= 0 && f[0] >= 0) { p[0] = f[0] - offset; p[13] = f[13] - offset; p[14] = 1; } - } - return p; -} -int af_mutate(word f, int place) { - word base = af_base(f); - if (!base || dry || !live) return 0; - af_mutation_tick++; base[10] = af_mutation_tick; base[11] = place; base[0] = -1; return 0; -} -int af_projection_access(word f, int place) { - word base; int saved; - if (!f || !f[6] || !f[8]) return 0; - base = af_base(f); - if (base[10] < 0 || base[10] == f[9] || dry || !live) return 0; - saved = tpack(); tunpack(place); - if (fchk) { if (panic) { tunpack(saved); return 0; } errat(); } - else if (!af_warning(place)) { tunpack(saved); return 0; } - rt.put_s("element borrow was invalidated by array mutation on line "); rt.put_n(base[11] & 0xFFFFFF); - rt.put_s("; borrow created on line "); rt.put_n(f[12] & 0xFFFFFF); - if (fchk) errend(); else af_warning_end(); tunpack(saved); return 0; -} - -int af_forget() { - vf_forget(); - int i = lbase; - while (i < nloc) { lafact[i] = 0; i++; } - xaf = 0; return 0; -} -// an unknown callee may change a passed allocation's length or lifetime. -int af_escape(word f) { - if (f) { word base = af_base(f); base[0] = -1; base[1] = 0; base[10] = -1; f[0] = -1; f[1] = 0; } - return 0; -} -int af_store(int i) { - if (!dry) lafact[i] = xaf; - return 0; -} -int af_expired(word f) { - word base; - if (!f) return 0; - if (f[1] == 2) return 1; - if (!f[6] || !f[8]) return 0; - base = af_base(f); return base[10] >= 0 && base[10] != f[9]; -} -int af_snapshot(word f) { - if (f && f[6] && af_expired(f)) { f[1] = 2; f[4] = af_base(f)[11]; } - return 0; -} -int af_record(int off, word f) { - int at; - if (!f || !cmode || dry || chkind != 3 || chbase != rsbase || !rsn) return 0; - af_snapshot(f); - at = (rsn - 1) * RS_W; - if (rsrec[at] != off) return 0; - rt.copy_w(rsrec + at + 4, f, 5); return 0; -} -// keep declaration previews as well as the facts at each use site. -int af_declaration(int off, word f) { - int i = rsn; - if (!f || !cmode || dry || chkind != 3 || chbase != rsbase) return 0; - while (i) { i--; if (rsrec[i * RS_W] == off) { rt.copy_w(rsrec + i * RS_W + 4, f, 5); return 0; } } - return 0; -} -int af_warning(int place) { - int sv; - if (dry || panic || chkabort || stubgen || !live) return 0; - sv = tpack(); tunpack(place); - if (cmode) dg_open(2); - else { rt.put_s("zc: line "); rt.put_n(tline); rt.put_s(": warning: "); } - tunpack(sv); return 1; -} -int af_warning_end() { if (cmode) dg_close(); else rt.put_c(10); return 0; } -int af_access(word f, int place) { - af_projection_access(f, place); if (f && f[6]) { word base = af_base(f); f[1] = base[1]; f[4] = base[4]; } - if (!f || f[1] != 2 || !af_warning(place)) return 0; - rt.put_s("use after free: this storage's lifetime ended on line "); - rt.put_n(f[4] & 0xFFFFFF); af_warning_end(); return 0; -} -int af_use(word f, int place, int c) { - if (fchk && c >= C_VAR && lstate[lroot(c - C_VAR)] == S_DEAD) return 0; - return af_access(f, place); -} -int af_index(word f, int place) { - int lower = 0; - if (f && f[6]) { word base = af_base(f); if (base[1] == 0 || base[10] < 0) return 0; if (f[14]) lower = f[13]; } - if (!f || f[1] == 2 || f[0] < 0 || !xconst || (xcval >= lower && xcval < f[0])) return 0; - if (!af_warning(place)) return 0; - rt.put_s("out-of-bounds access: index "); rt.put_n(xcval); - rt.put_s(" is outside length "); rt.put_n(f[0]); rt.put_s(" (storage declared or allocated on line "); rt.put_n(f[3] & 0xFFFFFF); rt.put_c(')'); af_warning_end(); return 0; -} -// freeing an alias ends the same allocation as freeing its original name. -int af_end(word f, int place) { - if (f && !dry && live) { f[1] = 2; f[4] = place; } - return 0; -} -int af_close(int save) { - int i = save; word f; - if (dry) return 0; - while (i < nloc) { - f = laaddr[i]; if (f && f[2] == 1) af_end(f, tpack()); - f = lafact[i]; - if (f && fchk && lstate[i] == S_M && lown[i] && !local_captured[i]) af_end(f, tpack()); - i++; - } - return 0; -} -// the analyzer's model of a local. locals are numbered in declaration order -// and every fact about one lives in a parallel array indexed by that number -// (see the data region). for ownership the ones that matter are: -// lstate what the local holds right now: -// S_PLAIN nothing tracked (a number, or untracked memory) -// S_M owns an object; the compiler frees it when the block ends -// S_U owns one that has to leave the block (return it, make -// it the block's value, or assign it to the enclosing block) -// S_D was handed in to be given away, and hasn't been yet -// S_DEAD the object is gone (given away); any use is an error -// S_CHILD the object belongs to a container, lowner says which local -// S_BORROW a .m parameter: usable but not freeable or storable -// lhome the block depth that owns the object (fdepth where it was declared) -// lown 1 once the local has owned an object, lused the line of its last use -// ltag the tag written on the declaration, plus the flag bits (float, -// byte string, o3 register, hint bits) -// fchk says the function being compiled is analyzed at all (unchecked turns -// it off) and live says the code just emitted is reachable. ownership rules -// return when fchk is off; definite storage warnings also inspect unchecked code. -// findloc looks a name up among the current function's locals, innermost first. -// name heads and shadow links keep declaration and use lookup independent of -// the number of earlier locals. dropping a scope restores the previous heads. -int local_bucket(int n, int cap) { return ((n ^ (n >> 16)) * 0x9E37) & (cap - 1); } -int local_key(int n) { - int k = local_bucket(n, lkeycap); - while (lkeys[k] && lkeys[k] != n) k = (k + 1) & (lkeycap - 1); - return k; -} -int local_bind(int n, int i) { - int k, j, at, cap; word keys, heads; - if (lkeyn + lkeyn >= lkeycap) { - keys = lkeys; heads = lheads; cap = lkeycap * 2; - lkeys = chk_resize(0, 0, cap); lheads = chk_resize(0, 0, cap); - j = 0; - while (j < lkeycap) { - if (keys[j]) { at = local_bucket(keys[j], cap); while (lkeys[at]) at = (at + 1) & (cap - 1); lkeys[at] = keys[j]; lheads[at] = heads[j]; } - j++; - } - lkeycap = cap; - } - k = local_key(n); - if (!lkeys[k]) { lkeys[k] = n; lheads[k] = -1; lkeyn++; } - lprev[i] = lheads[k]; lheads[k] = i; return 0; -} -int local_drop(int save) { - int i = nloc, k; - while (i > save) { i--; k = local_key(lname[i]); lheads[k] = lprev[i]; } - nloc = save; return 0; -} -int findloc(int n) { - int k = local_key(n), i; - if (!lkeys[k]) return -1; - i = lheads[k]; - while (i >= nloc) i = lprev[i]; - if (i >= lbase) return i; - return -1; -} -// declare a local: take the next slot in the frame (fslots) and reset every -// per-local fact so a reused index never inherits the old variable's state -int newloc(int n) { - int i = nloc; - if (nloc >= loccap) reserve_locals(nloc + 1); - nloc++; - lname[i] = n; local_bind(n, i); lslot[i] = fslots; lhome[i] = fdepth; lstate[i] = S_PLAIN; lsmark(0, n); - if (!local_origin) local_origin = chk_resize(0, 0, loccap); local_origin[i] = -1; - if (if_local) if_local[i] = 0; - if (!local_origin_mask) local_origin_mask = chk_resize(0, 0, loccap); local_origin_mask[i] = 0; - if (!local_cell) local_cell = chk_resize(0, 0, loccap); local_cell[i] = 0; - if (!local_captured) local_captured = chk_resize(0, 0, loccap); local_captured[i] = 0; - if (!local_ended) local_ended = chk_resize(0, 0, loccap); local_ended[i] = 0; - if (!local_end_info) local_end_info = chk_resize(0, 0, loccap); local_end_info[i] = 0; - lpos[i] = 0; - lreadpos[i] = 0; - lowner[i] = 0; - lrsoff[i] = 0 - 1; lrskind[i] = RS_LOCAL; - lused[i] = 0; - larr[i] = 0; - lown[i] = 0; - ltype[i] = tword; - ltag[i] = 0; - o3cache[i] = 0; - lafact[i] = 0; laaddr[i] = 0; - if (cmode) { - if (!lvkind) { lvkind = chk_resize(0, 0, loccap); lvvalue = chk_resize(0, 0, loccap); lvescaped = chk_resize(0, 0, loccap); } - lvkind[i] = 0; lvvalue[i] = 0; lvescaped[i] = 0; - } - return i; -} -// a local's known lower bound, and the array whose length it holds. -1 is unknown. -// the table is dropped as scopes close, so a reused slot does not keep the old fact. -word getlo(int i) { - int k = 0; - while (k < nfact) { - if (floc[k] == i) return flo[k]; - k++; - } - return -1; -} -// the length of the recorded fact for line i, or -1 when there is none. -word getln(int i) { - int k = 0; - while (k < nfact) { - if (floc[k] == i) return flen[k]; - k++; - } - return -1; -} -// record the range lo to lo+ln for line i. -int putfact(int i, int lo, int ln) { - int k = 0; - while (k < nfact) { if (floc[k] == i) { flo[k] = lo; flen[k] = ln; return 0; } k++; } - if (lo < 0 && ln < 0) return 0; - if (nfact >= factcap) { - floc = chk_resize(floc, nfact, factcap * 2); flo = chk_resize(flo, nfact, factcap * 2); flen = chk_resize(flen, nfact, factcap * 2); - factcap = factcap * 2; - } - floc[nfact] = i; flo[nfact] = lo; flen[nfact] = ln; nfact++; - return 0; -} -// drop the recorded facts from line from onward. -int fdrop(int from) { - int k = 0, w = 0; - while (k < nfact) { - if (floc[k] < from) { - if (w != k) { floc[w] = floc[k]; flo[w] = flo[k]; flen[w] = flen[k]; } - w++; - } - k++; - } - nfact = w; - return 0; -} -// a loop fact stores three full local indices: the -// induction variable, its bound and the array the bound is the length of. a -// loop "for (i = 0; i < len(a); i++)" records (i, bound, a) in lpfact, which -// is how proven() knows that a[i] needs no bounds check inside it. -int pfact(int iv, int bd, int ar) { word p = talloc(3); p[0] = iv; p[1] = bd; p[2] = ar; return p; } -// the induction variable of a loop proof. -int p_iv(int w) { return w[0]; } -// the array's bound in a loop proof. -int p_bd(int w) { return w[1]; } -// the array a loop proof refers to. -int p_ar(int w) { return w[2]; } -// a store to the induction variable, the bound, or the array ends the proof -int bpoison(int i) { - int k = lsp, w; - while (k > loopbase) { - k--; - w = lpfact[k]; - if (w >= 0 && (p_iv(w) == i || p_bd(w) == i || p_ar(w) == i)) lpfact[k] = -1; - } - return 0; -} -// note a store to a local: a constant or a length it now holds. -int note_store(int i) { - if (if_local && !dry) { - if (!if_local[i]) if_local[i] = chk_resize(0, 0, 1); - if (if_value) if_link(if_local[i], if_value); - else if (xty == tword) *if_local[i] = -1; - if_join(if_local[i], xty); - } - int lo = -1, ln = -1; - if (xconst && xcval >= 0) lo = xcval; - if (xlenloc >= 0) ln = xlenloc; - putfact(i, lo, ln); - bpoison(i); - return 0; -} -// i >= 0 and i < len(a), proved by the loop test and a step of +1 -int proven(int a, int ix) { - int k = lsp, w; - if (ltype[a][0] != TY_DYN || !ltype[a][3] || tbytes(ltype[a][4])) return 0; - while (k > loopbase) { - k--; - w = lpfact[k]; - if (w >= 0 && p_iv(w) == ix && p_ar(w) == a) return 1; - } - return 0; -} -// follow child links up to the local that really owns the object -int lroot(int i) { - while (lstate[i] == S_CHILD) i = lowner[i]; - return i; -} -// lend a local owner to global s for the rest of the current block. -int lend_add(int s, int i) { - if (nlend * 3 + 3 > lendcap) { if (!lendcap) lendcap = 48; else lendcap = lendcap * 2; lendrec = chk_resize(lendrec, nlend * 3, lendcap); } - lendrec[nlend * 3] = s; lendrec[nlend * 3 + 1] = i; lendrec[nlend * 3 + 2] = fdepth; nlend++; - return 0; -} -// remove entry k, keeping the others in order. -int lend_remove(int k) { - if (k + 1 < nlend) rt.copy_w(lendrec + k * 3, lendrec + k * 3 + 3, (nlend - k - 1) * 3); - nlend--; - return 0; -} -// an assignment to global s in the block that lent it ends the lend. -int lend_clear(int s) { - int k = nlend; - while (k > 0) { k--; if (lendrec[k * 3] == s && lendrec[k * 3 + 2] == fdepth && lendrec[k * 3 + 1] >= lbase) lend_remove(k); } - return 0; -} -// report local i still lent to the global of entry k. -int lend_err(int k, char* msg) { - int i = lendrec[k * 3 + 1]; - if (!fchk || !live || panic || dry) return 0; - errat(); rt.put_s(lname[i]); rt.put_s(": is lent to global "); rt.put_s(sname[lendrec[k * 3]]); rt.put_s(msg); errend(); - return 0; -} -// local i is about to be given away or released: no global may still borrow it. -int lend_check(int i) { - int k = 0; - while (k < nlend) { - if (lendrec[k * 3 + 1] == i && i >= lbase) { lend_err(k, "; assign the global again before giving the local away"); lend_remove(k); } - else k++; - } - return 0; -} -// leaving every block at depth dep or deeper: a lend made there must have ended. -int lend_exit(int dep) { - int k = 0; - while (k < nlend) { - if (lendrec[k * 3 + 2] >= dep && lendrec[k * 3 + 1] >= lbase) lend_err(k, "; assign the global again before the block that lent it ends"); - k++; - } - return 0; -} -// forget the lends made at depth dep or deeper, or by locals from index from onward. -int lend_drop(int dep, int from) { - int k = 0; - while (k < nlend) { - if ((lendrec[k * 3 + 2] >= dep && lendrec[k * 3 + 1] >= lbase) || lendrec[k * 3 + 1] >= from) lend_remove(k); - else k++; - } - return 0; -} -// retire existing aliases together with their owner. dead aliases do not keep later loop snapshots live. -int end_owner(int i) { - int j = lbase; word info = talloc(3); - lend_check(i); - info[0] = lname[i]; info[1] = lpos[i]; info[2] = tpack(); - local_ended[i] = info[2]; local_end_info[i] = info; lstate[i] = S_DEAD; - while (j < nloc) { - if (j != i && isalias(j) && lroot(j) == i) { local_ended[j] = info[2]; local_end_info[j] = info; lstate[j] = S_DEAD; } - j++; - } - return 0; -} -// true when a local has an ownership state the analyzer follows. -word tracked(int i) { return lstate[i] != S_PLAIN; } -// a child link with nothing owned: a pointer computed from some other variable -word isalias(int i) { return lstate[i] == S_CHILD && !lown[i]; } -// record the last-use line and saturating reference count for diagnostics. -int luse(int i) { - lused[i] = tline; - if ((ltag[i] >> 56) < 100) ltag[i] = ltag[i] + LCNT1; - return 0; -} -// touch records a use of a tracked local and rejects dead objects. a d object -// read in an inner block moves there, where the block owns it as m. -int touch(int i) { - int r; - luse(i); - ltag[i] = ltag[i] | LREAD; - if (!fchk) return 0; - r = lroot(i); - luse(r); - if (lstate[r] == S_DEAD) { cerrn(i, "used after it was given away"); return 0; } - // a d object that shows up in a deeper block is given to that block - if (lstate[r] == S_D && lhome[r] < fdepth) { lhome[r] = fdepth; lstate[r] = S_M; lown[r] = 1; } - return 0; -} -// an expression of class c used as a plain value (a lend, an operand). a new -// object can't be used that way, since nothing would own it. -int plainuse(int c) { - if (c == C_FRESH) cerr("a new object can't be used as a plain value; bind it to a name first"); - if (c >= C_VAR) touch(c - C_VAR); - return C_PLAIN; -} -// the size class of an n word object, for a constant-size new -int szclass(int n) { - int k = 3; - while (rt.pow2[k] < n + rt.HDR) k++; - return k; -} -// freeing an owned local. one that holds a typed object with no pointer -// fields goes back through recycle_leaf, which knows its class -int freevar(int i) { - if (local_captured && local_captured[i]) { lit(0); laddr(lslot[i]); op(O_ST); return 0; } - vaddr(i); op(O_LD); - if (lleaf[i] && !o3on && !dry && lookup(s_recycle_leaf) >= 0) { - lit(lleaf[i]); - lit(szclass(lnsz[i])); - callname(s_recycle_leaf); - } - else callname(s_recycle); op(O_DROP); lit(0); laddr(lslot[i]); op(O_ST); return 0; -} -// leaving every block deeper than or equal to dep: free what is owned, and -// complain about what should have left already. final means the block really -// ends here rather than an early exit. -int exit_vars(int dep, int final) { - int i = nloc, st; - if (!fchk || !live) return 0; - lend_exit(dep); - while (i > lbase) { - i--; - if (lhome[i] < dep) continue; - st = lstate[i]; - if (st == S_M) { - if (!local_captured || !local_captured[i]) ev_put(i, EV_FREE, -1); freevar(i); - } else if (st == S_U) { - cerrn(i, "u object must leave this block (block value, assignment to the enclosing block, or return)"); - } - else if (st == S_D) cerrn(i, "d object was never given away"); - } - return 0; -} -// an m object has to be used somewhere in the block that owns it, on any path -// block ends. -int unused_check(int from) { - while (from < nloc) { - if (fchk && lown[from] && !lused[from] && !(lrskind[from] == RS_PARAM && rt.eq_w(lname[from], "_"))) serrn(lname[from], "owned object is never used"); - from++; - } - return 0; -} -// what a block's own variables show once it ends (check mode only) -int hint_name(int i, char* msg, int kind) { - if (!hint_begin(lpos[i], kind)) return 0; - rt.put_s(lname[i]); rt.put_s(msg); - dg_close(); - return 0; -} -// report the locals declared from token from onward that were never used. -int local_hints(int from) { - int i = from, f; - if (!cmode || dry || panic || chkabort || stubgen) return 0; - while (i < nloc) { - f = ltag[i]; - if (lrskind[i] == RS_PARAM && lname[i] != s_this && !rt.eq_w(lname[i], "_") && !(f & LREAD)) { - if (hint_begin(lpos[i], HINT_SMALL)) { - rt.put_s("unused parameter `"); rt.put_s(lname[i]); rt.put_s("`"); dg_close(); - } - } else if ((f & LDECL) && !larr[i] && !rt.eq_w(lname[i], "_")) { // _ discards its value - if (!(f & LREAD)) hint_name(i, " is never read", HINT_SMALL); - else if ((f & LCONST) && !(f & LASG) && !(ltype[i] && treadonly(ltype[i]))) hint_name(i, " is never reassigned; make it a const", HINT_ALL); - else if (fchk && lstate[i] == S_M && lown[i] && lnsz[i] > 0 && lnsz[i] <= 64) hint_name(i, " never leaves this function; a stack buffer (let name[n]) avoids the heap", HINT_SMALL); - else if (fchk && lstate[i] == S_M && lown[i] && lused[i] && tline - lused[i] >= 4) hint_name(i, " is last used several lines above the end of its block; close the block sooner so it is freed sooner", HINT_ALL); - } - i++; - } - return 0; -} -// block_close runs the analyzer's end-of-block work in order: hints for the -// block's variables, o3 registers go back to the pool, an m object that was -// never used is an error, exit_vars frees what is owned and complains about -// what should have left, aliases whose owner dies are marked dead, then the -// block's locals are dropped. unused_check comes first because an unused m -// object is an error, not a free. -int block_close(int save) { - int i = save, k; - af_close(save); - local_hints(save); - if (o3on) { - while (i < nloc) { - k = (ltag[i] >> 8) & 7; - if ((ltag[i] & 16) && k && no3free < 8) { - if (getflt(i)) k = 0 - k; - o3free[no3free] = k; - no3free++; - } - i++; - } - } - i = lbase; - unused_check(save); - exit_vars(fdepth, 1); - lend_drop(fdepth, save); - alias_end(fdepth, save); - while (i < save) { if (lhome[i] >= fdepth) { lstate[i] = S_DEAD; lhome[i] = fdepth - 1; } i++; } - local_drop(save); - fdrop(save); - td_end(fdepth); - return 0; -} - -// an alias in an outer block whose owner is about to disappear dies with it -int alias_end(int dep, int save) { - int i, r; - if (!fchk) return 0; - i = lbase; - while (i < save) { - if (isalias(i)) { - r = lroot(i); - if (r >= save || lhome[r] >= dep || lstate[r] == S_DEAD) lstate[i] = S_DEAD; - } - i++; - } - return 0; -} -// snapshots: [lbase] [nloc] then state, owner, home per local -// save the states of all current locals; returns the snapshot offset -int snap_push() { - int b = snaptop, i = lbase; word p; - af_forget(); - reserve_R_SNAP(b + 2 + 3 * (nloc - lbase)); - p = R_SNAP + b; - p[0] = lbase; p[1] = nloc; - p = p + 2; - while (i < nloc) { p[0] = lstate[i]; p[1] = lowner[i]; p[2] = lhome[i]; p = p + 3; i++; } - snaptop = p - R_SNAP; - return b; -} -// drop the snapshot at base b. -int snap_pop(int b) { snaptop = b; return 0; } -// restore the local states saved in snapshot b. -int snap_restore(int b) { - word p = R_SNAP + b; int i = p[0], e = p[1]; - af_forget(); - if (e > nloc) e = nloc; - p = p + 2; - while (i < e) { lstate[i] = p[0]; lowner[i] = p[1]; lhome[i] = p[2]; p = p + 3; i++; } - return 0; -} -// true for a plain or dead local state, which merge to dead. -int empty(int s) { return s == S_PLAIN || s == S_DEAD; } -// compare the current states with a snapshot: -1 if they agree, else the -// first local that differs. plain and dead agree and merge to dead. -int snap_cmp(int b) { - word p = R_SNAP + b; int i = p[0], e = p[1], s; - if (e > nloc) e = nloc; - p = p + 2; - while (i < e) { - s = lstate[i]; - if (empty(s) && empty(p[0])) { if (p[0] == S_DEAD) lstate[i] = S_DEAD; } - else if (s != p[0] || lhome[i] != p[2] || (s == S_CHILD && lowner[i] != p[1])) return i; - p = p + 3; i++; - } - return -1; -} -// the state names used in join and loop messages -int statename(int s) { - if (s == S_PLAIN) return "empty"; - if (s == S_M) return "m"; - if (s == S_U) return "u"; - if (s == S_D) return "d"; - if (s == S_DEAD) return "given away"; - if (s == S_CHILD) return "owned by a container"; - return "borrowed"; -} -// report that local i ends two paths in different states -int merge_err(int i, int sa, int what) { - if (!fchk || panic || dry) return 0; - errat(); rt.put_s(lname[i]); rt.put_s(": "); rt.put_s(what); - rt.put_s(" (one path leaves it "); - rt.put_s(statename(sa)); - rt.put_s(", another "); - rt.put_s(statename(lstate[i])); - rt.put_s(")"); - errend(); - return 0; -} - -// ---- snapshots and comparing paths ---- - -// to analyze an if, the compiler snapshots the locals' states before the -// branch, compiles the then path, restores the snapshot for the else path, and -// then compares or merges the two. a snapshot is [lbase, nloc] followed by -// state, owner and home for each local; the stack of them is at R_SNAP. -// ---- joins ---- - -// where control flow comes back together (after an if, a loop, a switch or -// ?:) every variable has to agree across the paths that reach the join. one -// exception: a variable that became d, or was given away, on some path is dead -// after the join, and the paths where it's still alive free it on the way in -// (fixups). u objects and borrows don't get this treatment: a u object still -// has to leave, and a borrow never changes. - -int extop; word[JOIN_PATHS] jps_initial; word jps = jps_initial; int jpscap = JOIN_PATHS, njps; // the paths being joined: [lbase, nloc, triples] -// accessors on a path record p (a snapshot or an exit record): the state, -// owner and home of local i on that path. locals declared after the snapshot -// read as plain. -word jst(word p, int i) { - if (i >= p[1]) return S_PLAIN; - return p[2 + 3 * (i - p[0])]; -} -// the owner recorded in a jump's state for local i. -word jow(word p, int i) { return p[3 + 3 * (i - p[0])]; } -// the jump's marker for local i, in the same record as jow. -word jhm(word p, int i) { return p[4 + 3 * (i - p[0])]; } -// the states at a break, with the jump to patch: [site] then a snapshot -word exit_rec(int site) { - word p; int i = lbase; word q; - reserve_R_EXIT(extop + 3 + 3 * (nloc - lbase)); p = R_EXIT + extop; - p[0] = site; p[1] = lbase; p[2] = nloc; - q = p + 3; - while (i < nloc) { q[0] = lstate[i]; q[1] = lowner[i]; q[2] = lhome[i]; q = q + 3; i++; } - extop = q - R_EXIT; - return p; -} -// jps is the set of paths about to be joined. jadd adds one, jsame compares -// what two paths say about a local, and join folds them all into the current -// states (reporting any local that disagrees) -int jadd(int p) { - if (njps >= jpscap) { jps = chk_resize(jps, njps, jpscap * 2); jpscap = jpscap * 2; } - jps[njps] = p; - njps++; - return 0; -} -// true when the states of two jumps agree for local i. -word jsame(int p, int q, int i) { - int s = jst(p, i), t = jst(q, i); - if (empty(s) && empty(t)) return 1; - return s == t && jhm(p, i) == jhm(q, i) && (s != S_CHILD || jow(p, i) == jow(q, i)); -} -// join the paths in jps over the locals [lbase, n0) into the current states -int join(int n0, int what) { - int i = lbase, j, p, s, s0, dead, okdead, bad; - af_forget(); - if (!njps) return 0; - while (i < n0) { - p = jps[0]; s0 = jst(p, i); - dead = 0; okdead = 1; bad = -1; - j = 0; - while (j < njps) { - s = jst(jps[j], i); - if (s == S_DEAD || s == S_D) dead = 1; - if (s == S_U || s == S_BORROW) okdead = 0; - if (!jsame(p, jps[j], i)) bad = j; - j++; - } - lstate[i] = s0; lowner[i] = jow(p, i); lhome[i] = jhm(p, i); - if (bad < 0) { - if (empty(s0)) { - j = 0; - while (j < njps) { - if (jst(jps[j], i) == S_DEAD) lstate[i] = S_DEAD; - j++; - } - } - } else if (dead && okdead) lstate[i] = S_DEAD; - else if (fchk && !panic && !dry) { - errat(); rt.put_s(lname[i]); rt.put_s(": "); rt.put_s(what); - rt.put_s(" (one path leaves it "); - rt.put_s(statename(s0)); - rt.put_s(", another "); - rt.put_s(statename(jst(jps[bad], i))); - rt.put_s(")"); - errend(); - } - i++; - } - return 0; -} -// on path p: count (and with doit, emit) the frees for what the join made dead but p still owns -int fixups(int p, int n0, int doit) { - int i = lbase, n = 0, s; - if (!fchk) return 0; - while (i < n0) { - s = jst(p, i); - if (lstate[i] == S_DEAD && (s == S_M || s == S_D)) { n++; if (doit) freevar(i); } - i++; - } - return n; -} -// a jump back to the top of a loop: whatever the loop owns has to be as it was -int backedge(int s0) { - word p = R_SNAP + s0; int i = p[0], e = p[1], s; - if (!fchk || !live) return 0; - if (e > nloc) e = nloc; - while (i < e) { - s = lstate[i]; - if (empty(s) && empty(jst(p, i))) { if (jst(p, i) == S_DEAD) lstate[i] = S_DEAD; } - else if (s != jst(p, i) || lhome[i] != jhm(p, i) || (s == S_CHILD && lowner[i] != jow(p, i))) { - if (s == S_DEAD || s == S_D) { - cerrn(i, "is given away inside a loop that comes back around to it; give it away on a path that leaves the loop"); - } - else if (!panic && !dry) { - errat(); - rt.put_s(lname[i]); - rt.put_s(": changes state inside a loop; each pass has to leave it as it found it (it went from "); - rt.put_s(statename(jst(p, i))); rt.put_s(" to "); rt.put_s(statename(s)); rt.put_s(")"); errend(); - } - } - i++; - } - return 0; -} -// joins two paths that end at the same point, like the branches of if/else: -// path a jumped (through the hole at ja) and path b falls through to here -// (the then path's snapshot is sa; livea / livebv say whether each path can -// reach the join at all, since a path that returned is not part of it) -int join2(int sa, int livea, int ja, int livebv, int n0, int what) { - int sb, nt, cj; - sb = snap_push(); - njps = 0; if (livea) jadd(R_SNAP + sa); if (livebv) jadd(R_SNAP + sb); - join(n0, what); - if (livebv) fixups(R_SNAP + sb, n0, 1); - nt = 0; if (livea) nt = fixups(R_SNAP + sa, n0, 0); - if (nt) { cj = hole(); op(O_JMP); setlit(ja, here); fixups(R_SNAP + sa, n0, 1); setlit(cj, here); } - else setlit(ja, here); - live = livea || livebv; - return 0; -} - -// the rules below are the heart of the analyzer. each is a function the parser -// calls at one kind of place where a value moves: recast (u*x), argument -// passing, assignment, return, block values, and discarding. the argument -// c they take is an expression class: C_PLAIN (an ordinary word), C_FRESH (a -// new object nobody owns yet) or C_VAR + n (a bare local, number n), which is -// how the analyzer knows the thing being moved is a variable it tracks. -// ---- analyzer rules for the places values flow ---- - -// recast: x as u / x as d / x as m -int recast(int c, int t) { - int i, st; - if (c == C_FRESH && t == S_M) return C_PLAIN; // m*f(): the unnamed result is let go on purpose, so it can be discarded - if (!fchk) return c; - if (c < C_VAR) { cerr("only a named owned object can be recast; this expression has no owner"); return c; } - i = c - C_VAR; - st = lstate[i]; - luse(i); - if (st == S_CHILD) cerrn(i, "belongs to a container; recast the container instead"); - else if (st == S_BORROW) cerrn(i, "is borrowed and can't be recast"); - else if (st == S_PLAIN) cerrn(i, "isn't an owned object"); - else if (st == S_DEAD) cerrn(i, "used after it was given away"); - else if (t == S_M && st != S_M) cerrn(i, "can't be recast back to m"); - else lstate[i] = t; - return c; -} -// an argument for a parameter tagged t (0 plain, 1 m, 2 d) -int argcheck(int c, int t) { - int i, st; - if (fchk && t == 2 && xaf && xaf[2] == 5) { cerr("a borrowed temporary cannot transfer ownership"); return 0; } - if (fchk && t == 2 && xaf && xaf[2] == 4 && (isptr(xty) || isagg(xty))) { cerr("an array literal cannot transfer ownership to a d parameter"); return 0; } - if (!fchk) return 0; - if (t == 2 && c < C_VAR && c != C_FRESH && xbor >= 0 && tracked(lroot(xbor))) { cerr("a borrowed projection cannot transfer ownership; transfer its owner"); return 0; } - if (c == C_FRESH) { - if (t != 2) { - cerr("a new object can only go straight to a d parameter; bind it to a name first"); - } else { - xgive = 1; - } - return 0; - } - if (c < C_VAR || !tracked(c - C_VAR)) return 0; - i = c - C_VAR; - if (t == 2 && isalias(i)) { cerr("a borrowed projection cannot transfer ownership; transfer its owner"); return 0; } - st = lstate[i]; - if (lstate[lroot(i)] == S_DEAD) { cerrn(i, "used after it was given away"); return 0; } - if (t <= 1) { // untagged parameters are m: a lend - if (st == S_D) { - cerrn(i, "is d; pass it to a d parameter (lending it would lose it)"); - return 0; - } - touch(i); - return 0; - } - if (st == S_D || ((st == S_M || st == S_U) && lown[i])) { end_owner(i); luse(i); xgive = 1; ev_put(i, EV_DOWN, -1); return 0; } - if (st == S_M) cerrn(i, "is m; write d*x to give it away"); - else if (st == S_U) cerrn(i, "is u; write d*x to give it away instead of returning it"); - else if (st == S_CHILD) cerrn(i, "belongs to a container and can't be given away alone"); - else cerrn(i, "is borrowed and can't be given away"); - return 0; -} -// child, owner on the stack (owner on top). write owner into the child's -// header and leave the child. a shared child keeps the owner it has. a null -// child's address is out of range and the store does not land. -// o3 code can't call own(), so this is own() inlined: skip the store when the -// child is shared (flag bit 8) and otherwise write the owner into the child's -// header word p[-2] -int o3own() { - int sk, st; - lit(1); op(O_PICK); lit(4); op(O_SUB); op(O_LD); lit(8); op(16); - sk = hole(); op(O_JZ); - op(O_DROP); - st = hole(); op(O_JMP); - setlit(sk, here); - lit(1); op(O_PICK); lit(2); op(O_SUB); op(O_SWAP); op(O_SWAP); op(O_ST); - setlit(st, here); - return 0; -} -// storing a child into container local p: emit the call that records the -// owner in the child's header. a growable array's elements live in its items -// object, so that is the owner there. -int emitown(int p) { - vaddr(p); op(O_LD); - // a T[?]'s elements live in its items object - if (ltype[p][0] == TY_DYN) { - lit(2); - op(O_ADD); - op(O_LD); - } - if (o3on && !mdbg) o3own(); - else callname(s_own); - if (o3on && hasptr(xty) && !xstamp) { lit(xty); callname(s_fatten); } - return 0; -} -// a store of class rc into an lvalue of kind k (local or container index i) -int assigncheck(int k, int i, int rc) { - int st, j, r, js, bor, origin = effect_origin(rc); - if (k == LV_LOCAL && local_origin) { local_origin[i] = origin; local_origin_mask[i] = local_origin_mask[i] | effect_mask(rc); } - if (k == LV_ELEM) { effect_note_mask(effect_mask(C_VAR + i), EF_WRITE); } - if (k != LV_LOCAL && isptr(xty)) effect_note_mask(effect_mask(rc), EF_RETAIN); - else if (k != LV_LOCAL && xty == tword) effect_note_mask(effect_mask(rc), EF_UNKNOWN); - if (fchk && xaf && xaf[2] == 5 && (isptr(xty) || xty == tword)) cerr("a borrowed temporary cannot escape its expression into a variable or container"); - bor = xbor; xbor = -1; - if (!fchk) return C_PLAIN; - if (k != LV_LOCAL && xaf && xaf[2] == 4 && (isptr(xty) || isagg(xty))) { cerr("an array literal cannot escape into a global or container"); return C_PLAIN; } - j = -1; - if (rc >= C_VAR && tracked(rc - C_VAR)) j = rc - C_VAR; - if (j >= 0 && lstate[lroot(j)] == S_DEAD) { - cerrn(j, "used after it was given away"); - return C_PLAIN; - } - if (k == LV_LOCAL) { - st = lstate[i]; - if (isalias(i)) st = S_PLAIN; // a borrow isn't an owner; overwriting it drops the alias - if (j < 0 && rc != C_FRESH) { - if (!empty(st)) cerrn(i, "still owns an object; overwriting it would leak it"); - else if (bor >= 0 && bor != i && (tracked(bor) || larr[bor])) { lstate[i] = S_CHILD; lowner[i] = bor; lown[i] = 0; } - else lstate[i] = S_PLAIN; - return C_PLAIN; - } - if (!empty(st)) { - cerrn(i, "still owns an object; overwriting it would leak it"); - return C_PLAIN; - } - if (rc == C_FRESH) { - lstate[i] = S_M; - lused[i] = 0; - lown[i] = 1; - local_ended[i] = 0; local_end_info[i] = 0; - if (ltag[i] & 15) lstate[i] = ltag[i] & 15; - return C_PLAIN; - } - js = lstate[j]; - if (js == S_BORROW || isalias(j)) { lstate[i] = S_CHILD; lowner[i] = lroot(j); lown[i] = 0; touch(j); return C_PLAIN; } - // replace a released owner by moving a same-scope owner into its empty slot. - // fresh declarations still cannot copy owners; the source and its aliases expire. - if (st == S_DEAD && lown[i] && js == S_M && lown[j] && lhome[j] >= lhome[i] - && !local_captured[i] && !local_captured[j]) { - lstate[i] = S_M; lused[i] = 0; lown[i] = 1; - lnsz[i] = lnsz[j]; lleaf[i] = lleaf[j]; - local_ended[i] = 0; local_end_info[i] = 0; - end_owner(j); luse(j); ev_put(j, EV_MOVE, i); - if (ltag[i] & 15) lstate[i] = ltag[i] & 15; - return C_PLAIN; - } - if (js == S_U && lhome[i] == lhome[j] - 1) { // handing a u object up one block - lstate[i] = S_M; - lused[i] = 0; - lown[i] = 1; - end_owner(j); - luse(j); ev_put(j, EV_UP, i); - if (ltag[i] & 15) lstate[i] = ltag[i] & 15; - return C_PLAIN; - } - if (js == S_U) { - cerrn(j, "is u; it can only be handed to a variable of the enclosing block, or returned"); - } - else cerrn(j, "is an owned object and can't be copied into another variable"); - return C_PLAIN; - } - if (k == LV_ELEM) { - r = lroot(i); - st = lstate[r]; - touch(i); - if (rc == C_FRESH) { emitown(i); return C_PLAIN; } - if (j < 0) return C_PLAIN; - js = lstate[j]; - if (js == S_CHILD) cerrn(j, "already belongs to a container"); - else if (js == S_BORROW) cerrn(j, "is borrowed and can't be stored"); - else if (st == S_BORROW) { - if (js == S_U) { - cerrn(j, "is u; it has to leave through the block above, not into a borrowed object"); - } - else { end_owner(j); luse(j); ev_put(j, EV_STORE, i); emitown(i); } // storing it gives it away - } else if (js != st) cerrn(j, "its tag doesn't match its container's (both have to be m, u or d)"); - else if (lhome[j] < lhome[r]) cerrn(j, "lives in an outer block than the container it's stored into"); - else { lstate[j] = S_CHILD; lowner[j] = i; luse(j); ev_put(j, EV_STORE, i); emitown(i); } - return C_PLAIN; - } - // a global, or memory the analyzer doesn't track: the collector takes it - // an owner (d, u, or an m local that owns its object) is transferred as if by u*x - if (k == LV_GLOBAL) lend_clear(i); - if (j >= 0 && k == LV_GLOBAL && xlend == j && lstate[j] == S_M && lown[j]) { xlend = -1; touch(j); lend_add(i, j); return C_PLAIN; } - xlend = -1; - if (j >= 0) { - if (lstate[j] == S_D || lstate[j] == S_U || (lstate[j] == S_M && lown[j])) { ev_put(j, EV_GLOBAL, -1); end_owner(j); luse(j); } - else cerrn(j, "is not an owner, so it can't be stored in a global or in untracked memory (the collector takes it over)"); - } - return C_PLAIN; -} -// return c: an owned object (d, or u after u*x) goes up with the return, and the -// function's return is owned: declared with .u, or inferred from this return when -// the function has no .u (its callers compiled after this point see it). an m -// object is lent, not given, so returning it is an error, and so is a plain word -// from an owned function that was called before its body. -int retcheck(int c) { - int i; - if (isptr(xty) || xty == tword) { - effect_note_mask(effect_mask(c), EF_ALIAS); - if (c >= C_VAR && lrskind[c - C_VAR] == RS_PARAM) effect_note(effect_origin(c), EF_IDENTITY); - } - if (fchk && xaf && xaf[2] == 5 && (isptr(xty) || xty == tword)) cerr("a borrowed temporary cannot escape its expression through return"); - if (!fchk) return 0; - if (c < C_VAR && c != C_FRESH && xbor >= 0 && (isptr(xty) || xty == tword)) { - int root = lroot(xbor); - if (fret) { cerr("a borrowed projection cannot be returned as an owned result"); return 0; } - if (lown[root] && (lstate[root] == S_M || lstate[root] == S_U || lstate[root] == S_D)) { cerr("a borrow of a local owner cannot escape through return; return the owner"); return 0; } - } - if (c >= C_VAR && isalias(c - C_VAR)) { - int root = lroot(c - C_VAR); - if (fret) cerr("a borrowed projection cannot be returned as an owned result"); - else if (lown[root]) cerr("a borrow of a local owner cannot escape through return; return the owner"); - return 0; - } - if (c >= C_VAR && lstate[c - C_VAR] == S_BORROW) { - if (fret) cerr("a borrowed parameter cannot be returned as an owned result"); - return 0; - } - if ((isptr(xty) || isagg(xty)) && ((xaf && xaf[2] == 4) || (xbor >= 0 && lafact[xbor] && lafact[xbor][2] == 4))) { cerr("an array literal cannot escape its function through return"); return 0; } - i = -1; - if (c >= C_VAR && tracked(c - C_VAR)) i = c - C_VAR; - if (i >= 0 && lstate[lroot(i)] == S_DEAD) { cerrn(i, "used after it was given away"); return 0; } - if (i >= 0 && (lstate[i] == S_D || (lstate[i] == S_M && lown[i])) && (fret || finfer)) lstate[i] = S_U; // returning an owned object (a d object, or a local that owns one) gives it up, as u*x does - if (!fret && finfer && (c == C_FRESH || (i >= 0 && lstate[i] == S_U))) { - if (sflag[finsym] & F_EARLY) serrn(sname[finsym], "is called before its body, so its owned return can't be known there; declare the return type with .u"); - ssig[finsym] = ssig[finsym] | 1; // the return is owned: callers read it from the signature - sflag[finsym] = sflag[finsym] | F_RETOWN; - if (stype[finsym]) { word ft = stype[finsym]; stype[finsym] = tfn(ft[4], ft[3], ft + 7, ssig[finsym]); } - fret = 1; finfer = 0; - } - if (fret) { - if (i >= 0) { - if (lstate[i] == S_U) { end_owner(i); luse(i); ev_put(i, EV_RETURN, -1); } - else cerrn(i, "has to be u to be returned"); - } - return 0; - } - if (c == C_FRESH) cerr("returns a new object; give the function a .u return type"); - else if (i >= 0) cerrn(i, "is owned and can't be returned from a function whose return type isn't .u"); - return 0; -} -// the last expression of a block expression -int handup(int c) { - int i; - if (!fchk) return C_PLAIN; - if (c == C_FRESH) return C_FRESH; - if (c < C_VAR || !tracked(c - C_VAR)) return C_PLAIN; - i = c - C_VAR; - if (lstate[i] == S_U && lhome[i] == fdepth) { lstate[i] = S_DEAD; luse(i); ev_put(i, EV_VALUE, -1); return C_FRESH; } - if (lstate[i] == S_U) cerrn(i, "only a u object of this block can be its value"); - else cerrn(i, "has to be u to be handed up as the block's value"); - return C_PLAIN; -} -// throw away an expression result, which is an error for a fresh object. -int discard(int c) { - if (c == C_FRESH) cerr("a new object (or a u result) can't be thrown away"); - else if (c >= C_VAR) touch(c - C_VAR); - xbor = -1; - return 0; -} - -// ---- completion ---- - -// copy local i (kind: 1 local, 2 parameter, 3 captured from the function around a closure) into the probe, -// unless a nearer local has its name -int probe_add(int i, int kind) { - int k = 0; - while (k < probe_n) { if (probe_name[k] == lname[i]) return 0; k++; } - if (!probe_name || probe_n == probe_cap) { - if (!probe_cap) probe_cap = 32; else probe_cap = probe_cap * 2; - probe_name = chk_resize(probe_name, probe_n, probe_cap); probe_kind = chk_resize(probe_kind, probe_n, probe_cap); - probe_state = chk_resize(probe_state, probe_n, probe_cap); probe_type = chk_resize(probe_type, probe_n, probe_cap); probe_decl = chk_resize(probe_decl, probe_n, probe_cap); - } - probe_name[probe_n] = lname[i]; probe_kind[probe_n] = kind; - probe_state[probe_n] = lstate[lroot(i)] == S_DEAD || af_expired(lafact[i]); - if (lafact[i] && lafact[i][1] == 2) probe_state[probe_n] = 1; - probe_type[probe_n] = ltype[i]; probe_decl[probe_n] = lrsoff[i]; - probe_n++; - return 0; -} -// what is in scope right now: this function's locals, then those of the functions around a closure -int probe_snap() { - int i = nloc, k = nclo; - probe_n = 0; probe_has = 1; - while (i > lbase) { - i--; - if (lname[i][0] != '<') { if (lrskind[i] == RS_PARAM) probe_add(i, 2); else probe_add(i, 1); } - } - while (k) { - k--; - i = clo_hi[k]; - while (i > clo_lo[k]) { i--; if (lname[i][0] != '<') probe_add(i, 3); } - } - return 0; -} -// a statement of the document starts here: remember what is in scope, or stop when the place has been passed. -// statements of a method or generic compiled for a call belong to another function and are left alone -int probe_stmt() { - if (!cmode || dry || ginst || chkabort || incdepth != 0 || ctxsp > nclo) return 0; - probe_snap(); - if (toff > probe_off) { chkabort = 1; return 0; } - return 0; -} -// the place asked about is a member name, and t is the type before the dot -int probe_member(word t) { - probe_mem = t; probe_has = 2; chkabort = 1; - return 0; -} - -// ---- types ---- - -// types are a compiler primitive: records in the type area, hash-consed so -// the same type is always the same pointer and comparing types is comparing -// words. untyped code is typed word, which goes with anything. the records -// stay in memory after compiling, so programs can inspect them. -// t[0] kind t[1] size in words t[2] name t[3] n t[4] a t[5] b t[6] c t[7..] extra -// ptr: a = element arr: a = element, n = length -// fn: a = return type, n = parameter count, b = signature (u m d tags), extra = parameter types -// struct: n = field count (-1 until laid out), a = fields (name, type, offset), -// b = generic template, c = type arguments [n, t1, t2 ...] -// var: a type parameter, n = its index -// gen: a generic struct: n = parameter count, a = fields (name, type), b = field count, c = parameter names - - -// the type area is a bump allocator of its own (tytop .. R_TYEND). talloc -// hands out zeroed words, tnew makes a record and adds it to the list of all -// types (R_TY), and the constructors below look that list up first, which is -// the hash-consing: asking for the same pointer type twice returns the same -// record. the list is searched linearly, which is fine for the few hundred -// types a program has. -int talloc(int n) { - int p = tytop; - if (tytop + n > tyend) { - p = chk_resize(0, 0, n + 16385); p[0] = typeblocks; typeblocks = p; - p++; tytop = p; tyend = p + n + 16384; - } - tytop = tytop + n; - rt.fill_w(p, 0, n); - return p; -} -// make a type record of the given kind, size and name in the type area. -word tnew(int kind, int size, char* nm, int extra) { - word t = talloc(7 + extra); - t[0] = kind; t[1] = size; t[2] = nm; - if (ntl >= typecap) { typelist = chk_resize(typelist, ntl, typecap * 2); typecap = typecap * 2; } - typelist[ntl] = t; ntl++; - return t; -} -// qualified types retain their layout and reference their unqualified record. -word tunconst(word t) { - word entry = readonly_types; - while (entry) { if (entry[1] == t) return entry[2]; entry = entry[0]; } - return t; -} -int treadonly(word t) { return t && tunconst(t) != t; } -word tconst(word t) { - word entry = readonly_types, qualified; int words = 7; - if (treadonly(t)) return t; - while (entry) { if (entry[2] == t) return entry[1]; entry = entry[0]; } - if (t[0] == TY_FN) words = words + t[3]; - qualified = tnew(t[0], t[1], t[2], words - 7); rt.copy_w(qualified, t, words); - entry = talloc(3); entry[0] = readonly_types; entry[1] = qualified; entry[2] = t; readonly_types = entry; - return qualified; -} -// writes require mutable storage, independently of optional type checking. -int require_mutable(word t) { - if (treadonly(t)) serr("const storage cannot be modified"); - return 0; -} -// the pointer type to type e, reused when it already exists. -word tptr(int e) { - int i = 0; word t; - while (i < ntl) { t = typelist[i]; if (t[0] == TY_PTR && t[4] == e && !treadonly(t)) return t; i++; } - t = tnew(TY_PTR, 1, 0, 0); t[4] = e; - return t; -} -// sized integers (i8 u8 i16 u16 i32 u32) are TY_INT records with t[5] = bytes, -// t[6] bit 0 = signed, and t[4] = the "packed view" twin: the same name with -// t[6] bit 1 set. a variable, parameter or field of a sized type is an -// ordinary word that is cut to size whenever it is stored. an element of an -// array or a pointer of a sized type is stored packed, 8, 4 or 2 to a word, -// and indexing yields the twin, whose lvalue is a byte address. -word tbytes(word t) { if (t[0] == TY_INT || t[0] == TY_FLOAT) return t[5]; return 0; } -// true for a packed integer or float type. -word ispk(word t) { return (t[0] == TY_INT || t[0] == TY_FLOAT) && (t[6] & 2); } -// the type a packed type stores its elements as, or the type itself. -word tpk(word t) { if (treadonly(t)) return tconst(tpk(tunconst(t))); if ((t[0] == TY_INT || t[0] == TY_FLOAT) && t[4]) return t[4]; return t; } -// an enhancement used by the compiler itself, inline when the runtime has it -int xenh(char* name) { - int s = lookup(intern(name)); - if (s >= 0 && skind[s] == K_ENH) { op(XOP0 + sval[s]); return 0; } - callname(intern(name)); - return 0; -} -// words an array of n elements of e takes: packed for a sized integer -word tasz(word e, int n) { - if (n < 0) return 0; - if (tbytes(e)) return (n * e[5] + 7) >> 3; - return n * tsz(e); -} -// the fixed array type of n elements of type e, reused when it already exists. -word tarr(int e, int n) { - int i = 0; word t; - while (i < ntl) { t = typelist[i]; if (t[0] == TY_ARR && t[4] == e && t[3] == n && !treadonly(t)) return t; i++; } - t = tnew(TY_ARR, tasz(e, n), 0, 0); t[4] = e; t[3] = n; - return t; -} -// a sized integer type and its packed twin -word tsized(char* name, int bytes, int sgn) { - word t = tnew(TY_INT, 1, intern(name), 0), twin = tnew(TY_INT, 1, t[2], 0); - t[5] = bytes; t[6] = sgn; t[4] = twin; - twin[5] = bytes; twin[6] = sgn | 2; - addsym(t[2], K_TYPE, t); - return t; -} -// the function type with this return, parameter list and signature. -word tfn(int ret, int np, word ps, int sig) { - int i = 0, j; word t; - while (i < ntl) { - t = typelist[i]; - if (t[0] == TY_FN && t[4] == ret && t[3] == np && t[5] == sig) { - j = 0; while (j < np && t[7 + j] == ps[j]) j++; - if (j == np) return t; - } - i++; - } - t = tnew(TY_FN, 1, 0, np); t[4] = ret; t[3] = np; t[5] = sig; - rt.copy_w(t + 7, ps, np); - return t; -} -// T[?], or fixed T[?] -word tdyn(int e, int f) { - int i = 0; word t; - while (i < ntl) { t = typelist[i]; if (t[0] == TY_DYN && t[4] == e && t[3] == f && !treadonly(t)) return t; i++; } - t = tnew(TY_DYN, 1, 0, 0); t[4] = e; t[3] = f; - return t; -} -// the type variable with index i, reused when it already exists. -word tvar(int i, char* nm) { - int k = 0; word t; - while (k < ntl) { t = typelist[k]; if (t[0] == TY_VAR && t[3] == i) return t; k++; } - t = tnew(TY_VAR, 1, nm, 0); t[3] = i; - return t; -} -// the instance of generic struct g with type arguments args[0..n). found if it -// already exists, otherwise created unlaid-out (n = -1), so that mentioning -// vec<T> inside vec<T>'s own methods does not recurse; tresolve lays it out. -word tinst(word g, word args, int n) { - int i = 0, j; word t, v; - while (i < ntl) { - t = typelist[i]; - if (t[0] == TY_STRUCT && t[5] == g) { - v = t[6]; j = 0; while (j < n && v[1 + j] == args[j]) j++; - if (j == n) return t; - } - i++; - } - v = talloc(n + 1); v[0] = n; rt.copy_w(v + 1, args, n); - t = tnew(TY_STRUCT, 0, g[2], 0); t[3] = -1; t[5] = g; t[6] = v; - return t; -} -// lay out a generic instance on first use -word tresolve(word t) { - word g, f; int n, i; word fs; int off = 0, ft; word[8] bs; - if (t[0] != TY_STRUCT || t[3] >= 0) return t; - if (t[3] == -2) { serr("a struct can't contain itself by value"); t[3] = 0; return t; } - t[3] = -2; - g = t[5]; n = g[5]; f = g[4]; - rt.fill_w(bs, 0, 8); rt.copy_w(bs, t[6] + 1, t[6][0]); - fs = talloc(n * 3 + 1); - i = 0; - while (i < n) { - ft = subst(f[i + i + 1], bs); - fs[i * 3] = f[i + i]; fs[i * 3 + 1] = ft; fs[i * 3 + 2] = off; - off = off + tsz(ft); - i++; - } - t[4] = fs; t[1] = off; t[3] = n; - return t; -} -// size of a type in words, laying out a generic instance first if it needs it -word tsz(word t) { - if (t[0] == TY_STRUCT && t[3] < 0) tresolve(t); - return t[1]; -} -// replace type parameters with what they were bound to (unbound ones become word) -word subst(word t, word bs) { - if (treadonly(t)) return tconst(subst(tunconst(t), bs)); - int k = t[0], i, n; word[24] v; word vec; - if (k == TY_VAR) { if (bs[t[3]]) return bs[t[3]]; return tword; } - if (k == TY_PTR) return tptr(subst(t[4], bs)); - if (k == TY_ARR) return tarr(subst(t[4], bs), t[3]); - if (k == TY_DYN) return tdyn(subst(t[4], bs), t[3]); - if (k == TY_FN) { - n = t[3]; - i = 0; - while (i < n) { v[i] = subst(t[7 + i], bs); i++; } - return tfn(subst(t[4], bs), n, v, t[5]); - } - if (k == TY_STRUCT && t[5]) { - vec = t[6]; - n = vec[0]; - i = 0; - while (i < n) { v[i] = subst(vec[1 + i], bs); i++; } - return tinst(t[5], v, n); - } - return t; -} -// small type predicates. an array decays to a pointer to its element where a -// value is wanted, and telem is what indexing a pointer or array yields. -word isnum(word t) { return t[0] == TY_INT || t[0] == TY_CHAR || t[0] == TY_BOOL; } -// true for an aggregate type: an array or a struct. -word isagg(word t) { return t[0] == TY_ARR || t[0] == TY_STRUCT; } -// true for a pointer or an array type. -word isptr(word t) { return t[0] == TY_PTR || t[0] == TY_ARR || t[0] == TY_BSTR; } -// true for a reference value, including growable-array handles and functions. -word isref(word t) { return isptr(t) || t[0] == TY_DYN || t[0] == TY_FN; } -// the pointer an array type turns into when used as a value. -word decay(word t) { - if (treadonly(t)) return tconst(decay(tunconst(t))); - if (t[0] == TY_ARR) return tptr(t[4]); - return t; -} -// the element type of a pointer or array, or word for anything else. -word telem(word t) { - if (treadonly(t)) return tconst(telem(tunconst(t))); - if (t[0] == TY_PTR || t[0] == TY_ARR) return t[4]; - return tword; -} -// can a value of type src go where dst is expected? -// type compatibility, used by the optional typechecker. word is compatible with -// everything (untyped code stays legal), numbers convert freely, pointers need -// the same element unless one side is untyped or void, and a literal 0 is a -// null pointer. -word compat(word dst, word src) { - if (treadonly(src) && isref(src) && !treadonly(dst)) return 0; - dst = tunconst(dst); src = tunconst(src); - if (dst == src) return 1; - if (dst == tword && src && (src == tbstr || (src[0] == TY_PTR && src[4] == tchar) || (src[0] == TY_ARR && src[4] == tchar))) return 0; - if (dst == tword || src == tword) return 1; - if (dst == tbstr || src == tbstr) return dst == tbstr && xzero; - if (dst[0] == TY_PTR && src[0] == TY_STRUCT && dst[4] == src) return 1; // an in-place struct supplies its address - if (isen(dst) || isen(src)) { // an enum takes only its own enum; an enum still becomes an int - if (isen(dst) && isen(src)) return dst == src; - if (isen(dst)) return 0; - } - if (isnum(dst) && isnum(src)) return 1; - if (isfl(dst) && isfl(src)) return 1; - src = decay(src); - if (dst[0] == TY_PTR && src[0] == TY_PTR) { - if (dst[4] == tword || src[4] == tword || dst[4] == tvoid || src[4] == tvoid) return 1; - return dst[4] == src[4]; - } - if ((dst[0] == TY_PTR || dst[0] == TY_FN || dst[0] == TY_DYN) && xzero) return 1; // a literal 0 is a null pointer - if (dst[0] == TY_DYN && src[0] == TY_DYN && dst[4] == src[4]) return dst[3] || !src[3]; // a growable array can't hold a fixed one - return dst == src; -} -// match pattern type p (which may contain type parameters) against actual type -// a, filling the bindings bs. this is how a call to a generic function infers -// T from its arguments. -word unify(word p, word a, word bs) { - int i, n; word pv, av; - if (p[0] != TY_VAR) { - if (treadonly(a) && isref(a) && !treadonly(p)) return 0; - p = tunconst(p); a = tunconst(a); - } - if (p[0] == TY_VAR) { i = p[3]; if (!bs[i]) { bs[i] = a; return 1; } return compat(bs[i], a); } - a = decay(a); - if (p[0] == TY_PTR && a[0] == TY_PTR) { if (a[4] == tword) return 1; return unify(p[4], a[4], bs); } - if (p[0] == TY_DYN && a[0] == TY_DYN) return (p[3] || !a[3]) && unify(p[4], a[4], bs); - if (p[0] == TY_FN && a[0] == TY_FN) { - if (p[3] != a[3] || ((p[5] >> 6) != (a[5] >> 6))) return 0; - i = 0; - while (i < p[3]) { - pv = subst(p[7 + i], bs); av = a[7 + i]; - if (pv != tword && av != tword && pv != av) return 0; - if (!unify(p[7 + i], av, bs)) return 0; - i++; - } - return unify(p[4], a[4], bs); - } - if (p[0] == TY_STRUCT && p[5] && a[0] == TY_STRUCT && a[5] == p[5]) { - pv = p[6]; av = a[6]; n = pv[0]; i = 0; - while (i < n) { if (!unify(pv[1 + i], av[1 + i], bs)) return 0; i++; } - return 1; - } - return compat(p, a); -} -// print a type the way it is written in source. function pointers use an alias -// for source spelling; array pointers use T[N]*. -int tprint_dimensions(word t) { - while (t[0] == TY_ARR || t[0] == TY_DYN) { rt.put_c('['); if (t[0] == TY_DYN) rt.put_c('?'); else if (t[3] >= 0) rt.put_n(t[3]); rt.put_c(']'); t = t[4]; } - return 0; -} -word tprint(word t) { - if (treadonly(t)) { rt.put_s("const "); return tprint(tunconst(t)); } - int k = t[0], i; word v; - if (k == TY_PTR) { // postfix pointers; only function pointers keep the display-only prefix form - v = t[4][0]; - if (v == TY_FN) { rt.put_c('*'); return tprint(t[4]); } - tprint(t[4]); rt.put_c('*'); - return 0; - } - if (k == TY_ARR || k == TY_DYN) { if (k == TY_DYN && t[3]) rt.put_s("fixed "); v = t; while (v[0] == TY_ARR || v[0] == TY_DYN) v = v[4]; tprint(v); return tprint_dimensions(t); } - if (k == TY_FN) { - if (t[4] == tword) rt.put_s("fn"); else tprint(t[4]); - if (t[5] & 1) rt.put_s(".u"); - rt.put_c('('); - i = 0; - while (i < t[3]) { - if (i) rt.put_s(", "); - tprint(t[7 + i]); - if (i < 12 && tparamtag(t, i) == 1) rt.put_s(".m"); - if (i < 12 && tparamtag(t, i) == 2) rt.put_s(".d"); - i++; - } - rt.put_c(')'); - return 0; - } - if (t[2]) rt.put_s(t[2]); else rt.put_s("?"); - if (k == TY_STRUCT && t[5]) { - v = t[6]; rt.put_c('<'); - i = 0; while (i < v[0]) { if (i) rt.put_s(", "); tprint(v[1 + i]); i++; } - rt.put_c('>'); - } - return 0; -} -// typechecker errors. they only fire when "typecheck on" is set, in reachable -// code, and never during a dry run. -int tcerr(int what, int want, int got) { - if (!tc_on || !live || dry || panic) return 0; - errat(); rt.put_s("type error: "); rt.put_s(what); - rt.put_s(": expected "); tprint(want); rt.put_s(", got "); tprint(got); - errend(); - return 0; -} -// report a type error when ok is false, showing the type it got. -int tcneed(int ok, int what, int got) { - if (ok || !tc_on || !live || dry || panic) return 0; - errat(); rt.put_s("type error: "); rt.put_s(what); rt.put_s(", got "); tprint(got); - errend(); - return 0; -} -// the type parameters in scope (tpn): a generic function or struct body sees -// T as a type. istypename says whether a name starts a type here, which is how -// the parser tells a declaration from an expression. -int tpfind(int n) { - int i = ntp; - while (i) { - i--; - if (tpn[i] == n) return i; - } - return -1; -} -// true when the name n is a type, a type parameter or a generic type. -word istypename(char* n) { - int s; - if (n == s_fixed || n == s_const) return 1; - if (ginst && gfind(n) >= 0) return 1; - if (tpfind(n) >= 0) return 1; - if (findloc(n) >= 0) return 0; - s = lookup(n); - return s >= 0 && skind[s] == K_TYPE; -} -// the closing > of a type argument list; splits >> in two -int closeangle() { - if (is_op(s_shr)) { tv = s_gt; return 0; } - expect(s_gt); - return 0; -} -// a type, then any number of (params) after it: int(int, int) is a function -// returning int, fn(int) one returning a plain word -// ---- parsing types ---- - -// grammar, outermost first: -// ptype a type, then any number of (params): int(int, int) is a function type -// ptype0 postfix pointers and array groups: T*, T[N], T[], T[?] -// ptype1 a type name, fixed qualifier, function base, or generic instance -// the first thing in ptype's comment applies here too: fn(int) is a function -// returning a plain word. -int ptype() { - int start = toff, t = ptype0(), np, tags, tag; word[24] ps; - while (is_op(s_lp)) { - next(); np = 0; tags = 0; - if (!is_op(s_rp)) { - while (1) { - if (np >= 24) fatal("too many parameters"); - ps[np] = ptype(); if (ps[np][0] == TY_ARR) ps[np] = decay(ps[np]); - tag = 0; - if (accept(s_dot)) { - if (tv == s_m) tag = 1; else if (tv == s_d) tag = 2; else die("callback parameter lifetime is .m or .d"); - next(); - } - tags = tags | (tag << (np + np)); - np++; - if (!accept(s_comma)) break; - } - } - expect(s_rp); - t = tfn(t, np, ps, np + np + tags * 64); - } - ht_note(start, toff, t); - return t; -} -// a type with any stars after it: int*, vec<T>*, point** -int ptype0() { - int t = ptype1(), cap = 8, n, count, j; word dims = 0; - while (is_op(s_star) || is_op(s_lk)) { - if (accept(s_star)) t = tptr(t); - else { - count = 0; - while (accept(s_lk)) { - if (accept(s_q)) { expect(s_rk); n = -2; } - else if (accept(s_rk)) n = -1; - else { n = cexpr(); expect(s_rk); if (n < 0) serr("a negative array length"); } - if (!dims || count == cap) { if (dims) cap = cap * 2; dims = chk_resize(dims, count, cap); } - dims[count] = n; count++; - } - j = count; while (j) { j--; if (dims[j] == -2) t = tdyn(t, 0); else t = tarr(t, dims[j]); } - } - } - if (dims) rt.free(dims); - return t; -} -// parse one type with no trailing function parts. -word ptype1() { - int n, s; word t; word[8] args; int na = 0, i; - if (is_op(s_lk)) { die("array types are postfix: T[N], T[] or T[?]"); return tword; } - if (is_id(s_const)) { next(); return tconst(ptype0()); } - if (is_id(s_fixed)) { // fixed T[?] can't grow; fixed T[n] says what T[n] already means - next(); t = ptype0(); - if (t[0] == TY_DYN) { if (treadonly(t)) return tconst(tdyn(t[4], 1)); return tdyn(t[4], 1); } - if (t[0] != TY_ARR) serr("only arrays can be fixed"); - return t; - } - if (is_id(s_fn)) { - next(); - if (!is_op(s_lp)) die("fn as a type needs its parameters: fn(int)"); - return tword; - } - if (tk != T_ID) { diefound("expected a type"); return tword; } - n = tv; - if (ginst) { - i = gfind(n); - if (i >= 0) { next(); return gib[i]; } - } - i = tpfind(n); - if (i >= 0) { next(); return tvar(i, n); } - s = source_lookup(n); - // T in T first<T>(...) - if (tvauto && (s < 0 || skind[s] != K_TYPE) && ntp < 8) { - tpn[ntp] = n; - lsmark(2, n); - ntp++; - next(); - return tvar(ntp - 1, n); - } - if (s < 0 || skind[s] != K_TYPE) { dien(n, "isn't a type"); return tword; } - rs_put(toff, RS_SYM, rs_sym(s), s); - next(); - t = sval[s]; - if (t[0] != TY_GEN) return t; - expect(s_lt); - while (1) { - if (na >= 8) die("too many type arguments"); - args[na] = ptype(); - if (args[na] == tvoid) serr("a type argument can't be void"); - na++; - if (!accept(s_comma)) break; - } - closeangle(); - if (na != t[3]) serrn(n, "wrong number of type arguments"); - return tinst(t, args, na); -} -// does a declaration start here? (a type, or a bare .tag) -// does a declaration start at the current token? (a type name, [, or a bare .tag) -int tstart() { - if (tk == T_OP) { - if (tv == s_lk || tv == s_dot) return 1; - return 0; - } - if (tk != T_ID) return 0; - if (tv == s_fn) return 1; - if (!istypename(tv)) return 0; - // Type.name(...) at the start of a statement is a static call, not a declaration: - // a declaration's dot is followed by a tag, u, m or d - peekat(0); - if (pk_tk == T_OP && pk_tv == s_dot) { - peekat(1); - if (pk_tk == T_ID && pk_tv != s_u && pk_tv != s_m && pk_tv != s_d) return 0; - } - return 1; -} -// [type] [.tag] -// parse a declaration's [type] [.tag] into dty and dtag -int dtype() { - dty = 0; dtag = 0; - if (!is_op(s_dot)) dty = ptype(); - if (accept(s_dot)) { - if (tv == s_m) { - dtag = S_M; - } else if (tv == s_u) { - dtag = S_U; - } else if (tv == s_d) dtag = S_D; else die("a tag is .u, .m or .d"); - next(); - } - return 0; -} -// the struct whose body or methods are being compiled, when it declares constants: a bare -// name there is looked up as Struct.name before the global scope -char* mstruct; -// does a constant declaration start in a struct body? const NAME = e; const T NAME = e; -// (a field of a const type has no initializer, so the = tells them apart) -int struct_const_start() { - if (!is_id(s_const)) return 0; - peekat(0); if (pk_tk != T_ID) return 0; - peekat(1); - if (pk_tk == T_OP && pk_tv == s_eq) return 1; - if (pk_tk == T_ID) { peekat(2); return pk_tk == T_OP && pk_tv == s_eq; } - if (pk_tk == T_OP && pk_tv == s_star) { peekat(2); if (pk_tk != T_ID) return 0; peekat(3); return pk_tk == T_OP && pk_tv == s_eq; } - return 0; -} -// const NAME = e, NAME = e; inside struct sn: a compile-time constant named Struct.NAME that takes no -// space in the layout. the optional type is read and dropped, as in gconst. -int struct_const(char* sn) { - int off, s; char* cn; - next(); - if (tk == T_ID) { peekat(0); if (pk_tk == T_ID || (pk_tk == T_OP && pk_tv == s_star)) ptype(); } - mstruct = sn; - while (1) { - off = toff; cn = name(); - s = lookup(methname(sn, cn)); - if (s >= ubase && s >= 0) serrn(cn, "is defined twice in this struct"); - expect(s_eq); - s = addsym(methname(sn, cn), K_CONST, cexpr()); - rs_put(off, RS_SYM, rs_sym(s), s); - if (!is_op(s_comma) && !is_op(s_semi)) { // a call or a variable's value follows what cprim could read - serr("a struct constant must be a constant expression"); - while (!is_op(s_semi) && !is_op(s_rb) && tk != T_EOF) next(); - break; - } - if (!accept(s_comma)) break; - } - accept(s_semi); - return 0; -} -// struct name { type field, ... } or struct name<A, B> { ... } -// the struct keyword. fields go in a scratch buffer first (name, type pairs), -// then are laid out: each field takes tsz(type) words, so offsets are words. -// methods are collected as token lists while the struct is read (grabfn) and -// compiled only when the struct is complete, so a method can use every field -// and call its siblings. a generic struct keeps its fields unresolved and -// can't have methods; each instance is laid out later by tresolve. -int kw_struct() { - char* n; word t; int s, nf = 0; word buf = chk_resize(0, 0, 64); int bufcap = 64, i; word fs; int off = 0, gen = 0, ft, nm = 0, mcap = STRUCT_METHODS; char** mn = chk_resize(0, 0, mcap); word mq = chk_resize(0, 0, mcap), mst = chk_resize(0, 0, mcap), mty = chk_resize(0, 0, mcap), mrt = chk_resize(0, 0, mcap), mrg = chk_resize(0, 0, mcap); int bo, k, stat = 0; char* fname = 0; char* msv = mstruct; - next(); - n = module_symbol(name()); - if (is_op(s_lt)) { // generic - next(); ntp = 0; - while (1) { - if (ntp >= 8) die("too many type parameters"); - tpn[ntp] = name(); - lsmark(2, tpn[ntp]); - ntp++; - if (!accept(s_comma)) break; - } - closeangle(); - gen = 1; - t = tnew(TY_GEN, 0, n, 0); t[3] = ntp; - fs = talloc(ntp); rt.copy_w(fs, tpn, ntp); t[6] = fs; - } else { t = tnew(TY_STRUCT, 0, n, 0); t[3] = -2; } // -2 while its fields are read - s = addsym(n, K_TYPE, t); - expect(s_lb); - while (!is_op(s_rb)) { - if (tk == T_EOF) { die("missing }"); break; } - stat = 0; - if (struct_const_start()) { struct_const(n); continue; } - if (is_id(s_unchecked)) { next(); stat = 2; } - if (is_id(s_static)) { // static fn name(params) { body }, or static T name(params) { body }: a function of the struct, called as Struct.name(args), with no this - next(); - stat = stat | 1; - } - if (is_id(s_fn)) { // fn name(params) { body }: a method, compiled once the struct is complete - next(); - if (nm >= mcap) { - mcap = mcap * 2; mn = chk_resize(mn, nm, mcap); mq = chk_resize(mq, nm, mcap); - mst = chk_resize(mst, nm, mcap); mty = chk_resize(mty, nm, mcap); - mrt = chk_resize(mrt, nm, mcap); mrg = chk_resize(mrg, nm, mcap); - } - mn[nm] = name(); - mst[nm] = stat; - mq[nm] = grabfn(); - mty[nm] = 0; - k = methdecl(n, mn[nm]); - if (stat & 1) sflag[k] = sflag[k] | F_STATIC; - nm++; - continue; - } - // let x, y; fields are plain words - fname = 0; - if (is_id(s_var)) { - if (stat & 1) die("static must be followed by fn or a typed method"); - next(); - ft = tword; - } else { - // T name(params) { body } is a typed method; T name, ...; is a typed field - rettype(); - fname = name(); - if (is_op(s_lp)) { - if (nm >= mcap) { - mcap = mcap * 2; mn = chk_resize(mn, nm, mcap); mq = chk_resize(mq, nm, mcap); - mst = chk_resize(mst, nm, mcap); mty = chk_resize(mty, nm, mcap); - mrt = chk_resize(mrt, nm, mcap); mrg = chk_resize(mrg, nm, mcap); - } - mn[nm] = fname; - mst[nm] = stat; - mq[nm] = grabfn(); - mty[nm] = 1; mrt[nm] = dty; mrg[nm] = dtag == S_U; - if (dtag && dtag != S_U) serr("the only return tag is .u"); - k = methdecl(n, fname); - if (stat & 1) sflag[k] = sflag[k] | F_STATIC; - nm++; - continue; - } - if (stat & 1) die("static must be followed by fn or a typed method"); - if (dtag) serr("a field has no tag"); - ft = dty; - } - while (1) { - if ((nf + 1) * 2 > bufcap) { buf = chk_resize(buf, nf * 2, bufcap * 2); bufcap = bufcap * 2; } - if (fname) { buf[nf + nf] = fname; fname = 0; } else buf[nf + nf] = name(); - lsmark(3, buf[nf + nf]); buf[nf + nf + 1] = ft; - field_note(t, buf[nf + nf], nameoff); lt_note(nameoff, ft); rs_put(nameoff, RS_FIELD, nameoff, 0); nf++; - if (!accept(s_comma)) break; - } - expect(s_semi); - } - next(); - if (gen) { - fs = talloc(nf + nf); rt.copy_w(fs, buf, nf + nf); - t[4] = fs; t[5] = nf; - mdg = ntp; rt.copy_w(mdn, tpn, ntp); - ntp = 0; - } else { - fs = talloc(nf * 3 + 1); - i = 0; - bo = 0; - while (i < nf) { - ft = buf[i + i + 1]; - if (ft == t) { serr("a struct can't contain itself by value; use a pointer"); ft = tword; } - fs[i * 3] = buf[i + i]; fs[i * 3 + 1] = ft; - // a sized field packs into the current word at its own alignment; its - // offset is recorded as minus (byte offset + 1). anything else starts a word - k = tbytes(ft); - if (k) { bo = (bo + k - 1) / k * k; fs[i * 3 + 2] = 0 - bo - 1; bo = bo + k; } - else { bo = (bo + 7) / 8 * 8; fs[i * 3 + 2] = bo / 8; bo = bo + tsz(ft) * 8; } - i++; - } - off = (bo + 7) / 8; - t[4] = fs; t[1] = off; t[3] = nf; - } - accept(s_semi); - i = 0; - while (i < nm) { fn_method_compile = 1; methdef(n, mn[i], mq[i], mst[i], mty[i], mrt[i], mrg[i]); fn_method_compile = 0; i++; } - mdg = 0; - mstruct = msv; - return s; -} -// ---- methods ---- - -// a method is an ordinary function named Struct.name whose first parameter is -// this, a pointer to the struct. e.method(a) calls Struct.name(e, a). the -// receiver's struct comes from its type, or, for an untyped word, from the -// one struct that has a method of that name. -// the symbol for a method is the interned string "Struct.name" -char* methname(char* sn, char* mn) { - char* b = chk_resize(0, 0, rt.len_w(sn) + rt.len_w(mn) + 2); int i = 0, j = 0; - while (sn[j]) { b[i] = sn[j]; i++; j++; } - b[i] = '.'; i++; - j = 0; - while (mn[j]) { b[i] = mn[j]; i++; j++; } - b[i] = 0; - return intern(b); -} -// the method called n on struct type t, as a symbol index, or -1. methwhich -// (below) does the untyped version: search every struct for one with that -// method, which is unique or ambiguous (-2). -int methof(word t, char* n) { - int s = lookup(methname(t[2], n)); - if (s >= 0 && skind[s] == K_FN) return s; - return 0 - 1; -} -// the one method called n, -1 for none, -2 for several structs having one -int methscan(char* n) { - int i = 0; word t; int s, hit = 0 - 1; - while (i < ntl) { - t = typelist[i]; - if (t[0] == TY_STRUCT && t[3] >= 0) { - s = methof(t, n); - if (s >= 0) { if (hit >= 0) return 0 - 2; hit = s; } - } - i++; - } - return hit; -} -// declare Struct.name now, so methods can call each other in any order -// (the declaration is what makes mutual recursion between methods work: the -// symbol exists with no body yet, and calls to it go on the patch chain) -int methdecl(char* sn, char* mn) { - int nm = methname(sn, mn), s = lookup(nm); - if (s >= ubase && s >= 0 && skind[s] == K_FN) { - serrn(mn, "is defined twice in this struct"); - return s; - } - s = addsym(nm, K_FN, 0); - sflag[s] = 0; - return s; -} -// the tokens of a method, from its ( to the } that closes its body, as a token list -// a method body is not parsed when the struct is read. its tokens are copied -// into a token list (kind, value, place triples) up to the matching brace, and -// methdef replays them later. the place travels with each token, so errors -// inside a method still point at the right line. -word .u grabfn() { - int cap = 768; word q = rt.new(cap); int n = 0, depth = 0; word nq; - q[0] = 0; - if (!is_op(s_lp)) { die("expected ( after the method name"); return q; } - while (tk != T_EOF) { - if (n * 3 + 4 > cap) { - nq = rt.new(cap + cap); - rt.copy_w(nq, q, cap); - rt.free(q); - q = nq; - cap = cap + cap; - } - q[1 + n * 3] = tk; q[2 + n * 3] = tv; q[3 + n * 3] = tpack(); n++; - if (is_op(s_lb)) depth++; - else if (is_op(s_rb)) { depth--; if (depth <= 0) { next(); break; } } - next(); - } - if (tk == T_EOF && depth > 0) die("missing } at the end of a method"); - q[0] = n; - return q; -} -// compile a method by feeding the parser fn Struct.name(Struct* this, rest...) -// build the token list "fn Struct.name ( Struct* this , rest..." from the -// method's saved tokens, push it as an input source and parse it as an -// ordinary top-level function. a method is therefore just a function whose -// first parameter is this; nothing else about it is special. -int tkput(word r, int k, int kind, int val, int pl) { - r[1 + k * 3] = kind; r[2 + k * 3] = val; r[3 + k * 3] = pl; - return k + 1; -} -// compile a struct method once the struct is complete. -int methdef(char* sn, char* mn, word q, int stat, int typed, int rty, int rtag) { - int n = q[0]; word r; int i, k = 0, lnp, unchecked_method = stat & 2; - stat = stat & 1; - if (!n) { rt.free(q); return 0; } - if (!stat) { - i = 1; - while (i < n && q[2 + i * 3] != s_comma && q[2 + i * 3] != s_rp) { - if (q[1 + i * 3] == T_ID && q[2 + i * 3] == s_this) serr("this is implicit and cannot be declared as a method parameter"); - i++; - } - } - r = rt.new((n + 8 + mdg * 4) * 3 + 1); - lnp = q[3]; - if (unchecked_method) k = tkput(r, k, T_ID, s_unchecked, lnp); - k = tkput(r, k, T_ID, s_fn, lnp); - r[1 + k * 3] = T_ID; r[2 + k * 3] = methname(sn, mn); r[3 + k * 3] = lnp; k++; - if (mdg) { // a method of a generic struct is a generic function over the struct's parameters: fn S.m<K, V>(S<K, V>* this, ...) - k = tkput(r, k, T_OP, s_lt, lnp); - i = 0; - while (i < mdg) { - if (i) k = tkput(r, k, T_OP, s_comma, lnp); - k = tkput(r, k, T_ID, mdn[i], lnp); - i++; - } - k = tkput(r, k, T_OP, s_gt, lnp); - } - r[1 + k * 3] = q[1]; r[2 + k * 3] = q[2]; r[3 + k * 3] = q[3]; k++; - if (stat && mdg) die("a static function can't be in a generic struct"); - if (!stat) { - r[1 + k * 3] = T_ID; r[2 + k * 3] = sn; r[3 + k * 3] = lnp; k++; - if (mdg) { - k = tkput(r, k, T_OP, s_lt, lnp); - i = 0; - while (i < mdg) { - if (i) k = tkput(r, k, T_OP, s_comma, lnp); - k = tkput(r, k, T_ID, mdn[i], lnp); - i++; - } - k = tkput(r, k, T_OP, s_gt, lnp); - } - r[1 + k * 3] = T_OP; r[2 + k * 3] = s_star; r[3 + k * 3] = lnp; k++; - r[1 + k * 3] = T_ID; r[2 + k * 3] = s_this; r[3 + k * 3] = lnp; k++; - } - if (!stat && n > 1 && !(q[1 + 3] == T_OP && q[2 + 3] == s_rp)) { - r[1 + k * 3] = T_OP; - r[2 + k * 3] = s_comma; - r[3 + k * 3] = lnp; - k++; - } - i = 1; - while (i < n) { - r[1 + k * 3] = q[1 + i * 3]; - r[2 + k * 3] = q[2 + i * 3]; - r[3 + k * 3] = q[3 + i * 3]; - k++; - i++; - } - r[0] = k; - rt.free(q); - if (typed && mdg) { - // retain a typed generic method as ordinary typed function tokens. - int mode = rt.outmode, length = rt.outn, capacity = rt.outcap, prefix = unchecked_method != 0; - word output = rt.outbuf, grow = rt.outgrow, buffer = rt.new(4096), return_tokens, joined; - rt.outmode = 2; rt.outbuf = m*buffer; rt.outn = 0; rt.outcap = 4095; rt.outgrow = 0; - tprint(rty); if (rtag) rt.put_s(".u"); buffer[rt.outn] = 0; - rt.outmode = mode; rt.outbuf = output; rt.outn = length; rt.outcap = capacity; rt.outgrow = grow; - return_tokens = tokens(buffer); - int token = 0, variable; - while (token < return_tokens[0]) { - variable = 0; - while (variable < ntl) { - word descriptor = typelist[variable]; - if (descriptor[0] == TY_VAR && descriptor[3] < mdg && return_tokens[1 + token * 3] == T_ID && return_tokens[2 + token * 3] == descriptor[2]) return_tokens[2 + token * 3] = mdn[descriptor[3]]; - variable++; - } - token++; - } - joined = rt.new((r[0] + return_tokens[0]) * 3 + 1); - joined[0] = r[0] - 1 + return_tokens[0]; - if (prefix) rt.copy_w(joined + 1, r + 1, 3); - rt.copy_w(joined + 1 + prefix * 3, return_tokens + 1, return_tokens[0] * 3); - rt.copy_w(joined + 1 + (prefix + return_tokens[0]) * 3, r + 1 + (prefix + 1) * 3, (r[0] - prefix - 1) * 3); - rt.free(r); r = joined; rt.free(return_tokens); rt.free(buffer); - } - src_push(1, r, 0, 1); - next(); - if (typed && !mdg) { - // a typed method: skip the fn and the name, then compile from the ( with the declared return type - next(); - next(); - ifkp = tpack(); ifkk = 0 - 1; if (isp != 0) ifkk = 0; - o3nxt = 0; ntp = 0; - fndone(fndef(1, rty, rtag, methname(sn, mn)), 0); - } else topitem(); - if (tk != T_EOF) die("unexpected tokens after a method"); - src_pop(); - return 0; -} -// the call e.m(args): the receiver is on the stack and becomes this -// e.m(args): by now the receiver is on the stack, so it simply becomes the -// first argument. the call is checked against the method's signature if it -// has been compiled, and its result class is fresh if the method returns .u. -int methcall(int s, word rst, word receiver_fact, int receiver, word receiver_mask) { - af_forget(); - int np, tags = 0, ret = 0, temporary, owner, aliases, i; word ft = stype[s]; word[8] bs; word owner_fact, origins; - word requirements = effect_requirements, edges = effect_edges; int original = s; - effect_args_mask(s, 0, receiver_mask); - if (receiver_fact && (receiver_fact[2] == 4 || receiver_fact[2] == 5)) effect_require(s, 0, callpos); - if (receiver >= 0 && tracked(lroot(receiver))) effect_require_mode(s, 0, callpos, 1); - if (sflag[s] & F_SIG) { tags = ssig[s] >> 6; ret = ssig[s] & 1; } - else sflag[s] = sflag[s] | F_UNTYPED; - if (sflag[s] & F_RETOWN) ret = 1; - rt.fill_w(bs, 0, 8); - if (ft && rst) { - word actual = rst; if (rst[0] == TY_STRUCT) actual = tptr(rst); - if (!unify(ft[7], actual, bs)) tcerr("receiver", subst(ft[7], bs), actual); - } - ifcall = s; effect_argument_base = 1; - np = args(tags >> 2, ft, bs, 0) + 1; - temporary = argtemporary || (receiver_fact && receiver_fact[2] == 5); owner = argowner; owner_fact = argowner_fact; origins = argorigin_mask; - if (effect_get(s, 0) & (EF_ALIAS | EF_IDENTITY | EF_UNKNOWN)) { - origins = origins | receiver_mask; - if (owner < 0) { owner = receiver; owner_fact = receiver_fact; } - else if (owner != receiver) { owner = -1; owner_fact = 0; } - } - if ((sflag[s] & F_SIG) && np != ((ssig[s] >> 1) & 31)) serrn(sname[s], "wrong number of arguments"); - if (sflag[s] & F_GEN) { s = ginstance(s, bs); effect_specialize(original, s, requirements, edges); } - fnref(s); op(O_CALL); - lv = 0; - lvk = LV_NONE; - xzero = 0; - xbor = -1; - xconst = 0; - xfloat = 0; - xstr = 0; - xlenloc = -1; - xstamp = 0; - xty = tword; - if (ft) xty = subst(ft[4], bs); - if (isfl(xty)) xfloat = 1; - effect_value = 0; - if (!ret && (isptr(xty) || xty == tword)) { - effect_value = origins; - if (isptr(xty)) xbor = owner; - if (temporary) { aliases = 0; i = 0; while (i < np) { aliases = aliases | effect_get(s, i); i++; } if (aliases & (EF_ALIAS | EF_UNKNOWN)) xaf = af_new(-1, 5, callpos); } - } - if (ret) return C_FRESH; - return C_PLAIN; -} -// type name = T; -// the type keyword makes an alias, which is also how a pointer to an array or -// function type gets a name. -int kw_type() { - int n; - next(); - n = name(); - expect(s_eq); - addsym(n, K_TYPE, ptype()); - expect(s_semi); - return 0; -} -// does a parameter start with a type? a type name straight before , or ) is the parameter's -// own name (fn f(string)), not a type, since a typed parameter always has a name after it. -int paramstart() { - if (tk == T_OP) return tv == s_lk; - if (tk != T_ID) return 0; - if (tv == s_fn) return 1; - if (!istypename(tv)) return 0; - peek(); - return !(pk_tk == T_OP && (pk_tv == s_comma || pk_tv == s_rp)); -} -// does a typedef start here? typedef followed by a name is one, so typedef can still be -// a variable or a function's name. -int typedef_ahead() { - if (tk != T_ID || tv != s_typedef) return 0; - peekat(0); - return pk_tk == T_ID; -} -// unlink the symbol s from its name, leaving the rest of the name's chain as it was. -int td_unlink(int s) { - char* nm = sname[s]; int i = nm[-1] - 1; - if (i == s) { nm[-1] = snext[s]; return 0; } - while (i >= 0 && snext[i] - 1 != s) i = snext[i] - 1; - if (i >= 0) snext[i] = snext[s]; - return 0; -} -// end the scope of the local typedefs made at block depth depth or deeper in this -// compile context (and of any made in a context nested inside it). -int td_end(int depth) { - int k; - while (ntd) { - k = ntd * 3 - 3; - if (tdstk[k + 2] < ctxsp || (tdstk[k + 2] == ctxsp && tdstk[k + 1] < depth)) break; - td_unlink(tdstk[k]); - ntd--; - } - return 0; -} -// is the name q (as a symbol is named) already a typedef of the block being compiled? -int td_here(char* q) { - int k = ntd; - while (k) { - k--; - if (tdstk[k * 3 + 2] != ctxsp || tdstk[k * 3 + 1] != fdepth) break; - if (sname[tdstk[k * 3]] == q) return 1; - } - return 0; -} -// typedef T name; -// the C spelling of type name = T;. name becomes another way to write T: the very same -// type record, so the two are interchangeable everywhere. at the top level the name is -// global; in a function it lasts to the end of its block, like a local. a name that is -// already in use is an error, except the same type under the same name again. a tag -// (.u, .m, .d) belongs to a declaration, not to a type, so a typedef has none. -int kw_typedef() { - char* n; char* q; word t; int s, at, clash = 0, local = infn; - next(); - t = ptype(); - if (panic) return 0; - if (is_op(s_dot)) { die("a typedef names a type, which has no tag: .u, .m and .d go on a declaration"); return 0; } - at = toff; n = name(); - if (panic) return 0; - q = module_symbol(n); - if (local) { - s = findloc(n); - if ((s >= 0 && lhome[s] >= fdepth) || td_here(q)) clash = 1; - } else { - s = lookup_raw(q); - if (s < 0 && module_name) { s = lookup_raw(n); if (s >= 0 && skind[s] != K_TYPE && skind[s] != K_KW) s = 0 - 1; } - if (s >= 0 && skind[s] == K_TYPE && sval[s] == t) return expect(s_semi); // the same typedef again - if (s >= 0) clash = 1; - } - if (clash) serrn(n, "already defined"); - else { - s = addsym(n, K_TYPE, t); - rs_put(at, RS_SYM, rs_sym(s), s); - if (local) { - if (ntd == tdcap) { if (tdcap) tdcap = tdcap * 2; else tdcap = 16; tdstk = chk_resize(tdstk, ntd * 3, tdcap * 3); } - tdstk[ntd * 3] = s; tdstk[ntd * 3 + 1] = fdepth; tdstk[ntd * 3 + 2] = ctxsp; ntd++; - } - } - expect(s_semi); - return 0; -} -// (T) or (T*) ahead of the current '(', within the lookahead we have -// the parser sees ( and has to decide between a parenthesized expression and a -// cast. a cast is ( type stars ) so peek ahead through the stars to the ). -int caststart() { - int k; - peek(); - if (pk_tk == T_ID && (istypename(pk_tv) || pk_tv == s_fn)) { - k = 1; peekat(k); - if (pk_tk == T_OP && (pk_tv == s_lk || pk_tv == s_lt || (pk_tv == s_lp && pq_tv[0] == s_fn))) return 1; - while (pk_tk == T_OP && pk_tv == s_star && k < 3) { k++; peekat(k); } // (int*)x, (node**)x - return pk_tk == T_OP && pk_tv == s_rp; - } - return 0; -} -// memdebug on; checks and interior pinning. memdebug scrub; also poisons frees. -// memdebug off; stops both. at the top level, start() applies it before main. -// ---- statements that only configure the compiler ---- - -int kw_memdebug() { - int f = 0; - stmtonly(); - next(); - if (is_id(s_on)) { mdbg = 1; f = 5; } - else if (is_id(s_scrub)) { mdbg = 1; f = 7; } - else if (is_id(s_off)) { mdbg = 0; f = 0; } - else die("memdebug on, memdebug scrub or memdebug off"); - next(); - expect(s_semi); - if (infn) { lit(f); callname(s_memset); op(O_DROP); } - else mdbg_flags = f; - return 0; -} -// enum name { A, B = 5, C }; the names are global constants, the name is an int type -// the type of an enum: an int record that the typechecker keeps apart from the -// plain ints. t[6] bit 4 marks it; the members carry it in stype -word tenum(char* n) { word t = tnew(TY_INT, 1, n, 0); t[6] = 16; return t; } -// true for a type made by an enum declaration. -word isen(word t) { return t[0] == TY_INT && (t[6] & 16); } -// enum name { A, B = 5 }: each member is a constant, plain (A) and scoped (name.A) -// member records for tagged enums and for plain enums (pm_en is -1 for a plain -// member). a member's payload fields are at most 8. -// the text a tagged enum or an extern stub is generated into -char[GENERATED_WORDS] ebuf_initial; char* ebuf = ebuf_initial; int ebcap = GENERATED_WORDS, ebn; -char*[ENUM_MEMBERS] pm_name_initial, pm_en_initial; word[ENUM_MEMBERS] pm_tag_initial, pm_nf_initial, pm_val_initial, pm_exp_initial; -char** pm_name = pm_name_initial, pm_en = pm_en_initial; word pm_tag = pm_tag_initial, pm_nf = pm_nf_initial, pm_val = pm_val_initial, pm_exp = pm_exp_initial; int pmcap = ENUM_MEMBERS, npm; -char*[ENUM_MEMBERS * 8] pm_fnm_initial; word[ENUM_MEMBERS * 8] pm_ft_initial, pm_off_initial; -char** pm_fnm = pm_fnm_initial; word pm_ft = pm_ft_initial, pm_off = pm_off_initial; -word[TAGGED_ENUMS] pe_st_initial, pe_first_initial, pe_n_initial; char*[TAGGED_ENUMS] pe_name_initial; -word pe_st = pe_st_initial, pe_first = pe_first_initial, pe_n = pe_n_initial; char** pe_name = pe_name_initial; int pecap = TAGGED_ENUMS, npe; -// grow whole member rows, keeping their payload-field indices stable. -int reserve_enum_members(int need) { - int cap = pmcap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - pm_name = chk_resize(pm_name, npm, cap); pm_en = chk_resize(pm_en, npm, cap); - pm_tag = chk_resize(pm_tag, npm, cap); pm_nf = chk_resize(pm_nf, npm, cap); - pm_val = chk_resize(pm_val, npm, cap); pm_exp = chk_resize(pm_exp, npm, cap); - pm_fnm = chk_resize(pm_fnm, npm * 8, cap * 8); pm_ft = chk_resize(pm_ft, npm * 8, cap * 8); pm_off = chk_resize(pm_off, npm * 8, cap * 8); - pmcap = cap; return cap; -} -// grow the tagged-enum index independently of its member rows. -int reserve_tagged_enums(int need) { - int cap = pecap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - pe_st = chk_resize(pe_st, npe, cap); pe_first = chk_resize(pe_first, npe, cap); - pe_n = chk_resize(pe_n, npe, cap); pe_name = chk_resize(pe_name, npe, cap); - pecap = cap; return cap; -} -// a field's c-like spelling in a generated struct -char* ftext(int t) { if (t == tint) return "int"; if (t == tfloat) return "float"; return "let"; } -// the member named n of a tagged enum, or -1 -int pm_find(char* n) { - int gi = npm - 1; - while (gi >= 0) { if (pm_name[gi] == n && pm_en[gi] >= 0) return gi; gi--; } - return -1; -} -// the tagged enum a type is a pointer to, plus one; 0 for any other type -int ptagged(word t) { - int e = 0; - if (!t || t[0] != TY_PTR) return 0; - while (e < npe) { if (t[4] == pe_st[e]) return e + 1; e++; } - return 0; -} -// enum name { A, B = 5 } is a plain enum: each member is a constant, plain (A) -// and scoped (name.A). a member with a block of fields makes it a tagged enum -// (see enum_tagged): enum name { circle { int r; }, dot }. -int kw_enum() { - char* n; char* en = 0; int first = npm, cnt = 0, tagged = 0, gi, nf, v = 0, et = 0, sk, ft; char* fname; - stmtonly(); - next(); - if (tk == T_ID) { en = tv; next(); } - expect(s_lb); - while (!is_op(s_rb)) { - if (tk == T_EOF) { die("missing }"); break; } - reserve_enum_members(npm + 1); - gi = npm; npm++; - n = name(); - pm_name[gi] = n; pm_tag[gi] = cnt; pm_nf[gi] = 0; pm_en[gi] = -1; pm_val[gi] = 0; pm_exp[gi] = 0; - if (is_op(s_lb)) { - tagged = 1; next(); - while (!is_op(s_rb)) { - if (tk == T_EOF) { die("missing }"); break; } - if (pm_nf[gi] >= 8) { fatal("too many fields in a member"); break; } - if (is_id(s_var)) { next(); ft = tword; } - else { rettype(); ft = dty; if (dtag) serr("a payload field has no tag"); } - if (ft != tint && ft != tfloat && ft != tword) serr("a payload field is int, float or let"); - fname = name(); - nf = gi * 8 + pm_nf[gi]; - pm_fnm[nf] = fname; pm_ft[nf] = ft; pm_nf[gi]++; - expect(s_semi); - } - expect(s_rb); - } else if (accept(s_eq)) { pm_val[gi] = cexpr(); pm_exp[gi] = 1; } - cnt++; - if (!accept(s_comma)) break; - if (is_op(s_rb)) break; - } - expect(s_rb); - expect(s_semi); - if (tagged) return enum_tagged(en, first, cnt); - if (en) { et = tenum(en); addsym(en, K_TYPE, et); } - for (gi = first; gi < npm; gi++) { - if (pm_exp[gi]) v = pm_val[gi]; - sk = addsym(pm_name[gi], K_CONST, v); - stype[sk] = et; - if (en) { sk = addsym(methname(en, pm_name[gi]), K_CONST, v); stype[sk] = et; } - v++; - } - return 0; -} -// a tagged enum: one struct, with the tag as word 0 and the fields of every -// member after it, each named member_field. a member's constructor is a static -// function on the enum, en.member(fields...), that allocates the struct, sets -// the tag and the member's fields, and returns it owned. the struct is generated -// as source and compiled here, so its methods are ordinary zc. -int enum_tagged(char* en, int first, int cnt) { - int gi, j, nf, w = 1; word buf, oldimports = module_imports; int sk; - if (!en) { serr("a tagged enum needs a name"); return 0; } - reserve_tagged_enums(npe + 1); - if (cnt > 63) { fatal("a tagged enum has at most 63 members"); return 0; } - for (gi = first; gi < npm; gi++) if (pm_exp[gi]) serr("a tagged enum's members take no explicit values"); - ebn = 0; ebuf[0] = 0; - ebs("struct "); ebs(en); ebs(" { int tag; "); - for (gi = first; gi < npm; gi++) { - for (j = 0; j < pm_nf[gi]; j++) { - nf = gi * 8 + j; - pm_off[nf] = w; w++; - ebs(ftext(pm_ft[nf])); ebs(" "); ebs(pm_name[gi]); ebs("_"); ebs(pm_fnm[nf]); ebs("; "); - } - } - for (gi = first; gi < npm; gi++) { - ebs("static "); ebs(en); ebs("*.u "); ebs(pm_name[gi]); ebs("("); - for (j = 0; j < pm_nf[gi]; j++) { - nf = gi * 8 + j; - if (j) ebs(", "); - if (pm_ft[nf] == tint) ebs("int "); else if (pm_ft[nf] == tfloat) ebs("float "); - ebs(pm_fnm[nf]); - } - ebs(") { "); ebs(en); ebs("* o = rt.new("); ebs(en); ebs("); o.tag = "); ebn10(pm_tag[gi]); ebs("; "); - for (j = 0; j < pm_nf[gi]; j++) { - nf = gi * 8 + j; - ebs("o."); ebs(pm_name[gi]); ebs("_"); ebs(pm_fnm[nf]); ebs(" = "); ebs(pm_fnm[nf]); ebs("; "); - } - ebs("return u*o; } "); - } - ebs("}"); - buf = rt.new(ebn + 14); - rt.copy_w(buf, "include rt; ", 12); - rt.copy_w(buf + 12, ebuf, ebn + 1); - src_push(2, buf, 0, 0); - next(); - stubgen = 1; - while (tk != T_EOF) topitem(); - stubgen = 0; - src_pop(); - module_imports = oldimports; - rt.free(d*buf); - sk = lookup(en); - if (sk < 0 || skind[sk] != K_TYPE) { serr("the tagged enum didn't make its struct"); return 0; } - pe_st[npe] = sval[sk]; pe_first[npe] = first; pe_n[npe] = cnt; pe_name[npe] = en; - for (gi = first; gi < npm; gi++) { - pm_en[gi] = npe; - sk = addsym(pm_name[gi], K_CONST, pm_tag[gi]); - stype[sk] = 0; - } - npe++; - return 0; -} -// a switch over a tagged enum that leaves out a member, with no default, is a -// warning, on the line of the switch (sl). e is the enum, cov the bit set of -// the tags the switch covers. -int cover_warn(int e, int cov, int sl) { - int k, gi, first = 1; - if (dry || panic || cmode) return 0; - for (k = 0; k < pe_n[e]; k++) { - if (k < 63 && ((cov >> k) & 1)) continue; - if (first) { rt.put_s("zc: line "); rt.put_n(sl); rt.put_s(": warning: this switch doesn't cover "); first = 0; } - else rt.put_s(", "); - gi = pe_first[e] + k; - rt.put_s(pm_name[gi]); - } - if (!first) rt.put_c(10); - return 0; -} -// ---- include ---- - -// the text of an embedded library, one character to a word and ending in 0, as readfile gives a file. -// it is unpacked from the byte string the first time it is asked for. -char* bstr_text(bstr p) { - int n = rt.len_s(p), i = 0; char* b = m*rt.new(n + 2); - while (i < n) { b[i] = rt.at_s(p, i); i++; } - b[n] = 0; - return b; -} -// the embedded std module. -char* std_text() { - if (!std_buf) std_buf = bstr_text(std_source()); - return std_buf; -} -// the embedded icon module. -char* icon_text() { - if (!icon_buf) icon_buf = bstr_text(icon_source()); - return icon_buf; -} -// a path with its . and .. segments and doubled slashes taken out: /a/b/../c/./d.zc is /a/c/d.zc -char*.u inc_norm(char*.m p) { - int n = rt.len_w(p), i = 0, k = 0, s, e, j, isabs = 0; char* o; - o = rt.new(n + 4); - if (p[0] == '/') isabs = 1; - while (i < n) { - while (i < n && p[i] == '/') i++; - s = i; - while (i < n && p[i] != '/') i++; - e = i; - if (e == s) continue; - if (e - s == 1 && p[s] == '.') continue; - if (e - s == 2 && p[s] == '.' && p[s + 1] == '.') { - j = k; - while (j > 0 && o[j - 1] != '/') j--; - // the last segment is o[j..k); a leading .. of a relative path stays - if (k > 0 && !(k - j == 2 && o[j] == '.' && o[j + 1] == '.')) { - if (j > 0) k = j - 1; else k = 0; - continue; - } - if (k == 0 && isabs) continue; - } - if (k > 0 || isabs) { o[k] = '/'; k++; } - j = s; while (j < e) { o[k] = p[j]; k++; j++; } - } - if (k == 0 && isabs) { o[0] = '/'; k = 1; } - o[k] = 0; - return o; -} -// dir, a slash and path -char*.u inc_cat(char*.m dir, char*.m path) { - int a = rt.len_w(dir), b = rt.len_w(path); char* o; - o = rt.new(a + b + 3); - rt.copy_w(o, dir, a); - o[a] = '/'; - rt.copy_w(o + a + 1, path, b); - o[a + b + 1] = 0; - return o; -} -// the directory part of a path, without the last slash, or 0 when there is none -char*.u inc_dir(char*.m p) { - int n = rt.len_w(p), k; char* o; - k = n; - while (k > 0 && p[k - 1] != '/') k--; - if (k == 0) return 0; - if (k > 1) k--; - o = rt.new(k + 2); - rt.copy_w(o, p, k); - o[k] = 0; - return o; -} -// the text of the file at path: the editor's copy if it sent one, else the file -char*.u inc_try(char*.m path) { - if (inc_hook) { - char* text = inc_hook(path); - if (text) { - int n = rt.len_w(text); char* copy = rt.new(n + 1); - rt.copy_w(copy, text, n + 1); return copy; - } - } - char* copy = rt.readfile(path); - return copy; -} -// find an include: next to the file that has it, then in the workspace folder, then as written (relative to the -// working directory). incres is where it was found -char*.u inc_open(char*.m path) { - incres = inc_norm(path); - if (path[0] == '/' || path[1] == ':') { - char* c = inc_norm(path), b = inc_try(c); - if (b) incres = c; - return b; - } - if (incdir) { - char* combined = inc_cat(incdir, path); char* c = inc_norm(combined), b = inc_try(c); - if (b) { incres = c; return b; } - } - if (increoot) { - char* combined = inc_cat(increoot, path); char* c = inc_norm(combined), b = inc_try(c); - if (b) { incres = c; return b; } - } - return inc_try(path); -} -// compile the text buf as if it were written here, signatures first. name is what hovers and definitions call it -// and dir the directory its own includes are found from -int inc_compile(char* buf, char* name, char*.d dir) { - char* oldname = curinc; word olddir = incdir; - char* oldmodule = module_name; word oldimports = module_imports; - module_imports = 0; module_name = 0; - prescan(2, buf); - src_push(2, buf, 0, 0); - curinc = name; incdir = dir; incdepth++; - next(); - while (tk != T_EOF) topitem(); - curinc = oldname; incdir = olddir; incdepth--; - src_pop(); - module_name = oldmodule; module_imports = oldimports; - return 0; -} -// include "path"; reads the file and compiles it here, signatures first -// include std; does the same with the standard library, which is part of the compiler -int kw_include() { - int pp, sv; char* path; char* buf; - stmtonly(); - next(); - if (tk == T_ID) { - while (1) { - pp = tpack(); path = tv; - if (tk != T_ID) { die("expected a module name in include"); return 0; } - next(); - if (!module_imported(path)) { - word link = chk_resize(0, 0, 2); link[0] = module_imports; link[1] = path; module_imports = link; - } - if (path == s_std && !std_done) { - std_done = 1; if (incdepth == 0) incplace = pp; - inc_compile(std_text(), "tinyvm:std.zc", 0); - } - if (path == s_icon && !icon_done) { - icon_done = 1; if (incdepth == 0) incplace = pp; - inc_compile(icon_text(), "tinyvm:icon.zc", 0); - } - if (!module_known(path) && !module_loaded(path)) { - char* filename = inc_cat("src/lib", path); int length = rt.len_w(filename); - char* full = chk_resize(0, 0, length + 4); rt.copy_w(full, filename, length); rt.copy_w(full + length, ".zc", 4); - buf = m*inc_open(full); - if (!buf) { sv = tpack(); tunpack(pp); serr("can't read that module include"); tunpack(sv); return 0; } - word link = chk_resize(0, 0, 2); link[0] = included_modules; link[1] = path; included_modules = link; - if (incdepth == 0) incplace = pp; - inc_compile(buf, incres, inc_dir(incres)); - } - if (!accept(s_comma)) break; - } - expect(s_semi); return 0; - } - if (tk != T_STR) { die("include takes a path in quotes, or std"); return 0; } - pp = tpack(); - path = tv; - next(); - expect(s_semi); - buf = m*inc_open(path); - if (!buf) { - sv = tpack(); tunpack(pp); - serr("can't read that include"); - tunpack(sv); - return 0; - } - if (incdepth == 0) incplace = pp; - inc_compile(buf, incres, inc_dir(incres)); - return 0; -} -// embed "path" as [type] name; declares name with the bytes of the file at path, read -// when the unit is compiled. the path is found the way an include's is. with no type, or -// bstr, name is a byte string of the file; char* gives one character to a word; a number -// type takes the bytes little-endian (a float or f32 needs exactly its size); T[] and -// T[n] lay the bytes out as the array's elements, packed for the sized types, one byte -// to an element for char[]. at the top level name is a global, in a function a local. -int kw_embed() { - int pp, off, n, t = 0, i, w, count, k, a, j, v, sz, skip = -1; char* path; word data; - stmtonly(); - next(); - pp = tpack(); - if (tk != T_STR) { die("embed takes a path in quotes: embed \"file\" as name;"); return 0; } - path = tv; next(); - if (!is_id(s_as)) { die("expected as: embed \"file\" as [type] name;"); return 0; } - next(); - peekat(0); - if (!(tk == T_ID && pk_tk == T_OP && pk_tv == s_semi)) t = ptype(); - off = toff; n = name(); - expect(s_semi); - data = m*embed_open(path); - if (!data) { embed_fail(pp, "can't read the file named by embed"); data = chk_resize(0, 0, 1); } - sz = data[0]; - if (!t) t = tbstr; - // in a function the data sits in the code stream, so the code jumps over it - if (infn && (t == tbstr || t == tptr(tchar) || t[0] == TY_ARR)) { reserve_code(2); skip = hole(); op(O_JMP); } - if (t == tbstr || t == tptr(tchar)) { - if (t == tbstr) { a = embed_bstr(data); } - else { reserve_code(sz + 1); a = here; i = 0; while (i < sz) { emit(data[1 + i]); i++; } emit(0); } - if (skip >= 0) setlit(skip, here); - return embed_bind(n, off, t, a, 0); - } - if (t[0] == TY_ARR) { - w = embed_width(t[4]); - if (!w) { embed_fail(pp, "embed declares a bstr, a char*, a number, or an array of numbers"); if (skip >= 0) setlit(skip, here); return embed_bind(n, off, tword, 0, 2); } - if (sz % w) { embed_fail(pp, "the embedded file's size is not a whole number of array elements"); if (skip >= 0) setlit(skip, here); return embed_bind(n, off, tword, 0, 2); } - count = sz / w; - if (t[3] >= 0 && count > t[3]) { embed_fail(pp, "the embedded file is larger than the array it is declared as"); if (skip >= 0) setlit(skip, here); return embed_bind(n, off, tword, 0, 2); } - if (t[3] < 0) t = tarr(t[4], count); - k = tsz(t); reserve_code(k); a = here; i = 0; - if (t[4][0] == TY_CHAR) { while (i < k) { if (i < sz) emit(data[1 + i]); else emit(0); i++; } } - else { - while (i < k) { - v = 0; j = 0; - while (j < 8) { if (i * 8 + j < sz) v = v | (data[1 + i * 8 + j] << (j * 8)); j++; } - emit(v); i++; - } - } - if (skip >= 0) setlit(skip, here); - return embed_bind(n, off, t, a, 1); - } - w = embed_width(t); - if (!w) { embed_fail(pp, "embed declares a bstr, a char*, a number, or an array of numbers"); return embed_bind(n, off, tword, 0, 2); } - if (sz > w) { embed_fail(pp, "the embedded file is larger than the type it is declared as"); return embed_bind(n, off, tword, 0, 2); } - if (isfl(t) && sz != w) { embed_fail(pp, "an embedded float takes exactly as many bytes as its type"); return embed_bind(n, off, tword, 0, 2); } - v = 0; i = 0; - while (i < sz) { v = v | (data[1 + i] << (i * 8)); i++; } - if (isfl(t) && w == 4) v = rt.s2f(v); - else v = cnorm(t, v); - return embed_bind(n, off, t, v, 2); -} -// report msg at the embed declaration that starts at the token packed in pp. the -// caller still declares the name, as a plain 0, so its uses are not reported too. -int embed_fail(int pp, char* msg) { - int sv = tpack(); - tunpack(pp); serr(msg); tunpack(sv); - return 0; -} -// the bytes one element of type t takes in an embedded file: the packed size of a -// sized type or f32, 1 for char, 8 for the other number types, 0 when t is not a number. -int embed_width(word t) { - if (tbytes(t)) return tbytes(t); - if (t[0] == TY_CHAR) return 1; - if (t[0] == TY_INT || t[0] == TY_WORD || t[0] == TY_BOOL || t[0] == TY_FLOAT) return 8; - return 0; -} -// the bytes of block data ([n, b0, b1, ...]) as byte string storage in the code stream. -int embed_bstr(word data) { - int n = data[0], a, i = 0, w, k; - reserve_code((n >> 3) + 2); a = here; - emit(n); - while (i < n) { - w = 0; k = 0; - while (k < 8 && i < n) { w = w | (data[1 + i] << (k * 8)); k++; i++; } - emit(w); - } - if (!(n & 7)) emit(0); - return a; -} -// declare name of type t. kind 0: a is the address of the data, which becomes the value; -// kind 1: a is an array's storage; kind 2: a is the value itself. a global keeps the -// value in its slot; a local is initialized from it, and a local array copies the storage. -int embed_bind(int n, int off, word t, int a, int kind) { - int s, i; - if (!infn) { - if (kind == 1) { s = addsym(n, K_GARR, a); } - else { reserve_code(1); s = addsym(n, K_GVAR, here); emit(a); } - rs_put(off, RS_SYM, rs_sym(s), s); stype[s] = t; - lt_note(off, t); - return 0; - } - i = newloc(n); lrsoff[i] = off; rs_note(RS_LOCAL, off); - ltag[i] = LDECL; lpos[i] = tpack(); lnsz[i] = 0; lleaf[i] = 0; ltype[i] = t; - if (kind == 1) { - larr[i] = 1; fslots = fslots + tsz(t); capture_cell(i, 0); - vaddr(i); lit(a); lit(tsz(t)); callname(s_copy_w); op(O_DROP); - } else { - fslots++; if (isfl(t)) setflt(i, 1); capture_cell(i, 0); - lit(a); vaddr(i); op(O_ST); - setstr(i, t == tbstr); - } - lt_note(off, t); - return 0; -} -// the bytes of an embedded file as [n, b0, b1, ...], found next to the including file, -// then in the workspace folder, then as written, like an include. 0 when none opens. -word.u embed_open(char*.m path) { - if (path[0] != '/' && incdir) { char* combined = inc_cat(incdir, path); char* c = inc_norm(combined); word b = rt.readbytes(c); if (b) return b; } - if (path[0] != '/' && increoot) { char* combined = inc_cat(increoot, path); char* c = inc_norm(combined); word b = rt.readbytes(c); if (b) return b; } - word b = rt.readbytes(path); - return b; -} -// file namespace and imports are restored when an included source ends. -int kw_namespace() { - next(); - if (is_op(s_semi)) module_name = 0; - else { - module_name = tv; name(); - if (!module_known(module_name)) { word link = chk_resize(0, 0, 2); link[0] = included_modules; link[1] = module_name; included_modules = link; } - } - module_imports = 0; expect(s_semi); return 0; -} -// typecheck on; / typecheck off; -int kw_typecheck() { - next(); - if (is_id(s_on)) { - tc_on = 1; - } else if (is_id(s_off)) tc_on = 0; else die("typecheck on or typecheck off"); - next(); expect(s_semi); - return 0; -} -// docs on; keeps the doc comments of what follows in the image, docs off; stops -int kw_docs() { - next(); - if (is_id(s_on)) { - docs_on = 1; - } else if (is_id(s_off)) docs_on = 0; else die("docs on or docs off"); - next(); expect(s_semi); - return 0; -} -// hints on; / hints small; / hints off; from here to the end of the document (or the next such line) -int kw_hints() { - int level = 2, at = toff; - next(); - if (is_id(s_on)) level = 2; else if (is_id(s_small)) level = 1; else if (is_id(s_off)) level = 0; - else { die("hints on, hints small or hints off"); return 0; } - hint_set(level, at); - next(); expect(s_semi); - return 0; -} -// the doc symbol of a name: the symbol "doc name" that holds its byte string, -// or -1. the chain is walked by hand because lookup hides what is below vislo -int docsym(char* nm) { - int i, k = 0; char* p = "doc "; - doc_name_room(rt.len_w(nm) + 5); - while (p[k]) { dnmb[k] = p[k]; k++; } - i = 0; while (nm[i]) { dnmb[k] = nm[i]; k++; i++; } - dnmb[k] = 0; - nm = intern(dnmb); - i = nm[-1] - 1; - while (i >= 0) { - if (skind[i] == K_DOC) return i; - i = snext[i] - 1; - } - return -1; -} -// the packed documentation of a name (a byte string) or 0 -bstr docofs(char* nm) { - int s = docsym(nm); - if (s < 0) return 0; - return sval[s]; -} -// the documentation of a function, by address -bstr docof(int f) { - int i = nsym; - while (i) { - i--; - if (skind[i] == K_FN && sval[i] == f && (sflag[i] & F_DEF)) return docofs(sname[i]); - } - return 0; -} -// documentation uses stable packed objects. symbols keep them alive until an -// image copies their bytes after its code; ordinary docofs calls work too. -bstr doc_pack(char* text, int length) { - int i = 0, k, c, w, nw = (length + 7) >> 3; word p = chk_resize(0, 0, nw + 2); - p[-4] = p[-4] | rt.F_SHARE | rt.F_INTERNAL; p[0] = length; - while (i < length) { - w = 0; k = 0; - while (k < 8 && i < length) { c = text[i]; if (c > 255) c = 63; w = w | (c << (k * 8)); k++; i++; } - p[1 + ((i - 1) >> 3)] = w; - } - return p; -} -// section headings are organizational text, never documentation for a symbol. -int doc_section(char* text, int length) { - int start = 0, end, a, b; - while (start < length) { - end = start; while (end < length && text[end] != 10) end++; - a = start; b = end; - while (a < b && (text[a] == 32 || text[a] == 9 || text[a] == 13)) a++; - while (b > a && (text[b - 1] == 32 || text[b - 1] == 9 || text[b - 1] == 13)) b--; - if (b - a >= 4 && text[a] == '-' && text[a + 1] == '-' && text[b - 1] == '-' && text[b - 2] == '-') return 1; - start = end + 1; - } - return 0; -} - -// attach the doc comment above a declaration at name n. -int doc_attach(char* n) { - int i = 0, w, k, c, b, nw; - if (!dsn || !n || dry || ginst || doc_section(dsnap, dsn)) return 0; - b = doc_pack(dsnap, dsn); - doc_name_room(rt.len_w(n) + 5); - i = 0; while ("doc "[i]) { dnmb[i] = "doc "[i]; i++; } - k = 0; while (n[k]) { dnmb[i] = n[k]; i++; k++; } - dnmb[i] = 0; - addsym(intern(dnmb), K_DOC, b); - dsn = 0; - return 0; -} -// the signature of a function as source text, "int put_c(int ch)", kept in the -// image as the symbol "sig name" when docs are on, so the language server can -// show it for any genome function, with or without a doc comment. the text is -// printed through put_c with the output sent to sgb (the same redirect the -// check uses for a parse word that prints) -char[400] sgb_initial; char* sgb = sgb_initial; int sigcap = 400; -int sig_print(char* n, int rty, int np, char** pn, word pty, int typed) { - int i = 0; - if (typed) { tprint(rty); rt.put_c(' '); } else rt.put_s("fn "); - rt.put_s(n); rt.put_c('('); - while (i < np) { - if (i) rt.put_s(", "); - if (typed) { tprint(pty[i]); rt.put_c(' '); } - rt.put_s(pn[i]); i++; - } - rt.put_c(')'); return 0; -} -// attach a complete signature, measured before capture. -int sig_attach(char* n, int rty, int np, char** pn, word pty, int typed) { - int i = 0, k, b, nw, sm, sb, sn, sc, sl, w, c; - if (!docs_on || dry || ginst || ntp || panic) return 0; - sm = rt.outmode; sb = rt.outbuf; sn = rt.outn; sc = rt.outcap; - rt.outmode = 3; rt.outn = 0; sig_print(n, rty, np, pn, pty, typed); - if (rt.outn > sigcap) { while (rt.outn > sigcap) sigcap = sigcap * 2; sgb = chk_resize(sgb, 0, sigcap); } - rt.outmode = 2; rt.outbuf = sgb; rt.outn = 0; rt.outcap = sigcap; - sig_print(n, rty, np, pn, pty, typed); - sl = rt.outn; - rt.outmode = sm; rt.outbuf = sb; rt.outn = sn; rt.outcap = sc; - b = doc_pack(sgb, sl); - doc_name_room(rt.len_w(n) + 5); - i = 0; while ("sig "[i]) { dnmb[i] = "sig "[i]; i++; } - k = 0; while (n[k]) { dnmb[i] = n[k]; i++; k++; } - dnmb[i] = 0; - addsym(intern(dnmb), K_DOC, b); - return 0; -} -// the field of a struct type, or -1 -// ---- built-ins that look like functions ---- - -// sizeof and typeof take a type or an expression; the expression form is -// compiled with dry set so it contributes only its type. len, cap, push, pop -// and resize work on growable arrays (T[?] handles in rt.zc); arrarg parses -// their first argument and remembers which local it was, so the o3 and -// bounds-proof machinery can see it. -int tfindfield(word t, char* n) { - int i = 0; - tresolve(t); - while (i < t[3]) { - if (t[4][i * 3] == n) return i; - i++; - } - return -1; -} -// sizeof(T or expr), typeof(T or expr): the expression form is compiled dry, so it never runs -int typearg() { - int t, sb, c, ob; - expect(s_lp); - if (tstart() && !is_op(s_dot)) t = ptype(); - else { - sb = snap_push(); ob = xbor; dry++; - c = expr(); rval(); - t = xty; - dry--; xbor = ob; snap_restore(sb); snap_pop(sb); - } - expect(s_rp); - return t; -} -// len(a), cap(a), push(a, x), pop(a), resize(a, n): growable arrays. -// len of a T[n] is just n. -word arrvalue(int c) { - argloc = -1; arrloc = -1; - if (lv && lvk == LV_LOCAL && xty[0] == TY_DYN) { - arrloc = lvi; - if (o3on && o3cache[lvi]) argloc = lvi; - } - effect_value = effect_mask(c); rval(); plainuse(c); af_access(xaf, tpack()); - return xty; -} -// parse an array argument and retain the receiver's mutation and bounds facts. -word arrarg() { - int c; - expect(s_lp); c = expr(); - return arrvalue(c); -} -// report an error when a fixed array is asked to grow or shrink. -int nogrow(word t) { - require_mutable(t); - if (t[0] == TY_ARR || (t[0] == TY_DYN && t[3])) serr("a fixed array can't grow or shrink"); - return 0; -} -// compile len(a): the length of an array. -int array_len(word t) { - int a = arrloc; - xcls = C_PLAIN; xaf = 0; xbor = -1; effect_value = 0; - expect(s_rp); - if (xstr) { - callname(s_len_s); - xty = tint; - xstr = 0; - xfloat = 0; - xlenloc = -1; - xstamp = 0; - return 0; - } - if (t[0] == TY_ARR) { op(O_DROP); lit(t[3]); } - else { tcneed(t[0] == TY_DYN || t == tword, "len needs an array", t); if (argloc >= 0) { op(O_DROP); vaddr(o3cache[argloc] + 1); op(O_LD); } else if (o3on && t[0] == TY_DYN) op(O_LD); else callname(s_dlen); } - xty = tint; - xlenloc = -1; xstamp = 0; - if (a >= 0 && t[0] == TY_DYN && t[3]) xlenloc = a; // the whole expression is len of a fixed array - return 0; -} -int kw_len() { next(); return array_len(arrarg()); } -// compile cap(a): the capacity of a growable array. -int array_cap(word t) { - xcls = C_PLAIN; xaf = 0; xbor = -1; effect_value = 0; - expect(s_rp); - if (t[0] == TY_ARR) { op(O_DROP); lit(t[3]); } - else { tcneed(t[0] == TY_DYN || t == tword, "cap needs an array", t); callname(s_dcap); } - xty = tint; - return 0; -} -int kw_cap() { next(); return array_cap(arrarg()); } -// compile push(a, v): add an element to the end of a growable array. -int array_push(word t) { - int c; - tcneed(t[0] == TY_DYN || t == tword, "push needs a T[?]", t); - effect_note_mask(effect_value, EF_WRITE); af_mutate(xaf, tpack()); nogrow(t); - if (t[0] == TY_DYN && isagg(t[4])) serr("push takes scalar or reference elements; use resize for aggregates"); - c = expr(); rval(); if (t[0] == TY_DYN) fcoerce(t[4]); - if (isptr(xty) || xty == tword) effect_note_mask(effect_mask(c), EF_RETAIN); argcheck(c, 2); - if (t[0] == TY_DYN && !compat(t[4], xty)) tcerr("push", t[4], xty); - if (o3on && t[0] == TY_DYN && hasptr(t[4])) { lit(t[4]); callname(s_fatten); } - expect(s_rp); - if (t[0] == TY_DYN && tbytes(t[4])) { // packed: an f32 goes in as its 32 bits, the rest are cut by the store - if (isfl(t[4])) { if (!xfloat) op(OP_I2F); xenh("zf2s"); } - callname(intern("dpushb")); xty = tint; xfloat = 0; xcls = C_PLAIN; - return 0; - } - if (o3on && t[0] == TY_DYN && !hasptr(t[4]) && tsz(t[4]) == 1) { pushfast(); xty = tint; xfloat = 0; xcls = C_PLAIN; return 0; } - callname(s_dpush); xty = tint; xfloat = 0; xcls = C_PLAIN; - return 0; -} -int kw_push() { word t; next(); t = arrarg(); expect(s_comma); return array_push(t); } -// pop in o3 code, inline: a non-empty array gives up its last word, an empty one is dpop's report -int popfast() { - int k, e; - lit(0); op(O_PICK); op(O_LD); - k = hole(); op(O_JZ); - lit(0); op(O_PICK); op(O_LD); lit(1); op(O_SUB); lit(1); op(O_PICK); op(O_ST); - lit(0); op(O_PICK); op(O_LD); - lit(1); op(O_PICK); lit(2); op(O_ADD); op(O_LD); - op(O_ADD); lit(0); op(O_PICK); op(O_LD); op(O_SWAP); lit(0); op(O_SWAP); op(O_ST); - op(O_SWAP); op(O_DROP); - e = hole(); op(O_JMP); - setlit(k, here); - callname(s_dpop); - setlit(e, here); - return 0; -} -// push of a plain one-word element in o3 code, inline: when len < cap the word goes -// straight into the items and len moves up, otherwise dpush grows the array -int pushfast() { - int k, e; - lit(1); op(O_PICK); op(O_LD); - lit(2); op(O_PICK); lit(1); op(O_ADD); op(O_LD); - op(O_LT); - k = hole(); op(O_JZ); - lit(1); op(O_PICK); lit(2); op(O_ADD); op(O_LD); - lit(2); op(O_PICK); op(O_LD); op(O_ADD); - op(O_ST); - lit(0); op(O_PICK); op(O_LD); lit(1); op(O_ADD); lit(1); op(O_PICK); op(O_ST); - op(O_DROP); lit(0); - e = hole(); op(O_JMP); - setlit(k, here); - callname(s_dpush); - setlit(e, here); - return 0; -} -// compile pop(a): remove and return the last element of a growable array. -int array_pop(word t) { - xcls = C_PLAIN; - tcneed(t[0] == TY_DYN || t == tword, "pop needs a T[?]", t); - effect_note_mask(effect_value, EF_WRITE); af_mutate(xaf, tpack()); - nogrow(t); - if (t[0] == TY_DYN && isagg(t[4])) serr("pop takes scalar or reference elements; access the last aggregate before resizing"); - expect(s_rp); - if (t[0] == TY_DYN && tbytes(t[4])) { // packed: read zero extended, then sign extend or widen - callname(intern("dpopb")); - xty = t[4]; xfloat = 0; - xaf = 0; xbor = -1; effect_value = 0; - pkfix(xty); - if (isfl(xty)) xfloat = 1; - return 0; - } - if (o3on && t[0] == TY_DYN && !hasptr(t[4]) && tsz(t[4]) == 1) popfast(); else callname(s_dpop); - xty = telem(t); - if (t[0] == TY_DYN) xty = t[4]; - xfloat = 0; - if (isfl(xty)) xfloat = 1; - xaf = 0; xbor = -1; effect_value = 0; - if (isref(xty)) { - xcls = C_FRESH; xaf = af_new(-1, 2, tpack()); newsz = -1; newleaf = 0; return C_FRESH; - } - return 0; -} -int kw_pop() { next(); return array_pop(arrarg()); } -// compile resize(a, n): set the length of a growable array. -int array_resize(word t) { - int c; - tcneed(t[0] == TY_DYN || t == tword, "resize needs a T[?]", t); - effect_note_mask(effect_value, EF_WRITE); af_mutate(xaf, tpack()); - nogrow(t); - c = expr(); - rval(); - tcneed(isnum(xty) || isfl(xty) || xty == tword, "resize needs a numeric length", xty); - fcoerce(tint); - plainuse(c); - expect(s_rp); - if (t[0] == TY_DYN && tbytes(t[4])) callname(intern("dresizeb")); else callname(s_dresize); - xty = tint; xcls = C_PLAIN; xfloat = 0; - return 0; -} -int kw_resize() { word t; next(); t = arrarg(); expect(s_comma); return array_resize(t); } -// array receiver calls share the free-call lowering and ownership checks. -int array_method(int n) { - return n == intern("len") || n == intern("cap") || n == intern("push") || n == intern("pop") || n == intern("resize"); -} -int array_member(int c, int n) { - word t = arrvalue(c); - expect(s_lp); - if (n == intern("len")) return array_len(t); - if (n == intern("cap")) return array_cap(t); - if (n == intern("push")) return array_push(t); - if (n == intern("pop")) return array_pop(t); - return array_resize(t); -} -// compile sizeof(T) or sizeof(e): the size of a type or expression in words. -int kw_sizeof() { - int t; - next(); - t = typearg(); - lit(tsz(t)); - xty = tint; - return 0; -} -// compile typeof(e): the type of an expression, as a value. -int kw_typeof() { - int t; - next(); - t = typearg(); - lit(t); - xty = tword; - return 0; -} -// ---- c abi: extern and cfunc ---- - -// extern double pow(double, double); or with parameter names, and an -// optional "= "cname"" to give the zc function another name than the c symbol. -// the declaration is read with c's spelling of types and turned into an -// ordinary zc function, so a call to it is a call like any other. the function -// fills a block of words that sits in the code stream (the symbol's name, the -// signature, a cache slot, then room for the arguments) and stores its arguments there, -// and gate op 14 makes the native call (see the c abi section of core.c). -// a signature is 4 bits per kind: the result, then each argument: -// 0 void 1 long 2 double 3 byte pointer 4 int 5 float 6 word pointer -// 7 unsigned int 8 short 9 unsigned short 10 char 11 unsigned char -// 12 str, a zc byte string (b"..."), given to c as the address of its bytes -// a double or float is a zc float. any pointer is a byte address (what -// addr_b(s + 1) gives for a byte string), except "word *", which is a word -// address (a zc object or array). native results come back the same way. -// append the string s to the text being generated for an extern stub. -int eb_char(int c) { - if (ebn + 2 > ebcap) { ebcap = ebcap * 2; ebuf = chk_resize(ebuf, ebn, ebcap); } - ebuf[ebn] = c; ebn++; ebuf[ebn] = 0; return 0; -} -int ebs(char* s) { - while (*s) { eb_char(*s); s++; } - ebuf[ebn] = 0; - return 0; -} -// append the decimal digits of n to the generated text. -int ebn10(int n) { - if (n >= 10) ebn10(n / 10); - eb_char(48 + n % 10); - ebuf[ebn] = 0; - return 0; -} -// true for a c type keyword, like long, char or double. -int ctypeword(int w) { - return rt.eq_w(w, "unsigned") || rt.eq_w(w, "signed") || rt.eq_w(w, "long") || rt.eq_w(w, "short") || rt.eq_w(w, "int") || rt.eq_w(w, "char") || rt.eq_w(w, "double") || rt.eq_w(w, "float") || rt.eq_w(w, "void") || rt.eq_w(w, "const") || rt.eq_w(w, "volatile"); -} -// a c type at the current token: its signature kind, or -1 after an error. -// skipname also eats a parameter name that follows. -// a plain zc struct a native call takes by value: every field is one word (a long, -// a double or a pointer), so word g of the struct is eightbyte g. returns its -// word count, or -1 after an error. -int cstruct_ok(word st, char* nm) { - int i = 0, n; word ft; - tresolve(st); - n = st[1]; - if (st[0] != TY_STRUCT || st[5] || n < 1 || n > 15 || st[3] != n) { serrn(nm, "can't be passed by value to a native call: it needs one word fields, at most 15 words"); return 0 - 1; } - while (i < n) { - ft = st[4][i * 3 + 1]; - if (tbytes(ft) || tsz(ft) != 1 || ft[0] == TY_STRUCT || ft[0] == TY_ARR || st[4][i * 3 + 2] != i) { serrn(nm, "can't be passed by value to a native call: every field has to be one word (long, double or a pointer)"); return 0 - 1; } - i++; - } - return n; -} -// the native signature kind of the c type at the current token, or -1 after an error. -int ckind(int skipname) { - char* w; int uns = 0, lng = 0, shrt = 0, chr = 0, ints = 0, dbl = 0, flt = 0, vd = 0, ptr = 0, seen = 0, k = -1, sk, nw; word other = 0; char* sw = 0; - ckst = 0; ckn = 0; - while (tk == T_ID) { - w = tv; - if (rt.eq_w(w, "const") || rt.eq_w(w, "volatile") || rt.eq_w(w, "restrict")) { next(); continue; } - if (rt.eq_w(w, "struct") || rt.eq_w(w, "enum") || rt.eq_w(w, "union")) { - next(); - if (tk == T_ID) { if (rt.eq_w(w, "struct")) sw = tv; next(); } - other = 1; seen = 1; - continue; - } - if (seen && !ctypeword(w)) break; - if (rt.eq_w(w, "unsigned")) uns = 1; - else if (rt.eq_w(w, "signed")) ints = 1; - else if (rt.eq_w(w, "long")) lng++; - else if (rt.eq_w(w, "short")) shrt = 1; - else if (rt.eq_w(w, "char")) chr = 1; - else if (rt.eq_w(w, "int")) ints = 1; - else if (rt.eq_w(w, "double") || rt.eq_w(w, "f64")) dbl = 1; - else if (rt.eq_w(w, "float") || rt.eq_w(w, "f32")) flt = 1; - else if (rt.eq_w(w, "void")) vd = 1; - else other = w; - seen = 1; - next(); - } - if (!seen) { die("expected a c type"); return 0 - 1; } - while (is_op(s_star)) { - ptr = 1; next(); - while (is_id(intern("const"))) next(); - } - if (skipname && tk == T_ID) next(); - if (ptr) { - if (other && other != 1 && rt.eq_w(other, "word")) return 6; - return 3; - } - if (dbl) return 2; - if (flt) return 5; - if (vd) return 0; - if (chr) { if (uns) return 11; return 10; } - if (shrt) { if (uns) return 9; return 8; } - if (lng) return 1; - if (ints || uns) { if (uns) return 7; return 4; } - if (other == 1) { - if (!sw) { serr("a struct has to be passed by pointer"); return 0 - 1; } - sk = lookup(sw); - if (sk < 0 || skind[sk] != K_TYPE || sval[sk][0] != TY_STRUCT) { serrn(sw, "isn't a zc struct: a native call takes a zc struct by value, declared with word fields"); return 0 - 1; } - nw = cstruct_ok(sval[sk], sw); - if (nw < 0) return 0 - 1; - ckst = sval[sk]; ckn = sw; - if (nw <= 2) return 13; // in registers when they fit (sysv: 16 bytes or less) - return 14; // in memory: on the stack, word after word - } - if (rt.eq_w(other, "str")) return 12; - if (rt.eq_w(other, "size_t") || rt.eq_w(other, "ssize_t") || rt.eq_w(other, "intptr_t") || rt.eq_w(other, "uintptr_t") || rt.eq_w(other, "ptrdiff_t") || rt.eq_w(other, "int64_t") || rt.eq_w(other, "uint64_t") || rt.eq_w(other, "off_t") || rt.eq_w(other, "time_t") || rt.eq_w(other, "word")) return 1; - if (rt.eq_w(other, "int32_t") || rt.eq_w(other, "i32")) return 4; - if (rt.eq_w(other, "uint32_t") || rt.eq_w(other, "u32")) return 7; - if (rt.eq_w(other, "int16_t") || rt.eq_w(other, "i16")) return 8; - if (rt.eq_w(other, "uint16_t") || rt.eq_w(other, "u16")) return 9; - if (rt.eq_w(other, "int8_t") || rt.eq_w(other, "i8")) return 10; - if (rt.eq_w(other, "uint8_t") || rt.eq_w(other, "u8") || rt.eq_w(other, "byte") || rt.eq_w(other, "bool") || rt.eq_w(other, "_Bool")) return 11; - serrn(other, "isn't a c type extern knows (use long, int, double, a pointer, ...)"); - return 0 - 1; -} -// true for a native kind that travels in a float register. -int ckindfloat(int k) { return k == 2 || k == 5; } -// parse an extern declaration: a c function with its signature, and a zc stub for it. -int kw_extern() { - int rk; word[16] ks; word[16] kst; char*[16] ksn; int np = 0, k; char* fname; char* cname; int i, a, j, n, w, sig, bad = 0; char* buf; word oldimports = module_imports; int pos, len, g; word st, rst; int rn, rsd, sret = 0; - stmtonly(); - next(); - rk = ckind(0); - // a struct result: the stub makes it (new) and passes it as the first item; the - // call block's word 2 says how many words it has and which of them are floats - if (rk >= 13) { - sret = 1; rst = ckst; rn = ckn; - if (rst[1] > 7) { serr("a native call can return at most 7 words"); rk = 0 - 1; } - } - fname = name(); - cname = fname; - expect(s_lp); - if (!is_op(s_rp)) { - while (1) { - k = ckind(1); - if (k < 0) { bad = 1; break; } - if (k == 0 && np == 0 && is_op(s_rp)) break; // (void) - if (k == 0) { serr("a parameter can't be void"); bad = 1; break; } - if (np >= 15) { serr("a native call takes at most 15 arguments"); bad = 1; break; } - ks[np] = k; kst[np] = ckst; ksn[np] = ckn; np++; - if (!accept(s_comma)) break; - } - } - expect(s_rp); - if (accept(s_eq)) { - if (tk != T_STR) { die("expected the c name as a string"); return 0; } - cname = tv; - next(); - } - expect(s_semi); - if (bad || rk < 0 || panic) return 0; - if (sret && np > 14) { serr("a native call that returns a struct takes at most 14 arguments"); return 0; } - rsd = 0; - if (sret) { - rsd = rst[1]; g = 0; - while (g < rst[1]) { - if (rst[4][g * 3 + 1] == tfloat) rsd = rsd | (2 << (4 + 4 * g)); else rsd = rsd | (1 << (4 + 4 * g)); - g++; - } - } - // the block, then the name as packed bytes, then the block's own address - // (the word before the stub's entry, which is how a call site finds it): - // [0] the name's byte address, [1] the signature, [2] a lookup cache the - // host fills in, [3..17] the arguments, [18..] the name - // the arguments' nibbles: one per argument. a struct is a group: code 13 (in - // registers if they fit) or 14 (in memory), its length in eightbytes, then one - // class nibble per eightbyte (2 for a float word, 1 otherwise). the group is one - // stack item: the struct's address - sig = rk; - pos = 1; i = 0; - while (i < np) { - if (ks[i] < 13) { - sig = sig | (ks[i] << (4 * pos)); pos++; - } else { - st = kst[i]; len = st[1]; - if (pos + 1 + len > 15) { serr("the arguments of this native call are too many words"); return 0; } - sig = sig | (ks[i] << (4 * pos)); pos++; - sig = sig | (len << (4 * pos)); pos++; - g = 0; - while (g < len) { - if (st[4][g * 3 + 1] == tfloat) sig = sig | (2 << (4 * pos)); else sig = sig | (1 << (4 * pos)); - pos++; g++; - } - } - i++; - } - j = hole(); - op(O_JMP); - reserve_code(20 + (rt.len_w(cname) >> 3)); a = here; - emit((a + 18) << 3); - emit(sig); - emit(rsd << 32); // the cache's half is the host's; this half is the result's descriptor - i = 3; - while (i < 18) { emit(0); i++; } - n = 0; - while (cname[n]) n++; - i = 0; - while (i < n) { - w = 0; k = 0; - while (k < 8 && i < n) { w = w | (cname[i] << (k * 8)); k++; i++; } - emit(w); - } - if (!(n & 7)) emit(0); - emit(a); - setlit(j, here); - // the stub, compiled as if it had been written in the source. it stays - // for the places that take the function's address; a direct call to it - // compiles to the native-call opcode instead (see call) - ebn = 0; ebuf[0] = 0; - if (sret) { ebs(rn); ebs("*.u "); } - else if (rk == 5) ebs("f32 "); else if (ckindfloat(rk)) ebs("float "); else ebs("int "); - ebs(fname); ebs("("); - i = 0; - while (i < np) { - if (i) ebs(", "); - if (ks[i] >= 13) { ebs(ksn[i]); ebs(" p"); } else if (ks[i] == 5) ebs("f32 p"); else if (ckindfloat(ks[i])) ebs("float p"); else ebs("int p"); - ebn10(i); - i++; - } - ebs(") { let b = "); ebn10(a); ebs(";"); - // the native descriptor borrows an integer address, not ownership of r. - // clear that scratch address after the call so it cannot keep the result rooted. - if (sret) { ebs(" "); ebs(rn); ebs("* r = rt.new("); ebs(rn); ebs("); b[3] = core.addr_b(r) >> 3;"); } - i = 0; - while (i < np) { ebs(" b["); ebn10(i + 3 + sret); ebs("] = p"); ebn10(i); ebs(";"); i++; } - if (sret) ebs(" core.syscall(14, b, 0, 0); b[3] = 0; return r; }"); - else { - ebs(" return core.syscall(14, b, 0, 0)"); - if (rk == 5) ebs(" as f32"); else if (ckindfloat(rk)) ebs(" as float"); - ebs("; }"); - } - buf = rt.new(ebn + 19); - rt.copy_w(buf, "include rt, core; ", 18); - rt.copy_w(buf + 18, ebuf, ebn + 1); - src_push(2, buf, 0, 0); - next(); - stubgen = 1; // the stub is ours, not the document's: its locals get no hints - while (tk != T_EOF) topitem(); - stubgen = 0; - src_pop(); - module_imports = oldimports; - rt.free(buf); - k = lookup(fname); - if (k >= 0 && skind[k] == K_FN && (sflag[k] & F_DEF) && !sret) sflag[k] = sflag[k] | F_EXT; // a struct result is called as its stub - return 0; -} -// the c kind of a zc type, for cfunc -int ckindof(word t) { - if (t == tfloat) return 2; - if (t == tf32) return 5; - if (t[0] == TY_INT && t[5]) { // i8 u8 i16 u16 i32 u32 pass and come back at their width - if (t[5] == 1) { if (t[6] & 1) return 10; return 11; } - if (t[5] == 2) { if (t[6] & 1) return 8; return 9; } - if (t[6] & 1) return 4; - return 7; - } - if (t == tvoid) return 0; - if (t[0] == TY_PTR || t[0] == TY_ARR || t[0] == TY_DYN) return 6; - return 1; -} -// cfunc(f): a native function pointer that runs the zc function f, with the -// c signature its declared types give (float is a double, f32 a c float, i8..u32 their width, a pointer is a word -// address, anything else a long). the pointer can be given to c code, which -// may call it while a native call from zc is running. -int kw_cfunc() { - int s; word t; int sig = 1, i, nm; - next(); - expect(s_lp); - if (tk != T_ID) { die("cfunc needs the name of a function"); return 0; } - s = source_lookup(tv); - if (s < 0 || skind[s] != K_FN) { serrn(tv, "isn't a function"); return 0; } - t = sigof(sval[s]); - if (!t) t = stype[s]; - if (t && t[0] == TY_FN) { - sig = ckindof(t[4]); - i = 0; - if (t[3] > 14) serrn(tv, "has more than 14 parameters"); - while (i < t[3] && i < 14) { sig = sig | (ckindof(t[7 + i]) << (4 * (i + 1))); i++; } - } else { - i = (ssig[s] >> 1) & 31; - while (i > 0) { i--; sig = sig | (1 << (4 * (i + 1))); } - } - expr(); rval(); - lit(sig); - callname(intern("cbmake")); - expect(s_rp); - xty = tint; xfloat = 0; - return 0; -} -// intern the built-in types and the spellings the parser compares against. -// the s_ names are plain globals holding interned handles, so a check like -// is_id(s_fn) is one pointer compare. -int types_init() { - word f32p; - readonly_types = 0; - tytop = R_TY + NTL; tyend = R_TYEND; typeblocks = 0; ntl = 0; fieldn = 0; - if (R_TY + typecap > R_TYEND && typelist == R_TY) typelist = chk_resize(0, 0, typecap); - tword = tnew(TY_WORD, 1, intern("word"), 0); - tint = tnew(TY_INT, 1, intern("int"), 0); - tchar = tnew(TY_CHAR, 1, intern("char"), 0); - tbool = tnew(TY_BOOL, 1, intern("bool"), 0); - tvoid = tnew(TY_VOID, 0, intern("void"), 0); - tfloat = tnew(TY_FLOAT, 1, intern("float"), 0); - tbstr = tnew(TY_BSTR, 1, intern("bstr"), 0); addsym(tbstr[2], K_TYPE, tbstr); - addsym(tword[2], K_TYPE, tword); addsym(tint[2], K_TYPE, tint); addsym(tchar[2], K_TYPE, tchar); - addsym(tbool[2], K_TYPE, tbool); addsym(tvoid[2], K_TYPE, tvoid); addsym(tfloat[2], K_TYPE, tfloat); - addsym(intern("f64"), K_TYPE, tfloat); - addsym(intern("string"), K_TYPE, tptr(tchar)); // string is char*, as if by typedef char* string; - // f32: single precision. a variable holds the rounded value as a double; an element - // of an array or pointer of f32 is packed two to a word, like the sized integers - f32p = tnew(TY_FLOAT, 1, intern("f32"), 0); - tf32 = tnew(TY_FLOAT, 1, f32p[2], 0); - tf32[5] = 4; tf32[4] = f32p; f32p[5] = 4; f32p[6] = 2; - addsym(tf32[2], K_TYPE, tf32); - tsized("i8", 1, 1); addsym(intern("byte"), K_TYPE, tsized("u8", 1, 0)); - tsized("i16", 2, 1); tsized("u16", 2, 0); tsized("i32", 4, 1); tsized("u32", 4, 0); - s_lt = intern("<"); - s_gt = intern(">"); - s_shr = intern(">>"); - s_le = intern("<="); - s_ge = intern(">="); - s_eqeq = intern("=="); s_ne = intern("!="); s_plus = intern("+"); - s_struct = intern("struct"); s_type = intern("type"); s_typedef = intern("typedef"); s_typecheck = intern("typecheck"); s_docs = intern("docs"); s_std = intern("std"); s_icon = intern("icon"); s_small = intern("small"); - s_on = intern("on"); - s_off = intern("off"); - s_new = intern("rt.new"); - s_new_t = intern("rt.new_t"); - s_new_k = intern("rt.new_k"); - s_recycle_leaf = intern("rt.recycle_leaf"); - s_hitems = intern("#items"); s_hlen = intern("#len"); s_fatten = intern("run.fatten"); - s_this = intern("this"); - s_o3 = intern("o3"); - s_lfa = intern("rt.lfa"); - s_outside = intern("rt.outside"); - s_new_raw = intern("rt.new_raw"); - s_oob = intern("rt.oob"); - s_new_oz = intern("rt.new_oz"); - s_ld8 = intern("rt.ld8"); s_st8 = intern("rt.st8"); s_ld16 = intern("rt.ld16"); s_st16 = intern("rt.st16"); - s_ld32 = intern("rt.ld32"); s_st32 = intern("rt.st32"); - s_fixed = intern("fixed"); - s_dlen = intern("rt.dlen"); - s_dcap = intern("rt.dcap"); - s_dnew = intern("rt.dnew"); - s_dat = intern("rt.dat"); - s_len_s = intern("rt.len_s"); - s_at_s = intern("rt.at_s"); - s_slice_s = intern("rt.slice_s"); - s_cat_s = intern("rt.cat_s"); - s_eq_s = intern("rt.eq_s"); - s_lt_s = intern("rt.lt_s"); - s_le_s = intern("rt.le_s"); - s_put_bs = intern("rt.put_bs"); - s_put_s = intern("rt.put_s"); - s_n_to_s = intern("rt.n_to_s"); s_new_s = intern("rt.new_s"); - s_dpush = intern("rt.dpush"); s_dpop = intern("rt.dpop"); s_dresize = intern("rt.dresize"); - numops[0] = intern("*"); numops[1] = intern("/"); numops[2] = intern("%"); numops[3] = intern("&"); - numops[4] = intern("|"); - numops[5] = intern("^"); - numops[6] = intern("<<"); - numops[7] = s_shr; - nnumops = 8; - return 0; -} -// true for an operator that works on numbers. -int isnumop(int o) { - int i = 0; - while (i < nnumops) { - if (numops[i] == o) return 1; - i++; - } - return 0; -} -// the type of a binary operation, checking it when the typechecker is on -// the result type of binary operator b, which is also where the typechecker -// complains: pointer plus int is that pointer, pointer minus pointer is an int, -// word on either side wins, and a float on either side makes arithmetic float. -word bintype(int b, word lt, word rhs_type) { - int o = bname[b]; - lt = decay(lt); rhs_type = decay(rhs_type); - if (blen[b] < 0) { tcneed(!isagg(lt) && !isagg(rhs_type), "&& and || need scalars", lt); return tbool; } - if (isfl(lt) || isfl(rhs_type)) { - if (o == s_plus || o == s_minus || o == s_star || o == s_slash) { - if (lt[0] == TY_PTR || rhs_type[0] == TY_PTR) { tcerr("+", lt, rhs_type); return tword; } - return tfloat; - } - if (o == s_eqeq || o == s_ne || o == s_lt || o == s_gt || o == s_le || o == s_ge) return tbool; - } - if (o == s_eqeq || o == s_ne) { - if (!compat(lt, rhs_type) && !compat(rhs_type, lt)) tcerr("comparison", lt, rhs_type); - return tbool; - } - if (o == s_lt || o == s_gt || o == s_le || o == s_ge) { - if (!(lt == tword || rhs_type == tword || (isnum(lt) && isnum(rhs_type)) || (lt[0] == TY_PTR && rhs_type[0] == TY_PTR))) { - tcerr("comparison", lt, rhs_type); - } - return tbool; - } - if (o == s_plus) { - if (lt[0] == TY_PTR && (isnum(rhs_type) || rhs_type == tword)) return lt; - if (rhs_type[0] == TY_PTR && isnum(lt)) return rhs_type; - if (lt == tword || rhs_type == tword) return tword; - if (isnum(lt) && isnum(rhs_type)) return tint; - tcerr("+", lt, rhs_type); return tword; - } - if (o == s_minus) { - if (lt[0] == TY_PTR && rhs_type[0] == TY_PTR) return tint; - if (lt[0] == TY_PTR && (isnum(rhs_type) || rhs_type == tword)) return lt; - if (lt == tword || rhs_type == tword) return tword; - if (isnum(lt) && isnum(rhs_type)) return tint; - tcerr("-", lt, rhs_type); return tword; - } - if (isnumop(o)) { - if (lt == tword || rhs_type == tword) return tword; - if (isnum(lt) && isnum(rhs_type)) return tint; - if (isnum(lt)) lt = rhs_type; - tcneed(0, "arithmetic needs numbers", lt); return tword; - } - return tword; // operators a program added -} - -// ---- reflection ---- - -// for programs (and parse words) looking at types at runtime -word tkind(word t) { return t[0]; } -// the size of a type in words. -word tsize(int t) { return tsz(t); } -// the name of a type. -char* tname(word t) { return t[2]; } -// the length of an array type. -word tlen(word t) { return t[3]; } -// the number of fields of a struct type. -word tnfields(word t) { tresolve(t); return t[3]; } -// ---- fattening ---- - -// typed new stamps the header, in o3 the same as outside it. fatten is for a -// value that still has new_raw's blank type word when it reaches code that -// isn't o3: a store into a normal local or a global, an argument to a -// function that isn't o3, the call site where non-o3 code calls an o3 -// function whose result has pointers, and the end of an o3 block. a child -// store of a value new(T) just allocated, or that an o3 function returned, -// does not call fatten: the header is already that type. a call or return -// from o3 to o3 does not fatten. fatten(p, t) stamps the type, zeroes the -// slack, and follows every typed pointer. it returns p. -// a type whose objects can never hold a pointer: numbers, chars, bools, and -// arrays and plain structs of them. word, generics and anything unknown say no -// two questions about a type's shape, both answered structurally: -// isleaf can an object of this type never hold a heap pointer? the -// allocator uses it to pick the recycle_leaf fast path (no children -// to walk, so the block goes straight back on its size-class list). -// hasptr can it hold one? fatten uses it to decide whether stamping a -// type has to follow pointers. -word isleaf(word t) { - int k = t[0], i, n; - if (k == TY_INT || k == TY_CHAR || k == TY_BOOL || k == TY_FLOAT) return 1; - if (k == TY_ARR) return isleaf(t[4]); - if (k == TY_STRUCT && !t[5]) { - n = tnfields(t); - i = 0; - while (i < n) { - if (!isleaf(tftype(t, i))) return 0; - i++; - } - return 1; - } - return 0; -} -// true when a value of type t holds a pointer that fattening must follow. -word hasptr(word t) { - int k = t[0], i, n; - if (k == TY_PTR || k == TY_DYN || k == TY_BSTR) return 1; - if (k == TY_ARR) return hasptr(t[4]); - if (k == TY_STRUCT && !t[5]) { - n = tnfields(t); - i = 0; - while (i < n) { - if (hasptr(tftype(t, i))) return 1; - i++; - } - } - return 0; -} -// stamp p with its type t, then the pointers it holds. -int fatten(int p, int t) { fat1(p, t, 32); return p; } -// stamp p with type t if it has no type yet, then follow its pointers. dl is a -// depth limit so a cycle through growable arrays ends. a raw header holds -// -(n + 1) for an object of n words, which tells how many elements to type. -int fat1(word p, word t, int dl) { - word e; int n, k, i, es; - if (dl <= 0 || !t || !rt.isobj(p)) return 0; - if (t[0] == TY_DYN) { // a handle is always fat; what it holds may not be - if (hasptr(t[4])) { - es = tsz(t[4]); - n = p[0]; - i = 0; - while (i < n) { fatin(p[2] + i * es, t[4], dl - 1); i++; } - } - return 0; - } - if (t[0] != TY_PTR || p[-1] >= 0) return 0; - e = t[4]; n = 0 - p[-1] - 1; es = tsz(e); - if (es < 1) es = 1; - if (e == tword || e[0] == TY_VAR) p[-1] = 0; - else if (n > es) p[-1] = tarr(e, 0); - else p[-1] = e; - if (rt.osize(p) > n) rt.fill_w(p + n, 0, rt.osize(p) - n); - if (hasptr(e)) { k = n / es; i = 0; while (i < k) { fatin(p + i * es, e, dl - 1); i++; } } - return 0; -} -// fatten the value stored at address a, which has type t: a pointer or handle -// is fattened itself, an array or struct is walked element by element -word fatin(word a, word t, int dl) { - int k = t[0], i, n, o, ft, es; - if (k == TY_PTR || k == TY_DYN) return fat1(a[0], t, dl); - if (k == TY_ARR) { - if (!hasptr(t[4])) return 0; - n = t[3]; - es = tsz(t[4]); - i = 0; - while (i < n) { fatin(a + i * es, t[4], dl); i++; } - return 0; - } - if (k == TY_STRUCT && !t[5]) { - n = tnfields(t); - i = 0; - while (i < n) { - o = tfoff(t, i); - ft = tftype(t, i); - if (o >= 0) fatin(a + o, ft, dl); - i++; - } - } - return 0; -} -// reflection accessors over type records (see the layout under types). they -// resolve a generic instance first, so asking about vec<int> works. -char* tfname(word t, int i) { tresolve(t); return t[4][i * 3]; } -// the type of field i of a struct. -word tftype(word t, int i) { tresolve(t); return t[4][i * 3 + 1]; } -// the offset of field i of a struct. -word tfoff(word t, int i) { tresolve(t); return t[4][i * 3 + 2]; } -// the return type of a function type. -word tret(word t) { return t[4]; } -// the number of parameters of a function type. -word tnparams(word t) { return t[3]; } -// the type of parameter i of a function type. -word tparam(word t, int i) { return t[7 + i]; } -// the tag of parameter i: 0 untagged, 1 .m, 2 .d. -word tparamtag(word t, int i) { return ((t[5] >> 6) >> (i + i)) & 3; } // 0 untagged (m), 1 .m, 2 .d -// true when a function type returns an owned object (.u). -word tretu(word t) { return t[5] & 1; } -// the number of type arguments a generic struct instance has. -word tnargs(word t) { - if (t[0] != TY_STRUCT || !t[5]) return 0; - return t[6][0]; -} -// type argument i of a generic struct instance. -word targ(word t, int i) { return t[6][1 + i]; } -// the type of a function, by address. untyped functions get one made from their signature -// ---- function types by address ---- - -word sigof(int f) { - int i = nsym, s = -1; word[24] ws; int n, k; - while (i) { i--; if (skind[i] == K_FN && sval[i] == f && (sflag[i] & F_DEF)) { s = i; break; } } - if (s < 0) return 0; - if (stype[s]) return stype[s]; - n = (ssig[s] >> 1) & 31; k = 0; - while (k < n) { ws[k] = tword; k++; } - return tfn(tword, n, ws, ssig[s]); -} -// the function type of the function named nm, or 0 when there is none. -word sigofs(char* nm) { - int s = lookups(nm); - if (s < 0 || skind[s] != K_FN) return 0; - return sigof(sval[s]); -} - -// ---- expressions ---- - -// every expression leaves its value on the stack, returns its analyzer class, -// and sets xty to its type. lv means the stack holds an address that hasn't -// been loaded yet; structs and arrays are never loaded, their address is -// their value. -// memdebug: the address on top of the stack, checked before a heap load -// (dbg_addr duplicates the address on top and calls the runtime's checkptr on -// the copy, so memdebug catches a load from a freed object) -int dbg_addr() { - if (mdbg && !dry) { - lit(0); - op(O_PICK); - callname(s_check); - op(O_DROP); - } - return 0; -} -// turn an lvalue into a value. when lv is set the stack holds the address of a -// variable, field or element that has not been loaded; rval loads it, unless -// the thing is an array or struct, whose address is its value. every place -// that needs a value calls this after parsing an expression. -int rval() { - if (lv && lvk == LV_LOCAL) { - ltag[lvi] = ltag[lvi] | LREAD; - if (rt.eq_w(lname[lvi], "_") && af_warning(lreadpos[lvi])) { - rt.put_s("discard placeholder `_` is read\nhelp: give the parameter or pattern binding a name when its value is needed"); - af_warning_end(); - } - } - if (lv) { - if (ispk(xty)) pkld(xty); - else if (!isagg(xty)) { dbg_addr(); op(O_LD); } - lv = 0; - } - return 0; -} -// run a keyword's handler (kw_len, kw_struct, a parse word...) from the spot -// where the parser met it. e is 1 in expression position, where statement-only -// keywords refuse to run. the handler reports the class of what it produced -// in xcls. -int kwcall(int s, int e) { - int f = sval[s], old = kw_expr, c, n0 = ntok; - kw_expr = e; xcls = C_PLAIN; xty = tword; xzero = 0; - f(); xaf = 0; xvk = 0; - // a handler that never reads a token (not even its own name) would be called - // again on the same token forever, so it is an error, and the token is skipped - if (ntok == n0) die("a parse word has to read its own name with next()"); - xbor = -1; - kw_expr = old; - c = xcls; - lv = 0; lvk = LV_NONE; - return c; -} -// report an error when a statement is used inside an expression. -int stmtonly() { - if (kw_expr) serr("this can't be used inside an expression"); - return 0; -} -// the arguments of a call. ft is the callee's type (or 0), bs the generic bindings -// (tags packs two bits per parameter: 0 lend, 1 .m, 2 .d. bs collects type -// parameter bindings as the argument types are unified with the signature. -// fat asks for every pointer argument to be fattened first, for o3 code -// calling code that is not o3.) -int args(int tags, word ft, int bs, int fat) { - int np = 0, c, pt, t, cs = ifcall, freeing = 0, harmless = 0; word first = 0; int origin = -1, anytemp = 0, current, tag, place, parameter_base = effect_argument_base, owner = -1, current_owner, owner_seen = 0, aliases; word origins = 0, current_mask, owner_fact = 0; - if (cs >= 0 && cs < ubase) { - freeing = sname[cs] == s_free || sname[cs] == s_recycle; - harmless = sname[cs] == s_put_s || rt.eq_w(sname[cs], "rt.put_n") || rt.eq_w(sname[cs], "rt.put_c") || rt.eq_w(sname[cs], "len") || rt.eq_w(sname[cs], "cap"); - } - xgive = 0; ifcall = 0 - 1; effect_argument_base = 0; - expect(s_lp); - if (!is_op(s_rp)) { - while (1) { - word expected = closure_expected; - closure_expected = 0; - if (ft && np + parameter_base < ft[3] && is_id(s_fn)) closure_expected = subst(ft[7 + np + parameter_base], bs); - place = tpack(); c = expr(); rval(); closure_expected = expected; - current = effect_origin(c); current_mask = effect_mask(c); origins = origins | current_mask; effect_args_mask(cs, np + parameter_base, current_mask); - if (!np) origin = current; else if (origin != current) origin = -1; - tag = (tags >> (np + np)) & 3; - current_owner = xbor; - if (c >= C_VAR && tracked(c - C_VAR)) current_owner = lroot(c - C_VAR); - if (tag <= 1 && current_owner >= 0 && tracked(current_owner)) effect_require_mode(cs, np + parameter_base, place, 1); - aliases = effect_get(cs, np + parameter_base); - if (aliases & (EF_ALIAS | EF_IDENTITY | EF_UNKNOWN)) { - if (!owner_seen) { owner = current_owner; owner_fact = xaf; owner_seen = 1; } - else if (owner != current_owner) { owner = -1; owner_fact = 0; } - } - if (fchk && c == C_FRESH && tag != 2) { - if (!(cs < 0 && tag == 1)) effect_require(cs, np + parameter_base, place); emit_borrow_hold(); c = C_PLAIN; anytemp = 1; - } - if (xaf && xaf[2] == 5) { anytemp = 1; if (!(cs < 0 && tag == 1)) effect_require(cs, np + parameter_base, place); } - if (fchk && xaf && xaf[2] == 4 && (isptr(xty) || isagg(xty)) && !(cs < 0 && tag == 1)) effect_require(cs, np + parameter_base, place); - if (!np) first = xaf; - if (!freeing && !harmless && (effect_get(cs, np + parameter_base) & (EF_WRITE | EF_RETAIN | EF_CONSUME | EF_UNKNOWN))) af_escape(xaf); - if (ifs && if_value && cs >= 0 && sname[cs] == s_new && !np) if_join(if_value, tint); - if (cs >= 0) if_arg(cs, np + parameter_base, xty, xzero); - if (if_value && cs >= 0 && np + parameter_base < IFP) { - word infer_callee = if_get(cs); - if (infer_callee) { - if (ft && np + parameter_base < ft[3]) if_join(infer_callee + 8 + 3 * (np + parameter_base), subst(ft[7 + np + parameter_base], bs)); - if_link(if_value, infer_callee + 8 + 3 * (np + parameter_base)); - } - } - if (ft && np + parameter_base < ft[3]) fcoerce(subst(ft[7 + np + parameter_base], bs)); - t = xty; - argcheck(c, (tags >> (np + np)) & 3); - // o3 data handed to normal code - if (fat && hasptr(t)) { - lit(t); - callname(s_fatten); - xty = t; - } - if (ft && np + parameter_base < ft[3]) { - pt = ft[7 + np + parameter_base]; - if (cs >= 0 && sname[cs] == s_put_s && np == 0 && xstr) pt = tbstr; - if (!unify(pt, xty, bs)) tcerr("argument", subst(pt, bs), xty); - } - np++; - if (!accept(s_comma)) break; - } - } - expect(s_rp); - argfact = first; argorigin = origin; argorigin_mask = origins; argtemporary = anytemp; argowner = owner; argowner_fact = owner_fact; xaf = 0; vf_effect(); - return np; -} -// memdebug records the source line on the next new, via the runtime global -// memdebug: store the current line in the runtime's alloc_line just before an -// allocation, so a leak report can say where the object came from -int stamp_site() { - int s; - if (!mdbg || dry || o3on) return 0; - s = lookup(s_alloc); - if (s < 0) return 0; - lit(tline); lit(sval[s]); op(O_ST); - return 0; -} -// new(T) and new(T, count): a typed object, its type stamped into the header -// new(T), new(T, n), new(T[?]), new(fixed T[?], n). a growable array goes to -// dnew. otherwise the call is new_t(size, type), unless the size is known at -// compile time and we are not in o3 or a dry run: then the size class is -// computed here and the call is new_k(size, type, class), which pops a block -// straight off the class list. newsz and newleaf record what was allocated so -// the variable it lands in can later be freed with the matching fast path. -int newtyped() { - word t; int c, fast = 0, length = 1, place = tpack(); - t = ptype(); - if (t[0] == TY_DYN) { // a growable array: element size, length, type - if (tbytes(t[4])) lit(0 - tbytes(t[4])); else lit(tsz(t[4])); // packed elements say so with a negative size: minus their bytes - if (accept(s_comma)) { c = expr(); rval(); plainuse(c); length = -1; if (xconst && xcval >= 0) length = xcval; } - else { length = 0; lit(0); if (t[3]) serr("a fixed array needs its length: new(fixed T[?], n)"); } - lit(t); - expect(s_rp); - stamp_site(); - callname(s_dnew); vf_effect(); - xaf = af_new(length, 3, place); - lv = 0; lvk = LV_NONE; xty = t; xzero = 0; xstamp = 1; - return C_FRESH; - } - if (accept(s_comma)) { - c = expr(); rval(); plainuse(c); - length = -1; if (xconst && xcval >= 0) length = xcval; - if (tbytes(t)) { // packed: n elements take (n * bytes + 7) / 8 words - if (tbytes(t) != 1) { lit(tbytes(t)); op(15); } - lit(7); op(O_ADD); lit(3); op(19); - } else if (tsz(t) != 1) { lit(tsz(t)); op(15); } - lit(tarr(t, 0)); - newsz = -1; newleaf = 0; - } else { - lit(tsz(t)); lit(t); - newsz = tsz(t); newleaf = 0; - if (isleaf(t)) newleaf = t; - if (!o3on && !dry && lookup(s_new_k) >= 0) { lit(szclass(newsz)); fast = 1; } - } - expect(s_rp); - stamp_site(); - if (fast) callname(s_new_k); else callname(s_new_t); vf_effect(); - xaf = af_new(length, 2, place); - lv = 0; lvk = LV_NONE; xty = tptr(t); xzero = 0; xstamp = 1; - return C_FRESH; -} -// ld8/st8 and the 16- and 32-bit pair. 0 when this call should stay a call. -// a store's opcode is odd: the arguments arrive address then value, and the -// opcode wants the address on top. -// (the byte-access functions become single opcodes inside o3 code) -int byteop(int s) { - if (sname[s] == s_ld8) return 612; - if (sname[s] == s_st8) return 613; - if (sname[s] == s_ld16) return 614; - if (sname[s] == s_st16) return 615; - if (sname[s] == s_ld32) return 616; - if (sname[s] == s_st32) return 617; - return 0; -} -// ---- type facts ---- - -// annotate mode (the language server asks for it, ifon) collects the types the -// untyped declarations of a document could be given, as edits: a place, how -// many chars it replaces (0 inserts) and the text. a local gets the type of -// its initializer, a parameter the type every call passes it, and a function -// the type of what it returns. only types that change nothing are offered: no -// floats and no sized integers, because a typed one converts or cuts to size -// where the untyped word did not, so the annotated program is the same program. -// the declarations are found while they compile, the calls too, and the -// edits are written at the end of the unit, when every call has been seen. -// a type that can be written in front of a name and changes nothing -word ifpure(word t) { - int k; - if (!t || t == tword || t == tvoid) return 0; - k = t[0]; - if (k == TY_INT) return t[5] == 0; - if (k == TY_CHAR || k == TY_BOOL || k == TY_STRUCT || k == TY_BSTR) return t[2] != 0; - if (k == TY_PTR) { - k = t[4][0]; - if (k == TY_ARR || k == TY_DYN || k == TY_FN) return 0; - return t[4] == tword || ifpure(t[4]); - } - if (k == TY_ARR || k == TY_DYN) return t[4] == tword || ifpure(t[4]); - return 0; -} -// one edit: the token at place p is replaced by the type t and then the text -// after (len < 0: the token's own length; 0: nothing is replaced) -int fact_add(int p, int len, int t, int after) { - int sv, o, at, n; word e; int osm = rt.outmode, osb = rt.outbuf, osn = rt.outn, osc = rt.outcap; - // the text is printed with tprint into the pool: output mode 2 writes into outbuf - if (!ifx || dry || panic || chkabort) return 0; - sv = tpack(); tunpack(p); o = toff; if (len < 0) len = toklen; tunpack(sv); - if (o < 0) return 0; - at = nift; - rt.outn = 0; rt.outmode = 3; tprint(t); rt.put_s(after); n = rt.outn; - if (nifx >= ifxcap) { ifxcap = ifxcap * 2; ifx = chk_resize(ifx, nifx * 4, ifxcap * 4); } - if (at + n > iftcap) { while (at + n > iftcap) iftcap = iftcap * 2; ift = chk_resize(ift, at, iftcap); } - rt.outbuf = ift + at; rt.outn = 0; rt.outcap = n; rt.outmode = 2; - tprint(t); rt.put_s(after); - n = rt.outn; - rt.outmode = osm; rt.outbuf = osb; rt.outn = osn; rt.outcap = osc; - e = ifx + nifx * 4; - e[0] = o; e[1] = len; e[2] = at; e[3] = n; - nifx++; nift = at + n; - return 0; -} -// the record of function symbol s: calls can come before the definition -word if_get(int s) { - int id; word r; - if (!ifs || s < 0 || s >= symcap) return 0; - id = ifs[s]; - if (id) return ifr[id - 1]; - if (nif >= ifrcap) { ifrcap = ifrcap * 2; ifr = chk_resize(ifr, nif, ifrcap); } - nif++; ifs[s] = nif; - r = chk_resize(0, 0, IFW); ifr[nif - 1] = r; - rt.fill_w(r, 0, IFW); - r[0] = s; - return r; -} -// a state is 0 (nothing seen), a type, or -1 (no single type): p points at it -int if_join(word p, int t) { - if (!t || t == tword) return 0; - if (*p == 0 - 1) return 0; - if (!ifpure(t)) *p = 0 - 1; - else if (!*p) *p = t; - else if (*p != t) *p = 0 - 1; - return 0; -} -// equal type flows connect caller parameters, callee parameters and results. -// nodes are addresses of inference states, retained by the function records. -int if_link(word a, word b) { - word edge; - if (!ifs || !a || !b || a == b || dry) return 0; - edge = chk_resize(0, 0, 3); edge[0] = if_edges; edge[1] = a; edge[2] = b; if_edges = edge; - return 0; -} -int if_solve() { - word edge, a, b, record; int changed = 1, left, right, index = 0; - while (index < nif) { - record = ifr[index]; - if (!record[3] && (!record[2] || !record[1])) record[3] = -1; - index++; - } - while (changed) { - changed = 0; edge = if_edges; - while (edge) { - a = edge[1]; b = edge[2]; left = *a; right = *b; - if (left == -1 || right == -1 || (left && right && left != right)) { *a = -1; *b = -1; } - else if (left) *b = left; else if (right) *a = right; - if (*a != left || *b != right) changed = 1; - edge = edge[0]; - } - } - return 0; -} -// argument j of a call of s has the type t (a literal 0 says nothing: it is a -// null pointer as often as a number) -int if_arg(int s, int j, int t, int zero) { - int r; - if (!ifs || dry || j >= IFP || zero) return 0; - r = if_get(s); - if (r) if_join(r + 8 + 3 * j, t); - return 0; -} -// a function at the top level starts: its header is at ifkp and ifkk says how -// to edit it (1: fn becomes the type, 2: the type goes in front of a .u) -int if_begin(int s, int k) { - word r = if_get(s); - ifcur = 0; - if (!r) return 0; - r[2] = 1; r[5] = ifkp; r[6] = k; - ifcur = r; - return 0; -} -// parameter j: p is where its type would go, kind 0 for a typed one, 1 to -// insert the type and a space, 2 to insert it right in front of a tag -int if_param(int j, int p, int kind, word type) { - word r = ifcur; - if (!r || j >= IFP) return 0; - if_join(r + 8 + 3 * j, type); r[9 + 3 * j] = p; r[10 + 3 * j] = kind; - if (kind) r[7] = j + 1; - return 0; -} -// a return statement: t is what it returns, zero if that is a bare 0 -int if_ret(int t, int zero) { - word r = ifcur; - if (!r || nclo || dry) return 0; - r[1] = 1; - if (if_value && !zero) if_link(r + 3, if_value); - if (zero) r[4] = 1; else if_join(r + 3, t); - return 0; -} -// the function ends: which of its parameters were used as addresses -int if_fin() { - word r = ifcur; int j = 0; - while (j < r[7]) { - if (lptr[lbase + j]) r[10 + 3 * j] = r[10 + 3 * j] | 4; - j++; - } - return 0; -} -// the edits of every function, once all the calls have been seen -int if_emit() { - int i = 0, j; word r; int t, k; - if_solve(); - while (i < nif) { - r = ifr[i]; - i++; - if (!r[2]) continue; - j = 0; - while (j < r[7]) { - k = r[10 + 3 * j]; t = r[8 + 3 * j]; - if ((k & 3) && !t && (k & 4)) t = tptr(tword); // no call says more, but the body uses it as an address - if ((k & 3) == 1 && t > 0) fact_add(r[9 + 3 * j], 0, t, " "); - else if ((k & 3) == 2 && t > 0) fact_add(r[9 + 3 * j], 0, t, ""); - j++; - } - t = r[3]; - if (r[1] && t != 0 - 1) { - if (!t && r[4]) t = tint; - if (t && r[6] == 1) fact_add(r[5], 0 - 1, t, ""); - else if (t && r[6] == 2) fact_add(r[5], 0, t, ""); - } - } - return 0; -} -// ---- hints ---- - -// findings that are not errors. each is written only in check mode, through -// hint_begin, and none of them changes the code the compiler emits. -int magic(int v) { - if (literal_documented(toff) || !hint_begin(tpack(), HINT_ALL)) return 0; - rt.put_s("unannotated numeric literal "); rt.put_n(v); - rt.put_s("\nhelp: use a named constant or add an inline block comment before the literal to suppress this hint"); - dg_close(); - return 0; -} -// warn that a new inside a loop allocates on every pass. -int newloop(int p) { - if (!hint_begin(p, HINT_SMALL)) return 0; - rt.put_s("new inside a loop allocates on every pass; allocate once above the loop if the object can be reused"); - dg_close(); - return 0; -} -// hint when a shift by v is past the width of the value. -int shifthint(int p, int v) { - int k = 0; - if (!hint_begin(p, HINT_ALL)) return 0; - while ((1 << k) < v) k++; - rt.put_s("multiplying by "); rt.put_n(v); rt.put_s(" is a shift; write << "); rt.put_n(k); - dg_close(); - return 0; -} -// a call to function symbol s, with the callee name already consumed and the -// ( next. in order: -// 1. new(...) with a type argument is redirected to newtyped -// 2. in o3 code, untyped new becomes new_oz and the byte accessors turn into -// opcodes -// 3. the arguments are parsed against the signature (args), checking each -// against its parameter tag; generic type parameters are bound here -// 4. free whose argument was a given-away last pointer becomes recycle; a -// constant-size new becomes new_k with its class; put_s of a byte string -// becomes put_bs -// 5. the call is emitted (fnref pushes the address, or a patch site if the -// function isn't defined yet) -// 6. the result's type and class are set: .u functions return C_FRESH, which -// is how the analyzer knows the caller now owns a new object -int call(int s) { - int np, tags = 0, ret = 0; word ft = stype[s]; word[8] bs; int rs, rhs_type = 0, kop, cp = callpos, original = s; word storage; int origin, temporary, owner; word origins, owner_fact; - word requirements = effect_requirements, edges = effect_edges; - borrow_depth++; - if (sname[s] == s_new && skind[s] == K_FN) { - peek(); - if (pk_tk == T_OP && pk_tv == s_lk) { next(); borrow_depth--; return newtyped(); } - if (pk_tk == T_ID && (istypename(pk_tv) || pk_tv == s_fn)) { next(); borrow_depth--; return newtyped(); } - } - if (o3on && sname[s] == s_new) { tags = lookup(s_new_oz); if (tags >= 0) s = tags; tags = 0; } - if (o3on) { - kop = byteop(s); - if (kop) { - np = args(0, 0, bs, 0); - if (kop & 1) { - if (np != 2) serrn(sname[s], "wrong number of arguments"); - op(O_SWAP); op(kop); lit(0); - } else { - if (np != 1) serrn(sname[s], "wrong number of arguments"); - op(kop); - } - lv = 0; lvk = LV_NONE; xty = tword; xzero = 0; xbor = -1; - xfloat = 0; xstr = 0; xlenloc = -1; xstamp = 0; - borrow_depth--; return C_PLAIN; - } - } - if (sflag[s] & F_SIG) { tags = ssig[s] >> 6; ret = ssig[s] & 1; } - else sflag[s] = sflag[s] | F_UNTYPED; - if (sflag[s] & F_RETOWN) ret = 1; - rt.fill_w(bs, 0, 8); - if (ngbind) { rt.copy_w(bs, gbind, ngbind); ngbind = 0; } - np = 0; - if (o3on && !(sflag[s] & F_O3) && sname[s] != s_free && sname[s] != s_recycle) np = 1; // o3 calling normal code: fatten the arguments - ifcall = s; effect_argument_base = 0; - np = args(tags, ft, bs, np); - storage = argfact; origin = argorigin; origins = argorigin_mask; temporary = argtemporary; owner = argowner; owner_fact = argowner_fact; - if (original < ubase && (sname[original] == s_free || sname[original] == s_recycle)) af_end(storage, cp); - if (sflag[s] & F_GEN) { int template = s; s = ginstance(s, bs); effect_specialize(template, s, requirements, edges); } - if (sname[s] == s_free && xgive) { // the last pointer: straight back to the size-class list - rs = lookup(s_recycle); - if (rs >= 0) s = rs; - } - if ((sflag[s] & F_SIG) && np != ((ssig[s] >> 1) & 31)) serrn(sname[s], "wrong number of arguments"); - if (sname[s] == s_new) { - stamp_site(); newsz = -1; newleaf = 0; - if (np == 1 && xconst && xcval >= 0) newsz = xcval; - xaf = af_new(newsz, 2, cp); - if (lsp > 0) newloop(cp); - // a constant size: the class is known now, so use the allocator's direct path - if (newsz >= 0 && !o3on && !dry && lookup(s_new_k) >= 0) { - lit(0); - lit(szclass(newsz)); - s = lookup(s_new_k); - } - } - if (sname[s] == s_put_s && np == 1 && xstr) { - s = lookup(s_put_bs); - if (s < 0) s = fwdfn(s_put_bs); - } - if (!(sflag[s] & F_DEF) || s == finsym) sflag[s] = sflag[s] | F_EARLY; // called before its body: an owned return is not inferred for it - if (sflag[s] & F_EXT) { lit(sval[s][-1]); op(620); } // an extern: the native call opcode, with the block's address - else { fnref(s); op(O_CALL); } - if (!o3on && (sflag[s] & F_O3) && ft) { // non-o3 called o3: fatten the result here, not inside the callee - rhs_type = subst(ft[4], bs); - if (hasptr(rhs_type)) { lit(rhs_type); callname(s_fatten); } - } - xstr = 0; - if (sname[s] == s_cat_s || sname[s] == s_slice_s || sname[s] == s_n_to_s || sname[s] == s_new_s) { - xstr = 1; - } - lv = 0; lvk = LV_NONE; xzero = 0; xbor = -1; xconst = 0; - xty = tword; - if (ft) xty = subst(ft[4], bs); - if (xstr || xty == tbstr) { xstr = 1; xty = tbstr; } - xfloat = 0; - if (isfl(xty)) xfloat = 1; - xlenloc = -1; xstamp = 0; - if (o3on && (sflag[s] & F_O3) && ft && hasptr(xty)) xstamp = 1; - borrow_depth--; effect_value = 0; - if (!ret && (isptr(xty) || xty == tword)) { - effect_value = origins; - if (owner >= 0 && (isptr(xty) || (effect_get(original, 0) & EF_IDENTITY))) xbor = owner; - if (owner_fact && (effect_get(original, 0) & EF_IDENTITY)) xaf = owner_fact; - if (origin >= 0 && owner < 0) { int oi = lbase; while (oi < nloc) { if (lrskind[oi] == RS_PARAM && oi - lbase == origin) { xbor = oi; break; } oi++; } } - if (temporary) { int ai = 0, aliases = 0; while (ai < np) { aliases = aliases | effect_get(original, ai); ai++; } if (aliases & (EF_ALIAS | EF_UNKNOWN)) xaf = af_new(-1, 5, cp); } - } - if_value = 0; - if (ifs && !dry) { - word result_node = if_get(original); - if (result_node) { - if (sname[original] == s_new && !result_node[3]) result_node[3] = tword; - else if_join(result_node + 3, xty); - if_value = result_node + 3; - } - } - if (ret) return C_FRESH; - return C_PLAIN; -} -// an enhancement used like a function: the arguments, then its one opcode -// an enhancement (a custom opcode declared with "enhance") called like a -// function. it compiles to the one opcode, or to a call of its fallback -// function when the cpu does not have the opcode. -int enhcall(int s) { - int nin = ssig[s] & 15, nout = (ssig[s] >> 4) & 15, n = 0, c; - expect(s_lp); - if (!is_op(s_rp)) { - while (1) { - c = expr(); - rval(); - plainuse(c); - n++; - if (!accept(s_comma)) break; - } - } - expect(s_rp); - vf_effect(); - if (n != nin) serrn(sname[s], "wrong number of inputs"); - lv = 0; lvk = LV_NONE; xty = tword; xzero = 0; xbor = -1; xlenloc = -1; xstamp = 0; - if (sflag[s] & F_FALLBACK) { fnref(sfix[s] - 1); op(O_CALL); return C_PLAIN; } - op(XOP0 + sval[s]); - if (nout == 0) lit(0); - if (nout > 1) serrn(sname[s], "leaves several values; use it from asm"); - return C_PLAIN; -} -// ---- closures ---- - -// fn(a, b) { ... } is a function whose last, hidden parameter is a pointer to -// its environment. the closure value is a heap block: word 0 is the body's -// entry, and the captured variables start at word 3. the block itself is never -// run as code. callable_code distinguishes plain function addresses, including -// linked heap code segments, from closure blocks. a plain function is called -// as it is. a closure is called with block + 3 as its env, at the entry in -// word 0. a capture is a shared cell. the enclosing function and every closure -// read and write the same value; cells remain reachable through escaping closures. -// scan with the compiler lexer, restoring its source and lookahead. only functions containing a closure -// need cells; their scalars get cells from declaration time, so loop back edges and earlier references agree. -int capture_scan() { - word old, f; int depth = 1, found = 0, docs = docs_on, record = grec; - if (isp < 0 || R_SRC[isp * 32] == 0 || !is_op(s_lb)) return 0; - old = R_SRC + isp * 32; - src_push(1, capture_dummy, 0, 0); old = R_SRC + (isp - 1) * 32; f = R_SRC + isp * 32; - f[0] = old[0]; f[1] = old[1]; f[2] = old[2]; f[3] = old[3]; f[4] = 0; pq_n = f[14]; - docs_on = 0; grec = 0; dry++; - while (depth) { - next(); if (tk == T_EOF) break; - if (is_op(s_lb)) depth++; - else if (is_op(s_rb)) depth--; - else if (is_id(s_fn)) { peek(); if (pk_tk == T_OP && (pk_tv == s_lp || pk_tv == s_colon)) found = 1; } - } - dry--; src_pop(); docs_on = docs; grec = record; return found; -} -int capture_cell(int i, int copy) { - if (!capture_enabled || o3on || (ltag[i] & 16) || larr[i] || local_cell[i]) return 0; - if (copy) { laddr(lslot[i]); op(O_LD); } - lit(1); lit(ltype[i]); callname(s_new_t); callname(intern("share")); - if (copy) { lit(0); op(O_PICK); laddr(lslot[i]); op(O_ST); op(O_ST); } - else { laddr(lslot[i]); op(O_ST); } - local_cell[i] = 1; return 0; -} -int capture_prepare() { - int i = lbase; capture_enabled = capture_scan(); - if (!capture_enabled) return 0; - while (i < nloc) { if (lrskind[i] == RS_PARAM) capture_cell(i, 1); i++; } - return 0; -} -// grow closure frames and capture rows while keeping closure and capture indices. -int reserve_closures(int need) { - int cap = closure_cap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - clo_lo = chk_resize(clo_lo, nclo, cap); - clo_hi = chk_resize(clo_hi, nclo, cap); - clo_n = chk_resize(clo_n, nclo, cap); - clo_env = chk_resize(clo_env, nclo, cap); - clo_ret = chk_resize(clo_ret, nclo, cap); - clo_name = chk_resize(clo_name, nclo * capture_width, cap * capture_width); - clo_loc = chk_resize(clo_loc, nclo * capture_width, cap * capture_width); - clo_via = chk_resize(clo_via, nclo * capture_width, cap * capture_width); - closure_cap = cap; return cap; -} -int reserve_captures(int need) { - int cap = capture_width, i = 0; char** names; word locals, via; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - names = chk_resize(0, 0, closure_cap * cap); - locals = chk_resize(0, 0, closure_cap * cap); - via = chk_resize(0, 0, closure_cap * cap); - while (i < nclo) { - rt.copy_w(names + i * cap, clo_name + i * capture_width, clo_n[i]); - rt.copy_w(locals + i * cap, clo_loc + i * capture_width, clo_n[i]); - rt.copy_w(via + i * cap, clo_via + i * capture_width, clo_n[i]); i++; - } - clo_name = names; clo_loc = locals; clo_via = via; capture_width = cap; return cap; -} -// find or create the capture slot for variable n in the innermost closure -// being compiled: if the name is one of the enclosing function's locals, it is -// recorded once and the body reads its shared cell through the environment. -// a name from further out is captured through each closure in between: the -// closure that encloses this one captures it too, and this one copies it from -// that closure's environment (clo_via is the slot there, -1 for a local). -word clocap(int n) { return clocap_at(nclo - 1, n); } -// the slot of the name n in closure c's captures, adding it when the closure has -// not captured it yet. returns the capture index, or -1 when no such name is visible. -word clocap_at(int c, int n) { - int i = 0, j, base = c * capture_width, r, root; - while (i < clo_n[c]) { if (clo_name[base + i] == n) return i; i++; } - j = clo_hi[c]; - while (j > clo_lo[c]) { - j--; - if (lname[j] != n) continue; - effect_note(effect_origin(C_VAR + j), EF_RETAIN); - root = lroot(j); local_captured[j] = 1; local_captured[root] = 1; - if (lafact[j] && (lafact[j][2] == 1 || lafact[j][2] == 4 || lafact[j][2] == 5)) serrn(n, "a stack or temporary borrow cannot escape through a closure"); - if (root != j && lname[root]) { clocap_at(c, lname[root]); base = c * capture_width; } - if (ltag[j] & 16) serrn(n, "an o3 variable cannot be captured by reference; use an ordinary local"); - if (larr[j]) serrn(n, "an array can't be captured; capture a pointer to it"); - reserve_captures(clo_n[c] + 1); base = c * capture_width; - clo_name[base + clo_n[c]] = n; clo_loc[base + clo_n[c]] = j; clo_via[base + clo_n[c]] = 0 - 1; clo_n[c]++; - return clo_n[c] - 1; - } - if (c == 0) return 0 - 1; - r = clocap_at(c - 1, n); base = c * capture_width; - if (r < 0) return 0 - 1; - reserve_captures(clo_n[c] + 1); base = c * capture_width; - clo_name[base + clo_n[c]] = n; clo_loc[base + clo_n[c]] = clo_loc[(c - 1) * capture_width + r]; clo_via[base + clo_n[c]] = r; clo_n[c]++; - return clo_n[c] - 1; -} -// closure environments retain precise capture-cell types for runtime reflection. -word closure_layout(int closure_index, int count) { - word t = tnew(TY_STRUCT, 3 + count, 0, 0), fields = talloc((3 + count) * 3); int i = 0, local; - t[3] = 3 + count; t[4] = fields; - while (i < 3 + count) { - fields[i * 3 + 2] = i; - if (i < 3) fields[i * 3 + 1] = tword; - else { local = clo_loc[closure_index * capture_width + i - 3]; fields[i * 3 + 1] = tptr(ltype[local]); } - i++; - } - return t; -} -// fn(params) { body } as an expression. the body is compiled in place behind -// a jump (hop) as a nested function with a hidden last parameter, env. when -// the code that creates the closure runs, it allocates the block (code, then -// captured values), writes the three code words and copies each captured -// variable in. ctx_push / ctx_pop save and restore the enclosing function's -// compiler state around the body. -int closure() { - int np = 0; char*[20] pn; word[20] pty; int hop, entry, i, t, envl, ncap, c, result, declared = 0, tags = 0, tag; word[20] ptags; - word expected = closure_expected; closure_expected = 0; - reserve_closures(nclo + 1); - if (o3on) serr("closures can't be made inside an o3 region"); - next(); - // fn:type(args) names the result; returns are coerced and checked against it - if (accept(s_colon)) declared = ptype0(); - expect(s_lp); - if (!is_op(s_rp)) { - while (1) { - pty[np] = tword; - if (expected && expected[0] == TY_FN && np < expected[3]) pty[np] = expected[7 + np]; - if (!is_op(s_dot) && paramstart()) { - pty[np] = ptype(); if (pty[np][0] == TY_ARR) pty[np] = decay(pty[np]); - } - tag = 0; - if (accept(s_dot)) { if (is_id(s_m)) tag = 1; else if (is_id(s_d)) tag = 2; else die("a parameter tag is .m or .d"); next(); } - ptags[np] = tag; if (np < 12) tags = tags | (tag << (np + np)); else if (tag) die("only the first 12 parameters can be tagged"); - if (expected && expected[0] == TY_FN && !tag && (isptr(pty[np]) || pty[np][0] == TY_DYN || pty[np][0] == TY_FN)) ptags[np] = 1; - pnoffs[np] = toff; pnplaces[np] = tpack(); rs_note(RS_PARAM, toff); pn[np] = name(); - np++; - if (np >= 20) { die("too many parameters"); break; } - if (!accept(s_comma)) break; - } - } - expect(s_rp); - ctx_push(); ifcur = 0; ifcall = -1; - clo_lo[nclo] = lbase; clo_hi[nclo] = nloc; clo_n[nclo] = 0; clo_ret[nclo] = 0; - nclo++; - hop = hole(); op(O_JMP); - entry = here; - fn_begin(1, 0); - if (declared) fty = declared; - prologue(); borrow_prologue(); - i = 0; - while (i < np) { - t = newloc(pn[i]); lrskind[t] = RS_PARAM; lrsoff[t] = pnoffs[i]; lpos[t] = pnplaces[i]; fslots++; lsmark(1, pn[i]); - ltype[t] = pty[i]; - if (fchk && ptags[i] == 1) lstate[t] = S_BORROW; - if (fchk && ptags[i] == 2) { lstate[t] = S_M; lown[t] = 1; } - if (isfl(pty[i])) setflt(t, 1); - i++; - } - envl = newloc(intern("<env>")); fslots++; - clo_env[nclo - 1] = envl; - i = np + 1; - while (i) { i--; laddr(i); op(O_ST); } - body(); - result = clo_ret[nclo - 1]; - if (result <= 0 || live) result = tword; - if (declared) result = declared; - epilogue(); - nclo--; - c = nclo; - ncap = clo_n[c]; - setlit(hop, here); - ctx_pop(); - if (probe_off >= 0 && probe_has == 1 && tpend <= probe_off && !ginst) probe_snap(); // past the closure: what the function around it has - // the block: [push env][push body][jump] and the captured values - // the block: word 0 is the body's entry, words 3 on are the captured values. - // a call through the closure pushes block + 3 as the env and jumps to word 0. - lit(3 + ncap); lit(closure_layout(c, ncap)); callname(s_new_t); - lit(entry); lit(1); op(O_PICK); op(O_ST); - i = 0; - while (i < ncap) { - t = clo_loc[c * capture_width + i]; - if (clo_via[c * capture_width + i] < 0) { - touch(t); effect_note_mask(effect_mask(C_VAR + t), EF_RETAIN); ev_put(t, EV_CAPTURE, -1); - if (cmode && !dry) { lvescaped[t] = 1; lvkind[t] = 0; } - if (!isalias(t)) { vaddr(t); op(O_LD); callname(intern("share")); op(O_DROP); } - vaddr(t); - } - else { vaddr(clo_env[c - 1]); op(O_LD); lit(clo_via[c * capture_width + i]); op(O_ADD); op(O_LD); } - lit(1); op(O_PICK); lit(3 + i); op(O_ADD); op(O_ST); - i++; - } - lv = 0; - lvk = LV_NONE; - xzero = 0; - xbor = -1; - xconst = 0; - xfloat = 0; - xstr = 0; - xlenloc = -1; - xstamp = 0; - xty = tfn(result, np, pty, np + np + tags * 64); - return C_FRESH; -} - -// ---- array initializer lists and compound literals ---- - -// keep original token places while measuring and replaying an initializer. measurement never evaluates -// value expressions: values run once, in source order, after zero filling the storage. -word array_tokens() { - word q = chk_resize(0, 0, 97); int cap = 32, n = 0, depth = 0; - while (tk != T_EOF) { - if (n == cap) { cap = cap * 2; q = chk_resize(q, n * 3 + 1, cap * 3 + 1); } - q[1 + n * 3] = tk; q[2 + n * 3] = tv; q[3 + n * 3] = tpack(); n++; - if (is_op(s_lb)) depth++; - if (is_op(s_rb)) depth--; - next(); if (!depth) break; - } - if (depth) die("unterminated array initializer"); - q[0] = n; return q; -} -word array_string_tokens(word t) { - int count, i = 0, place = tpack(); char* text = tv; word q; - count = rt.len_w(text); - if (t[3] >= 0 && t[3] == count) count--; // the array has no room for the optional terminating zero - q = chk_resize(0, 0, (count + 3) * 6 + 1); q[0] = 1; - q[1] = T_OP; q[2] = s_lb; q[3] = place; - while (i <= count) { - if (i) { q[1 + q[0] * 3] = T_OP; q[2 + q[0] * 3] = s_comma; q[3 + q[0] * 3] = place; q[0]++; } - q[1 + q[0] * 3] = T_NUM; q[2 + q[0] * 3] = text[i]; - q[3 + q[0] * 3] = place; q[0]++; i++; - } - q[1 + q[0] * 3] = T_OP; q[2 + q[0] * 3] = s_rb; q[3 + q[0] * 3] = place; q[0]++; - next(); return q; -} -word array_initializer_tokens(word t) { - if (tk == T_STR || tk == T_BSTR) return array_string_tokens(t); - return array_tokens(); -} -int array_skip_value() { - int depth = 0; - while (tk != T_EOF) { - if (!depth && (is_op(s_comma) || is_op(s_rb))) break; - if (is_op(s_lp) || is_op(s_lk) || is_op(s_lb)) depth++; - if (is_op(s_rp) || is_op(s_rk) || is_op(s_rb)) depth--; - next(); - } - return 0; -} -int ar_flat; word ar_target; -int array_scalar_count(word t) { - if (t[0] != TY_ARR) return 1; - if (t[3] < 0) return 0; - return t[3] * array_scalar_count(t[4]); -} -// a chain [row][column] selects a subarray or scalar; flat positions resume after that subobject. -int array_designator(word t) { - int index, stride; word part = t; - ar_flat = 0; - while (accept(s_lk)) { - index = cexpr(); expect(s_rk); - if (index < 0) { serr("a negative array designator"); index = 0; } - if (part[0] != TY_ARR) { serr("too many indices in array designator"); break; } - if (part[3] >= 0 && index >= part[3]) serr("array designator index is out of range"); - stride = array_scalar_count(part[4]); if (!stride) { serr("array designator needs a complete element type"); stride = 1; } - ar_flat = ar_flat + index * stride; part = part[4]; - } - expect(s_eq); ar_target = part; return 0; -} -word array_shape(word t) { - int flat = 0, count = 0, width, stride, end; word elem = t[4], row, target; - expect(s_lb); - while (tk != T_EOF && !is_op(s_rb)) { - stride = array_scalar_count(elem); if (!stride) stride = 1; - target = elem; - if (is_op(s_lk)) { array_designator(t); flat = ar_flat; target = ar_target; } - if (is_op(s_lb)) { - if (target[0] != TY_ARR) { serr("nested initializer needs an array element type"); array_skip_value(); flat++; } - else { - row = array_shape(target); - if (elem[0] == TY_ARR && elem[3] < 0) { elem = row; stride = array_scalar_count(elem); } - width = array_scalar_count(row); flat = flat + width; - } - } else { array_skip_value(); flat++; } - end = (flat + stride - 1) / stride; if (end > count) count = end; - if (t[3] >= 0 && count > t[3]) serr("too many elements in array initializer"); - if (!accept(s_comma)) break; - } - expect(s_rb); - if (t[3] >= 0) count = t[3]; - return tarr(elem, count); -} -word array_measure(word q, word t) { - word result; - src_push(1, q, 0, 0); next(); result = array_shape(t); src_pop(); - if (treadonly(t)) result = tconst(result); - return result; -} -int array_address(int base, int local, int offset) { - if (local) laddr(base + offset); else lit(base + offset); - return 0; -} -// flatten a scalar position while preserving each packed row's independent word alignment. -int array_store_scalar(word t, int flat, int base, int local, int offset) { - int c, at, stride, bytes, value, address, shift, mask; word elem = t; - while (elem[0] == TY_ARR) { - stride = array_scalar_count(elem[4]); if (!stride) stride = 1; - at = flat / stride; flat = flat % stride; - if (elem[4][0] == TY_ARR) offset = offset + at * tsz(elem[4]); - else { flat = at; elem = elem[4]; break; } - elem = elem[4]; - } - if (isagg(elem)) { serr("aggregate array elements need nested initializer lists"); array_skip_value(); return 0; } - if (local) { - c = expr(); rval(); plainuse(c); fcoerce(elem); - if (!compat(elem, xty)) tcerr("array initializer", elem, xty); - if (tbytes(elem)) { array_address(base, 1, offset); lit(8); op(15); lit(flat * tbytes(elem)); op(O_ADD); pkst(elem); } - else { array_address(base, 1, offset + flat * tsz(elem)); op(O_ST); } - } else { - if (isfl(elem) && tk == T_FLT) { value = tv; next(); if (elem == tf32) value = rt.zf2s(value); } - else { value = cnorm(elem, cexpr()); if (isfl(elem)) { value = vf_i2f(value); if (elem == tf32) value = rt.zf2s(value); } } - bytes = tbytes(elem); - if (bytes) { - address = base + offset + (flat * bytes >> 3) + hdelta; shift = ((flat * bytes) & 7) * 8; - mask = ((1 << (bytes * 8)) - 1) << shift; *address = (*address & ~mask) | ((value << shift) & mask); - } else *(base + offset + flat * tsz(elem) + hdelta) = value; - } - return 0; -} -// globals use constant expressions, locals use ordinary expressions. brace elision walks scalar elements. -int array_suboffset(word t, int flat, word target) { - int offset = 0, stride, at; - while (t != target && t[0] == TY_ARR) { - stride = array_scalar_count(t[4]); if (!stride) stride = 1; - at = flat / stride; flat = flat % stride; offset = offset + at * tsz(t[4]); t = t[4]; - } - return offset; -} -int array_values(word t, int base, int local, int offset) { - int flat = 0, total = array_scalar_count(t), stride = array_scalar_count(t[4]), dest, width; - word target; - expect(s_lb); - while (tk != T_EOF && !is_op(s_rb)) { - target = t[4]; - if (is_op(s_lk)) { array_designator(t); flat = ar_flat; target = ar_target; } - if (flat < 0 || flat >= total) { serr("array initializer index is out of range"); array_skip_value(); } - else if (target[0] == TY_ARR && is_op(s_lb)) { - // the selected subarray starts at its scalar position, converted to an in-place word offset. - dest = array_suboffset(t, flat, target); - array_values(target, base, local, offset + dest); flat = flat + array_scalar_count(target); - } else { array_store_scalar(t, flat, base, local, offset); flat++; } - if (!accept(s_comma)) break; - } - expect(s_rb); return 0; -} -int array_initialize(word q, word t, int base, int local) { - if (local && tsz(t)) { array_address(base, 1, 0); lit(0); lit(tsz(t)); callname(intern("fill_w")); op(O_DROP); } - src_push(1, q, 0, 0); next(); array_values(t, base, local, 0); src_pop(); - return 0; -} -int array_literal_tokens(word q, word t) { - if (!infn) { - int base, cells; t = array_measure(q, t); cells = tsz(t); reserve_code(cells); base = here; - while (cells) { emit(0); cells--; } array_initialize(q, t, base, 0); rt.free(q); - if_value = 0; - xty = t; lv = 0; lvk = LV_NONE; xbor = -1; xaf = 0; xconst = 1; xcval = base; - return C_PLAIN; - } - int i = newloc(intern("<array literal>")), place = tpack(); - t = array_measure(q, t); ltype[i] = t; larr[i] = 1; fslots = fslots + tsz(t); - array_initialize(q, t, lslot[i], 1); - lafact[i] = af_new(t[3], 4, place); laaddr[i] = lafact[i]; if (lafact[i]) lafact[i][5] = i; - laddr(lslot[i]); xty = t; lv = 0; lvk = LV_NONE; xbor = i; xaf = lafact[i]; - xzero = 0; xfloat = 0; xstr = 0; xconst = 0; xvk = 0; rt.free(q); - return C_PLAIN; -} -// a typed growable initializer owns a heap handle, filled from temporary in-place values. -int array_dynamic_literal(word t) { - word q = array_tokens(), shape = array_measure(q, tarr(t[4], -1)); - int slot = fslots, cells = tsz(shape), place = tpack(); fslots = fslots + cells; - if (!infn) serr("a growable array initializer requires function scope"); - array_initialize(q, shape, slot, 1); rt.free(q); - if (tbytes(t[4])) lit(0 - tbytes(t[4])); else lit(tsz(t[4])); - lit(shape[3]); lit(t); callname(s_dnew); vf_effect(); - lit(0); op(O_PICK); lit(2); op(O_ADD); op(O_LD); laddr(slot); lit(cells); callname(intern("copy_w")); op(O_DROP); - xty = t; lv = 0; lvk = LV_NONE; xbor = -1; xaf = af_new(shape[3], 3, place); - xzero = 0; xfloat = 0; xstr = 0; xconst = 0; xvk = 0; xstamp = 1; newsz = -1; newleaf = 0; - return C_FRESH; -} -int array_literal(word t) { word q = array_tokens(); return array_literal_tokens(q, t); } -// statement-bearing braces keep block-expression semantics; expression-only braces are array literals. -int brace_primary() { - word q = array_tokens(); int k = 0, depth = 0, block = 0, c; - while (k < q[0]) { - if (q[1 + k * 3] == T_OP) { - if (q[2 + k * 3] == s_lb) depth++; - if (q[2 + k * 3] == s_rb) depth--; - if (q[2 + k * 3] == s_semi && depth == 1) block = 1; - } - k++; - } - if (!block) return array_literal_tokens(q, tarr(tint, -1)); - src_push(1, q, 0, 0); next(); c = blockexpr(); src_pop(); rt.free(q); return c; -} - -// ---- the expression grammar ---- - -// precedence, loosest to tightest: -// expr (assignment, ?:) -> bin (binary operators by precedence) -> -// unary (prefix operators, casts, recasts) -> postfix (index, field, call, -// ++ --, as) -> primary (literals, names, parentheses, blocks, closures) -// each level leaves its value on the stack and sets the x* globals: xty is the -// type, xfloat / xstr say the value is a float or byte string, xzero and -// xconst mark a literal 0 or constant (xcval holds it), xbor names the local a -// pointer value was borrowed from, and lv / lvk / lvi say the stack holds an -// unloaded address and what it is the address of. -// -// primary: one operand. a number, string, parenthesized expression, block -// expression, closure, or a name. a name is looked up in this order: a local -// of the current function, a variable captured by an enclosing closure, then -// the symbol table (keyword, constant, global, enhancement, function). a local -// the analyzer tracks returns C_VAR + its index so the caller can tell which -// variable the value is. -int primary() { - effect_value = 0; xprojection = 0; if_value = 0; - int n, i, s, c, k, npos, sv; word[8] bs0; int ft, mn, ms; - lv = 0; lvk = LV_NONE; xzero = 0; xbor = -1; - xconst = 0; xfloat = 0; xstr = 0; xlenloc = -1; xstamp = 0; xaf = 0; xvk = 0; - if (tk == T_FLT) { op(OP_FLIT); emit(tv); xvk = 2; xvv = tv; xfloat = 1; xty = tfloat; next(); return C_PLAIN; } - if (tk == T_NUM) { - if (cmode && tv >= 100 && tglue != 3 && !nomagic && (tv & (tv - 1)) && (tv & (tv + 1))) { - magic(tv); - } - lit(tv); xvk = 1; xvv = tv; xzero = tv == 0; xconst = 1; xcval = tv; xty = tint; next(); return C_PLAIN; - } - if (tk == T_BSTR) { xvk = 4; xvv = vf_string(tv); bstrlit(tv); xstr = 1; xty = tbstr; next(); return C_PLAIN; } - if (tk == T_STR) { xvk = 3; xvv = vf_string(tv); strlit(tv); next(); xty = tptr(tchar); return C_PLAIN; } - if (tk == T_OP) { - if (rt.eq_w(tv, "$")) { next(); return quote_tokens(); } - if (tv == s_lp) { next(); c = expr(); expect(s_rp); return c; } - if (tv == s_lb) return brace_primary(); - diefound("expected an expression"); lit(0); xty = tword; return C_PLAIN; - } - if (tk != T_ID) { diefound("expected an expression"); lit(0); xty = tword; return C_PLAIN; } - if (tv == s_fn) { peek(); if (pk_tk == T_OP && (pk_tv == s_lp || pk_tv == s_colon)) return closure(); } - n = tv; callpos = tpack(); - i = findloc(n); - if (i < 0 && nclo) { - k = clocap(n); - if (k >= 0) { - rs_note(RS_LOCAL, 0 - 1); - next(); - i = nclo - 1; - vaddr(clo_env[i]); op(O_LD); lit(k); op(O_ADD); op(O_LD); - i = clo_loc[i * capture_width + k]; - xty = ltype[i]; xfloat = getflt(i); if (isfl(xty)) xfloat = 1; xstr = getstr(i); - lv = 1; lvk = LV_OTHER; - return C_PLAIN; - } - } - if (i >= 0) { - if (if_local) if_value = if_local[i]; - lreadpos[i] = tpack(); - luse(i); effect_note_mask(effect_mask(C_VAR + i), EF_READ); - rs_note(lrskind[i], lrsoff[i]); os_at(toff, i); - xaf = lafact[i]; af_record(toff, xaf); - if (cmode) { xvk = lvkind[i]; xvv = lvvalue[i]; vf_at(rsn - 1, xvk, xvv); } - next(); - if (!fchk && tracked(i)) { - serrn(n, "is tracked by the analyzer and can't be used in unchecked code"); - } - vaddr(i); - xty = ltype[i]; - if (larr[i]) return C_PLAIN; - xfloat = getflt(i); - if (isfl(ltype[i])) xfloat = 1; - xstr = getstr(i); if (xty == tbstr) xstr = 1; - lv = 1; lvk = LV_LOCAL; lvi = i; - if (isalias(i)) { touch(i); xbor = lroot(i); return C_VAR + i; } - if (tracked(i) || lrskind[i] == RS_PARAM || (local_origin_mask && local_origin_mask[i])) return C_VAR + i; - return C_PLAIN; - } - s = source_lookup(n); - if (mstruct && !ginst) { ms = lookup(methname(mstruct, n)); if (ms >= 0 && skind[ms] == K_CONST) s = ms; } - if (s < 0 && rt.eq_w(n, "new")) s = source_lookup(s_new); - if (s >= 0) rs_put(toff, RS_SYM, rs_sym(s), s); - if (s >= 0 && skind[s] == K_KW) { - if (!(sflag[s] & F_EXPR)) dien(n, "can't be used inside an expression"); - return kwcall(s, 1); - } - if (s >= 0 && skind[s] == K_TYPE) { - next(); - if (is_op(s_dot)) { // Struct.name(args): a static function of the struct - c = toff + toklen; - next(); - if (probe_off >= 0 && cmode && !dry && !ginst && incdepth == 0 && probe_off >= c && ((tk == T_ID && probe_off <= toff + toklen) || (tk != T_ID && probe_off <= toff))) { probe_static = n; probe_has = 3; chkabort = 1; } - k = toff; sv = tpack(); mn = name(); - ms = lookup(methname(sname[s], mn)); - if (ms >= 0) rs_put(k, RS_SYM, rs_sym(ms), ms); - if (ms >= 0 && skind[ms] == K_CONST) { // Enum.MEMBER - xvk = 1; xvv = sval[ms]; - lit(sval[ms]); - xty = tint; - if (stype[ms]) xty = stype[ms]; - xzero = sval[ms] == 0; - xconst = 1; - xcval = sval[ms]; - return C_PLAIN; - } - if (ms >= 0 && skind[ms] == K_FN && (sflag[ms] & F_STATIC)) { - if (!is_op(s_lp)) die("a static function is called with ( after its name"); - return call(ms); - } - if (!panic && !dry) { - c = tpack(); tunpack(sv); // the error sits on the member name, where the quick fix replaces it - errat(); rt.put_s(mn); rt.put_s(": is not a static function of this struct"); suggest_member(sname[s], 0, mn, 1); errend(); - tunpack(c); - } - lit(0); - xty = tword; - return C_PLAIN; - } - dien(n, "is a type, not a value"); - lit(0); - xty = tword; - return C_PLAIN; - } - npos = tpack(); - next(); - xty = tword; - if (s >= 0 && skind[s] == K_CONST) { - xvk = 1; xvv = sval[s]; - lit(sval[s]); - xty = tint; - if (stype[s]) xty = stype[s]; - xzero = sval[s] == 0; - xconst = 1; - xcval = sval[s]; - return C_PLAIN; - } - if (s >= 0 && skind[s] == K_GVAR) { - if (cmode) { vf_symbols(); xvk = svkind[s]; xvv = svvalue[s]; vf_at(rsn - 1, xvk, xvv); } - lit(sval[s]); - if (stype[s]) xty = stype[s]; - if (isfl(xty)) xfloat = 1; if (xty == tbstr) xstr = 1; - lv = 1; - lvk = LV_GLOBAL; lvi = s; - return C_PLAIN; - } - if (s >= 0 && skind[s] == K_GARR) { lit(sval[s]); if (stype[s]) xty = stype[s]; return C_PLAIN; } - if (s >= 0 && skind[s] == K_ENH) return enhcall(s); - if (s < 0 && is_op(s_dot)) { - peekat(0); - if (pk_tk == T_ID && lib_file(n)) { - // lib.name before include lib, for a library read from a file: the lexer only joins names of loaded modules - sv = tpack(); tunpack(npos); - errat(); rt.put_s("module access requires `include "); rt.put_s(n); rt.put_s(";`"); errend(); - tunpack(sv); - word link = chk_resize(0, 0, 2); link[0] = missing_imports; link[1] = n; missing_imports = link; - next(); n = methname(n, tv); next(); - } - // Text.itoa with no std: a library's static function, reported as one missing name - else if (pk_tk == T_ID && export_owner(methname(n, pk_tv))) { next(); n = methname(n, tv); next(); } - } - if (s < 0) s = fwdfn_at(n, callpos); - if (is_op(s_lt) && (sflag[s] & F_GEN)) { // f<int, char*>(...) - next(); ngbind = 0; - while (1) { - if (ngbind >= 8) die("too many type arguments"); - gbind[ngbind] = ptype(); - ngbind++; - if (!accept(s_comma)) break; - } - closeangle(); - if (!is_op(s_lp)) { // f<int> alone is that instance, as a function value - rt.fill_w(bs0, 0, 8); rt.copy_w(bs0, gbind, ngbind); ngbind = 0; - ft = stype[s]; - s = ginstance(s, bs0); - fnref(s); - xty = tword; - if (ft) xty = subst(ft, bs0); - return C_PLAIN; - } - } - if (is_op(s_lp)) return call(s); - fnref(s); - if (stype[s]) xty = stype[s]; - return C_PLAIN; -} -// ++ and -- on the address at the top of the stack. post says the old value -// is the result; dlt is +1 or -1. it also updates the loop-bound facts: a -// decrement or a store of an unknown value ends any proof about the variable. -int incdec(int post, int dlt) { - require_mutable(xty); - int t; - if (cmode && !dry) { if (lvk == LV_LOCAL) lvkind[lvi] = 0; else vf_effect(); } xvk = 0; - if (lv && lvk == LV_LOCAL) lafact[lvi] = 0; xaf = 0; - if (!lv) die("++ and -- need something to assign to"); - if (lvk == LV_LOCAL && fchk && tracked(lvi) && !isalias(lvi)) { - cerrn(lvi, "owns an object and can't be incremented"); - } - tcneed(isnum(xty) || xty[0] == TY_PTR || xty == tword, "++ and -- need a number or a pointer", xty); - if (lvk == LV_LOCAL) { - ltag[lvi] = ltag[lvi] | LASG; - setflt(lvi, 0); setstr(lvi, 0); - if (forstep && lvi == ft_iv && dlt == 1) fs_ok = 1; - if (dlt < 0) putfact(lvi, -1, -1); else putfact(lvi, getlo(lvi), -1); - bpoison(lvi); - } - lv = 0; lvk = LV_NONE; xzero = 0; xfloat = 0; xstr = 0; - t = xty; - lit(0); op(O_PICK); - if (ispk(t)) pkld(t); else { dbg_addr(); op(O_LD); } - // a sized integer wraps: the new value is cut to size before it is stored - if (post) { - lit(0); op(O_PICK); lit(dlt); op(O_ADD); - if (tbytes(t)) tnorm(t); - lit(2); op(O_PICK); - if (ispk(t)) pkst(t); else op(O_ST); - } else { - lit(dlt); op(O_ADD); - if (tbytes(t)) tnorm(t); - lit(0); op(O_PICK); lit(2); op(O_PICK); - if (ispk(t)) pkst(t); else op(O_ST); - } - op(O_SWAP); op(O_DROP); - return C_PLAIN; -} -// p.x and p->x: a field of a struct, or of the struct a pointer points at -// the field access after a dot or arrow: c is the class of the left side. a -// field of a typed struct is its offset added to the address. an untyped word -// is searched for the one struct that has that field name (fieldscan). if the -// name is followed by ( it is a method call instead. a pointer-valued field -// of a tracked variable is a borrow of that variable (xbor). -word field(int c) { - int readonly = treadonly(xty); - if_value = 0; - int n, fieldoff, fieldpos, sv; word st; - word borrowed = xaf, base_mask = effect_mask(c); af_use(xaf, tpack(), c); xaf = 0; xvk = 0; int i, b = xbor, k = 0, scanned = 0, ms, de, pmatch = 0; - de = toff + toklen; - next(); - // is the place asked about on this member name (or just after the dot, with none typed) - if (probe_off >= 0 && cmode && !dry && !ginst && incdepth == 0 && probe_off >= de) { - if (tk == T_ID) { if (probe_off <= toff + toklen) pmatch = 1; } - else if (probe_off <= toff) pmatch = 1; - } - fieldpos = tpack(); n = name(); fieldoff = nameoff; - st = xty; - if (st[0] == TY_ARR || st[0] == TY_DYN) { - if (pmatch) probe_member(st); - if (is_op(s_lp) && array_method(n)) { xaf = borrowed; return array_member(c, n); } - if (is_op(s_lp)) { - ms = receiver_method(st, n); - if (ms == -2) serrn(n, "matches more than one receiver method"); - if (ms >= 0) { - rval(); plainuse(c); - if (c >= C_VAR) b = lroot(c - C_VAR); - if (st[0] == TY_ARR && stype[ms][7][0] == TY_DYN) { - int slot = fslots; fslots = fslots + 4; - laddr(slot + 2); op(O_ST); - lit(st[3]); laddr(slot); op(O_ST); lit(st[3]); laddr(slot + 1); op(O_ST); - if (tbytes(st[4])) lit(0 - tbytes(st[4])); else lit(tsz(st[4])); - laddr(slot + 3); op(O_ST); laddr(slot); st = tdyn(st[4], 1); - } - return methcall(ms, st, borrowed, b, base_mask); - } - } - } - rval(); - if (c == C_FRESH) cerr("use a field of a new object only after binding it to a name"); - else if (c >= C_VAR) { touch(c - C_VAR); b = c - C_VAR; } - st = xty; - if (st[0] == TY_PTR) st = st[4]; - if (pmatch) probe_member(st); - if (is_op(s_lp)) { // e.name(...): a method, unless the struct has a field of that name - ms = 0 - 1; - if (st[0] == TY_STRUCT) { if (tfindfield(st, n) < 0) ms = methof(st, n); } - else if (st == tword || isnum(st)) { if (fieldscan(n) == 0) ms = methscan(n); } - if (ms == 0 - 2) { serrn(n, "is a method of more than one struct"); ms = 0 - 1; } - if (ms >= 0) { - return methcall(ms, st, borrowed, b, base_mask); - } - } - if (st[0] != TY_STRUCT && (st == tword || isnum(st))) { - k = fieldscan(n); - if (k == 1) { st = fldst; scanned = 1; } - else if (k > 1) { - if (!panic && !dry) { - errat(); - rt.put_c('.'); - rt.put_s(n); - rt.put_s(" is a field of more than one struct"); - errend(); - } - xty = tword; lv = 1; lvk = LV_OTHER; xbor = -1; return C_PLAIN; - } - } - if (st[0] != TY_STRUCT) { - if (!panic && !dry) { - errat(); - rt.put_c('.'); - rt.put_s(n); - rt.put_s(": that's a "); - tprint(xty); - rt.put_s(", not a struct or a pointer to one"); - errend(); - } - xty = tword; lv = 1; lvk = LV_OTHER; xbor = -1; return C_PLAIN; - } - if (scanned) i = fldi; else i = tfindfield(st, n); - if (i < 0) { - if (!panic && !dry) { - sv = tpack(); tunpack(fieldpos); // the error sits on the field name, where the quick fix replaces it - errat(); tprint(st); rt.put_s(" has no field "); rt.put_s(n); suggest_member(st[2], st, n, 0); errend(); - tunpack(sv); - } - xty = tword; lv = 1; lvk = LV_OTHER; xbor = -1; return C_PLAIN; - } - rs_put(fieldoff, RS_FIELD, field_decl(st, n), st[4][i * 3 + 1]); - k = st[4][i * 3 + 2]; - xty = st[4][i * 3 + 1]; - if (readonly) xty = tconst(xty); - if (k < 0) { // a packed field: the lvalue is a byte address, of the packed twin - lit(8); op(15); - k = 0 - k - 1; - if (k) { lit(k); op(O_ADD); } - xty = tpk(xty); - } else if (k) { lit(k); op(O_ADD); } - if (isref(xty)) { if (b >= 0) xbor = lroot(b); } - else xbor = -1; - if (borrowed && borrowed[2] == 5) xaf = borrowed; - effect_value = base_mask; - xprojection = af_project(borrowed, k, tsz(xty), tpack(), 0); - lv = 1; xzero = 0; - if (b >= 0) { lvk = LV_ELEM; lvi = b; } else lvk = LV_OTHER; - return C_PLAIN; -} -// array indexing, address arithmetic. mulsz scales an index by the element -// size. the addr_ functions build the address of element ix of a growable -// array in the different situations the bounds-check removal produces: the -// stack holds handle and index (addr_stk), the items pointer is cached in a -// hidden o3 variable (addr_cached), or only the handle is available -// (addr_hdr). range_jz is the inline bounds check, one "outside" enhancement -// and a branch to the oob report. -int mulsz(int et) { - if (tsz(et) != 1) { lit(tsz(et)); op(15); } - return 0; -} -// address of an element of a stack array, from the index on the stack. -int addr_stk(int et) { - op(O_SWAP); - lit(2); - op(O_ADD); - op(O_LD); - op(O_SWAP); - mulsz(et); - op(O_ADD); - return 0; -} -// address of an element of an array whose base is cached in a register. -int addr_cached(int ci, int ix, int et) { - vaddr(ci); - op(O_LD); - vaddr(ix); - op(O_LD); - mulsz(et); - op(O_ADD); - return 0; -} -// address of an element of a growable array, reading its header. -int addr_hdr(int al, int ix, int et) { - vaddr(al); op(O_LD); lit(2); op(O_ADD); op(O_LD); - vaddr(ix); op(O_LD); mulsz(et); op(O_ADD); - return 0; -} -// reload the index and the length on each side of the branch, so neither is a stack temporary -int range_jz(int ix, int src, int fld) { - int k, s; - vaddr(ix); op(O_LD); vaddr(src); op(O_LD); if (fld) op(O_LD); - s = lookup(s_outside); op(XOP0 + sval[s]); - k = hole(); op(O_JZ); - vaddr(ix); op(O_LD); vaddr(src); op(O_LD); if (fld) op(O_LD); - callname(s_oob); - setlit(k, here); - return 0; -} -// suffixes after a primary, in a loop: e as T, [index], .field, ->field, -// (call through a value), ++ and --. the index case is the long one. a byte -// string takes an index or a slice. a growable array is checked: through dat() -// normally, but inline when the compiler can prove or cheaply test the bound -// (o3 code with the cache variables, or an index the loop test already -// compared with the length). a plain pointer or fixed array is just base + -// index * size, and a constant index against a known length is checked at -// compile time. -int postfix() { return postfix_tail(primary()); } -int postfix_tail(int c) { - int b, n, t, et; word ft; word[8] bs; int k, ci, was, al, ix, enh, cz, cj, place, offset, temporary, call_owner, readonly; word storage, storage_mask, call_origins; - while (1) { - if (tk == T_ID && tv == s_as) { // e as T: same bits, another type - next(); t = ptype(); rval(); if (!xaf || xaf[2] != 5) xaf = 0; vf_coerce(t, 0); - if (tbytes(t) && !xfloat && !samesz(t, xty)) { tnorm(t); xconst = 0; } // x as u8 cuts the value to size - if_value = 0; - xty = t; xfloat = 0; lvk = LV_NONE; - if (isfl(t)) xfloat = 1; - continue; - } - if (tk != T_OP) return c; - if (tv == s_lk) { - readonly = treadonly(xty); - xvk = 0; - storage = xaf; storage_mask = effect_mask(c); offset = -1; n = -1; place = tpack(); af_use(storage, place, c); - was = xstr; - ci = 0; - if (lv && lvk == LV_LOCAL) lptr[lvi] = 1; - if (o3on && lv && lvk == LV_LOCAL && o3cache[lvi]) ci = o3cache[lvi]; - next(); - rval(); - if (was) { - c = expr(); rval(); plainuse(c); - if (accept(s_colon)) { - c = expr(); rval(); plainuse(c); - expect(s_rk); - callname(s_slice_s); xvk = 0; - lv = 0; lvk = LV_NONE; xty = tbstr; xfloat = 0; xstr = 1; xzero = 0; xbor = -1; - c = C_PLAIN; - continue; - } - expect(s_rk); - callname(s_at_s); xvk = 0; - lv = 0; lvk = LV_NONE; xty = tint; xfloat = 0; xstr = 0; xzero = 0; xbor = -1; - c = C_PLAIN; - continue; - } - b = -1; - if (c == C_FRESH) cerr("index a new object only after binding it to a name"); - else if (c >= C_VAR) { touch(c - C_VAR); b = c - C_VAR; } - if (xty[0] == TY_DYN && tbytes(xty[4])) { // packed elements: the runtime checks and gives the byte address - et = xty[4]; - c = expr(); rval(); plainuse(c); - if (xconst) offset = xcval; af_index(storage, place); - tcneed(isnum(xty) || xty == tword, "an index has to be a number", xty); - expect(s_rk); - callname(intern("datb")); - et = tpk(et); - } else if (xty[0] == TY_DYN) { // a growable array: bounds-checked, through the runtime - et = xty[4]; - al = -1; - if (lvk == LV_LOCAL) al = lvi; - if (ci && !mdbg && lookup(s_outside) >= 0) op(O_DROP); - c = expr(); - ix = -1; - if (lv && lvk == LV_LOCAL) ix = lvi; - rval(); plainuse(c); - if (xconst) offset = xcval; af_index(storage, place); - tcneed(isnum(xty) || xty == tword, "an index has to be a number", xty); - expect(s_rk); - k = lookup(s_outside); - enh = k >= 0 && skind[k] == K_ENH; - if (!mdbg && ix >= 0 && al >= 0 && proven(al, ix)) { // the loop test already compared this index with the length - if (ci) { vaddr(ci); op(O_LD); op(O_SWAP); mulsz(et); op(O_ADD); } - else addr_stk(et); - } else if (ci && !mdbg && enh && ix >= 0) { - op(O_DROP); - range_jz(ix, ci + 1, 0); - addr_cached(ci, ix, et); - } else if (ci && !mdbg && enh) { - lit(0); op(O_PICK); vaddr(ci + 1); op(O_LD); op(XOP0 + sval[k]); - k = hole(); op(O_JZ); - vaddr(ci + 1); op(O_LD); callname(s_oob); - setlit(k, here); - vaddr(ci); op(O_LD); op(O_SWAP); - mulsz(et); op(O_ADD); - } else if (!mdbg && o3on && enh && ix >= 0 && al >= 0) { - op(O_DROP); op(O_DROP); - range_jz(ix, al, 1); - addr_hdr(al, ix, et); - } else if (!mdbg && o3on && enh && ix >= 0) { - op(O_DROP); - vaddr(ix); op(O_LD); - lit(1); op(O_PICK); op(O_LD); op(XOP0 + sval[k]); - k = hole(); op(O_JZ); - vaddr(ix); op(O_LD); lit(1); op(O_PICK); op(O_LD); callname(s_oob); - setlit(k, here); - lit(2); op(O_ADD); op(O_LD); vaddr(ix); op(O_LD); - mulsz(et); op(O_ADD); - } else if (!mdbg && o3on && enh) { - lit(0); op(O_PICK); lit(2); op(O_PICK); op(O_LD); op(XOP0 + sval[k]); - k = hole(); op(O_JZ); - lit(1); op(O_PICK); op(O_LD); callname(s_oob); - setlit(k, here); - op(O_SWAP); lit(2); op(O_ADD); op(O_LD); op(O_SWAP); - mulsz(et); op(O_ADD); - } else callname(s_dat); - } else { - tcneed(isptr(xty) || xty == tword, "only pointers and arrays can be indexed", xty); - et = telem(xty); - n = 0 - 1; if (xty[0] == TY_ARR && xty[3] > 0) n = xty[3]; - xconst = 0; - c = expr(); rval(); plainuse(c); - if (xconst) offset = xcval; if (n < 0) af_index(storage, place); - if (n >= 0 && xconst && (xcval < 0 || xcval >= n)) { - serr("the index is out of range for this array"); - } - tcneed(isnum(xty) || xty == tword, "an index has to be a number", xty); - expect(s_rk); - if (tbytes(et)) { // packed: byte address = base * 8 + index * bytes - if (tbytes(et) != 1) { lit(tbytes(et)); op(15); } - op(O_SWAP); lit(8); op(15); op(O_ADD); - et = tpk(et); - } else { - if (tsz(et) != 1) { lit(tsz(et)); op(15); } - op(O_ADD); - } - } - xprojection = af_project(storage, offset, 1, place, 1); - if (xprojection && n >= 0 && offset >= 0) { xprojection[0] = n - offset; xprojection[13] = 0 - offset; xprojection[14] = 1; } - xaf = 0; if (storage && storage[2] == 5) xaf = storage; - effect_value = 0; if (isptr(et) || et == tword) effect_value = storage_mask; - if_value = 0; - if (readonly) et = tconst(et); - lv = 1; xty = et; xzero = 0; xconst = 0; xvk = 0; xlenloc = -1; xstamp = 0; - if (isref(xty)) { if (b >= 0) xbor = lroot(b); } - else xbor = -1; - if (b >= 0) { lvk = LV_ELEM; lvi = b; } else lvk = LV_OTHER; - c = C_PLAIN; - xfloat = 0; if (isfl(xty)) xfloat = 1; xstr = 0; - continue; - } - if (tv == s_dot || tv == s_arrow) { - xconst = 0; - c = field(c); - xfloat = 0; - if (isfl(xty)) xfloat = 1; - xstr = xty == tbstr; - continue; - } - if (tv == s_lp) { // call through a value - effect_note_mask(effect_mask(c), EF_READ); - rval(); plainuse(c); - ft = 0; - if (xty[0] == TY_FN) ft = xty; - else tcneed(xty == tword, "only functions can be called", xty); - rt.fill_w(bs, 0, 8); - ifcall = -1; effect_argument_base = 0; - int call_tags = 0; if (ft) call_tags = ft[5] >> 6; - n = args(call_tags, ft, bs, o3on); temporary = argtemporary; call_owner = argowner; call_origins = argorigin_mask; - // raw code can live in a heap segment; a closure has an environment block - lit(n); op(O_PICK); callname(intern("callable_code")); - cz = hole(); op(O_JZ); - lit(n); op(O_PICK); op(O_CALL); - cj = hole(); op(O_JMP); - setlit(cz, here); - lit(n); op(O_PICK); lit(3); op(O_ADD); // env: the captures start 3 words into the block - lit(n + 1); op(O_PICK); op(O_LD); // the body's entry, word 0 of the block - op(O_CALL); - setlit(cj, here); - op(O_SWAP); op(O_DROP); - if_value = 0; - lv = 0; lvk = LV_NONE; c = C_PLAIN; xzero = 0; xbor = -1; xconst = 0; - xty = tword; - if (ft) xty = subst(ft[4], bs); - if (ft && (ft[5] & 1)) c = C_FRESH; - else if (isptr(xty) || xty == tword) { effect_value = call_origins; if (isptr(xty)) xbor = call_owner; if (temporary) xaf = af_new(-1, 5, place); } - xfloat = 0; if (isfl(xty)) xfloat = 1; xstr = 0; - continue; - } - if (tv == s_inc) { next(); c = incdec(1, 1); continue; } - if (tv == s_dec) { next(); c = incdec(1, -1); continue; } - return c; - } - return c; -} -// prefix operators: the u*x / d*x / m*x recasts (the star has to touch the -// tag), casts like (int)x and (node*)x, dereference, address-of, prefix ++ and -// --, and any unary operator a program added (- ! ~ built in). -int unary() { - int c, un, t, place, constant, value; word storage, dependency; - if (tk == T_ID && (tv == s_u || tv == s_m || tv == s_d)) { // u*x, d*x, m*x: recast only when * is glued - peek(); - if (pk_tk == T_OP && pk_tv == s_star && pq_gl[0]) { - t = S_M; if (tv == s_u) t = S_U; if (tv == s_d) t = S_D; - next(); expect(s_star); - c = unary(); place = -1; if (lvk == LV_LOCAL) place = lvi; rval(); - lvk = LV_NONE; - if (c < C_VAR && c != C_FRESH && place >= 0 && fchk) { cerrn(place, "isn't an owned object, so there is no ownership to recast"); return c; } - c = recast(c, t); - if (t == S_M && c >= C_VAR && lstate[c - C_VAR] == S_M && lown[c - C_VAR]) xlend = c - C_VAR; - return c; - } - } - if (tk == T_OP && tv == s_lp && caststart()) { // (int)x, (node*)x: same bits, the type changes - next(); - t = ptype(); - expect(s_rp); - if (t[0] == TY_ARR && is_op(s_lb)) return postfix_tail(array_literal(t)); - if (t[0] == TY_DYN && is_op(s_lb)) return postfix_tail(array_dynamic_literal(t)); - c = unary(); dependency = effect_mask(c); rval(); plainuse(c); effect_value = dependency; if (!xaf || xaf[2] != 5) xaf = 0; vf_coerce(t, 1); - if (isfl(t)) { if (!xfloat) { op(OP_I2F); xfloat = 1; } if (t == tf32) { xenh("zf2s"); xenh("zs2f"); } } - else if (xfloat && isnum(t)) { op(OP_F2I); xfloat = 0; } - else xfloat = 0; - if (tbytes(t) && !samesz(t, xty)) { tnorm(t); xconst = 0; } // (u8)x cuts the value to size - if_value = 0; - xty = t; lv = 0; lvk = LV_NONE; xzero = 0; xbor = -1; - return C_PLAIN; - } - if (tk == T_OP) { - if (tv == s_star) { - place = tpack(); next(); c = unary(); dependency = effect_mask(c); - storage = xaf; af_use(xaf, place, c); xaf = 0; if (storage && storage[2] == 5) xaf = storage; xvk = 0; - if (lv && lvk == LV_LOCAL) lptr[lvi] = 1; - rval(); plainuse(c); - tcneed(xty[0] == TY_PTR || xty == tword, "only pointers can be dereferenced", xty); - if_value = 0; - xty = telem(xty); effect_value = 0; if (isptr(xty) || xty == tword) effect_value = dependency; - if (tbytes(xty)) { lit(8); op(15); xty = tpk(xty); } // the first packed element: a byte address - lv = 1; - lvk = LV_OTHER; - xzero = 0; - xbor = -1; - xfloat = 0; - if (isfl(xty)) xfloat = 1; - xstr = 0; - return C_PLAIN; - } - if (tv == s_amp) { - place = tpack(); next(); c = unary(); - storage = xprojection; if (xaf && xaf[2] == 5) storage = xaf; - if (lv && lvk == LV_LOCAL) { - if (cmode && !dry) { lvescaped[lvi] = 1; lvkind[lvi] = 0; } - storage = laaddr[lvi]; - if (!storage) { int storage_kind = 1; if (local_cell[lvi]) storage_kind = 2; storage = af_new(1, storage_kind, place); laaddr[lvi] = storage; if (storage) storage[5] = lvi; } - af_forget(); - } - else if (isagg(xty)) storage = 0; - if (!lv && !isagg(xty)) die("& needs something addressable"); - if (lv && ispk(xty)) serr("an element of a sized-integer array has no address; it is a byte inside a word"); - if (lv && lvk == LV_LOCAL && (ltag[lvi] & 16)) serr("an o3 variable has no address"); - if (lv && lvk == LV_LOCAL) ltag[lvi] = ltag[lvi] | LREAD | LASG; - if (c >= C_VAR) touch(c - C_VAR); - if (lvk == LV_ELEM && lvi >= 0) xbor = lroot(lvi); - lv = 0; lvk = LV_NONE; xzero = 0; xfloat = 0; xstr = 0; - if_value = 0; - xty = tptr(xty); xaf = storage; xvk = 0; - return C_PLAIN; - } - if (tv == s_inc) { next(); unary(); return incdec(0, 1); } - if (tv == s_dec) { next(); unary(); return incdec(0, -1); } - un = unfind(tv); - if (un >= 0) { - next(); c = unary(); rval(); plainuse(c); - constant = xconst && !xfloat && uname[un] == s_minus; value = xcval; xaf = 0; - if (uname[un] == s_minus && xvk == 1) xvv = 0 - xvv; - else if (uname[un] == s_minus && xvk == 2) xvv = vf_negf(xvv); - else xvk = 0; - if (un <= 1) ftest(); - if (uname[un] == s_minus && xfloat) { - op(OP_FLIT); - emit(0); - op(O_SWAP); - op(OP_FSUB); - xty = tfloat; - } - else { - emitseq(useq[un], ulen[un]); xfloat = 0; - if (un <= 1) xty = tbool; - else if (xty != tword && !isfl(xty)) { tcneed(isnum(xty), "this operator needs a number", xty); xty = tint; } - } - if (un <= 1) if_value = 0; - xstr = 0; - xconst = constant; if (constant) xcval = 0 - value; xlenloc = -1; xstamp = 0; - lv = 0; lvk = LV_NONE; xzero = 0; xbor = -1; - return C_PLAIN; - } - } - return postfix(); -} -// binary operators by precedence climbing: parse an operand, then while the -// next token is a binary operator of at least minp, parse its right side at one -// level tighter and emit its code sequence from the operator table. && and || -// short-circuit with jumps; floats get their own opcodes; a * or / by a -// constant that is a power of two gets a shift hint in check mode. -int if_comparison(int name) { - return name == s_eqeq || name == s_ne || name == s_lt || name == s_gt || name == s_le || name == s_ge; -} -int bin(int minp) { - int c = unary(), b, p, a, j, lt, lb, rb, src, lf, rf, ls, rs, fresh, top = 0, il = -1, opp; word borrowed, rightborrow, left_mask, right_mask, infer_left, infer_right; - if (fortest == 1) { fortest = 2; top = 1; ft_nops = 0; } - while (tk == T_OP) { - b = binfind(tv); - if (b < 0) return c; - p = bprec[b]; - if (p < minp) return c; - lb = -1; - if (c >= C_VAR && tracked(c - C_VAR)) lb = lroot(c - C_VAR); else lb = xbor; - borrowed = xaf; left_mask = effect_mask(c); lf = xfloat; ls = xstr; fresh = 0; - il = -1; - if (top && lv && lvk == LV_LOCAL) il = lvi; - infer_left = if_value; infer_right = 0; - opp = tpack(); - next(); rval(); plainuse(c); - lt = xty; - if (isfl(lt)) lf = 1; - src = -1; - if (blen[b] == -1) { // && : 0 if the left side is 0, else the right side as 0 or 1 - af_forget(); - ftest(); - a = hole(); op(O_JZ); - c = bin(p + 1); rval(); plainuse(c); - ftest(); - emitseq(useq[1], ulen[1]); // !! normalizes - j = hole(); op(O_JMP); - setlit(a, here); lit(0); setlit(j, here); - xfloat = 0; xstr = 0; - } else if (blen[b] == -2) { // || : 1 if the left side isn't 0, else the right side as 0 or 1 - af_forget(); - ftest(); - a = hole(); op(O_JZ); - lit(1); j = hole(); op(O_JMP); - setlit(a, here); - c = bin(p + 1); rval(); plainuse(c); - ftest(); - emitseq(useq[1], ulen[1]); - setlit(j, here); - xfloat = 0; xstr = 0; - } else { - c = bin(p + 1); infer_right = if_value; rightborrow = xaf; right_mask = effect_mask(c); - rb = -1; - if (c >= C_VAR && tracked(c - C_VAR)) rb = lroot(c - C_VAR); else rb = xbor; - if (top) { ft_ir = -1; if (lv && lvk == LV_LOCAL) ft_ir = lvi; } - rval(); plainuse(c); - if (cmode && bname[b] == s_star && xconst && xcval >= 2 && !(xcval & (xcval - 1)) && !lf) { - shifthint(opp, xcval); - } - rf = xfloat; rs = xstr; - if (isfl(xty)) rf = 1; - if ((ls || rs) && (bname[b] == s_plus || bname[b] == s_eqeq || bname[b] == s_ne || bname[b] == s_lt || bname[b] == s_gt || bname[b] == s_le || bname[b] == s_ge)) { - if (!(ls && rs)) serr("a byte string needs a byte string on the other side"); - if (bname[b] == s_plus) { callname(s_cat_s); xstr = 1; xfloat = 0; } - else if (bname[b] == s_eqeq) { callname(s_eq_s); xstr = 0; xfloat = 0; } - else if (bname[b] == s_ne) { callname(s_eq_s); lit(1); op(18); xstr = 0; xfloat = 0; } - else if (bname[b] == s_lt) { callname(s_lt_s); xstr = 0; xfloat = 0; } - else if (bname[b] == s_gt) { op(O_SWAP); callname(s_lt_s); xstr = 0; xfloat = 0; } - else if (bname[b] == s_le) { callname(s_le_s); xstr = 0; xfloat = 0; } - else { op(O_SWAP); callname(s_le_s); xstr = 0; xfloat = 0; } - } else if ((lf || rf) && isfop(bname[b])) { femit(bname[b], lf, rf); xstr = 0; } - else { emitseq(bseq[b], blen[b]); xfloat = 0; xstr = 0; } - if (bname[b] == s_plus || bname[b] == s_minus) { - if ((!borrowed || borrowed[2] != 5) && rightborrow && rightborrow[2] == 5) borrowed = rightborrow; - if (lb >= 0) src = lb; else src = rb; - if (bname[b] == s_minus && lb >= 0 && rb >= 0) src = -1; // pointer difference is a distance - } - } - if (blen[b] < 0) af_forget(); - if (xstr) xty = tbstr; else xty = bintype(b, lt, xty); - if_value = 0; - if (blen[b] > 0 && !if_comparison(bname[b]) && !xstr && !lf && !rf && (xty == tint || xty == tword) && (infer_left || infer_right)) { - if_link(infer_left, infer_right); - if_value = infer_left; if (!if_value) if_value = infer_right; - if_join(if_value, xty); - } - if (src >= 0 && xty[0] != TY_PTR && xty != tword) src = -1; - xbor = src; - if (top) { - ft_nops++; - if (ft_nops > 1) ft_iv = -1; - else if (bname[b] == s_lt && blen[b] > 0 && il >= 0 && ft_ir >= 0 && !lf && !rf && !ls && !rs) { ft_iv = il; ft_bd = ft_ir; } - } - lv = 0; lvk = LV_NONE; c = C_PLAIN; xzero = 0; xconst = 0; xlenloc = -1; xstamp = 0; xaf = 0; xvk = 0; - effect_value = 0; if (isptr(xty) || xty == tword) effect_value = left_mask | right_mask; - if (fresh) c = C_FRESH; - if (borrowed && borrowed[2] == 5 && (isptr(xty) || xty == tword) && (bname[b] == s_plus || bname[b] == s_minus)) xaf = borrowed; - } - return c; -} -// ---- assignment, conditionals and blocks as expressions ---- - -int storekeep(int t) { - if (mdbg && !dry && !ispk(t)) { lit(1); op(O_PICK); callname(s_check); op(O_DROP); } - op(O_SWAP); lit(1); op(O_PICK); op(O_SWAP); - if (ispk(t)) pkst(t); else op(O_ST); - return 0; -} -// c ? a : b. both sides are analyzed from the same starting state (a snapshot, -// restored for the second side) and then joined, so a variable can't be given -// away on one side only. both sides are fresh objects, or neither is. -int ternary(int c) { - int a, b, s0, sa, livea, live0 = live, n0 = nloc, ca, cb, ta, ba, bb; word temp_a, temp_b, mask_a, mask_b; - rval(); plainuse(c); - ftest(); - next(); - a = hole(); op(O_JZ); - s0 = snap_push(); - ca = expr(); rval(); ta = xty; ba = xbor; temp_a = xaf; mask_a = effect_mask(ca); - if (ca != C_FRESH) plainuse(ca); - livea = live; sa = snap_push(); - b = hole(); op(O_JMP); - expect(s_colon); - setlit(a, here); - snap_restore(s0); live = live0; - cb = expr(); rval(); bb = xbor; temp_b = xaf; mask_b = effect_mask(cb); - if (cb != C_FRESH) plainuse(cb); - join2(sa, livea, b, live, n0, "ends the two sides of ?: in different states"); - snap_pop(s0); - if (isfl(ta) && !isfl(xty)) { op(OP_I2F); xfloat = 1; xty = tfloat; } - if (!compat(ta, xty) && !compat(xty, ta)) tcerr("the two sides of ?:", ta, xty); - if (ta != tword && !isfl(ta)) xty = ta; - else if (isfl(ta) || isfl(xty)) { xty = tfloat; xfloat = 1; } - if_value = 0; - effect_value = mask_a | mask_b; - if (ba != bb) xbor = -1; - if (temp_a && temp_a[2] == 5) xaf = temp_a; else if (temp_b && temp_b[2] == 5) xaf = temp_b; - lv = 0; lvk = LV_NONE; xzero = 0; xvk = 0; - if (ca == C_FRESH && cb == C_FRESH) return C_FRESH; - if (ca == C_FRESH || cb == C_FRESH) cerr("both sides of ?: have to be new objects, or neither"); - return C_PLAIN; -} -// the top of the expression grammar: a binary expression, then possibly ?: or -// an assignment. plain = stores the value and leaves it on the stack; the -// analyzer's assigncheck decides what the store means for ownership (a fresh -// object makes the target an owner, a u object moves up a block, and so on). -// compound assignment (+= etc.) loads the old value, applies the operator -// sequence from the table and stores the result. an o3 variable is stored with -// the value first and the address after, so no address waits on the stack -// across a branch. -int expr() { - int c = bin(1), k, i, a, rc, lt, lf, rf, fk; - if (tk != T_OP) return c; - if (tv == s_q) return ternary(c); - if (tv == s_eq) { - if (!lv) die("this can't be assigned to"); - k = lvk; i = lvi; lt = xty; require_mutable(lt); - if (isagg(lt)) serr("a whole struct or array can't be assigned; assign its fields"); - next(); - a = o3on && k == LV_LOCAL && (ltag[i] & 16) && !larr[i]; - if (a) op(O_DROP); // an o3 variable: its address comes after the value, so it never waits on the stack across a branch - xlend = -1; - rc = expr(); rval(); - if (k != LV_LOCAL) af_escape(xaf); - fcoerce(lt); - if (k == LV_LOCAL) { - ltag[i] = ltag[i] | LASG; - setflt(i, xfloat); - setstr(i, xstr); - note_store(i); af_store(i); vf_store(i); - } - if (cmode && !dry && k == LV_GLOBAL) { vf_symbols(); svkind[i] = xvk; svvalue[i] = xvv; } - else if (k != LV_LOCAL) vf_effect(); - if (!compat(lt, xty)) tcerr("assignment", lt, xty); - if (a) { lit(0); op(O_PICK); vaddr(i); op(O_ST); o3refresh(i); } - else { - if (o3on && k != LV_ELEM && hasptr(lt)) { lit(lt); callname(s_fatten); } // o3 data going somewhere normal code can see - storekeep(lt); - } - lv = 0; lvk = LV_NONE; xty = lt; xzero = 0; - return assigncheck(k, i, rc); - } - a = asgfind(tv); - if (a >= 0) { - if (!lv) die("this can't be assigned to"); - if (lvk == LV_LOCAL && fchk && tracked(lvi) && !isalias(lvi)) { - cerrn(lvi, "owns an object; compound assignment would lose it"); - } - lt = xty; require_mutable(lt); - fk = -1; lf = xfloat; - if (lvk == LV_LOCAL) fk = lvi; - if (isfl(lt)) lf = 1; - next(); - i = o3on && lvk == LV_LOCAL && (ltag[lvi] & 16) && !larr[lvi]; - if (i) { i = lvi; op(O_DROP); vaddr(i); op(O_LD); i = i + 1; } - else if (ispk(lt)) { lit(0); op(O_PICK); pkld(lt); } - else { lit(0); op(O_PICK); op(O_LD); } - lv = 0; - rc = expr(); rval(); plainuse(rc); - rf = xfloat; - if ((lf || rf) && isfop(bname[abin[a]])) femit(bname[abin[a]], lf, rf); - else { emitseq(bseq[abin[a]], blen[abin[a]]); xfloat = 0; } - fcoerce(lt); - if (fk >= 0) { - lafact[fk] = 0; xaf = 0; if (cmode && !dry) lvkind[fk] = 0; - ltag[fk] = ltag[fk] | LASG; - setflt(fk, xfloat); setstr(fk, 0); - if (forstep && fk == ft_iv && bname[abin[a]] == s_plus && xconst && xcval == 1) fs_ok = 1; - if (bname[abin[a]] == s_plus && xconst && xcval > 0) putfact(fk, getlo(fk), -1); - else putfact(fk, -1, -1); - bpoison(fk); - } - if (fk < 0) vf_effect(); xvk = 0; - k = bintype(abin[a], lt, xty); - if (!compat(lt, k)) tcerr("compound assignment", lt, k); - if (i) { lit(0); op(O_PICK); vaddr(i - 1); op(O_ST); } else storekeep(lt); - lv = 0; lvk = LV_NONE; xty = lt; xzero = 0; - return C_PLAIN; - } - return c; -} -// does the current token start a statement rather than an expression? -// a block as an expression: { stmts; tail }. it opens a scope like any block, -// so its variables are freed when it closes, and the tail is how a u object -// leaves the block with a value (handup). -word stmtstart() { - int s; - if (tk == T_OP) return tv == s_semi || tv == s_lb || tstart(); - if (tk != T_ID || findloc(tv) >= 0) return 0; - if (tstart() || typedef_ahead()) return 1; - s = lookup(tv); - return s >= 0 && skind[s] == K_KW && !(sflag[s] & F_EXPR); -} -// { stmts; tail } as an expression: its value is the tail, or 0 -int blockexpr() { - int save = nloc, c = C_PLAIN, tail = 0, t = tint; - next(); - fdepth++; bxdepth++; - while (!is_op(s_rb)) { - if (tk == T_EOF) { die("missing }"); break; } - if (stmtstart()) { stmt(); continue; } - c = expr(); rval(); - if (accept(s_semi)) { op(O_DROP); discard(c); c = C_PLAIN; continue; } - if (!is_op(s_rb)) die("expected ; or } after an expression"); - tail = 1; t = xty; - } - next(); - if (tail) c = handup(c); else { lit(0); xbor = -1; xfloat = 0; xstr = 0; xvk = 1; xvv = 0; } - if (xbor >= save) xbor = -1; // that local dies with this block; the pointer leaves untracked - block_close(save); - fdepth--; bxdepth--; - lv = 0; lvk = LV_NONE; xty = t; xzero = 0; - return c; -} - -// ---- constant expressions ---- - -// constant expressions, evaluated while compiling: numbers, named constants, -// addresses of globals and functions, unary minus and ~, parentheses, and the -// operators + - * | & << >>. used for array lengths, enum values, const -// initializers and asm operands. -word cprim() { - int v, s, mc; - if (tk == T_NUM) { v = tv; next(); return v; } - if (accept(s_minus)) return 0 - cprim(); - if (tk == T_OP && tv == uname[2]) { next(); return ~cprim(); } - if (accept(s_lp)) { v = cexpr(); expect(s_rp); return v; } - if (tk == T_ID) { - s = lookup(tv); - if (mstruct && !ginst) { mc = lookup(methname(mstruct, tv)); if (mc >= 0 && skind[mc] == K_CONST) s = mc; } - if (s >= 0 && (skind[s] == K_CONST || skind[s] == K_GARR || (skind[s] == K_FN && (sflag[s] & F_DEF)) || skind[s] == K_GVAR)) { - next(); - return sval[s]; - } - if (s >= 0 && skind[s] == K_TYPE) { // Enum.MEMBER in a constant expression - peekat(0); - if (pk_tk == T_OP && pk_tv == s_dot) { - peekat(1); - if (pk_tk == T_ID) { - mc = lookup(methname(tv, pk_tv)); - if (mc >= 0 && skind[mc] == K_CONST) { next(); next(); next(); return sval[mc]; } - } - } - } - } - die("expected a constant"); - return 0; -} -// compile an expression: a primary, then the operators that follow it. -int cexpr() { - int v = cprim(), o; - while (tk == T_OP) { - o = tv; - if (o == bname[3]) { next(); v = v + cprim(); } - else if (o == bname[4]) { next(); v = v - cprim(); } - else if (o == bname[0]) { next(); v = v * cprim(); } - else if (o == bname[15]) { next(); v = v | cprim(); } - else if (o == bname[13]) { next(); v = v & cprim(); } - else if (o == bname[5]) { next(); v = v << cprim(); } - else if (o == bname[6]) { next(); v = v >> cprim(); } - else return v; - } - return v; -} - -// ---- statements ---- - -// after a syntax error: skip to the end of the statement -// statements. stmt is the dispatcher: a keyword statement (if, while, return, -// a parse word...), a declaration, an empty statement, a block, or an -// expression statement whose value is dropped. after a syntax error panic is -// set and stmt calls resync to skip to the next ; or }. -int resync() { - while (tk != T_EOF && !is_op(s_semi) && !is_op(s_rb)) next(); - if (is_op(s_semi)) next(); - panic = 0; - return 0; -} -// compile one statement. -int stmt() { - word before = borrow_slots; int result; emit_statement_hooks(); result = stmt_inner(); borrow_cleanup_since(before); return result; -} -int stmt_inner() { - int s, c; - if (probe_off >= 0) probe_stmt(); - if (panic) return resync(); - if (tk == T_ID && findloc(tv) < 0) { - s = lookup(tv); - if (s >= 0 && skind[s] == K_KW && (sflag[s] & F_STMT)) { kwcall(s, 0); return 0; } - } - if (untyped_const()) { next(); localdecl(2); return 0; } - if (typedef_ahead() && findloc(tv) < 0) return kw_typedef(); - if (tstart()) { localdecl(1); return 0; } - if (tk == T_OP) { - if (tv == s_semi) { next(); return 0; } - if (tv == s_lb) { block(); return 0; } - } - c = expr(); rval(); op(O_DROP); - expect(s_semi); - discard(c); - return 0; -} -// compile a block, with its own scope for the locals it declares. -int block() { - int save = nloc; - expect(s_lb); - fdepth++; - while (!is_op(s_rb)) { if (tk == T_EOF) { die("missing }"); break; } stmt(); } - if (probe_off >= 0 && !chkabort && !dry && !ginst && incdepth == 0 && ctxsp <= nclo && toff >= probe_off) { probe_snap(); chkabort = 1; } - block_close(save); // before the }, so its line is the one reported - next(); - fdepth--; - return 0; -} -// the body of if/while/for: its own block, even without braces -// (the body of if/while/for is always its own block, even without braces, so -// variables declared in it die with it) -int substmt() { - int save = nloc; - if (is_op(s_lb)) return block(); - fdepth++; - stmt(); - block_close(save); - fdepth--; - return 0; -} -// let a = e, b[10]; or, typed: int a = 1, b; node*.m p = new(node); .u r; -// let infers pointer, struct and function types from the initializer and -// keeps numbers as plain words. -// (each declared name gets a slot; an initializer is compiled and stored, and -// assigncheck gives the new local its analyzer state. an initializer that is a -// constant number marks the local as a const candidate for the hints, and a -// new records the size and leaf-ness for the matching recycle.) -// the type of each untyped local while the language server checks a document: -// the byte offset of its name in the document, and the type's text in ltxt -// (one character per word). the server turns it into the hover of the local. -// record a parsed type expression so arrays, stars, and function types have hovers. -int ht_note(int start, int end, word t) { - if (!lt_on || isp != 0 || dry || !t || start < 0 || end <= start) return 0; - if (!ht_start || ht_n == htcap) { - if (ht_start) htcap = htcap * 2; - ht_start = chk_resize(ht_start, ht_n, htcap); - ht_end = chk_resize(ht_end, ht_n, htcap); - ht_type = chk_resize(ht_type, ht_n, htcap); - } - ht_start[ht_n] = start; ht_end[ht_n] = end; ht_type[ht_n] = t; ht_n++; - return 0; -} -// record the type of a local at byte offset o for the language server. -int lt_note(int o, int t) { - int sm, sb, sn, sc, sl, need; - if (!lt_on || isp != 0 || dry || o < 0 || !t) return 0; - if (!lt_off || lt_n == ltcap) { - if (lt_off) ltcap = ltcap * 2; - lt_off = chk_resize(lt_off, lt_n, ltcap); lt_at = chk_resize(lt_at, lt_n, ltcap); - lt_len = chk_resize(lt_len, lt_n, ltcap); lt_type = chk_resize(lt_type, lt_n, ltcap); - } - sm = rt.outmode; sb = rt.outbuf; sn = rt.outn; sc = rt.outcap; - rt.outmode = 3; rt.outn = 0; tprint(t); need = ltxn + rt.outn; - if (!ltxt || need > ltxtcap) { - while (need > ltxtcap) ltxtcap = ltxtcap * 2; - ltxt = chk_resize(ltxt, ltxn, ltxtcap); - } - rt.outmode = 2; rt.outbuf = ltxt; rt.outn = ltxn; rt.outcap = ltxtcap; - tprint(t); - sl = rt.outn; - rt.outmode = sm; rt.outbuf = sb; rt.outn = sn; rt.outcap = sc; - lt_off[lt_n] = o; lt_at[lt_n] = ltxn; lt_len[lt_n] = sl - ltxn; lt_type[lt_n] = t; lt_n++; - ltxn = sl; - return 0; -} -// compile a local declaration, typed or let-style. -// does `const name =` start here? a const with no type takes the initializer's type. -int untyped_const() { - if (tk != T_ID || tv != s_const) return 0; - peekat(0); if (pk_tk != T_ID || istypename(pk_tv)) return 0; - peekat(1); return pk_tk == T_OP && (pk_tv == s_eq || pk_tv == s_semi || pk_tv == s_comma); -} -// typed is 1 after a type, 0 after let, and 2 after an untyped const. -int localdecl(int typed) { - int n, i, c, k, t = 0, tg = 0, pp, kc, kp = ifkp, gty = 0, gok = !typed && ifon && !dry, o, vr, ro = typed == 2; word iq, it; - if (ro) typed = 0; - if (typed) { dtype(); t = dty; tg = dtag; if (t == tvoid) serr("a variable can't be void"); } - if ((o3on || o3one) && !typed) serr("o3 variables need a type"); - while (1) { - pp = tpack(); - o = toff; - rs_note(RS_LOCAL, o); vr = rsn - 1; - n = name(); - i = newloc(n); - lrsoff[i] = o; - ltag[i] = tg | LDECL; lpos[i] = pp; lnsz[i] = 0; lleaf[i] = 0; - if (o3on || o3one) ltag[i] = ltag[i] | 16; - if (accept(s_lk)) { - k = cexpr(); expect(s_rk); - if (!t) t = tword; - larr[i] = 1; ltype[i] = tarr(t, k); fslots = fslots + tsz(ltype[i]); - if (gok) { if (!gty) gty = tint; else if (gty != tint) gok = 0; } - } else if (t && isagg(t)) { larr[i] = 1; ltype[i] = t; fslots = fslots + tsz(t); } - else { fslots++; if (t) ltype[i] = t; if (isfl(t)) setflt(i, 1); if (o3on) { o3promote(i); o3mkcache(i); } } - capture_cell(i, 0); - if (accept(s_eq)) { - if (larr[i] && ltype[i][0] == TY_ARR && (is_op(s_lb) || tk == T_STR || tk == T_BSTR)) { - iq = array_initializer_tokens(ltype[i]); it = array_measure(iq, ltype[i]); - fslots = fslots + tsz(it) - tsz(ltype[i]); - ltype[i] = it; array_initialize(iq, it, lslot[i], 1); rt.free(iq); - } else { - if (larr[i]) serr("an array or a struct needs an initializer list"); - newsz = -1; newleaf = 0; if (tk == T_NUM) nomagic = 1; - if (t && t[0] == TY_DYN && is_op(s_lb)) c = array_dynamic_literal(t); else c = expr(); - rval(); nomagic = 0; kc = xconst && !xfloat; - if (kc) ltag[i] = ltag[i] | LCONST; - if (c == C_FRESH) { lnsz[i] = newsz; lleaf[i] = newleaf; } - if (t) fcoerce(t); - vaddr(i); - op(O_ST); - o3refresh(i); - setflt(i, xfloat); - setstr(i, xstr); - note_store(i); af_store(i); vf_store(i); - if (t) { if (!compat(t, xty)) tcerr("initializer", t, xty); } - else if (!isnum(xty)) { - if (xty[0] == TY_ARR) ltype[i] = decay(xty); - else if (xty[0] == TY_STRUCT) ltype[i] = tptr(xty); - else ltype[i] = xty; - } - assigncheck(LV_LOCAL, i, c); - if (ro) { k = ltype[i]; if (!k) { k = tword; if (isnum(xty)) k = xty; } ltype[i] = tconst(k); } - if (gok) { k = xty; if (isagg(k)) k = ltype[i]; if (!ifpure(k) || (gty && gty != k)) gok = 0; else gty = k; } - } - } else if (ro) serr("a const needs an initializer"); - else if (gok && !larr[i]) gok = 0; // no initializer: nothing says what it is - if (larr[i] && ltype[i][0] == TY_ARR && ltype[i][3] < 0) serr("T[] needs an initializer to determine its length"); - if (larr[i]) { k = 1; if (ltype[i][0] == TY_ARR) k = ltype[i][3]; lafact[i] = af_new(k, 1, pp); laaddr[i] = lafact[i]; if (lafact[i]) lafact[i][5] = i; } - af_declaration(o, lafact[i]); os_at(o, i); - if (cmode) vf_at(vr, lvkind[i], lvvalue[i]); - kc = tword; - if (ltype[i]) kc = ltype[i]; - lt_note(((pp >> 40) & 0x1FFFFF) - 1, kc); - if (!accept(s_comma)) break; - } - expect(s_semi); - if (gok && gty && kp) fact_add(kp, 0 - 1, gty, ""); - return 0; -} -// compile a variable declaration. -int kw_var() { - stmtonly(); - ifkp = 0; - if (isp == 0) ifkp = tpack(); // only the document itself: an include has other offsets - next(); - return localdecl(0); -} -// if / else. the shape of every branching construct is the same: snapshot the -// analyzer state, compile one path, restore the snapshot, compile the other, -// then join the two end states (join2). a path that does not reach the join -// (it returned or broke) is left out. without an else the "other path" is -// the state before the branch. -int kw_if() { - int a, b, c, s0, sa, live0, livea, n0 = nloc, nt, cj; - stmtonly(); - next(); - expect(s_lp); c = borrow_condition(); expect(s_rp); - a = hole(); op(O_JZ); - live0 = live; - s0 = snap_push(); - substmt(); - livea = live; sa = snap_push(); - if (is_id(s_else)) { - next(); - b = hole(); op(O_JMP); - setlit(a, here); - snap_restore(s0); live = live0; - substmt(); - join2(sa, livea, b, live, n0, "ends the branches of an if in different states"); - } else { - // the path that skips the then branch still has the states from before it - njps = 0; if (livea) jadd(R_SNAP + sa); if (live0) jadd(R_SNAP + s0); - join(n0, "ends the branches of an if in different states"); - if (livea) fixups(R_SNAP + sa, n0, 1); - nt = 0; if (live0) nt = fixups(R_SNAP + s0, n0, 0); - if (nt) { - cj = hole(); - op(O_JMP); - setlit(a, here); - fixups(R_SNAP + s0, n0, 1); - setlit(cj, here); - } - else setlit(a, here); - live = livea || live0; - } - snap_pop(s0); - return 0; -} -// loop frame: 0 continue target (-1 for a switch) 2 depth 3 snapshot at the top -// 4 block-expression depth 5 locals at the start 6 its first exit record -// loops and switches share a frame stack (R_LOOP, 8 words each). a break adds -// an exit record (the analyzer state at that point) with a jump to patch; -// loop_end joins all of them with the normal fall-out and patches the jumps. -int loop_push(int cont, int s0) { - word f; - reserve_R_LOOP((lsp + 1) * 8); - if (lsp >= loopfacts_cap) { lpfact = chk_resize(lpfact, loopfacts_cap, loopfacts_cap * 2); loopfacts_cap = loopfacts_cap * 2; } - f = R_LOOP + lsp * 8; - f[0] = cont; - f[1] = 0; - f[2] = fdepth; - f[3] = s0; - f[4] = bxdepth; - f[5] = nloc; - f[6] = extop; - f[7] = 0; - lpfact[lsp] = -1; - lsp++; - return 0; -} -// the end of a loop or switch: join its exits (the fall-out path sc through -// the jump at a, if there is one, and every break) and lay out each path's -// fixups on its way to the end -int loop_end(int a, int sc) { - word f, p; int e, n0, outs = 0; - lsp--; - f = R_LOOP + lsp * 8; - n0 = f[5]; - njps = 0; - if (sc >= 0) jadd(R_SNAP + sc); - p = R_EXIT + f[6]; e = R_EXIT + extop; - while (p < e) { jadd(p + 1); p = p + 3 + 3 * (p[2] - p[1]); } - join(n0, "leaves the loop in different states on different paths"); - live = njps > 0; - p = R_EXIT + f[6]; - while (p < e) { - if (fixups(p + 1, n0, 0)) { - setlit(p[0], here); - fixups(p + 1, n0, 1); - outs = fixnew(hole(), outs); - op(O_JMP); - } - else outs = fixnew(p[0], outs); - p = p + 3 + 3 * (p[2] - p[1]); - } - if (a >= 0) setlit(a, here); - if (sc >= 0) fixups(R_SNAP + sc, n0, 1); - fixall(outs, here); - extop = f[6]; - return 0; -} -// while. the back edge is checked against the snapshot taken at the top, so a -// loop body that gives something away and comes around again is an error. a -// literal while (1) has no fall-out path, only its breaks. -int kw_while() { - int c0 = here, a, s0, sc = -1, c, inf = 0; - stmtonly(); - next(); - s0 = snap_push(); - expect(s_lp); - // while (1): no way out but break - if (tk == T_NUM && tv) { - peek(); - if (pk_tk == T_OP && pk_tv == s_rp) inf = 1; - } - c = borrow_condition(); expect(s_rp); - a = hole(); op(O_JZ); - if (!inf && live) sc = snap_push(); - loop_push(c0, s0); - substmt(); - backedge(s0); - jmp(c0); - loop_end(a, sc); - snap_pop(s0); - return 0; -} -// for (init; test; step) body. the code is laid out test first, then a jump -// over the step into the body, and the step jumps back to the test, so the -// step is compiled before the body but runs after it. while the test and the -// step are parsed, fortest and forstep record the shape "i < bound ... i++": -// if it holds and the bound is len(a) of a fixed array, the body is told that -// a[i] is in range (lpfact), and its bounds check disappears. -int kw_for() { - int save = nloc, s0, sc = -1, c0, a, b, st, c, inf = 0; word borrow_mark = borrow_slots; - stmtonly(); - next(); - expect(s_lp); - fdepth++; - if (is_id(s_var)) kw_var(); - else if (tstart()) localdecl(1); - else { if (!is_op(s_semi)) { c = expr(); rval(); op(O_DROP); discard(c); } expect(s_semi); } - borrow_cleanup_since(borrow_mark); - s0 = snap_push(); - c0 = here; - ft_iv = -1; fs_ok = 0; fortest = 1; - if (!is_op(s_semi)) { - if (tk == T_NUM && tv) { peek(); if (pk_tk == T_OP && pk_tv == s_semi) inf = 1; } - c = borrow_condition(); - } else { lit(1); inf = 1; xfloat = 0; } - fortest = 0; - expect(s_semi); - a = hole(); op(O_JZ); - if (!inf && live) sc = snap_push(); - b = hole(); op(O_JMP); - st = here; - forstep = 1; borrow_mark = borrow_slots; - if (!is_op(s_rp)) { c = expr(); rval(); op(O_DROP); discard(c); } - borrow_cleanup_since(borrow_mark); - forstep = 0; - expect(s_rp); - jmp(c0); - setlit(b, here); - loop_push(st, s0); - if (ft_iv >= 0 && ft_bd >= 0 && fs_ok && getlo(ft_iv) >= 0 && getln(ft_bd) >= 0) { - lpfact[lsp - 1] = pfact(ft_iv, ft_bd, getln(ft_bd)); - } - substmt(); - backedge(s0); - jmp(st); - loop_end(a, sc); - snap_pop(s0); - block_close(save); - fdepth--; - return 0; -} -// do stmt while (cond); the body runs once before the test. continue jumps -// to the test, which is patched in after the body (f[0] == -2 marks that). -int kw_do() { - int body, s0, sc = -1, a, c; word f; int inf = 0; - stmtonly(); - next(); - s0 = snap_push(); - body = here; - loop_push(-2, s0); - substmt(); - f = R_LOOP + (lsp - 1) * 8; - fixall(f[7], here); - backedge(s0); - if (!is_id(s_while)) die("expected while after do"); - next(); - expect(s_lp); - if (tk == T_NUM && tv) { peek(); if (pk_tk == T_OP && pk_tv == s_rp) inf = 1; } - c = borrow_condition(); - expect(s_rp); - expect(s_semi); - if (!inf && live) sc = snap_push(); - a = hole(); op(O_JZ); - jmp(body); - loop_end(a, sc); - snap_pop(s0); - return 0; -} -// break and continue unwind the scopes they leave (exit_vars frees what those -// blocks own on the way out). break records the analyzer state for the join -// at the end of the loop; continue checks the state against the loop top. -int kw_break() { - word f; - stmtonly(); - next(); expect(s_semi); - if (lsp <= loopbase) { serr("break outside a loop or a switch"); return 0; } - f = R_LOOP + (lsp - 1) * 8; - if (f[4] != bxdepth) { serr("break can't leave a block expression"); return 0; } - exit_vars(f[2] + 1, 0); - if (live) exit_rec(hole()); else hole(); - op(O_JMP); - live = 0; - return 0; -} -// compile continue: jump to the test of the innermost loop. -int kw_continue() { - int j = lsp - 1; word f; - stmtonly(); - next(); expect(s_semi); - while (j >= loopbase && R_LOOP[j * 8] == -1) j--; // past any switch - if (j < loopbase) { serr("continue outside a loop"); return 0; } - f = R_LOOP + j * 8; - if (f[4] != bxdepth) { serr("continue can't leave a block expression"); return 0; } - backedge(f[3]); - exit_vars(f[2] + 1, 0); - if (f[0] == -2) { f[7] = fixnew(hole(), f[7]); op(O_JMP); } // do/while: the condition isn't placed yet - else jmp(f[0]); - live = 0; - return 0; -} -// switch (e) { case 1, 2: ... case 'x': ... default: ... } -// cases don't fall through: each one ends where the next begins, and break -// leaves the switch early. case labels are constants. -// one case's statements, analyzed from the state at the top of the switch. -// the end of a case jumps out of the switch, so cases never fall through. -int casebody(int s0, int live0) { - int save = nloc; - snap_restore(s0); live = live0; - fdepth++; - while (!is_op(s_rb) && !is_id(s_case) && !is_id(s_default)) { - if (tk == T_EOF) { die("missing }"); break; } - stmt(); - } - block_close(save); - fdepth--; - if (live) { exit_rec(hole()); op(O_JMP); } // the end of a case leaves the switch - live = 0; - return 0; -} -// compile a switch statement. -int kw_switch() { - int sl = tline; - int save = nloc, tmp, c, s0, live0, defent = -1, skip, nxt, bj, v, a = -1, st, tg = 0, cov = 0, gi, nb = 0, j, arm, bi; - word[8] pbn; word[8] pbl; - stmtonly(); - next(); - expect(s_lp); c = expr(); st = xty; rval(); plainuse(c); expect(s_rp); - tg = ptagged(st); - fdepth++; - tmp = newloc(0); fslots++; // a hidden local holds the value - laddr(lslot[tmp]); op(O_ST); - live0 = live; - s0 = snap_push(); - loop_push(-1, s0); - expect(s_lb); - while (!is_op(s_rb)) { - if (tk == T_EOF) { die("missing }"); break; } - if (is_id(s_case)) { - next(); - if (tk == T_ID && rt.eq_w(tv, "_")) { - next(); expect(s_colon); - if (defent >= 0) serr("a switch has one catch-all case"); - skip = hole(); op(O_JMP); defent = here; - casebody(s0, live0); setlit(skip, here); - continue; - } - bj = 0; - gi = -1; - peek(); - if (tg && tk == T_ID && pk_tk == T_OP && pk_tv == s_lp) gi = pm_find(tv); - if (gi >= 0) { - // case member(a, b): a member of a tagged enum, its fields bound to the names - next(); next(); - nb = 0; - while (!is_op(s_rp)) { - if (nb >= 8) { die("too many names in a case"); break; } - pbn[nb] = name(); nb++; - if (!accept(s_comma)) break; - } - expect(s_rp); - if (nb != pm_nf[gi]) serr("a case binds every field of its member"); - v = pm_tag[gi]; - if (v < 63) cov = cov | (1 << v); - laddr(lslot[tmp]); op(O_LD); op(O_LD); lit(v); op(O_SUB); - bj = fixnew(hole(), bj); op(O_JZ); - expect(s_colon); - arm = nloc; - for (j = 0; j < nb && j < pm_nf[gi]; j++) { - bi = newloc(pbn[j]); fslots++; - ltype[bi] = pm_ft[gi * 8 + j]; - if (isfl(ltype[bi])) setflt(bi, 1); - pbl[j] = bi; - } - nxt = hole(); op(O_JMP); - fixall(bj, here); - for (j = 0; j < nb && j < pm_nf[gi]; j++) { - capture_cell(pbl[j], 0); - laddr(lslot[tmp]); op(O_LD); lit(pm_off[gi * 8 + j]); op(O_ADD); op(O_LD); - vaddr(pbl[j]); op(O_ST); - } - casebody(s0, live0); - block_close(arm); - setlit(nxt, here); - } else { - while (1) { - v = cexpr(); - if (tg && v >= 0 && v < 63) cov = cov | (1 << v); - laddr(lslot[tmp]); op(O_LD); - if (tg) op(O_LD); - lit(v); op(O_SUB); - bj = fixnew(hole(), bj); op(O_JZ); - if (!accept(s_comma)) break; - } - expect(s_colon); - nxt = hole(); op(O_JMP); - fixall(bj, here); - casebody(s0, live0); - setlit(nxt, here); - } - } else if (is_id(s_default)) { - next(); expect(s_colon); - if (defent >= 0) serr("a switch has one default"); - skip = hole(); op(O_JMP); // only reached when nothing matched - defent = here; - casebody(s0, live0); - setlit(skip, here); - } else die("expected case or default"); - } - next(); - snap_restore(s0); live = live0; - if (tg && defent < 0) cover_warn(tg - 1, cov, sl); - if (defent >= 0) { jmp(defent); loop_end(-1, -1); } - else { a = hole(); op(O_JMP); loop_end(a, s0); } // nothing matched: out with the states from the top - snap_pop(s0); - block_close(save); - fdepth--; - return 0; -} -// return: coerce and check the value, run the analyzer's return rule, free -// everything the function owns (exit_vars from depth 1), and jump to the -// epilogue, which is a patch chain resolved when the function ends. -int kw_return() { - int c, returned = tint; - stmtonly(); - next(); - if (!infn) serr("return outside a function"); - if (bxdepth) serr("return can't leave a block expression"); - if (is_op(s_semi)) { lit(0); c = C_PLAIN; if_ret(0, 1); } - else { - c = expr(); rval(); - returned = xty; - if (!nclo && !xzero) { - if (!fn_return_type) fn_return_type = returned; - else if (fn_return_type != returned) fn_return_type = 0 - 1; - } - if_ret(xty, xzero); - if (fty && fty != tvoid) fcoerce(fty); - if (fty == tvoid) tcerr("return value", tvoid, xty); - else if (fty && !compat(fty, xty)) tcerr("return value", fty, xty); - } - if (nclo) { - if (!clo_ret[nclo - 1]) clo_ret[nclo - 1] = returned; - else if (clo_ret[nclo - 1] != returned) clo_ret[nclo - 1] = 0 - 1; - } - expect(s_semi); - retcheck(c); - borrow_cleanup(); - exit_vars(1, 0); - epichain = fixnew(here, epichain); - lit(rt.MW - 1); op(O_JMP); - live = 0; - return 0; -} -// o3 { ... }: an optimized region. o3 T x = e;: one o3 variable -// (a block is compiled as normal code behind a jump and handed to the core -// as a region once the function is complete: o3pend queues them. variables -// declared outside that are o3 get registers too, and anything with a pointer -// in it is fattened when control leaves the region.) -int kw_o3() { - int lo, i; word p; int save, outer; - stmtonly(); - next(); - if (!is_op(s_lb)) { o3one = 1; localdecl(1); o3one = 0; return 0; } - if (o3on || !infn) return block(); - reserve_code(2); lo = here + 2; lit(lo); op(O_JMP); - o3on = 1; o3reset(); - save = nloc; outer = nloc; - i = lbase; - // o3 variables declared outside - while (i < outer) { - if (ltag[i] & 16) { - o3promote(i); - o3mkcache(i); - o3refresh(i); - } - i++; - } - block(); - o3on = 0; - if (no3pend >= o3pendcap) { o3pend = chk_resize(o3pend, no3pend * 67, o3pendcap * 2 * 67); o3pendcap = o3pendcap * 2; } - { - p = o3pend + no3pend * 67; p[0] = lo; p[1] = here; p[2] = no3sl; - rt.copy_w(p + 3, o3sl, no3sl); rt.copy_w(p + 35, o3rg, no3sl); no3pend++; - } - local_drop(save); fdrop(save); // the hidden cache variables - i = lbase; - while (i < outer) { - ltag[i] = ltag[i] - (ltag[i] & 1792); o3cache[i] = 0; - // leaving o3: fatten what they hold - if ((ltag[i] & 16) && hasptr(ltype[i])) { - vaddr(i); - op(O_LD); - lit(ltype[i]); - callname(s_fatten); - op(O_DROP); - } - i++; - } - return 0; -} -// unchecked turns the analyzer off for one statement -int kw_unchecked() { - int old = fchk; - stmtonly(); - next(); - fchk = 0; - substmt(); - fchk = old; - return 0; -} - -// ---- asm blocks: raw vm words, one per token ---- - -// numbers push, -n pushes a negative number, #n emits a raw word, one-char -// tokens from the mnemonic table are opcodes, names push the address of a -// variable or function, :name defines a label for this block. -// (labels are local to the block: :name defines one, a later name refers to it, -// and references to labels not yet defined are resolved when the block ends) -word asm_names = R_ASM, asm_sites = R_ASM + ASM_LABELS; -word asm_refs = R_ASM + ASM_LABELS + ASM_LABELS, asm_fixes = R_ASM + ASM_LABELS + ASM_LABELS + ASM_REFS; -int asm_labelcap = ASM_LABELS, asm_refcap = ASM_REFS; -int reserve_asm_labels(int need) { - int cap = asm_labelcap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - asm_names = chk_resize(asm_names, asm_labelcap, cap); asm_sites = chk_resize(asm_sites, asm_labelcap, cap); - asm_labelcap = cap; return cap; -} -int reserve_asm_references(int need) { - int cap = asm_refcap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - asm_refs = chk_resize(asm_refs, asm_refcap, cap); asm_fixes = chk_resize(asm_fixes, asm_refcap, cap); - asm_refcap = cap; return cap; -} -int asmblock() { - af_forget(); - int nl = 0, nr = 0, i, k, c, s; word ln = asm_names, la = asm_sites, rn = asm_refs, ra = asm_fixes; - expect(s_lb); - while (!is_op(s_rb)) { - if (tk == T_EOF) { die("unterminated asm block"); break; } - if (tk == T_FLT) { op(OP_FLIT); emit(tv); next(); continue; } - if (tk == T_NUM) { lit(tv); next(); continue; } - if (tk == T_BSTR) { bstrlit(tv); next(); continue; } - if (tk == T_STR) { strlit(tv); next(); continue; } - if (is_op(s_hash)) { - next(); - if (tk != T_NUM) die("# needs a number"); - emit(tv); - next(); - continue; - } - if (is_op(s_minus)) { - next(); - if (tk == T_NUM && tglue) { - lit(0 - tv); - next(); - } else { - op(O_SUB); - } - continue; - } - if (is_op(s_colon)) { - next(); - reserve_asm_labels(nl + 1); ln = asm_names; la = asm_sites; - ln[nl] = name(); - la[nl] = here; - nl++; - continue; - } - if (tv[0] && !tv[1] && (c = mnemidx(tv[0])) >= 0) { op(c); next(); continue; } - if (tk != T_ID) die("bad token in asm"); - i = findloc(tv); - if (i >= 0 && (ltag[i] & 16) && !larr[i]) serr("asm can't reach an o3 variable"); - if (i >= 0) { luse(i); ltag[i] = ltag[i] | LREAD | LASG; vaddr(i); next(); continue; } - if (nclo) { - k = clocap(tv); - if (k >= 0) { vaddr(clo_env[nclo - 1]); op(O_LD); lit(k); op(O_ADD); op(O_LD); next(); continue; } - } - k = -1; i = 0; - while (i < nl) { if (ln[i] == tv) k = i; i++; } - if (k >= 0) { lit(la[k]); next(); continue; } - s = lookup(tv); - if (s >= 0 && skind[s] == K_ENH) { op(XOP0 + sval[s]); next(); continue; } - if (s >= 0 && skind[s] != K_FN && skind[s] != K_KW) { lit(sval[s]); next(); continue; } - if (s >= 0 && skind[s] == K_FN) { fnref(s); next(); continue; } - reserve_asm_references(nr + 1); rn = asm_refs; ra = asm_fixes; - rn[nr] = tv; ra[nr] = hole(); nr++; next(); // a label further down this block - } - next(); - i = 0; - while (i < nr) { - k = -1; c = 0; - while (c < nl) { if (ln[c] == rn[i]) k = c; c++; } - if (k < 0) serrn(rn[i], "unknown name in asm"); - setlit(ra[i], la[k]); - i++; - } - return 0; -} -// virtual enhancement bodies are recipes read as a flat range by the host. -int contiguous_asm() { - word q = grab(), wrapped; int n = q[0], i = 0, need = 2, entry, at; - while (i < n) { - need = need + 16; - if (q[1 + i * 3] == T_STR || q[1 + i * 3] == T_BSTR) need = need + rt.len_w(q[2 + i * 3]); - i++; - } - reserve_code(need); entry = here; - wrapped = chk_resize(0, 0, n * 3 + 7); wrapped[0] = n + 2; - wrapped[1] = T_OP; wrapped[2] = s_lb; wrapped[3] = tpack(); - rt.copy_w(wrapped + 4, q + 1, n * 3); at = 4 + n * 3; - wrapped[at] = T_OP; wrapped[at + 1] = s_rb; wrapped[at + 2] = tpack(); - src_push(1, wrapped, 0, 0); next(); asmblock(); src_pop(); op(O_RET); - rt.free(d*q); return entry; -} -// asm as a keyword (statement or expression): the block's own stack effect is -// the program's responsibility, so the result is a plain word -int kw_asm() { - if (effect_current) effect_current[0] = 2; - next(); - asmblock(); - xcls = C_PLAIN; - return 0; -} - -// ---- quote: code as data ---- - -// a token list is an object: [n] then kind, value, line for each token. names, -// strings and operators are interned pointers. -// grab reads the { ... } after quote or times (a parse word) into a token list, -// stopping at the matching brace. it buffers the tokens in R_QBUF, then copies -// them to a fresh object. quote compiles a list into the program as data and -// returns a copy at run time; feed (below) pushes a list back as input. -word .u grab() { - int n = 0, depth = 0; word q; - if (!is_op(s_lb)) die("expected {"); - next(); - while (1) { - if (tk == T_EOF) { die("unterminated quote"); break; } - if (is_op(s_lb)) depth++; - if (is_op(s_rb)) { if (!depth) break; depth--; } - reserve_R_QBUF((n + 1) * 3); - R_QBUF[n * 3] = tk; R_QBUF[n * 3 + 1] = tv; R_QBUF[n * 3 + 2] = tline; n++; - next(); - } - next(); - q = rt.new(n * 3 + 1); - q[0] = n; - rt.copy_w(q + 1, R_QBUF, n * 3); - return u*q; -} -word .u qnew(word src) { int n = src[0] * 3 + 1; word q = rt.new(n); rt.copy_w(q, src, n); return q; } -word .u qdup(.m q) { int n = q[0] * 3 + 1; word r = rt.new(n); rt.copy_w(r, q, n); return u*r; } -// emit n words copied from a well-formed token buffer q; references in the emitted words must outlive the image. -int wcopyto(.m q, int n) { - int i = 0; reserve_code(n); - while (i < n) { emit(q[i]); i++; } - return 0; -} -// quote { tokens }: the list is emitted into the code behind a jump, and the -// expression's value is a fresh copy of it (qnew), so a program can edit its -// copy without touching the original -int quote_tokens() { - word q; int j, a, n; - q = grab(); - j = hole(); op(O_JMP); - n = q[0] * 3 + 1; reserve_code(n); a = here; - wcopyto(q, n); - rt.free(d*q); - setlit(j, here); - lit(a); - callname(s_qnew); - xcls = C_FRESH; - return 0; -} -int kw_quote() { next(); return quote_tokens(); } -// tokens of a string, as a token list -word*.u tokens(char* s) { - int n = 0; word q; - src_push(2, s, 0, 0); - next(); - while (tk != T_EOF) { - reserve_R_QBUF((n + 1) * 3); - R_QBUF[n * 3] = tk; R_QBUF[n * 3 + 1] = tv; R_QBUF[n * 3 + 2] = tline; n++; - next(); - } - src_pop(); - q = rt.new(n * 3 + 1); - q[0] = n; - rt.copy_w(q + 1, R_QBUF, n * 3); - return u*q; -} -// feed a token list into the input: the parser reads it next, then carries -// on where it was. the list is freed once it has been read. -int feed(.d q) { - src_push(1, q, 1, 1); - next(); - return 0; -} - -// ---- reading a unit and scanning it for signatures ---- - -// a unit from stdin is read whole first (4 chars to a word), then scanned -// once for function signatures, so a function's tags are known before its first -// call even when it's defined further down. -// read the whole unit from stdin, up to a backtick (outside comments and -// literals, see srcend) or end of input, packing 4 -// characters to a word, so prescan can run over it and then the real parse can -// run over the same text -word .u readsrc() { - int cap = 4090; word b; int n = 0, c; word nb; int w; - b = rt.new(4090); - qst = 0; qp = 0; qesc = 0; - while (!asm { ? }) { - c = asm { , } & 255; - if (c == 255 && asm { ? }) break; // the end of a slow pipe, found by the read itself - // srcend only has anything to decide at a quote, a slash or a star (or - // inside a comment or literal); every other character in plain code just - // clears the "previous char was a slash" memory - if (qst || c == 34 || c == 39 || c == '/' || c == '*' || c == 96) { if (srcend(c)) break; } - else qp = 0; - if ((n >> 2) + 2 >= cap) { - nb = rt.new(cap + cap + 6); - rt.copy_w(nb, b, cap); - rt.free(b); - b = nb; - cap = cap + cap + 6; - } - w = n >> 2; - if (!(n & 3)) b[w] = 0; - b[w] = b[w] | (c << ((n & 3) * 8)); - n++; - } - b[n >> 2] = b[n >> 2] & (rt.pow2[(n & 3) * 8] - 1); // the 0 at the end - if ((n & 3) == 0) b[n >> 2] = 0; - b[(n >> 2) + 1] = 0; - return b; -} -// skip a <...> group (type parameters) -// prescan: before a unit is compiled, one dry pass over it records the -// signature (parameter tags, .u return, o3) of every function declared in it, -// so a call that appears before its definition is checked against the tags -// the callee will have. it only needs the shape of a declaration, not its body. -int pskipangle() { - int dp = 0; - while (tk != T_EOF) { - if (is_op(s_lt)) dp++; - else if (is_op(s_gt)) dp--; - else if (is_op(s_shr)) dp = dp - 2; - next(); - if (dp <= 0) return 0; - } - return 0; -} -// at the name after fn: read the signature's tags and record them -// prescan: at the ( after a function name, read the parameter list and record -// the signature packed into ssig (bit 0 .u return, bits 1..5 parameter count, -// two bits per parameter from bit 6) -int prescan_fn(int ret, int n, int o3) { - int np = 0, tags = 0, t = 0, dp, s; - next(); // the ( - if (!is_op(s_rp)) { - np = 1; dp = 1; - while (tk != T_EOF) { - if (is_op(s_lp)) dp++; - else if (is_op(s_rp)) { dp--; if (!dp) break; } - else if (dp == 1 && is_op(s_comma)) { if (np <= 12) tags = tags + (t << (np + np - 2)); np++; t = 0; } - else if (dp == 1 && is_op(s_dot)) { next(); if (is_id(s_m)) t = 1; else if (is_id(s_d)) t = 2; continue; } - next(); - } - if (np <= 12) tags = tags + (t << (np + np - 2)); - } - next(); - if (!is_op(s_lb) && !is_op(s_semi)) return 0; // not a function: an enhancement, a call in a parse word... - if (!dnm) dnm = n; - s = lookup(n); - if (s >= 0 && skind[s] == K_KW) return 0; - if (s < 0 || s < ubase || skind[s] != K_FN || (sflag[s] & F_DEF)) s = fwdfn(n); - if (!(sflag[s] & F_SIG)) { - ssig[s] = ret + np + np + tags * 64; - sflag[s] = sflag[s] | F_SIG | F_PRE; - } - if (o3) sflag[s] = sflag[s] | F_O3; - return 0; -} -// scan ahead, without emitting code, to learn what a block of tokens contains. -int prescan(int kind, int obj) { - int depth = 0, sp = panic, sn = nerr, pend = 0, po3 = 0, n, dep; char* oldmodule = module_name; word oldimports = module_imports; - dry++; - src_push(kind, obj, 0, 0); - next(); - while (tk != T_EOF) { - if (is_op(s_lb)) depth++; - else if (is_op(s_rb)) { depth--; pend = 0; po3 = 0; } - else if (depth <= 0) { - if (is_id(s_type) || typedef_ahead()) { while (tk != T_EOF && !is_op(s_semi)) next(); if (is_op(s_semi)) next(); pend = 0; po3 = 0; continue; } - if (tk == T_ID && rt.eq_w(tv, "namespace")) { kw_namespace(); continue; } - if (is_op(s_semi)) { pend = 0; po3 = 0; } - else if (is_op(s_dot)) { peek(); if (pk_tk == T_ID && pk_tv == s_u) pend = 1; } - else if (is_id(s_o3)) po3 = 1; - else if (tk == T_ID) { - peek(); - if (pk_tk == T_OP && pk_tv == s_lp) { - n = tv; - next(); - prescan_fn(pend, n, po3); - pend = 0; - po3 = 0; - continue; - } - if (pk_tk == T_OP && pk_tv == s_lt) { // list<T> in a type, or first<T>( a generic function - n = tv; next(); dep = 0; - while (tk != T_EOF) { - if (is_op(s_lt)) { - dep++; - } else if (is_op(s_gt)) { - dep--; - } else if (is_op(s_shr)) dep = dep - 2; - next(); - if (dep <= 0) break; - } - if (is_op(s_lp)) { prescan_fn(pend, n, po3); pend = 0; po3 = 0; } - continue; - } - } - } - next(); - } - src_pop(); - dry--; panic = sp; nerr = sn; module_name = oldmodule; module_imports = oldimports; - return 0; -} -// compile a whole unit from stdin -int unit_stdin() { - int src = readsrc(); - module_name = 0; module_imports = 0; - prescan(3, src); - src_push(3, src, 0, 0); - next(); - unit_top(); - unit_end(ubase); - src_pop(); - rt.free(src); - return 0; -} - -// ---- nested compiles ---- - -// compile() and load() can run while another function is being compiled (from -// a parse word). the function context is saved, the new code is emitted in -// place with a jump around it, and then everything is put back. -// the context frame is 34 words: everything about the function being compiled -// that a nested compile would otherwise clobber (locals, depth, frame size, -// loop stack position, the expression-state globals). closures use the same -// pair. -const int CTX_WORDS = 48; -int ctx_push() { - af_forget(); - word f; - reserve_R_CTX((ctxsp + 1) * CTX_WORDS); f = R_CTX + ctxsp * CTX_WORDS; - ctxsp++; - f[0] = nloc; f[1] = lbase; f[2] = fdepth; f[3] = fslots; f[4] = fchk; f[5] = fret; f[6] = live; - f[7] = bxdepth; f[8] = epichain; f[9] = fframe; f[10] = infn; f[11] = lsp; f[12] = loopbase; - f[13] = lv; f[14] = lvk; f[15] = lvi; f[16] = kw_expr; f[17] = xcls; f[18] = snaptop; - f[19] = fty; f[20] = ntp; f[21] = xty; rt.copy_w(f + 22, tpn, 8); - f[30] = finsym; f[31] = finfer; f[32] = ifcur; f[33] = ifcall; f[34] = effect_current; f[35] = borrow_slots; f[36] = borrow_pending; f[37] = borrow_depth; f[38] = borrow_jump; f[39] = borrow_entry; f[40] = capture_enabled; f[41] = effect_value; f[42] = xprojection; f[43] = effect_floor; f[44] = module_name; f[45] = module_imports; - f[46] = fn_return_type; f[47] = if_value; - return 0; -} -// pop the innermost function context. -int ctx_pop() { - word f; - ctxsp--; - f = R_CTX + ctxsp * CTX_WORDS; - local_drop(f[0]); - fdrop(nloc); - lend_drop(1 << 30, f[0]); - lbase = f[1]; - fdepth = f[2]; - fslots = f[3]; - fchk = f[4]; - fret = f[5]; - live = f[6]; - bxdepth = f[7]; epichain = f[8]; fframe = f[9]; infn = f[10]; lsp = f[11]; loopbase = f[12]; - lv = f[13]; lvk = f[14]; lvi = f[15]; kw_expr = f[16]; xcls = f[17]; snaptop = f[18]; - fty = f[19]; ntp = f[20]; xty = f[21]; rt.copy_w(tpn, f + 22, 8); - finsym = f[30]; finfer = f[31]; ifcur = f[32]; ifcall = f[33]; effect_current = f[34]; borrow_slots = f[35]; borrow_pending = f[36]; borrow_depth = f[37]; borrow_jump = f[38]; borrow_entry = f[39]; capture_enabled = f[40]; effect_value = f[41]; xprojection = f[42]; effect_floor = f[43]; module_name = f[44]; module_imports = f[45]; xvk = 0; - fn_return_type = f[46]; if_value = f[47]; - return 0; -} - -// ---- functions and the top level ---- - -// ---- a function, start to finish ---- - -// fndef reads the header, then: fn_begin resets the per-function state, -// prologue emits the frame setup, the parameters are declared and stored from -// the stack into their slots, body parses the statements, and epilogue emits -// the shared exit (return jumps here through the epichain patch list). the -// frame size is not known until the end, so the prologue's subtract is a hole -// that epilogue fills with fslots. -// fn_begin: chk is 0 for unchecked, ret is 1 for a .u return -int fn_begin(int chk, int ret) { - capture_enabled = 0; effect_value = 0; effect_current = 0; borrow_slots = 0; borrow_pending = 0; borrow_depth = 0; borrow_jump = -1; borrow_entry = 0; - if (cmode && svkind && !dry) rt.fill_w(svkind, 0, nsym); xvk = 0; - lend_drop(1 << 30, nloc); - lbase = nloc; fdepth = 1; fslots = 0; fchk = chk && check_on; fret = ret; fty = 0; finfer = 0; fn_return_type = 0; - live = 1; bxdepth = 0; epichain = 0; infn = 1; loopbase = lsp; lv = 0; lvk = LV_NONE; xbor = -1; - return 0; -} -// allocate the frame, retaining the caller's pointer until any growth finishes. -int prologue() { - int k, busy, done, helper = !nclo && (sname[finsym] == s_frame_grow || sname[finsym] == s_frame_leave); - lit(rt.ZFP); op(O_LD); - if (!helper) { lit(0); op(O_PICK); } - fframe = hole(); op(O_SUB); lit(rt.ZFP); op(O_ST); - if (helper) return 0; - lit(sval[lookup(s_frame_busy)]); op(O_LD); busy = hole(); op(O_JZ); - done = hole(); op(O_JMP); setlit(busy, here); - lit(rt.ZFP); op(O_LD); lit(sval[lookup(s_frame_floor)]); op(O_LD); - lit(sval[lookup(s_frame_guard)]); op(O_LD); op(O_ADD); op(O_LT); k = hole(); op(O_JZ); - lit(rt.ZFP); op(O_LD); lit(1); op(O_PICK); lit(rt.ZFP); op(O_ST); - callname(s_frame_grow); lit(rt.ZFP); op(O_ST); - busy = hole(); op(O_JMP); - setlit(k, here); setlit(done, here); op(O_DROP); setlit(busy, here); - return 0; -} -// emit the function epilogue, patching every return to it. -int epilogue() { - int k, node, done, helper = !nclo && (sname[finsym] == s_frame_grow || sname[finsym] == s_frame_leave); - lit(0); - fixall(epichain, here); - lit(rt.ZFP); op(O_LD); lit(fslots); op(O_ADD); lit(rt.ZFP); op(O_ST); - if (!helper) { - lit(sval[lookup(s_frame_node)]); op(O_LD); node = hole(); op(O_JZ); - lit(rt.ZFP); op(O_LD); lit(sval[lookup(s_frame_top)]); op(O_LD); op(O_SUB); k = hole(); op(O_JZ); - done = hole(); op(O_JMP); setlit(k, here); - callname(s_frame_leave); lit(rt.ZFP); op(O_ST); setlit(done, here); setlit(node, here); - } - op(O_RET); borrow_initializer(); - setlit(fframe, fslots); - infn = 0; - local_drop(lbase); - fdrop(lbase); - td_end(0); - return 0; -} -// the statements of a function between its braces, then the end-of-function -// checks: hints, unused m objects, anything still owned is freed, and a .u -// function that falls off the end is an error -int body() { - int closed, endplace; - capture_prepare(); borrow_begin(); - expect(s_lb); - while (!is_op(s_rb)) { - if (tk == T_EOF) { die("missing } at the end of a function"); break; } - stmt(); - } - closed = is_op(s_rb); endplace = toff + toklen; - if (probe_off >= 0 && !chkabort && !dry && !ginst && incdepth == 0 && ctxsp <= nclo && toff >= probe_off) { probe_snap(); chkabort = 1; } - local_hints(lbase); - unused_check(lbase); - if (ifcur) if_fin(); - if (ifcur && live && ifcur[1]) ifcur[4] = 1; // falling off the end gives 0 - if (fchk && live) { - exit_vars(1, 1); - if (fret) cerr("a function returning .u has to end with a return"); - } - next(); - if (probe_off >= 0 && !chkabort && closed && nclo == 0 && probe_has == 1 && endplace <= probe_off && !ginst && ctxsp == 0) { probe_has = 0; probe_n = 0; } // the place is past this function - return 0; -} -// the rest of a function, after its return type and name: -// int f(int a, node*.m n, .d x, b) { ... } <T> *list<T>.u mk(int n) { ... } -// rty is 0 for fn (a plain word), ret is 1 for a .u return -// (this is also where a function's signature is packed into ssig and its type -// into stype, checked against any earlier prototype or prescan result, and -// where the o3 region for an o3 function is handed to the core) -int fndef(int chk, int rty, int ret, int n) { - int s, np = 0, tags = 0, t, sig, i; char*[24] pn; word[24] pt; word[24] pty; int typed = 0, ft, explicit_rty = !!rty, o3f = o3nxt, lo, hi, pk; - o3nxt = 0; - if (rty || ntp) typed = 1; - if (!dnm) dnm = n; - s = lookup(n); - if (s < 0 || s < ubase || skind[s] != K_FN || (sflag[s] & F_DEF)) { - s = addsym(n, K_FN, 0); - sflag[s] = 0; - } - def_note(s, nameoff); - ifcur = 0; - if (ifon && ifkk && !ginst && !ntp && !dry) { - if (ifkk > 0 && !rty) if_begin(s, ifkk); else if_begin(s, 0); - } - ifkk = 0; - expect(s_lp); - if (!is_op(s_rp)) { - while (1) { - t = 0; - pty[np] = tword; - pk = 0; - if (!is_op(s_dot) && paramstart()) { - pty[np] = ptype(); typed = 1; pk = 1; - if (pty[np][0] == TY_ARR) pty[np] = decay(pty[np]); - if (isagg(pty[np])) { - if (ginst) pty[np] = tptr(pty[np]); else if (!stubgen) serr("structs and arrays are passed by pointer"); - // (an extern stub takes its struct by value, which is an address here) - } - } - if (ifcur) { - if (pk) if_param(np, tpack(), 0, pty[np]); - else if (is_op(s_dot)) if_param(np, tpack(), 2, pty[np]); - else if_param(np, tpack(), 1, pty[np]); - } - if (accept(s_dot)) { // .m lends, .d takes over - if (tv == s_m) { - t = 1; - } else if (tv == s_d) t = 2; else die("a parameter tag is .m or .d"); - next(); - } - pnoffs[np] = toff; pnplaces[np] = tpack(); rs_note(RS_PARAM, toff); pn[np] = name(); - if (o3f && pty[np] == tword) serr("o3 parameters need a type"); - if (np >= 12 && t) die("only the first 12 parameters can be tagged"); - pt[np] = t; - if (np < 12) tags = tags + (t << (np + np)); - np++; - if (np >= 24) die("too many parameters"); - if (!accept(s_comma)) break; - } - } - expect(s_rp); - sig = ret + np + np + tags * 64; - if ((sflag[s] & F_SIG) && !(sflag[s] & F_PRE)) { - if (ssig[s] != sig) serrn(n, "doesn't match its earlier declaration"); - } - else if ((sflag[s] & F_UNTYPED) && (tags || ret)) serrn(n, "was called before its tags were declared; put a prototype above the first call"); - ssig[s] = sig; - sflag[s] = (sflag[s] | F_SIG) - (sflag[s] & F_PRE); - if (ntp) { sflag[s] = sflag[s] | F_GEN; if (!ginst) gtmake(s); } - if (typed) { - if (!rty) rty = tword; - ft = tfn(rty, np, pty, sig); - if (stype[s] && stype[s] != ft) serrn(n, "doesn't match its earlier declaration"); - stype[s] = ft; - } - if (ifcur && explicit_rty) if_join(ifcur + 3, rty); - sig_attach(n, rty, np, pn, pty, typed); - if (accept(s_semi)) { sflag[s] = sflag[s] | F_DECL; ntp = 0; return s; } - if (sflag[s] & F_DEF) serrn(n, "is defined twice"); - if (!ginst && ntp) { // a generic function is not compiled here: its tokens are kept and each set of type arguments gets its own copy - if (sflag[s] & F_TPL) serrn(n, "is defined twice"); - sflag[s] = sflag[s] | F_TPL; - gnew = gtmake(s); - gskip(); - ntp = 0; - sval[s] = here; // the name still has an address and a type, so reflection can ask for them; it halts if it is ever jumped to - sflag[s] = sflag[s] | F_DEF; - fixall(sfix[s], here); sfix[s] = 0; - lit(rt.MW - 1); op(O_JMP); - return s; - } - sval[s] = here; - sflag[s] = sflag[s] | F_DEF; - fixall(sfix[s], here); sfix[s] = 0; - fn_begin(chk, ret); - finsym = s; finfer = !ret; - effect_current = effect_record(s); rt.fill_w(effect_current, 0, 50); effect_current[0] = 1; if (!chk || stubgen) effect_current[0] = 2; effect_current[1] = np; - i = 0; while (i < np) { if (pt[i] == 2) { effect_current[2 + i] = EF_CONSUME; effect_current[26 + i] = EF_CONSUME; } i++; } - if (typed && explicit_rty) fty = rty; - no3pend = 0; - if (o3f) { o3on = 1; o3reset(); sflag[s] = sflag[s] | F_O3; } - prologue(); borrow_prologue(); - i = 0; - while (i < np) { - t = newloc(pn[i]); lrskind[t] = RS_PARAM; lrsoff[t] = pnoffs[i]; lpos[t] = pnplaces[i]; fslots++; lsmark(1, pn[i]); - ltype[t] = pty[i]; lptr[t] = 0; - if (ifcur && if_local && i < IFP) if_local[t] = ifcur + 8 + 3 * i; - lt_note(pnoffs[i], pty[i]); - if (o3f) { ltag[t] = 16; o3promote(t); } - if (isfl(pty[i])) setflt(t, 1); - if (fchk && pt[i] == 1) lstate[t] = S_BORROW; - if (fchk && pt[i] == 2) { lstate[t] = S_M; lown[t] = 1; } - i++; - } - i = np; - while (i) { i--; laddr(i); op(O_ST); } - if (o3f) { // the region starts here: a jump, so the cpu enters it - reserve_code(2); lo = here + 2; lit(lo); op(O_JMP); - i = 0; while (i < np) { t = lbase + i; o3mkcache(t); o3refresh(t); i++; } - } - body(); - if (!explicit_rty && fn_return_type > 0 && fn_return_type != tword) { - stype[s] = tfn(fn_return_type, np, pty, ssig[s]); - sig_attach(n, fn_return_type, np, pn, pty, 1); - } - hi = here; - epilogue(); - if (o3f) { - o3on = 0; - o3fs = fslots; - o3na = np; - o3fn0 = sval[s]; - o3region(lo, hi, o3sl, o3rg, no3sl); - } - o3flush(); - ntp = 0; ifcur = 0; - fn_remember(s, n); - return s; -} -// let g = 1, buf[10]; int g = 3; int[10] buf; point origin; -// ---- globals, constants, enhancements ---- - -// a global's storage is emitted in place among the code (one word, or the -// whole array), and the symbol's value is its address. a string initializer -// points at characters emitted right after. -int gdecl(int typed) { - ifgk = 0; - if (isp == 0) ifgk = tpack(); - next(); - return gdecl2(0, 0, name()); -} -// compile the rest of a global declaration: its type, name and initial value. -int gdecl2(int t, int tg, int n) { - int a, ng = nameoff; word k, arraytype, iq; int s, kp = ifgk, gty = 0, gok = ifon && !t && !dry, vr, vk; word vv; - ifgk = 0; - if (!dnm) dnm = n; - if (tg) serr("tags go on locals and parameters, not globals"); - if (t == tvoid) serr("a variable can't be void"); - while (1) { - if (accept(s_lk)) { - k = cexpr(); expect(s_rk); - if (!t) t = tword; - arraytype = tarr(t, k); k = tsz(arraytype); reserve_code(k); - s = addsym(n, K_GARR, here); rs_put(ng, RS_SYM, rs_sym(s), s); stype[s] = arraytype; - while (k) { emit(0); k--; } - if (gok) { if (!gty) gty = tint; else if (gty != tint) gok = 0; } - } else if (t && isagg(t)) { - arraytype = t; iq = 0; - if (t[0] == TY_ARR && accept(s_eq)) { if (!is_op(s_lb) && tk != T_STR && tk != T_BSTR) serr("an array needs an initializer list"); else { iq = array_initializer_tokens(t); arraytype = array_measure(iq, t); } } - if (arraytype[0] == TY_ARR && arraytype[3] < 0) serr("T[] needs an initializer to determine its length"); - k = tsz(arraytype); reserve_code(k); a = here; - s = addsym(n, K_GARR, here); rs_put(ng, RS_SYM, rs_sym(s), s); stype[s] = arraytype; - while (k) { emit(0); k--; } - if (iq) { array_initialize(iq, arraytype, a, 0); rt.free(iq); } - } else { - reserve_code(1); a = here; - s = addsym(n, K_GVAR, a); rs_put(ng, RS_SYM, rs_sym(s), s); vr = rsn - 1; vk = 1; vv = 0; - if (t) stype[s] = t; - emit(0); - if (accept(s_eq)) { - if (tk == T_STR) { - vk = 3; vv = vf_string(tv); if (!t) stype[s] = tptr(tchar); - reserve_code(rt.len_w(tv) + 1); *(a + hdelta) = here; - k = tv; - while (*k) { emit(*k); k++; } - emit(0); - next(); - if (gok) { if (!gty) gty = tptr(tchar); else if (gty != tptr(tchar)) gok = 0; } - } - else if (tk == T_BSTR) { - vk = 4; vv = vf_string(tv); *(a + hdelta) = bstrdata(tv); next(); - if (!t) stype[s] = tbstr; else if (t != tbstr) tcerr("initializer", t, tbstr); - if (gok) { if (!gty) gty = tbstr; else if (gty != tbstr) gok = 0; } - } - else if (is_op(s_lb) || (is_op(s_lp) && caststart())) { - arraytype = tarr(tint, -1); - if (accept(s_lp)) { arraytype = ptype(); expect(s_rp); } - if (arraytype[0] != TY_ARR || !is_op(s_lb)) serr("a global compound literal needs an array initializer"); - else { array_literal(arraytype); vv = xcval; *(a + hdelta) = vv; if (!t) stype[s] = decay(xty); } - } - else { - vv = cnorm(t, cexpr()); *(a + hdelta) = vv; - if (gok) { if (!gty) gty = tint; else if (gty != tint) gok = 0; } - } - } else if (gok) gok = 0; // no initializer: nothing says what it is - vf_at(vr, vk, vv); - } - if (stype[s]) lt_note(ng, stype[s]); else lt_note(ng, tword); - if (!accept(s_comma)) break; - ng = toff; n = name(); - } - expect(s_semi); - if (gok && gty && kp) fact_add(kp, 0 - 1, gty, ""); - return 0; -} -// a constant cut to the sized integer type t, the way tnorm does at run time -int cnorm(word t, int v) { - int b, mask, sg; - if (!t) return v; - b = tbytes(t); - if (!b || t[0] != TY_INT) return v; - mask = (1 << (8 * b)) - 1; - v = v & mask; - if (t[6] & 1) { sg = 1 << (8 * b - 1); v = (v ^ sg) - sg; } - return v; -} -// const A = 1, B = A + 2; -// const [type] NAME = e, NAME = e; the type is optional and is read and dropped, as in zc0 -int gconst() { - int n, off, s; - next(); - if (tk == T_ID) { peekat(0); if (pk_tk == T_ID || (pk_tk == T_OP && pk_tv == s_star)) ptype(); } - while (1) { off = toff; n = name(); if (!dnm) dnm = n; expect(s_eq); s = addsym(n, K_CONST, cexpr()); rs_put(off, RS_SYM, rs_sym(s), s); if (!accept(s_comma)) break; } - expect(s_semi); - return 0; -} -// enhance [n] name(a, b) micro "recipe" [else fn]; n outputs, 1 if left out -// enhance [n] name(a, b) vm { asm } [else fn]; -// lays out the descriptor the install opcode reads, and wires it in right -// away (an image wires its own in when it boots). -int enhdef() { - int n, np = 0, nout = 1, dsc, na, pay, len = 0, kind = 0, i; word p; int s, slot, r; char*[8] pn; - next(); - // enhance 2 divmod(a, b) - if (tk == T_NUM) { - if (tv < 0 || tv > 8) serr("an enhancement has at most 8 outputs"); - nout = tv; - next(); - } - n = name(); - expect(s_lp); - if (!is_op(s_rp)) { - while (1) { - if (np == 8) die("an enhancement takes at most 8 inputs"); - pnoffs[np] = toff; pnplaces[np] = tpack(); rs_note(RS_PARAM, toff); pn[np] = name(); - np++; - if (!accept(s_comma)) break; - } - } - expect(s_rp); - len = 7 + np; i = 0; while (i < np) { len = len + rt.len_w(pn[i]) + 1; i++; } - reserve_code(len); len = 0; dsc = here; i = 0; while (i < 7) { emit(0); i++; } - na = here; i = 0; while (i < np) { emit(0); i++; } - i = 0; - while (i < np) { *(na + i + hdelta) = here; p = pn[i]; while (*p) { emit(*p); p++; } emit(0); i++; } - if (accept(s_micro)) { - if (tk != T_STR) die("expected the recipe as a string"); - ebn = 0; while (tk == T_STR) { ebs(tv); next(); } - reserve_code(ebn + 1); pay = here; wcopyto(ebuf, ebn + 1); - } else if (accept(s_vm)) { pay = contiguous_asm(); len = here - pay; kind = 1; } - else die("expected micro or vm"); - slot = nenh; - if (slot >= 512) fatal("all 512 enhancement slots are taken"); - nenh++; - p = dsc + hdelta; - p[0] = slot; p[1] = kind; p[2] = np; p[3] = nout; p[4] = pay; p[5] = len; p[6] = na; - s = addsym(n, K_ENH, slot); - ssig[s] = np + nout * 16 + dsc * 256; - if (accept(s_else)) { r = lookup(tv); if (r < 0) r = fwdfn(tv); next(); sfix[s] = r + 1; } - expect(s_semi); - if (!hdelta) { - r = rt.install(dsc); - if (r < 0) { - if (!sfix[s]) serrn(n, "the enhancement didn't install (see install's codes in core.c)"); - sflag[s] = sflag[s] | F_FALLBACK; - } - } - return 0; -} -// recovery at the top level: skip to the next token that can start a -// declaration, outside any braces -int topsync() { - int depth = 0; - while (tk != T_EOF) { - if (is_op(s_lb)) depth++; - else if (is_op(s_rb)) { depth--; if (depth < 0) depth = 0; } - else if (!depth && tk == T_ID && (tv == s_fn || tv == s_var || tv == s_const || tv == s_struct || tv == s_type || tv == s_typedef - || tv == s_enhance || tv == s_typecheck || tv == s_docs || tv == s_unchecked || tv == s_immediate || tv == s_o3 - || tv == s_memdebug || tv == s_enum || tv == s_include)) break; - next(); - } - panic = 0; - return 0; -} -// after a function's name: <T, U> declares its type parameters. any the return -// type used were taken on trust; now they get their real numbers -int genparams() { - int i, j, n = 0; char*[8] ps; word[8] bs; - if (!is_op(s_lt)) { if (ntp) serrn(tpn[0], "isn't a type"); ntp = 0; return 0; } - next(); - while (1) { - if (n >= 8) fatal("too many type parameters"); - ps[n] = name(); - n++; - if (!accept(s_comma)) break; - } - closeangle(); - rt.fill_w(bs, 0, 8); - i = 0; - while (i < ntp) { - j = 0; - while (j < n && ps[j] != tpn[i]) j++; - if (j == n) serrn(tpn[i], "isn't a type"); else bs[i] = tvar(j, ps[j]); - i++; - } - if (ntp && dty) dty = subst(dty, bs); - rt.copy_w(tpn, ps, n); ntp = n; - if (ginst) ntp = 0; - return 0; -} -// the return type of a function or the type of a global, with unknown names allowed for now -int rettype() { - tvauto = 1; - dtype(); - tvauto = 0; - return 0; -} -// finish a function declaration. an immediate fn also becomes a keyword -// (kind K_KW whose handler is the function), which is how parse words work: -// the parser calls it with kwcall the moment it meets the name. -int fndone(int s, int imm) { - int k; - ntp = 0; - if (imm) { - if (hdelta) serr("parse words can't be defined while building an image"); - k = addsym(sname[s], K_KW, sval[s]); - sflag[k] = F_DEF | F_STMT | F_EXPR | F_TOP; - } - return 0; -} -// one top-level item. this is the dispatcher for everything a unit can -// contain: unchecked / immediate / o3 prefixes, fn declarations (with or -// without a leading return type), globals, struct, type, enum, include, -// typecheck, memdebug, enhance, const, and parse words used at the top level. -// anything else is a typed global or function: a type, a name, and then -// ( or < means function, otherwise a global. -int topitem0() { - int r, i; - dsn = 0; dnm = 0; - if (docs_on && dn && dline == tline && !ginst) { - if (dn > docsnapcap) { docsnapcap = dn; dsnap = chk_resize(dsnap, 0, docsnapcap); } - i = 0; while (i < dn) { dsnap[i] = dbuf[i]; i++; } - dsn = dn; dline = 0; - } - r = topitem1(); - if (dsn && dnm) doc_attach(dnm); - dsn = 0; dnm = 0; - return r; -} -// compile one top-level item, after the first check of its keyword. -int topitem1() { - int chk = 1, imm = 0, s, k, n; - if (panic) return topsync(); - if (is_id(s_unchecked)) { - next(); - if (accept(s_semi)) { check_on = 0; return 0; } // unchecked; switches the analyzer off from here on - chk = 0; - } - if (is_id(s_immediate)) { next(); imm = 1; } - o3nxt = 0; - if (is_id(s_o3)) { next(); o3nxt = 1; } - ntp = 0; ifkk = 0; - if (is_id(s_fn)) { - peek(); - if (!(pk_tk == T_OP && pk_tv == s_lp)) { - ifkp = tpack(); ifkk = 1; - if (isp != 0) ifkk = 0; - next(); - n = name(); - if (ginst) n = ginsn; - dty = 0; - genparams(); - return fndone(fndef(chk, 0, 0, n), imm); - } - } - if (!chk || imm || o3nxt) { // has to be a function, with a return type - ifkp = tpack(); ifkk = 0 - 1; if (is_op(s_dot)) ifkk = 2; - if (isp != 0) ifkk = 0; - rettype(); n = name(); if (ginst) n = ginsn; genparams(); - if (dtag && dtag != S_U) serr("the only return tag is .u"); - if (!is_op(s_lp)) { die("expected a function"); return 0; } - return fndone(fndef(chk, dty, dtag == S_U, n), imm); - } - if (is_id(s_var)) return gdecl(0); - if (is_id(s_struct)) { kw_struct(); return 0; } - if (is_id(s_type)) return kw_type(); - if (typedef_ahead()) return kw_typedef(); - if (is_id(s_typecheck)) return kw_typecheck(); - if (is_id(s_docs)) return kw_docs(); - if (is_id(s_memdebug)) return kw_memdebug(); - if (is_id(s_enum)) return kw_enum(); - if (is_id(s_include)) return kw_include(); - if (tk == T_ID && rt.eq_w(tv, "namespace")) return kw_namespace(); - if (is_id(s_enhance)) return enhdef(); - if (is_id(s_const)) { - peekat(0); - if (pk_tk != T_ID || pk_tv != s_fixed) return gconst(); - } - if (tk == T_ID) { - s = lookup(tv); - if (s >= 0 && skind[s] == K_KW && (sflag[s] & F_TOP)) { kwcall(s, 0); return 0; } - } - if (tstart() || (tk == T_ID && lookup(tv) < 0)) { // int f(...) { }, T first<T>(...) { }, int g = 1; - ifkp = tpack(); ifkk = 0 - 1; if (is_op(s_dot)) ifkk = 2; - if (isp != 0) ifkk = 0; - rettype(); n = name(); - if (is_op(s_lp) || is_op(s_lt)) { - if (ginst) n = ginsn; - genparams(); - if (dtag && dtag != S_U) serr("the only return tag is .u"); - return fndone(fndef(chk, dty, dtag == S_U, n), 0); - } - if (ntp) { serrn(tpn[0], "isn't a type"); ntp = 0; } - return gdecl2(dty, dtag, n); - } - die("expected a declaration"); - return 0; -} -// ---- generic functions: templates and instances ---- - -// which type parameter does name n stand for while an instance is compiled -int gfind(int n) { - int i = 0; - while (i < gng) { - if (gnm[i] == n) return i; - i++; - } - return 0 - 1; -} -// does a type still mention a type parameter? -word hasvar(word t) { - int k = t[0], i; word v; - if (k == TY_VAR) return 1; - if (k == TY_PTR || k == TY_ARR || k == TY_DYN) return hasvar(t[4]); - if (k == TY_FN) { - if (hasvar(t[4])) return 1; - i = 0; - while (i < t[3]) { - if (hasvar(t[7 + i])) return 1; - i++; - } - return 0; - } - if (k == TY_STRUCT && t[5]) { - v = t[6]; i = 0; - while (i < v[0]) { - if (hasvar(v[1 + i])) return 1; - i++; - } - } - return 0; -} -// add the current token to the item being recorded -int grec_add() { - word nb; - if (gn * 3 + 4 > gcap) { - nb = rt.new(gcap + gcap); - rt.copy_w(nb, gbuf, gcap); - rt.free(gbuf); - gbuf = nb; - gcap = gcap + gcap; - } - gbuf[1 + gn * 3] = tk; gbuf[2 + gn * 3] = tv; gbuf[3 + gn * 3] = tpack(); - gn++; - return 0; -} -// the template entry for generic function s, made on first sight of its header -int ginit() { - gtab = 1; - gt_sym = chk_resize(0, 0, gtcap); gt_tok = chk_resize(0, 0, gtcap); gt_np = chk_resize(0, 0, gtcap); - gt_nm = chk_resize(0, 0, gtcap * 8); gt_module = chk_resize(0, 0, gtcap); gt_imports = chk_resize(0, 0, gtcap); gt_source = chk_resize(0, 0, gtcap); - gi_t = chk_resize(0, 0, gicap); gi_sym = chk_resize(0, 0, gicap); gi_done = chk_resize(0, 0, gicap); - gi_ar = chk_resize(0, 0, gicap * 8); - return 0; -} -int reserve_generics(int need) { - int cap = gtcap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - gt_sym = chk_resize(gt_sym, gt_n, cap); gt_tok = chk_resize(gt_tok, gt_n, cap); - gt_np = chk_resize(gt_np, gt_n, cap); gt_nm = chk_resize(gt_nm, gt_n * 8, cap * 8); - gt_module = chk_resize(gt_module, gt_n, cap); gt_imports = chk_resize(gt_imports, gt_n, cap); gt_source = chk_resize(gt_source, gt_n, cap); - gtcap = cap; return cap; -} -int reserve_instances(int need) { - int cap = gicap; - if (need <= cap) return cap; - while (cap < need) cap = cap * 2; - gi_t = chk_resize(gi_t, gi_n, cap); gi_sym = chk_resize(gi_sym, gi_n, cap); - gi_done = chk_resize(gi_done, gi_n, cap); gi_ar = chk_resize(gi_ar, gi_n * 8, cap * 8); - gicap = cap; return cap; -} -// make the generic instance of the function or struct s for its type arguments. -int gtmake(int s) { - int k = 0; - if (!gtab) ginit(); - while (k < gt_n) { - if (gt_sym[k] == s) break; - k++; - } - if (k == gt_n) { - if (gt_n >= gtcap) reserve_generics(gt_n + 1); - gt_n++; - gt_sym[k] = s; gt_tok[k] = 0; gt_module[k] = module_name; gt_imports[k] = module_imports; gt_source[k] = curinc; - } - gt_np[k] = ntp; - rt.copy_w(gt_nm + k * 8, tpn, ntp); - return k; -} -// skip a function body without compiling it: from the { to its matching } -int gskip() { - int depth = 0; - if (!is_op(s_lb)) { die("expected { to start the function"); return 0; } - while (tk != T_EOF) { - if (is_op(s_lb)) depth++; - else if (is_op(s_rb)) { - depth--; - if (depth <= 0) { next(); break; } - } - next(); - } - return 0; -} -// the item just read defined generic function gnew: keep its tokens -int gattach(word .d buf, int n) { - n--; // the last token belongs to the next item - if (nerr) { rt.free(buf); return 0; } - buf[0] = n; - gt_tok[gnew] = buf; - return 0; -} -// retain one ordinary function's source tokens and the namespace needed to compile it again. -int fn_remember(int s, int name) { - int count = gn - 1, i = 0, j, depth, namepos = -1, open = -1, close = -1; word* q; word meta; - if (!grec || !gbuf || fn_method_compile || dry || ginst || nerr || (sflag[s] & F_GEN) || count <= 0) return 0; - while (i < count && open < 0) { - if (gbuf[1 + i * 3] == T_ID && gbuf[2 + i * 3] == name) { - j = i + 1; - if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_lt) { - depth = 1; j++; - while (j < count && depth) { - if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_lt) depth++; - else if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_gt) depth--; - j++; - } - } - if (j < count && gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_lp) { - depth = 1; j++; - while (j < count && depth) { - if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_lp) depth++; - else if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_rp) depth--; - j++; - } - while (j < count && !(gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_lb)) j++; - if (j < count) { namepos = i; open = j; } - } - } - i++; - } - if (open < 0) return 0; - depth = 1; j = open + 1; - while (j < count && depth) { - if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_lb) depth++; - else if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_rb) depth--; - if (depth) j++; - } - if (depth) return 0; - close = j; - // compiler storage, not program objects: internal, so live_objects and leak reports skip it - q = chk_resize(0, 0, 1 + count * 3); q[0] = count; rt.copy_w(q + 1, gbuf + 1, count * 3); - meta = chk_resize(0, 0, 6); meta[0] = q; meta[1] = module_name; meta[2] = module_imports; - meta[3] = namepos; meta[4] = open; meta[5] = close; fnmeta[s] = meta; - return 1; -} -// compile one retained source function for a syntax callback's host context. -// calls into emitted source are cloned recursively; existing host bindings retain -// their original globals and entry addresses. -int syntax_host_function(int symbol) { - word meta = fnmeta[symbol], source, full; int clone, count, name; - char[40] generated; char[24] reversed; int sequence = fnpatchseq, digits = 0, i = 0; - if (syntax_host_symbols[symbol]) return syntax_host_symbols[symbol] - 1; - if (!meta || (sflag[symbol] & F_GEN)) { serr("an emitted syntax dependency requires retained nongeneric source"); return -1; } - fnpatchseq++; - rt.copy_w(generated, "__run_syntax_", 13); - if (!sequence) reversed[digits++] = '0'; - while (sequence) { reversed[digits++] = '0' + sequence % 10; sequence = sequence / 10; } - while (i < digits) { generated[13 + i] = reversed[digits - i - 1]; i++; } - generated[13 + digits] = 0; name = intern(generated); - source = meta[0]; count = source[0]; full = rt.new(1 + count * 3); full[0] = count; - rt.copy_w(full + 1, source + 1, count * 3); full[2 + meta[3] * 3] = name; - if (meta[1]) clone = fwdfn(methname(meta[1], name)); else clone = fwdfn(name); - syntax_host_symbols[symbol] = clone + 1; - compile_list_context(full, 1, meta[1], meta[2]); rt.free(full); - return clone; -} -// source syntax executes in resident host code even when its library's runtime -// functions are emitted into a compiler-free image. staged symbols never enter -// the image blob; executable storage stays rooted by the ordinary code blocks. -int syntax_resident(int symbol) { - int saved_here = here, saved_delta = hdelta, saved_limit = hlimit, saved_ptop = ptop, saved_vislo = vislo; - int saved_active = image_active, saved_chunk = code_chunk, from = nsym, clone, entry = -1, top; - word block = chk_resize(0, 0, 4096); - syntax_host_symbols = chk_resize(0, 0, from); syntax_host_floor = ubase; syntax_host_from = from; - top = block + block[-5] - rt.HDR; - block[0] = rt.code_blocks; block[1] = block + 3; block[2] = top; rt.code_blocks = block; - if (rt.syscall(9, 9, top, 0) < top) fatal("syntax code address reservation failed"); - here = block + 3; hdelta = 0; hlimit = top; image_active = 0; vislo = 0; - clone = syntax_host_function(symbol); if (clone >= 0 && (sflag[clone] & F_DEF)) entry = sval[clone]; - popsyms(from); syntax_host_floor = 0; syntax_host_from = 0; rt.free(syntax_host_symbols); syntax_host_symbols = 0; - here = saved_here; hdelta = saved_delta; hlimit = saved_limit; ptop = saved_ptop; vislo = saved_vislo; image_active = saved_active; code_chunk = saved_chunk; - return entry; -} -// the symbol for template s with these type arguments, made on first use and -// queued to be compiled once the template's tokens are in. a type parameter -// that nothing bound is a plain word. -word ginstance(int s, word bs) { - int k = 0, t = 0 - 1, np, i, j, ns; char* nm; char[24] dg; int nd; char* a; - if (dry || !gtab) return s; - while (k < gt_n) { - if (gt_sym[k] == s) { t = k; break; } - k++; - } - if (t < 0) return s; - np = gt_np[t]; - i = 0; - while (i < np) { - if (!bs[i]) bs[i] = tword; - if (hasvar(bs[i])) return s; - i++; - } - k = 0; - while (k < gi_n) { - if (gi_t[k] == t) { - j = 0; - while (j < np && gi_ar[k * 8 + j] == bs[j]) j++; - if (j == np) return gi_sym[k]; - } - k++; - } - if (gi_n >= gicap) reserve_instances(gi_n + 1); - i = 0; j = 0; - a = sname[s]; nm = chk_resize(0, 0, rt.len_w(a) + np * 18 + 3); - while (a[j]) { nm[i] = a[j]; i++; j++; } - nm[i] = '<'; i++; - k = 0; - while (k < np) { - if (k) { nm[i] = ','; i++; } - a = bs[k]; nd = 0; - while (a && nd < 20) { dg[nd] = '0' + (a & 15); nd++; a = a >> 4; } - while (nd) { nd--; nm[i] = dg[nd]; i++; } - k++; - } - nm[i] = '>'; i++; nm[i] = 0; - ns = addsym(intern(nm), K_FN, tpack()); - sflag[ns] = F_SIG | (sflag[s] & F_O3); - ssig[ns] = ssig[s]; - k = gi_n; gi_n++; - gi_t[k] = t; gi_sym[k] = ns; gi_done[k] = 0; - rt.copy_w(gi_ar + k * 8, bs, np); - return ns; -} -// compile one instance: replay the template's tokens with its type -// parameters bound -int gcompile(int k) { - int t = gi_t[k]; char* oldmodule = module_name; word oldimports = module_imports; char* oldsource = curinc; int olddepth = incdepth; - curinc = gt_source[t]; if (curinc) incdepth++; - module_name = gt_module[t]; module_imports = gt_imports[t]; - ginst = 1; gng = gt_np[t]; - rt.copy_w(gnm, gt_nm + t * 8, 8); - rt.copy_w(gib, gi_ar + k * 8, 8); - ginsn = sname[gi_sym[k]]; - src_push(1, gt_tok[t], 0, 0); - next(); - topitem0(); - if (tk != T_EOF) die("unexpected tokens after a generic function"); - src_pop(); - ginst = 0; gng = 0; module_name = oldmodule; module_imports = oldimports; curinc = oldsource; incdepth = olddepth; - return 0; -} -// compile the instances asked for so far whose templates have been read, and -// any they ask for in turn -int gdrain() { - int k, more = 1; - if (infn || ginst) return 0; - if (panic) return 0; - while (more) { - more = 0; - k = 0; - while (k < gi_n) { - if (!gi_done[k] && gt_tok[gi_t[k]]) { - gi_done[k] = 1; - gcompile(k); - more = 1; - } - k++; - } - } - return 0; -} -// one top-level item, recording its tokens so that a generic function's can be kept -int topitem() { - int sb = gbuf, sn = gn, sc = gcap, sr = grec, sg = gnew, rb, rn; - if (ginst) return topitem0(); - gbuf = rt.new(256); gcap = 256; gn = 0; grec = 1; gnew = 0 - 1; - grec_add(); - topitem0(); - rb = gbuf; rn = gn; - grec = sr; gbuf = sb; gn = sn; gcap = sc; - if (gnew >= 0) gattach(rb, rn); else rt.free(rb); - gnew = sg; - gdrain(); - if (chk_hook && cmode && isp == 0 && !chkabort) chk_hook(); - return 0; -} -// a unit is compiled item by item; unit_end then reports any function that -// was called but never defined, and stops if any error was found -int unit_top() { - if (effect_floor < ubase) effect_floor = ubase; - while (tk != T_EOF) { - if (probe_off >= 0 && incdepth == 0 && toff > probe_off) { probe_n = 0; probe_has = 0; chkabort = 1; break; } - topitem(); - } - return 0; -} -// the edit distance between two names (levenshtein), or 99 when one is too long -word editdist(word a, word b) { - int la = rt.len_w(a), lb = rt.len_w(b), i, j, x, y, z, last, row[65]; - if (la > 60 || lb > 60) return 99; - j = 0; while (j <= lb) { row[j] = j; j++; } - i = 1; - while (i <= la) { - last = row[0]; row[0] = i; j = 1; - while (j <= lb) { - x = row[j] + 1; - y = row[j - 1] + 1; - z = last + (a[i - 1] != b[j - 1]); - last = row[j]; - if (y < x) x = y; - if (z < x) x = z; - row[j] = x; - j++; - } - i++; - } - return row[lb]; -} -// the part of a symbol a bare name is compared with: put_c for rt.put_c, the whole name otherwise -char* similar_tail(char* name) { - int k = 0; char* tail = name; - while (name[k] && name[k] != '.') k++; - if (name[k] && module_named(name, k)) tail = name + k + 1; - return tail; -} -// is name[0..k) a module the compiler knows? -int module_named(char* name, int k) { - char* prefix = chk_resize(0, 0, k + 1); int known; - rt.copy_w(prefix, name, k); prefix[k] = 0; - known = module_known(prefix); - rt.free(prefix); - return known; -} -// the defined function, global or constant whose name is closest to n, if it -// is close enough to be a likely typo; 0 when none is. a module's export is -// compared without its prefix, so putc finds rt.put_c -int similar(int n) { - int j = 0, best = 0, bd = 99, dist, k, lim = rt.len_w(n) / 3; - if (rt.len_w(n) < 3) return 0; // one edit turns g into gc: too short to guess from - if (lim < 1) lim = 1; - if (lim > 3) lim = 3; - while (j < nsym) { - k = skind[j]; - if (k == K_FN || k == K_GVAR || k == K_GARR || k == K_CONST) { - if (sflag[j] & F_DEF) { - if (sname[j] != n) { - dist = editdist(n, similar_tail(sname[j])); - if (dist < bd) { bd = dist; best = sname[j]; } - } - } - } - j++; - } - if (bd > lim) return 0; - return best; -} -// the field or method of struct sn closest to n (statics is 0), or the static function or constant -// (statics is 1), when it is close enough to be a likely typo. the limits are those of similar. -char* similar_member(char* sn, word st, char* n, int statics) { - int j, bd = 99, dist, k, ln = rt.len_w(sn), lim = rt.len_w(n) / 3, nf; char* best = 0; char* tail; - if (rt.len_w(n) < 3) return 0; - if (lim < 1) lim = 1; - if (lim > 3) lim = 3; - if (!statics && st && st[0] == TY_STRUCT) { - nf = st[3]; k = 0; - while (k < nf) { - tail = st[4][k * 3]; dist = editdist(n, tail); - if (dist == 2 && swapped(n, tail)) dist = 1; - if (dist > 0 && dist < bd) { bd = dist; best = tail; } - k++; - } - } - j = 0; - while (j < nsym) { - k = skind[j]; - if (((k == K_FN && ((sflag[j] & F_STATIC) != 0) == statics) || (statics && k == K_CONST)) && rt.len_w(sname[j]) > ln + 1 && sname[j][ln] == '.') { - tail = sname[j] + ln + 1; - if (has_prefix(sname[j], sn, ln) && !has_dot(tail)) { - dist = editdist(n, tail); - if (dist == 2 && swapped(n, tail)) dist = 1; - if (dist > 0 && dist < bd) { bd = dist; best = tail; } - } - } - j++; - } - if (bd > lim) return 0; - return best; -} -// are a and b the same but for two neighbouring chars swapped (itao for itoa)? a swap counts as one typo. -int swapped(char* a, char* b) { - int i = 0, la = rt.len_w(a); - if (la != rt.len_w(b)) return 0; - while (a[i] && a[i] == b[i]) i++; - if (!a[i] || !a[i + 1] || a[i] != b[i + 1] || a[i + 1] != b[i]) return 0; - i = i + 2; - while (a[i] && a[i] == b[i]) i++; - return !a[i]; -} -// does s start with the first n chars of p? -int has_prefix(char* s, char* p, int n) { - int i = 0; - while (i < n && s[i] == p[i]) i++; - return i == n; -} -// does the string contain a dot? -int has_dot(char* s) { - int i = 0; - while (s[i]) { if (s[i] == '.') return 1; i++; } - return 0; -} -// ends an error message with "; did you mean 'x'?" when a member of struct sn is close to n -int suggest_member(char* sn, word st, char* n, int statics) { - char* best = similar_member(sn, st, n, statics); - if (best) { rt.put_s("; did you mean '"); rt.put_s(best); rt.put_s("'?"); } - return 0; -} -// a copy of s[a..e) as a string -char* lib_copy(char* s, int a, int e) { - char* b = chk_resize(0, 0, e - a + 1); rt.copy_w(b, s + a, e - a); b[e - a] = 0; - return b; -} -// group.name, as a fresh string: the lists below outlive the name table of a check -char* lib_join(char* group, char* name) { - int g = rt.len_w(group), n = rt.len_w(name); char* b = chk_resize(0, 0, g + n + 2); - rt.copy_w(b, group, g); b[g] = '.'; rt.copy_w(b + g + 1, name, n); b[g + n + 1] = 0; - return b; -} -// the declarations of a library, read from its source text, as a list of [next, tail, path]: -// a top-level name's path is the name itself, a static method's is Group.name. the scan -// only looks at shapes: a name before ( or a declarator's = ; , at the top, or inside a -// struct's braces before (. qualified calls (run.statement_hook(...)) are not declarations. -word lib_scan(char* s) { - int i = 0, depth = 0, paren = 0, init = 0, la = -1, le = 0, dotted = 0, after_dot = 0, words = 0, skip = 0, gpend = 0, gdepth = -1, c, a, k; - char* group = 0; char* path; word list = 0, entry, back = 0; - while (s[i]) { - c = s[i]; - if (c == '/' && s[i + 1] == '/') { while (s[i] && s[i] != 10) i++; continue; } - if (c == '/' && s[i + 1] == '*') { i = i + 2; while (s[i] && !(s[i] == '*' && s[i + 1] == '/')) i++; if (s[i]) i = i + 2; continue; } - if (c == 34 || c == 39) { i++; while (s[i] && s[i] != c) { if (s[i] == 92 && s[i + 1]) i++; i++; } if (s[i]) i++; la = -1; continue; } - if (c == 32 || c == 9 || c == 10 || c == 13) { i++; continue; } - if (isdig(c)) { while (isidc(s[i])) i++; la = -1; continue; } - if (isal(c)) { - a = i; while (isidc(s[i])) i++; - if (gpend) { group = lib_copy(s, a, i); gpend = 0; entry = chk_resize(0, 0, 3); entry[0] = list; entry[1] = group; entry[2] = group; list = entry; } - if (depth == 0 && paren == 0 && !init) { - if (words == 0 && ((i - a == 9 && rt.eq_w(lib_copy(s, a, i), "namespace")) || (i - a == 7 && rt.eq_w(lib_copy(s, a, i), "include")))) skip = 1; - if (i - a == 6 && rt.eq_w(lib_copy(s, a, i), "struct")) { gpend = 1; group = 0; } - words++; - } - la = a; le = i; dotted = after_dot; after_dot = 0; - continue; - } - after_dot = c == '.'; - if (c == '<' && s[i + 1] != '<' && s[i + 1] != '=' && paren == 0 && !init && (depth == 0 || (depth == 1 && group))) { - // generic parameters: int find<T>(...) is still find - k = 1; i++; while (s[i] && k) { if (s[i] == '<') k++; if (s[i] == '>') k--; i++; } - continue; - } - if (c == '(' && paren == 0 && !init && la >= 0 && !dotted && !skip) { - if (depth == 0 || (depth == 1 && group && gdepth == 0)) { - path = lib_copy(s, la, le); - entry = chk_resize(0, 0, 3); entry[0] = list; entry[1] = path; entry[2] = path; - if (depth == 1) entry[2] = lib_join(group, path); - list = entry; - } - } - if (((c == '=' && s[i + 1] != '=') || c == ';' || c == ',' || c == '[') && depth == 0 && paren == 0 && !init && la >= 0 && !dotted && !skip && words > 1) { - entry = chk_resize(0, 0, 3); entry[0] = list; entry[1] = lib_copy(s, la, le); entry[2] = entry[1]; list = entry; - } - if (c == '=' && s[i + 1] != '=' && depth == 0 && paren == 0) init = 1; - if (c == '(' || c == '[') paren++; - if ((c == ')' || c == ']') && paren > 0) paren--; - if (c == '{') { if (depth == 0 && paren == 0 && group) gdepth = 0; else if (depth == 0) group = 0; depth++; } - if (c == '}' && depth > 0) { depth--; if (depth == 0) { group = 0; gdepth = -1; words = 0; skip = 0; init = 0; } } - if (c == ';' && depth == 0 && paren == 0) { init = 0; words = 0; skip = 0; group = 0; } - if (c == ',' && depth == 0 && paren == 0) init = 0; - la = -1; i++; - } - // first declared first: Text.itoa comes before Bytes.itoa - while (list) { entry = list[0]; list[0] = back; back = list; list = entry; } - return back; -} -// the embedded libraries, scanned once -word std_names, icon_names; int names_read; -word lib_names(char* module) { - if (!names_read) { names_read = 1; std_names = lib_scan(std_text()); icon_names = lib_scan(icon_text()); } - if (rt.eq_w(module, "std")) return std_names; - return icon_names; -} -// the libraries read from files, named in src/lib/modules.txt (one per line, # starts a -// comment), as a list of [next, name, declarations, scanned]. the compiler names none of them. -word file_libs; int file_libs_read; -word lib_files() { - int i = 0, a; word entry, tail = 0; char* save = incres; - if (file_libs_read) return file_libs; - file_libs_read = 1; - char* text = inc_open("src/lib/modules.txt"); - incres = save; - if (!text) return 0; - while (text[i]) { - while (text[i] == 32 || text[i] == 9 || text[i] == 10 || text[i] == 13) i++; - if (text[i] == '#') { while (text[i] && text[i] != 10) i++; continue; } - a = i; while (isidc(text[i])) i++; - if (i == a) { if (text[i]) i++; continue; } - entry = chk_resize(0, 0, 4); entry[1] = lib_copy(text, a, i); - if (tail) tail[0] = entry; else file_libs = entry; - tail = entry; - } - rt.free(text); - return file_libs; -} -// the entry of a file library, or 0 -word lib_file(char* name) { - word p = lib_files(); - while (p) { if (rt.eq_w(p[1], name)) return p; p = p[0]; } - return 0; -} -// a file library's declarations, read and scanned the first time they are needed -word lib_file_names(word entry) { - char* save = incres; - if (entry[3]) return entry[2]; - entry[3] = 1; - char* filename = inc_cat("src/lib", entry[1]); int length = rt.len_w(filename); - char* full = chk_resize(0, 0, length + 4); rt.copy_w(full, filename, length); rt.copy_w(full + length, ".zc", 4); - char* text = inc_open(full); - if (text) entry[2] = lib_scan(text); - rt.free(text); - incres = save; - return entry[2]; -} -// where name is in a library's list: Text.itoa is itself, a bare itoa is a top-level -// declaration first, then a static method of some group. 0 when it is not there. -char* lib_path(word list, char* name) { - word p = list; int k = 0; - while (p) { if (rt.eq_w(p[2], name)) return p[2]; p = p[0]; } - while (name[k]) { if (name[k] == '.') return 0; k++; } - p = list; - while (p) { if (rt.eq_w(p[1], name)) return p[2]; p = p[0]; } - return 0; -} -// the module that exports a bare name, and the path after its prefix: put_c is rt's put_c, -// itoa is std's Text.itoa. every owner, best first, is a list of [next, module, path]: the -// compiler's own modules, then the modules this unit loaded, then the libraries, which are -// read from their source whether this unit loaded them or not. -word export_list, export_tail; char* export_path; -int export_add(char* module, char* path) { - word p = export_list; - if (!path) return 0; - while (p) { if (rt.eq_w(p[1], module)) return 0; p = p[0]; } - p = chk_resize(0, 0, 3); p[1] = module; p[2] = path; - if (export_tail) export_tail[0] = p; else export_list = p; - export_tail = p; - return 0; -} -word export_owners(char* name) { - word p = included_modules; - export_list = 0; export_tail = 0; - export_add("rt", export_in("rt", name)); - export_add("core", export_in("core", name)); - export_add("run", export_in("run", name)); - while (p) { export_add(p[1], export_in(p[1], name)); p = p[0]; } - export_add("std", lib_path(lib_names("std"), name)); - export_add("icon", lib_path(lib_names("icon"), name)); - p = lib_files(); - while (p) { export_add(p[1], lib_path(lib_file_names(p), name)); p = p[0]; } - return export_list; -} -char* export_owner(char* name) { - word p = export_owners(name); - if (!p) return 0; - export_path = p[2]; return p[1]; -} -// name, when module defines it: a function, a global, a constant or a type of it. 0 otherwise. -char* export_in(char* module, char* name) { - int s = lookup_raw(methname(module, name)); - if (s < 0) return 0; - if (skind[s] != K_DOC && (skind[s] != K_FN || (sflag[s] & F_DEF))) return name; - return 0; -} -// end a compile unit from token from onward, and report what it defined. -int unit_end(int from) { - int i = from, best; char* owner; - effect_finish(from); - panic = 0; - while (i < nsym) { - // a bodyless declaration (fn f();) promises a definition elsewhere. in check mode that - // elsewhere is another file of the same build, which this document can't see - if (skind[i] == K_FN && !(sflag[i] & F_DEF) && sfix[i] && !(cmode && (sflag[i] & F_DECL)) && !missing_import_name(sname[i])) { - tunpack(sval[i]); - owner = export_owner(sname[i]); - best = 0; if (!owner) best = similar(sname[i]); - if (!dry) { - errat(); - rt.put_s(sname[i]); rt.put_s(" is used but never defined"); - if (owner) { - rt.put_s("; did you mean to "); - if (!module_imported(owner)) { rt.put_s("include "); rt.put_s(owner); rt.put_s(" and "); } - rt.put_s("use "); rt.put_s(owner); rt.put_c('.'); rt.put_s(export_path); rt.put_s("?"); - } - else if (best) { rt.put_s("; did you mean '"); rt.put_s(best); rt.put_s("'?"); } - errend(); - } - } - i++; - } - missing_imports = 0; - if (nerr) giveup(); - return 0; -} - -// compile a token list as the body of a new function, or (top) as top-level -// declarations. returns the entry address of the function. -// (this is what eval, compile and load are built on. a function body made this -// way is emitted behind a jump when another function is being compiled, so -// compile() works inside a parse word.) -int compile_list_context(int q, int top, char* module, word imports) { - int hop = -1, entry = 0, from = nsym; - ctx_push(); ifcur = 0; ifcall = -1; effect_floor = from; module_name = module; module_imports = imports; - if (infn) { hop = hole(); op(O_JMP); } - if (top) prescan(1, q); - src_push(1, q, 0, 0); - next(); - if (top) unit_top(); - else { - entry = here; - fn_begin(1, 0); - prologue(); borrow_prologue(); borrow_begin(); - while (tk != T_EOF) stmt(); - unused_check(lbase); - if (fchk && live) exit_vars(1, 1); - epilogue(); - } - src_pop(); - unit_end(from); - if (hop >= 0) setlit(hop, here); - if (here > ptop) ptop = here; - ctx_pop(); - return entry; -} -int compile_list(int q, int top) { return compile_list_context(q, top, module_name, module_imports); } -// return an independent copy of a retained function body, excluding its braces. -word*.u function_tokens(int addr) { - int s = 0, open, close, count; word meta, source, result; - while (s < nsym && !(skind[s] == K_FN && sval[s] == addr && fnmeta[s])) s++; - if (s >= nsym) return 0; - meta = fnmeta[s]; source = meta[0]; open = meta[4]; close = meta[5]; count = close - open - 1; - result = rt.new(1 + count * 3); result[0] = count; - if (count) rt.copy_w(result + 1, source + 1 + (open + 1) * 3, count * 3); - return result; -} -// compile a changed body under a fresh name, check its signature, then redirect -// the original entry so existing calls and function values use the new code. -int replace_function(int addr, word* body_tokens) { - int s = 0, open, close, namepos, olderr = nerr, oldpanic = panic, oldabort = chkabort; - int errors, generated_id, new_symbol, count, i = 0, out = 0, cap, diag, seq, digits = 0, published; - char[40] generated; char[24] reversed; word meta, source, full, imports; char* module; - while (s < nsym && !(skind[s] == K_FN && sval[s] == addr && fnmeta[s])) s++; - if (s >= nsym || !body_tokens || body_tokens[0] < 0) return 0; - meta = fnmeta[s]; source = meta[0]; open = meta[4]; close = meta[5]; namepos = meta[3]; module = meta[1]; imports = meta[2]; - count = source[0] - (close - open - 1) + body_tokens[0]; - full = rt.new(1 + count * 3); full[0] = count; - i = 0; while (i <= open) { rt.copy_w(full + 1 + out * 3, source + 1 + i * 3, 3); i++; out++; } - i = 0; while (i < body_tokens[0]) { rt.copy_w(full + 1 + out * 3, body_tokens + 1 + i * 3, 3); i++; out++; } - i = close; while (i < source[0]) { rt.copy_w(full + 1 + out * 3, source + 1 + i * 3, 3); i++; out++; } - seq = fnpatchseq; fnpatchseq++; - rt.copy_w(generated, "__run_patch_", 12); generated[12] = 0; - if (!seq) { reversed[digits] = '0'; digits++; } - else { while (seq > 0) { reversed[digits] = '0' + seq % 10; digits++; seq = seq / 10; } } - i = 0; while (i < digits) { generated[12 + i] = reversed[digits - i - 1]; i++; } - generated[12 + digits] = 0; generated_id = intern(generated); full[2 + namepos * 3] = generated_id; - cap = 4096; diag = rt.new(cap); chk_begin(diag, cap); - compile_list_context(full, 1, module, imports); - errors = nerr || chkabort || panic; - chk_end(); nerr = olderr; panic = oldpanic; chkabort = oldabort; - if (module) new_symbol = lookup_raw(methname(module, generated_id)); else new_symbol = lookup_raw(generated_id); - if (!errors && new_symbol >= 0 && skind[new_symbol] == K_FN && (ssig[new_symbol] == ssig[s]) && (stype[new_symbol] == stype[s])) { - setlit(addr, sval[new_symbol]); *(addr + 1 + hdelta) = O_JMP + O_JMP; - // subsequent transformations start from the body that was published. - if (fnmeta[new_symbol]) fnmeta[s] = fnmeta[new_symbol]; - published = 1; - } else published = 0; - rt.free(diag); rt.free(full); return published; -} -// compile a token list into a function and return its address. -int compile(int q) { return compile_list(q, 0); } -// compile a token list and run its top-level code. -int load(int q) { compile_list(q, 1); return 0; } -// compile a token list and run it, returning its result. -int eval(int q) { int f = compile_list(q, 0); return f(); } -// compile the source string s and run it, returning its result. -int evals(char* s) { - word q = tokens(s); int r = eval(q); - rt.free(d*q); - return r; -} - -// ---- symbol blob ---- - -// an image ends with its own symbol table so the genome knows its names: -// kind, value, signature, name chars, 0 ... then a 0 kind. -// emit the names defined since symbol index from (functions, globals, -// constants, enhancements) as the image's trailing symbol table -int blob_emit(int from) { - int i = from, table, words = 1; word p; int b, k; - while (i < nsym) { - if (skind[i] == K_DOC) { b = sval[i]; words = words + 2 + (b[0] >> 3); } - if ((skind[i] >= K_FN && skind[i] <= K_CONST) || skind[i] == K_ENH || skind[i] == K_DOC) words = words + 4 + rt.len_w(sname[i]); - i++; - } - reserve_code(words); i = from; - // the docs first: each one's bytes go in the image here, and its symbol - // takes their address (the bytes come after the code, so no code moves) - while (i < nsym) { - if (skind[i] == K_DOC) { - b = sval[i]; - sval[i] = here; - emit(b[0]); - k = 0; - while (k < ((b[0] + 7) >> 3)) { emit(b[1 + k]); k++; } - if (!(b[0] & 7)) emit(0); - } - i++; - } - table = here; i = from; - while (i < nsym) { - if ((skind[i] >= K_FN && skind[i] <= K_CONST) || skind[i] == K_ENH || skind[i] == K_DOC) { - emit(skind[i]); emit(sval[i]); emit(ssig[i]); - p = sname[i]; - while (*p) { emit(*p); p++; } - emit(0); - } - i++; - } - emit(0); - return table; -} -// the reverse: read a blob and define its names, so a running genome knows its -// own functions by name -// hide: leave out the language server's names. functions are entered before -// constants and globals in the blob, so the server's functions come after -// chk_mark but the runtime's constants and globals come after it too: the -// cut is by name, the functions after chk_mark, and everything from the -// server's marker const (LSP_SERVER, the first thing src/lsp/server.zc declares) -int blob_load(word p, int hide) { - int s, n, past = 0, after = 0, skip, mkn = intern("run.chk_mark"), srvn = intern("lsp.LSP_SERVER"); - while (*p) { - n = intern(p + 3); - if (hide && n == srvn) after = 1; - skip = 0; - if (hide && n == mkn) past = 1; - if (!skip) { - s = addsym(n, p[0], p[1]); - ssig[s] = p[2]; - sflag[s] = F_DEF | F_SIG; - if (p[0] == K_ENH) { if (p[1] >= nenh) nenh = p[1] + 1; rt.install(p[2] >> 8); } - } - p = p + 3 + rt.len_w(p + 3) + 1; - } - return 0; -} -// compile the rest of stdin for org into a growing output buffer. the image's -// addresses remain logical addresses at org while its temporary storage grows. -// image_words and image_size expose the finished image; initial is its first -// buffer reservation. the matching host layout must still fit the finished image. -int build_image_grow(int org, int initial) { - int sh = here, sd = hdelta, sl = hlimit, sub = ubase, svl = vislo, sns = nsym, sen = nenh, n; - word saved_buffer = image_buffer; int saved_active = image_active, saved_org = image_org, saved_capacity = image_capacity; - if (org < 0 || initial < 7 || initial > 0x7fffffff - org) fatal("bad image reservation"); - image_buffer = chk_resize(0, 0, initial); image_active = 1; image_org = org; image_capacity = initial; - ubase = nsym; vislo = nsym; nenh = 0; - here = org; hdelta = image_buffer - org; hlimit = org + initial; - lit(rt.FTOP); lit(rt.ZFP); op(O_ST); fnref(fwdfn(s_start)); op(O_JMP); emit(0); emit(0); - unit_stdin(); - n = blob_emit(ubase); - image_buffer[5] = n; - image_buffer[6] = here; - n = here - org; image_words = image_buffer; image_size = n; - popsyms(sns); - ubase = sub; vislo = svl; here = sh; hdelta = sd; hlimit = sl; nenh = sen; - image_buffer = saved_buffer; image_active = saved_active; image_org = saved_org; image_capacity = saved_capacity; - return n; -} -// retain the explicit destination interface for callers that already own storage. -// use build_image_grow and image_words when the output must grow independently. -int build_image(int org, int dst) { - int n = build_image_grow(org, 4096), words = rt.syscall(9, 0, 0, 0); - if (dst < 0 || n > words - dst) fatal("image destination too small"); - rt.copy_w(dst, image_words, n); - return n; -} - -// ---- setup ---- - -// the collector's roots for the compiler's half of the memory map: the program -// area (globals live between functions), the genome's globals, the input source -// frames, the live frame stack and the live data stack. rt.zc declares this -// and calls it from gc(). -int gc_roots() { - int end = ptop; if (end > rt.CODE_END) end = rt.CODE_END; - rt.markrange(0, end); - if (!hdelta && here > ptop && ptop < rt.CODE_END) { end = here; if (end > rt.CODE_END) end = rt.CODE_END; rt.markrange(ptop, end); } // globals of a unit still compiling - rt.markrange(rt.GORG, rt.GORG[6]); - rt.markrange(R_IH, R_POOLEND); - rt.markrange(sval, sval + nsym); - rt.markrange(R_TY, R_TYEND); - rt.markrange(R_SRC, R_SRC + R_SRC_cap); - rt.gc_frames(); - rt.markrange(rt.dsp(), rt.data_top); - return 0; -} -// start-up in two parts. zc_init clears the intern table and interns the -// spellings the parser compares against; zc_tables fills the operator and -// keyword tables. an operator is a name, a precedence and a code sequence in -// the vm's own one-letter mnemonics (see seqasm), so "<=" is "~ < 1 ^": swap, -// less-than, flip the bit. -int zc_init() { - receiver_methods = 0; closure_expected = 0; - effect_floor = 0; effect_symbols = 0; effect_edges = 0; effect_requirements = 0; effect_current = 0; - pooltop = R_POOL; - poolend = R_POOLEND; poolblocks = 0; namecount = 0; - rt.fill_w(namehash, 0, hashcap); - nsym = 0; nfixu = 0; fixfree = 0; nbin = 0; nun = 0; nasg = 0; nkop = 0; seqtop = 0; - lsp = 0; ctxsp = 0; ntd = 0; snaptop = 0; nloc = 0; lbase = 0; npm = 0; npe = 0; rt.fill_w(lkeys, 0, lkeycap); lkeyn = 0; - s_lp = intern("("); s_rp = intern(")"); s_lb = intern("{"); s_rb = intern("}"); - s_lk = intern("["); s_rk = intern("]"); s_comma = intern(","); s_semi = intern(";"); - s_eq = intern("="); s_arrow = intern("->"); s_inc = intern("++"); s_dec = intern("--"); - s_star = intern("*"); - s_slash = intern("/"); - s_amp = intern("&"); - s_colon = intern(":"); - s_hash = intern("#"); - s_minus = intern("-"); - s_dot = intern("."); - s_q = intern("?"); - s_case = intern("case"); - s_default = intern("default"); - s_andand = intern("&&"); - s_oror = intern("||"); - s_fn = intern("fn"); s_var = intern("let"); s_const = intern("const"); s_else = intern("else"); - s_as = intern("as"); s_unchecked = intern("unchecked"); s_immediate = intern("immediate"); - s_m = intern("m"); s_d = intern("d"); s_u = intern("u"); s_static = intern("static"); - s_start = intern("run.start"); - s_main = intern("main"); - s_free = intern("rt.free"); - s_recycle = intern("rt.recycle"); s_copy_w = intern("rt.copy_w"); - s_do = intern("do"); - s_while = intern("while"); - s_enum = intern("enum"); - s_include = intern("include"); - s_memdebug = intern("memdebug"); s_scrub = intern("rt.scrub"); s_alloc = intern("rt.alloc_line"); - s_check = intern("rt.checkptr"); s_memset = intern("rt.mem_set"); - s_own = intern("rt.own"); s_qnew = intern("run.qnew"); s_fovf = intern("rt.frame_overflow"); - s_frame_floor = intern("rt.frame_floor"); s_frame_top = intern("rt.frame_top"); s_frame_node = intern("rt.frame_node"); - s_frame_busy = intern("rt.frame_busy"); s_frame_guard = intern("rt.frame_guard"); - s_frame_grow = intern("rt.frame_grow"); s_frame_leave = intern("rt.frame_leave"); - s_enhance = intern("enhance"); s_micro = intern("micro"); s_vm = intern("vm"); - return 0; -} -// operators and keywords. these are plain tables in memory: a program can -// add operators, change precedences or replace keywords with its own. -// (the order of the first binary operators matters: cexpr refers to * + - << >> -// & | by their index, and the comments mark which ones) -int zc_tables() { - add_unop("!", "0 $ 0 ~ - | 63 > 1 +"); // index 0 - add_unop("!!", "0 $ 0 ~ - | 63 > 0 ~ -"); // index 1: normalize to 0 or 1 - add_unop("~", "-1 ^"); // index 2 - add_unop("-", "0 ~ -"); - add_binop("*", 10, "*"); // the first few indexes are used by cexpr - add_binop("/", 10, "idiv"); // native divide, wired in by rt.zc - add_binop("%", 10, "imod"); - add_binop("+", 9, "+"); // 3 - add_binop("-", 9, "-"); // 4 - add_binop("<<", 8, "ishl"); // 5 - add_binop(">>", 8, ">"); // 6 - add_binop("<", 7, "<"); - add_binop(">", 7, "~ <"); - add_binop("<=", 7, "~ < 1 ^"); - add_binop(">=", 7, "< 1 ^"); - add_binop("==", 6, "- 0 $ 0 ~ - | 63 > 1 +"); - add_binop("!=", 6, "- 0 $ 0 ~ - | 63 > 0 ~ -"); - add_binop("&", 5, "&"); // 13 - add_binop("^", 4, "^"); - add_binop("|", 3, "|"); // 15 - add_binop("&&", -1, 0); - add_binop("||", -2, 0); - addkop(s_eq); addkop(s_arrow); addkop(s_inc); addkop(s_dec); - add_kw("let", kw_var, F_STMT); - add_kw("if", kw_if, F_STMT); - add_kw("while", kw_while, F_STMT); - add_kw("for", kw_for, F_STMT); - add_kw("do", kw_do, F_STMT); - add_kw("enum", kw_enum, F_STMT | F_TOP); - add_kw("include", kw_include, F_TOP | F_STMT); - add_kw("embed", kw_embed, F_TOP | F_STMT); - add_kw("run.method", kw_method, F_TOP | F_STMT); - add_kw("run.share_value", kw_share_value, F_EXPR); - add_kw("run.syntax", kw_syntax, F_TOP | F_STMT); - add_kw("run.statement_hook", kw_statement_hook, F_TOP | F_STMT); - add_kw("memdebug", kw_memdebug, F_STMT | F_TOP); - add_kw("hints", kw_hints, F_STMT | F_TOP); - add_kw("switch", kw_switch, F_STMT); - add_kw("break", kw_break, F_STMT); - add_kw("continue", kw_continue, F_STMT); - add_kw("return", kw_return, F_STMT); - add_kw("unchecked", kw_unchecked, F_STMT); - add_kw("asm", kw_asm, F_STMT | F_EXPR); - add_kw("quote", kw_quote, F_EXPR); - add_kw("sizeof", kw_sizeof, F_EXPR); - add_kw("typeof", kw_typeof, F_EXPR); - add_kw("extern", kw_extern, F_TOP); - add_kw("cfunc", kw_cfunc, F_EXPR); - add_kw("o3", kw_o3, F_STMT); - add_kw("len", kw_len, F_EXPR); - add_kw("cap", kw_cap, F_EXPR); - add_kw("push", kw_push, F_EXPR); - add_kw("pop", kw_pop, F_EXPR); - add_kw("resize", kw_resize, F_EXPR); - return 0; -} -// the default map's type records sit in the last 16384 words of the heap. -// the type list is the first words of that range, so it cannot be kept aside -// as a buddy block: types_init would overwrite the header and the allocator -// would hand the range out again. cut the heap off at R_TY until the unit -// has been compiled, then put the tail back for the program. -int heldk; -// the size class of the type area, for the memory map. -int type_class() { - int k = 0, n = R_TYEND - R_TY; - while (k < 31 && rt.pow2[k] < n) k++; - if (rt.pow2[k] != n) return 0 - 1; - return k; -} -// keep the type area out of the collector's reach while it is in use. -int shield_types() { - int k; - heldk = 0 - 1; - if (R_TY <= rt.HEAP_LO || R_TY >= rt.HEAP_HI || R_TYEND > rt.HEAP_HI) return 0; - k = type_class(); - if (k < 0) return 0; - if (R_TY + rt.pow2[k] != R_TYEND) return 0; - if (rt.heap_reset(R_TY)) heldk = k; - return 0; -} -// let the collector see the type area again. -int unshield_types() { - if (heldk >= 0) { rt.heap_uncut(R_TY, heldk); heldk = 0 - 1; } - return 0; -} -// the genome's entry point, run when the image boots: set up the heap and -// the compiler, then either run the language server (a genome that carries it) -// or compile the unit on stdin, collect the garbage that compiling made, and -// call main(). -int start() { - int s, f; - rt.heap_init(); chk_heap = 1; - shield_types(); - zc_init(); - blob_load(rt.GORG[5], 0); - zc_tables(); - types_init(); - here = 0; hdelta = 0; hlimit = rt.CODE_END; ptop = 0; - ubase = nsym; vislo = 0; - // a genome that carries the language server runs it instead of compiling stdin - s = lookup(intern("lsp.lsp_main")); - if (s >= 0 && skind[s] == K_FN && (sflag[s] & F_DEF)) { f = sval[s]; f(); rt.halt(); } - unit_stdin(); - if (here > ptop) ptop = here; - rt.scrub(); // compilation's stale words would keep quarantined blocks alive - rt.gc(); - // the type area stays out of the allocator. main() may compile again - // (the self-host does) and a later new() would otherwise reuse it. - if (mdbg_flags) rt.mem_set(mdbg_flags); - // a unit that carries src/lib/std.zc has a libinit to fill argc/argv/envc/envp - // with. it runs before main, the same way lsp_main runs instead of stdin. - s = lookup(intern("std.libinit")); - if (s >= 0 && skind[s] == K_FN && (sflag[s] & F_DEF)) { f = sval[s]; f(); } - s = lookup(s_main); - if (s >= 0 && skind[s] == K_FN && (sflag[s] & F_DEF)) { f = sval[s]; f(); } - rt.halt(); - return 0; -} - -// ---- check mode: the language server ---- - -// the server runs this compiler, unchanged, over a document it holds in -// memory. chk_unit compiles it as a whole unit, in a machine set up the way -// start() sets one up (the genome's own names, the default operators and -// keywords), so the document's parse words, operators and types change what -// the rest of it means exactly as they would in a real build. the symbols -// the document made stay in the tables afterwards for the highlighter. -// chk_mark is the last function of the compiler proper, and LSP_SERVER (in -// src/lsp/server.zc) the first constant of the server: the server's names are -// hidden from the document, the runtime's and the compiler's are not. -int chk_mark() { return 0; } -// reset check mode for a new document. -int chk_reset() { - statement_hooks = 0; included_modules = 0; - if_local = 0; if_edges = 0; if_value = 0; - rt.code_blocks = 0; code_chunk = rt.CODE_END; - isp = -1; - docs_on = 1; dn = 0; dline = 0; docpending = 0; dsect = 0; dsn = 0; dnm = 0; - pq_n = 0; - pendot = 0; - qdone = 0; - ch = 0; - chkind = 0; - line = 1; - tk = 0; - tv = 0; - tglue = 0; - tline = 1; - toff = 0 - 1; toklen = 0; tpend = 0 - 1; tpline = 1; - infn = 0; - fdepth = 0; - fslots = 0; - fchk = 0; - fret = 0; - live = 0; - bxdepth = 0; - epichain = 0; - fframe = 0; - xbor = 0 - 1; xaf = 0; argfact = 0; xvk = 0; - if (svkind) rt.fill_w(svkind, 0, symcap); - mdbg = 0; mdbg_flags = 0; lv = 0; lvk = 0; lvi = 0; kw_expr = 0; xcls = 0; fty = 0; - check_on = 1; tc_on = ifstrict; dry = 0; ntp = 0; tvauto = 0; ngbind = 0; xgive = 0; xconst = 0; - o3on = 0; o3nxt = 0; no3pend = 0; no3sl = 0; no3free = 0; o3nreg = 0; o3na = 0; o3fn0 = 0; - nfact = 0; fortest = 0; forstep = 0; argloc = 0; arrloc = 0; njps = 0; extop = 0; nnumops = 0; - nenh = 0; panic = 0; nerr = 0; dgprev = 0; nclo = 0; newsz = 0 - 1; nomagic = 0; callpos = 0; - grec = 0; gnew = 0 - 1; gt_n = 0; gi_n = 0; ginst = 0; gtab = 0; - return 0; -} -// src: the document, 4 chars to a word and a 0 at the end. buf, cap: where -// the records go. sets: 4 * LSW words for the name sets. lim: code may grow up -// to here. every block the caller holds is a heap object reachable from its -// globals, so the check's garbage is collected and nothing is cut off. -// check one document in check mode and fill the records it made. -int chk_unit(int src, int buf, int cap, int sets, int lim) { - chk_reset(); - module_name = 0; module_imports = 0; - chk_begin(buf, cap); - lsets = sets; - if (sets) rt.fill_w(sets, 0, 4 * LSW); - if (!chk_heap) { rt.heap_init(); chk_heap = 1; } else rt.gc(); // the caller's blocks are reachable from its globals, the rest goes - rsn = 0; evn = 0; hl_n = 0; literal_doc_n = 0; if (literal_docs) rt.fill_w(literal_docs, 0, literal_doc_cap); curinc = 0; std_done = 0; icon_done = 0; incdepth = 0; dgdrop = 0; probe_has = 0; probe_n = 0; probe_mem = 0; probe_static = 0; nameoff = 0 - 1; rsbase = src; - if (!rsrec) { rscap = RS_CAP; rsrec = rt.new(RS_CAP * RS_W); rstmp = rt.new(RS_CAP * RS_W); } - ifs = 0; ifx = 0; nifx = 0; nift = 0; nif = 0; ifcur = 0; ifkk = 0; ifgk = 0; ifcall = 0 - 1; - if (ifon) { - ifrcap = NIF; ifxcap = IFN; iftcap = IFT; - ifs = rt.new(symcap); ifr = chk_resize(0, 0, ifrcap); ifx = chk_resize(0, 0, ifxcap * 4); ift = chk_resize(0, 0, iftcap); - if_local = chk_resize(0, 0, loccap); if_edges = 0; if_value = 0; - rt.fill_w(ifs, 0, symcap); - } - zc_init(); - blob_load(rt.GORG[5], 1); - zc_tables(); - types_init(); - here = 0; hdelta = 0; hlimit = lim; ptop = 0; - ubase = nsym; vislo = 0; - prescan(3, src); - src_push(3, src, 0, 0); - next(); - unit_top(); - rs_fix(); - unit_end(ubase); - if (ifx) if_emit(); - src_pop(); - rs_sort(); - chk_end(); - return 0; -} -// walk a document's tokens with the lexer as the check left it. hl_begin -// loads the first token into tk, tv, toff and toklen; next() moves on; hl_end -// restores the previous state. -// the syntax highlighter's view of the lexer. it re-lexes the document after -// the check, so the tokens it sees are cut exactly where the compiler's were -// (parse words that changed the operators included). -int hl_begin(int src) { - chkabort = 0; isp = -1; pq_n = 0; pendot = 0; qdone = 0; panic = 0; - dry++; - src_push(3, src, 0, 0); - next(); - return 0; -} -// end a highlight pass: leave the source and the dry run. -int hl_end() { - src_pop(); - dry--; - isp = -1; - pq_n = 0; - return 0; -} -// the source of the std and icon libraries, embedded when the compiler is built, so -// `include std;` and `include icon;` need no file at run time. -embed "src/lib/std.zc" as bstr std_embedded; -embed "src/lib/icon.zc" as bstr icon_embedded; -bstr std_source() { return std_embedded; } -bstr icon_source() { return icon_embedded; } diff --git a/src/compiler/decl.zc b/src/compiler/decl.zc new file mode 100644 index 0000000..45269a4 --- /dev/null +++ b/src/compiler/decl.zc @@ -0,0 +1,1413 @@ +// ---- reading a unit and scanning it for signatures ---- + +// a unit from stdin is read whole first (4 chars to a word), then scanned +// once for function signatures, so a function's tags are known before its first +// call even when it's defined further down. +// read the whole unit from stdin, up to a backtick (outside comments and +// literals, see srcend) or end of input, packing 4 +// characters to a word, so prescan can run over it and then the real parse can +// run over the same text +word .u readsrc() { + int cap = 4090; word b; int n = 0, c; word nb; int w; + b = rt.new(4090); + qst = 0; qp = 0; qesc = 0; + while (!asm { ? }) { + c = asm { , } & 255; + if (c == 255 && asm { ? }) break; // the end of a slow pipe, found by the read itself + // srcend only has anything to decide at a quote, a slash or a star (or + // inside a comment or literal); every other character in plain code just + // clears the "previous char was a slash" memory + if (qst || c == 34 || c == 39 || c == '/' || c == '*' || c == 96) { if (srcend(c)) break; } + else qp = 0; + if ((n >> 2) + 2 >= cap) { + nb = rt.new(cap + cap + 6); + rt.copy_w(nb, b, cap); + rt.free(b); + b = nb; + cap = cap + cap + 6; + } + w = n >> 2; + if (!(n & 3)) b[w] = 0; + b[w] = b[w] | (c << ((n & 3) * 8)); + n++; + } + b[n >> 2] = b[n >> 2] & (rt.pow2[(n & 3) * 8] - 1); // the 0 at the end + if ((n & 3) == 0) b[n >> 2] = 0; + b[(n >> 2) + 1] = 0; + return b; +} +// skip a <...> group (type parameters) +// prescan: before a unit is compiled, one dry pass over it records the +// signature (parameter tags, .u return, o3) of every function declared in it, +// so a call that appears before its definition is checked against the tags +// the callee will have. it only needs the shape of a declaration, not its body. +int pskipangle() { + int dp = 0; + while (tk != T_EOF) { + if (is_op(s_lt)) dp++; + else if (is_op(s_gt)) dp--; + else if (is_op(s_shr)) dp = dp - 2; + next(); + if (dp <= 0) return 0; + } + return 0; +} +// at the name after fn: read the signature's tags and record them +// prescan: at the ( after a function name, read the parameter list and record +// the signature packed into ssig (bit 0 .u return, bits 1..5 parameter count, +// two bits per parameter from bit 6) +int prescan_fn(int ret, int n, int o3) { + int np = 0, tags = 0, t = 0, dp, s; + next(); // the ( + if (!is_op(s_rp)) { + np = 1; dp = 1; + while (tk != T_EOF) { + if (is_op(s_lp)) dp++; + else if (is_op(s_rp)) { dp--; if (!dp) break; } + else if (dp == 1 && is_op(s_comma)) { if (np <= 12) tags = tags + (t << (np + np - 2)); np++; t = 0; } + else if (dp == 1 && is_op(s_dot)) { next(); if (is_id(s_m)) t = 1; else if (is_id(s_d)) t = 2; continue; } + next(); + } + if (np <= 12) tags = tags + (t << (np + np - 2)); + } + next(); + if (!is_op(s_lb) && !is_op(s_semi)) return 0; // not a function: an enhancement, a call in a parse word... + if (!dnm) dnm = n; + s = lookup(n); + if (s >= 0 && skind[s] == K_KW) return 0; + if (s < 0 || s < ubase || skind[s] != K_FN || (sflag[s] & F_DEF)) s = fwdfn(n); + if (!(sflag[s] & F_SIG)) { + ssig[s] = ret + np + np + tags * 64; + sflag[s] = sflag[s] | F_SIG | F_PRE; + } + if (o3) sflag[s] = sflag[s] | F_O3; + return 0; +} +// scan ahead, without emitting code, to learn what a block of tokens contains. +int prescan(int kind, int obj) { + int depth = 0, sp = panic, sn = nerr, pend = 0, po3 = 0, n, dep; char* oldmodule = module_name; word oldimports = module_imports; + dry++; + src_push(kind, obj, 0, 0); + next(); + while (tk != T_EOF) { + if (is_op(s_lb)) depth++; + else if (is_op(s_rb)) { depth--; pend = 0; po3 = 0; } + else if (depth <= 0) { + if (is_id(s_type) || typedef_ahead()) { while (tk != T_EOF && !is_op(s_semi)) next(); if (is_op(s_semi)) next(); pend = 0; po3 = 0; continue; } + if (tk == T_ID && rt.eq_w(tv, "namespace")) { kw_namespace(); continue; } + if (is_op(s_semi)) { pend = 0; po3 = 0; } + else if (is_op(s_dot)) { peek(); if (pk_tk == T_ID && pk_tv == s_u) pend = 1; } + else if (is_id(s_o3)) po3 = 1; + else if (tk == T_ID) { + peek(); + if (pk_tk == T_OP && pk_tv == s_lp) { + n = tv; + next(); + prescan_fn(pend, n, po3); + pend = 0; + po3 = 0; + continue; + } + if (pk_tk == T_OP && pk_tv == s_lt) { // list<T> in a type, or first<T>( a generic function + n = tv; next(); dep = 0; + while (tk != T_EOF) { + if (is_op(s_lt)) { + dep++; + } else if (is_op(s_gt)) { + dep--; + } else if (is_op(s_shr)) dep = dep - 2; + next(); + if (dep <= 0) break; + } + if (is_op(s_lp)) { prescan_fn(pend, n, po3); pend = 0; po3 = 0; } + continue; + } + } + } + next(); + } + src_pop(); + dry--; panic = sp; nerr = sn; module_name = oldmodule; module_imports = oldimports; + return 0; +} +// compile a whole unit from stdin +int unit_stdin() { + int src = readsrc(); + module_name = 0; module_imports = 0; + prescan(3, src); + src_push(3, src, 0, 0); + next(); + unit_top(); + unit_end(ubase); + src_pop(); + rt.free(src); + return 0; +} + +// ---- nested compiles ---- + +// compile() and load() can run while another function is being compiled (from +// a parse word). the function context is saved, the new code is emitted in +// place with a jump around it, and then everything is put back. +// the context frame is 34 words: everything about the function being compiled +// that a nested compile would otherwise clobber (locals, depth, frame size, +// loop stack position, the expression-state globals). closures use the same +// pair. +const int CTX_WORDS = 48; +int ctx_push() { + af_forget(); + word f; + reserve_R_CTX((ctxsp + 1) * CTX_WORDS); f = R_CTX + ctxsp * CTX_WORDS; + ctxsp++; + f[0] = nloc; f[1] = lbase; f[2] = fdepth; f[3] = fslots; f[4] = fchk; f[5] = fret; f[6] = live; + f[7] = bxdepth; f[8] = epichain; f[9] = fframe; f[10] = infn; f[11] = lsp; f[12] = loopbase; + f[13] = lv; f[14] = lvk; f[15] = lvi; f[16] = kw_expr; f[17] = xcls; f[18] = snaptop; + f[19] = fty; f[20] = ntp; f[21] = xty; rt.copy_w(f + 22, tpn, 8); + f[30] = finsym; f[31] = finfer; f[32] = ifcur; f[33] = ifcall; f[34] = effect_current; f[35] = borrow_slots; f[36] = borrow_pending; f[37] = borrow_depth; f[38] = borrow_jump; f[39] = borrow_entry; f[40] = capture_enabled; f[41] = effect_value; f[42] = xprojection; f[43] = effect_floor; f[44] = module_name; f[45] = module_imports; + f[46] = fn_return_type; f[47] = if_value; + return 0; +} +// pop the innermost function context. +int ctx_pop() { + word f; + ctxsp--; + f = R_CTX + ctxsp * CTX_WORDS; + local_drop(f[0]); + fdrop(nloc); + lend_drop(1 << 30, f[0]); + lbase = f[1]; + fdepth = f[2]; + fslots = f[3]; + fchk = f[4]; + fret = f[5]; + live = f[6]; + bxdepth = f[7]; epichain = f[8]; fframe = f[9]; infn = f[10]; lsp = f[11]; loopbase = f[12]; + lv = f[13]; lvk = f[14]; lvi = f[15]; kw_expr = f[16]; xcls = f[17]; snaptop = f[18]; + fty = f[19]; ntp = f[20]; xty = f[21]; rt.copy_w(tpn, f + 22, 8); + finsym = f[30]; finfer = f[31]; ifcur = f[32]; ifcall = f[33]; effect_current = f[34]; borrow_slots = f[35]; borrow_pending = f[36]; borrow_depth = f[37]; borrow_jump = f[38]; borrow_entry = f[39]; capture_enabled = f[40]; effect_value = f[41]; xprojection = f[42]; effect_floor = f[43]; module_name = f[44]; module_imports = f[45]; xvk = 0; + fn_return_type = f[46]; if_value = f[47]; + return 0; +} + +// ---- functions and the top level ---- + +// ---- a function, start to finish ---- + +// fndef reads the header, then: fn_begin resets the per-function state, +// prologue emits the frame setup, the parameters are declared and stored from +// the stack into their slots, body parses the statements, and epilogue emits +// the shared exit (return jumps here through the epichain patch list). the +// frame size is not known until the end, so the prologue's subtract is a hole +// that epilogue fills with fslots. +// fn_begin: chk is 0 for unchecked, ret is 1 for a .u return +int fn_begin(int chk, int ret) { + capture_enabled = 0; effect_value = 0; effect_current = 0; borrow_slots = 0; borrow_pending = 0; borrow_depth = 0; borrow_jump = -1; borrow_entry = 0; + if (cmode && svkind && !dry) rt.fill_w(svkind, 0, nsym); xvk = 0; + lend_drop(1 << 30, nloc); + lbase = nloc; fdepth = 1; fslots = 0; fchk = chk && check_on; fret = ret; fty = 0; finfer = 0; fn_return_type = 0; + live = 1; bxdepth = 0; epichain = 0; infn = 1; loopbase = lsp; lv = 0; lvk = LV_NONE; xbor = -1; + return 0; +} +// allocate the frame, retaining the caller's pointer until any growth finishes. +int prologue() { + int k, busy, done, helper = !nclo && (sname[finsym] == s_frame_grow || sname[finsym] == s_frame_leave); + lit(rt.ZFP); op(O_LD); + if (!helper) { lit(0); op(O_PICK); } + fframe = hole(); op(O_SUB); lit(rt.ZFP); op(O_ST); + if (helper) return 0; + lit(sval[lookup(s_frame_busy)]); op(O_LD); busy = hole(); op(O_JZ); + done = hole(); op(O_JMP); setlit(busy, here); + lit(rt.ZFP); op(O_LD); lit(sval[lookup(s_frame_floor)]); op(O_LD); + lit(sval[lookup(s_frame_guard)]); op(O_LD); op(O_ADD); op(O_LT); k = hole(); op(O_JZ); + lit(rt.ZFP); op(O_LD); lit(1); op(O_PICK); lit(rt.ZFP); op(O_ST); + callname(s_frame_grow); lit(rt.ZFP); op(O_ST); + busy = hole(); op(O_JMP); + setlit(k, here); setlit(done, here); op(O_DROP); setlit(busy, here); + return 0; +} +// emit the function epilogue, patching every return to it. +int epilogue() { + int k, node, done, helper = !nclo && (sname[finsym] == s_frame_grow || sname[finsym] == s_frame_leave); + lit(0); + fixall(epichain, here); + lit(rt.ZFP); op(O_LD); lit(fslots); op(O_ADD); lit(rt.ZFP); op(O_ST); + if (!helper) { + lit(sval[lookup(s_frame_node)]); op(O_LD); node = hole(); op(O_JZ); + lit(rt.ZFP); op(O_LD); lit(sval[lookup(s_frame_top)]); op(O_LD); op(O_SUB); k = hole(); op(O_JZ); + done = hole(); op(O_JMP); setlit(k, here); + callname(s_frame_leave); lit(rt.ZFP); op(O_ST); setlit(done, here); setlit(node, here); + } + op(O_RET); borrow_initializer(); + setlit(fframe, fslots); + infn = 0; + local_drop(lbase); + fdrop(lbase); + td_end(0); + return 0; +} +// the statements of a function between its braces, then the end-of-function +// checks: hints, unused m objects, anything still owned is freed, and a .u +// function that falls off the end is an error +int body() { + int closed, endplace; + capture_prepare(); borrow_begin(); + expect(s_lb); + while (!is_op(s_rb)) { + if (tk == T_EOF) { die("missing } at the end of a function"); break; } + stmt(); + } + closed = is_op(s_rb); endplace = toff + toklen; + if (probe_off >= 0 && !chkabort && !dry && !ginst && incdepth == 0 && ctxsp <= nclo && toff >= probe_off) { probe_snap(); chkabort = 1; } + local_hints(lbase); + unused_check(lbase); + if (ifcur) if_fin(); + if (ifcur && live && ifcur[1]) ifcur[4] = 1; // falling off the end gives 0 + if (fchk && live) { + exit_vars(1, 1); + if (fret) cerr("a function returning .u has to end with a return"); + } + next(); + if (probe_off >= 0 && !chkabort && closed && nclo == 0 && probe_has == 1 && endplace <= probe_off && !ginst && ctxsp == 0) { probe_has = 0; probe_n = 0; } // the place is past this function + return 0; +} +// the rest of a function, after its return type and name: +// int f(int a, node*.m n, .d x, b) { ... } <T> *list<T>.u mk(int n) { ... } +// rty is 0 for fn (a plain word), ret is 1 for a .u return +// (this is also where a function's signature is packed into ssig and its type +// into stype, checked against any earlier prototype or prescan result, and +// where the o3 region for an o3 function is handed to the core) +int fndef(int chk, int rty, int ret, int n) { + int s, np = 0, tags = 0, t, sig, i; char*[24] pn; word[24] pt; word[24] pty; int typed = 0, ft, explicit_rty = !!rty, o3f = o3nxt, lo, hi, pk; + o3nxt = 0; + if (rty || ntp) typed = 1; + if (!dnm) dnm = n; + s = lookup(n); + if (s < 0 || s < ubase || skind[s] != K_FN || (sflag[s] & F_DEF)) { + s = addsym(n, K_FN, 0); + sflag[s] = 0; + } + def_note(s, nameoff); + ifcur = 0; + if (ifon && ifkk && !ginst && !ntp && !dry) { + if (ifkk > 0 && !rty) if_begin(s, ifkk); else if_begin(s, 0); + } + ifkk = 0; + expect(s_lp); + if (!is_op(s_rp)) { + while (1) { + t = 0; + pty[np] = tword; + pk = 0; + if (!is_op(s_dot) && paramstart()) { + pty[np] = ptype(); typed = 1; pk = 1; + if (pty[np][0] == TY_ARR) pty[np] = decay(pty[np]); + if (isagg(pty[np])) { + if (ginst) pty[np] = tptr(pty[np]); else if (!stubgen) serr("structs and arrays are passed by pointer"); + // (an extern stub takes its struct by value, which is an address here) + } + } + if (ifcur) { + if (pk) if_param(np, tpack(), 0, pty[np]); + else if (is_op(s_dot)) if_param(np, tpack(), 2, pty[np]); + else if_param(np, tpack(), 1, pty[np]); + } + if (accept(s_dot)) { // .m lends, .d takes over + if (tv == s_m) { + t = 1; + } else if (tv == s_d) t = 2; else die("a parameter tag is .m or .d"); + next(); + } + pnoffs[np] = toff; pnplaces[np] = tpack(); rs_note(RS_PARAM, toff); pn[np] = name(); + if (o3f && pty[np] == tword) serr("o3 parameters need a type"); + if (np >= 12 && t) die("only the first 12 parameters can be tagged"); + pt[np] = t; + if (np < 12) tags = tags + (t << (np + np)); + np++; + if (np >= 24) die("too many parameters"); + if (!accept(s_comma)) break; + } + } + expect(s_rp); + sig = ret + np + np + tags * 64; + if ((sflag[s] & F_SIG) && !(sflag[s] & F_PRE)) { + if (ssig[s] != sig) serrn(n, "doesn't match its earlier declaration"); + } + else if ((sflag[s] & F_UNTYPED) && (tags || ret)) serrn(n, "was called before its tags were declared; put a prototype above the first call"); + ssig[s] = sig; + sflag[s] = (sflag[s] | F_SIG) - (sflag[s] & F_PRE); + if (ntp) { sflag[s] = sflag[s] | F_GEN; if (!ginst) gtmake(s); } + if (typed) { + if (!rty) rty = tword; + ft = tfn(rty, np, pty, sig); + if (stype[s] && stype[s] != ft) serrn(n, "doesn't match its earlier declaration"); + stype[s] = ft; + } + if (ifcur && explicit_rty) if_join(ifcur + 3, rty); + sig_attach(n, rty, np, pn, pty, typed); + if (accept(s_semi)) { sflag[s] = sflag[s] | F_DECL; ntp = 0; return s; } + if (sflag[s] & F_DEF) serrn(n, "is defined twice"); + if (!ginst && ntp) { // a generic function is not compiled here: its tokens are kept and each set of type arguments gets its own copy + if (sflag[s] & F_TPL) serrn(n, "is defined twice"); + sflag[s] = sflag[s] | F_TPL; + gnew = gtmake(s); + gskip(); + ntp = 0; + sval[s] = here; // the name still has an address and a type, so reflection can ask for them; it halts if it is ever jumped to + sflag[s] = sflag[s] | F_DEF; + fixall(sfix[s], here); sfix[s] = 0; + lit(rt.MW - 1); op(O_JMP); + return s; + } + sval[s] = here; + sflag[s] = sflag[s] | F_DEF; + fixall(sfix[s], here); sfix[s] = 0; + fn_begin(chk, ret); + finsym = s; finfer = !ret; + effect_current = effect_record(s); rt.fill_w(effect_current, 0, 50); effect_current[0] = 1; if (!chk || stubgen) effect_current[0] = 2; effect_current[1] = np; + i = 0; while (i < np) { if (pt[i] == 2) { effect_current[2 + i] = EF_CONSUME; effect_current[26 + i] = EF_CONSUME; } i++; } + if (typed && explicit_rty) fty = rty; + no3pend = 0; + if (o3f) { o3on = 1; o3reset(); sflag[s] = sflag[s] | F_O3; } + prologue(); borrow_prologue(); + i = 0; + while (i < np) { + t = newloc(pn[i]); lrskind[t] = RS_PARAM; lrsoff[t] = pnoffs[i]; lpos[t] = pnplaces[i]; fslots++; lsmark(1, pn[i]); + ltype[t] = pty[i]; lptr[t] = 0; + if (ifcur && if_local && i < IFP) if_local[t] = ifcur + 8 + 3 * i; + lt_note(pnoffs[i], pty[i]); + if (o3f) { ltag[t] = 16; o3promote(t); } + if (isfl(pty[i])) setflt(t, 1); + if (fchk && pt[i] == 1) lstate[t] = S_BORROW; + if (fchk && pt[i] == 2) { lstate[t] = S_M; lown[t] = 1; } + i++; + } + i = np; + while (i) { i--; laddr(i); op(O_ST); } + if (o3f) { // the region starts here: a jump, so the cpu enters it + reserve_code(2); lo = here + 2; lit(lo); op(O_JMP); + i = 0; while (i < np) { t = lbase + i; o3mkcache(t); o3refresh(t); i++; } + } + body(); + if (!explicit_rty && fn_return_type > 0 && fn_return_type != tword) { + stype[s] = tfn(fn_return_type, np, pty, ssig[s]); + sig_attach(n, fn_return_type, np, pn, pty, 1); + } + hi = here; + epilogue(); + if (o3f) { + o3on = 0; + o3fs = fslots; + o3na = np; + o3fn0 = sval[s]; + o3region(lo, hi, o3sl, o3rg, no3sl); + } + o3flush(); + ntp = 0; ifcur = 0; + fn_remember(s, n); + return s; +} +// let g = 1, buf[10]; int g = 3; int[10] buf; point origin; +// ---- globals, constants, enhancements ---- + +// a global's storage is emitted in place among the code (one word, or the +// whole array), and the symbol's value is its address. a string initializer +// points at characters emitted right after. +int gdecl(int typed) { + ifgk = 0; + if (isp == 0) ifgk = tpack(); + next(); + return gdecl2(0, 0, name()); +} +// compile the rest of a global declaration: its type, name and initial value. +int gdecl2(int t, int tg, int n) { + int a, ng = nameoff; word k, arraytype, iq; int s, kp = ifgk, gty = 0, gok = ifon && !t && !dry, vr, vk; word vv; + ifgk = 0; + if (!dnm) dnm = n; + if (tg) serr("tags go on locals and parameters, not globals"); + if (t == tvoid) serr("a variable can't be void"); + while (1) { + if (accept(s_lk)) { + k = cexpr(); expect(s_rk); + if (!t) t = tword; + arraytype = tarr(t, k); k = tsz(arraytype); reserve_code(k); + s = addsym(n, K_GARR, here); rs_put(ng, RS_SYM, rs_sym(s), s); stype[s] = arraytype; + while (k) { emit(0); k--; } + if (gok) { if (!gty) gty = tint; else if (gty != tint) gok = 0; } + } else if (t && isagg(t)) { + arraytype = t; iq = 0; + if (t[0] == TY_ARR && accept(s_eq)) { if (!is_op(s_lb) && tk != T_STR && tk != T_BSTR) serr("an array needs an initializer list"); else { iq = array_initializer_tokens(t); arraytype = array_measure(iq, t); } } + if (arraytype[0] == TY_ARR && arraytype[3] < 0) serr("T[] needs an initializer to determine its length"); + k = tsz(arraytype); reserve_code(k); a = here; + s = addsym(n, K_GARR, here); rs_put(ng, RS_SYM, rs_sym(s), s); stype[s] = arraytype; + while (k) { emit(0); k--; } + if (iq) { array_initialize(iq, arraytype, a, 0); rt.free(iq); } + } else { + reserve_code(1); a = here; + s = addsym(n, K_GVAR, a); rs_put(ng, RS_SYM, rs_sym(s), s); vr = rsn - 1; vk = 1; vv = 0; + if (t) stype[s] = t; + emit(0); + if (accept(s_eq)) { + if (tk == T_STR) { + vk = 3; vv = vf_string(tv); if (!t) stype[s] = tptr(tchar); + reserve_code(rt.len_w(tv) + 1); *(a + hdelta) = here; + k = tv; + while (*k) { emit(*k); k++; } + emit(0); + next(); + if (gok) { if (!gty) gty = tptr(tchar); else if (gty != tptr(tchar)) gok = 0; } + } + else if (tk == T_BSTR) { + vk = 4; vv = vf_string(tv); *(a + hdelta) = bstrdata(tv); next(); + if (!t) stype[s] = tbstr; else if (t != tbstr) tcerr("initializer", t, tbstr); + if (gok) { if (!gty) gty = tbstr; else if (gty != tbstr) gok = 0; } + } + else if (is_op(s_lb) || (is_op(s_lp) && caststart())) { + arraytype = tarr(tint, -1); + if (accept(s_lp)) { arraytype = ptype(); expect(s_rp); } + if (arraytype[0] != TY_ARR || !is_op(s_lb)) serr("a global compound literal needs an array initializer"); + else { array_literal(arraytype); vv = xcval; *(a + hdelta) = vv; if (!t) stype[s] = decay(xty); } + } + else { + vv = cnorm(t, cexpr()); *(a + hdelta) = vv; + if (gok) { if (!gty) gty = tint; else if (gty != tint) gok = 0; } + } + } else if (gok) gok = 0; // no initializer: nothing says what it is + vf_at(vr, vk, vv); + } + if (stype[s]) lt_note(ng, stype[s]); else lt_note(ng, tword); + if (!accept(s_comma)) break; + ng = toff; n = name(); + } + expect(s_semi); + if (gok && gty && kp) fact_add(kp, 0 - 1, gty, ""); + return 0; +} +// a constant cut to the sized integer type t, the way tnorm does at run time +int cnorm(word t, int v) { + int b, mask, sg; + if (!t) return v; + b = tbytes(t); + if (!b || t[0] != TY_INT) return v; + mask = (1 << (8 * b)) - 1; + v = v & mask; + if (t[6] & 1) { sg = 1 << (8 * b - 1); v = (v ^ sg) - sg; } + return v; +} +// const A = 1, B = A + 2; +// const [type] NAME = e, NAME = e; the type is optional and is read and dropped, as in zc0 +int gconst() { + int n, off, s; + next(); + if (tk == T_ID) { peekat(0); if (pk_tk == T_ID || (pk_tk == T_OP && pk_tv == s_star)) ptype(); } + while (1) { off = toff; n = name(); if (!dnm) dnm = n; expect(s_eq); s = addsym(n, K_CONST, cexpr()); rs_put(off, RS_SYM, rs_sym(s), s); if (!accept(s_comma)) break; } + expect(s_semi); + return 0; +} +// enhance [n] name(a, b) micro "recipe" [else fn]; n outputs, 1 if left out +// enhance [n] name(a, b) vm { asm } [else fn]; +// lays out the descriptor the install opcode reads, and wires it in right +// away (an image wires its own in when it boots). +int enhdef() { + int n, np = 0, nout = 1, dsc, na, pay, len = 0, kind = 0, i; word p; int s, slot, r; char*[8] pn; + next(); + // enhance 2 divmod(a, b) + if (tk == T_NUM) { + if (tv < 0 || tv > 8) serr("an enhancement has at most 8 outputs"); + nout = tv; + next(); + } + n = name(); + expect(s_lp); + if (!is_op(s_rp)) { + while (1) { + if (np == 8) die("an enhancement takes at most 8 inputs"); + pnoffs[np] = toff; pnplaces[np] = tpack(); rs_note(RS_PARAM, toff); pn[np] = name(); + np++; + if (!accept(s_comma)) break; + } + } + expect(s_rp); + len = 7 + np; i = 0; while (i < np) { len = len + rt.len_w(pn[i]) + 1; i++; } + reserve_code(len); len = 0; dsc = here; i = 0; while (i < 7) { emit(0); i++; } + na = here; i = 0; while (i < np) { emit(0); i++; } + i = 0; + while (i < np) { *(na + i + hdelta) = here; p = pn[i]; while (*p) { emit(*p); p++; } emit(0); i++; } + if (accept(s_micro)) { + if (tk != T_STR) die("expected the recipe as a string"); + ebn = 0; while (tk == T_STR) { ebs(tv); next(); } + reserve_code(ebn + 1); pay = here; wcopyto(ebuf, ebn + 1); + } else if (accept(s_vm)) { pay = contiguous_asm(); len = here - pay; kind = 1; } + else die("expected micro or vm"); + slot = nenh; + if (slot >= 512) fatal("all 512 enhancement slots are taken"); + nenh++; + p = dsc + hdelta; + p[0] = slot; p[1] = kind; p[2] = np; p[3] = nout; p[4] = pay; p[5] = len; p[6] = na; + s = addsym(n, K_ENH, slot); + ssig[s] = np + nout * 16 + dsc * 256; + if (accept(s_else)) { r = lookup(tv); if (r < 0) r = fwdfn(tv); next(); sfix[s] = r + 1; } + expect(s_semi); + if (!hdelta) { + r = rt.install(dsc); + if (r < 0) { + if (!sfix[s]) serrn(n, "the enhancement didn't install (see install's codes in core.c)"); + sflag[s] = sflag[s] | F_FALLBACK; + } + } + return 0; +} +// recovery at the top level: skip to the next token that can start a +// declaration, outside any braces +int topsync() { + int depth = 0; + while (tk != T_EOF) { + if (is_op(s_lb)) depth++; + else if (is_op(s_rb)) { depth--; if (depth < 0) depth = 0; } + else if (!depth && tk == T_ID && (tv == s_fn || tv == s_var || tv == s_const || tv == s_struct || tv == s_type || tv == s_typedef + || tv == s_enhance || tv == s_typecheck || tv == s_docs || tv == s_unchecked || tv == s_immediate || tv == s_o3 + || tv == s_memdebug || tv == s_enum || tv == s_include)) break; + next(); + } + panic = 0; + return 0; +} +// after a function's name: <T, U> declares its type parameters. any the return +// type used were taken on trust; now they get their real numbers +int genparams() { + int i, j, n = 0; char*[8] ps; word[8] bs; + if (!is_op(s_lt)) { if (ntp) serrn(tpn[0], "isn't a type"); ntp = 0; return 0; } + next(); + while (1) { + if (n >= 8) fatal("too many type parameters"); + ps[n] = name(); + n++; + if (!accept(s_comma)) break; + } + closeangle(); + rt.fill_w(bs, 0, 8); + i = 0; + while (i < ntp) { + j = 0; + while (j < n && ps[j] != tpn[i]) j++; + if (j == n) serrn(tpn[i], "isn't a type"); else bs[i] = tvar(j, ps[j]); + i++; + } + if (ntp && dty) dty = subst(dty, bs); + rt.copy_w(tpn, ps, n); ntp = n; + if (ginst) ntp = 0; + return 0; +} +// the return type of a function or the type of a global, with unknown names allowed for now +int rettype() { + tvauto = 1; + dtype(); + tvauto = 0; + return 0; +} +// finish a function declaration. an immediate fn also becomes a keyword +// (kind K_KW whose handler is the function), which is how parse words work: +// the parser calls it with kwcall the moment it meets the name. +int fndone(int s, int imm) { + int k; + ntp = 0; + if (imm) { + if (hdelta) serr("parse words can't be defined while building an image"); + k = addsym(sname[s], K_KW, sval[s]); + sflag[k] = F_DEF | F_STMT | F_EXPR | F_TOP; + } + return 0; +} +// one top-level item. this is the dispatcher for everything a unit can +// contain: unchecked / immediate / o3 prefixes, fn declarations (with or +// without a leading return type), globals, struct, type, enum, include, +// typecheck, memdebug, enhance, const, and parse words used at the top level. +// anything else is a typed global or function: a type, a name, and then +// ( or < means function, otherwise a global. +int topitem0() { + int r, i; + dsn = 0; dnm = 0; + if (docs_on && dn && dline == tline && !ginst) { + if (dn > docsnapcap) { docsnapcap = dn; dsnap = chk_resize(dsnap, 0, docsnapcap); } + i = 0; while (i < dn) { dsnap[i] = dbuf[i]; i++; } + dsn = dn; dline = 0; + } + r = topitem1(); + if (dsn && dnm) doc_attach(dnm); + dsn = 0; dnm = 0; + return r; +} +// compile one top-level item, after the first check of its keyword. +int topitem1() { + int chk = 1, imm = 0, s, k, n; + if (panic) return topsync(); + if (is_id(s_unchecked)) { + next(); + if (accept(s_semi)) { check_on = 0; return 0; } // unchecked; switches the analyzer off from here on + chk = 0; + } + if (is_id(s_immediate)) { next(); imm = 1; } + o3nxt = 0; + if (is_id(s_o3)) { next(); o3nxt = 1; } + ntp = 0; ifkk = 0; + if (is_id(s_fn)) { + peek(); + if (!(pk_tk == T_OP && pk_tv == s_lp)) { + ifkp = tpack(); ifkk = 1; + if (isp != 0) ifkk = 0; + next(); + n = name(); + if (ginst) n = ginsn; + dty = 0; + genparams(); + return fndone(fndef(chk, 0, 0, n), imm); + } + } + if (!chk || imm || o3nxt) { // has to be a function, with a return type + ifkp = tpack(); ifkk = 0 - 1; if (is_op(s_dot)) ifkk = 2; + if (isp != 0) ifkk = 0; + rettype(); n = name(); if (ginst) n = ginsn; genparams(); + if (dtag && dtag != S_U) serr("the only return tag is .u"); + if (!is_op(s_lp)) { die("expected a function"); return 0; } + return fndone(fndef(chk, dty, dtag == S_U, n), imm); + } + if (is_id(s_var)) return gdecl(0); + if (is_id(s_struct)) { kw_struct(); return 0; } + if (is_id(s_type)) return kw_type(); + if (typedef_ahead()) return kw_typedef(); + if (is_id(s_typecheck)) return kw_typecheck(); + if (is_id(s_docs)) return kw_docs(); + if (is_id(s_memdebug)) return kw_memdebug(); + if (is_id(s_enum)) return kw_enum(); + if (is_id(s_include)) return kw_include(); + if (tk == T_ID && rt.eq_w(tv, "namespace")) return kw_namespace(); + if (is_id(s_enhance)) return enhdef(); + if (is_id(s_const)) { + peekat(0); + if (pk_tk != T_ID || pk_tv != s_fixed) return gconst(); + } + if (tk == T_ID) { + s = lookup(tv); + if (s >= 0 && skind[s] == K_KW && (sflag[s] & F_TOP)) { kwcall(s, 0); return 0; } + } + if (tstart() || (tk == T_ID && lookup(tv) < 0)) { // int f(...) { }, T first<T>(...) { }, int g = 1; + ifkp = tpack(); ifkk = 0 - 1; if (is_op(s_dot)) ifkk = 2; + if (isp != 0) ifkk = 0; + rettype(); n = name(); + if (is_op(s_lp) || is_op(s_lt)) { + if (ginst) n = ginsn; + genparams(); + if (dtag && dtag != S_U) serr("the only return tag is .u"); + return fndone(fndef(chk, dty, dtag == S_U, n), 0); + } + if (ntp) { serrn(tpn[0], "isn't a type"); ntp = 0; } + return gdecl2(dty, dtag, n); + } + die("expected a declaration"); + return 0; +} +// ---- generic functions: templates and instances ---- + +// which type parameter does name n stand for while an instance is compiled +int gfind(int n) { + int i = 0; + while (i < gng) { + if (gnm[i] == n) return i; + i++; + } + return 0 - 1; +} +// does a type still mention a type parameter? +word hasvar(word t) { + int k = t[0], i; word v; + if (k == TY_VAR) return 1; + if (k == TY_PTR || k == TY_ARR || k == TY_DYN) return hasvar(t[4]); + if (k == TY_FN) { + if (hasvar(t[4])) return 1; + i = 0; + while (i < t[3]) { + if (hasvar(t[7 + i])) return 1; + i++; + } + return 0; + } + if (k == TY_STRUCT && t[5]) { + v = t[6]; i = 0; + while (i < v[0]) { + if (hasvar(v[1 + i])) return 1; + i++; + } + } + return 0; +} +// add the current token to the item being recorded +int grec_add() { + word nb; + if (gn * 3 + 4 > gcap) { + nb = rt.new(gcap + gcap); + rt.copy_w(nb, gbuf, gcap); + rt.free(gbuf); + gbuf = nb; + gcap = gcap + gcap; + } + gbuf[1 + gn * 3] = tk; gbuf[2 + gn * 3] = tv; gbuf[3 + gn * 3] = tpack(); + gn++; + return 0; +} +// the template entry for generic function s, made on first sight of its header +int ginit() { + gtab = 1; + gt_sym = chk_resize(0, 0, gtcap); gt_tok = chk_resize(0, 0, gtcap); gt_np = chk_resize(0, 0, gtcap); + gt_nm = chk_resize(0, 0, gtcap * 8); gt_module = chk_resize(0, 0, gtcap); gt_imports = chk_resize(0, 0, gtcap); gt_source = chk_resize(0, 0, gtcap); + gi_t = chk_resize(0, 0, gicap); gi_sym = chk_resize(0, 0, gicap); gi_done = chk_resize(0, 0, gicap); + gi_ar = chk_resize(0, 0, gicap * 8); + return 0; +} +int reserve_generics(int need) { + int cap = gtcap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + gt_sym = chk_resize(gt_sym, gt_n, cap); gt_tok = chk_resize(gt_tok, gt_n, cap); + gt_np = chk_resize(gt_np, gt_n, cap); gt_nm = chk_resize(gt_nm, gt_n * 8, cap * 8); + gt_module = chk_resize(gt_module, gt_n, cap); gt_imports = chk_resize(gt_imports, gt_n, cap); gt_source = chk_resize(gt_source, gt_n, cap); + gtcap = cap; return cap; +} +int reserve_instances(int need) { + int cap = gicap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + gi_t = chk_resize(gi_t, gi_n, cap); gi_sym = chk_resize(gi_sym, gi_n, cap); + gi_done = chk_resize(gi_done, gi_n, cap); gi_ar = chk_resize(gi_ar, gi_n * 8, cap * 8); + gicap = cap; return cap; +} +// make the generic instance of the function or struct s for its type arguments. +int gtmake(int s) { + int k = 0; + if (!gtab) ginit(); + while (k < gt_n) { + if (gt_sym[k] == s) break; + k++; + } + if (k == gt_n) { + if (gt_n >= gtcap) reserve_generics(gt_n + 1); + gt_n++; + gt_sym[k] = s; gt_tok[k] = 0; gt_module[k] = module_name; gt_imports[k] = module_imports; gt_source[k] = curinc; + } + gt_np[k] = ntp; + rt.copy_w(gt_nm + k * 8, tpn, ntp); + return k; +} +// skip a function body without compiling it: from the { to its matching } +int gskip() { + int depth = 0; + if (!is_op(s_lb)) { die("expected { to start the function"); return 0; } + while (tk != T_EOF) { + if (is_op(s_lb)) depth++; + else if (is_op(s_rb)) { + depth--; + if (depth <= 0) { next(); break; } + } + next(); + } + return 0; +} +// the item just read defined generic function gnew: keep its tokens +int gattach(word .d buf, int n) { + n--; // the last token belongs to the next item + if (nerr) { rt.free(buf); return 0; } + buf[0] = n; + gt_tok[gnew] = buf; + return 0; +} +// retain one ordinary function's source tokens and the namespace needed to compile it again. +int fn_remember(int s, int name) { + int count = gn - 1, i = 0, j, depth, namepos = -1, open = -1, close = -1; word* q; word meta; + if (!grec || !gbuf || fn_method_compile || dry || ginst || nerr || (sflag[s] & F_GEN) || count <= 0) return 0; + while (i < count && open < 0) { + if (gbuf[1 + i * 3] == T_ID && gbuf[2 + i * 3] == name) { + j = i + 1; + if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_lt) { + depth = 1; j++; + while (j < count && depth) { + if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_lt) depth++; + else if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_gt) depth--; + j++; + } + } + if (j < count && gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_lp) { + depth = 1; j++; + while (j < count && depth) { + if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_lp) depth++; + else if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_rp) depth--; + j++; + } + while (j < count && !(gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_lb)) j++; + if (j < count) { namepos = i; open = j; } + } + } + i++; + } + if (open < 0) return 0; + depth = 1; j = open + 1; + while (j < count && depth) { + if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_lb) depth++; + else if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_rb) depth--; + if (depth) j++; + } + if (depth) return 0; + close = j; + // compiler storage, not program objects: internal, so live_objects and leak reports skip it + q = chk_resize(0, 0, 1 + count * 3); q[0] = count; rt.copy_w(q + 1, gbuf + 1, count * 3); + meta = chk_resize(0, 0, 6); meta[0] = q; meta[1] = module_name; meta[2] = module_imports; + meta[3] = namepos; meta[4] = open; meta[5] = close; fnmeta[s] = meta; + return 1; +} +// compile one retained source function for a syntax callback's host context. +// calls into emitted source are cloned recursively; existing host bindings retain +// their original globals and entry addresses. +int syntax_host_function(int symbol) { + word meta = fnmeta[symbol], source, full; int clone, count, name; + char[40] generated; char[24] reversed; int sequence = fnpatchseq, digits = 0, i = 0; + if (syntax_host_symbols[symbol]) return syntax_host_symbols[symbol] - 1; + if (!meta || (sflag[symbol] & F_GEN)) { serr("an emitted syntax dependency requires retained nongeneric source"); return -1; } + fnpatchseq++; + rt.copy_w(generated, "__run_syntax_", 13); + if (!sequence) reversed[digits++] = '0'; + while (sequence) { reversed[digits++] = '0' + sequence % 10; sequence = sequence / 10; } + while (i < digits) { generated[13 + i] = reversed[digits - i - 1]; i++; } + generated[13 + digits] = 0; name = intern(generated); + source = meta[0]; count = source[0]; full = rt.new(1 + count * 3); full[0] = count; + rt.copy_w(full + 1, source + 1, count * 3); full[2 + meta[3] * 3] = name; + if (meta[1]) clone = fwdfn(methname(meta[1], name)); else clone = fwdfn(name); + syntax_host_symbols[symbol] = clone + 1; + compile_list_context(full, 1, meta[1], meta[2]); rt.free(full); + return clone; +} +// source syntax executes in resident host code even when its library's runtime +// functions are emitted into a compiler-free image. staged symbols never enter +// the image blob; executable storage stays rooted by the ordinary code blocks. +int syntax_resident(int symbol) { + int saved_here = here, saved_delta = hdelta, saved_limit = hlimit, saved_ptop = ptop, saved_vislo = vislo; + int saved_active = image_active, saved_chunk = code_chunk, from = nsym, clone, entry = -1, top; + word block = chk_resize(0, 0, 4096); + syntax_host_symbols = chk_resize(0, 0, from); syntax_host_floor = ubase; syntax_host_from = from; + top = block + block[-5] - rt.HDR; + block[0] = rt.code_blocks; block[1] = block + 3; block[2] = top; rt.code_blocks = block; + if (rt.syscall(9, 9, top, 0) < top) fatal("syntax code address reservation failed"); + here = block + 3; hdelta = 0; hlimit = top; image_active = 0; vislo = 0; + clone = syntax_host_function(symbol); if (clone >= 0 && (sflag[clone] & F_DEF)) entry = sval[clone]; + popsyms(from); syntax_host_floor = 0; syntax_host_from = 0; rt.free(syntax_host_symbols); syntax_host_symbols = 0; + here = saved_here; hdelta = saved_delta; hlimit = saved_limit; ptop = saved_ptop; vislo = saved_vislo; image_active = saved_active; code_chunk = saved_chunk; + return entry; +} +// the symbol for template s with these type arguments, made on first use and +// queued to be compiled once the template's tokens are in. a type parameter +// that nothing bound is a plain word. +word ginstance(int s, word bs) { + int k = 0, t = 0 - 1, np, i, j, ns; char* nm; char[24] dg; int nd; char* a; + if (dry || !gtab) return s; + while (k < gt_n) { + if (gt_sym[k] == s) { t = k; break; } + k++; + } + if (t < 0) return s; + np = gt_np[t]; + i = 0; + while (i < np) { + if (!bs[i]) bs[i] = tword; + if (hasvar(bs[i])) return s; + i++; + } + k = 0; + while (k < gi_n) { + if (gi_t[k] == t) { + j = 0; + while (j < np && gi_ar[k * 8 + j] == bs[j]) j++; + if (j == np) return gi_sym[k]; + } + k++; + } + if (gi_n >= gicap) reserve_instances(gi_n + 1); + i = 0; j = 0; + a = sname[s]; nm = chk_resize(0, 0, rt.len_w(a) + np * 18 + 3); + while (a[j]) { nm[i] = a[j]; i++; j++; } + nm[i] = '<'; i++; + k = 0; + while (k < np) { + if (k) { nm[i] = ','; i++; } + a = bs[k]; nd = 0; + while (a && nd < 20) { dg[nd] = '0' + (a & 15); nd++; a = a >> 4; } + while (nd) { nd--; nm[i] = dg[nd]; i++; } + k++; + } + nm[i] = '>'; i++; nm[i] = 0; + ns = addsym(intern(nm), K_FN, tpack()); + sflag[ns] = F_SIG | (sflag[s] & F_O3); + ssig[ns] = ssig[s]; + k = gi_n; gi_n++; + gi_t[k] = t; gi_sym[k] = ns; gi_done[k] = 0; + rt.copy_w(gi_ar + k * 8, bs, np); + return ns; +} +// compile one instance: replay the template's tokens with its type +// parameters bound +int gcompile(int k) { + int t = gi_t[k]; char* oldmodule = module_name; word oldimports = module_imports; char* oldsource = curinc; int olddepth = incdepth; + curinc = gt_source[t]; if (curinc) incdepth++; + module_name = gt_module[t]; module_imports = gt_imports[t]; + ginst = 1; gng = gt_np[t]; + rt.copy_w(gnm, gt_nm + t * 8, 8); + rt.copy_w(gib, gi_ar + k * 8, 8); + ginsn = sname[gi_sym[k]]; + src_push(1, gt_tok[t], 0, 0); + next(); + topitem0(); + if (tk != T_EOF) die("unexpected tokens after a generic function"); + src_pop(); + ginst = 0; gng = 0; module_name = oldmodule; module_imports = oldimports; curinc = oldsource; incdepth = olddepth; + return 0; +} +// compile the instances asked for so far whose templates have been read, and +// any they ask for in turn +int gdrain() { + int k, more = 1; + if (infn || ginst) return 0; + if (panic) return 0; + while (more) { + more = 0; + k = 0; + while (k < gi_n) { + if (!gi_done[k] && gt_tok[gi_t[k]]) { + gi_done[k] = 1; + gcompile(k); + more = 1; + } + k++; + } + } + return 0; +} +// one top-level item, recording its tokens so that a generic function's can be kept +int topitem() { + int sb = gbuf, sn = gn, sc = gcap, sr = grec, sg = gnew, rb, rn; + if (ginst) return topitem0(); + gbuf = rt.new(256); gcap = 256; gn = 0; grec = 1; gnew = 0 - 1; + grec_add(); + topitem0(); + rb = gbuf; rn = gn; + grec = sr; gbuf = sb; gn = sn; gcap = sc; + if (gnew >= 0) gattach(rb, rn); else rt.free(rb); + gnew = sg; + gdrain(); + if (chk_hook && cmode && isp == 0 && !chkabort) chk_hook(); + return 0; +} +// a unit is compiled item by item; unit_end then reports any function that +// was called but never defined, and stops if any error was found +int unit_top() { + if (effect_floor < ubase) effect_floor = ubase; + while (tk != T_EOF) { + if (probe_off >= 0 && incdepth == 0 && toff > probe_off) { probe_n = 0; probe_has = 0; chkabort = 1; break; } + topitem(); + } + return 0; +} +// the edit distance between two names (levenshtein), or 99 when one is too long +word editdist(word a, word b) { + int la = rt.len_w(a), lb = rt.len_w(b), i, j, x, y, z, last, row[65]; + if (la > 60 || lb > 60) return 99; + j = 0; while (j <= lb) { row[j] = j; j++; } + i = 1; + while (i <= la) { + last = row[0]; row[0] = i; j = 1; + while (j <= lb) { + x = row[j] + 1; + y = row[j - 1] + 1; + z = last + (a[i - 1] != b[j - 1]); + last = row[j]; + if (y < x) x = y; + if (z < x) x = z; + row[j] = x; + j++; + } + i++; + } + return row[lb]; +} +// the part of a symbol a bare name is compared with: put_c for rt.put_c, the whole name otherwise +char* similar_tail(char* name) { + int k = 0; char* tail = name; + while (name[k] && name[k] != '.') k++; + if (name[k] && module_named(name, k)) tail = name + k + 1; + return tail; +} +// is name[0..k) a module the compiler knows? +int module_named(char* name, int k) { + char* prefix = chk_resize(0, 0, k + 1); int known; + rt.copy_w(prefix, name, k); prefix[k] = 0; + known = module_known(prefix); + rt.free(prefix); + return known; +} +// the defined function, global or constant whose name is closest to n, if it +// is close enough to be a likely typo; 0 when none is. a module's export is +// compared without its prefix, so putc finds rt.put_c +int similar(int n) { + int j = 0, best = 0, bd = 99, dist, k, lim = rt.len_w(n) / 3; + if (rt.len_w(n) < 3) return 0; // one edit turns g into gc: too short to guess from + if (lim < 1) lim = 1; + if (lim > 3) lim = 3; + while (j < nsym) { + k = skind[j]; + if (k == K_FN || k == K_GVAR || k == K_GARR || k == K_CONST) { + if (sflag[j] & F_DEF) { + if (sname[j] != n) { + dist = editdist(n, similar_tail(sname[j])); + if (dist < bd) { bd = dist; best = sname[j]; } + } + } + } + j++; + } + if (bd > lim) return 0; + return best; +} +// the field or method of struct sn closest to n (statics is 0), or the static function or constant +// (statics is 1), when it is close enough to be a likely typo. the limits are those of similar. +char* similar_member(char* sn, word st, char* n, int statics) { + int j, bd = 99, dist, k, ln = rt.len_w(sn), lim = rt.len_w(n) / 3, nf; char* best = 0; char* tail; + if (rt.len_w(n) < 3) return 0; + if (lim < 1) lim = 1; + if (lim > 3) lim = 3; + if (!statics && st && st[0] == TY_STRUCT) { + nf = st[3]; k = 0; + while (k < nf) { + tail = st[4][k * 3]; dist = editdist(n, tail); + if (dist == 2 && swapped(n, tail)) dist = 1; + if (dist > 0 && dist < bd) { bd = dist; best = tail; } + k++; + } + } + j = 0; + while (j < nsym) { + k = skind[j]; + if (((k == K_FN && ((sflag[j] & F_STATIC) != 0) == statics) || (statics && k == K_CONST)) && rt.len_w(sname[j]) > ln + 1 && sname[j][ln] == '.') { + tail = sname[j] + ln + 1; + if (has_prefix(sname[j], sn, ln) && !has_dot(tail)) { + dist = editdist(n, tail); + if (dist == 2 && swapped(n, tail)) dist = 1; + if (dist > 0 && dist < bd) { bd = dist; best = tail; } + } + } + j++; + } + if (bd > lim) return 0; + return best; +} +// are a and b the same but for two neighbouring chars swapped (itao for itoa)? a swap counts as one typo. +int swapped(char* a, char* b) { + int i = 0, la = rt.len_w(a); + if (la != rt.len_w(b)) return 0; + while (a[i] && a[i] == b[i]) i++; + if (!a[i] || !a[i + 1] || a[i] != b[i + 1] || a[i + 1] != b[i]) return 0; + i = i + 2; + while (a[i] && a[i] == b[i]) i++; + return !a[i]; +} +// does s start with the first n chars of p? +int has_prefix(char* s, char* p, int n) { + int i = 0; + while (i < n && s[i] == p[i]) i++; + return i == n; +} +// does the string contain a dot? +int has_dot(char* s) { + int i = 0; + while (s[i]) { if (s[i] == '.') return 1; i++; } + return 0; +} +// ends an error message with "; did you mean 'x'?" when a member of struct sn is close to n +int suggest_member(char* sn, word st, char* n, int statics) { + char* best = similar_member(sn, st, n, statics); + if (best) { rt.put_s("; did you mean '"); rt.put_s(best); rt.put_s("'?"); } + return 0; +} +// a copy of s[a..e) as a string +char* lib_copy(char* s, int a, int e) { + char* b = chk_resize(0, 0, e - a + 1); rt.copy_w(b, s + a, e - a); b[e - a] = 0; + return b; +} +// group.name, as a fresh string: the lists below outlive the name table of a check +char* lib_join(char* group, char* name) { + int g = rt.len_w(group), n = rt.len_w(name); char* b = chk_resize(0, 0, g + n + 2); + rt.copy_w(b, group, g); b[g] = '.'; rt.copy_w(b + g + 1, name, n); b[g + n + 1] = 0; + return b; +} +// the declarations of a library, read from its source text, as a list of [next, tail, path]: +// a top-level name's path is the name itself, a static method's is Group.name. the scan +// only looks at shapes: a name before ( or a declarator's = ; , at the top, or inside a +// struct's braces before (. qualified calls (run.statement_hook(...)) are not declarations. +word lib_scan(char* s) { + int i = 0, depth = 0, paren = 0, init = 0, la = -1, le = 0, dotted = 0, after_dot = 0, words = 0, skip = 0, gpend = 0, gdepth = -1, c, a, k; + char* group = 0; char* path; word list = 0, entry, back = 0; + while (s[i]) { + c = s[i]; + if (c == '/' && s[i + 1] == '/') { while (s[i] && s[i] != 10) i++; continue; } + if (c == '/' && s[i + 1] == '*') { i = i + 2; while (s[i] && !(s[i] == '*' && s[i + 1] == '/')) i++; if (s[i]) i = i + 2; continue; } + if (c == 34 || c == 39) { i++; while (s[i] && s[i] != c) { if (s[i] == 92 && s[i + 1]) i++; i++; } if (s[i]) i++; la = -1; continue; } + if (c == 32 || c == 9 || c == 10 || c == 13) { i++; continue; } + if (isdig(c)) { while (isidc(s[i])) i++; la = -1; continue; } + if (isal(c)) { + a = i; while (isidc(s[i])) i++; + if (gpend) { group = lib_copy(s, a, i); gpend = 0; entry = chk_resize(0, 0, 3); entry[0] = list; entry[1] = group; entry[2] = group; list = entry; } + if (depth == 0 && paren == 0 && !init) { + if (words == 0 && ((i - a == 9 && rt.eq_w(lib_copy(s, a, i), "namespace")) || (i - a == 7 && rt.eq_w(lib_copy(s, a, i), "include")))) skip = 1; + if (i - a == 6 && rt.eq_w(lib_copy(s, a, i), "struct")) { gpend = 1; group = 0; } + words++; + } + la = a; le = i; dotted = after_dot; after_dot = 0; + continue; + } + after_dot = c == '.'; + if (c == '<' && s[i + 1] != '<' && s[i + 1] != '=' && paren == 0 && !init && (depth == 0 || (depth == 1 && group))) { + // generic parameters: int find<T>(...) is still find + k = 1; i++; while (s[i] && k) { if (s[i] == '<') k++; if (s[i] == '>') k--; i++; } + continue; + } + if (c == '(' && paren == 0 && !init && la >= 0 && !dotted && !skip) { + if (depth == 0 || (depth == 1 && group && gdepth == 0)) { + path = lib_copy(s, la, le); + entry = chk_resize(0, 0, 3); entry[0] = list; entry[1] = path; entry[2] = path; + if (depth == 1) entry[2] = lib_join(group, path); + list = entry; + } + } + if (((c == '=' && s[i + 1] != '=') || c == ';' || c == ',' || c == '[') && depth == 0 && paren == 0 && !init && la >= 0 && !dotted && !skip && words > 1) { + entry = chk_resize(0, 0, 3); entry[0] = list; entry[1] = lib_copy(s, la, le); entry[2] = entry[1]; list = entry; + } + if (c == '=' && s[i + 1] != '=' && depth == 0 && paren == 0) init = 1; + if (c == '(' || c == '[') paren++; + if ((c == ')' || c == ']') && paren > 0) paren--; + if (c == '{') { if (depth == 0 && paren == 0 && group) gdepth = 0; else if (depth == 0) group = 0; depth++; } + if (c == '}' && depth > 0) { depth--; if (depth == 0) { group = 0; gdepth = -1; words = 0; skip = 0; init = 0; } } + if (c == ';' && depth == 0 && paren == 0) { init = 0; words = 0; skip = 0; group = 0; } + if (c == ',' && depth == 0 && paren == 0) init = 0; + la = -1; i++; + } + // first declared first: Text.itoa comes before Bytes.itoa + while (list) { entry = list[0]; list[0] = back; back = list; list = entry; } + return back; +} +// the embedded libraries, scanned once +word std_names, icon_names; int names_read; +word lib_names(char* module) { + if (!names_read) { names_read = 1; std_names = lib_scan(std_text()); icon_names = lib_scan(icon_text()); } + if (rt.eq_w(module, "std")) return std_names; + return icon_names; +} +// the libraries read from files, named in src/lib/modules.txt (one per line, # starts a +// comment), as a list of [next, name, declarations, scanned]. the compiler names none of them. +word file_libs; int file_libs_read; +word lib_files() { + int i = 0, a; word entry, tail = 0; char* save = incres; + if (file_libs_read) return file_libs; + file_libs_read = 1; + char* text = inc_open("src/lib/modules.txt"); + incres = save; + if (!text) return 0; + while (text[i]) { + while (text[i] == 32 || text[i] == 9 || text[i] == 10 || text[i] == 13) i++; + if (text[i] == '#') { while (text[i] && text[i] != 10) i++; continue; } + a = i; while (isidc(text[i])) i++; + if (i == a) { if (text[i]) i++; continue; } + entry = chk_resize(0, 0, 4); entry[1] = lib_copy(text, a, i); + if (tail) tail[0] = entry; else file_libs = entry; + tail = entry; + } + rt.free(text); + return file_libs; +} +// the entry of a file library, or 0 +word lib_file(char* name) { + word p = lib_files(); + while (p) { if (rt.eq_w(p[1], name)) return p; p = p[0]; } + return 0; +} +// a file library's declarations, read and scanned the first time they are needed +word lib_file_names(word entry) { + char* save = incres; + if (entry[3]) return entry[2]; + entry[3] = 1; + char* filename = inc_cat("src/lib", entry[1]); int length = rt.len_w(filename); + char* full = chk_resize(0, 0, length + 4); rt.copy_w(full, filename, length); rt.copy_w(full + length, ".zc", 4); + char* text = inc_open(full); + if (text) entry[2] = lib_scan(text); + rt.free(text); + incres = save; + return entry[2]; +} +// where name is in a library's list: Text.itoa is itself, a bare itoa is a top-level +// declaration first, then a static method of some group. 0 when it is not there. +char* lib_path(word list, char* name) { + word p = list; int k = 0; + while (p) { if (rt.eq_w(p[2], name)) return p[2]; p = p[0]; } + while (name[k]) { if (name[k] == '.') return 0; k++; } + p = list; + while (p) { if (rt.eq_w(p[1], name)) return p[2]; p = p[0]; } + return 0; +} +// the module that exports a bare name, and the path after its prefix: put_c is rt's put_c, +// itoa is std's Text.itoa. every owner, best first, is a list of [next, module, path]: the +// compiler's own modules, then the modules this unit loaded, then the libraries, which are +// read from their source whether this unit loaded them or not. +word export_list, export_tail; char* export_path; +int export_add(char* module, char* path) { + word p = export_list; + if (!path) return 0; + while (p) { if (rt.eq_w(p[1], module)) return 0; p = p[0]; } + p = chk_resize(0, 0, 3); p[1] = module; p[2] = path; + if (export_tail) export_tail[0] = p; else export_list = p; + export_tail = p; + return 0; +} +word export_owners(char* name) { + word p = included_modules; + export_list = 0; export_tail = 0; + export_add("rt", export_in("rt", name)); + export_add("core", export_in("core", name)); + export_add("run", export_in("run", name)); + while (p) { export_add(p[1], export_in(p[1], name)); p = p[0]; } + export_add("std", lib_path(lib_names("std"), name)); + export_add("icon", lib_path(lib_names("icon"), name)); + p = lib_files(); + while (p) { export_add(p[1], lib_path(lib_file_names(p), name)); p = p[0]; } + return export_list; +} +char* export_owner(char* name) { + word p = export_owners(name); + if (!p) return 0; + export_path = p[2]; return p[1]; +} +// name, when module defines it: a function, a global, a constant or a type of it. 0 otherwise. +char* export_in(char* module, char* name) { + int s = lookup_raw(methname(module, name)); + if (s < 0) return 0; + if (skind[s] != K_DOC && (skind[s] != K_FN || (sflag[s] & F_DEF))) return name; + return 0; +} +// end a compile unit from token from onward, and report what it defined. +int unit_end(int from) { + int i = from, best; char* owner; + effect_finish(from); + panic = 0; + while (i < nsym) { + // a bodyless declaration (fn f();) promises a definition elsewhere. in check mode that + // elsewhere is another file of the same build, which this document can't see + if (skind[i] == K_FN && !(sflag[i] & F_DEF) && sfix[i] && !(cmode && (sflag[i] & F_DECL)) && !missing_import_name(sname[i])) { + tunpack(sval[i]); + owner = export_owner(sname[i]); + best = 0; if (!owner) best = similar(sname[i]); + if (!dry) { + errat(); + rt.put_s(sname[i]); rt.put_s(" is used but never defined"); + if (owner) { + rt.put_s("; did you mean to "); + if (!module_imported(owner)) { rt.put_s("include "); rt.put_s(owner); rt.put_s(" and "); } + rt.put_s("use "); rt.put_s(owner); rt.put_c('.'); rt.put_s(export_path); rt.put_s("?"); + } + else if (best) { rt.put_s("; did you mean '"); rt.put_s(best); rt.put_s("'?"); } + errend(); + } + } + i++; + } + missing_imports = 0; + if (nerr) giveup(); + return 0; +} + +// compile a token list as the body of a new function, or (top) as top-level +// declarations. returns the entry address of the function. +// (this is what eval, compile and load are built on. a function body made this +// way is emitted behind a jump when another function is being compiled, so +// compile() works inside a parse word.) +int compile_list_context(int q, int top, char* module, word imports) { + int hop = -1, entry = 0, from = nsym; + ctx_push(); ifcur = 0; ifcall = -1; effect_floor = from; module_name = module; module_imports = imports; + if (infn) { hop = hole(); op(O_JMP); } + if (top) prescan(1, q); + src_push(1, q, 0, 0); + next(); + if (top) unit_top(); + else { + entry = here; + fn_begin(1, 0); + prologue(); borrow_prologue(); borrow_begin(); + while (tk != T_EOF) stmt(); + unused_check(lbase); + if (fchk && live) exit_vars(1, 1); + epilogue(); + } + src_pop(); + unit_end(from); + if (hop >= 0) setlit(hop, here); + if (here > ptop) ptop = here; + ctx_pop(); + return entry; +} +int compile_list(int q, int top) { return compile_list_context(q, top, module_name, module_imports); } +// return an independent copy of a retained function body, excluding its braces. +word*.u function_tokens(int addr) { + int s = 0, open, close, count; word meta, source, result; + while (s < nsym && !(skind[s] == K_FN && sval[s] == addr && fnmeta[s])) s++; + if (s >= nsym) return 0; + meta = fnmeta[s]; source = meta[0]; open = meta[4]; close = meta[5]; count = close - open - 1; + result = rt.new(1 + count * 3); result[0] = count; + if (count) rt.copy_w(result + 1, source + 1 + (open + 1) * 3, count * 3); + return result; +} +// compile a changed body under a fresh name, check its signature, then redirect +// the original entry so existing calls and function values use the new code. +int replace_function(int addr, word* body_tokens) { + int s = 0, open, close, namepos, olderr = nerr, oldpanic = panic, oldabort = chkabort; + int errors, generated_id, new_symbol, count, i = 0, out = 0, cap, diag, seq, digits = 0, published; + char[40] generated; char[24] reversed; word meta, source, full, imports; char* module; + while (s < nsym && !(skind[s] == K_FN && sval[s] == addr && fnmeta[s])) s++; + if (s >= nsym || !body_tokens || body_tokens[0] < 0) return 0; + meta = fnmeta[s]; source = meta[0]; open = meta[4]; close = meta[5]; namepos = meta[3]; module = meta[1]; imports = meta[2]; + count = source[0] - (close - open - 1) + body_tokens[0]; + full = rt.new(1 + count * 3); full[0] = count; + i = 0; while (i <= open) { rt.copy_w(full + 1 + out * 3, source + 1 + i * 3, 3); i++; out++; } + i = 0; while (i < body_tokens[0]) { rt.copy_w(full + 1 + out * 3, body_tokens + 1 + i * 3, 3); i++; out++; } + i = close; while (i < source[0]) { rt.copy_w(full + 1 + out * 3, source + 1 + i * 3, 3); i++; out++; } + seq = fnpatchseq; fnpatchseq++; + rt.copy_w(generated, "__run_patch_", 12); generated[12] = 0; + if (!seq) { reversed[digits] = '0'; digits++; } + else { while (seq > 0) { reversed[digits] = '0' + seq % 10; digits++; seq = seq / 10; } } + i = 0; while (i < digits) { generated[12 + i] = reversed[digits - i - 1]; i++; } + generated[12 + digits] = 0; generated_id = intern(generated); full[2 + namepos * 3] = generated_id; + cap = 4096; diag = rt.new(cap); chk_begin(diag, cap); + compile_list_context(full, 1, module, imports); + errors = nerr || chkabort || panic; + chk_end(); nerr = olderr; panic = oldpanic; chkabort = oldabort; + if (module) new_symbol = lookup_raw(methname(module, generated_id)); else new_symbol = lookup_raw(generated_id); + if (!errors && new_symbol >= 0 && skind[new_symbol] == K_FN && (ssig[new_symbol] == ssig[s]) && (stype[new_symbol] == stype[s])) { + setlit(addr, sval[new_symbol]); *(addr + 1 + hdelta) = O_JMP + O_JMP; + // subsequent transformations start from the body that was published. + if (fnmeta[new_symbol]) fnmeta[s] = fnmeta[new_symbol]; + published = 1; + } else published = 0; + rt.free(diag); rt.free(full); return published; +} +// compile a token list into a function and return its address. +int compile(int q) { return compile_list(q, 0); } +// compile a token list and run its top-level code. +int load(int q) { compile_list(q, 1); return 0; } +// compile a token list and run it, returning its result. +int eval(int q) { int f = compile_list(q, 0); return f(); } +// compile the source string s and run it, returning its result. +int evals(char* s) { + word q = tokens(s); int r = eval(q); + rt.free(d*q); + return r; +} + diff --git a/src/compiler/expr.zc b/src/compiler/expr.zc new file mode 100644 index 0000000..c6d29c6 --- /dev/null +++ b/src/compiler/expr.zc @@ -0,0 +1,1912 @@ +// ---- expressions ---- + +// every expression leaves its value on the stack, returns its analyzer class, +// and sets xty to its type. lv means the stack holds an address that hasn't +// been loaded yet; structs and arrays are never loaded, their address is +// their value. +// memdebug: the address on top of the stack, checked before a heap load +// (dbg_addr duplicates the address on top and calls the runtime's checkptr on +// the copy, so memdebug catches a load from a freed object) +int dbg_addr() { + if (mdbg && !dry) { + lit(0); + op(O_PICK); + callname(s_check); + op(O_DROP); + } + return 0; +} +// turn an lvalue into a value. when lv is set the stack holds the address of a +// variable, field or element that has not been loaded; rval loads it, unless +// the thing is an array or struct, whose address is its value. every place +// that needs a value calls this after parsing an expression. +int rval() { + if (lv && lvk == LV_LOCAL) { + ltag[lvi] = ltag[lvi] | LREAD; + if (rt.eq_w(lname[lvi], "_") && af_warning(lreadpos[lvi])) { + rt.put_s("discard placeholder `_` is read\nhelp: give the parameter or pattern binding a name when its value is needed"); + af_warning_end(); + } + } + if (lv) { + if (ispk(xty)) pkld(xty); + else if (!isagg(xty)) { dbg_addr(); op(O_LD); } + lv = 0; + } + return 0; +} +// run a keyword's handler (kw_len, kw_struct, a parse word...) from the spot +// where the parser met it. e is 1 in expression position, where statement-only +// keywords refuse to run. the handler reports the class of what it produced +// in xcls. +int kwcall(int s, int e) { + int f = sval[s], old = kw_expr, c, n0 = ntok; + kw_expr = e; xcls = C_PLAIN; xty = tword; xzero = 0; + f(); xaf = 0; xvk = 0; + // a handler that never reads a token (not even its own name) would be called + // again on the same token forever, so it is an error, and the token is skipped + if (ntok == n0) die("a parse word has to read its own name with next()"); + xbor = -1; + kw_expr = old; + c = xcls; + lv = 0; lvk = LV_NONE; + return c; +} +// report an error when a statement is used inside an expression. +int stmtonly() { + if (kw_expr) serr("this can't be used inside an expression"); + return 0; +} +// the arguments of a call. ft is the callee's type (or 0), bs the generic bindings +// (tags packs two bits per parameter: 0 lend, 1 .m, 2 .d. bs collects type +// parameter bindings as the argument types are unified with the signature. +// fat asks for every pointer argument to be fattened first, for o3 code +// calling code that is not o3.) +int args(int tags, word ft, int bs, int fat) { + int np = 0, c, pt, t, cs = ifcall, freeing = 0, harmless = 0; word first = 0; int origin = -1, anytemp = 0, current, tag, place, parameter_base = effect_argument_base, owner = -1, current_owner, owner_seen = 0, aliases; word origins = 0, current_mask, owner_fact = 0; + if (cs >= 0 && cs < ubase) { + freeing = sname[cs] == s_free || sname[cs] == s_recycle; + harmless = sname[cs] == s_put_s || rt.eq_w(sname[cs], "rt.put_n") || rt.eq_w(sname[cs], "rt.put_c") || rt.eq_w(sname[cs], "len") || rt.eq_w(sname[cs], "cap"); + } + xgive = 0; ifcall = 0 - 1; effect_argument_base = 0; + expect(s_lp); + if (!is_op(s_rp)) { + while (1) { + word expected = closure_expected; + closure_expected = 0; + if (ft && np + parameter_base < ft[3] && is_id(s_fn)) closure_expected = subst(ft[7 + np + parameter_base], bs); + place = tpack(); c = expr(); rval(); closure_expected = expected; + current = effect_origin(c); current_mask = effect_mask(c); origins = origins | current_mask; effect_args_mask(cs, np + parameter_base, current_mask); + if (!np) origin = current; else if (origin != current) origin = -1; + tag = (tags >> (np + np)) & 3; + current_owner = xbor; + if (c >= C_VAR && tracked(c - C_VAR)) current_owner = lroot(c - C_VAR); + if (tag <= 1 && current_owner >= 0 && tracked(current_owner)) effect_require_mode(cs, np + parameter_base, place, 1); + aliases = effect_get(cs, np + parameter_base); + if (aliases & (EF_ALIAS | EF_IDENTITY | EF_UNKNOWN)) { + if (!owner_seen) { owner = current_owner; owner_fact = xaf; owner_seen = 1; } + else if (owner != current_owner) { owner = -1; owner_fact = 0; } + } + if (fchk && c == C_FRESH && tag != 2) { + if (!(cs < 0 && tag == 1)) effect_require(cs, np + parameter_base, place); emit_borrow_hold(); c = C_PLAIN; anytemp = 1; + } + if (xaf && xaf[2] == 5) { anytemp = 1; if (!(cs < 0 && tag == 1)) effect_require(cs, np + parameter_base, place); } + if (fchk && xaf && xaf[2] == 4 && (isptr(xty) || isagg(xty)) && !(cs < 0 && tag == 1)) effect_require(cs, np + parameter_base, place); + if (!np) first = xaf; + if (!freeing && !harmless && (effect_get(cs, np + parameter_base) & (EF_WRITE | EF_RETAIN | EF_CONSUME | EF_UNKNOWN))) af_escape(xaf); + if (ifs && if_value && cs >= 0 && sname[cs] == s_new && !np) if_join(if_value, tint); + if (cs >= 0) if_arg(cs, np + parameter_base, xty, xzero); + if (if_value && cs >= 0 && np + parameter_base < IFP) { + word infer_callee = if_get(cs); + if (infer_callee) { + if (ft && np + parameter_base < ft[3]) if_join(infer_callee + 8 + 3 * (np + parameter_base), subst(ft[7 + np + parameter_base], bs)); + if_link(if_value, infer_callee + 8 + 3 * (np + parameter_base)); + } + } + if (ft && np + parameter_base < ft[3]) fcoerce(subst(ft[7 + np + parameter_base], bs)); + t = xty; + argcheck(c, (tags >> (np + np)) & 3); + // o3 data handed to normal code + if (fat && hasptr(t)) { + lit(t); + callname(s_fatten); + xty = t; + } + if (ft && np + parameter_base < ft[3]) { + pt = ft[7 + np + parameter_base]; + if (cs >= 0 && sname[cs] == s_put_s && np == 0 && xstr) pt = tbstr; + if (!unify(pt, xty, bs)) tcerr("argument", subst(pt, bs), xty); + } + np++; + if (!accept(s_comma)) break; + } + } + expect(s_rp); + argfact = first; argorigin = origin; argorigin_mask = origins; argtemporary = anytemp; argowner = owner; argowner_fact = owner_fact; xaf = 0; vf_effect(); + return np; +} +// memdebug records the source line on the next new, via the runtime global +// memdebug: store the current line in the runtime's alloc_line just before an +// allocation, so a leak report can say where the object came from +int stamp_site() { + int s; + if (!mdbg || dry || o3on) return 0; + s = lookup(s_alloc); + if (s < 0) return 0; + lit(tline); lit(sval[s]); op(O_ST); + return 0; +} +// new(T) and new(T, count): a typed object, its type stamped into the header +// new(T), new(T, n), new(T[?]), new(fixed T[?], n). a growable array goes to +// dnew. otherwise the call is new_t(size, type), unless the size is known at +// compile time and we are not in o3 or a dry run: then the size class is +// computed here and the call is new_k(size, type, class), which pops a block +// straight off the class list. newsz and newleaf record what was allocated so +// the variable it lands in can later be freed with the matching fast path. +int newtyped() { + word t; int c, fast = 0, length = 1, place = tpack(); + t = ptype(); + if (t[0] == TY_DYN) { // a growable array: element size, length, type + if (tbytes(t[4])) lit(0 - tbytes(t[4])); else lit(tsz(t[4])); // packed elements say so with a negative size: minus their bytes + if (accept(s_comma)) { c = expr(); rval(); plainuse(c); length = -1; if (xconst && xcval >= 0) length = xcval; } + else { length = 0; lit(0); if (t[3]) serr("a fixed array needs its length: new(fixed T[?], n)"); } + lit(t); + expect(s_rp); + stamp_site(); + callname(s_dnew); vf_effect(); + xaf = af_new(length, 3, place); + lv = 0; lvk = LV_NONE; xty = t; xzero = 0; xstamp = 1; + return C_FRESH; + } + if (accept(s_comma)) { + c = expr(); rval(); plainuse(c); + length = -1; if (xconst && xcval >= 0) length = xcval; + if (tbytes(t)) { // packed: n elements take (n * bytes + 7) / 8 words + if (tbytes(t) != 1) { lit(tbytes(t)); op(15); } + lit(7); op(O_ADD); lit(3); op(19); + } else if (tsz(t) != 1) { lit(tsz(t)); op(15); } + lit(tarr(t, 0)); + newsz = -1; newleaf = 0; + } else { + lit(tsz(t)); lit(t); + newsz = tsz(t); newleaf = 0; + if (isleaf(t)) newleaf = t; + if (!o3on && !dry && lookup(s_new_k) >= 0) { lit(szclass(newsz)); fast = 1; } + } + expect(s_rp); + stamp_site(); + if (fast) callname(s_new_k); else callname(s_new_t); vf_effect(); + xaf = af_new(length, 2, place); + lv = 0; lvk = LV_NONE; xty = tptr(t); xzero = 0; xstamp = 1; + return C_FRESH; +} +// ld8/st8 and the 16- and 32-bit pair. 0 when this call should stay a call. +// a store's opcode is odd: the arguments arrive address then value, and the +// opcode wants the address on top. +// (the byte-access functions become single opcodes inside o3 code) +int byteop(int s) { + if (sname[s] == s_ld8) return 612; + if (sname[s] == s_st8) return 613; + if (sname[s] == s_ld16) return 614; + if (sname[s] == s_st16) return 615; + if (sname[s] == s_ld32) return 616; + if (sname[s] == s_st32) return 617; + return 0; +} +// ---- type facts ---- + +// annotate mode (the language server asks for it, ifon) collects the types the +// untyped declarations of a document could be given, as edits: a place, how +// many chars it replaces (0 inserts) and the text. a local gets the type of +// its initializer, a parameter the type every call passes it, and a function +// the type of what it returns. only types that change nothing are offered: no +// floats and no sized integers, because a typed one converts or cuts to size +// where the untyped word did not, so the annotated program is the same program. +// the declarations are found while they compile, the calls too, and the +// edits are written at the end of the unit, when every call has been seen. +// a type that can be written in front of a name and changes nothing +word ifpure(word t) { + int k; + if (!t || t == tword || t == tvoid) return 0; + k = t[0]; + if (k == TY_INT) return t[5] == 0; + if (k == TY_CHAR || k == TY_BOOL || k == TY_STRUCT || k == TY_BSTR) return t[2] != 0; + if (k == TY_PTR) { + k = t[4][0]; + if (k == TY_ARR || k == TY_DYN || k == TY_FN) return 0; + return t[4] == tword || ifpure(t[4]); + } + if (k == TY_ARR || k == TY_DYN) return t[4] == tword || ifpure(t[4]); + return 0; +} +// one edit: the token at place p is replaced by the type t and then the text +// after (len < 0: the token's own length; 0: nothing is replaced) +int fact_add(int p, int len, int t, int after) { + int sv, o, at, n; word e; int osm = rt.outmode, osb = rt.outbuf, osn = rt.outn, osc = rt.outcap; + // the text is printed with tprint into the pool: output mode 2 writes into outbuf + if (!ifx || dry || panic || chkabort) return 0; + sv = tpack(); tunpack(p); o = toff; if (len < 0) len = toklen; tunpack(sv); + if (o < 0) return 0; + at = nift; + rt.outn = 0; rt.outmode = 3; tprint(t); rt.put_s(after); n = rt.outn; + if (nifx >= ifxcap) { ifxcap = ifxcap * 2; ifx = chk_resize(ifx, nifx * 4, ifxcap * 4); } + if (at + n > iftcap) { while (at + n > iftcap) iftcap = iftcap * 2; ift = chk_resize(ift, at, iftcap); } + rt.outbuf = ift + at; rt.outn = 0; rt.outcap = n; rt.outmode = 2; + tprint(t); rt.put_s(after); + n = rt.outn; + rt.outmode = osm; rt.outbuf = osb; rt.outn = osn; rt.outcap = osc; + e = ifx + nifx * 4; + e[0] = o; e[1] = len; e[2] = at; e[3] = n; + nifx++; nift = at + n; + return 0; +} +// the record of function symbol s: calls can come before the definition +word if_get(int s) { + int id; word r; + if (!ifs || s < 0 || s >= symcap) return 0; + id = ifs[s]; + if (id) return ifr[id - 1]; + if (nif >= ifrcap) { ifrcap = ifrcap * 2; ifr = chk_resize(ifr, nif, ifrcap); } + nif++; ifs[s] = nif; + r = chk_resize(0, 0, IFW); ifr[nif - 1] = r; + rt.fill_w(r, 0, IFW); + r[0] = s; + return r; +} +// a state is 0 (nothing seen), a type, or -1 (no single type): p points at it +int if_join(word p, int t) { + if (!t || t == tword) return 0; + if (*p == 0 - 1) return 0; + if (!ifpure(t)) *p = 0 - 1; + else if (!*p) *p = t; + else if (*p != t) *p = 0 - 1; + return 0; +} +// equal type flows connect caller parameters, callee parameters and results. +// nodes are addresses of inference states, retained by the function records. +int if_link(word a, word b) { + word edge; + if (!ifs || !a || !b || a == b || dry) return 0; + edge = chk_resize(0, 0, 3); edge[0] = if_edges; edge[1] = a; edge[2] = b; if_edges = edge; + return 0; +} +int if_solve() { + word edge, a, b, record; int changed = 1, left, right, index = 0; + while (index < nif) { + record = ifr[index]; + if (!record[3] && (!record[2] || !record[1])) record[3] = -1; + index++; + } + while (changed) { + changed = 0; edge = if_edges; + while (edge) { + a = edge[1]; b = edge[2]; left = *a; right = *b; + if (left == -1 || right == -1 || (left && right && left != right)) { *a = -1; *b = -1; } + else if (left) *b = left; else if (right) *a = right; + if (*a != left || *b != right) changed = 1; + edge = edge[0]; + } + } + return 0; +} +// argument j of a call of s has the type t (a literal 0 says nothing: it is a +// null pointer as often as a number) +int if_arg(int s, int j, int t, int zero) { + int r; + if (!ifs || dry || j >= IFP || zero) return 0; + r = if_get(s); + if (r) if_join(r + 8 + 3 * j, t); + return 0; +} +// a function at the top level starts: its header is at ifkp and ifkk says how +// to edit it (1: fn becomes the type, 2: the type goes in front of a .u) +int if_begin(int s, int k) { + word r = if_get(s); + ifcur = 0; + if (!r) return 0; + r[2] = 1; r[5] = ifkp; r[6] = k; + ifcur = r; + return 0; +} +// parameter j: p is where its type would go, kind 0 for a typed one, 1 to +// insert the type and a space, 2 to insert it right in front of a tag +int if_param(int j, int p, int kind, word type) { + word r = ifcur; + if (!r || j >= IFP) return 0; + if_join(r + 8 + 3 * j, type); r[9 + 3 * j] = p; r[10 + 3 * j] = kind; + if (kind) r[7] = j + 1; + return 0; +} +// a return statement: t is what it returns, zero if that is a bare 0 +int if_ret(int t, int zero) { + word r = ifcur; + if (!r || nclo || dry) return 0; + r[1] = 1; + if (if_value && !zero) if_link(r + 3, if_value); + if (zero) r[4] = 1; else if_join(r + 3, t); + return 0; +} +// the function ends: which of its parameters were used as addresses +int if_fin() { + word r = ifcur; int j = 0; + while (j < r[7]) { + if (lptr[lbase + j]) r[10 + 3 * j] = r[10 + 3 * j] | 4; + j++; + } + return 0; +} +// the edits of every function, once all the calls have been seen +int if_emit() { + int i = 0, j; word r; int t, k; + if_solve(); + while (i < nif) { + r = ifr[i]; + i++; + if (!r[2]) continue; + j = 0; + while (j < r[7]) { + k = r[10 + 3 * j]; t = r[8 + 3 * j]; + if ((k & 3) && !t && (k & 4)) t = tptr(tword); // no call says more, but the body uses it as an address + if ((k & 3) == 1 && t > 0) fact_add(r[9 + 3 * j], 0, t, " "); + else if ((k & 3) == 2 && t > 0) fact_add(r[9 + 3 * j], 0, t, ""); + j++; + } + t = r[3]; + if (r[1] && t != 0 - 1) { + if (!t && r[4]) t = tint; + if (t && r[6] == 1) fact_add(r[5], 0 - 1, t, ""); + else if (t && r[6] == 2) fact_add(r[5], 0, t, ""); + } + } + return 0; +} +// ---- hints ---- + +// findings that are not errors. each is written only in check mode, through +// hint_begin, and none of them changes the code the compiler emits. +int magic(int v) { + if (literal_documented(toff) || !hint_begin(tpack(), HINT_ALL)) return 0; + rt.put_s("unannotated numeric literal "); rt.put_n(v); + rt.put_s("\nhelp: use a named constant or add an inline block comment before the literal to suppress this hint"); + dg_close(); + return 0; +} +// warn that a new inside a loop allocates on every pass. +int newloop(int p) { + if (!hint_begin(p, HINT_SMALL)) return 0; + rt.put_s("new inside a loop allocates on every pass; allocate once above the loop if the object can be reused"); + dg_close(); + return 0; +} +// hint when a shift by v is past the width of the value. +int shifthint(int p, int v) { + int k = 0; + if (!hint_begin(p, HINT_ALL)) return 0; + while ((1 << k) < v) k++; + rt.put_s("multiplying by "); rt.put_n(v); rt.put_s(" is a shift; write << "); rt.put_n(k); + dg_close(); + return 0; +} +// a call to function symbol s, with the callee name already consumed and the +// ( next. in order: +// 1. new(...) with a type argument is redirected to newtyped +// 2. in o3 code, untyped new becomes new_oz and the byte accessors turn into +// opcodes +// 3. the arguments are parsed against the signature (args), checking each +// against its parameter tag; generic type parameters are bound here +// 4. free whose argument was a given-away last pointer becomes recycle; a +// constant-size new becomes new_k with its class; put_s of a byte string +// becomes put_bs +// 5. the call is emitted (fnref pushes the address, or a patch site if the +// function isn't defined yet) +// 6. the result's type and class are set: .u functions return C_FRESH, which +// is how the analyzer knows the caller now owns a new object +int call(int s) { + int np, tags = 0, ret = 0; word ft = stype[s]; word[8] bs; int rs, rhs_type = 0, kop, cp = callpos, original = s; word storage; int origin, temporary, owner; word origins, owner_fact; + word requirements = effect_requirements, edges = effect_edges; + borrow_depth++; + if (sname[s] == s_new && skind[s] == K_FN) { + peek(); + if (pk_tk == T_OP && pk_tv == s_lk) { next(); borrow_depth--; return newtyped(); } + if (pk_tk == T_ID && (istypename(pk_tv) || pk_tv == s_fn)) { next(); borrow_depth--; return newtyped(); } + } + if (o3on && sname[s] == s_new) { tags = lookup(s_new_oz); if (tags >= 0) s = tags; tags = 0; } + if (o3on) { + kop = byteop(s); + if (kop) { + np = args(0, 0, bs, 0); + if (kop & 1) { + if (np != 2) serrn(sname[s], "wrong number of arguments"); + op(O_SWAP); op(kop); lit(0); + } else { + if (np != 1) serrn(sname[s], "wrong number of arguments"); + op(kop); + } + lv = 0; lvk = LV_NONE; xty = tword; xzero = 0; xbor = -1; + xfloat = 0; xstr = 0; xlenloc = -1; xstamp = 0; + borrow_depth--; return C_PLAIN; + } + } + if (sflag[s] & F_SIG) { tags = ssig[s] >> 6; ret = ssig[s] & 1; } + else sflag[s] = sflag[s] | F_UNTYPED; + if (sflag[s] & F_RETOWN) ret = 1; + rt.fill_w(bs, 0, 8); + if (ngbind) { rt.copy_w(bs, gbind, ngbind); ngbind = 0; } + np = 0; + if (o3on && !(sflag[s] & F_O3) && sname[s] != s_free && sname[s] != s_recycle) np = 1; // o3 calling normal code: fatten the arguments + ifcall = s; effect_argument_base = 0; + np = args(tags, ft, bs, np); + storage = argfact; origin = argorigin; origins = argorigin_mask; temporary = argtemporary; owner = argowner; owner_fact = argowner_fact; + if (original < ubase && (sname[original] == s_free || sname[original] == s_recycle)) af_end(storage, cp); + if (sflag[s] & F_GEN) { int template = s; s = ginstance(s, bs); effect_specialize(template, s, requirements, edges); } + if (sname[s] == s_free && xgive) { // the last pointer: straight back to the size-class list + rs = lookup(s_recycle); + if (rs >= 0) s = rs; + } + if ((sflag[s] & F_SIG) && np != ((ssig[s] >> 1) & 31)) serrn(sname[s], "wrong number of arguments"); + if (sname[s] == s_new) { + stamp_site(); newsz = -1; newleaf = 0; + if (np == 1 && xconst && xcval >= 0) newsz = xcval; + xaf = af_new(newsz, 2, cp); + if (lsp > 0) newloop(cp); + // a constant size: the class is known now, so use the allocator's direct path + if (newsz >= 0 && !o3on && !dry && lookup(s_new_k) >= 0) { + lit(0); + lit(szclass(newsz)); + s = lookup(s_new_k); + } + } + if (sname[s] == s_put_s && np == 1 && xstr) { + s = lookup(s_put_bs); + if (s < 0) s = fwdfn(s_put_bs); + } + if (!(sflag[s] & F_DEF) || s == finsym) sflag[s] = sflag[s] | F_EARLY; // called before its body: an owned return is not inferred for it + if (sflag[s] & F_EXT) { lit(sval[s][-1]); op(620); } // an extern: the native call opcode, with the block's address + else { fnref(s); op(O_CALL); } + if (!o3on && (sflag[s] & F_O3) && ft) { // non-o3 called o3: fatten the result here, not inside the callee + rhs_type = subst(ft[4], bs); + if (hasptr(rhs_type)) { lit(rhs_type); callname(s_fatten); } + } + xstr = 0; + if (sname[s] == s_cat_s || sname[s] == s_slice_s || sname[s] == s_n_to_s || sname[s] == s_new_s) { + xstr = 1; + } + lv = 0; lvk = LV_NONE; xzero = 0; xbor = -1; xconst = 0; + xty = tword; + if (ft) xty = subst(ft[4], bs); + if (xstr || xty == tbstr) { xstr = 1; xty = tbstr; } + xfloat = 0; + if (isfl(xty)) xfloat = 1; + xlenloc = -1; xstamp = 0; + if (o3on && (sflag[s] & F_O3) && ft && hasptr(xty)) xstamp = 1; + borrow_depth--; effect_value = 0; + if (!ret && (isptr(xty) || xty == tword)) { + effect_value = origins; + if (owner >= 0 && (isptr(xty) || (effect_get(original, 0) & EF_IDENTITY))) xbor = owner; + if (owner_fact && (effect_get(original, 0) & EF_IDENTITY)) xaf = owner_fact; + if (origin >= 0 && owner < 0) { int oi = lbase; while (oi < nloc) { if (lrskind[oi] == RS_PARAM && oi - lbase == origin) { xbor = oi; break; } oi++; } } + if (temporary) { int ai = 0, aliases = 0; while (ai < np) { aliases = aliases | effect_get(original, ai); ai++; } if (aliases & (EF_ALIAS | EF_UNKNOWN)) xaf = af_new(-1, 5, cp); } + } + if_value = 0; + if (ifs && !dry) { + word result_node = if_get(original); + if (result_node) { + if (sname[original] == s_new && !result_node[3]) result_node[3] = tword; + else if_join(result_node + 3, xty); + if_value = result_node + 3; + } + } + if (ret) return C_FRESH; + return C_PLAIN; +} +// an enhancement used like a function: the arguments, then its one opcode +// an enhancement (a custom opcode declared with "enhance") called like a +// function. it compiles to the one opcode, or to a call of its fallback +// function when the cpu does not have the opcode. +int enhcall(int s) { + int nin = ssig[s] & 15, nout = (ssig[s] >> 4) & 15, n = 0, c; + expect(s_lp); + if (!is_op(s_rp)) { + while (1) { + c = expr(); + rval(); + plainuse(c); + n++; + if (!accept(s_comma)) break; + } + } + expect(s_rp); + vf_effect(); + if (n != nin) serrn(sname[s], "wrong number of inputs"); + lv = 0; lvk = LV_NONE; xty = tword; xzero = 0; xbor = -1; xlenloc = -1; xstamp = 0; + if (sflag[s] & F_FALLBACK) { fnref(sfix[s] - 1); op(O_CALL); return C_PLAIN; } + op(XOP0 + sval[s]); + if (nout == 0) lit(0); + if (nout > 1) serrn(sname[s], "leaves several values; use it from asm"); + return C_PLAIN; +} +// ---- closures ---- + +// fn(a, b) { ... } is a function whose last, hidden parameter is a pointer to +// its environment. the closure value is a heap block: word 0 is the body's +// entry, and the captured variables start at word 3. the block itself is never +// run as code. callable_code distinguishes plain function addresses, including +// linked heap code segments, from closure blocks. a plain function is called +// as it is. a closure is called with block + 3 as its env, at the entry in +// word 0. a capture is a shared cell. the enclosing function and every closure +// read and write the same value; cells remain reachable through escaping closures. +// scan with the compiler lexer, restoring its source and lookahead. only functions containing a closure +// need cells; their scalars get cells from declaration time, so loop back edges and earlier references agree. +int capture_scan() { + word old, f; int depth = 1, found = 0, docs = docs_on, record = grec; + if (isp < 0 || R_SRC[isp * 32] == 0 || !is_op(s_lb)) return 0; + old = R_SRC + isp * 32; + src_push(1, capture_dummy, 0, 0); old = R_SRC + (isp - 1) * 32; f = R_SRC + isp * 32; + f[0] = old[0]; f[1] = old[1]; f[2] = old[2]; f[3] = old[3]; f[4] = 0; pq_n = f[14]; + docs_on = 0; grec = 0; dry++; + while (depth) { + next(); if (tk == T_EOF) break; + if (is_op(s_lb)) depth++; + else if (is_op(s_rb)) depth--; + else if (is_id(s_fn)) { peek(); if (pk_tk == T_OP && (pk_tv == s_lp || pk_tv == s_colon)) found = 1; } + } + dry--; src_pop(); docs_on = docs; grec = record; return found; +} +int capture_cell(int i, int copy) { + if (!capture_enabled || o3on || (ltag[i] & 16) || larr[i] || local_cell[i]) return 0; + if (copy) { laddr(lslot[i]); op(O_LD); } + lit(1); lit(ltype[i]); callname(s_new_t); callname(intern("share")); + if (copy) { lit(0); op(O_PICK); laddr(lslot[i]); op(O_ST); op(O_ST); } + else { laddr(lslot[i]); op(O_ST); } + local_cell[i] = 1; return 0; +} +int capture_prepare() { + int i = lbase; capture_enabled = capture_scan(); + if (!capture_enabled) return 0; + while (i < nloc) { if (lrskind[i] == RS_PARAM) capture_cell(i, 1); i++; } + return 0; +} +// grow closure frames and capture rows while keeping closure and capture indices. +int reserve_closures(int need) { + int cap = closure_cap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + clo_lo = chk_resize(clo_lo, nclo, cap); + clo_hi = chk_resize(clo_hi, nclo, cap); + clo_n = chk_resize(clo_n, nclo, cap); + clo_env = chk_resize(clo_env, nclo, cap); + clo_ret = chk_resize(clo_ret, nclo, cap); + clo_name = chk_resize(clo_name, nclo * capture_width, cap * capture_width); + clo_loc = chk_resize(clo_loc, nclo * capture_width, cap * capture_width); + clo_via = chk_resize(clo_via, nclo * capture_width, cap * capture_width); + closure_cap = cap; return cap; +} +int reserve_captures(int need) { + int cap = capture_width, i = 0; char** names; word locals, via; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + names = chk_resize(0, 0, closure_cap * cap); + locals = chk_resize(0, 0, closure_cap * cap); + via = chk_resize(0, 0, closure_cap * cap); + while (i < nclo) { + rt.copy_w(names + i * cap, clo_name + i * capture_width, clo_n[i]); + rt.copy_w(locals + i * cap, clo_loc + i * capture_width, clo_n[i]); + rt.copy_w(via + i * cap, clo_via + i * capture_width, clo_n[i]); i++; + } + clo_name = names; clo_loc = locals; clo_via = via; capture_width = cap; return cap; +} +// find or create the capture slot for variable n in the innermost closure +// being compiled: if the name is one of the enclosing function's locals, it is +// recorded once and the body reads its shared cell through the environment. +// a name from further out is captured through each closure in between: the +// closure that encloses this one captures it too, and this one copies it from +// that closure's environment (clo_via is the slot there, -1 for a local). +word clocap(int n) { return clocap_at(nclo - 1, n); } +// the slot of the name n in closure c's captures, adding it when the closure has +// not captured it yet. returns the capture index, or -1 when no such name is visible. +word clocap_at(int c, int n) { + int i = 0, j, base = c * capture_width, r, root; + while (i < clo_n[c]) { if (clo_name[base + i] == n) return i; i++; } + j = clo_hi[c]; + while (j > clo_lo[c]) { + j--; + if (lname[j] != n) continue; + effect_note(effect_origin(C_VAR + j), EF_RETAIN); + root = lroot(j); local_captured[j] = 1; local_captured[root] = 1; + if (lafact[j] && (lafact[j][2] == 1 || lafact[j][2] == 4 || lafact[j][2] == 5)) serrn(n, "a stack or temporary borrow cannot escape through a closure"); + if (root != j && lname[root]) { clocap_at(c, lname[root]); base = c * capture_width; } + if (ltag[j] & 16) serrn(n, "an o3 variable cannot be captured by reference; use an ordinary local"); + if (larr[j]) serrn(n, "an array can't be captured; capture a pointer to it"); + reserve_captures(clo_n[c] + 1); base = c * capture_width; + clo_name[base + clo_n[c]] = n; clo_loc[base + clo_n[c]] = j; clo_via[base + clo_n[c]] = 0 - 1; clo_n[c]++; + return clo_n[c] - 1; + } + if (c == 0) return 0 - 1; + r = clocap_at(c - 1, n); base = c * capture_width; + if (r < 0) return 0 - 1; + reserve_captures(clo_n[c] + 1); base = c * capture_width; + clo_name[base + clo_n[c]] = n; clo_loc[base + clo_n[c]] = clo_loc[(c - 1) * capture_width + r]; clo_via[base + clo_n[c]] = r; clo_n[c]++; + return clo_n[c] - 1; +} +// closure environments retain precise capture-cell types for runtime reflection. +word closure_layout(int closure_index, int count) { + word t = tnew(TY_STRUCT, 3 + count, 0, 0), fields = talloc((3 + count) * 3); int i = 0, local; + t[3] = 3 + count; t[4] = fields; + while (i < 3 + count) { + fields[i * 3 + 2] = i; + if (i < 3) fields[i * 3 + 1] = tword; + else { local = clo_loc[closure_index * capture_width + i - 3]; fields[i * 3 + 1] = tptr(ltype[local]); } + i++; + } + return t; +} +// fn(params) { body } as an expression. the body is compiled in place behind +// a jump (hop) as a nested function with a hidden last parameter, env. when +// the code that creates the closure runs, it allocates the block (code, then +// captured values), writes the three code words and copies each captured +// variable in. ctx_push / ctx_pop save and restore the enclosing function's +// compiler state around the body. +int closure() { + int np = 0; char*[20] pn; word[20] pty; int hop, entry, i, t, envl, ncap, c, result, declared = 0, tags = 0, tag; word[20] ptags; + word expected = closure_expected; closure_expected = 0; + reserve_closures(nclo + 1); + if (o3on) serr("closures can't be made inside an o3 region"); + next(); + // fn:type(args) names the result; returns are coerced and checked against it + if (accept(s_colon)) declared = ptype0(); + expect(s_lp); + if (!is_op(s_rp)) { + while (1) { + pty[np] = tword; + if (expected && expected[0] == TY_FN && np < expected[3]) pty[np] = expected[7 + np]; + if (!is_op(s_dot) && paramstart()) { + pty[np] = ptype(); if (pty[np][0] == TY_ARR) pty[np] = decay(pty[np]); + } + tag = 0; + if (accept(s_dot)) { if (is_id(s_m)) tag = 1; else if (is_id(s_d)) tag = 2; else die("a parameter tag is .m or .d"); next(); } + ptags[np] = tag; if (np < 12) tags = tags | (tag << (np + np)); else if (tag) die("only the first 12 parameters can be tagged"); + if (expected && expected[0] == TY_FN && !tag && (isptr(pty[np]) || pty[np][0] == TY_DYN || pty[np][0] == TY_FN)) ptags[np] = 1; + pnoffs[np] = toff; pnplaces[np] = tpack(); rs_note(RS_PARAM, toff); pn[np] = name(); + np++; + if (np >= 20) { die("too many parameters"); break; } + if (!accept(s_comma)) break; + } + } + expect(s_rp); + ctx_push(); ifcur = 0; ifcall = -1; + clo_lo[nclo] = lbase; clo_hi[nclo] = nloc; clo_n[nclo] = 0; clo_ret[nclo] = 0; + nclo++; + hop = hole(); op(O_JMP); + entry = here; + fn_begin(1, 0); + if (declared) fty = declared; + prologue(); borrow_prologue(); + i = 0; + while (i < np) { + t = newloc(pn[i]); lrskind[t] = RS_PARAM; lrsoff[t] = pnoffs[i]; lpos[t] = pnplaces[i]; fslots++; lsmark(1, pn[i]); + ltype[t] = pty[i]; + if (fchk && ptags[i] == 1) lstate[t] = S_BORROW; + if (fchk && ptags[i] == 2) { lstate[t] = S_M; lown[t] = 1; } + if (isfl(pty[i])) setflt(t, 1); + i++; + } + envl = newloc(intern("<env>")); fslots++; + clo_env[nclo - 1] = envl; + i = np + 1; + while (i) { i--; laddr(i); op(O_ST); } + body(); + result = clo_ret[nclo - 1]; + if (result <= 0 || live) result = tword; + if (declared) result = declared; + epilogue(); + nclo--; + c = nclo; + ncap = clo_n[c]; + setlit(hop, here); + ctx_pop(); + if (probe_off >= 0 && probe_has == 1 && tpend <= probe_off && !ginst) probe_snap(); // past the closure: what the function around it has + // the block: [push env][push body][jump] and the captured values + // the block: word 0 is the body's entry, words 3 on are the captured values. + // a call through the closure pushes block + 3 as the env and jumps to word 0. + lit(3 + ncap); lit(closure_layout(c, ncap)); callname(s_new_t); + lit(entry); lit(1); op(O_PICK); op(O_ST); + i = 0; + while (i < ncap) { + t = clo_loc[c * capture_width + i]; + if (clo_via[c * capture_width + i] < 0) { + touch(t); effect_note_mask(effect_mask(C_VAR + t), EF_RETAIN); ev_put(t, EV_CAPTURE, -1); + if (cmode && !dry) { lvescaped[t] = 1; lvkind[t] = 0; } + if (!isalias(t)) { vaddr(t); op(O_LD); callname(intern("share")); op(O_DROP); } + vaddr(t); + } + else { vaddr(clo_env[c - 1]); op(O_LD); lit(clo_via[c * capture_width + i]); op(O_ADD); op(O_LD); } + lit(1); op(O_PICK); lit(3 + i); op(O_ADD); op(O_ST); + i++; + } + lv = 0; + lvk = LV_NONE; + xzero = 0; + xbor = -1; + xconst = 0; + xfloat = 0; + xstr = 0; + xlenloc = -1; + xstamp = 0; + xty = tfn(result, np, pty, np + np + tags * 64); + return C_FRESH; +} + +// ---- array initializer lists and compound literals ---- + +// keep original token places while measuring and replaying an initializer. measurement never evaluates +// value expressions: values run once, in source order, after zero filling the storage. +word array_tokens() { + word q = chk_resize(0, 0, 97); int cap = 32, n = 0, depth = 0; + while (tk != T_EOF) { + if (n == cap) { cap = cap * 2; q = chk_resize(q, n * 3 + 1, cap * 3 + 1); } + q[1 + n * 3] = tk; q[2 + n * 3] = tv; q[3 + n * 3] = tpack(); n++; + if (is_op(s_lb)) depth++; + if (is_op(s_rb)) depth--; + next(); if (!depth) break; + } + if (depth) die("unterminated array initializer"); + q[0] = n; return q; +} +word array_string_tokens(word t) { + int count, i = 0, place = tpack(); char* text = tv; word q; + count = rt.len_w(text); + if (t[3] >= 0 && t[3] == count) count--; // the array has no room for the optional terminating zero + q = chk_resize(0, 0, (count + 3) * 6 + 1); q[0] = 1; + q[1] = T_OP; q[2] = s_lb; q[3] = place; + while (i <= count) { + if (i) { q[1 + q[0] * 3] = T_OP; q[2 + q[0] * 3] = s_comma; q[3 + q[0] * 3] = place; q[0]++; } + q[1 + q[0] * 3] = T_NUM; q[2 + q[0] * 3] = text[i]; + q[3 + q[0] * 3] = place; q[0]++; i++; + } + q[1 + q[0] * 3] = T_OP; q[2 + q[0] * 3] = s_rb; q[3 + q[0] * 3] = place; q[0]++; + next(); return q; +} +word array_initializer_tokens(word t) { + if (tk == T_STR || tk == T_BSTR) return array_string_tokens(t); + return array_tokens(); +} +int array_skip_value() { + int depth = 0; + while (tk != T_EOF) { + if (!depth && (is_op(s_comma) || is_op(s_rb))) break; + if (is_op(s_lp) || is_op(s_lk) || is_op(s_lb)) depth++; + if (is_op(s_rp) || is_op(s_rk) || is_op(s_rb)) depth--; + next(); + } + return 0; +} +int ar_flat; word ar_target; +int array_scalar_count(word t) { + if (t[0] != TY_ARR) return 1; + if (t[3] < 0) return 0; + return t[3] * array_scalar_count(t[4]); +} +// a chain [row][column] selects a subarray or scalar; flat positions resume after that subobject. +int array_designator(word t) { + int index, stride; word part = t; + ar_flat = 0; + while (accept(s_lk)) { + index = cexpr(); expect(s_rk); + if (index < 0) { serr("a negative array designator"); index = 0; } + if (part[0] != TY_ARR) { serr("too many indices in array designator"); break; } + if (part[3] >= 0 && index >= part[3]) serr("array designator index is out of range"); + stride = array_scalar_count(part[4]); if (!stride) { serr("array designator needs a complete element type"); stride = 1; } + ar_flat = ar_flat + index * stride; part = part[4]; + } + expect(s_eq); ar_target = part; return 0; +} +word array_shape(word t) { + int flat = 0, count = 0, width, stride, end; word elem = t[4], row, target; + expect(s_lb); + while (tk != T_EOF && !is_op(s_rb)) { + stride = array_scalar_count(elem); if (!stride) stride = 1; + target = elem; + if (is_op(s_lk)) { array_designator(t); flat = ar_flat; target = ar_target; } + if (is_op(s_lb)) { + if (target[0] != TY_ARR) { serr("nested initializer needs an array element type"); array_skip_value(); flat++; } + else { + row = array_shape(target); + if (elem[0] == TY_ARR && elem[3] < 0) { elem = row; stride = array_scalar_count(elem); } + width = array_scalar_count(row); flat = flat + width; + } + } else { array_skip_value(); flat++; } + end = (flat + stride - 1) / stride; if (end > count) count = end; + if (t[3] >= 0 && count > t[3]) serr("too many elements in array initializer"); + if (!accept(s_comma)) break; + } + expect(s_rb); + if (t[3] >= 0) count = t[3]; + return tarr(elem, count); +} +word array_measure(word q, word t) { + word result; + src_push(1, q, 0, 0); next(); result = array_shape(t); src_pop(); + if (treadonly(t)) result = tconst(result); + return result; +} +int array_address(int base, int local, int offset) { + if (local) laddr(base + offset); else lit(base + offset); + return 0; +} +// flatten a scalar position while preserving each packed row's independent word alignment. +int array_store_scalar(word t, int flat, int base, int local, int offset) { + int c, at, stride, bytes, value, address, shift, mask; word elem = t; + while (elem[0] == TY_ARR) { + stride = array_scalar_count(elem[4]); if (!stride) stride = 1; + at = flat / stride; flat = flat % stride; + if (elem[4][0] == TY_ARR) offset = offset + at * tsz(elem[4]); + else { flat = at; elem = elem[4]; break; } + elem = elem[4]; + } + if (isagg(elem)) { serr("aggregate array elements need nested initializer lists"); array_skip_value(); return 0; } + if (local) { + c = expr(); rval(); plainuse(c); fcoerce(elem); + if (!compat(elem, xty)) tcerr("array initializer", elem, xty); + if (tbytes(elem)) { array_address(base, 1, offset); lit(8); op(15); lit(flat * tbytes(elem)); op(O_ADD); pkst(elem); } + else { array_address(base, 1, offset + flat * tsz(elem)); op(O_ST); } + } else { + if (isfl(elem) && tk == T_FLT) { value = tv; next(); if (elem == tf32) value = rt.zf2s(value); } + else { value = cnorm(elem, cexpr()); if (isfl(elem)) { value = vf_i2f(value); if (elem == tf32) value = rt.zf2s(value); } } + bytes = tbytes(elem); + if (bytes) { + address = base + offset + (flat * bytes >> 3) + hdelta; shift = ((flat * bytes) & 7) * 8; + mask = ((1 << (bytes * 8)) - 1) << shift; *address = (*address & ~mask) | ((value << shift) & mask); + } else *(base + offset + flat * tsz(elem) + hdelta) = value; + } + return 0; +} +// globals use constant expressions, locals use ordinary expressions. brace elision walks scalar elements. +int array_suboffset(word t, int flat, word target) { + int offset = 0, stride, at; + while (t != target && t[0] == TY_ARR) { + stride = array_scalar_count(t[4]); if (!stride) stride = 1; + at = flat / stride; flat = flat % stride; offset = offset + at * tsz(t[4]); t = t[4]; + } + return offset; +} +int array_values(word t, int base, int local, int offset) { + int flat = 0, total = array_scalar_count(t), stride = array_scalar_count(t[4]), dest, width; + word target; + expect(s_lb); + while (tk != T_EOF && !is_op(s_rb)) { + target = t[4]; + if (is_op(s_lk)) { array_designator(t); flat = ar_flat; target = ar_target; } + if (flat < 0 || flat >= total) { serr("array initializer index is out of range"); array_skip_value(); } + else if (target[0] == TY_ARR && is_op(s_lb)) { + // the selected subarray starts at its scalar position, converted to an in-place word offset. + dest = array_suboffset(t, flat, target); + array_values(target, base, local, offset + dest); flat = flat + array_scalar_count(target); + } else { array_store_scalar(t, flat, base, local, offset); flat++; } + if (!accept(s_comma)) break; + } + expect(s_rb); return 0; +} +int array_initialize(word q, word t, int base, int local) { + if (local && tsz(t)) { array_address(base, 1, 0); lit(0); lit(tsz(t)); callname(intern("fill_w")); op(O_DROP); } + src_push(1, q, 0, 0); next(); array_values(t, base, local, 0); src_pop(); + return 0; +} +int array_literal_tokens(word q, word t) { + if (!infn) { + int base, cells; t = array_measure(q, t); cells = tsz(t); reserve_code(cells); base = here; + while (cells) { emit(0); cells--; } array_initialize(q, t, base, 0); rt.free(q); + if_value = 0; + xty = t; lv = 0; lvk = LV_NONE; xbor = -1; xaf = 0; xconst = 1; xcval = base; + return C_PLAIN; + } + int i = newloc(intern("<array literal>")), place = tpack(); + t = array_measure(q, t); ltype[i] = t; larr[i] = 1; fslots = fslots + tsz(t); + array_initialize(q, t, lslot[i], 1); + lafact[i] = af_new(t[3], 4, place); laaddr[i] = lafact[i]; if (lafact[i]) lafact[i][5] = i; + laddr(lslot[i]); xty = t; lv = 0; lvk = LV_NONE; xbor = i; xaf = lafact[i]; + xzero = 0; xfloat = 0; xstr = 0; xconst = 0; xvk = 0; rt.free(q); + return C_PLAIN; +} +// a typed growable initializer owns a heap handle, filled from temporary in-place values. +int array_dynamic_literal(word t) { + word q = array_tokens(), shape = array_measure(q, tarr(t[4], -1)); + int slot = fslots, cells = tsz(shape), place = tpack(); fslots = fslots + cells; + if (!infn) serr("a growable array initializer requires function scope"); + array_initialize(q, shape, slot, 1); rt.free(q); + if (tbytes(t[4])) lit(0 - tbytes(t[4])); else lit(tsz(t[4])); + lit(shape[3]); lit(t); callname(s_dnew); vf_effect(); + lit(0); op(O_PICK); lit(2); op(O_ADD); op(O_LD); laddr(slot); lit(cells); callname(intern("copy_w")); op(O_DROP); + xty = t; lv = 0; lvk = LV_NONE; xbor = -1; xaf = af_new(shape[3], 3, place); + xzero = 0; xfloat = 0; xstr = 0; xconst = 0; xvk = 0; xstamp = 1; newsz = -1; newleaf = 0; + return C_FRESH; +} +int array_literal(word t) { word q = array_tokens(); return array_literal_tokens(q, t); } +// statement-bearing braces keep block-expression semantics; expression-only braces are array literals. +int brace_primary() { + word q = array_tokens(); int k = 0, depth = 0, block = 0, c; + while (k < q[0]) { + if (q[1 + k * 3] == T_OP) { + if (q[2 + k * 3] == s_lb) depth++; + if (q[2 + k * 3] == s_rb) depth--; + if (q[2 + k * 3] == s_semi && depth == 1) block = 1; + } + k++; + } + if (!block) return array_literal_tokens(q, tarr(tint, -1)); + src_push(1, q, 0, 0); next(); c = blockexpr(); src_pop(); rt.free(q); return c; +} + +// ---- the expression grammar ---- + +// precedence, loosest to tightest: +// expr (assignment, ?:) -> bin (binary operators by precedence) -> +// unary (prefix operators, casts, recasts) -> postfix (index, field, call, +// ++ --, as) -> primary (literals, names, parentheses, blocks, closures) +// each level leaves its value on the stack and sets the x* globals: xty is the +// type, xfloat / xstr say the value is a float or byte string, xzero and +// xconst mark a literal 0 or constant (xcval holds it), xbor names the local a +// pointer value was borrowed from, and lv / lvk / lvi say the stack holds an +// unloaded address and what it is the address of. +// +// primary: one operand. a number, string, parenthesized expression, block +// expression, closure, or a name. a name is looked up in this order: a local +// of the current function, a variable captured by an enclosing closure, then +// the symbol table (keyword, constant, global, enhancement, function). a local +// the analyzer tracks returns C_VAR + its index so the caller can tell which +// variable the value is. +int primary() { + effect_value = 0; xprojection = 0; if_value = 0; + int n, i, s, c, k, npos, sv; word[8] bs0; int ft, mn, ms; + lv = 0; lvk = LV_NONE; xzero = 0; xbor = -1; + xconst = 0; xfloat = 0; xstr = 0; xlenloc = -1; xstamp = 0; xaf = 0; xvk = 0; + if (tk == T_FLT) { op(OP_FLIT); emit(tv); xvk = 2; xvv = tv; xfloat = 1; xty = tfloat; next(); return C_PLAIN; } + if (tk == T_NUM) { + if (cmode && tv >= 100 && tglue != 3 && !nomagic && (tv & (tv - 1)) && (tv & (tv + 1))) { + magic(tv); + } + lit(tv); xvk = 1; xvv = tv; xzero = tv == 0; xconst = 1; xcval = tv; xty = tint; next(); return C_PLAIN; + } + if (tk == T_BSTR) { xvk = 4; xvv = vf_string(tv); bstrlit(tv); xstr = 1; xty = tbstr; next(); return C_PLAIN; } + if (tk == T_STR) { xvk = 3; xvv = vf_string(tv); strlit(tv); next(); xty = tptr(tchar); return C_PLAIN; } + if (tk == T_OP) { + if (rt.eq_w(tv, "$")) { next(); return quote_tokens(); } + if (tv == s_lp) { next(); c = expr(); expect(s_rp); return c; } + if (tv == s_lb) return brace_primary(); + diefound("expected an expression"); lit(0); xty = tword; return C_PLAIN; + } + if (tk != T_ID) { diefound("expected an expression"); lit(0); xty = tword; return C_PLAIN; } + if (tv == s_fn) { peek(); if (pk_tk == T_OP && (pk_tv == s_lp || pk_tv == s_colon)) return closure(); } + n = tv; callpos = tpack(); + i = findloc(n); + if (i < 0 && nclo) { + k = clocap(n); + if (k >= 0) { + rs_note(RS_LOCAL, 0 - 1); + next(); + i = nclo - 1; + vaddr(clo_env[i]); op(O_LD); lit(k); op(O_ADD); op(O_LD); + i = clo_loc[i * capture_width + k]; + xty = ltype[i]; xfloat = getflt(i); if (isfl(xty)) xfloat = 1; xstr = getstr(i); + lv = 1; lvk = LV_OTHER; + return C_PLAIN; + } + } + if (i >= 0) { + if (if_local) if_value = if_local[i]; + lreadpos[i] = tpack(); + luse(i); effect_note_mask(effect_mask(C_VAR + i), EF_READ); + rs_note(lrskind[i], lrsoff[i]); os_at(toff, i); + xaf = lafact[i]; af_record(toff, xaf); + if (cmode) { xvk = lvkind[i]; xvv = lvvalue[i]; vf_at(rsn - 1, xvk, xvv); } + next(); + if (!fchk && tracked(i)) { + serrn(n, "is tracked by the analyzer and can't be used in unchecked code"); + } + vaddr(i); + xty = ltype[i]; + if (larr[i]) return C_PLAIN; + xfloat = getflt(i); + if (isfl(ltype[i])) xfloat = 1; + xstr = getstr(i); if (xty == tbstr) xstr = 1; + lv = 1; lvk = LV_LOCAL; lvi = i; + if (isalias(i)) { touch(i); xbor = lroot(i); return C_VAR + i; } + if (tracked(i) || lrskind[i] == RS_PARAM || (local_origin_mask && local_origin_mask[i])) return C_VAR + i; + return C_PLAIN; + } + s = source_lookup(n); + if (mstruct && !ginst) { ms = lookup(methname(mstruct, n)); if (ms >= 0 && skind[ms] == K_CONST) s = ms; } + if (s < 0 && rt.eq_w(n, "new")) s = source_lookup(s_new); + if (s >= 0) rs_put(toff, RS_SYM, rs_sym(s), s); + if (s >= 0 && skind[s] == K_KW) { + if (!(sflag[s] & F_EXPR)) dien(n, "can't be used inside an expression"); + return kwcall(s, 1); + } + if (s >= 0 && skind[s] == K_TYPE) { + next(); + if (is_op(s_dot)) { // Struct.name(args): a static function of the struct + c = toff + toklen; + next(); + if (probe_off >= 0 && cmode && !dry && !ginst && incdepth == 0 && probe_off >= c && ((tk == T_ID && probe_off <= toff + toklen) || (tk != T_ID && probe_off <= toff))) { probe_static = n; probe_has = 3; chkabort = 1; } + k = toff; sv = tpack(); mn = name(); + ms = lookup(methname(sname[s], mn)); + if (ms >= 0) rs_put(k, RS_SYM, rs_sym(ms), ms); + if (ms >= 0 && skind[ms] == K_CONST) { // Enum.MEMBER + xvk = 1; xvv = sval[ms]; + lit(sval[ms]); + xty = tint; + if (stype[ms]) xty = stype[ms]; + xzero = sval[ms] == 0; + xconst = 1; + xcval = sval[ms]; + return C_PLAIN; + } + if (ms >= 0 && skind[ms] == K_FN && (sflag[ms] & F_STATIC)) { + if (!is_op(s_lp)) die("a static function is called with ( after its name"); + return call(ms); + } + if (!panic && !dry) { + c = tpack(); tunpack(sv); // the error sits on the member name, where the quick fix replaces it + errat(); rt.put_s(mn); rt.put_s(": is not a static function of this struct"); suggest_member(sname[s], 0, mn, 1); errend(); + tunpack(c); + } + lit(0); + xty = tword; + return C_PLAIN; + } + dien(n, "is a type, not a value"); + lit(0); + xty = tword; + return C_PLAIN; + } + npos = tpack(); + next(); + xty = tword; + if (s >= 0 && skind[s] == K_CONST) { + xvk = 1; xvv = sval[s]; + lit(sval[s]); + xty = tint; + if (stype[s]) xty = stype[s]; + xzero = sval[s] == 0; + xconst = 1; + xcval = sval[s]; + return C_PLAIN; + } + if (s >= 0 && skind[s] == K_GVAR) { + if (cmode) { vf_symbols(); xvk = svkind[s]; xvv = svvalue[s]; vf_at(rsn - 1, xvk, xvv); } + lit(sval[s]); + if (stype[s]) xty = stype[s]; + if (isfl(xty)) xfloat = 1; if (xty == tbstr) xstr = 1; + lv = 1; + lvk = LV_GLOBAL; lvi = s; + return C_PLAIN; + } + if (s >= 0 && skind[s] == K_GARR) { lit(sval[s]); if (stype[s]) xty = stype[s]; return C_PLAIN; } + if (s >= 0 && skind[s] == K_ENH) return enhcall(s); + if (s < 0 && is_op(s_dot)) { + peekat(0); + if (pk_tk == T_ID && lib_file(n)) { + // lib.name before include lib, for a library read from a file: the lexer only joins names of loaded modules + sv = tpack(); tunpack(npos); + errat(); rt.put_s("module access requires `include "); rt.put_s(n); rt.put_s(";`"); errend(); + tunpack(sv); + word link = chk_resize(0, 0, 2); link[0] = missing_imports; link[1] = n; missing_imports = link; + next(); n = methname(n, tv); next(); + } + // Text.itoa with no std: a library's static function, reported as one missing name + else if (pk_tk == T_ID && export_owner(methname(n, pk_tv))) { next(); n = methname(n, tv); next(); } + } + if (s < 0) s = fwdfn_at(n, callpos); + if (is_op(s_lt) && (sflag[s] & F_GEN)) { // f<int, char*>(...) + next(); ngbind = 0; + while (1) { + if (ngbind >= 8) die("too many type arguments"); + gbind[ngbind] = ptype(); + ngbind++; + if (!accept(s_comma)) break; + } + closeangle(); + if (!is_op(s_lp)) { // f<int> alone is that instance, as a function value + rt.fill_w(bs0, 0, 8); rt.copy_w(bs0, gbind, ngbind); ngbind = 0; + ft = stype[s]; + s = ginstance(s, bs0); + fnref(s); + xty = tword; + if (ft) xty = subst(ft, bs0); + return C_PLAIN; + } + } + if (is_op(s_lp)) return call(s); + fnref(s); + if (stype[s]) xty = stype[s]; + return C_PLAIN; +} +// ++ and -- on the address at the top of the stack. post says the old value +// is the result; dlt is +1 or -1. it also updates the loop-bound facts: a +// decrement or a store of an unknown value ends any proof about the variable. +int incdec(int post, int dlt) { + require_mutable(xty); + int t; + if (cmode && !dry) { if (lvk == LV_LOCAL) lvkind[lvi] = 0; else vf_effect(); } xvk = 0; + if (lv && lvk == LV_LOCAL) lafact[lvi] = 0; xaf = 0; + if (!lv) die("++ and -- need something to assign to"); + if (lvk == LV_LOCAL && fchk && tracked(lvi) && !isalias(lvi)) { + cerrn(lvi, "owns an object and can't be incremented"); + } + tcneed(isnum(xty) || xty[0] == TY_PTR || xty == tword, "++ and -- need a number or a pointer", xty); + if (lvk == LV_LOCAL) { + ltag[lvi] = ltag[lvi] | LASG; + setflt(lvi, 0); setstr(lvi, 0); + if (forstep && lvi == ft_iv && dlt == 1) fs_ok = 1; + if (dlt < 0) putfact(lvi, -1, -1); else putfact(lvi, getlo(lvi), -1); + bpoison(lvi); + } + lv = 0; lvk = LV_NONE; xzero = 0; xfloat = 0; xstr = 0; + t = xty; + lit(0); op(O_PICK); + if (ispk(t)) pkld(t); else { dbg_addr(); op(O_LD); } + // a sized integer wraps: the new value is cut to size before it is stored + if (post) { + lit(0); op(O_PICK); lit(dlt); op(O_ADD); + if (tbytes(t)) tnorm(t); + lit(2); op(O_PICK); + if (ispk(t)) pkst(t); else op(O_ST); + } else { + lit(dlt); op(O_ADD); + if (tbytes(t)) tnorm(t); + lit(0); op(O_PICK); lit(2); op(O_PICK); + if (ispk(t)) pkst(t); else op(O_ST); + } + op(O_SWAP); op(O_DROP); + return C_PLAIN; +} +// p.x and p->x: a field of a struct, or of the struct a pointer points at +// the field access after a dot or arrow: c is the class of the left side. a +// field of a typed struct is its offset added to the address. an untyped word +// is searched for the one struct that has that field name (fieldscan). if the +// name is followed by ( it is a method call instead. a pointer-valued field +// of a tracked variable is a borrow of that variable (xbor). +word field(int c) { + int readonly = treadonly(xty); + if_value = 0; + int n, fieldoff, fieldpos, sv; word st; + word borrowed = xaf, base_mask = effect_mask(c); af_use(xaf, tpack(), c); xaf = 0; xvk = 0; int i, b = xbor, k = 0, scanned = 0, ms, de, pmatch = 0; + de = toff + toklen; + next(); + // is the place asked about on this member name (or just after the dot, with none typed) + if (probe_off >= 0 && cmode && !dry && !ginst && incdepth == 0 && probe_off >= de) { + if (tk == T_ID) { if (probe_off <= toff + toklen) pmatch = 1; } + else if (probe_off <= toff) pmatch = 1; + } + fieldpos = tpack(); n = name(); fieldoff = nameoff; + st = xty; + if (st[0] == TY_ARR || st[0] == TY_DYN) { + if (pmatch) probe_member(st); + if (is_op(s_lp) && array_method(n)) { xaf = borrowed; return array_member(c, n); } + if (is_op(s_lp)) { + ms = receiver_method(st, n); + if (ms == -2) serrn(n, "matches more than one receiver method"); + if (ms >= 0) { + rval(); plainuse(c); + if (c >= C_VAR) b = lroot(c - C_VAR); + if (st[0] == TY_ARR && stype[ms][7][0] == TY_DYN) { + int slot = fslots; fslots = fslots + 4; + laddr(slot + 2); op(O_ST); + lit(st[3]); laddr(slot); op(O_ST); lit(st[3]); laddr(slot + 1); op(O_ST); + if (tbytes(st[4])) lit(0 - tbytes(st[4])); else lit(tsz(st[4])); + laddr(slot + 3); op(O_ST); laddr(slot); st = tdyn(st[4], 1); + } + return methcall(ms, st, borrowed, b, base_mask); + } + } + } + rval(); + if (c == C_FRESH) cerr("use a field of a new object only after binding it to a name"); + else if (c >= C_VAR) { touch(c - C_VAR); b = c - C_VAR; } + st = xty; + if (st[0] == TY_PTR) st = st[4]; + if (pmatch) probe_member(st); + if (is_op(s_lp)) { // e.name(...): a method, unless the struct has a field of that name + ms = 0 - 1; + if (st[0] == TY_STRUCT) { if (tfindfield(st, n) < 0) ms = methof(st, n); } + else if (st == tword || isnum(st)) { if (fieldscan(n) == 0) ms = methscan(n); } + if (ms == 0 - 2) { serrn(n, "is a method of more than one struct"); ms = 0 - 1; } + if (ms >= 0) { + return methcall(ms, st, borrowed, b, base_mask); + } + } + if (st[0] != TY_STRUCT && (st == tword || isnum(st))) { + k = fieldscan(n); + if (k == 1) { st = fldst; scanned = 1; } + else if (k > 1) { + if (!panic && !dry) { + errat(); + rt.put_c('.'); + rt.put_s(n); + rt.put_s(" is a field of more than one struct"); + errend(); + } + xty = tword; lv = 1; lvk = LV_OTHER; xbor = -1; return C_PLAIN; + } + } + if (st[0] != TY_STRUCT) { + if (!panic && !dry) { + errat(); + rt.put_c('.'); + rt.put_s(n); + rt.put_s(": that's a "); + tprint(xty); + rt.put_s(", not a struct or a pointer to one"); + errend(); + } + xty = tword; lv = 1; lvk = LV_OTHER; xbor = -1; return C_PLAIN; + } + if (scanned) i = fldi; else i = tfindfield(st, n); + if (i < 0) { + if (!panic && !dry) { + sv = tpack(); tunpack(fieldpos); // the error sits on the field name, where the quick fix replaces it + errat(); tprint(st); rt.put_s(" has no field "); rt.put_s(n); suggest_member(st[2], st, n, 0); errend(); + tunpack(sv); + } + xty = tword; lv = 1; lvk = LV_OTHER; xbor = -1; return C_PLAIN; + } + rs_put(fieldoff, RS_FIELD, field_decl(st, n), st[4][i * 3 + 1]); + k = st[4][i * 3 + 2]; + xty = st[4][i * 3 + 1]; + if (readonly) xty = tconst(xty); + if (k < 0) { // a packed field: the lvalue is a byte address, of the packed twin + lit(8); op(15); + k = 0 - k - 1; + if (k) { lit(k); op(O_ADD); } + xty = tpk(xty); + } else if (k) { lit(k); op(O_ADD); } + if (isref(xty)) { if (b >= 0) xbor = lroot(b); } + else xbor = -1; + if (borrowed && borrowed[2] == 5) xaf = borrowed; + effect_value = base_mask; + xprojection = af_project(borrowed, k, tsz(xty), tpack(), 0); + lv = 1; xzero = 0; + if (b >= 0) { lvk = LV_ELEM; lvi = b; } else lvk = LV_OTHER; + return C_PLAIN; +} +// array indexing, address arithmetic. mulsz scales an index by the element +// size. the addr_ functions build the address of element ix of a growable +// array in the different situations the bounds-check removal produces: the +// stack holds handle and index (addr_stk), the items pointer is cached in a +// hidden o3 variable (addr_cached), or only the handle is available +// (addr_hdr). range_jz is the inline bounds check, one "outside" enhancement +// and a branch to the oob report. +int mulsz(int et) { + if (tsz(et) != 1) { lit(tsz(et)); op(15); } + return 0; +} +// address of an element of a stack array, from the index on the stack. +int addr_stk(int et) { + op(O_SWAP); + lit(2); + op(O_ADD); + op(O_LD); + op(O_SWAP); + mulsz(et); + op(O_ADD); + return 0; +} +// address of an element of an array whose base is cached in a register. +int addr_cached(int ci, int ix, int et) { + vaddr(ci); + op(O_LD); + vaddr(ix); + op(O_LD); + mulsz(et); + op(O_ADD); + return 0; +} +// address of an element of a growable array, reading its header. +int addr_hdr(int al, int ix, int et) { + vaddr(al); op(O_LD); lit(2); op(O_ADD); op(O_LD); + vaddr(ix); op(O_LD); mulsz(et); op(O_ADD); + return 0; +} +// reload the index and the length on each side of the branch, so neither is a stack temporary +int range_jz(int ix, int src, int fld) { + int k, s; + vaddr(ix); op(O_LD); vaddr(src); op(O_LD); if (fld) op(O_LD); + s = lookup(s_outside); op(XOP0 + sval[s]); + k = hole(); op(O_JZ); + vaddr(ix); op(O_LD); vaddr(src); op(O_LD); if (fld) op(O_LD); + callname(s_oob); + setlit(k, here); + return 0; +} +// suffixes after a primary, in a loop: e as T, [index], .field, ->field, +// (call through a value), ++ and --. the index case is the long one. a byte +// string takes an index or a slice. a growable array is checked: through dat() +// normally, but inline when the compiler can prove or cheaply test the bound +// (o3 code with the cache variables, or an index the loop test already +// compared with the length). a plain pointer or fixed array is just base + +// index * size, and a constant index against a known length is checked at +// compile time. +int postfix() { return postfix_tail(primary()); } +int postfix_tail(int c) { + int b, n, t, et; word ft; word[8] bs; int k, ci, was, al, ix, enh, cz, cj, place, offset, temporary, call_owner, readonly; word storage, storage_mask, call_origins; + while (1) { + if (tk == T_ID && tv == s_as) { // e as T: same bits, another type + next(); t = ptype(); rval(); if (!xaf || xaf[2] != 5) xaf = 0; vf_coerce(t, 0); + if (tbytes(t) && !xfloat && !samesz(t, xty)) { tnorm(t); xconst = 0; } // x as u8 cuts the value to size + if_value = 0; + xty = t; xfloat = 0; lvk = LV_NONE; + if (isfl(t)) xfloat = 1; + continue; + } + if (tk != T_OP) return c; + if (tv == s_lk) { + readonly = treadonly(xty); + xvk = 0; + storage = xaf; storage_mask = effect_mask(c); offset = -1; n = -1; place = tpack(); af_use(storage, place, c); + was = xstr; + ci = 0; + if (lv && lvk == LV_LOCAL) lptr[lvi] = 1; + if (o3on && lv && lvk == LV_LOCAL && o3cache[lvi]) ci = o3cache[lvi]; + next(); + rval(); + if (was) { + c = expr(); rval(); plainuse(c); + if (accept(s_colon)) { + c = expr(); rval(); plainuse(c); + expect(s_rk); + callname(s_slice_s); xvk = 0; + lv = 0; lvk = LV_NONE; xty = tbstr; xfloat = 0; xstr = 1; xzero = 0; xbor = -1; + c = C_PLAIN; + continue; + } + expect(s_rk); + callname(s_at_s); xvk = 0; + lv = 0; lvk = LV_NONE; xty = tint; xfloat = 0; xstr = 0; xzero = 0; xbor = -1; + c = C_PLAIN; + continue; + } + b = -1; + if (c == C_FRESH) cerr("index a new object only after binding it to a name"); + else if (c >= C_VAR) { touch(c - C_VAR); b = c - C_VAR; } + if (xty[0] == TY_DYN && tbytes(xty[4])) { // packed elements: the runtime checks and gives the byte address + et = xty[4]; + c = expr(); rval(); plainuse(c); + if (xconst) offset = xcval; af_index(storage, place); + tcneed(isnum(xty) || xty == tword, "an index has to be a number", xty); + expect(s_rk); + callname(intern("datb")); + et = tpk(et); + } else if (xty[0] == TY_DYN) { // a growable array: bounds-checked, through the runtime + et = xty[4]; + al = -1; + if (lvk == LV_LOCAL) al = lvi; + if (ci && !mdbg && lookup(s_outside) >= 0) op(O_DROP); + c = expr(); + ix = -1; + if (lv && lvk == LV_LOCAL) ix = lvi; + rval(); plainuse(c); + if (xconst) offset = xcval; af_index(storage, place); + tcneed(isnum(xty) || xty == tword, "an index has to be a number", xty); + expect(s_rk); + k = lookup(s_outside); + enh = k >= 0 && skind[k] == K_ENH; + if (!mdbg && ix >= 0 && al >= 0 && proven(al, ix)) { // the loop test already compared this index with the length + if (ci) { vaddr(ci); op(O_LD); op(O_SWAP); mulsz(et); op(O_ADD); } + else addr_stk(et); + } else if (ci && !mdbg && enh && ix >= 0) { + op(O_DROP); + range_jz(ix, ci + 1, 0); + addr_cached(ci, ix, et); + } else if (ci && !mdbg && enh) { + lit(0); op(O_PICK); vaddr(ci + 1); op(O_LD); op(XOP0 + sval[k]); + k = hole(); op(O_JZ); + vaddr(ci + 1); op(O_LD); callname(s_oob); + setlit(k, here); + vaddr(ci); op(O_LD); op(O_SWAP); + mulsz(et); op(O_ADD); + } else if (!mdbg && o3on && enh && ix >= 0 && al >= 0) { + op(O_DROP); op(O_DROP); + range_jz(ix, al, 1); + addr_hdr(al, ix, et); + } else if (!mdbg && o3on && enh && ix >= 0) { + op(O_DROP); + vaddr(ix); op(O_LD); + lit(1); op(O_PICK); op(O_LD); op(XOP0 + sval[k]); + k = hole(); op(O_JZ); + vaddr(ix); op(O_LD); lit(1); op(O_PICK); op(O_LD); callname(s_oob); + setlit(k, here); + lit(2); op(O_ADD); op(O_LD); vaddr(ix); op(O_LD); + mulsz(et); op(O_ADD); + } else if (!mdbg && o3on && enh) { + lit(0); op(O_PICK); lit(2); op(O_PICK); op(O_LD); op(XOP0 + sval[k]); + k = hole(); op(O_JZ); + lit(1); op(O_PICK); op(O_LD); callname(s_oob); + setlit(k, here); + op(O_SWAP); lit(2); op(O_ADD); op(O_LD); op(O_SWAP); + mulsz(et); op(O_ADD); + } else callname(s_dat); + } else { + tcneed(isptr(xty) || xty == tword, "only pointers and arrays can be indexed", xty); + et = telem(xty); + n = 0 - 1; if (xty[0] == TY_ARR && xty[3] > 0) n = xty[3]; + xconst = 0; + c = expr(); rval(); plainuse(c); + if (xconst) offset = xcval; if (n < 0) af_index(storage, place); + if (n >= 0 && xconst && (xcval < 0 || xcval >= n)) { + serr("the index is out of range for this array"); + } + tcneed(isnum(xty) || xty == tword, "an index has to be a number", xty); + expect(s_rk); + if (tbytes(et)) { // packed: byte address = base * 8 + index * bytes + if (tbytes(et) != 1) { lit(tbytes(et)); op(15); } + op(O_SWAP); lit(8); op(15); op(O_ADD); + et = tpk(et); + } else { + if (tsz(et) != 1) { lit(tsz(et)); op(15); } + op(O_ADD); + } + } + xprojection = af_project(storage, offset, 1, place, 1); + if (xprojection && n >= 0 && offset >= 0) { xprojection[0] = n - offset; xprojection[13] = 0 - offset; xprojection[14] = 1; } + xaf = 0; if (storage && storage[2] == 5) xaf = storage; + effect_value = 0; if (isptr(et) || et == tword) effect_value = storage_mask; + if_value = 0; + if (readonly) et = tconst(et); + lv = 1; xty = et; xzero = 0; xconst = 0; xvk = 0; xlenloc = -1; xstamp = 0; + if (isref(xty)) { if (b >= 0) xbor = lroot(b); } + else xbor = -1; + if (b >= 0) { lvk = LV_ELEM; lvi = b; } else lvk = LV_OTHER; + c = C_PLAIN; + xfloat = 0; if (isfl(xty)) xfloat = 1; xstr = 0; + continue; + } + if (tv == s_dot || tv == s_arrow) { + xconst = 0; + c = field(c); + xfloat = 0; + if (isfl(xty)) xfloat = 1; + xstr = xty == tbstr; + continue; + } + if (tv == s_lp) { // call through a value + effect_note_mask(effect_mask(c), EF_READ); + rval(); plainuse(c); + ft = 0; + if (xty[0] == TY_FN) ft = xty; + else tcneed(xty == tword, "only functions can be called", xty); + rt.fill_w(bs, 0, 8); + ifcall = -1; effect_argument_base = 0; + int call_tags = 0; if (ft) call_tags = ft[5] >> 6; + n = args(call_tags, ft, bs, o3on); temporary = argtemporary; call_owner = argowner; call_origins = argorigin_mask; + // raw code can live in a heap segment; a closure has an environment block + lit(n); op(O_PICK); callname(intern("callable_code")); + cz = hole(); op(O_JZ); + lit(n); op(O_PICK); op(O_CALL); + cj = hole(); op(O_JMP); + setlit(cz, here); + lit(n); op(O_PICK); lit(3); op(O_ADD); // env: the captures start 3 words into the block + lit(n + 1); op(O_PICK); op(O_LD); // the body's entry, word 0 of the block + op(O_CALL); + setlit(cj, here); + op(O_SWAP); op(O_DROP); + if_value = 0; + lv = 0; lvk = LV_NONE; c = C_PLAIN; xzero = 0; xbor = -1; xconst = 0; + xty = tword; + if (ft) xty = subst(ft[4], bs); + if (ft && (ft[5] & 1)) c = C_FRESH; + else if (isptr(xty) || xty == tword) { effect_value = call_origins; if (isptr(xty)) xbor = call_owner; if (temporary) xaf = af_new(-1, 5, place); } + xfloat = 0; if (isfl(xty)) xfloat = 1; xstr = 0; + continue; + } + if (tv == s_inc) { next(); c = incdec(1, 1); continue; } + if (tv == s_dec) { next(); c = incdec(1, -1); continue; } + return c; + } + return c; +} +// prefix operators: the u*x / d*x / m*x recasts (the star has to touch the +// tag), casts like (int)x and (node*)x, dereference, address-of, prefix ++ and +// --, and any unary operator a program added (- ! ~ built in). +int unary() { + int c, un, t, place, constant, value; word storage, dependency; + if (tk == T_ID && (tv == s_u || tv == s_m || tv == s_d)) { // u*x, d*x, m*x: recast only when * is glued + peek(); + if (pk_tk == T_OP && pk_tv == s_star && pq_gl[0]) { + t = S_M; if (tv == s_u) t = S_U; if (tv == s_d) t = S_D; + next(); expect(s_star); + c = unary(); place = -1; if (lvk == LV_LOCAL) place = lvi; rval(); + lvk = LV_NONE; + if (c < C_VAR && c != C_FRESH && place >= 0 && fchk) { cerrn(place, "isn't an owned object, so there is no ownership to recast"); return c; } + c = recast(c, t); + if (t == S_M && c >= C_VAR && lstate[c - C_VAR] == S_M && lown[c - C_VAR]) xlend = c - C_VAR; + return c; + } + } + if (tk == T_OP && tv == s_lp && caststart()) { // (int)x, (node*)x: same bits, the type changes + next(); + t = ptype(); + expect(s_rp); + if (t[0] == TY_ARR && is_op(s_lb)) return postfix_tail(array_literal(t)); + if (t[0] == TY_DYN && is_op(s_lb)) return postfix_tail(array_dynamic_literal(t)); + c = unary(); dependency = effect_mask(c); rval(); plainuse(c); effect_value = dependency; if (!xaf || xaf[2] != 5) xaf = 0; vf_coerce(t, 1); + if (isfl(t)) { if (!xfloat) { op(OP_I2F); xfloat = 1; } if (t == tf32) { xenh("zf2s"); xenh("zs2f"); } } + else if (xfloat && isnum(t)) { op(OP_F2I); xfloat = 0; } + else xfloat = 0; + if (tbytes(t) && !samesz(t, xty)) { tnorm(t); xconst = 0; } // (u8)x cuts the value to size + if_value = 0; + xty = t; lv = 0; lvk = LV_NONE; xzero = 0; xbor = -1; + return C_PLAIN; + } + if (tk == T_OP) { + if (tv == s_star) { + place = tpack(); next(); c = unary(); dependency = effect_mask(c); + storage = xaf; af_use(xaf, place, c); xaf = 0; if (storage && storage[2] == 5) xaf = storage; xvk = 0; + if (lv && lvk == LV_LOCAL) lptr[lvi] = 1; + rval(); plainuse(c); + tcneed(xty[0] == TY_PTR || xty == tword, "only pointers can be dereferenced", xty); + if_value = 0; + xty = telem(xty); effect_value = 0; if (isptr(xty) || xty == tword) effect_value = dependency; + if (tbytes(xty)) { lit(8); op(15); xty = tpk(xty); } // the first packed element: a byte address + lv = 1; + lvk = LV_OTHER; + xzero = 0; + xbor = -1; + xfloat = 0; + if (isfl(xty)) xfloat = 1; + xstr = 0; + return C_PLAIN; + } + if (tv == s_amp) { + place = tpack(); next(); c = unary(); + storage = xprojection; if (xaf && xaf[2] == 5) storage = xaf; + if (lv && lvk == LV_LOCAL) { + if (cmode && !dry) { lvescaped[lvi] = 1; lvkind[lvi] = 0; } + storage = laaddr[lvi]; + if (!storage) { int storage_kind = 1; if (local_cell[lvi]) storage_kind = 2; storage = af_new(1, storage_kind, place); laaddr[lvi] = storage; if (storage) storage[5] = lvi; } + af_forget(); + } + else if (isagg(xty)) storage = 0; + if (!lv && !isagg(xty)) die("& needs something addressable"); + if (lv && ispk(xty)) serr("an element of a sized-integer array has no address; it is a byte inside a word"); + if (lv && lvk == LV_LOCAL && (ltag[lvi] & 16)) serr("an o3 variable has no address"); + if (lv && lvk == LV_LOCAL) ltag[lvi] = ltag[lvi] | LREAD | LASG; + if (c >= C_VAR) touch(c - C_VAR); + if (lvk == LV_ELEM && lvi >= 0) xbor = lroot(lvi); + lv = 0; lvk = LV_NONE; xzero = 0; xfloat = 0; xstr = 0; + if_value = 0; + xty = tptr(xty); xaf = storage; xvk = 0; + return C_PLAIN; + } + if (tv == s_inc) { next(); unary(); return incdec(0, 1); } + if (tv == s_dec) { next(); unary(); return incdec(0, -1); } + un = unfind(tv); + if (un >= 0) { + next(); c = unary(); rval(); plainuse(c); + constant = xconst && !xfloat && uname[un] == s_minus; value = xcval; xaf = 0; + if (uname[un] == s_minus && xvk == 1) xvv = 0 - xvv; + else if (uname[un] == s_minus && xvk == 2) xvv = vf_negf(xvv); + else xvk = 0; + if (un <= 1) ftest(); + if (uname[un] == s_minus && xfloat) { + op(OP_FLIT); + emit(0); + op(O_SWAP); + op(OP_FSUB); + xty = tfloat; + } + else { + emitseq(useq[un], ulen[un]); xfloat = 0; + if (un <= 1) xty = tbool; + else if (xty != tword && !isfl(xty)) { tcneed(isnum(xty), "this operator needs a number", xty); xty = tint; } + } + if (un <= 1) if_value = 0; + xstr = 0; + xconst = constant; if (constant) xcval = 0 - value; xlenloc = -1; xstamp = 0; + lv = 0; lvk = LV_NONE; xzero = 0; xbor = -1; + return C_PLAIN; + } + } + return postfix(); +} +// binary operators by precedence climbing: parse an operand, then while the +// next token is a binary operator of at least minp, parse its right side at one +// level tighter and emit its code sequence from the operator table. && and || +// short-circuit with jumps; floats get their own opcodes; a * or / by a +// constant that is a power of two gets a shift hint in check mode. +int if_comparison(int name) { + return name == s_eqeq || name == s_ne || name == s_lt || name == s_gt || name == s_le || name == s_ge; +} +int bin(int minp) { + int c = unary(), b, p, a, j, lt, lb, rb, src, lf, rf, ls, rs, fresh, top = 0, il = -1, opp; word borrowed, rightborrow, left_mask, right_mask, infer_left, infer_right; + if (fortest == 1) { fortest = 2; top = 1; ft_nops = 0; } + while (tk == T_OP) { + b = binfind(tv); + if (b < 0) return c; + p = bprec[b]; + if (p < minp) return c; + lb = -1; + if (c >= C_VAR && tracked(c - C_VAR)) lb = lroot(c - C_VAR); else lb = xbor; + borrowed = xaf; left_mask = effect_mask(c); lf = xfloat; ls = xstr; fresh = 0; + il = -1; + if (top && lv && lvk == LV_LOCAL) il = lvi; + infer_left = if_value; infer_right = 0; + opp = tpack(); + next(); rval(); plainuse(c); + lt = xty; + if (isfl(lt)) lf = 1; + src = -1; + if (blen[b] == -1) { // && : 0 if the left side is 0, else the right side as 0 or 1 + af_forget(); + ftest(); + a = hole(); op(O_JZ); + c = bin(p + 1); rval(); plainuse(c); + ftest(); + emitseq(useq[1], ulen[1]); // !! normalizes + j = hole(); op(O_JMP); + setlit(a, here); lit(0); setlit(j, here); + xfloat = 0; xstr = 0; + } else if (blen[b] == -2) { // || : 1 if the left side isn't 0, else the right side as 0 or 1 + af_forget(); + ftest(); + a = hole(); op(O_JZ); + lit(1); j = hole(); op(O_JMP); + setlit(a, here); + c = bin(p + 1); rval(); plainuse(c); + ftest(); + emitseq(useq[1], ulen[1]); + setlit(j, here); + xfloat = 0; xstr = 0; + } else { + c = bin(p + 1); infer_right = if_value; rightborrow = xaf; right_mask = effect_mask(c); + rb = -1; + if (c >= C_VAR && tracked(c - C_VAR)) rb = lroot(c - C_VAR); else rb = xbor; + if (top) { ft_ir = -1; if (lv && lvk == LV_LOCAL) ft_ir = lvi; } + rval(); plainuse(c); + if (cmode && bname[b] == s_star && xconst && xcval >= 2 && !(xcval & (xcval - 1)) && !lf) { + shifthint(opp, xcval); + } + rf = xfloat; rs = xstr; + if (isfl(xty)) rf = 1; + if ((ls || rs) && (bname[b] == s_plus || bname[b] == s_eqeq || bname[b] == s_ne || bname[b] == s_lt || bname[b] == s_gt || bname[b] == s_le || bname[b] == s_ge)) { + if (!(ls && rs)) serr("a byte string needs a byte string on the other side"); + if (bname[b] == s_plus) { callname(s_cat_s); xstr = 1; xfloat = 0; } + else if (bname[b] == s_eqeq) { callname(s_eq_s); xstr = 0; xfloat = 0; } + else if (bname[b] == s_ne) { callname(s_eq_s); lit(1); op(18); xstr = 0; xfloat = 0; } + else if (bname[b] == s_lt) { callname(s_lt_s); xstr = 0; xfloat = 0; } + else if (bname[b] == s_gt) { op(O_SWAP); callname(s_lt_s); xstr = 0; xfloat = 0; } + else if (bname[b] == s_le) { callname(s_le_s); xstr = 0; xfloat = 0; } + else { op(O_SWAP); callname(s_le_s); xstr = 0; xfloat = 0; } + } else if ((lf || rf) && isfop(bname[b])) { femit(bname[b], lf, rf); xstr = 0; } + else { emitseq(bseq[b], blen[b]); xfloat = 0; xstr = 0; } + if (bname[b] == s_plus || bname[b] == s_minus) { + if ((!borrowed || borrowed[2] != 5) && rightborrow && rightborrow[2] == 5) borrowed = rightborrow; + if (lb >= 0) src = lb; else src = rb; + if (bname[b] == s_minus && lb >= 0 && rb >= 0) src = -1; // pointer difference is a distance + } + } + if (blen[b] < 0) af_forget(); + if (xstr) xty = tbstr; else xty = bintype(b, lt, xty); + if_value = 0; + if (blen[b] > 0 && !if_comparison(bname[b]) && !xstr && !lf && !rf && (xty == tint || xty == tword) && (infer_left || infer_right)) { + if_link(infer_left, infer_right); + if_value = infer_left; if (!if_value) if_value = infer_right; + if_join(if_value, xty); + } + if (src >= 0 && xty[0] != TY_PTR && xty != tword) src = -1; + xbor = src; + if (top) { + ft_nops++; + if (ft_nops > 1) ft_iv = -1; + else if (bname[b] == s_lt && blen[b] > 0 && il >= 0 && ft_ir >= 0 && !lf && !rf && !ls && !rs) { ft_iv = il; ft_bd = ft_ir; } + } + lv = 0; lvk = LV_NONE; c = C_PLAIN; xzero = 0; xconst = 0; xlenloc = -1; xstamp = 0; xaf = 0; xvk = 0; + effect_value = 0; if (isptr(xty) || xty == tword) effect_value = left_mask | right_mask; + if (fresh) c = C_FRESH; + if (borrowed && borrowed[2] == 5 && (isptr(xty) || xty == tword) && (bname[b] == s_plus || bname[b] == s_minus)) xaf = borrowed; + } + return c; +} +// ---- assignment, conditionals and blocks as expressions ---- + +int storekeep(int t) { + if (mdbg && !dry && !ispk(t)) { lit(1); op(O_PICK); callname(s_check); op(O_DROP); } + op(O_SWAP); lit(1); op(O_PICK); op(O_SWAP); + if (ispk(t)) pkst(t); else op(O_ST); + return 0; +} +// c ? a : b. both sides are analyzed from the same starting state (a snapshot, +// restored for the second side) and then joined, so a variable can't be given +// away on one side only. both sides are fresh objects, or neither is. +int ternary(int c) { + int a, b, s0, sa, livea, live0 = live, n0 = nloc, ca, cb, ta, ba, bb; word temp_a, temp_b, mask_a, mask_b; + rval(); plainuse(c); + ftest(); + next(); + a = hole(); op(O_JZ); + s0 = snap_push(); + ca = expr(); rval(); ta = xty; ba = xbor; temp_a = xaf; mask_a = effect_mask(ca); + if (ca != C_FRESH) plainuse(ca); + livea = live; sa = snap_push(); + b = hole(); op(O_JMP); + expect(s_colon); + setlit(a, here); + snap_restore(s0); live = live0; + cb = expr(); rval(); bb = xbor; temp_b = xaf; mask_b = effect_mask(cb); + if (cb != C_FRESH) plainuse(cb); + join2(sa, livea, b, live, n0, "ends the two sides of ?: in different states"); + snap_pop(s0); + if (isfl(ta) && !isfl(xty)) { op(OP_I2F); xfloat = 1; xty = tfloat; } + if (!compat(ta, xty) && !compat(xty, ta)) tcerr("the two sides of ?:", ta, xty); + if (ta != tword && !isfl(ta)) xty = ta; + else if (isfl(ta) || isfl(xty)) { xty = tfloat; xfloat = 1; } + if_value = 0; + effect_value = mask_a | mask_b; + if (ba != bb) xbor = -1; + if (temp_a && temp_a[2] == 5) xaf = temp_a; else if (temp_b && temp_b[2] == 5) xaf = temp_b; + lv = 0; lvk = LV_NONE; xzero = 0; xvk = 0; + if (ca == C_FRESH && cb == C_FRESH) return C_FRESH; + if (ca == C_FRESH || cb == C_FRESH) cerr("both sides of ?: have to be new objects, or neither"); + return C_PLAIN; +} +// the top of the expression grammar: a binary expression, then possibly ?: or +// an assignment. plain = stores the value and leaves it on the stack; the +// analyzer's assigncheck decides what the store means for ownership (a fresh +// object makes the target an owner, a u object moves up a block, and so on). +// compound assignment (+= etc.) loads the old value, applies the operator +// sequence from the table and stores the result. an o3 variable is stored with +// the value first and the address after, so no address waits on the stack +// across a branch. +int expr() { + int c = bin(1), k, i, a, rc, lt, lf, rf, fk; + if (tk != T_OP) return c; + if (tv == s_q) return ternary(c); + if (tv == s_eq) { + if (!lv) die("this can't be assigned to"); + k = lvk; i = lvi; lt = xty; require_mutable(lt); + if (isagg(lt)) serr("a whole struct or array can't be assigned; assign its fields"); + next(); + a = o3on && k == LV_LOCAL && (ltag[i] & 16) && !larr[i]; + if (a) op(O_DROP); // an o3 variable: its address comes after the value, so it never waits on the stack across a branch + xlend = -1; + rc = expr(); rval(); + if (k != LV_LOCAL) af_escape(xaf); + fcoerce(lt); + if (k == LV_LOCAL) { + ltag[i] = ltag[i] | LASG; + setflt(i, xfloat); + setstr(i, xstr); + note_store(i); af_store(i); vf_store(i); + } + if (cmode && !dry && k == LV_GLOBAL) { vf_symbols(); svkind[i] = xvk; svvalue[i] = xvv; } + else if (k != LV_LOCAL) vf_effect(); + if (!compat(lt, xty)) tcerr("assignment", lt, xty); + if (a) { lit(0); op(O_PICK); vaddr(i); op(O_ST); o3refresh(i); } + else { + if (o3on && k != LV_ELEM && hasptr(lt)) { lit(lt); callname(s_fatten); } // o3 data going somewhere normal code can see + storekeep(lt); + } + lv = 0; lvk = LV_NONE; xty = lt; xzero = 0; + return assigncheck(k, i, rc); + } + a = asgfind(tv); + if (a >= 0) { + if (!lv) die("this can't be assigned to"); + if (lvk == LV_LOCAL && fchk && tracked(lvi) && !isalias(lvi)) { + cerrn(lvi, "owns an object; compound assignment would lose it"); + } + lt = xty; require_mutable(lt); + fk = -1; lf = xfloat; + if (lvk == LV_LOCAL) fk = lvi; + if (isfl(lt)) lf = 1; + next(); + i = o3on && lvk == LV_LOCAL && (ltag[lvi] & 16) && !larr[lvi]; + if (i) { i = lvi; op(O_DROP); vaddr(i); op(O_LD); i = i + 1; } + else if (ispk(lt)) { lit(0); op(O_PICK); pkld(lt); } + else { lit(0); op(O_PICK); op(O_LD); } + lv = 0; + rc = expr(); rval(); plainuse(rc); + rf = xfloat; + if ((lf || rf) && isfop(bname[abin[a]])) femit(bname[abin[a]], lf, rf); + else { emitseq(bseq[abin[a]], blen[abin[a]]); xfloat = 0; } + fcoerce(lt); + if (fk >= 0) { + lafact[fk] = 0; xaf = 0; if (cmode && !dry) lvkind[fk] = 0; + ltag[fk] = ltag[fk] | LASG; + setflt(fk, xfloat); setstr(fk, 0); + if (forstep && fk == ft_iv && bname[abin[a]] == s_plus && xconst && xcval == 1) fs_ok = 1; + if (bname[abin[a]] == s_plus && xconst && xcval > 0) putfact(fk, getlo(fk), -1); + else putfact(fk, -1, -1); + bpoison(fk); + } + if (fk < 0) vf_effect(); xvk = 0; + k = bintype(abin[a], lt, xty); + if (!compat(lt, k)) tcerr("compound assignment", lt, k); + if (i) { lit(0); op(O_PICK); vaddr(i - 1); op(O_ST); } else storekeep(lt); + lv = 0; lvk = LV_NONE; xty = lt; xzero = 0; + return C_PLAIN; + } + return c; +} +// does the current token start a statement rather than an expression? +// a block as an expression: { stmts; tail }. it opens a scope like any block, +// so its variables are freed when it closes, and the tail is how a u object +// leaves the block with a value (handup). +word stmtstart() { + int s; + if (tk == T_OP) return tv == s_semi || tv == s_lb || tstart(); + if (tk != T_ID || findloc(tv) >= 0) return 0; + if (tstart() || typedef_ahead()) return 1; + s = lookup(tv); + return s >= 0 && skind[s] == K_KW && !(sflag[s] & F_EXPR); +} +// { stmts; tail } as an expression: its value is the tail, or 0 +int blockexpr() { + int save = nloc, c = C_PLAIN, tail = 0, t = tint; + next(); + fdepth++; bxdepth++; + while (!is_op(s_rb)) { + if (tk == T_EOF) { die("missing }"); break; } + if (stmtstart()) { stmt(); continue; } + c = expr(); rval(); + if (accept(s_semi)) { op(O_DROP); discard(c); c = C_PLAIN; continue; } + if (!is_op(s_rb)) die("expected ; or } after an expression"); + tail = 1; t = xty; + } + next(); + if (tail) c = handup(c); else { lit(0); xbor = -1; xfloat = 0; xstr = 0; xvk = 1; xvv = 0; } + if (xbor >= save) xbor = -1; // that local dies with this block; the pointer leaves untracked + block_close(save); + fdepth--; bxdepth--; + lv = 0; lvk = LV_NONE; xty = t; xzero = 0; + return c; +} + +// ---- constant expressions ---- + +// constant expressions, evaluated while compiling: numbers, named constants, +// addresses of globals and functions, unary minus and ~, parentheses, and the +// operators + - * | & << >>. used for array lengths, enum values, const +// initializers and asm operands. +word cprim() { + int v, s, mc; + if (tk == T_NUM) { v = tv; next(); return v; } + if (accept(s_minus)) return 0 - cprim(); + if (tk == T_OP && tv == uname[2]) { next(); return ~cprim(); } + if (accept(s_lp)) { v = cexpr(); expect(s_rp); return v; } + if (tk == T_ID) { + s = lookup(tv); + if (mstruct && !ginst) { mc = lookup(methname(mstruct, tv)); if (mc >= 0 && skind[mc] == K_CONST) s = mc; } + if (s >= 0 && (skind[s] == K_CONST || skind[s] == K_GARR || (skind[s] == K_FN && (sflag[s] & F_DEF)) || skind[s] == K_GVAR)) { + next(); + return sval[s]; + } + if (s >= 0 && skind[s] == K_TYPE) { // Enum.MEMBER in a constant expression + peekat(0); + if (pk_tk == T_OP && pk_tv == s_dot) { + peekat(1); + if (pk_tk == T_ID) { + mc = lookup(methname(tv, pk_tv)); + if (mc >= 0 && skind[mc] == K_CONST) { next(); next(); next(); return sval[mc]; } + } + } + } + } + die("expected a constant"); + return 0; +} +// compile an expression: a primary, then the operators that follow it. +int cexpr() { + int v = cprim(), o; + while (tk == T_OP) { + o = tv; + if (o == bname[3]) { next(); v = v + cprim(); } + else if (o == bname[4]) { next(); v = v - cprim(); } + else if (o == bname[0]) { next(); v = v * cprim(); } + else if (o == bname[15]) { next(); v = v | cprim(); } + else if (o == bname[13]) { next(); v = v & cprim(); } + else if (o == bname[5]) { next(); v = v << cprim(); } + else if (o == bname[6]) { next(); v = v >> cprim(); } + else return v; + } + return v; +} + diff --git a/src/compiler/image.zc b/src/compiler/image.zc new file mode 100644 index 0000000..dfe5f77 --- /dev/null +++ b/src/compiler/image.zc @@ -0,0 +1,95 @@ +// ---- symbol blob ---- + +// an image ends with its own symbol table so the genome knows its names: +// kind, value, signature, name chars, 0 ... then a 0 kind. +// emit the names defined since symbol index from (functions, globals, +// constants, enhancements) as the image's trailing symbol table +int blob_emit(int from) { + int i = from, table, words = 1; word p; int b, k; + while (i < nsym) { + if (skind[i] == K_DOC) { b = sval[i]; words = words + 2 + (b[0] >> 3); } + if ((skind[i] >= K_FN && skind[i] <= K_CONST) || skind[i] == K_ENH || skind[i] == K_DOC) words = words + 4 + rt.len_w(sname[i]); + i++; + } + reserve_code(words); i = from; + // the docs first: each one's bytes go in the image here, and its symbol + // takes their address (the bytes come after the code, so no code moves) + while (i < nsym) { + if (skind[i] == K_DOC) { + b = sval[i]; + sval[i] = here; + emit(b[0]); + k = 0; + while (k < ((b[0] + 7) >> 3)) { emit(b[1 + k]); k++; } + if (!(b[0] & 7)) emit(0); + } + i++; + } + table = here; i = from; + while (i < nsym) { + if ((skind[i] >= K_FN && skind[i] <= K_CONST) || skind[i] == K_ENH || skind[i] == K_DOC) { + emit(skind[i]); emit(sval[i]); emit(ssig[i]); + p = sname[i]; + while (*p) { emit(*p); p++; } + emit(0); + } + i++; + } + emit(0); + return table; +} +// the reverse: read a blob and define its names, so a running genome knows its +// own functions by name +// hide: leave out the language server's names. functions are entered before +// constants and globals in the blob, so the server's functions come after +// chk_mark but the runtime's constants and globals come after it too: the +// cut is by name, the functions after chk_mark, and everything from the +// server's marker const (LSP_SERVER, the first thing src/lsp/server.zc declares) +int blob_load(word p, int hide) { + int s, n, past = 0, after = 0, skip, mkn = intern("run.chk_mark"), srvn = intern("lsp.LSP_SERVER"); + while (*p) { + n = intern(p + 3); + if (hide && n == srvn) after = 1; + skip = 0; + if (hide && n == mkn) past = 1; + if (!skip) { + s = addsym(n, p[0], p[1]); + ssig[s] = p[2]; + sflag[s] = F_DEF | F_SIG; + if (p[0] == K_ENH) { if (p[1] >= nenh) nenh = p[1] + 1; rt.install(p[2] >> 8); } + } + p = p + 3 + rt.len_w(p + 3) + 1; + } + return 0; +} +// compile the rest of stdin for org into a growing output buffer. the image's +// addresses remain logical addresses at org while its temporary storage grows. +// image_words and image_size expose the finished image; initial is its first +// buffer reservation. the matching host layout must still fit the finished image. +int build_image_grow(int org, int initial) { + int sh = here, sd = hdelta, sl = hlimit, sub = ubase, svl = vislo, sns = nsym, sen = nenh, n; + word saved_buffer = image_buffer; int saved_active = image_active, saved_org = image_org, saved_capacity = image_capacity; + if (org < 0 || initial < 7 || initial > 0x7fffffff - org) fatal("bad image reservation"); + image_buffer = chk_resize(0, 0, initial); image_active = 1; image_org = org; image_capacity = initial; + ubase = nsym; vislo = nsym; nenh = 0; + here = org; hdelta = image_buffer - org; hlimit = org + initial; + lit(rt.FTOP); lit(rt.ZFP); op(O_ST); fnref(fwdfn(s_start)); op(O_JMP); emit(0); emit(0); + unit_stdin(); + n = blob_emit(ubase); + image_buffer[5] = n; + image_buffer[6] = here; + n = here - org; image_words = image_buffer; image_size = n; + popsyms(sns); + ubase = sub; vislo = svl; here = sh; hdelta = sd; hlimit = sl; nenh = sen; + image_buffer = saved_buffer; image_active = saved_active; image_org = saved_org; image_capacity = saved_capacity; + return n; +} +// retain the explicit destination interface for callers that already own storage. +// use build_image_grow and image_words when the output must grow independently. +int build_image(int org, int dst) { + int n = build_image_grow(org, 4096), words = rt.syscall(9, 0, 0, 0); + if (dst < 0 || n > words - dst) fatal("image destination too small"); + rt.copy_w(dst, image_words, n); + return n; +} + diff --git a/src/compiler/lex.zc b/src/compiler/lex.zc new file mode 100644 index 0000000..0b509e4 --- /dev/null +++ b/src/compiler/lex.zc @@ -0,0 +1,652 @@ +// ---- input sources ---- + +// frame: 0 kind (0 stdin, 1 token list, 2 string) 1 object 2 index 3 end +// 4 transparent 5..7 saved token 8..10 saved char state 11 owned 12 saved line +// 13 saved token line 14 saved lookahead count 15..30 saved lookahead 31 saved char buffer +// a backtick ends the source: it is how a program tells the compiler where its +// own text stops and the program's input begins. but only a backtick the +// lexer would read as a token counts. srcend is fed every character of a +// stdin source in order and follows just enough of the lexer's rules to know +// whether it is inside a // comment, a /* */ comment, a string or a char +// literal (a backslash escapes the next character in the last two). it +// returns 1 for a backtick outside all of them. readsrc and nextch both use +// it, so a backtick in a comment or a string is just a character. +int srcend(int c) { + if (qst == 0) { + if (c == 96) return 1; + if (c == 34) qst = 3; + else if (c == 39) qst = 4; + else if (c == '*' && qp == '/') { qst = 2; qp = 0; return 0; } + else if (c == '/' && qp == '/') { qst = 1; qp = 0; return 0; } + qp = c; + return 0; + } + if (qst == 1) { + if (c == 10) { qst = 0; qp = 0; } + return 0; + } + if (qst == 2) { + if (c == '/' && qp == '*') { qst = 0; qp = 0; return 0; } + qp = c; + return 0; + } + if (qesc) { qesc = 0; return 0; } + if (c == 92) { qesc = 1; return 0; } + if ((qst == 3 && c == 34) || (qst == 4 && c == 39)) { qst = 0; qp = 0; } + return 0; +} +// the character level of the lexer: ch is the current char (-1 at the end), +// and chkind says where it comes from (0 stdin, 3 a byte string, anything +// else a zero-terminated char* string). a stdin source ends at a backtick (see srcend). +int nextch() { + if (chkind == 3) { // the usual case: the unit readsrc packed + ch = (chbase[chptr >> 2] >> ((chptr & 3) * 8)) & 255; + if (ch) chptr++; else ch = -1; + } else if (chkind == 0) { + if (qdone || rt.eof()) { ch = -1; return 0; } + ch = rt.get_c() & 255; + // a read that waited for a slow pipe and found its end returns -1: that is the end, not a 0xFF byte + if (ch == 255 && rt.eof()) { qdone = 1; ch = -1; return 0; } + if (srcend(ch)) { qdone = 1; ch = -1; return 0; } // a backtick ends the source + } else { + ch = *chptr & 255; + if (ch) chptr++; else ch = -1; + } + if (ch == 10) line++; + return 0; +} +// the place of a token travels with its line: line in the low 24 bits, then +// the length (16 bits), the offset + 1 (21 bits) and its glue (2 bits, so a +// replayed token list still tells u*x from a product) +int tpack() { return tline | (toklen << 24) | ((toff + 1) << 40) | ((tglue & 3) << 61); } +// restore the token position that tpack saved in a word. +int tunpack(int x) { + tline = x & 0xFFFFFF; + toklen = (x >> 24) & 0xFFFF; + toff = ((x >> 40) & 0x1FFFFF) - 1; + tglue = (x >> 61) & 3; + return 0; +} +// chars consumed so far in the source being read: the index of the char in ch +// (or of the end, once the source is used up). a stdin source reports -1. +int cpos() { + if (chkind == 3) { if (ch < 0) return chptr; return chptr - 1; } + if (chkind == 2) { if (ch < 0) return chptr - chbase; return chptr - chbase - 1; } + return 0 - 1; +} +// push a new input source. the current token, char state and lookahead are +// saved into a 32 word frame so src_pop can put them back exactly. kind 0 is +// stdin, 1 a token list (obj[0] is the count, then triples of kind, value and +// packed place: this is how macros, quote/grab and feed replay code), 2 a +// char* string and 3 a byte string. trans means "transparent": when a token +// list runs out the reader pops it and carries on in the source below, which +// is what lets a fed list splice into the text around it. owned means the +// frame frees obj when it pops. +int src_push(int kind, word obj, int trans, int owned) { + word f; int i; + isp++; + reserve_R_SRC((isp + 1) * 32); + f = R_SRC + isp * 32; + f[0] = kind; f[1] = obj; f[2] = 0; f[3] = 0; f[4] = trans; f[11] = owned; + f[5] = tk; f[6] = tv; f[7] = tglue; f[8] = ch; f[9] = chkind; f[10] = chptr; + f[12] = line; f[13] = tpack(); f[14] = pq_n; f[31] = chbase; + i = 0; + while (i < pq_n) { + f[15 + i * 4] = pq_tk[i]; + f[16 + i * 4] = pq_tv[i]; + f[17 + i * 4] = pq_gl[i]; + f[18 + i * 4] = pq_ln[i]; + i++; + } + pq_n = 0; + if (kind == 1) f[3] = obj[0]; + if (kind == 0) { chkind = 0; qdone = 0; qst = 0; qp = 0; qesc = 0; line = 1; nextch(); } + if (kind == 2) { chkind = 2; chptr = obj; chbase = obj; line = 1; nextch(); } + if (kind == 3) { chkind = 3; chbase = obj; chptr = 0; line = 1; nextch(); } + return 0; +} +// leave the current source and restore the one beneath it +int src_pop() { + word f = R_SRC + isp * 32; int i = 0; + tk = f[5]; tv = f[6]; tglue = f[7]; ch = f[8]; chkind = f[9]; chptr = f[10]; + line = f[12]; tunpack(f[13]); pq_n = f[14]; chbase = f[31]; + while (i < pq_n) { + pq_tk[i] = f[15 + i * 4]; + pq_tv[i] = f[16 + i * 4]; + pq_gl[i] = f[17 + i * 4]; + pq_ln[i] = f[18 + i * 4]; + i++; + } + isp--; + if (f[11]) rt.free(f[1]); + f[1] = 0; // a dead source pointer would pin a quarantined block + return 0; +} +// the lexer's character classes. an operator is built one character at a time +// for as long as the text so far is still a prefix of some known operator +// (opprefix), so a program that adds an operator with add_binop changes how +// the next source is split into tokens without any other bookkeeping. +// the operator characters are + - * / % = < > ! & | ^ ~ ? : . @ # $ . those below +// 64 are a bit mask indexed from 33, the rest are four compares +int isopch(int c) { + if (c < 33 || c > 126) return 0; + if (c < 64) return (0x7a00763d >> (c - 33)) & 1; + return c == 64 || c == 94 || c == 124 || c == 126; +} +// is buf[0..n) followed by c the start of some operator the lexer knows? +int opprefix(int n, int c) { + int i = 0; word k; int j; + while (i < nkop) { + k = kops[i]; + j = 0; + while (j < n && k[j] == tbuf[j]) j++; + if (j == n && k[n] == c) return 1; + i++; + } + return 0; +} +// true for a decimal digit. +int isdig(int c) { return c >= '0' && c <= '9'; } +// true for a letter or an underscore. +int isal(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; } +// a letter, digit or _ : the characters after the first one of a name +int isidc(int c) { + if (c >= 'a') return c <= 'z'; + if (c >= 'A') return c <= 'Z' || c == '_'; + return c >= '0' && c <= '9'; +} +// the value of a hex digit, or a value past 15 when c is not one. +int hexv(int c) { + if (isdig(c)) return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} +// one possibly escaped character of a string or char literal +int escch() { + int c = ch; + nextch(); + if (c != 92) return c; + c = ch; + nextch(); + if (c == 'n') return 10; + if (c == 't') return 9; + if (c == 'r') return 13; + if (c == '0') return 0; + if (c == 'e') return 27; + return c; +} +// finish lexing an operator whose first n characters are in tbuf +int oplex(int n) { + while (ch >= 0 && isopch(ch) && opprefix(n, ch)) { n = token_char(n, ch); nextch(); } + tbuf[n] = 0; + tk = T_OP; tv = intern(tbuf); + return 0; +} +// per-local type flags kept in ltag. these are tiny accessors; the whole bit +// layout is: 0..3 the u/m/d tag, 4 o3, 5 float, 6 byte string, 8..10 the o3 +// register, 12.. the hint bits (see LASG and friends). +// bit 5 of ltag: this local holds a float. the low bits are the u/m/d tag and bit 4 is o3. +word getflt(int i) { return (ltag[i] >> 5) & 1; } +// mark local i as holding a float, or clear the mark. +int setflt(int i, int f) { + if (f) ltag[i] = ltag[i] | 32; + else ltag[i] = ltag[i] - (ltag[i] & 32); + return 0; +} +// bit 6 of ltag: this local holds a byte string. bit 5 is the float. +word getstr(int i) { return (ltag[i] >> 6) & 1; } +// mark local i as holding a string, or clear the mark. +int setstr(int i, int f) { + if (f) ltag[i] = ltag[i] | 64; + else ltag[i] = ltag[i] - (ltag[i] & 64); + return 0; +} +// b"...": length word, then bytes packed 8 per word, little-endian, and a 0 +// after the last byte. a jump skips the data. the address pushed is the length. +int bstrdata(char* s) { + int a, n = 0, i = 0, w, k; + while (s[n]) n++; + reserve_code((n >> 3) + 2); a = here; + emit(n); + while (i < n) { + w = 0; k = 0; + while (k < 8 && i < n) { w = w | (s[i] << (k * 8)); k++; i++; } + emit(w); + } + if (!(n & 7)) emit(0); + return a; +} +int bstrlit(char* s) { + int j = hole(), a; + op(O_JMP); a = bstrdata(s); setlit(j, here); lit(a); return 0; +} +// ---- float literals and float code ---- + +// a float is one word holding the ieee bits. the lexer collects the literal's +// text in tbuf and fbits asks the core (opcode 611) to convert it, so the +// compiler needs no float parser of its own. float operations are single vm +// opcodes (OP_FADD ...). an int operand is converted on the fly, and a +// comparison leaves a plain 0/1 behind. xfloat says whether the expression +// just compiled is a float. +int fbits() { return asm { tbuf @ #1222 }; } +// collect an exponent (e+3) onto the literal in tbuf; n is the length so far +int takeexp(int n) { + if (ch != 'e' && ch != 'E') return n; + n = token_char(n, ch); + nextch(); + if (ch == '+' || ch == '-') { n = token_char(n, ch); nextch(); } + if (!isdig(ch)) { die("bad float"); return n; } + while (isdig(ch)) { n = token_char(n, ch); nextch(); } + return n; +} +// the literal in tbuf[0..n) is a float: hand it back as a T_FLT token +int floatok(int n) { + tbuf[n] = 0; + tk = T_FLT; + tv = fbits(); + return 0; +} +// the binary operators that have a float form +int isfop(int o) { + if (o == s_plus || o == s_minus || o == s_star || o == s_slash) return 1; + if (o == s_lt || o == s_gt || o == s_le || o == s_ge || o == s_eqeq || o == s_ne) return 1; + return 0; +} +// emit float arithmetic or comparison o for operands that are floats (lf, rf). +// an int left operand sits under the right one, so it is converted with +// swap / i2f / swap. > and >= are < and <= with the operands swapped, and != +// is == followed by not. +int femit(int o, int lf, int rf) { + if (!lf) { op(O_SWAP); op(OP_I2F); op(O_SWAP); } + if (!rf) op(OP_I2F); + if (o == s_plus) { op(OP_FADD); xfloat = 1; return 0; } + if (o == s_minus) { op(OP_FSUB); xfloat = 1; return 0; } + if (o == s_star) { op(OP_FMUL); xfloat = 1; return 0; } + if (o == s_slash) { op(OP_FDIV); xfloat = 1; return 0; } + if (o == s_eqeq) { op(OP_FEQ); xfloat = 0; return 0; } + if (o == s_ne) { op(OP_FEQ); lit(1); op(18); xfloat = 0; return 0; } + if (o == s_lt) { op(OP_FLT); xfloat = 0; return 0; } + if (o == s_gt) { op(O_SWAP); op(OP_FLT); xfloat = 0; return 0; } + if (o == s_le) { op(OP_FLE); xfloat = 0; return 0; } + if (o == s_ge) { op(O_SWAP); op(OP_FLE); xfloat = 0; return 0; } + xfloat = 0; + return 0; +} +// true when a type is a float, the same as isfl. +word isfloat(int t) { return isfl(t); } +// float, f32, and f32's packed twin: everything computed as a double +word isfl(word t) { return t && t[0] == TY_FLOAT; } +// ±0 is false, like 0.0. every other float, including NaN, is true. the vm's +// jz stays an integer test, so a float is compared with +0 first. +int ftest() { + if (!xfloat) return 0; + op(OP_FLIT); emit(0); op(OP_FEQ); lit(1); op(18); + xfloat = 0; xty = tbool; + return 0; +} +// a float stored into an int becomes an int, and an int stored into a float +// becomes a float. word keeps the bits and drops the float mark. +int fcoerce(word dst) { + if (dst && treadonly(xty) && isref(xty) && !treadonly(dst) && dst[0] != TY_VAR) serr("const storage cannot be converted to a mutable reference"); + vf_coerce(dst, 1); + if (!dst || dst[0] == TY_VAR) return 0; + if (isfl(dst)) { + if (!xfloat) { op(OP_I2F); xfloat = 1; xty = tfloat; } + // an f32 variable holds a value rounded to single precision; a packed + // element is rounded when pkst stores it + if (dst == tf32 && xty != tf32 && !ispk(xty)) { xenh("zf2s"); xenh("zs2f"); xty = tf32; } + return 0; + } + if (!xfloat) { + if (tbytes(dst) && !samesz(dst, xty)) tnorm(dst); + return 0; + } + if (isnum(dst)) { op(OP_F2I); xfloat = 0; xty = dst; if (tbytes(dst)) tnorm(dst); return 0; } + if (dst == tword) xfloat = 0; + return 0; +} +// sized integers. a value stored into a variable, field, parameter or array +// element of type t is cut to size first: unsigned types are masked and signed +// ones are masked and then sign-extended (xor the sign bit, subtract it). +// samesz says a value of type b is already in range for type a. +word samesz(word a, word b) { return tbytes(a) && tbytes(b) == tbytes(a) && (a[6] & 1) == (b[6] & 1); } +// narrow the integer on the stack to the width of its type. +int tnorm(word t) { + int b = tbytes(t), s; + if (!b || t[0] != TY_INT) return 0; + lit((1 << (8 * b)) - 1); op(16); + if (t[6] & 1) { s = 1 << (8 * b - 1); lit(s); op(18); lit(s); op(O_SUB); } + return 0; +} +// [byte address] -> [value]: load a packed element of type t (the twin) +int pkld(word t) { + int b = t[5]; + if (b == 1) op(612); else if (b == 2) op(614); else op(616); + pkfix(t); + return 0; +} +// a zero extended packed element on the stack becomes the value of type t +int pkfix(word t) { + int b = t[5], s; + if (t[0] == TY_FLOAT) { xenh("zs2f"); return 0; } // f32: the bits widen to a double + if (t[6] & 1) { s = 1 << (8 * b - 1); lit(s); op(18); lit(s); op(O_SUB); } + return 0; +} +// [value byte-address] -> []: store a packed element of type t +int pkst(word t) { + int b = t[5]; + if (t[0] == TY_FLOAT) { op(O_SWAP); xenh("zf2s"); op(O_SWAP); op(617); return 0; } // f32: round the double to single bits + if (b == 1) op(613); else if (b == 2) op(615); else op(617); + return 0; +} +// an integer address and a field name: the name has to belong to one struct. +// field lookup on a bare address, for untyped code such as p.x where p is a +// plain word. if exactly one struct in the type area has a field called n, that +// is the one meant; with two or more it is ambiguous (the caller reports it). +int fieldscan(int n) { + int i = 0; word t; int k, hit = 0; + fldst = 0; fldi = -1; + while (i < ntl) { + t = typelist[i]; + if (t[0] == TY_STRUCT && t[3] >= 0) { + k = 0; + while (k < t[3]) { + if (t[4][k * 3] == n) { + if (hit) return 2; + hit = 1; fldst = t; fldi = k; + } + k++; + } + } + i++; + } + return hit; +} +// a // comment, with ch on its second slash. one that starts a line joins the +// doc comment being collected, if the line above was part of it, or starts a +// new one; a comment after code on the same line is not documentation. +int doccomment(int nl) { + int cl = line, start; + nextch(); + if (ch == ':') { while (ch >= 0 && ch != 10) nextch(); dn = 0; dsect = 1; dlast = cl; docpending = 1; return 0; } + if (!nl) { while (ch >= 0 && ch != 10) nextch(); return 0; } + if (dlast != cl - 1) { dn = 0; dsect = 0; } + if (ch == 32) nextch(); + start = dn; + while (ch >= 0 && ch != 10) { doc_char(ch); nextch(); } + while (dn > start && (dbuf[dn - 1] == 32 || dbuf[dn - 1] == 9 || dbuf[dn - 1] == 13)) dn--; + // a comment block with a section heading is section text: it documents nothing + if (dn - start >= 4 && dbuf[start] == '-' && dbuf[start + 1] == '-' && dbuf[dn - 1] == '-' && dbuf[dn - 2] == '-') dsect = 1; + if (dsect) { dn = 0; dlast = cl; docpending = 1; return 0; } + doc_char(10); + dlast = cl; docpending = 1; + return 0; +} +// block documentation can end on the declaration's line. remove the optional +// leading star on each line, while leaving markdown inside the comment intact. +int docblock(int nl) { + int cl = line, bol = 1, c, adj = dsect && dlast == cl - 1; + if (!nl || (dn && dlast != cl - 1)) dn = 0; + if (!adj) dsect = 0; + nextch(); + if (ch == '*') { nextch(); if (ch == '/') { nextch(); return 0; } } + while (ch >= 0) { + if (bol) { + while (ch == 32 || ch == 9) nextch(); + if (ch == '*') { nextch(); if (ch == '/') { cl = line; nextch(); break; } if (ch == 32) nextch(); } + bol = 0; + } + c = ch; nextch(); + if (c == '*' && ch == '/') { cl = line; nextch(); break; } + doc_char(c); + if (c == 10) bol = 1; + } + while (dn && (dbuf[dn - 1] == 32 || dbuf[dn - 1] == 9 || dbuf[dn - 1] == 10 || dbuf[dn - 1] == 13)) dn--; + doc_char(10); dlast = cl; docpending = 1; + if (adj) dn = 0; + return 0; +} +// the lexer proper: skip white space and comments, then read one token into +// tk / tv with its place in tline / toff. numbers (decimal, hex, floats), +// names, char and string literals (b"..." is a byte string), and operators +// all start here. tglue is 1 when the token touches the one before it with no +// white space, 0 when it does not, and 3 for a char literal (so the magic +// number hint can leave 'a' alone). asm blocks use it to read -5 as one +// literal. +// commented numeric literals are recorded by source offset, including lookahead. +word literal_docs; int literal_doc_n, literal_doc_cap = 32, token_block_doc; +int literal_documented(int off) { + int key = off + 1, i; + if (!literal_doc_n || key <= 0) return 0; + i = dg_hash(key, literal_doc_cap); + while (literal_docs[i]) { if (literal_docs[i] == key) return 1; i = (i + 1) & (literal_doc_cap - 1); } + return 0; +} +int literal_doc_note(int off) { + int key = off + 1, i, cap, k; word old; + if (literal_documented(off)) return 0; + if (!literal_docs) literal_docs = chk_resize(0, 0, literal_doc_cap); + if ((literal_doc_n + 1) * 2 >= literal_doc_cap) { + old = literal_docs; cap = literal_doc_cap * 2; literal_docs = chk_resize(0, 0, cap); + i = 0; + while (i < literal_doc_cap) { if (old[i]) { k = dg_hash(old[i], cap); while (literal_docs[k]) k = (k + 1) & (cap - 1); literal_docs[k] = old[i]; } i++; } + literal_doc_cap = cap; + } + i = dg_hash(key, literal_doc_cap); while (literal_docs[i]) i = (i + 1) & (literal_doc_cap - 1); + literal_docs[i] = key; literal_doc_n++; return 0; +} +int lex1() { + int n = 0, v, dg, nl; + token_block_doc = 0; + if (pendot) { + pendot = 0; tglue = 1; tline = line; toff = cpos(); + tbuf[0] = '.'; tbuf[1] = 0; tk = T_OP; tv = intern(tbuf); + return 0; + } + tglue = 1; + nl = tline != line; + while (1) { + while (ch == 32 || ch == 9 || ch == 10 || ch == 13) { if (ch == 10) nl = 1; nextch(); tglue = 0; } + if (ch != '/') break; + nextch(); + if (ch == '/') { + if (docs_on) doccomment(nl); else while (ch >= 0 && ch != 10) nextch(); + tglue = 0; continue; + } + if (ch == '*') { + token_block_doc = 1; + if (docs_on) { docblock(nl); tglue = 0; continue; } + nextch(); + while (ch >= 0) { if (ch == '*') { nextch(); if (ch == '/') break; } else nextch(); } + nextch(); tglue = 0; continue; + } + tline = line; toff = cpos() - 1; tbuf[0] = '/'; oplex(1); return 0; + } + tline = line; toff = cpos(); + if (docpending) { + dline = 0; + if (dn && (tline == dlast || tline == dlast + 1)) dline = tline; + docpending = 0; + } + if (ch < 0) { tk = T_EOF; tv = 0; return 0; } + if (isdig(ch)) { + v = 0; n = 0; + if (ch == '0') { + nextch(); + if (ch == 'x' || ch == 'X') { + nextch(); + while ((dg = hexv(ch)) >= 0) { v = v * 16 + dg; nextch(); } + tk = T_NUM; tv = v; return 0; + } + tbuf[0] = '0'; n = 1; + } + while (isdig(ch)) { n = token_char(n, ch); v = v * 10 + ch - '0'; nextch(); } + if (ch == 'e' || ch == 'E') { n = takeexp(n); return floatok(n); } + if (ch == '.') { + nextch(); + if (isdig(ch) || ch == 'e' || ch == 'E') { + n = token_char(n, '.'); + while (isdig(ch)) { n = token_char(n, ch); nextch(); } + return floatok(takeexp(n)); + } + if (isal(ch)) { pendot = 1; tk = T_NUM; tv = v; return 0; } // 1.field + n = token_char(n, '.'); + return floatok(n); + } + tk = T_NUM; tv = v; return 0; + } + if (isal(ch)) { + while (isidc(ch)) { n = token_char(n, ch); nextch(); } + tbuf[n] = 0; + if (n == 1 && tbuf[0] == 'b' && ch == 34) { + n = 0; nextch(); + while (ch >= 0 && ch != 34) { v = escch(); n = token_char(n, v); } + nextch(); + tbuf[n] = 0; + tk = T_BSTR; tv = intern(tbuf); return 0; + } + if (ch == '.' && module_known(tbuf) && findloc(intern(tbuf)) < 0) { + n = token_char(n, '.'); nextch(); + while (isidc(ch)) { n = token_char(n, ch); nextch(); } + tbuf[n] = 0; + } + tk = T_ID; tv = intern(tbuf); return 0; + } + if (ch == 39) { + nextch(); + tv = escch(); + if (ch != 39) die("bad character literal"); + nextch(); + tk = T_NUM; + tglue = 3; + return 0; + } + if (ch == 34) { + nextch(); + while (ch >= 0 && ch != 34) { v = escch(); n = token_char(n, v); } + nextch(); + tbuf[n] = 0; + tk = T_STR; tv = intern(tbuf); return 0; + } + if (ch == '.') { + nextch(); + if (isdig(ch)) { + n = 0; tbuf[0] = '.'; n = 1; + while (isdig(ch)) { n = token_char(n, ch); nextch(); } + return floatok(takeexp(n)); + } + tbuf[0] = '.'; + if (isopch(ch) && opprefix(1, ch)) return oplex(1); + tbuf[1] = 0; + tk = T_OP; tv = intern(tbuf); + return 0; + } + tbuf[0] = ch; + nextch(); + if (isopch(tbuf[0])) return oplex(1); + tbuf[1] = 0; + tk = T_OP; tv = intern(tbuf); + return 0; +} +// lex1 plus the token's length, so the language server knows its extent +int lex() { + lex1(); + if (cmode && tk == T_NUM && token_block_doc && chkind == 3 && chbase == rsbase && toff >= 0) { + literal_doc_note(toff); + } + if (toff < 0) toklen = 0; else toklen = cpos() - toff; + return 0; +} +// one token of lookahead: peek() fills pk_*, next() takes it first +int next() { + int i = 1; + ntok++; + if (toff >= 0) { tpend = toff + toklen; tpline = tline; } + if (pq_n) { + tk = pq_tk[0]; tv = pq_tv[0]; tglue = pq_gl[0]; tunpack(pq_ln[0]); + while (i < pq_n) { + pq_tk[i - 1] = pq_tk[i]; + pq_tv[i - 1] = pq_tv[i]; + pq_gl[i - 1] = pq_gl[i]; + pq_ln[i - 1] = pq_ln[i]; + i++; + } + pq_n--; + if (grec) grec_add(); + return 0; + } + next0(); + if (grec) grec_add(); + return 0; +} +// the k-th token after the current one (k from 0 to 3), into pk_tk and pk_tv +int peekat(int k) { + int a, b, c, e; + while (pq_n <= k) { + a = tk; b = tv; c = tglue; e = tpack(); + next0(); + pq_tk[pq_n] = tk; pq_tv[pq_n] = tv; pq_gl[pq_n] = tglue; pq_ln[pq_n] = tpack(); pq_n++; + tk = a; tv = b; tglue = c; tunpack(e); + } + pk_tk = pq_tk[k]; pk_tv = pq_tv[k]; + return 0; +} +// look at the next token without consuming it. +int peek() { return peekat(0); } +// the lowest level of "next token": from a token list if one is the current +// source, otherwise from the lexer. in check mode after an abort it returns +// end of file forever, which unwinds every parsing loop without any special +// case in them. +int next0() { + word f, q; int i; + if (chkabort) { tk = T_EOF; tv = 0; chkfuel++; if (chkfuel > 3000000) rt.halt(); return 0; } + f = R_SRC + isp * 32; + if (f[0] != 1) { lex(); return 0; } + i = f[2]; + if (i < f[3]) { + q = f[1]; + tk = q[1 + i * 3]; + tv = q[2 + i * 3]; + tunpack(q[3 + i * 3]); + f[2] = i + 1; + return 0; + } + if (!f[4]) { tk = T_EOF; tv = 0; return 0; } + src_pop(); // a fed list ran out: continue with the token that followed it + return 0; +} +// token tests. s is an interned spelling, so these are pointer compares. +int is_op(int s) { return tk == T_OP && tv == s; } +// true when the current token is the name s. +int is_id(int s) { return tk == T_ID && tv == s; } +// consume the current token if it is the operator or name s, and say whether it did. +int accept(int s) { + if ((tk == T_OP || tk == T_ID) && tv == s) { next(); return 1; } + return 0; +} +// accept(s) consumes the token if it is s. expect reports "expected s" at the +// end of the previous token (dgprev) when it is missing, and goes into panic. +int expect(int s) { + if (!accept(s)) { + if (!panic && !dry) { + dgprev = 1; + errat(); + dgprev = 0; + rt.put_s("expected '"); rt.put_s(s); rt.put_s("', found "); tokshow(); + errend(); + } + panic = 1; + if (tk == T_EOF) giveup(); + } + return 0; +} +// read an identifier and return its interned handle +int name() { + int n = tv; + if (tk != T_ID) diefound("expected a name"); + nameoff = toff; + next(); + return n; +} + diff --git a/src/compiler/ops.zc b/src/compiler/ops.zc new file mode 100644 index 0000000..a396f72 --- /dev/null +++ b/src/compiler/ops.zc @@ -0,0 +1,218 @@ +// ---- operator tables ---- + +// a sequence is written like asm: one-char mnemonics, numbers, and names +// (resolved when the operator is used, so image builds stay self-contained) +int seqasm(char* s) { + int start = seqtop, n, neg, v, k, c; + while (1) { + while (*s == 32) s++; + if (!*s) break; + n = 0; + while (s[n] && s[n] != 32) { n = token_char(n, s[n]); } + tbuf[n] = 0; + s = s + n; + reserve_R_SEQ(seqtop + 2); + neg = tbuf[0] == '-' && isdig(tbuf[1]); + if (isdig(tbuf[neg])) { + v = 0; k = neg; + while (tbuf[k]) { v = v * 10 + tbuf[k] - '0'; k++; } + if (neg) v = 0 - v; + if ((v >> 62) != 0 && (v >> 62) != -1) { R_SEQ[seqtop] = SEQWORD; R_SEQ[seqtop + 1] = v; seqtop = seqtop + 2; } + else { R_SEQ[seqtop] = v + v + 1; seqtop++; } + } else if (n == 1 && (c = mnemidx(tbuf[0])) >= 0) { + R_SEQ[seqtop] = c + c; seqtop++; + } else { + R_SEQ[seqtop] = SEQREF; R_SEQ[seqtop + 1] = intern(tbuf); seqtop = seqtop + 2; + } + } + return start; +} +// the index of an assembler mnemonic in the table, or -1 when there is none. +int mnemidx(int c) { + int i = 0; + while (mnem[i]) { + if (mnem[i] == c) return i; + i++; + } + return -1; +} +// emit n words of a stored sequence, resolving its references. +int emitseq(int a, int n) { + int e = a + n, s; + while (a < e) { + if (R_SEQ[a] == SEQWORD) { lit(R_SEQ[a + 1]); a = a + 2; } + else if (R_SEQ[a] == SEQREF) { + s = lookup(R_SEQ[a + 1]); + if (s < 0) s = fwdfn(R_SEQ[a + 1]); + if (skind[s] == K_FN) { + fnref(s); + } else if (skind[s] == K_ENH) op(XOP0 + sval[s]); else lit(sval[s]); + a = a + 2; + } else { emit(R_SEQ[a]); a++; } + } + return 0; +} +// add an operator name to the table of known operators, once. +int addkop(int p) { + int i = 0; + while (i < nkop) { + if (kops[i] == p) return 0; + i++; + } + reserve_operators(nkop + 1); + kops[nkop] = p; + nkop++; + return 0; +} +// find a binary operator by its name. +int binfind(int p) { + int i = nbin; + while (i) { + i--; + if (bname[i] == p) return i; + } + return -1; +} +// find a unary operator by its name. +int unfind(int p) { + int i = nun; + while (i) { + i--; + if (uname[i] == p) return i; + } + return -1; +} +// find an assignment operator by its name. +int asgfind(int p) { + int i = nasg; + while (i) { + i--; + if (aname[i] == p) return i; + } + return -1; +} +// add or replace a binary operator. code is a sequence string, or 0 for the +// built-in && (prec 2) and || (prec 1) behaviour, chosen by -1 / -2 in prec. +int add_binop(char* nm, int prec, char* code) { + int p = intern(nm), i = binfind(p), a, n; + if (i < 0) { reserve_binary(nbin + 1); i = nbin; nbin++; } + bname[i] = p; bprec[i] = prec; + // && (-1) and || (-2) short-circuit: they bind below |, and && above ||, as in c + if (prec < 0) { bprec[i] = 3 + prec; blen[i] = prec; bseq[i] = 0; addkop(p); return i; } + bseq[i] = seqasm(code); blen[i] = seqtop - bseq[i]; + addkop(p); + // and its compound assignment, x op= y + n = rt.len_w(nm); + while (1002 + n > tbufcap) { tbuf = chk_resize(tbuf, 0, tbufcap * 2); tbufcap = tbufcap * 2; } + rt.copy_w(tbuf + 1000, nm, n); + tbuf[1000 + n] = '='; tbuf[1001 + n] = 0; + a = intern(tbuf + 1000); + if (prec >= 8 || prec == 5 || prec == 4 || prec == 3) { + if (asgfind(a) < 0) { reserve_assignment(nasg + 1); aname[nasg] = a; nasg++; } + abin[asgfind(a)] = i; + addkop(a); + } + return i; +} +// register a unary operator and the sequence it expands to. +int add_unop(char* nm, char* code) { + int p = intern(nm), i = unfind(p); + if (i < 0) { reserve_unary(nun + 1); i = nun; nun++; } + uname[i] = p; useq[i] = seqasm(code); ulen[i] = seqtop - useq[i]; + addkop(p); + return i; +} +// register a keyword with its handler and flags. +int add_kw(char* nm, int handler, int flags) { + int s = addsym(intern(nm), K_KW, handler); + sflag[s] = F_DEF | flags; + return s; +} +// register a library function as a receiver method. its first parameter +// determines the receiver type; remaining parameters follow the member name. +word receiver_methods, closure_expected; +int add_method(char* member, char* function_name) { + int s = lookup_raw(intern(function_name)); word link; + if (s < 0 || skind[s] != K_FN || !stype[s] || !stype[s][3]) { + serr("a receiver method requires a function with at least one typed parameter"); return -1; + } + link = chk_resize(0, 0, 3); + link[0] = receiver_methods; link[1] = intern(member); link[2] = s; receiver_methods = link; + return s; +} +int receiver_method(word receiver_type, int member) { + word link = receiver_methods, ft, actual; word[8] bs; int found = -1, s; + while (link) { + s = link[2]; + if (s < nsym && link[1] == member) { + ft = stype[s]; actual = receiver_type; + if (actual[0] == TY_ARR && ft[7][0] == TY_DYN && ft[7][3]) actual = tdyn(actual[4], 1); + rt.fill_w(bs, 0, 8); + if (unify(ft[7], actual, bs)) { if (found >= 0 && found != s) return -2; found = s; } + } + link = link[0]; + } + return found; +} +// run.method("member", function) registers a receiver extension while parsing +// the importing source, including when its code is emitted into an image. +int kw_method() { + char* member; int s; + stmtonly(); next(); expect(s_lp); + if (tk != T_STR) { die("a receiver method name must be a character string"); return 0; } + member = tv; next(); expect(s_comma); + s = -1; if (tk == T_ID) s = source_lookup(tv); + if (s < 0 || skind[s] != K_FN) serr("a receiver extension requires a function name"); + next(); expect(s_rp); expect(s_semi); + if (s >= 0) add_method(member, sname[s]); + return 0; +} +// publish a reference for shallow copies while preserving its declared type. +// scalar values are unchanged; in-place aggregates require explicit copying. +int kw_share_value() { + int c; word t; + next(); expect(s_lp); c = expr(); rval(); plainuse(c); t = xty; expect(s_rp); + if (isagg(t)) serr("sharing an aggregate requires sharing its pointer fields explicitly"); + else if (isptr(t) || t[0] == TY_DYN || t[0] == TY_FN || t == tword) callname(intern("rt.share")); + xbor = -1; xaf = 0; effect_value = 0; xty = t; xconst = 0; xvk = 0; + return C_PLAIN; +} + +word statement_hooks; + +// source libraries can register syntax without emitting a runtime registration call. +int kw_syntax() { + char* keyword; int handler, entry = -1; + stmtonly(); next(); expect(s_lp); + if (tk != T_STR) { die("a syntax keyword must be a character string"); return 0; } + keyword = tv; next(); expect(s_comma); handler = -1; + if (tk == T_ID) handler = source_lookup(tv); + if (handler < 0 || skind[handler] != K_FN) serr("a syntax extension requires a function name"); + next(); expect(s_rp); expect(s_semi); + if (handler >= 0) { + if (hdelta && handler >= ubase) entry = syntax_resident(handler); else entry = sval[handler]; + if (entry >= 0) add_kw(keyword, entry, F_TOP | F_STMT); + } + return 0; +} +int kw_statement_hook() { + int handler = -1; word link; + stmtonly(); next(); expect(s_lp); if (tk == T_ID) handler = source_lookup(tv); + if (handler < 0 || skind[handler] != K_FN) serr("a statement hook requires a function name"); + next(); expect(s_rp); expect(s_semi); + if (handler >= 0) { link = chk_resize(0, 0, 3); link[0] = statement_hooks; link[1] = handler; link[2] = 1; statement_hooks = link; } + return 0; +} +// optional statement callbacks installed by source extensions at include time. +// callbacks execute at ordinary vm call boundaries and must guard reentrancy. +int add_statement_hook(int callback) { + word link = chk_resize(0, 0, 3); link[0] = statement_hooks; link[1] = callback; link[2] = 0; statement_hooks = link; + return 0; +} +int emit_statement_hooks() { + word link = statement_hooks; + if (dry || o3on || cmode || incdepth || !infn) return 0; + while (link) { if (link[2]) fnref(link[1]); else lit(link[1]); op(O_CALL); op(O_DROP); link = link[0]; } + return 0; +} + diff --git a/src/compiler/parts b/src/compiler/parts new file mode 100644 index 0000000..a10c6b3 --- /dev/null +++ b/src/compiler/parts @@ -0,0 +1,12 @@ +base.zc +lex.zc +ops.zc +analyzer.zc +types.zc +builtins.zc +expr.zc +stmt.zc +decl.zc +image.zc +setup.zc +check.zc diff --git a/src/compiler/setup.zc b/src/compiler/setup.zc new file mode 100644 index 0000000..4986fc3 --- /dev/null +++ b/src/compiler/setup.zc @@ -0,0 +1,191 @@ +// ---- setup ---- + +// the collector's roots for the compiler's half of the memory map: the program +// area (globals live between functions), the genome's globals, the input source +// frames, the live frame stack and the live data stack. rt.zc declares this +// and calls it from gc(). +int gc_roots() { + int end = ptop; if (end > rt.CODE_END) end = rt.CODE_END; + rt.markrange(0, end); + if (!hdelta && here > ptop && ptop < rt.CODE_END) { end = here; if (end > rt.CODE_END) end = rt.CODE_END; rt.markrange(ptop, end); } // globals of a unit still compiling + rt.markrange(rt.GORG, rt.GORG[6]); + rt.markrange(R_IH, R_POOLEND); + rt.markrange(sval, sval + nsym); + rt.markrange(R_TY, R_TYEND); + rt.markrange(R_SRC, R_SRC + R_SRC_cap); + rt.gc_frames(); + rt.markrange(rt.dsp(), rt.data_top); + return 0; +} +// start-up in two parts. zc_init clears the intern table and interns the +// spellings the parser compares against; zc_tables fills the operator and +// keyword tables. an operator is a name, a precedence and a code sequence in +// the vm's own one-letter mnemonics (see seqasm), so "<=" is "~ < 1 ^": swap, +// less-than, flip the bit. +int zc_init() { + receiver_methods = 0; closure_expected = 0; + effect_floor = 0; effect_symbols = 0; effect_edges = 0; effect_requirements = 0; effect_current = 0; + pooltop = R_POOL; + poolend = R_POOLEND; poolblocks = 0; namecount = 0; + rt.fill_w(namehash, 0, hashcap); + nsym = 0; nfixu = 0; fixfree = 0; nbin = 0; nun = 0; nasg = 0; nkop = 0; seqtop = 0; + lsp = 0; ctxsp = 0; ntd = 0; snaptop = 0; nloc = 0; lbase = 0; npm = 0; npe = 0; rt.fill_w(lkeys, 0, lkeycap); lkeyn = 0; + s_lp = intern("("); s_rp = intern(")"); s_lb = intern("{"); s_rb = intern("}"); + s_lk = intern("["); s_rk = intern("]"); s_comma = intern(","); s_semi = intern(";"); + s_eq = intern("="); s_arrow = intern("->"); s_inc = intern("++"); s_dec = intern("--"); + s_star = intern("*"); + s_slash = intern("/"); + s_amp = intern("&"); + s_colon = intern(":"); + s_hash = intern("#"); + s_minus = intern("-"); + s_dot = intern("."); + s_q = intern("?"); + s_case = intern("case"); + s_default = intern("default"); + s_andand = intern("&&"); + s_oror = intern("||"); + s_fn = intern("fn"); s_var = intern("let"); s_const = intern("const"); s_else = intern("else"); + s_as = intern("as"); s_unchecked = intern("unchecked"); s_immediate = intern("immediate"); + s_m = intern("m"); s_d = intern("d"); s_u = intern("u"); s_static = intern("static"); + s_start = intern("run.start"); + s_main = intern("main"); + s_free = intern("rt.free"); + s_recycle = intern("rt.recycle"); s_copy_w = intern("rt.copy_w"); + s_do = intern("do"); + s_while = intern("while"); + s_enum = intern("enum"); + s_include = intern("include"); + s_memdebug = intern("memdebug"); s_scrub = intern("rt.scrub"); s_alloc = intern("rt.alloc_line"); + s_check = intern("rt.checkptr"); s_memset = intern("rt.mem_set"); + s_own = intern("rt.own"); s_qnew = intern("run.qnew"); s_fovf = intern("rt.frame_overflow"); + s_frame_floor = intern("rt.frame_floor"); s_frame_top = intern("rt.frame_top"); s_frame_node = intern("rt.frame_node"); + s_frame_busy = intern("rt.frame_busy"); s_frame_guard = intern("rt.frame_guard"); + s_frame_grow = intern("rt.frame_grow"); s_frame_leave = intern("rt.frame_leave"); + s_enhance = intern("enhance"); s_micro = intern("micro"); s_vm = intern("vm"); + return 0; +} +// operators and keywords. these are plain tables in memory: a program can +// add operators, change precedences or replace keywords with its own. +// (the order of the first binary operators matters: cexpr refers to * + - << >> +// & | by their index, and the comments mark which ones) +int zc_tables() { + add_unop("!", "0 $ 0 ~ - | 63 > 1 +"); // index 0 + add_unop("!!", "0 $ 0 ~ - | 63 > 0 ~ -"); // index 1: normalize to 0 or 1 + add_unop("~", "-1 ^"); // index 2 + add_unop("-", "0 ~ -"); + add_binop("*", 10, "*"); // the first few indexes are used by cexpr + add_binop("/", 10, "idiv"); // native divide, wired in by rt.zc + add_binop("%", 10, "imod"); + add_binop("+", 9, "+"); // 3 + add_binop("-", 9, "-"); // 4 + add_binop("<<", 8, "ishl"); // 5 + add_binop(">>", 8, ">"); // 6 + add_binop("<", 7, "<"); + add_binop(">", 7, "~ <"); + add_binop("<=", 7, "~ < 1 ^"); + add_binop(">=", 7, "< 1 ^"); + add_binop("==", 6, "- 0 $ 0 ~ - | 63 > 1 +"); + add_binop("!=", 6, "- 0 $ 0 ~ - | 63 > 0 ~ -"); + add_binop("&", 5, "&"); // 13 + add_binop("^", 4, "^"); + add_binop("|", 3, "|"); // 15 + add_binop("&&", -1, 0); + add_binop("||", -2, 0); + addkop(s_eq); addkop(s_arrow); addkop(s_inc); addkop(s_dec); + add_kw("let", kw_var, F_STMT); + add_kw("if", kw_if, F_STMT); + add_kw("while", kw_while, F_STMT); + add_kw("for", kw_for, F_STMT); + add_kw("do", kw_do, F_STMT); + add_kw("enum", kw_enum, F_STMT | F_TOP); + add_kw("include", kw_include, F_TOP | F_STMT); + add_kw("embed", kw_embed, F_TOP | F_STMT); + add_kw("run.method", kw_method, F_TOP | F_STMT); + add_kw("run.share_value", kw_share_value, F_EXPR); + add_kw("run.syntax", kw_syntax, F_TOP | F_STMT); + add_kw("run.statement_hook", kw_statement_hook, F_TOP | F_STMT); + add_kw("memdebug", kw_memdebug, F_STMT | F_TOP); + add_kw("hints", kw_hints, F_STMT | F_TOP); + add_kw("switch", kw_switch, F_STMT); + add_kw("break", kw_break, F_STMT); + add_kw("continue", kw_continue, F_STMT); + add_kw("return", kw_return, F_STMT); + add_kw("unchecked", kw_unchecked, F_STMT); + add_kw("asm", kw_asm, F_STMT | F_EXPR); + add_kw("quote", kw_quote, F_EXPR); + add_kw("sizeof", kw_sizeof, F_EXPR); + add_kw("typeof", kw_typeof, F_EXPR); + add_kw("extern", kw_extern, F_TOP); + add_kw("cfunc", kw_cfunc, F_EXPR); + add_kw("o3", kw_o3, F_STMT); + add_kw("len", kw_len, F_EXPR); + add_kw("cap", kw_cap, F_EXPR); + add_kw("push", kw_push, F_EXPR); + add_kw("pop", kw_pop, F_EXPR); + add_kw("resize", kw_resize, F_EXPR); + return 0; +} +// the default map's type records sit in the last 16384 words of the heap. +// the type list is the first words of that range, so it cannot be kept aside +// as a buddy block: types_init would overwrite the header and the allocator +// would hand the range out again. cut the heap off at R_TY until the unit +// has been compiled, then put the tail back for the program. +int heldk; +// the size class of the type area, for the memory map. +int type_class() { + int k = 0, n = R_TYEND - R_TY; + while (k < 31 && rt.pow2[k] < n) k++; + if (rt.pow2[k] != n) return 0 - 1; + return k; +} +// keep the type area out of the collector's reach while it is in use. +int shield_types() { + int k; + heldk = 0 - 1; + if (R_TY <= rt.HEAP_LO || R_TY >= rt.HEAP_HI || R_TYEND > rt.HEAP_HI) return 0; + k = type_class(); + if (k < 0) return 0; + if (R_TY + rt.pow2[k] != R_TYEND) return 0; + if (rt.heap_reset(R_TY)) heldk = k; + return 0; +} +// let the collector see the type area again. +int unshield_types() { + if (heldk >= 0) { rt.heap_uncut(R_TY, heldk); heldk = 0 - 1; } + return 0; +} +// the genome's entry point, run when the image boots: set up the heap and +// the compiler, then either run the language server (a genome that carries it) +// or compile the unit on stdin, collect the garbage that compiling made, and +// call main(). +int start() { + int s, f; + rt.heap_init(); chk_heap = 1; + shield_types(); + zc_init(); + blob_load(rt.GORG[5], 0); + zc_tables(); + types_init(); + here = 0; hdelta = 0; hlimit = rt.CODE_END; ptop = 0; + ubase = nsym; vislo = 0; + // a genome that carries the language server runs it instead of compiling stdin + s = lookup(intern("lsp.lsp_main")); + if (s >= 0 && skind[s] == K_FN && (sflag[s] & F_DEF)) { f = sval[s]; f(); rt.halt(); } + unit_stdin(); + if (here > ptop) ptop = here; + rt.scrub(); // compilation's stale words would keep quarantined blocks alive + rt.gc(); + // the type area stays out of the allocator. main() may compile again + // (the self-host does) and a later new() would otherwise reuse it. + if (mdbg_flags) rt.mem_set(mdbg_flags); + // a unit that carries src/lib/std.zc has a libinit to fill argc/argv/envc/envp + // with. it runs before main, the same way lsp_main runs instead of stdin. + s = lookup(intern("std.libinit")); + if (s >= 0 && skind[s] == K_FN && (sflag[s] & F_DEF)) { f = sval[s]; f(); } + s = lookup(s_main); + if (s >= 0 && skind[s] == K_FN && (sflag[s] & F_DEF)) { f = sval[s]; f(); } + rt.halt(); + return 0; +} + diff --git a/src/compiler/stmt.zc b/src/compiler/stmt.zc new file mode 100644 index 0000000..a2ad0d1 --- /dev/null +++ b/src/compiler/stmt.zc @@ -0,0 +1,816 @@ +// ---- statements ---- + +// after a syntax error: skip to the end of the statement +// statements. stmt is the dispatcher: a keyword statement (if, while, return, +// a parse word...), a declaration, an empty statement, a block, or an +// expression statement whose value is dropped. after a syntax error panic is +// set and stmt calls resync to skip to the next ; or }. +int resync() { + while (tk != T_EOF && !is_op(s_semi) && !is_op(s_rb)) next(); + if (is_op(s_semi)) next(); + panic = 0; + return 0; +} +// compile one statement. +int stmt() { + word before = borrow_slots; int result; emit_statement_hooks(); result = stmt_inner(); borrow_cleanup_since(before); return result; +} +int stmt_inner() { + int s, c; + if (probe_off >= 0) probe_stmt(); + if (panic) return resync(); + if (tk == T_ID && findloc(tv) < 0) { + s = lookup(tv); + if (s >= 0 && skind[s] == K_KW && (sflag[s] & F_STMT)) { kwcall(s, 0); return 0; } + } + if (untyped_const()) { next(); localdecl(2); return 0; } + if (typedef_ahead() && findloc(tv) < 0) return kw_typedef(); + if (tstart()) { localdecl(1); return 0; } + if (tk == T_OP) { + if (tv == s_semi) { next(); return 0; } + if (tv == s_lb) { block(); return 0; } + } + c = expr(); rval(); op(O_DROP); + expect(s_semi); + discard(c); + return 0; +} +// compile a block, with its own scope for the locals it declares. +int block() { + int save = nloc; + expect(s_lb); + fdepth++; + while (!is_op(s_rb)) { if (tk == T_EOF) { die("missing }"); break; } stmt(); } + if (probe_off >= 0 && !chkabort && !dry && !ginst && incdepth == 0 && ctxsp <= nclo && toff >= probe_off) { probe_snap(); chkabort = 1; } + block_close(save); // before the }, so its line is the one reported + next(); + fdepth--; + return 0; +} +// the body of if/while/for: its own block, even without braces +// (the body of if/while/for is always its own block, even without braces, so +// variables declared in it die with it) +int substmt() { + int save = nloc; + if (is_op(s_lb)) return block(); + fdepth++; + stmt(); + block_close(save); + fdepth--; + return 0; +} +// let a = e, b[10]; or, typed: int a = 1, b; node*.m p = new(node); .u r; +// let infers pointer, struct and function types from the initializer and +// keeps numbers as plain words. +// (each declared name gets a slot; an initializer is compiled and stored, and +// assigncheck gives the new local its analyzer state. an initializer that is a +// constant number marks the local as a const candidate for the hints, and a +// new records the size and leaf-ness for the matching recycle.) +// the type of each untyped local while the language server checks a document: +// the byte offset of its name in the document, and the type's text in ltxt +// (one character per word). the server turns it into the hover of the local. +// record a parsed type expression so arrays, stars, and function types have hovers. +int ht_note(int start, int end, word t) { + if (!lt_on || isp != 0 || dry || !t || start < 0 || end <= start) return 0; + if (!ht_start || ht_n == htcap) { + if (ht_start) htcap = htcap * 2; + ht_start = chk_resize(ht_start, ht_n, htcap); + ht_end = chk_resize(ht_end, ht_n, htcap); + ht_type = chk_resize(ht_type, ht_n, htcap); + } + ht_start[ht_n] = start; ht_end[ht_n] = end; ht_type[ht_n] = t; ht_n++; + return 0; +} +// record the type of a local at byte offset o for the language server. +int lt_note(int o, int t) { + int sm, sb, sn, sc, sl, need; + if (!lt_on || isp != 0 || dry || o < 0 || !t) return 0; + if (!lt_off || lt_n == ltcap) { + if (lt_off) ltcap = ltcap * 2; + lt_off = chk_resize(lt_off, lt_n, ltcap); lt_at = chk_resize(lt_at, lt_n, ltcap); + lt_len = chk_resize(lt_len, lt_n, ltcap); lt_type = chk_resize(lt_type, lt_n, ltcap); + } + sm = rt.outmode; sb = rt.outbuf; sn = rt.outn; sc = rt.outcap; + rt.outmode = 3; rt.outn = 0; tprint(t); need = ltxn + rt.outn; + if (!ltxt || need > ltxtcap) { + while (need > ltxtcap) ltxtcap = ltxtcap * 2; + ltxt = chk_resize(ltxt, ltxn, ltxtcap); + } + rt.outmode = 2; rt.outbuf = ltxt; rt.outn = ltxn; rt.outcap = ltxtcap; + tprint(t); + sl = rt.outn; + rt.outmode = sm; rt.outbuf = sb; rt.outn = sn; rt.outcap = sc; + lt_off[lt_n] = o; lt_at[lt_n] = ltxn; lt_len[lt_n] = sl - ltxn; lt_type[lt_n] = t; lt_n++; + ltxn = sl; + return 0; +} +// compile a local declaration, typed or let-style. +// does `const name =` start here? a const with no type takes the initializer's type. +int untyped_const() { + if (tk != T_ID || tv != s_const) return 0; + peekat(0); if (pk_tk != T_ID || istypename(pk_tv)) return 0; + peekat(1); return pk_tk == T_OP && (pk_tv == s_eq || pk_tv == s_semi || pk_tv == s_comma); +} +// typed is 1 after a type, 0 after let, and 2 after an untyped const. +int localdecl(int typed) { + int n, i, c, k, t = 0, tg = 0, pp, kc, kp = ifkp, gty = 0, gok = !typed && ifon && !dry, o, vr, ro = typed == 2; word iq, it; + if (ro) typed = 0; + if (typed) { dtype(); t = dty; tg = dtag; if (t == tvoid) serr("a variable can't be void"); } + if ((o3on || o3one) && !typed) serr("o3 variables need a type"); + while (1) { + pp = tpack(); + o = toff; + rs_note(RS_LOCAL, o); vr = rsn - 1; + n = name(); + i = newloc(n); + lrsoff[i] = o; + ltag[i] = tg | LDECL; lpos[i] = pp; lnsz[i] = 0; lleaf[i] = 0; + if (o3on || o3one) ltag[i] = ltag[i] | 16; + if (accept(s_lk)) { + k = cexpr(); expect(s_rk); + if (!t) t = tword; + larr[i] = 1; ltype[i] = tarr(t, k); fslots = fslots + tsz(ltype[i]); + if (gok) { if (!gty) gty = tint; else if (gty != tint) gok = 0; } + } else if (t && isagg(t)) { larr[i] = 1; ltype[i] = t; fslots = fslots + tsz(t); } + else { fslots++; if (t) ltype[i] = t; if (isfl(t)) setflt(i, 1); if (o3on) { o3promote(i); o3mkcache(i); } } + capture_cell(i, 0); + if (accept(s_eq)) { + if (larr[i] && ltype[i][0] == TY_ARR && (is_op(s_lb) || tk == T_STR || tk == T_BSTR)) { + iq = array_initializer_tokens(ltype[i]); it = array_measure(iq, ltype[i]); + fslots = fslots + tsz(it) - tsz(ltype[i]); + ltype[i] = it; array_initialize(iq, it, lslot[i], 1); rt.free(iq); + } else { + if (larr[i]) serr("an array or a struct needs an initializer list"); + newsz = -1; newleaf = 0; if (tk == T_NUM) nomagic = 1; + if (t && t[0] == TY_DYN && is_op(s_lb)) c = array_dynamic_literal(t); else c = expr(); + rval(); nomagic = 0; kc = xconst && !xfloat; + if (kc) ltag[i] = ltag[i] | LCONST; + if (c == C_FRESH) { lnsz[i] = newsz; lleaf[i] = newleaf; } + if (t) fcoerce(t); + vaddr(i); + op(O_ST); + o3refresh(i); + setflt(i, xfloat); + setstr(i, xstr); + note_store(i); af_store(i); vf_store(i); + if (t) { if (!compat(t, xty)) tcerr("initializer", t, xty); } + else if (!isnum(xty)) { + if (xty[0] == TY_ARR) ltype[i] = decay(xty); + else if (xty[0] == TY_STRUCT) ltype[i] = tptr(xty); + else ltype[i] = xty; + } + assigncheck(LV_LOCAL, i, c); + if (ro) { k = ltype[i]; if (!k) { k = tword; if (isnum(xty)) k = xty; } ltype[i] = tconst(k); } + if (gok) { k = xty; if (isagg(k)) k = ltype[i]; if (!ifpure(k) || (gty && gty != k)) gok = 0; else gty = k; } + } + } else if (ro) serr("a const needs an initializer"); + else if (gok && !larr[i]) gok = 0; // no initializer: nothing says what it is + if (larr[i] && ltype[i][0] == TY_ARR && ltype[i][3] < 0) serr("T[] needs an initializer to determine its length"); + if (larr[i]) { k = 1; if (ltype[i][0] == TY_ARR) k = ltype[i][3]; lafact[i] = af_new(k, 1, pp); laaddr[i] = lafact[i]; if (lafact[i]) lafact[i][5] = i; } + af_declaration(o, lafact[i]); os_at(o, i); + if (cmode) vf_at(vr, lvkind[i], lvvalue[i]); + kc = tword; + if (ltype[i]) kc = ltype[i]; + lt_note(((pp >> 40) & 0x1FFFFF) - 1, kc); + if (!accept(s_comma)) break; + } + expect(s_semi); + if (gok && gty && kp) fact_add(kp, 0 - 1, gty, ""); + return 0; +} +// compile a variable declaration. +int kw_var() { + stmtonly(); + ifkp = 0; + if (isp == 0) ifkp = tpack(); // only the document itself: an include has other offsets + next(); + return localdecl(0); +} +// if / else. the shape of every branching construct is the same: snapshot the +// analyzer state, compile one path, restore the snapshot, compile the other, +// then join the two end states (join2). a path that does not reach the join +// (it returned or broke) is left out. without an else the "other path" is +// the state before the branch. +int kw_if() { + int a, b, c, s0, sa, live0, livea, n0 = nloc, nt, cj; + stmtonly(); + next(); + expect(s_lp); c = borrow_condition(); expect(s_rp); + a = hole(); op(O_JZ); + live0 = live; + s0 = snap_push(); + substmt(); + livea = live; sa = snap_push(); + if (is_id(s_else)) { + next(); + b = hole(); op(O_JMP); + setlit(a, here); + snap_restore(s0); live = live0; + substmt(); + join2(sa, livea, b, live, n0, "ends the branches of an if in different states"); + } else { + // the path that skips the then branch still has the states from before it + njps = 0; if (livea) jadd(R_SNAP + sa); if (live0) jadd(R_SNAP + s0); + join(n0, "ends the branches of an if in different states"); + if (livea) fixups(R_SNAP + sa, n0, 1); + nt = 0; if (live0) nt = fixups(R_SNAP + s0, n0, 0); + if (nt) { + cj = hole(); + op(O_JMP); + setlit(a, here); + fixups(R_SNAP + s0, n0, 1); + setlit(cj, here); + } + else setlit(a, here); + live = livea || live0; + } + snap_pop(s0); + return 0; +} +// loop frame: 0 continue target (-1 for a switch) 2 depth 3 snapshot at the top +// 4 block-expression depth 5 locals at the start 6 its first exit record +// loops and switches share a frame stack (R_LOOP, 8 words each). a break adds +// an exit record (the analyzer state at that point) with a jump to patch; +// loop_end joins all of them with the normal fall-out and patches the jumps. +int loop_push(int cont, int s0) { + word f; + reserve_R_LOOP((lsp + 1) * 8); + if (lsp >= loopfacts_cap) { lpfact = chk_resize(lpfact, loopfacts_cap, loopfacts_cap * 2); loopfacts_cap = loopfacts_cap * 2; } + f = R_LOOP + lsp * 8; + f[0] = cont; + f[1] = 0; + f[2] = fdepth; + f[3] = s0; + f[4] = bxdepth; + f[5] = nloc; + f[6] = extop; + f[7] = 0; + lpfact[lsp] = -1; + lsp++; + return 0; +} +// the end of a loop or switch: join its exits (the fall-out path sc through +// the jump at a, if there is one, and every break) and lay out each path's +// fixups on its way to the end +int loop_end(int a, int sc) { + word f, p; int e, n0, outs = 0; + lsp--; + f = R_LOOP + lsp * 8; + n0 = f[5]; + njps = 0; + if (sc >= 0) jadd(R_SNAP + sc); + p = R_EXIT + f[6]; e = R_EXIT + extop; + while (p < e) { jadd(p + 1); p = p + 3 + 3 * (p[2] - p[1]); } + join(n0, "leaves the loop in different states on different paths"); + live = njps > 0; + p = R_EXIT + f[6]; + while (p < e) { + if (fixups(p + 1, n0, 0)) { + setlit(p[0], here); + fixups(p + 1, n0, 1); + outs = fixnew(hole(), outs); + op(O_JMP); + } + else outs = fixnew(p[0], outs); + p = p + 3 + 3 * (p[2] - p[1]); + } + if (a >= 0) setlit(a, here); + if (sc >= 0) fixups(R_SNAP + sc, n0, 1); + fixall(outs, here); + extop = f[6]; + return 0; +} +// while. the back edge is checked against the snapshot taken at the top, so a +// loop body that gives something away and comes around again is an error. a +// literal while (1) has no fall-out path, only its breaks. +int kw_while() { + int c0 = here, a, s0, sc = -1, c, inf = 0; + stmtonly(); + next(); + s0 = snap_push(); + expect(s_lp); + // while (1): no way out but break + if (tk == T_NUM && tv) { + peek(); + if (pk_tk == T_OP && pk_tv == s_rp) inf = 1; + } + c = borrow_condition(); expect(s_rp); + a = hole(); op(O_JZ); + if (!inf && live) sc = snap_push(); + loop_push(c0, s0); + substmt(); + backedge(s0); + jmp(c0); + loop_end(a, sc); + snap_pop(s0); + return 0; +} +// for (init; test; step) body. the code is laid out test first, then a jump +// over the step into the body, and the step jumps back to the test, so the +// step is compiled before the body but runs after it. while the test and the +// step are parsed, fortest and forstep record the shape "i < bound ... i++": +// if it holds and the bound is len(a) of a fixed array, the body is told that +// a[i] is in range (lpfact), and its bounds check disappears. +int kw_for() { + int save = nloc, s0, sc = -1, c0, a, b, st, c, inf = 0; word borrow_mark = borrow_slots; + stmtonly(); + next(); + expect(s_lp); + fdepth++; + if (is_id(s_var)) kw_var(); + else if (tstart()) localdecl(1); + else { if (!is_op(s_semi)) { c = expr(); rval(); op(O_DROP); discard(c); } expect(s_semi); } + borrow_cleanup_since(borrow_mark); + s0 = snap_push(); + c0 = here; + ft_iv = -1; fs_ok = 0; fortest = 1; + if (!is_op(s_semi)) { + if (tk == T_NUM && tv) { peek(); if (pk_tk == T_OP && pk_tv == s_semi) inf = 1; } + c = borrow_condition(); + } else { lit(1); inf = 1; xfloat = 0; } + fortest = 0; + expect(s_semi); + a = hole(); op(O_JZ); + if (!inf && live) sc = snap_push(); + b = hole(); op(O_JMP); + st = here; + forstep = 1; borrow_mark = borrow_slots; + if (!is_op(s_rp)) { c = expr(); rval(); op(O_DROP); discard(c); } + borrow_cleanup_since(borrow_mark); + forstep = 0; + expect(s_rp); + jmp(c0); + setlit(b, here); + loop_push(st, s0); + if (ft_iv >= 0 && ft_bd >= 0 && fs_ok && getlo(ft_iv) >= 0 && getln(ft_bd) >= 0) { + lpfact[lsp - 1] = pfact(ft_iv, ft_bd, getln(ft_bd)); + } + substmt(); + backedge(s0); + jmp(st); + loop_end(a, sc); + snap_pop(s0); + block_close(save); + fdepth--; + return 0; +} +// do stmt while (cond); the body runs once before the test. continue jumps +// to the test, which is patched in after the body (f[0] == -2 marks that). +int kw_do() { + int body, s0, sc = -1, a, c; word f; int inf = 0; + stmtonly(); + next(); + s0 = snap_push(); + body = here; + loop_push(-2, s0); + substmt(); + f = R_LOOP + (lsp - 1) * 8; + fixall(f[7], here); + backedge(s0); + if (!is_id(s_while)) die("expected while after do"); + next(); + expect(s_lp); + if (tk == T_NUM && tv) { peek(); if (pk_tk == T_OP && pk_tv == s_rp) inf = 1; } + c = borrow_condition(); + expect(s_rp); + expect(s_semi); + if (!inf && live) sc = snap_push(); + a = hole(); op(O_JZ); + jmp(body); + loop_end(a, sc); + snap_pop(s0); + return 0; +} +// break and continue unwind the scopes they leave (exit_vars frees what those +// blocks own on the way out). break records the analyzer state for the join +// at the end of the loop; continue checks the state against the loop top. +int kw_break() { + word f; + stmtonly(); + next(); expect(s_semi); + if (lsp <= loopbase) { serr("break outside a loop or a switch"); return 0; } + f = R_LOOP + (lsp - 1) * 8; + if (f[4] != bxdepth) { serr("break can't leave a block expression"); return 0; } + exit_vars(f[2] + 1, 0); + if (live) exit_rec(hole()); else hole(); + op(O_JMP); + live = 0; + return 0; +} +// compile continue: jump to the test of the innermost loop. +int kw_continue() { + int j = lsp - 1; word f; + stmtonly(); + next(); expect(s_semi); + while (j >= loopbase && R_LOOP[j * 8] == -1) j--; // past any switch + if (j < loopbase) { serr("continue outside a loop"); return 0; } + f = R_LOOP + j * 8; + if (f[4] != bxdepth) { serr("continue can't leave a block expression"); return 0; } + backedge(f[3]); + exit_vars(f[2] + 1, 0); + if (f[0] == -2) { f[7] = fixnew(hole(), f[7]); op(O_JMP); } // do/while: the condition isn't placed yet + else jmp(f[0]); + live = 0; + return 0; +} +// switch (e) { case 1, 2: ... case 'x': ... default: ... } +// cases don't fall through: each one ends where the next begins, and break +// leaves the switch early. case labels are constants. +// one case's statements, analyzed from the state at the top of the switch. +// the end of a case jumps out of the switch, so cases never fall through. +int casebody(int s0, int live0) { + int save = nloc; + snap_restore(s0); live = live0; + fdepth++; + while (!is_op(s_rb) && !is_id(s_case) && !is_id(s_default)) { + if (tk == T_EOF) { die("missing }"); break; } + stmt(); + } + block_close(save); + fdepth--; + if (live) { exit_rec(hole()); op(O_JMP); } // the end of a case leaves the switch + live = 0; + return 0; +} +// compile a switch statement. +int kw_switch() { + int sl = tline; + int save = nloc, tmp, c, s0, live0, defent = -1, skip, nxt, bj, v, a = -1, st, tg = 0, cov = 0, gi, nb = 0, j, arm, bi; + word[8] pbn; word[8] pbl; + stmtonly(); + next(); + expect(s_lp); c = expr(); st = xty; rval(); plainuse(c); expect(s_rp); + tg = ptagged(st); + fdepth++; + tmp = newloc(0); fslots++; // a hidden local holds the value + laddr(lslot[tmp]); op(O_ST); + live0 = live; + s0 = snap_push(); + loop_push(-1, s0); + expect(s_lb); + while (!is_op(s_rb)) { + if (tk == T_EOF) { die("missing }"); break; } + if (is_id(s_case)) { + next(); + if (tk == T_ID && rt.eq_w(tv, "_")) { + next(); expect(s_colon); + if (defent >= 0) serr("a switch has one catch-all case"); + skip = hole(); op(O_JMP); defent = here; + casebody(s0, live0); setlit(skip, here); + continue; + } + bj = 0; + gi = -1; + peek(); + if (tg && tk == T_ID && pk_tk == T_OP && pk_tv == s_lp) gi = pm_find(tv); + if (gi >= 0) { + // case member(a, b): a member of a tagged enum, its fields bound to the names + next(); next(); + nb = 0; + while (!is_op(s_rp)) { + if (nb >= 8) { die("too many names in a case"); break; } + pbn[nb] = name(); nb++; + if (!accept(s_comma)) break; + } + expect(s_rp); + if (nb != pm_nf[gi]) serr("a case binds every field of its member"); + v = pm_tag[gi]; + if (v < 63) cov = cov | (1 << v); + laddr(lslot[tmp]); op(O_LD); op(O_LD); lit(v); op(O_SUB); + bj = fixnew(hole(), bj); op(O_JZ); + expect(s_colon); + arm = nloc; + for (j = 0; j < nb && j < pm_nf[gi]; j++) { + bi = newloc(pbn[j]); fslots++; + ltype[bi] = pm_ft[gi * 8 + j]; + if (isfl(ltype[bi])) setflt(bi, 1); + pbl[j] = bi; + } + nxt = hole(); op(O_JMP); + fixall(bj, here); + for (j = 0; j < nb && j < pm_nf[gi]; j++) { + capture_cell(pbl[j], 0); + laddr(lslot[tmp]); op(O_LD); lit(pm_off[gi * 8 + j]); op(O_ADD); op(O_LD); + vaddr(pbl[j]); op(O_ST); + } + casebody(s0, live0); + block_close(arm); + setlit(nxt, here); + } else { + while (1) { + v = cexpr(); + if (tg && v >= 0 && v < 63) cov = cov | (1 << v); + laddr(lslot[tmp]); op(O_LD); + if (tg) op(O_LD); + lit(v); op(O_SUB); + bj = fixnew(hole(), bj); op(O_JZ); + if (!accept(s_comma)) break; + } + expect(s_colon); + nxt = hole(); op(O_JMP); + fixall(bj, here); + casebody(s0, live0); + setlit(nxt, here); + } + } else if (is_id(s_default)) { + next(); expect(s_colon); + if (defent >= 0) serr("a switch has one default"); + skip = hole(); op(O_JMP); // only reached when nothing matched + defent = here; + casebody(s0, live0); + setlit(skip, here); + } else die("expected case or default"); + } + next(); + snap_restore(s0); live = live0; + if (tg && defent < 0) cover_warn(tg - 1, cov, sl); + if (defent >= 0) { jmp(defent); loop_end(-1, -1); } + else { a = hole(); op(O_JMP); loop_end(a, s0); } // nothing matched: out with the states from the top + snap_pop(s0); + block_close(save); + fdepth--; + return 0; +} +// return: coerce and check the value, run the analyzer's return rule, free +// everything the function owns (exit_vars from depth 1), and jump to the +// epilogue, which is a patch chain resolved when the function ends. +int kw_return() { + int c, returned = tint; + stmtonly(); + next(); + if (!infn) serr("return outside a function"); + if (bxdepth) serr("return can't leave a block expression"); + if (is_op(s_semi)) { lit(0); c = C_PLAIN; if_ret(0, 1); } + else { + c = expr(); rval(); + returned = xty; + if (!nclo && !xzero) { + if (!fn_return_type) fn_return_type = returned; + else if (fn_return_type != returned) fn_return_type = 0 - 1; + } + if_ret(xty, xzero); + if (fty && fty != tvoid) fcoerce(fty); + if (fty == tvoid) tcerr("return value", tvoid, xty); + else if (fty && !compat(fty, xty)) tcerr("return value", fty, xty); + } + if (nclo) { + if (!clo_ret[nclo - 1]) clo_ret[nclo - 1] = returned; + else if (clo_ret[nclo - 1] != returned) clo_ret[nclo - 1] = 0 - 1; + } + expect(s_semi); + retcheck(c); + borrow_cleanup(); + exit_vars(1, 0); + epichain = fixnew(here, epichain); + lit(rt.MW - 1); op(O_JMP); + live = 0; + return 0; +} +// o3 { ... }: an optimized region. o3 T x = e;: one o3 variable +// (a block is compiled as normal code behind a jump and handed to the core +// as a region once the function is complete: o3pend queues them. variables +// declared outside that are o3 get registers too, and anything with a pointer +// in it is fattened when control leaves the region.) +int kw_o3() { + int lo, i; word p; int save, outer; + stmtonly(); + next(); + if (!is_op(s_lb)) { o3one = 1; localdecl(1); o3one = 0; return 0; } + if (o3on || !infn) return block(); + reserve_code(2); lo = here + 2; lit(lo); op(O_JMP); + o3on = 1; o3reset(); + save = nloc; outer = nloc; + i = lbase; + // o3 variables declared outside + while (i < outer) { + if (ltag[i] & 16) { + o3promote(i); + o3mkcache(i); + o3refresh(i); + } + i++; + } + block(); + o3on = 0; + if (no3pend >= o3pendcap) { o3pend = chk_resize(o3pend, no3pend * 67, o3pendcap * 2 * 67); o3pendcap = o3pendcap * 2; } + { + p = o3pend + no3pend * 67; p[0] = lo; p[1] = here; p[2] = no3sl; + rt.copy_w(p + 3, o3sl, no3sl); rt.copy_w(p + 35, o3rg, no3sl); no3pend++; + } + local_drop(save); fdrop(save); // the hidden cache variables + i = lbase; + while (i < outer) { + ltag[i] = ltag[i] - (ltag[i] & 1792); o3cache[i] = 0; + // leaving o3: fatten what they hold + if ((ltag[i] & 16) && hasptr(ltype[i])) { + vaddr(i); + op(O_LD); + lit(ltype[i]); + callname(s_fatten); + op(O_DROP); + } + i++; + } + return 0; +} +// unchecked turns the analyzer off for one statement +int kw_unchecked() { + int old = fchk; + stmtonly(); + next(); + fchk = 0; + substmt(); + fchk = old; + return 0; +} + +// ---- asm blocks: raw vm words, one per token ---- + +// numbers push, -n pushes a negative number, #n emits a raw word, one-char +// tokens from the mnemonic table are opcodes, names push the address of a +// variable or function, :name defines a label for this block. +// (labels are local to the block: :name defines one, a later name refers to it, +// and references to labels not yet defined are resolved when the block ends) +word asm_names = R_ASM, asm_sites = R_ASM + ASM_LABELS; +word asm_refs = R_ASM + ASM_LABELS + ASM_LABELS, asm_fixes = R_ASM + ASM_LABELS + ASM_LABELS + ASM_REFS; +int asm_labelcap = ASM_LABELS, asm_refcap = ASM_REFS; +int reserve_asm_labels(int need) { + int cap = asm_labelcap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + asm_names = chk_resize(asm_names, asm_labelcap, cap); asm_sites = chk_resize(asm_sites, asm_labelcap, cap); + asm_labelcap = cap; return cap; +} +int reserve_asm_references(int need) { + int cap = asm_refcap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + asm_refs = chk_resize(asm_refs, asm_refcap, cap); asm_fixes = chk_resize(asm_fixes, asm_refcap, cap); + asm_refcap = cap; return cap; +} +int asmblock() { + af_forget(); + int nl = 0, nr = 0, i, k, c, s; word ln = asm_names, la = asm_sites, rn = asm_refs, ra = asm_fixes; + expect(s_lb); + while (!is_op(s_rb)) { + if (tk == T_EOF) { die("unterminated asm block"); break; } + if (tk == T_FLT) { op(OP_FLIT); emit(tv); next(); continue; } + if (tk == T_NUM) { lit(tv); next(); continue; } + if (tk == T_BSTR) { bstrlit(tv); next(); continue; } + if (tk == T_STR) { strlit(tv); next(); continue; } + if (is_op(s_hash)) { + next(); + if (tk != T_NUM) die("# needs a number"); + emit(tv); + next(); + continue; + } + if (is_op(s_minus)) { + next(); + if (tk == T_NUM && tglue) { + lit(0 - tv); + next(); + } else { + op(O_SUB); + } + continue; + } + if (is_op(s_colon)) { + next(); + reserve_asm_labels(nl + 1); ln = asm_names; la = asm_sites; + ln[nl] = name(); + la[nl] = here; + nl++; + continue; + } + if (tv[0] && !tv[1] && (c = mnemidx(tv[0])) >= 0) { op(c); next(); continue; } + if (tk != T_ID) die("bad token in asm"); + i = findloc(tv); + if (i >= 0 && (ltag[i] & 16) && !larr[i]) serr("asm can't reach an o3 variable"); + if (i >= 0) { luse(i); ltag[i] = ltag[i] | LREAD | LASG; vaddr(i); next(); continue; } + if (nclo) { + k = clocap(tv); + if (k >= 0) { vaddr(clo_env[nclo - 1]); op(O_LD); lit(k); op(O_ADD); op(O_LD); next(); continue; } + } + k = -1; i = 0; + while (i < nl) { if (ln[i] == tv) k = i; i++; } + if (k >= 0) { lit(la[k]); next(); continue; } + s = lookup(tv); + if (s >= 0 && skind[s] == K_ENH) { op(XOP0 + sval[s]); next(); continue; } + if (s >= 0 && skind[s] != K_FN && skind[s] != K_KW) { lit(sval[s]); next(); continue; } + if (s >= 0 && skind[s] == K_FN) { fnref(s); next(); continue; } + reserve_asm_references(nr + 1); rn = asm_refs; ra = asm_fixes; + rn[nr] = tv; ra[nr] = hole(); nr++; next(); // a label further down this block + } + next(); + i = 0; + while (i < nr) { + k = -1; c = 0; + while (c < nl) { if (ln[c] == rn[i]) k = c; c++; } + if (k < 0) serrn(rn[i], "unknown name in asm"); + setlit(ra[i], la[k]); + i++; + } + return 0; +} +// virtual enhancement bodies are recipes read as a flat range by the host. +int contiguous_asm() { + word q = grab(), wrapped; int n = q[0], i = 0, need = 2, entry, at; + while (i < n) { + need = need + 16; + if (q[1 + i * 3] == T_STR || q[1 + i * 3] == T_BSTR) need = need + rt.len_w(q[2 + i * 3]); + i++; + } + reserve_code(need); entry = here; + wrapped = chk_resize(0, 0, n * 3 + 7); wrapped[0] = n + 2; + wrapped[1] = T_OP; wrapped[2] = s_lb; wrapped[3] = tpack(); + rt.copy_w(wrapped + 4, q + 1, n * 3); at = 4 + n * 3; + wrapped[at] = T_OP; wrapped[at + 1] = s_rb; wrapped[at + 2] = tpack(); + src_push(1, wrapped, 0, 0); next(); asmblock(); src_pop(); op(O_RET); + rt.free(d*q); return entry; +} +// asm as a keyword (statement or expression): the block's own stack effect is +// the program's responsibility, so the result is a plain word +int kw_asm() { + if (effect_current) effect_current[0] = 2; + next(); + asmblock(); + xcls = C_PLAIN; + return 0; +} + +// ---- quote: code as data ---- + +// a token list is an object: [n] then kind, value, line for each token. names, +// strings and operators are interned pointers. +// grab reads the { ... } after quote or times (a parse word) into a token list, +// stopping at the matching brace. it buffers the tokens in R_QBUF, then copies +// them to a fresh object. quote compiles a list into the program as data and +// returns a copy at run time; feed (below) pushes a list back as input. +word .u grab() { + int n = 0, depth = 0; word q; + if (!is_op(s_lb)) die("expected {"); + next(); + while (1) { + if (tk == T_EOF) { die("unterminated quote"); break; } + if (is_op(s_lb)) depth++; + if (is_op(s_rb)) { if (!depth) break; depth--; } + reserve_R_QBUF((n + 1) * 3); + R_QBUF[n * 3] = tk; R_QBUF[n * 3 + 1] = tv; R_QBUF[n * 3 + 2] = tline; n++; + next(); + } + next(); + q = rt.new(n * 3 + 1); + q[0] = n; + rt.copy_w(q + 1, R_QBUF, n * 3); + return u*q; +} +word .u qnew(word src) { int n = src[0] * 3 + 1; word q = rt.new(n); rt.copy_w(q, src, n); return q; } +word .u qdup(.m q) { int n = q[0] * 3 + 1; word r = rt.new(n); rt.copy_w(r, q, n); return u*r; } +// emit n words copied from a well-formed token buffer q; references in the emitted words must outlive the image. +int wcopyto(.m q, int n) { + int i = 0; reserve_code(n); + while (i < n) { emit(q[i]); i++; } + return 0; +} +// quote { tokens }: the list is emitted into the code behind a jump, and the +// expression's value is a fresh copy of it (qnew), so a program can edit its +// copy without touching the original +int quote_tokens() { + word q; int j, a, n; + q = grab(); + j = hole(); op(O_JMP); + n = q[0] * 3 + 1; reserve_code(n); a = here; + wcopyto(q, n); + rt.free(d*q); + setlit(j, here); + lit(a); + callname(s_qnew); + xcls = C_FRESH; + return 0; +} +int kw_quote() { next(); return quote_tokens(); } +// tokens of a string, as a token list +word*.u tokens(char* s) { + int n = 0; word q; + src_push(2, s, 0, 0); + next(); + while (tk != T_EOF) { + reserve_R_QBUF((n + 1) * 3); + R_QBUF[n * 3] = tk; R_QBUF[n * 3 + 1] = tv; R_QBUF[n * 3 + 2] = tline; n++; + next(); + } + src_pop(); + q = rt.new(n * 3 + 1); + q[0] = n; + rt.copy_w(q + 1, R_QBUF, n * 3); + return u*q; +} +// feed a token list into the input: the parser reads it next, then carries +// on where it was. the list is freed once it has been read. +int feed(.d q) { + src_push(1, q, 1, 1); + next(); + return 0; +} + diff --git a/src/compiler/types.zc b/src/compiler/types.zc new file mode 100644 index 0000000..c643f0b --- /dev/null +++ b/src/compiler/types.zc @@ -0,0 +1,1003 @@ +// ---- types ---- + +// types are a compiler primitive: records in the type area, hash-consed so +// the same type is always the same pointer and comparing types is comparing +// words. untyped code is typed word, which goes with anything. the records +// stay in memory after compiling, so programs can inspect them. +// t[0] kind t[1] size in words t[2] name t[3] n t[4] a t[5] b t[6] c t[7..] extra +// ptr: a = element arr: a = element, n = length +// fn: a = return type, n = parameter count, b = signature (u m d tags), extra = parameter types +// struct: n = field count (-1 until laid out), a = fields (name, type, offset), +// b = generic template, c = type arguments [n, t1, t2 ...] +// var: a type parameter, n = its index +// gen: a generic struct: n = parameter count, a = fields (name, type), b = field count, c = parameter names + + +// the type area is a bump allocator of its own (tytop .. R_TYEND). talloc +// hands out zeroed words, tnew makes a record and adds it to the list of all +// types (R_TY), and the constructors below look that list up first, which is +// the hash-consing: asking for the same pointer type twice returns the same +// record. the list is searched linearly, which is fine for the few hundred +// types a program has. +int talloc(int n) { + int p = tytop; + if (tytop + n > tyend) { + p = chk_resize(0, 0, n + 16385); p[0] = typeblocks; typeblocks = p; + p++; tytop = p; tyend = p + n + 16384; + } + tytop = tytop + n; + rt.fill_w(p, 0, n); + return p; +} +// make a type record of the given kind, size and name in the type area. +word tnew(int kind, int size, char* nm, int extra) { + word t = talloc(7 + extra); + t[0] = kind; t[1] = size; t[2] = nm; + if (ntl >= typecap) { typelist = chk_resize(typelist, ntl, typecap * 2); typecap = typecap * 2; } + typelist[ntl] = t; ntl++; + return t; +} +// qualified types retain their layout and reference their unqualified record. +word tunconst(word t) { + word entry = readonly_types; + while (entry) { if (entry[1] == t) return entry[2]; entry = entry[0]; } + return t; +} +int treadonly(word t) { return t && tunconst(t) != t; } +word tconst(word t) { + word entry = readonly_types, qualified; int words = 7; + if (treadonly(t)) return t; + while (entry) { if (entry[2] == t) return entry[1]; entry = entry[0]; } + if (t[0] == TY_FN) words = words + t[3]; + qualified = tnew(t[0], t[1], t[2], words - 7); rt.copy_w(qualified, t, words); + entry = talloc(3); entry[0] = readonly_types; entry[1] = qualified; entry[2] = t; readonly_types = entry; + return qualified; +} +// writes require mutable storage, independently of optional type checking. +int require_mutable(word t) { + if (treadonly(t)) serr("const storage cannot be modified"); + return 0; +} +// the pointer type to type e, reused when it already exists. +word tptr(int e) { + int i = 0; word t; + while (i < ntl) { t = typelist[i]; if (t[0] == TY_PTR && t[4] == e && !treadonly(t)) return t; i++; } + t = tnew(TY_PTR, 1, 0, 0); t[4] = e; + return t; +} +// sized integers (i8 u8 i16 u16 i32 u32) are TY_INT records with t[5] = bytes, +// t[6] bit 0 = signed, and t[4] = the "packed view" twin: the same name with +// t[6] bit 1 set. a variable, parameter or field of a sized type is an +// ordinary word that is cut to size whenever it is stored. an element of an +// array or a pointer of a sized type is stored packed, 8, 4 or 2 to a word, +// and indexing yields the twin, whose lvalue is a byte address. +word tbytes(word t) { if (t[0] == TY_INT || t[0] == TY_FLOAT) return t[5]; return 0; } +// true for a packed integer or float type. +word ispk(word t) { return (t[0] == TY_INT || t[0] == TY_FLOAT) && (t[6] & 2); } +// the type a packed type stores its elements as, or the type itself. +word tpk(word t) { if (treadonly(t)) return tconst(tpk(tunconst(t))); if ((t[0] == TY_INT || t[0] == TY_FLOAT) && t[4]) return t[4]; return t; } +// an enhancement used by the compiler itself, inline when the runtime has it +int xenh(char* name) { + int s = lookup(intern(name)); + if (s >= 0 && skind[s] == K_ENH) { op(XOP0 + sval[s]); return 0; } + callname(intern(name)); + return 0; +} +// words an array of n elements of e takes: packed for a sized integer +word tasz(word e, int n) { + if (n < 0) return 0; + if (tbytes(e)) return (n * e[5] + 7) >> 3; + return n * tsz(e); +} +// the fixed array type of n elements of type e, reused when it already exists. +word tarr(int e, int n) { + int i = 0; word t; + while (i < ntl) { t = typelist[i]; if (t[0] == TY_ARR && t[4] == e && t[3] == n && !treadonly(t)) return t; i++; } + t = tnew(TY_ARR, tasz(e, n), 0, 0); t[4] = e; t[3] = n; + return t; +} +// a sized integer type and its packed twin +word tsized(char* name, int bytes, int sgn) { + word t = tnew(TY_INT, 1, intern(name), 0), twin = tnew(TY_INT, 1, t[2], 0); + t[5] = bytes; t[6] = sgn; t[4] = twin; + twin[5] = bytes; twin[6] = sgn | 2; + addsym(t[2], K_TYPE, t); + return t; +} +// the function type with this return, parameter list and signature. +word tfn(int ret, int np, word ps, int sig) { + int i = 0, j; word t; + while (i < ntl) { + t = typelist[i]; + if (t[0] == TY_FN && t[4] == ret && t[3] == np && t[5] == sig) { + j = 0; while (j < np && t[7 + j] == ps[j]) j++; + if (j == np) return t; + } + i++; + } + t = tnew(TY_FN, 1, 0, np); t[4] = ret; t[3] = np; t[5] = sig; + rt.copy_w(t + 7, ps, np); + return t; +} +// T[?], or fixed T[?] +word tdyn(int e, int f) { + int i = 0; word t; + while (i < ntl) { t = typelist[i]; if (t[0] == TY_DYN && t[4] == e && t[3] == f && !treadonly(t)) return t; i++; } + t = tnew(TY_DYN, 1, 0, 0); t[4] = e; t[3] = f; + return t; +} +// the type variable with index i, reused when it already exists. +word tvar(int i, char* nm) { + int k = 0; word t; + while (k < ntl) { t = typelist[k]; if (t[0] == TY_VAR && t[3] == i) return t; k++; } + t = tnew(TY_VAR, 1, nm, 0); t[3] = i; + return t; +} +// the instance of generic struct g with type arguments args[0..n). found if it +// already exists, otherwise created unlaid-out (n = -1), so that mentioning +// vec<T> inside vec<T>'s own methods does not recurse; tresolve lays it out. +word tinst(word g, word args, int n) { + int i = 0, j; word t, v; + while (i < ntl) { + t = typelist[i]; + if (t[0] == TY_STRUCT && t[5] == g) { + v = t[6]; j = 0; while (j < n && v[1 + j] == args[j]) j++; + if (j == n) return t; + } + i++; + } + v = talloc(n + 1); v[0] = n; rt.copy_w(v + 1, args, n); + t = tnew(TY_STRUCT, 0, g[2], 0); t[3] = -1; t[5] = g; t[6] = v; + return t; +} +// lay out a generic instance on first use +word tresolve(word t) { + word g, f; int n, i; word fs; int off = 0, ft; word[8] bs; + if (t[0] != TY_STRUCT || t[3] >= 0) return t; + if (t[3] == -2) { serr("a struct can't contain itself by value"); t[3] = 0; return t; } + t[3] = -2; + g = t[5]; n = g[5]; f = g[4]; + rt.fill_w(bs, 0, 8); rt.copy_w(bs, t[6] + 1, t[6][0]); + fs = talloc(n * 3 + 1); + i = 0; + while (i < n) { + ft = subst(f[i + i + 1], bs); + fs[i * 3] = f[i + i]; fs[i * 3 + 1] = ft; fs[i * 3 + 2] = off; + off = off + tsz(ft); + i++; + } + t[4] = fs; t[1] = off; t[3] = n; + return t; +} +// size of a type in words, laying out a generic instance first if it needs it +word tsz(word t) { + if (t[0] == TY_STRUCT && t[3] < 0) tresolve(t); + return t[1]; +} +// replace type parameters with what they were bound to (unbound ones become word) +word subst(word t, word bs) { + if (treadonly(t)) return tconst(subst(tunconst(t), bs)); + int k = t[0], i, n; word[24] v; word vec; + if (k == TY_VAR) { if (bs[t[3]]) return bs[t[3]]; return tword; } + if (k == TY_PTR) return tptr(subst(t[4], bs)); + if (k == TY_ARR) return tarr(subst(t[4], bs), t[3]); + if (k == TY_DYN) return tdyn(subst(t[4], bs), t[3]); + if (k == TY_FN) { + n = t[3]; + i = 0; + while (i < n) { v[i] = subst(t[7 + i], bs); i++; } + return tfn(subst(t[4], bs), n, v, t[5]); + } + if (k == TY_STRUCT && t[5]) { + vec = t[6]; + n = vec[0]; + i = 0; + while (i < n) { v[i] = subst(vec[1 + i], bs); i++; } + return tinst(t[5], v, n); + } + return t; +} +// small type predicates. an array decays to a pointer to its element where a +// value is wanted, and telem is what indexing a pointer or array yields. +word isnum(word t) { return t[0] == TY_INT || t[0] == TY_CHAR || t[0] == TY_BOOL; } +// true for an aggregate type: an array or a struct. +word isagg(word t) { return t[0] == TY_ARR || t[0] == TY_STRUCT; } +// true for a pointer or an array type. +word isptr(word t) { return t[0] == TY_PTR || t[0] == TY_ARR || t[0] == TY_BSTR; } +// true for a reference value, including growable-array handles and functions. +word isref(word t) { return isptr(t) || t[0] == TY_DYN || t[0] == TY_FN; } +// the pointer an array type turns into when used as a value. +word decay(word t) { + if (treadonly(t)) return tconst(decay(tunconst(t))); + if (t[0] == TY_ARR) return tptr(t[4]); + return t; +} +// the element type of a pointer or array, or word for anything else. +word telem(word t) { + if (treadonly(t)) return tconst(telem(tunconst(t))); + if (t[0] == TY_PTR || t[0] == TY_ARR) return t[4]; + return tword; +} +// can a value of type src go where dst is expected? +// type compatibility, used by the optional typechecker. word is compatible with +// everything (untyped code stays legal), numbers convert freely, pointers need +// the same element unless one side is untyped or void, and a literal 0 is a +// null pointer. +word compat(word dst, word src) { + if (treadonly(src) && isref(src) && !treadonly(dst)) return 0; + dst = tunconst(dst); src = tunconst(src); + if (dst == src) return 1; + if (dst == tword && src && (src == tbstr || (src[0] == TY_PTR && src[4] == tchar) || (src[0] == TY_ARR && src[4] == tchar))) return 0; + if (dst == tword || src == tword) return 1; + if (dst == tbstr || src == tbstr) return dst == tbstr && xzero; + if (dst[0] == TY_PTR && src[0] == TY_STRUCT && dst[4] == src) return 1; // an in-place struct supplies its address + if (isen(dst) || isen(src)) { // an enum takes only its own enum; an enum still becomes an int + if (isen(dst) && isen(src)) return dst == src; + if (isen(dst)) return 0; + } + if (isnum(dst) && isnum(src)) return 1; + if (isfl(dst) && isfl(src)) return 1; + src = decay(src); + if (dst[0] == TY_PTR && src[0] == TY_PTR) { + if (dst[4] == tword || src[4] == tword || dst[4] == tvoid || src[4] == tvoid) return 1; + return dst[4] == src[4]; + } + if ((dst[0] == TY_PTR || dst[0] == TY_FN || dst[0] == TY_DYN) && xzero) return 1; // a literal 0 is a null pointer + if (dst[0] == TY_DYN && src[0] == TY_DYN && dst[4] == src[4]) return dst[3] || !src[3]; // a growable array can't hold a fixed one + return dst == src; +} +// match pattern type p (which may contain type parameters) against actual type +// a, filling the bindings bs. this is how a call to a generic function infers +// T from its arguments. +word unify(word p, word a, word bs) { + int i, n; word pv, av; + if (p[0] != TY_VAR) { + if (treadonly(a) && isref(a) && !treadonly(p)) return 0; + p = tunconst(p); a = tunconst(a); + } + if (p[0] == TY_VAR) { i = p[3]; if (!bs[i]) { bs[i] = a; return 1; } return compat(bs[i], a); } + a = decay(a); + if (p[0] == TY_PTR && a[0] == TY_PTR) { if (a[4] == tword) return 1; return unify(p[4], a[4], bs); } + if (p[0] == TY_DYN && a[0] == TY_DYN) return (p[3] || !a[3]) && unify(p[4], a[4], bs); + if (p[0] == TY_FN && a[0] == TY_FN) { + if (p[3] != a[3] || ((p[5] >> 6) != (a[5] >> 6))) return 0; + i = 0; + while (i < p[3]) { + pv = subst(p[7 + i], bs); av = a[7 + i]; + if (pv != tword && av != tword && pv != av) return 0; + if (!unify(p[7 + i], av, bs)) return 0; + i++; + } + return unify(p[4], a[4], bs); + } + if (p[0] == TY_STRUCT && p[5] && a[0] == TY_STRUCT && a[5] == p[5]) { + pv = p[6]; av = a[6]; n = pv[0]; i = 0; + while (i < n) { if (!unify(pv[1 + i], av[1 + i], bs)) return 0; i++; } + return 1; + } + return compat(p, a); +} +// print a type the way it is written in source. function pointers use an alias +// for source spelling; array pointers use T[N]*. +int tprint_dimensions(word t) { + while (t[0] == TY_ARR || t[0] == TY_DYN) { rt.put_c('['); if (t[0] == TY_DYN) rt.put_c('?'); else if (t[3] >= 0) rt.put_n(t[3]); rt.put_c(']'); t = t[4]; } + return 0; +} +word tprint(word t) { + if (treadonly(t)) { rt.put_s("const "); return tprint(tunconst(t)); } + int k = t[0], i; word v; + if (k == TY_PTR) { // postfix pointers; only function pointers keep the display-only prefix form + v = t[4][0]; + if (v == TY_FN) { rt.put_c('*'); return tprint(t[4]); } + tprint(t[4]); rt.put_c('*'); + return 0; + } + if (k == TY_ARR || k == TY_DYN) { if (k == TY_DYN && t[3]) rt.put_s("fixed "); v = t; while (v[0] == TY_ARR || v[0] == TY_DYN) v = v[4]; tprint(v); return tprint_dimensions(t); } + if (k == TY_FN) { + if (t[4] == tword) rt.put_s("fn"); else tprint(t[4]); + if (t[5] & 1) rt.put_s(".u"); + rt.put_c('('); + i = 0; + while (i < t[3]) { + if (i) rt.put_s(", "); + tprint(t[7 + i]); + if (i < 12 && tparamtag(t, i) == 1) rt.put_s(".m"); + if (i < 12 && tparamtag(t, i) == 2) rt.put_s(".d"); + i++; + } + rt.put_c(')'); + return 0; + } + if (t[2]) rt.put_s(t[2]); else rt.put_s("?"); + if (k == TY_STRUCT && t[5]) { + v = t[6]; rt.put_c('<'); + i = 0; while (i < v[0]) { if (i) rt.put_s(", "); tprint(v[1 + i]); i++; } + rt.put_c('>'); + } + return 0; +} +// typechecker errors. they only fire when "typecheck on" is set, in reachable +// code, and never during a dry run. +int tcerr(int what, int want, int got) { + if (!tc_on || !live || dry || panic) return 0; + errat(); rt.put_s("type error: "); rt.put_s(what); + rt.put_s(": expected "); tprint(want); rt.put_s(", got "); tprint(got); + errend(); + return 0; +} +// report a type error when ok is false, showing the type it got. +int tcneed(int ok, int what, int got) { + if (ok || !tc_on || !live || dry || panic) return 0; + errat(); rt.put_s("type error: "); rt.put_s(what); rt.put_s(", got "); tprint(got); + errend(); + return 0; +} +// the type parameters in scope (tpn): a generic function or struct body sees +// T as a type. istypename says whether a name starts a type here, which is how +// the parser tells a declaration from an expression. +int tpfind(int n) { + int i = ntp; + while (i) { + i--; + if (tpn[i] == n) return i; + } + return -1; +} +// true when the name n is a type, a type parameter or a generic type. +word istypename(char* n) { + int s; + if (n == s_fixed || n == s_const) return 1; + if (ginst && gfind(n) >= 0) return 1; + if (tpfind(n) >= 0) return 1; + if (findloc(n) >= 0) return 0; + s = lookup(n); + return s >= 0 && skind[s] == K_TYPE; +} +// the closing > of a type argument list; splits >> in two +int closeangle() { + if (is_op(s_shr)) { tv = s_gt; return 0; } + expect(s_gt); + return 0; +} +// a type, then any number of (params) after it: int(int, int) is a function +// returning int, fn(int) one returning a plain word +// ---- parsing types ---- + +// grammar, outermost first: +// ptype a type, then any number of (params): int(int, int) is a function type +// ptype0 postfix pointers and array groups: T*, T[N], T[], T[?] +// ptype1 a type name, fixed qualifier, function base, or generic instance +// the first thing in ptype's comment applies here too: fn(int) is a function +// returning a plain word. +int ptype() { + int start = toff, t = ptype0(), np, tags, tag; word[24] ps; + while (is_op(s_lp)) { + next(); np = 0; tags = 0; + if (!is_op(s_rp)) { + while (1) { + if (np >= 24) fatal("too many parameters"); + ps[np] = ptype(); if (ps[np][0] == TY_ARR) ps[np] = decay(ps[np]); + tag = 0; + if (accept(s_dot)) { + if (tv == s_m) tag = 1; else if (tv == s_d) tag = 2; else die("callback parameter lifetime is .m or .d"); + next(); + } + tags = tags | (tag << (np + np)); + np++; + if (!accept(s_comma)) break; + } + } + expect(s_rp); + t = tfn(t, np, ps, np + np + tags * 64); + } + ht_note(start, toff, t); + return t; +} +// a type with any stars after it: int*, vec<T>*, point** +int ptype0() { + int t = ptype1(), cap = 8, n, count, j; word dims = 0; + while (is_op(s_star) || is_op(s_lk)) { + if (accept(s_star)) t = tptr(t); + else { + count = 0; + while (accept(s_lk)) { + if (accept(s_q)) { expect(s_rk); n = -2; } + else if (accept(s_rk)) n = -1; + else { n = cexpr(); expect(s_rk); if (n < 0) serr("a negative array length"); } + if (!dims || count == cap) { if (dims) cap = cap * 2; dims = chk_resize(dims, count, cap); } + dims[count] = n; count++; + } + j = count; while (j) { j--; if (dims[j] == -2) t = tdyn(t, 0); else t = tarr(t, dims[j]); } + } + } + if (dims) rt.free(dims); + return t; +} +// parse one type with no trailing function parts. +word ptype1() { + int n, s; word t; word[8] args; int na = 0, i; + if (is_op(s_lk)) { die("array types are postfix: T[N], T[] or T[?]"); return tword; } + if (is_id(s_const)) { next(); return tconst(ptype0()); } + if (is_id(s_fixed)) { // fixed T[?] can't grow; fixed T[n] says what T[n] already means + next(); t = ptype0(); + if (t[0] == TY_DYN) { if (treadonly(t)) return tconst(tdyn(t[4], 1)); return tdyn(t[4], 1); } + if (t[0] != TY_ARR) serr("only arrays can be fixed"); + return t; + } + if (is_id(s_fn)) { + next(); + if (!is_op(s_lp)) die("fn as a type needs its parameters: fn(int)"); + return tword; + } + if (tk != T_ID) { diefound("expected a type"); return tword; } + n = tv; + if (ginst) { + i = gfind(n); + if (i >= 0) { next(); return gib[i]; } + } + i = tpfind(n); + if (i >= 0) { next(); return tvar(i, n); } + s = source_lookup(n); + // T in T first<T>(...) + if (tvauto && (s < 0 || skind[s] != K_TYPE) && ntp < 8) { + tpn[ntp] = n; + lsmark(2, n); + ntp++; + next(); + return tvar(ntp - 1, n); + } + if (s < 0 || skind[s] != K_TYPE) { dien(n, "isn't a type"); return tword; } + rs_put(toff, RS_SYM, rs_sym(s), s); + next(); + t = sval[s]; + if (t[0] != TY_GEN) return t; + expect(s_lt); + while (1) { + if (na >= 8) die("too many type arguments"); + args[na] = ptype(); + if (args[na] == tvoid) serr("a type argument can't be void"); + na++; + if (!accept(s_comma)) break; + } + closeangle(); + if (na != t[3]) serrn(n, "wrong number of type arguments"); + return tinst(t, args, na); +} +// does a declaration start here? (a type, or a bare .tag) +// does a declaration start at the current token? (a type name, [, or a bare .tag) +int tstart() { + if (tk == T_OP) { + if (tv == s_lk || tv == s_dot) return 1; + return 0; + } + if (tk != T_ID) return 0; + if (tv == s_fn) return 1; + if (!istypename(tv)) return 0; + // Type.name(...) at the start of a statement is a static call, not a declaration: + // a declaration's dot is followed by a tag, u, m or d + peekat(0); + if (pk_tk == T_OP && pk_tv == s_dot) { + peekat(1); + if (pk_tk == T_ID && pk_tv != s_u && pk_tv != s_m && pk_tv != s_d) return 0; + } + return 1; +} +// [type] [.tag] +// parse a declaration's [type] [.tag] into dty and dtag +int dtype() { + dty = 0; dtag = 0; + if (!is_op(s_dot)) dty = ptype(); + if (accept(s_dot)) { + if (tv == s_m) { + dtag = S_M; + } else if (tv == s_u) { + dtag = S_U; + } else if (tv == s_d) dtag = S_D; else die("a tag is .u, .m or .d"); + next(); + } + return 0; +} +// the struct whose body or methods are being compiled, when it declares constants: a bare +// name there is looked up as Struct.name before the global scope +char* mstruct; +// does a constant declaration start in a struct body? const NAME = e; const T NAME = e; +// (a field of a const type has no initializer, so the = tells them apart) +int struct_const_start() { + if (!is_id(s_const)) return 0; + peekat(0); if (pk_tk != T_ID) return 0; + peekat(1); + if (pk_tk == T_OP && pk_tv == s_eq) return 1; + if (pk_tk == T_ID) { peekat(2); return pk_tk == T_OP && pk_tv == s_eq; } + if (pk_tk == T_OP && pk_tv == s_star) { peekat(2); if (pk_tk != T_ID) return 0; peekat(3); return pk_tk == T_OP && pk_tv == s_eq; } + return 0; +} +// const NAME = e, NAME = e; inside struct sn: a compile-time constant named Struct.NAME that takes no +// space in the layout. the optional type is read and dropped, as in gconst. +int struct_const(char* sn) { + int off, s; char* cn; + next(); + if (tk == T_ID) { peekat(0); if (pk_tk == T_ID || (pk_tk == T_OP && pk_tv == s_star)) ptype(); } + mstruct = sn; + while (1) { + off = toff; cn = name(); + s = lookup(methname(sn, cn)); + if (s >= ubase && s >= 0) serrn(cn, "is defined twice in this struct"); + expect(s_eq); + s = addsym(methname(sn, cn), K_CONST, cexpr()); + rs_put(off, RS_SYM, rs_sym(s), s); + if (!is_op(s_comma) && !is_op(s_semi)) { // a call or a variable's value follows what cprim could read + serr("a struct constant must be a constant expression"); + while (!is_op(s_semi) && !is_op(s_rb) && tk != T_EOF) next(); + break; + } + if (!accept(s_comma)) break; + } + accept(s_semi); + return 0; +} +// struct name { type field, ... } or struct name<A, B> { ... } +// the struct keyword. fields go in a scratch buffer first (name, type pairs), +// then are laid out: each field takes tsz(type) words, so offsets are words. +// methods are collected as token lists while the struct is read (grabfn) and +// compiled only when the struct is complete, so a method can use every field +// and call its siblings. a generic struct keeps its fields unresolved and +// can't have methods; each instance is laid out later by tresolve. +int kw_struct() { + char* n; word t; int s, nf = 0; word buf = chk_resize(0, 0, 64); int bufcap = 64, i; word fs; int off = 0, gen = 0, ft, nm = 0, mcap = STRUCT_METHODS; char** mn = chk_resize(0, 0, mcap); word mq = chk_resize(0, 0, mcap), mst = chk_resize(0, 0, mcap), mty = chk_resize(0, 0, mcap), mrt = chk_resize(0, 0, mcap), mrg = chk_resize(0, 0, mcap); int bo, k, stat = 0; char* fname = 0; char* msv = mstruct; + next(); + n = module_symbol(name()); + if (is_op(s_lt)) { // generic + next(); ntp = 0; + while (1) { + if (ntp >= 8) die("too many type parameters"); + tpn[ntp] = name(); + lsmark(2, tpn[ntp]); + ntp++; + if (!accept(s_comma)) break; + } + closeangle(); + gen = 1; + t = tnew(TY_GEN, 0, n, 0); t[3] = ntp; + fs = talloc(ntp); rt.copy_w(fs, tpn, ntp); t[6] = fs; + } else { t = tnew(TY_STRUCT, 0, n, 0); t[3] = -2; } // -2 while its fields are read + s = addsym(n, K_TYPE, t); + expect(s_lb); + while (!is_op(s_rb)) { + if (tk == T_EOF) { die("missing }"); break; } + stat = 0; + if (struct_const_start()) { struct_const(n); continue; } + if (is_id(s_unchecked)) { next(); stat = 2; } + if (is_id(s_static)) { // static fn name(params) { body }, or static T name(params) { body }: a function of the struct, called as Struct.name(args), with no this + next(); + stat = stat | 1; + } + if (is_id(s_fn)) { // fn name(params) { body }: a method, compiled once the struct is complete + next(); + if (nm >= mcap) { + mcap = mcap * 2; mn = chk_resize(mn, nm, mcap); mq = chk_resize(mq, nm, mcap); + mst = chk_resize(mst, nm, mcap); mty = chk_resize(mty, nm, mcap); + mrt = chk_resize(mrt, nm, mcap); mrg = chk_resize(mrg, nm, mcap); + } + mn[nm] = name(); + mst[nm] = stat; + mq[nm] = grabfn(); + mty[nm] = 0; + k = methdecl(n, mn[nm]); + if (stat & 1) sflag[k] = sflag[k] | F_STATIC; + nm++; + continue; + } + // let x, y; fields are plain words + fname = 0; + if (is_id(s_var)) { + if (stat & 1) die("static must be followed by fn or a typed method"); + next(); + ft = tword; + } else { + // T name(params) { body } is a typed method; T name, ...; is a typed field + rettype(); + fname = name(); + if (is_op(s_lp)) { + if (nm >= mcap) { + mcap = mcap * 2; mn = chk_resize(mn, nm, mcap); mq = chk_resize(mq, nm, mcap); + mst = chk_resize(mst, nm, mcap); mty = chk_resize(mty, nm, mcap); + mrt = chk_resize(mrt, nm, mcap); mrg = chk_resize(mrg, nm, mcap); + } + mn[nm] = fname; + mst[nm] = stat; + mq[nm] = grabfn(); + mty[nm] = 1; mrt[nm] = dty; mrg[nm] = dtag == S_U; + if (dtag && dtag != S_U) serr("the only return tag is .u"); + k = methdecl(n, fname); + if (stat & 1) sflag[k] = sflag[k] | F_STATIC; + nm++; + continue; + } + if (stat & 1) die("static must be followed by fn or a typed method"); + if (dtag) serr("a field has no tag"); + ft = dty; + } + while (1) { + if ((nf + 1) * 2 > bufcap) { buf = chk_resize(buf, nf * 2, bufcap * 2); bufcap = bufcap * 2; } + if (fname) { buf[nf + nf] = fname; fname = 0; } else buf[nf + nf] = name(); + lsmark(3, buf[nf + nf]); buf[nf + nf + 1] = ft; + field_note(t, buf[nf + nf], nameoff); lt_note(nameoff, ft); rs_put(nameoff, RS_FIELD, nameoff, 0); nf++; + if (!accept(s_comma)) break; + } + expect(s_semi); + } + next(); + if (gen) { + fs = talloc(nf + nf); rt.copy_w(fs, buf, nf + nf); + t[4] = fs; t[5] = nf; + mdg = ntp; rt.copy_w(mdn, tpn, ntp); + ntp = 0; + } else { + fs = talloc(nf * 3 + 1); + i = 0; + bo = 0; + while (i < nf) { + ft = buf[i + i + 1]; + if (ft == t) { serr("a struct can't contain itself by value; use a pointer"); ft = tword; } + fs[i * 3] = buf[i + i]; fs[i * 3 + 1] = ft; + // a sized field packs into the current word at its own alignment; its + // offset is recorded as minus (byte offset + 1). anything else starts a word + k = tbytes(ft); + if (k) { bo = (bo + k - 1) / k * k; fs[i * 3 + 2] = 0 - bo - 1; bo = bo + k; } + else { bo = (bo + 7) / 8 * 8; fs[i * 3 + 2] = bo / 8; bo = bo + tsz(ft) * 8; } + i++; + } + off = (bo + 7) / 8; + t[4] = fs; t[1] = off; t[3] = nf; + } + accept(s_semi); + i = 0; + while (i < nm) { fn_method_compile = 1; methdef(n, mn[i], mq[i], mst[i], mty[i], mrt[i], mrg[i]); fn_method_compile = 0; i++; } + mdg = 0; + mstruct = msv; + return s; +} +// ---- methods ---- + +// a method is an ordinary function named Struct.name whose first parameter is +// this, a pointer to the struct. e.method(a) calls Struct.name(e, a). the +// receiver's struct comes from its type, or, for an untyped word, from the +// one struct that has a method of that name. +// the symbol for a method is the interned string "Struct.name" +char* methname(char* sn, char* mn) { + char* b = chk_resize(0, 0, rt.len_w(sn) + rt.len_w(mn) + 2); int i = 0, j = 0; + while (sn[j]) { b[i] = sn[j]; i++; j++; } + b[i] = '.'; i++; + j = 0; + while (mn[j]) { b[i] = mn[j]; i++; j++; } + b[i] = 0; + return intern(b); +} +// the method called n on struct type t, as a symbol index, or -1. methwhich +// (below) does the untyped version: search every struct for one with that +// method, which is unique or ambiguous (-2). +int methof(word t, char* n) { + int s = lookup(methname(t[2], n)); + if (s >= 0 && skind[s] == K_FN) return s; + return 0 - 1; +} +// the one method called n, -1 for none, -2 for several structs having one +int methscan(char* n) { + int i = 0; word t; int s, hit = 0 - 1; + while (i < ntl) { + t = typelist[i]; + if (t[0] == TY_STRUCT && t[3] >= 0) { + s = methof(t, n); + if (s >= 0) { if (hit >= 0) return 0 - 2; hit = s; } + } + i++; + } + return hit; +} +// declare Struct.name now, so methods can call each other in any order +// (the declaration is what makes mutual recursion between methods work: the +// symbol exists with no body yet, and calls to it go on the patch chain) +int methdecl(char* sn, char* mn) { + int nm = methname(sn, mn), s = lookup(nm); + if (s >= ubase && s >= 0 && skind[s] == K_FN) { + serrn(mn, "is defined twice in this struct"); + return s; + } + s = addsym(nm, K_FN, 0); + sflag[s] = 0; + return s; +} +// the tokens of a method, from its ( to the } that closes its body, as a token list +// a method body is not parsed when the struct is read. its tokens are copied +// into a token list (kind, value, place triples) up to the matching brace, and +// methdef replays them later. the place travels with each token, so errors +// inside a method still point at the right line. +word .u grabfn() { + int cap = 768; word q = rt.new(cap); int n = 0, depth = 0; word nq; + q[0] = 0; + if (!is_op(s_lp)) { die("expected ( after the method name"); return q; } + while (tk != T_EOF) { + if (n * 3 + 4 > cap) { + nq = rt.new(cap + cap); + rt.copy_w(nq, q, cap); + rt.free(q); + q = nq; + cap = cap + cap; + } + q[1 + n * 3] = tk; q[2 + n * 3] = tv; q[3 + n * 3] = tpack(); n++; + if (is_op(s_lb)) depth++; + else if (is_op(s_rb)) { depth--; if (depth <= 0) { next(); break; } } + next(); + } + if (tk == T_EOF && depth > 0) die("missing } at the end of a method"); + q[0] = n; + return q; +} +// compile a method by feeding the parser fn Struct.name(Struct* this, rest...) +// build the token list "fn Struct.name ( Struct* this , rest..." from the +// method's saved tokens, push it as an input source and parse it as an +// ordinary top-level function. a method is therefore just a function whose +// first parameter is this; nothing else about it is special. +int tkput(word r, int k, int kind, int val, int pl) { + r[1 + k * 3] = kind; r[2 + k * 3] = val; r[3 + k * 3] = pl; + return k + 1; +} +// compile a struct method once the struct is complete. +int methdef(char* sn, char* mn, word q, int stat, int typed, int rty, int rtag) { + int n = q[0]; word r; int i, k = 0, lnp, unchecked_method = stat & 2; + stat = stat & 1; + if (!n) { rt.free(q); return 0; } + if (!stat) { + i = 1; + while (i < n && q[2 + i * 3] != s_comma && q[2 + i * 3] != s_rp) { + if (q[1 + i * 3] == T_ID && q[2 + i * 3] == s_this) serr("this is implicit and cannot be declared as a method parameter"); + i++; + } + } + r = rt.new((n + 8 + mdg * 4) * 3 + 1); + lnp = q[3]; + if (unchecked_method) k = tkput(r, k, T_ID, s_unchecked, lnp); + k = tkput(r, k, T_ID, s_fn, lnp); + r[1 + k * 3] = T_ID; r[2 + k * 3] = methname(sn, mn); r[3 + k * 3] = lnp; k++; + if (mdg) { // a method of a generic struct is a generic function over the struct's parameters: fn S.m<K, V>(S<K, V>* this, ...) + k = tkput(r, k, T_OP, s_lt, lnp); + i = 0; + while (i < mdg) { + if (i) k = tkput(r, k, T_OP, s_comma, lnp); + k = tkput(r, k, T_ID, mdn[i], lnp); + i++; + } + k = tkput(r, k, T_OP, s_gt, lnp); + } + r[1 + k * 3] = q[1]; r[2 + k * 3] = q[2]; r[3 + k * 3] = q[3]; k++; + if (stat && mdg) die("a static function can't be in a generic struct"); + if (!stat) { + r[1 + k * 3] = T_ID; r[2 + k * 3] = sn; r[3 + k * 3] = lnp; k++; + if (mdg) { + k = tkput(r, k, T_OP, s_lt, lnp); + i = 0; + while (i < mdg) { + if (i) k = tkput(r, k, T_OP, s_comma, lnp); + k = tkput(r, k, T_ID, mdn[i], lnp); + i++; + } + k = tkput(r, k, T_OP, s_gt, lnp); + } + r[1 + k * 3] = T_OP; r[2 + k * 3] = s_star; r[3 + k * 3] = lnp; k++; + r[1 + k * 3] = T_ID; r[2 + k * 3] = s_this; r[3 + k * 3] = lnp; k++; + } + if (!stat && n > 1 && !(q[1 + 3] == T_OP && q[2 + 3] == s_rp)) { + r[1 + k * 3] = T_OP; + r[2 + k * 3] = s_comma; + r[3 + k * 3] = lnp; + k++; + } + i = 1; + while (i < n) { + r[1 + k * 3] = q[1 + i * 3]; + r[2 + k * 3] = q[2 + i * 3]; + r[3 + k * 3] = q[3 + i * 3]; + k++; + i++; + } + r[0] = k; + rt.free(q); + if (typed && mdg) { + // retain a typed generic method as ordinary typed function tokens. + int mode = rt.outmode, length = rt.outn, capacity = rt.outcap, prefix = unchecked_method != 0; + word output = rt.outbuf, grow = rt.outgrow, buffer = rt.new(4096), return_tokens, joined; + rt.outmode = 2; rt.outbuf = m*buffer; rt.outn = 0; rt.outcap = 4095; rt.outgrow = 0; + tprint(rty); if (rtag) rt.put_s(".u"); buffer[rt.outn] = 0; + rt.outmode = mode; rt.outbuf = output; rt.outn = length; rt.outcap = capacity; rt.outgrow = grow; + return_tokens = tokens(buffer); + int token = 0, variable; + while (token < return_tokens[0]) { + variable = 0; + while (variable < ntl) { + word descriptor = typelist[variable]; + if (descriptor[0] == TY_VAR && descriptor[3] < mdg && return_tokens[1 + token * 3] == T_ID && return_tokens[2 + token * 3] == descriptor[2]) return_tokens[2 + token * 3] = mdn[descriptor[3]]; + variable++; + } + token++; + } + joined = rt.new((r[0] + return_tokens[0]) * 3 + 1); + joined[0] = r[0] - 1 + return_tokens[0]; + if (prefix) rt.copy_w(joined + 1, r + 1, 3); + rt.copy_w(joined + 1 + prefix * 3, return_tokens + 1, return_tokens[0] * 3); + rt.copy_w(joined + 1 + (prefix + return_tokens[0]) * 3, r + 1 + (prefix + 1) * 3, (r[0] - prefix - 1) * 3); + rt.free(r); r = joined; rt.free(return_tokens); rt.free(buffer); + } + src_push(1, r, 0, 1); + next(); + if (typed && !mdg) { + // a typed method: skip the fn and the name, then compile from the ( with the declared return type + next(); + next(); + ifkp = tpack(); ifkk = 0 - 1; if (isp != 0) ifkk = 0; + o3nxt = 0; ntp = 0; + fndone(fndef(1, rty, rtag, methname(sn, mn)), 0); + } else topitem(); + if (tk != T_EOF) die("unexpected tokens after a method"); + src_pop(); + return 0; +} +// the call e.m(args): the receiver is on the stack and becomes this +// e.m(args): by now the receiver is on the stack, so it simply becomes the +// first argument. the call is checked against the method's signature if it +// has been compiled, and its result class is fresh if the method returns .u. +int methcall(int s, word rst, word receiver_fact, int receiver, word receiver_mask) { + af_forget(); + int np, tags = 0, ret = 0, temporary, owner, aliases, i; word ft = stype[s]; word[8] bs; word owner_fact, origins; + word requirements = effect_requirements, edges = effect_edges; int original = s; + effect_args_mask(s, 0, receiver_mask); + if (receiver_fact && (receiver_fact[2] == 4 || receiver_fact[2] == 5)) effect_require(s, 0, callpos); + if (receiver >= 0 && tracked(lroot(receiver))) effect_require_mode(s, 0, callpos, 1); + if (sflag[s] & F_SIG) { tags = ssig[s] >> 6; ret = ssig[s] & 1; } + else sflag[s] = sflag[s] | F_UNTYPED; + if (sflag[s] & F_RETOWN) ret = 1; + rt.fill_w(bs, 0, 8); + if (ft && rst) { + word actual = rst; if (rst[0] == TY_STRUCT) actual = tptr(rst); + if (!unify(ft[7], actual, bs)) tcerr("receiver", subst(ft[7], bs), actual); + } + ifcall = s; effect_argument_base = 1; + np = args(tags >> 2, ft, bs, 0) + 1; + temporary = argtemporary || (receiver_fact && receiver_fact[2] == 5); owner = argowner; owner_fact = argowner_fact; origins = argorigin_mask; + if (effect_get(s, 0) & (EF_ALIAS | EF_IDENTITY | EF_UNKNOWN)) { + origins = origins | receiver_mask; + if (owner < 0) { owner = receiver; owner_fact = receiver_fact; } + else if (owner != receiver) { owner = -1; owner_fact = 0; } + } + if ((sflag[s] & F_SIG) && np != ((ssig[s] >> 1) & 31)) serrn(sname[s], "wrong number of arguments"); + if (sflag[s] & F_GEN) { s = ginstance(s, bs); effect_specialize(original, s, requirements, edges); } + fnref(s); op(O_CALL); + lv = 0; + lvk = LV_NONE; + xzero = 0; + xbor = -1; + xconst = 0; + xfloat = 0; + xstr = 0; + xlenloc = -1; + xstamp = 0; + xty = tword; + if (ft) xty = subst(ft[4], bs); + if (isfl(xty)) xfloat = 1; + effect_value = 0; + if (!ret && (isptr(xty) || xty == tword)) { + effect_value = origins; + if (isptr(xty)) xbor = owner; + if (temporary) { aliases = 0; i = 0; while (i < np) { aliases = aliases | effect_get(s, i); i++; } if (aliases & (EF_ALIAS | EF_UNKNOWN)) xaf = af_new(-1, 5, callpos); } + } + if (ret) return C_FRESH; + return C_PLAIN; +} +// type name = T; +// the type keyword makes an alias, which is also how a pointer to an array or +// function type gets a name. +int kw_type() { + int n; + next(); + n = name(); + expect(s_eq); + addsym(n, K_TYPE, ptype()); + expect(s_semi); + return 0; +} +// does a parameter start with a type? a type name straight before , or ) is the parameter's +// own name (fn f(string)), not a type, since a typed parameter always has a name after it. +int paramstart() { + if (tk == T_OP) return tv == s_lk; + if (tk != T_ID) return 0; + if (tv == s_fn) return 1; + if (!istypename(tv)) return 0; + peek(); + return !(pk_tk == T_OP && (pk_tv == s_comma || pk_tv == s_rp)); +} +// does a typedef start here? typedef followed by a name is one, so typedef can still be +// a variable or a function's name. +int typedef_ahead() { + if (tk != T_ID || tv != s_typedef) return 0; + peekat(0); + return pk_tk == T_ID; +} +// unlink the symbol s from its name, leaving the rest of the name's chain as it was. +int td_unlink(int s) { + char* nm = sname[s]; int i = nm[-1] - 1; + if (i == s) { nm[-1] = snext[s]; return 0; } + while (i >= 0 && snext[i] - 1 != s) i = snext[i] - 1; + if (i >= 0) snext[i] = snext[s]; + return 0; +} +// end the scope of the local typedefs made at block depth depth or deeper in this +// compile context (and of any made in a context nested inside it). +int td_end(int depth) { + int k; + while (ntd) { + k = ntd * 3 - 3; + if (tdstk[k + 2] < ctxsp || (tdstk[k + 2] == ctxsp && tdstk[k + 1] < depth)) break; + td_unlink(tdstk[k]); + ntd--; + } + return 0; +} +// is the name q (as a symbol is named) already a typedef of the block being compiled? +int td_here(char* q) { + int k = ntd; + while (k) { + k--; + if (tdstk[k * 3 + 2] != ctxsp || tdstk[k * 3 + 1] != fdepth) break; + if (sname[tdstk[k * 3]] == q) return 1; + } + return 0; +} +// typedef T name; +// the C spelling of type name = T;. name becomes another way to write T: the very same +// type record, so the two are interchangeable everywhere. at the top level the name is +// global; in a function it lasts to the end of its block, like a local. a name that is +// already in use is an error, except the same type under the same name again. a tag +// (.u, .m, .d) belongs to a declaration, not to a type, so a typedef has none. +int kw_typedef() { + char* n; char* q; word t; int s, at, clash = 0, local = infn; + next(); + t = ptype(); + if (panic) return 0; + if (is_op(s_dot)) { die("a typedef names a type, which has no tag: .u, .m and .d go on a declaration"); return 0; } + at = toff; n = name(); + if (panic) return 0; + q = module_symbol(n); + if (local) { + s = findloc(n); + if ((s >= 0 && lhome[s] >= fdepth) || td_here(q)) clash = 1; + } else { + s = lookup_raw(q); + if (s < 0 && module_name) { s = lookup_raw(n); if (s >= 0 && skind[s] != K_TYPE && skind[s] != K_KW) s = 0 - 1; } + if (s >= 0 && skind[s] == K_TYPE && sval[s] == t) return expect(s_semi); // the same typedef again + if (s >= 0) clash = 1; + } + if (clash) serrn(n, "already defined"); + else { + s = addsym(n, K_TYPE, t); + rs_put(at, RS_SYM, rs_sym(s), s); + if (local) { + if (ntd == tdcap) { if (tdcap) tdcap = tdcap * 2; else tdcap = 16; tdstk = chk_resize(tdstk, ntd * 3, tdcap * 3); } + tdstk[ntd * 3] = s; tdstk[ntd * 3 + 1] = fdepth; tdstk[ntd * 3 + 2] = ctxsp; ntd++; + } + } + expect(s_semi); + return 0; +} +// (T) or (T*) ahead of the current '(', within the lookahead we have +// the parser sees ( and has to decide between a parenthesized expression and a +// cast. a cast is ( type stars ) so peek ahead through the stars to the ). +int caststart() { + int k; + peek(); + if (pk_tk == T_ID && (istypename(pk_tv) || pk_tv == s_fn)) { + k = 1; peekat(k); + if (pk_tk == T_OP && (pk_tv == s_lk || pk_tv == s_lt || (pk_tv == s_lp && pq_tv[0] == s_fn))) return 1; + while (pk_tk == T_OP && pk_tv == s_star && k < 3) { k++; peekat(k); } // (int*)x, (node**)x + return pk_tk == T_OP && pk_tv == s_rp; + } + return 0; +} |
