// ============================================================================ // 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 int IHN = 0x8000; const word R_IH = 0x90100; const word R_POOL = 0x98100; const word R_POOLEND = 0xD8100; const int NS = 0x4000; const word R_SYM = 0xD8200; const word sname_BASE = 0xD8200; const word skind_BASE = 0xDC200; const word sval_BASE = 0xE0200; const word ssig_BASE = 0xE4200; const word sflag_BASE = 0xE8200; const word sfix_BASE = 0xEC200; const word snext_BASE = 0xF0200; const int NL = 0x200; const word lname_BASE = 0xF4200; const word lslot_BASE = 0xF4400; const word lhome_BASE = 0xF4600; const word lstate_BASE = 0xF4800; const word lowner_BASE = 0xF4A00; const word lused_BASE = 0xF4C00; const word larr_BASE = 0xF4E00; const word lown_BASE = 0xF5000; const int NF = 0x8000; const word fsite_BASE = 0xF5200; const word fnext_BASE = 0xFD200; const word R_SNAP_BASE = 0x105200; const word R_QBUF_BASE = 0x109200; const word R_SRC_BASE = 0x10D200; const word R_SEQ_BASE = 0x10D600; const word bname_BASE = 0x10DE00; const word bprec_BASE = 0x10DE40; const word bseq_BASE = 0x10DE80; const word blen_BASE = 0x10DEC0; const word uname_BASE = 0x10DF00; const word useq_BASE = 0x10DF20; const word ulen_BASE = 0x10DF40; const word aname_BASE = 0x10DF60; const word abin_BASE = 0x10DFA0; const word kops_BASE = 0x10E000; const word R_CTX_BASE = 0x10E200; const word R_LOOP_BASE = 0x10EA00; const word R_ASM = 0x10EE00; const int ASM_LABELS = 0x40; const int ASM_REFS = 0x100; const int ASM_WORDS = 0x400; const int EXIT_WORDS = 0xA00; const int ENUM_MEMBERS = 0x80; const int TAGGED_ENUMS = 0x10; const int STRUCT_METHODS = 0x20; const int GENERATED_WORDS = 0x2000; const int RANGE_FACTS = 0x20; const int JOIN_PATHS = 0x40; const int O3_REGIONS = 0xA; // @end-map // table bases start in the generated map; growth copies columns without changing indices. word sname = sname_BASE; word skind = skind_BASE; word sval = sval_BASE; word ssig = ssig_BASE; word sflag = sflag_BASE; word sfix = sfix_BASE; word snext = snext_BASE; word lname = lname_BASE; word lslot = lslot_BASE; word lhome = lhome_BASE; word lstate = lstate_BASE; word lowner = lowner_BASE; word lused = lused_BASE; word larr = larr_BASE; word lown = lown_BASE; word fsite = fsite_BASE; word fnext = fnext_BASE; int symcap = NS, loccap = NL, fixcap = NF; const word R_TY = ZFP - 16384, R_TYEND = ZFP; // interned text and type records never move: only their index vectors grow. word namehash = R_IH, 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; [60]word lpfact_initial; word lpfact = lpfact_initial; int loopfacts_cap = 60; // operator columns grow independently of their initial map reservations. word bname = bname_BASE; word bprec = bprec_BASE; word bseq = bseq_BASE; word blen = blen_BASE; word uname = uname_BASE; word useq = useq_BASE; word ulen = ulen_BASE; word 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; 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 [1100]word tbuf_initial; word 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 [2100]word dbuf_initial; word dbuf = dbuf_initial; int docbufcap = 2100; int dn, dlast, dline, docpending; [2100]word dsnap_initial; word dsnap = dsnap_initial; int docsnapcap = 2100; int dsn, dnm; [300]word dnmb_initial; word dnmb = dnmb_initial; int docnamecap = 300; int pk_tk, pk_tv, pq_n; [4]word pq_tk; [4]word pq_tv; [4]word pq_gl; [4]word pq_ln; // up to 4 tokens of lookahead int here, hdelta, hlimit, ptop; int code_chunk = 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; int finsym, finfer; // the function being compiled, and whether its owned return is still to be inferred (no .u) int stubgen; // 1 while an extern's generated stub is compiled: the stub gets no hints 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_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; 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; int ckst, 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; int ifrcap = NIF, ifxcap = IFN, iftcap = IFT; int fldst, fldi; // the one struct a bare address's field name resolved to [8]word tpn; int ntp; // type parameters in scope // reading a return type: unknown names are type parameters declared after the function's name int tvauto; int dty, dtag; // result of dtype() 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_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; [32]word o3sl; [32]word o3rg; int no3sl; [O3_REGIONS * 67]word o3pend_initial; word o3pend = o3pend_initial; int o3pendcap = O3_REGIONS, no3pend; [80]word o3dsc; int o3fs, o3na, o3fn0; // o3free holds registers released by closed scopes; o3cache holds the items pointer and length slots for fixed arrays. [8]word o3free; int no3free, o3nreg; [NL]word o3cache_initial; word o3cache = o3cache_initial; int argloc, s_hitems, s_hlen, s_fatten; // operator names used by numeric type checks. [12]word 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; [RANGE_FACTS]word 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; [8]word mdn; // the type parameters of the generic struct whose methods are being compiled [8]word gbind; int ngbind; // explicit type arguments for the next call: f(...) // 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; [8]word gnew_nm; // a generic function was just defined word gtab; int gt_n, gtcap = 64, gicap = 128; word gt_sym, gt_tok, gt_np; 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; [8]word gnm; [8]word gib; int gng, ginsn; // compiling an instance: the names and what they stand for int s_this; // 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 const int NCLO = 8, NCAP = 32; int nclo, closure_cap = NCLO, capture_width = NCAP; [NCLO]word clo_lo_initial; word clo_lo = clo_lo_initial; [NCLO]word clo_hi_initial; word clo_hi = clo_hi_initial; [NCLO]word clo_n_initial; word clo_n = clo_n_initial; [NCLO]word clo_env_initial; word clo_env = clo_env_initial; [NCLO]word clo_ret_initial; word clo_ret = clo_ret_initial; [256]word clo_name_initial; word clo_name = clo_name_initial; [256]word clo_loc_initial; word clo_loc = clo_loc_initial; [256]word 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; [NL]word lpos_initial; word lpos = lpos_initial; [NL]word lnsz_initial; word lnsz = lnsz_initial; [NL]word lleaf_initial; word lleaf = lleaf_initial; [NL]word lrsoff_initial; word lrsoff = lrsoff_initial; [NL]word lrskind_initial; word lrskind = lrskind_initial; int newsz, newleaf, nomagic, callpos; [NL]word 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. [NL]word lafact_initial; word lafact = lafact_initial; [NL]word laaddr_initial; word laaddr = laaddr_initial; word xaf, argfact; // local name lookup keeps a head for each spelling and a shadow link per slot. [NL]word lprev_initial; word lprev = lprev_initial; [256]word lkeys_initial; word lkeys = lkeys_initial; [256]word 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 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 + outn, dgcap * 2); dgcap = dgcap * 2; outbuf = dgbuf + dgpos + 5; 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. int dg_close() { if (!dgopen) return 0; dgbuf[dgpos + 4] = outn; dgpos = dgpos + 5 + outn; dgn++; dgopen = 0; outmode = dgmode; 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; outbuf = dgbuf + dgpos + 5; outn = 0; outcap = dgcap - dgpos - 5; dggrow = outgrow; outgrow = dg_expand; outmode = 2; dgopen = 1; return 0; } [256]word 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 = outmode; 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; 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; } // start a check-mode hint at token position p, or say no when hints are off. int hint_begin(int p) { int sv, ok; if (!cmode || dry || panic || chkabort || stubgen) return 0; sv = tpack(); tunpack(p); ok = !dg_dup(); if (ok) { dg_open(4); 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 { put_s("zc: line "); put_n(tline); 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; } put_c(10); if (nerr >= 20) { put_s("zc: too many errors, stopping\n"); halt(); } return 0; } // stop compiling after errors and report how many there were. int giveup() { if (cmode) { dg_close(); chkabort = 1; return 0; } put_s("zc: "); put_n(nerr); if (nerr == 1) put_s(" error\n"); else put_s(" errors\n"); halt(); return 0; } // report a fatal error and stop, or record it in check mode. int fatal(int msg) { errat(); put_s(msg); if (cmode) { dg_close(); chkabort = 1; return 0; } put_c(10); halt(); return 0; } // report a syntax error at the current token, once per recovery. int serr(int msg) { if (!panic && !dry) { errat(); put_s(msg); errend(); } return 0; } // report an error that names something, then the message. int serrn(int name, int msg) { if (!panic && !dry) { errat(); put_s(name); put_s(": "); put_s(msg); errend(); } return 0; } // report an error and skip to the next top-level item. int die(int 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) { put_s("the end of the file"); return 0; } if (tk == T_NUM) { put_s("the number "); put_n(tv); return 0; } if (tk == T_STR) { put_s("a string"); return 0; } if (tk == T_BSTR) { put_s("a byte string"); return 0; } if (tk == T_FLT) { put_s("a float"); return 0; } put_s("'"); put_s(tv); put_s("'"); return 0; } // like die, and says what was found instead int diefound(int msg) { if (!panic && !dry) { errat(); put_s(msg); 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(int name, int 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 = 9; 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, fieldnames, fieldoffs; int field_note(word t, word 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, word n) { int k = 0; while (k < fieldn) { if (fieldtypes[k] == t && fieldnames[k] == n) return fieldoffs[k]; k++; } return 0 - 1; } word rsrec, rstmp; [32]word pnoffs; int rsn, rscap, rsbase, nameoff; // resize check-mode tables; their global pointers keep them visible to gc. unchecked word chk_resize(word old, int count, int capacity) { word p = new(capacity); p[-4] = p[-4] | F_INTERNAL; fill_w(p, 0, capacity); if (old && count) 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 (ifs) ifs = chk_resize(ifs, symcap, 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); 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); lafact = chk_resize(lafact, nloc, cap); laaddr = chk_resize(laaddr, nloc, cap); lprev = chk_resize(lprev, 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; fill_w(rsrec + at + 4, 0, RS_W - 4); rsn++; 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) rsrec[i * RS_W + 2] = rs_sym(rsrec[i * RS_W + 3]); 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) { 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(int msg) { if (fchk && live) serr(msg); return 0; } // report an analyzer error about local i, named in the message. int cerrn(int i, int msg) { if (fchk && live) 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(word 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; word p, 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. word intern(word 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] && eq_w(p, s)) return p; h = (h + 1) & (hashcap - 1); } n = 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; 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. int addsym(word name, int kind, int val) { int i = nsym; if (nsym >= symcap) reserve_symbols(nsym + 1); nsym++; sname[i] = name; skind[i] = kind; sval[i] = val; ssig[i] = 0; sflag[i] = F_DEF; sfix[i] = 0; stype[i] = 0; snext[i] = name[-1]; name[-1] = i + 1; return i; } // while an image builds, names below vislo are hidden, except keywords int lookup(word name) { int i = name[-1] - 1; while (i >= 0) { if (i >= vislo || skind[i] == K_KW || skind[i] == K_TYPE) return i; i = snext[i] - 1; } return -1; } // drop the symbols above index to, which ends a scope. int popsyms(int to) { while (nsym > to) { nsym--; sname[nsym][-1] = snext[nsym]; } return 0; } // look a name up from a plain string. word lookups(int 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] - HDR; if (syscall(9, 9, top, 0) < top) fatal("code address reservation failed"); p[0] = code_blocks; p[1] = p + 3; p[2] = top; 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 = code_blocks; if (image_active || (lo >= 0 && hi <= 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(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(int 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(int 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 (sflag[s] & F_DEF) { lit(sval[s]); return 0; } if (dry) return 0; reserve_code(1); sfix[s] = fixnew(here, sfix[s]); lit(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(int name) { int 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(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; } } return laddr(lslot[i]); } // 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; 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(word s) { int j = hole(), a; op(O_JMP); reserve_code(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 word 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 || eof()) { ch = -1; return 0; } ch = get_c() & 255; 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 // word 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]) 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 bstrlit(word s) { int j = hole(), a, n = 0, i = 0, w, k; op(O_JMP); 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); 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 || 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; nextch(); if (!nl) { while (ch >= 0 && ch != 10) nextch(); return 0; } if (dn && dlast != cl - 1) dn = 0; if (ch == 32) nextch(); while (ch >= 0 && ch != 10) { doc_char(ch); nextch(); } while (dn && (dbuf[dn - 1] == 32 || dbuf[dn - 1] == 9 || dbuf[dn - 1] == 13)) dn--; 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; if (!nl || (dn && dlast != cl - 1)) dn = 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; 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. int lex1() { int n = 0, v, dg, nl; 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 == '*') { 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; } 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 (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) 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; put_s("expected '"); put_s(s); 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(word 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(int nm, int prec, int 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; if (prec < 0) { bprec[i] = 0 - 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 = len_w(nm); while (1002 + n > tbufcap) { tbuf = chk_resize(tbuf, 0, tbufcap * 2); tbufcap = tbufcap * 2; } 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(int nm, int 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(int nm, int handler, int flags) { int s = addsym(intern(nm), K_KW, handler); sflag[s] = F_DEF | flags; return s; } // ---- locals and the analyzer's view of them ---- // 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(6); f[0] = length; f[1] = 1; f[2] = kind; f[3] = place; f[4] = 0; f[5] = -1; return f; } int af_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) { f[0] = -1; f[1] = 0; } return 0; } int af_store(int i) { if (!dry) lafact[i] = xaf; return 0; } int af_record(int off, word f) { int at; if (!f || !cmode || dry || chkind != 3 || chbase != rsbase || !rsn) return 0; at = (rsn - 1) * RS_W; if (rsrec[at] != off) return 0; 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) { 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 { put_s("zc: line "); put_n(tline); put_s(": warning: "); } tunpack(sv); return 1; } int af_warning_end() { if (cmode) dg_close(); else put_c(10); return 0; } int af_access(word f, int place) { if (!f || f[1] != 2 || !af_warning(place)) return 0; put_s("use after free: this storage's lifetime ended on line "); put_n(f[4] & 0xFFFFFF); af_warning_end(); return 0; } int af_index(word f, int place) { if (!f || f[1] == 2 || f[0] < 0 || !xconst || (xcval >= 0 && xcval < f[0])) return 0; if (!af_warning(place)) return 0; put_s("out-of-bounds access: index "); put_n(xcval); put_s(" is outside length "); put_n(f[0]); 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) af_end(f, tpack()); f = lafact[i]; if (f && fchk && lstate[i] == S_M && lown[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); 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; 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) { 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; } // 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 count read by the hints ("only // handed off once"). 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 (pow2[k] < n + 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) { laddr(lslot[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; while (i > lbase) { i--; if (lhome[i] < dep) continue; st = lstate[i]; if (st == S_M) { 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]) serrn(lname[from], "m 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, int msg) { if (!hint_begin(lpos[i])) return 0; put_s(lname[i]); 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 ((f & LDECL) && !larr[i]) { if (!(f & LREAD)) hint_name(i, " is never read"); else if ((f & LCONST) && !(f & LASG)) hint_name(i, " is never reassigned; make it a const"); 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"); 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"); } 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); 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); 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's 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(); put_s(lname[i]); put_s(": "); put_s(what); put_s(" (one path leaves it "); put_s(statename(sa)); put_s(", another "); put_s(statename(lstate[i])); 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; [JOIN_PATHS]word 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(); put_s(lname[i]); put_s(": "); put_s(what); put_s(" (one path leaves it "); put_s(statename(s0)); put_s(", another "); put_s(statename(jst(jps[bad], i))); 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(); put_s(lname[i]); put_s(": changes state inside a loop; each pass has to leave it as it found it (it went from "); put_s(statename(jst(p, i))); put_s(" to "); put_s(statename(s)); 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"); 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) 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; 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) { lstate[i] = S_DEAD; luse(i); xgive = 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) { laddr(lslot[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; bor = xbor; xbor = -1; if (!fchk) 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) { 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; if (ltag[i] & 15) lstate[i] = ltag[i] & 15; return C_PLAIN; } js = lstate[j]; 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; lstate[j] = S_DEAD; luse(j); 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 { lstate[j] = S_DEAD; luse(j); 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); emitown(i); } return C_PLAIN; } // a global, or memory the analyzer doesn't track: the collector takes it if (j >= 0) { if (lstate[j] == S_D) { lstate[j] = S_DEAD; luse(j); } else cerrn(j, "must be d to 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 (!fchk) 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; fret = 1; finfer = 0; } if (fret) { if (i >= 0) { if (lstate[i] == S_U) { lstate[i] = S_DEAD; luse(i); } 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); 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; } // ---- 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. unchecked int talloc(int n) { int p = tytop; if (tytop + n > tyend) { p = new(n + 16385); p[-4] = p[-4] | F_INTERNAL; p[0] = typeblocks; typeblocks = p; p++; tytop = p; tyend = p + n + 16384; } tytop = tytop + n; 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, int 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; } // 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) 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 ((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(int 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 (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) 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(int 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; 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) 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, int 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 inside vec'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; 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; [8]word 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]; fill_w(bs, 0, 8); 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) { int k = t[0], i, n; [24]word 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_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; } // the pointer an array type turns into when used as a value. word decay(word 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 (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 (dst == src || dst == tword || src == tword) return 1; 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) { 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_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. a pointer to an array or a // function has no source spelling (T* would read as an array of pointers; give // it a type alias), so it prints with a leading star for the reader's benefit. word tprint(word t) { int k = t[0], i; word v; if (k == TY_PTR) { // T*; a pointer to an array or a function keeps the prefix form, since T* would read as an array of pointers v = t[4][0]; if (v == TY_ARR || v == TY_DYN || v == TY_FN) { put_c('*'); return tprint(t[4]); } tprint(t[4]); put_c('*'); return 0; } if (k == TY_DYN) { if (t[3]) put_s("fixed "); put_s("[]"); return tprint(t[4]); } if (k == TY_ARR) { put_c('['); put_n(t[3]); put_c(']'); return tprint(t[4]); } if (k == TY_FN) { if (t[4] == tword) put_s("fn"); else tprint(t[4]); if (t[5] & 1) put_s(".u"); put_c('('); i = 0; while (i < t[3]) { if (i) put_s(", "); tprint(t[7 + i]); if (i < 12 && tparamtag(t, i) == 1) put_s(".m"); if (i < 12 && tparamtag(t, i) == 2) put_s(".d"); i++; } put_c(')'); return 0; } if (t[2]) put_s(t[2]); else put_s("?"); if (k == TY_STRUCT && t[5]) { v = t[6]; put_c('<'); i = 0; while (i < v[0]) { if (i) put_s(", "); tprint(v[1 + i]); i++; } 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(); put_s("type error: "); put_s(what); put_s(": expected "); tprint(want); 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(); put_s("type error: "); put_s(what); 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(int n) { int s; if (n == s_fixed) 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 a type with its postfix stars: int*, vec*, point** // ptype1 a base: [n]T, []T, fixed []T, a type name, or a 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; [24]word ps; while (is_op(s_lp)) { next(); np = 0; if (!is_op(s_rp)) { while (1) { if (np >= 24) fatal("too many parameters"); ps[np] = ptype(); np++; if (!accept(s_comma)) break; } } expect(s_rp); t = tfn(t, np, ps, np + np); } ht_note(start, toff, t); return t; } // a type with any stars after it: int*, vec*, point** int ptype0() { int t = ptype1(); while (is_op(s_star)) { next(); t = tptr(t); } return t; } // parse one type with no trailing function parts. word ptype1() { int n, s; word t; [8]word args; int na = 0, i; if (accept(s_lk)) { if (accept(s_rk)) { // []T: grows t = ptype0(); return tdyn(t, 0); } n = cexpr(); expect(s_rk); t = ptype0(); if (n < 0) serr("a negative array length"); return tarr(t, n); } if (is_id(s_fixed)) { // fixed []T can't grow; fixed [n]T says what [n]T already means next(); t = ptype0(); if (t[0] == TY_DYN) 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 = lookup(n); // T in T first(...) 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; } 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; } // struct name { type field, ... } or struct name { ... } // 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() { int 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; word mn = chk_resize(0, 0, mcap), 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, fname = 0; next(); n = 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); 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 (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 = 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) sflag[k] = sflag[k] | F_STATIC; nm++; continue; } // let x, y; fields are plain words fname = 0; if (is_id(s_var)) { if (stat) 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) sflag[k] = sflag[k] | F_STATIC; nm++; continue; } if (stat) 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); copy_w(fs, buf, nf + nf); t[4] = fs; t[5] = nf; mdg = ntp; 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) { methdef(n, mn[i], mq[i], mst[i], mty[i], mrt[i], mrg[i]); i++; } mdg = 0; 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" word methname(word sn, word mn) { word b = chk_resize(0, 0, len_w(sn) + 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, int 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(int 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(int sn, int 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. unchecked word grabfn() { int cap = 768; word q = 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 = new(cap + cap); copy_w(nq, q, cap); 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. unchecked 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. unchecked int methdef(int sn, int mn, word q, int stat, int typed, int rty, int rtag) { int n = q[0]; word r; int i, k = 0, lnp; if (!n) { free(q); return 0; } r = new((n + 8 + mdg * 4) * 3 + 1); lnp = q[3]; r[1] = T_ID; r[2] = s_fn; r[3] = lnp; k = 1; 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(S* 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; free(q); src_push(1, r, 0, 1); next(); if (typed) { // a typed method: skip the fn and the name, then compile from the ( with the declared return type if (mdg) die("a typed method of a generic struct is not supported yet"); 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) { af_forget(); int np, tags = 0, ret = 0; word ft = stype[s]; [8]word bs; 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; fill_w(bs, 0, 8); if ((sflag[s] & F_GEN) && ft && rst && rst[0] == TY_STRUCT) unify(ft[7], tptr(rst), bs); // a method of a generic struct: the receiver says what its parameters are np = args(tags >> 2, 0, bs, 0) + 1; 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); 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; 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; } // (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); 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(int 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 [GENERATED_WORDS]word ebuf_initial; word ebuf = ebuf_initial; int ebcap = GENERATED_WORDS, ebn; [ENUM_MEMBERS]word pm_name_initial, pm_en_initial, pm_tag_initial, pm_nf_initial, pm_val_initial, pm_exp_initial; word pm_name = pm_name_initial, pm_en = pm_en_initial, 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; [ENUM_MEMBERS * 8]word pm_fnm_initial, pm_ft_initial, pm_off_initial; word pm_fnm = pm_fnm_initial, pm_ft = pm_ft_initial, pm_off = pm_off_initial; [TAGGED_ENUMS]word pe_st_initial, pe_first_initial, pe_n_initial, pe_name_initial; word pe_st = pe_st_initial, pe_first = pe_first_initial, pe_n = pe_n_initial, 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 int 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(int 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() { int n, en = 0, first = npm, cnt = 0, tagged = 0, gi, nf, v = 0, et = 0, sk, ft, 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(int en, int first, int cnt) { int gi, j, nf, w = 1; word buf; 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 = 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 = new(ebn + 2); copy_w(buf, ebuf, ebn + 1); src_push(2, buf, 0, 0); next(); stubgen = 1; while (tk != T_EOF) topitem(); stubgen = 0; src_pop(); 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) { put_s("zc: line "); put_n(sl); put_s(": warning: this switch doesn't cover "); first = 0; } else put_s(", "); gi = pe_first[e] + k; put_s(pm_name[gi]); } if (!first) put_c(10); return 0; } // include "path"; reads the file and compiles it here, signatures first int kw_include() { int path, buf; stmtonly(); next(); if (tk != T_STR) { die("include needs a string"); return 0; } path = tv; next(); expect(s_semi); buf = readfile(path); if (!buf) { serr("can't read that include"); return 0; } prescan(2, buf); src_push(2, buf, 0, 0); next(); while (tk != T_EOF) topitem(); src_pop(); 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; } // 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(word nm) { int i, k = 0; word p = "doc "; doc_name_room(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 word docofs(int nm) { int s = docsym(nm); if (s < 0) return 0; return sval[s]; } // the documentation of a function, by address word 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. word doc_pack(word 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] | F_SHARE | 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; } // attach the doc comment above a declaration at name n. int doc_attach(word n) { int i = 0, w, k, c, b, nw; if (!dsn || !n || dry || ginst) return 0; b = doc_pack(dsnap, dsn); doc_name_room(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) [400]word sgb_initial; word sgb = sgb_initial; int sigcap = 400; int sig_print(word n, int rty, int np, word pn, word pty, int typed) { int i = 0; if (typed) { tprint(rty); put_c(' '); } else put_s("fn "); put_s(n); put_c('('); while (i < np) { if (i) put_s(", "); if (typed) { tprint(pty[i]); put_c(' '); } put_s(pn[i]); i++; } put_c(')'); return 0; } // attach a complete signature, measured before capture. int sig_attach(word n, int rty, int np, word 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 = outmode; sb = outbuf; sn = outn; sc = outcap; outmode = 3; outn = 0; sig_print(n, rty, np, pn, pty, typed); if (outn > sigcap) { while (outn > sigcap) sigcap = sigcap * 2; sgb = chk_resize(sgb, 0, sigcap); } outmode = 2; outbuf = sgb; outn = 0; outcap = sigcap; sig_print(n, rty, np, pn, pty, typed); sl = outn; outmode = sm; outbuf = sb; outn = sn; outcap = sc; b = doc_pack(sgb, sl); doc_name_room(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, int 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 [n]T is just n. word arrarg() { int c; expect(s_lp); c = expr(); argloc = -1; arrloc = -1; if (lv && lvk == LV_LOCAL && xty[0] == TY_DYN) { arrloc = lvi; if (o3on && o3cache[lvi]) argloc = lvi; } rval(); plainuse(c); af_access(xaf, tpack()); return xty; } // report an error when a fixed array is asked to grow or shrink. int nogrow(word t) { if (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 kw_len() { word t; int a; next(); t = arrarg(); a = arrloc; 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; } // compile cap(a): the capacity of a growable array. int kw_cap() { word t; next(); t = arrarg(); expect(s_rp); tcneed(t[0] == TY_DYN || t == tword, "cap needs a []T", t); callname(s_dcap); xty = tint; return 0; } // compile push(a, v): add an element to the end of a growable array. int kw_push() { word t; int c; next(); t = arrarg(); af_escape(xaf); nogrow(t); if (t[0] == TY_DYN && tsz(t[4]) != 1) serr("push takes one-word elements; use resize for structs"); expect(s_comma); c = expr(); rval(); 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; return 0; } if (o3on && t[0] == TY_DYN && !hasptr(t[4]) && tsz(t[4]) == 1) { pushfast(); xty = tint; xfloat = 0; return 0; } callname(s_dpush); xty = tint; return 0; } // 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); op(O_LD); 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 kw_pop() { word t; next(); t = arrarg(); af_escape(xaf); nogrow(t); 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; pkfix(xty); if (isfl(xty)) xfloat = 1; return 0; } if (o3on && t[0] == TY_DYN && 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; return 0; } // compile resize(a, n): set the length of a growable array. int kw_resize() { word t; int c; next(); t = arrarg(); af_escape(xaf); nogrow(t); expect(s_comma); c = expr(); rval(); plainuse(c); expect(s_rp); if (t[0] == TY_DYN && tbytes(t[4])) callname(intern("dresizeb")); else callname(s_dresize); xty = tint; return 0; } // 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(word 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 eq_w(w, "unsigned") || eq_w(w, "signed") || eq_w(w, "long") || eq_w(w, "short") || eq_w(w, "int") || eq_w(w, "char") || eq_w(w, "double") || eq_w(w, "float") || eq_w(w, "void") || eq_w(w, "const") || 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, int 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) { int w, uns = 0, lng = 0, shrt = 0, chr = 0, ints = 0, dbl = 0, flt = 0, vd = 0, other = 0, ptr = 0, seen = 0, k = -1, sw = 0, sk, nw; ckst = 0; ckn = 0; while (tk == T_ID) { w = tv; if (eq_w(w, "const") || eq_w(w, "volatile") || eq_w(w, "restrict")) { next(); continue; } if (eq_w(w, "struct") || eq_w(w, "enum") || eq_w(w, "union")) { next(); if (tk == T_ID) { if (eq_w(w, "struct")) sw = tv; next(); } other = 1; seen = 1; continue; } if (seen && !ctypeword(w)) break; if (eq_w(w, "unsigned")) uns = 1; else if (eq_w(w, "signed")) ints = 1; else if (eq_w(w, "long")) lng++; else if (eq_w(w, "short")) shrt = 1; else if (eq_w(w, "char")) chr = 1; else if (eq_w(w, "int")) ints = 1; else if (eq_w(w, "double") || eq_w(w, "f64")) dbl = 1; else if (eq_w(w, "float") || eq_w(w, "f32")) flt = 1; else if (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 && 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 (eq_w(other, "str")) return 12; if (eq_w(other, "size_t") || eq_w(other, "ssize_t") || eq_w(other, "intptr_t") || eq_w(other, "uintptr_t") || eq_w(other, "ptrdiff_t") || eq_w(other, "int64_t") || eq_w(other, "uint64_t") || eq_w(other, "off_t") || eq_w(other, "time_t") || eq_w(other, "word")) return 1; if (eq_w(other, "int32_t") || eq_w(other, "i32")) return 4; if (eq_w(other, "uint32_t") || eq_w(other, "u32")) return 7; if (eq_w(other, "int16_t") || eq_w(other, "i16")) return 8; if (eq_w(other, "uint16_t") || eq_w(other, "u16")) return 9; if (eq_w(other, "int8_t") || eq_w(other, "i8")) return 10; if (eq_w(other, "uint8_t") || eq_w(other, "u8") || eq_w(other, "byte") || eq_w(other, "bool") || 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. unchecked int kw_extern() { int rk; [16]word ks; [16]word kst; [16]word ksn; int np = 0, k, fname; word cname; int i, a, j, n, w, sig, bad = 0; word buf; 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 + (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("unchecked "); 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(";"); if (sret) { ebs(" "); ebs(rn); ebs("* r = new("); ebs(rn); ebs("); b[3] = r;"); } i = 0; while (i < np) { ebs(" b["); ebn10(i + 3 + sret); ebs("] = p"); ebn10(i); ebs(";"); i++; } if (sret) ebs(" syscall(14, b, 0, 0); return r; }"); else { ebs(" return syscall(14, b, 0, 0)"); if (rk == 5) ebs(" as f32"); else if (ckindfloat(rk)) ebs(" as float"); ebs("; }"); } buf = new(ebn + 2); copy_w(buf, 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(); 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 = 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; 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); 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); // 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_typecheck = intern("typecheck"); s_docs = intern("docs"); s_on = intern("on"); s_off = intern("off"); s_new = intern("new"); s_new_t = intern("new_t"); s_new_k = intern("new_k"); s_recycle_leaf = intern("recycle_leaf"); s_hitems = intern("#items"); s_hlen = intern("#len"); s_fatten = intern("fatten"); s_this = intern("this"); s_o3 = intern("o3"); s_lfa = intern("lfa"); s_outside = intern("outside"); s_new_raw = intern("new_raw"); s_oob = intern("oob"); s_new_oz = intern("new_oz"); s_ld8 = intern("ld8"); s_st8 = intern("st8"); s_ld16 = intern("ld16"); s_st16 = intern("st16"); s_ld32 = intern("ld32"); s_st32 = intern("st32"); s_fixed = intern("fixed"); s_dlen = intern("dlen"); s_dcap = intern("dcap"); s_dnew = intern("dnew"); s_dat = intern("dat"); s_len_s = intern("len_s"); s_at_s = intern("at_s"); s_slice_s = intern("slice_s"); s_cat_s = intern("cat_s"); s_eq_s = intern("eq_s"); s_lt_s = intern("lt_s"); s_le_s = intern("le_s"); s_put_bs = intern("put_bs"); s_put_s = intern("put_s"); s_n_to_s = intern("n_to_s"); s_new_s = intern("new_s"); s_dpush = intern("dpush"); s_dpop = intern("dpop"); s_dresize = intern("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 rt) { int o = bname[b]; lt = decay(lt); rt = decay(rt); if (blen[b] < 0) { tcneed(!isagg(lt) && !isagg(rt), "&& and || need scalars", lt); return tbool; } if (isfl(lt) || isfl(rt)) { if (o == s_plus || o == s_minus || o == s_star || o == s_slash) { if (lt[0] == TY_PTR || rt[0] == TY_PTR) { tcerr("+", lt, rt); 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, rt) && !compat(rt, lt)) tcerr("comparison", lt, rt); return tbool; } if (o == s_lt || o == s_gt || o == s_le || o == s_ge) { if (!(lt == tword || rt == tword || (isnum(lt) && isnum(rt)) || (lt[0] == TY_PTR && rt[0] == TY_PTR))) { tcerr("comparison", lt, rt); } return tbool; } if (o == s_plus) { if (lt[0] == TY_PTR && (isnum(rt) || rt == tword)) return lt; if (rt[0] == TY_PTR && isnum(lt)) return rt; if (lt == tword || rt == tword) return tword; if (isnum(lt) && isnum(rt)) return tint; tcerr("+", lt, rt); return tword; } if (o == s_minus) { if (lt[0] == TY_PTR && rt[0] == TY_PTR) return tint; if (lt[0] == TY_PTR && (isnum(rt) || rt == tword)) return lt; if (lt == tword || rt == tword) return tword; if (isnum(lt) && isnum(rt)) return tint; tcerr("-", lt, rt); return tword; } if (isnumop(o)) { if (lt == tword || rt == tword) return tword; if (isnum(lt) && isnum(rt)) return tint; if (isnum(lt)) lt = rt; 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. word 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) 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 || !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 (osize(p) > n) fill_w(p + n, 0, 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 works. word 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; [24]word 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(int 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 (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; // 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; if (cs >= 0 && cs < ubase) { freeing = sname[cs] == s_free || sname[cs] == s_recycle; harmless = sname[cs] == s_put_s || eq_w(sname[cs], "put_n") || eq_w(sname[cs], "put_c") || eq_w(sname[cs], "len") || eq_w(sname[cs], "cap"); } xgive = 0; ifcall = 0 - 1; expect(s_lp); if (!is_op(s_rp)) { while (1) { c = expr(); rval(); if (!np) first = xaf; if (!freeing && !harmless) af_escape(xaf); if (cs >= 0) if_arg(cs, np, xty, xzero); if (ft && np < ft[3]) fcoerce(subst(ft[7 + np], 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 < ft[3]) { pt = ft[7 + np]; if (!unify(pt, xty, bs)) tcerr("argument", subst(pt, bs), xty); } np++; if (!accept(s_comma)) break; } } expect(s_rp); argfact = first; xaf = 0; 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); 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); 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) 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 = outmode, osb = outbuf, osn = outn, osc = 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; outn = 0; outmode = 3; tprint(t); put_s(after); n = 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); } outbuf = ift + at; outn = 0; outcap = n; outmode = 2; tprint(t); put_s(after); n = outn; outmode = osm; outbuf = osb; outn = osn; 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; 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 (*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; } // 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 r = ifcur; if (!r || j >= IFP) return 0; r[8 + 3 * j] = 0; 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 (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; 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 (!hint_begin(tpack())) return 0; put_s("the number "); put_n(v); put_s(" has no name; a const says what it means"); dg_close(); return 0; } // warn that a new inside a loop allocates on every pass. int newloop(int p) { if (!hint_begin(p)) return 0; 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)) return 0; while ((1 << k) < v) k++; put_s("multiplying by "); put_n(v); put_s(" is a shift; write << "); 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]; [8]word bs; int rs, rt = 0, kop, cp = callpos, original = s; word storage; if (sname[s] == s_new && skind[s] == K_FN) { peek(); if (pk_tk == T_OP && pk_tv == s_lk) { next(); return newtyped(); } if (pk_tk == T_ID && (istypename(pk_tv) || pk_tv == s_fn)) { next(); 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; 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; fill_w(bs, 0, 8); if (ngbind) { 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; np = args(tags, ft, bs, np); storage = argfact; if (original < ubase && (sname[original] == s_free || sname[original] == s_recycle)) af_end(storage, cp); if (sflag[s] & F_GEN) s = ginstance(s, bs); 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 rt = subst(ft[4], bs); if (hasptr(rt)) { lit(rt); 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); xfloat = 0; if (isfl(xty)) xfloat = 1; xlenloc = -1; xstamp = 0; if (o3on && (sflag[s] & F_O3) && ft && hasptr(xty)) xstamp = 1; 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); 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 captured variable is copied in when the closure is made; the body // reads and writes its own copy. // 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; word names, 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) { copy_w(names + i * cap, clo_name + i * capture_width, clo_n[i]); copy_w(locals + i * cap, clo_loc + i * capture_width, clo_n[i]); 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 it from its own environment copy. // 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; 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; 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; } // 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; [20]word pn; [20]word pty; int hop, entry, i, t, envl, ncap, c, result; reserve_closures(nclo + 1); if (o3on) serr("closures can't be made inside an o3 region"); next(); expect(s_lp); if (!is_op(s_rp)) { while (1) { pty[np] = tword; if (!is_op(s_dot) && ((tk == T_OP && tv == s_lk) || (tk == T_ID && (tv == s_fn || istypename(tv))))) { pty[np] = ptype(); } pnoffs[np] = toff; 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); prologue(); i = 0; while (i < np) { t = newloc(pn[i]); lrskind[t] = RS_PARAM; lrsoff[t] = pnoffs[i]; fslots++; lsmark(1, pn[i]); ltype[t] = pty[i]; if (isfl(pty[i])) setflt(t, 1); i++; } envl = newloc(intern("")); 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; epilogue(); nclo--; c = nclo; ncap = clo_n[c]; setlit(hop, here); ctx_pop(); // 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); callname(s_new); 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); vaddr(t); op(O_LD); } 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); return C_FRESH; } // ---- 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() { int n, i, s, c, k; [8]word 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; if (tk == T_FLT) { op(OP_FLIT); emit(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); xzero = tv == 0; xconst = 1; xcval = tv; xty = tint; next(); return C_PLAIN; } if (tk == T_BSTR) { bstrlit(tv); xstr = 1; xty = tword; next(); return C_PLAIN; } if (tk == T_STR) { strlit(tv); next(); xty = tptr(tchar); return C_PLAIN; } if (tk == T_OP) { if (tv == s_lp) { next(); c = expr(); expect(s_rp); return c; } if (tv == s_lb) return blockexpr(); 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) 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); 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) { luse(i); rs_note(lrskind[i], lrsoff[i]); xaf = lafact[i]; af_record(toff, xaf); 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); lv = 1; lvk = LV_LOCAL; lvi = i; if (isalias(i)) { touch(i); xbor = lroot(i); return C_PLAIN; } if (tracked(i)) return C_VAR + i; return C_PLAIN; } s = lookup(n); 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 next(); mn = name(); ms = lookup(methname(n, mn)); if (ms >= 0 && skind[ms] == K_CONST) { // Enum.MEMBER 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); } serrn(mn, "is not a static function of this struct"); lit(0); xty = tword; return C_PLAIN; } dien(n, "is a type, not a value"); lit(0); xty = tword; return C_PLAIN; } next(); xty = tword; if (s >= 0 && skind[s] == K_CONST) { 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) { lit(sval[s]); if (stype[s]) xty = stype[s]; if (isfl(xty)) xfloat = 1; lv = 1; lvk = LV_GLOBAL; 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) s = fwdfn_at(n, callpos); if (is_op(s_lt) && (sflag[s] & F_GEN)) { // f(...) 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 alone is that instance, as a function value fill_w(bs0, 0, 8); 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) { int t; 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 n, fieldoff; word st; af_access(xaf, tpack()); xaf = 0; int i, b = -1, k = 0, scanned = 0, ms; next(); n = name(); fieldoff = nameoff; 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 (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); } 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(); put_c('.'); put_s(n); 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(); put_c('.'); put_s(n); put_s(": that's a "); tprint(xty); 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) { errat(); tprint(st); put_s(" has no field "); put_s(n); errend(); } 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 (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 (xty[0] == TY_PTR) { if (b >= 0) xbor = lroot(b); } else xbor = -1; 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() { int c = primary(), b, n, t, et; word ft; [8]word bs; int k, ci, was, al, ix, enh, cz, cj, place; word storage; while (1) { if (tk == T_ID && tv == s_as) { // e as T: same bits, another type next(); t = ptype(); rval(); xaf = 0; if (tbytes(t) && !xfloat && !samesz(t, xty)) { tnorm(t); xconst = 0; } // x as u8 cuts the value to size xty = t; xfloat = 0; lvk = LV_NONE; if (isfl(t)) xfloat = 1; continue; } if (tk != T_OP) return c; if (tv == s_lk) { storage = xaf; place = tpack(); af_access(storage, place); 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); lv = 0; lvk = LV_NONE; xty = tword; xfloat = 0; xstr = 1; xzero = 0; xbor = -1; c = C_PLAIN; continue; } expect(s_rk); callname(s_at_s); 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); 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); 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 (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); } } xaf = 0; lv = 1; xty = et; xzero = 0; xconst = 0; xlenloc = -1; xstamp = 0; if (xty[0] == TY_PTR) { 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 = 0; continue; } if (tv == s_lp) { // call through a value rval(); plainuse(c); ft = 0; if (xty[0] == TY_FN) ft = xty; else tcneed(xty == tword, "only functions can be called", xty); fill_w(bs, 0, 8); n = args(0, ft, bs, o3on); // 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); lv = 0; lvk = LV_NONE; c = C_PLAIN; xzero = 0; xbor = -1; xconst = 0; xty = tword; if (ft) xty = subst(ft[4], bs); 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; 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(); rval(); lvk = LV_NONE; return recast(c, t); } } if (tk == T_OP && tv == s_lp && caststart()) { // (int)x, (node*)x: same bits, the type changes next(); t = ptype(); expect(s_rp); c = unary(); rval(); plainuse(c); xaf = 0; 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 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(); af_access(xaf, place); xaf = 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); xty = telem(xty); 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 = 0; if (lv && lvk == LV_LOCAL) { storage = laaddr[lvi]; if (!storage) { storage = af_new(1, 1, 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); lv = 0; lvk = LV_NONE; xzero = 0; xbor = -1; xfloat = 0; xstr = 0; xty = tptr(xty); xaf = storage; 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 (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; } } 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 bin(int minp) { int c = unary(), b, p, a, j, lt, lb, rb, src, lf, rf, ls, rs, fresh, top = 0, il = -1, opp; 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; lf = xfloat; ls = xstr; fresh = 0; il = -1; if (top && lv && lvk == LV_LOCAL) il = lvi; 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); 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 (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(); xty = bintype(b, lt, 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; if (fresh) c = C_FRESH; } 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; rval(); plainuse(c); ftest(); next(); a = hole(); op(O_JZ); s0 = snap_push(); ca = expr(); rval(); ta = xty; ba = xbor; 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; 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 (ba != bb) xbor = -1; lv = 0; lvk = LV_NONE; xzero = 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; 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 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); } 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; 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; 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); } 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()) 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; } 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 (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() { int s, c; 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 (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(); } 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 = outmode; sb = outbuf; sn = outn; sc = outcap; outmode = 3; outn = 0; tprint(t); need = ltxn + outn; if (!ltxt || need > ltxtcap) { while (need > ltxtcap) ltxtcap = ltxtcap * 2; ltxt = chk_resize(ltxt, ltxn, ltxtcap); } outmode = 2; outbuf = ltxt; outn = ltxn; outcap = ltxtcap; tprint(t); sl = outn; outmode = sm; outbuf = sb; outn = sn; 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. int localdecl(int typed) { int n, i, c, k, t = 0, tg = 0, pp, kc, kp = ifkp, gty = 0, gok = !typed && ifon && !dry, o; 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); 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); } } if (accept(s_eq)) { if (larr[i]) serr("an array or a struct can't be initialized with ="); newsz = -1; newleaf = 0; if (tk == T_NUM) nomagic = 1; 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); 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 (gok) { k = xty; if (isagg(k)) k = ltype[i]; if (!ifpure(k) || (gty && gty != k)) gok = 0; else gty = k; } } else if (gok && !larr[i]) gok = 0; // no initializer: nothing says what it is 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]); 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 = expr(); rval(); plainuse(c); ftest(); 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 = expr(); rval(); plainuse(c); ftest(); 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; 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); } 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 = expr(); rval(); plainuse(c); ftest(); } 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; if (!is_op(s_rp)) { c = expr(); rval(); op(O_DROP); discard(c); } 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 = expr(); rval(); plainuse(c); ftest(); 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; [8]word pbn; [8]word 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(); 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++) { 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_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); exit_vars(1, 0); epichain = fixnew(here, epichain); lit(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; copy_w(p + 3, o3sl, no3sl); 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; laddr(lslot[i]); 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 + 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(); 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); 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() { 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 = new(n * 3 + 1); q[0] = n; copy_w(q + 1, R_QBUF, n * 3); return u*q; } unchecked word .u qnew(word src) { int n = src[0] * 3 + 1; word q = new(n); copy_w(q, src, n); return q; } word .u qdup(.m q) { int n = q[0] * 3 + 1; word r = new(n); copy_w(r, q, n); return u*r; } // emit n words copied from the array q. 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 kw_quote() { word q; int j, a, n; next(); q = grab(); j = hole(); op(O_JMP); n = q[0] * 3 + 1; reserve_code(n); a = here; wcopyto(q, n); free(d*q); setlit(j, here); lit(a); callname(s_qnew); xcls = C_FRESH; return 0; } // tokens of a string, as a token list word .u tokens(int 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 = new(n * 3 + 1); q[0] = n; 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. unchecked 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 unchecked word readsrc() { int cap = 4090; word b; int n = 0, c; word nb; int w; b = new(4090); qst = 0; qp = 0; qesc = 0; while (!asm { ? }) { c = asm { , } & 255; // 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 = new(cap + cap + 6); copy_w(nb, b, cap); 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] & (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 || 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; 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_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 in a type, or first( 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; return 0; } // compile a whole unit from stdin int unit_stdin() { int src = readsrc(); prescan(3, src); src_push(3, src, 0, 0); next(); unit_top(); unit_end(ubase); src_pop(); 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 = 34; 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; copy_w(f + 22, tpn, 8); f[30] = finsym; f[31] = finfer; f[32] = ifcur; f[33] = ifcall; 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); 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]; copy_w(tpn, f + 22, 8); finsym = f[30]; finfer = f[31]; ifcur = f[32]; ifcall = f[33]; 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) { lbase = nloc; fdepth = 1; fslots = 0; fchk = chk && check_on; fret = ret; fty = 0; finfer = 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(ZFP); op(O_LD); if (!helper) { lit(0); op(O_PICK); } fframe = hole(); op(O_SUB); lit(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(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(ZFP); op(O_LD); lit(1); op(O_PICK); lit(ZFP); op(O_ST); callname(s_frame_grow); lit(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(ZFP); op(O_LD); lit(fslots); op(O_ADD); lit(ZFP); op(O_ST); if (!helper) { lit(sval[lookup(s_frame_node)]); op(O_LD); node = hole(); op(O_JZ); lit(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(ZFP); op(O_ST); setlit(done, here); setlit(node, here); } op(O_RET); setlit(fframe, fslots); infn = 0; local_drop(lbase); fdrop(lbase); 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() { expect(s_lb); while (!is_op(s_rb)) { if (tk == T_EOF) { die("missing } at the end of a function"); break; } stmt(); } 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(); return 0; } // the rest of a function, after its return type and name: // int f(int a, node*.m n, .d x, b) { ... } *list.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; [24]word pn; [24]word pt; [24]word pty; int typed = 0, ft, 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; } 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) && ((tk == T_OP && tv == s_lk) || (tk == T_ID && (tv == s_fn || istypename(tv))))) { pty[np] = ptype(); typed = 1; pk = 1; 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); else if (is_op(s_dot)) if_param(np, tpack(), 2); else if_param(np, tpack(), 1); } 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; 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; } 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(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; if (typed) fty = rty; no3pend = 0; if (o3f) { o3on = 1; o3reset(); sflag[s] = sflag[s] | F_O3; } prologue(); i = 0; while (i < np) { t = newloc(pn[i]); lrskind[t] = RS_PARAM; lrsoff[t] = pnoffs[i]; fslots++; lsmark(1, pn[i]); ltype[t] = pty[i]; lptr[t] = 0; 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(); 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; return s; } // let g = 1, buf[10]; int g = 3; [10]int 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; int s, kp = ifgk, gty = 0, gok = ifon && !t && !dry; 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)) { k = tsz(t); reserve_code(k); s = addsym(n, K_GARR, here); rs_put(ng, RS_SYM, rs_sym(s), s); stype[s] = t; while (k) { emit(0); k--; } } else { reserve_code(1); a = here; s = addsym(n, K_GVAR, a); rs_put(ng, RS_SYM, rs_sym(s), s); if (t) stype[s] = t; emit(0); if (accept(s_eq)) { if (tk == T_STR) { reserve_code(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 { *(a + hdelta) = cnorm(t, cexpr()); if (gok) { if (!gty) gty = tint; else if (gty != tint) gok = 0; } } } else if (gok) gok = 0; // no initializer: nothing says what it is } 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; next(); if (tk == T_ID) { peekat(0); if (pk_tk == T_ID || (pk_tk == T_OP && pk_tv == s_star)) ptype(); } while (1) { n = name(); if (!dnm) dnm = n; expect(s_eq); addsym(n, K_CONST, cexpr()); 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; [8]word 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; 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 + 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 = 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_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: 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; [8]word ps; [8]word 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(); 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); 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 (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 (is_id(s_enhance)) return enhdef(); if (is_id(s_const)) 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(...) { }, 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 unchecked int grec_add() { word nb; if (gn * 3 + 4 > gcap) { nb = new(gcap + gcap); copy_w(nb, gbuf, gcap); 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 unchecked 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); 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); 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. unchecked 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_np[k] = ntp; 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 unchecked int gattach(word buf, int n) { n--; // the last token belongs to the next item if (nerr) { free(buf); return 0; } buf[0] = n; gt_tok[gnew] = buf; return 0; } // 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; word nm; [24]word dg; int nd; word 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, 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; copy_w(gi_ar + k * 8, bs, np); return ns; } // compile one instance: replay the template's tokens with its type // parameters bound unchecked int gcompile(int k) { int t = gi_t[k]; ginst = 1; gng = gt_np[t]; copy_w(gnm, gt_nm + t * 8, 8); 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; 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 = 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 free(rb); gnew = sg; gdrain(); 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() { while (tk != T_EOF) 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 = len_w(a), lb = 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 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 int similar(int n) { int j = 0, best = 0, bd = 99, dist, k, lim = len_w(n) / 3; 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, sname[j]); if (dist < bd) { bd = dist; best = sname[j]; } } } } j++; } if (bd > lim) return 0; return best; } // end a compile unit from token from onward, and report what it defined. int unit_end(int from) { int i = from, best; 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))) { tunpack(sval[i]); best = similar(sname[i]); if (!dry) { errat(); put_s(sname[i]); put_s(" is used but never defined"); if (best) { put_s("; did you mean '"); put_s(best); put_s("'?"); } errend(); } } i++; } 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.) unchecked int compile_list(int q, int top) { int hop = -1, entry = 0, from = nsym; ctx_push(); ifcur = 0; ifcall = -1; 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(); 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; } // 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(int s) { word q = tokens(s); int r = eval(q); 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 + 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("chk_mark"), srvn = intern("LSP_SERVER"); while (*p) { n = intern(p + 3); if (hide && n == srvn) after = 1; skip = hide && (after || (past && p[0] == K_FN)); 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; install(p[2] >> 8); } } p = p + 3 + 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(FTOP); lit(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 = syscall(9, 0, 0, 0); if (dst < 0 || n > words - dst) fatal("image destination too small"); 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 > CODE_END) end = CODE_END; markrange(0, end); if (!hdelta && here > ptop && ptop < CODE_END) { end = here; if (end > CODE_END) end = CODE_END; markrange(ptop, end); } // globals of a unit still compiling markrange(GORG, GORG[6]); markrange(R_IH, R_POOLEND); markrange(sval, sval + nsym); markrange(R_TY, R_TYEND); markrange(R_SRC, R_SRC + R_SRC_cap); gc_frames(); markrange(dsp(), 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() { pooltop = R_POOL; poolend = R_POOLEND; poolblocks = 0; namecount = 0; 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; snaptop = 0; nloc = 0; lbase = 0; npm = 0; npe = 0; 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("start"); s_main = intern("main"); s_free = intern("free"); s_recycle = intern("recycle"); s_do = intern("do"); s_while = intern("while"); s_enum = intern("enum"); s_include = intern("include"); s_memdebug = intern("memdebug"); s_scrub = intern("scrub"); s_alloc = intern("alloc_line"); s_check = intern("checkptr"); s_memset = intern("mem_set"); s_own = intern("own"); s_qnew = intern("qnew"); s_fovf = intern("frame_overflow"); s_frame_floor = intern("frame_floor"); s_frame_top = intern("frame_top"); s_frame_node = intern("frame_node"); s_frame_busy = intern("frame_busy"); s_frame_guard = intern("frame_guard"); s_frame_grow = intern("frame_grow"); s_frame_leave = intern("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); add_kw("memdebug", kw_memdebug, 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 && pow2[k] < n) k++; if (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 <= HEAP_LO || R_TY >= HEAP_HI || R_TYEND > HEAP_HI) return 0; k = type_class(); if (k < 0) return 0; if (R_TY + pow2[k] != R_TYEND) return 0; if (heap_reset(R_TY)) heldk = k; return 0; } // let the collector see the type area again. int unshield_types() { if (heldk >= 0) { 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; heap_init(); chk_heap = 1; shield_types(); zc_init(); blob_load(GORG[5], 0); zc_tables(); types_init(); here = 0; hdelta = 0; hlimit = 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_main")); if (s >= 0 && skind[s] == K_FN && (sflag[s] & F_DEF)) { f = sval[s]; f(); halt(); } unit_stdin(); if (here > ptop) ptop = here; scrub(); // compilation's stale words would keep quarantined blocks alive 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) 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("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(); } 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() { code_blocks = 0; code_chunk = CODE_END; isp = -1; docs_on = 0; dn = 0; dline = 0; docpending = 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; 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(); chk_begin(buf, cap); lsets = sets; if (sets) fill_w(sets, 0, 4 * LSW); if (!chk_heap) { heap_init(); chk_heap = 1; } else gc(); // the caller's blocks are reachable from its globals, the rest goes rsn = 0; rsbase = src; if (!rsrec) { rscap = RS_CAP; rsrec = new(RS_CAP * RS_W); rstmp = 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 = new(symcap); ifr = chk_resize(0, 0, ifrcap); ifx = chk_resize(0, 0, ifxcap * 4); ift = chk_resize(0, 0, iftcap); fill_w(ifs, 0, symcap); } zc_init(); blob_load(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; }