diff options
Diffstat (limited to 'src/compiler/expr.zc')
| -rw-r--r-- | src/compiler/expr.zc | 1912 |
1 files changed, 1912 insertions, 0 deletions
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; +} + |
