From ce920b6ff47f32bc06dd27124e1b882f82f3d6ae Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 10:05:40 +0000 Subject: split compiler.zc into files by phase src/compiler/compiler.zc is now twelve files under src/compiler/, listed in order in src/compiler/parts. cat of the parts in order is byte-identical to the old file, so every compiler and image except the language server's is unchanged. no function was edited. consumers read the list: the makefile, bootstrap-check, genmap (which still writes one compiler.zc into a generated map directory), tinyvm.bat, test.sh, test_win.ps1 and the lsp drivers. the language server embeds each part and serves them joined as the one document tinyvm:/compiler.zc. --- src/compiler/base.zc | 1384 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1384 insertions(+) create mode 100644 src/compiler/base.zc (limited to 'src/compiler/base.zc') diff --git a/src/compiler/base.zc b/src/compiler/base.zc new file mode 100644 index 0000000..17ec80b --- /dev/null +++ b/src/compiler/base.zc @@ -0,0 +1,1384 @@ +namespace run; +include rt; + +// ============================================================================ +// compiler.zc: the resident zc compiler. it lives in the genome next to the +// runtime, compiles source straight to vm words in memory, and is visible to +// the programs it compiles: every function and global here can be called or +// patched by name, and parse words (immediate fns) run inside it. +// ============================================================================ + +// how this file is organized, in the order a source file travels through it: +// +// 1 lexer bytes -> tokens (nextch, lex1, next/peek). a token is a +// kind (tk), a value (tv: number or interned name) and a +// place (line, offset, length). sources nest: stdin, a +// token list for macros, a string for eval. +// 2 symbols every name is interned once in a string pool; symbols +// (functions, globals, constants, keywords, types) hang +// off the interned name and shadow one another. +// 3 types records in the type area, hash-consed, so comparing two +// types is comparing two words. typing is optional: an +// untyped program is all "word". +// 4 analyzer the ownership/lifetime pass. it runs while the parser +// emits code, tracking for every local whether it owns an +// object in this block (m), moves one up and out (u), or +// moves one down into a lower scope (d). borrowing is +// tracked separately. the pass inserts frees, and joins +// merge that knowledge at +// if/else, loops and switches. +// 5 expressions precedence climbing over the operator tables, which a +// program can extend at compile time. +// 6 statements blocks, control flow, switch, asm blocks, quote/grab. +// 7 functions signatures, prologues, closures, methods, generics, +// fattening (typed new) and the top level. +// 8 setup the keyword table, built-in names, entry point. +// 9 check mode the same compiler run over an in-memory document for the +// language server: errors and hints become records. +// +// the compiler emits vm words directly into memory at "here" (there is no +// intermediate tree). a forward reference emits a placeholder literal and +// remembers the site on a patch chain; defining the name patches the chain. +// the compiler is itself written in zc and lives in the genome next to rt.zc, +// so a program being compiled can call any function in this file, and +// immediate fns (parse words) run in the middle of compilation and can read +// tokens, emit code, or add operators. a few conventions that recur: +// - "plain word": most values are untyped 64-bit words. a pointer is just a +// word that happens to be a heap address. +// - a code word is lit(v) = 2v+1 (odd) or op(k) = 2k (even). see lit and op. +// - names ending in an s_ prefix are interned spellings the parser compares +// against by pointer, which is why intern exists. +// - serr reports and carries on; die reports and skips input to the next +// ; or }. nothing in here raises or unwinds. +// - a backtick outside a comment or a literal ends the source (srcend). zc0, +// the c bootstrap, reads the same way, so a backtick in a comment is fine. +// +// ---- compiler data region (@map-cc) ---- + +// src/tools/genmap computes these addresses from the compiler table sizes for each +// build. later regions (the type area, local types, exit records) are computed +// from these names. + +// @map-cc +const IHN = 0x8000; +const R_IH = 0x90100; +const R_POOL = 0x98100; +const R_POOLEND = 0xD8100; +const NS = 0x4000; +const R_SYM = 0xD8200; +const sname_BASE = 0xD8200; +const skind_BASE = 0xDC200; +const sval_BASE = 0xE0200; +const ssig_BASE = 0xE4200; +const sflag_BASE = 0xE8200; +const sfix_BASE = 0xEC200; +const snext_BASE = 0xF0200; +const NL = 0x200; +const lname_BASE = 0xF4200; +const lslot_BASE = 0xF4400; +const lhome_BASE = 0xF4600; +const lstate_BASE = 0xF4800; +const lowner_BASE = 0xF4A00; +const lused_BASE = 0xF4C00; +const larr_BASE = 0xF4E00; +const lown_BASE = 0xF5000; +const NF = 0x8000; +const fsite_BASE = 0xF5200; +const fnext_BASE = 0xFD200; +const R_SNAP_BASE = 0x105200; +const R_QBUF_BASE = 0x109200; +const R_SRC_BASE = 0x10D200; +const R_SEQ_BASE = 0x10D600; +const bname_BASE = 0x10DE00; +const bprec_BASE = 0x10DE40; +const bseq_BASE = 0x10DE80; +const blen_BASE = 0x10DEC0; +const uname_BASE = 0x10DF00; +const useq_BASE = 0x10DF20; +const ulen_BASE = 0x10DF40; +const aname_BASE = 0x10DF60; +const abin_BASE = 0x10DFA0; +const kops_BASE = 0x10E000; +const R_CTX_BASE = 0x10E200; +const R_LOOP_BASE = 0x10EA00; +const R_ASM = 0x10EE00; +const ASM_LABELS = 0x40; +const ASM_REFS = 0x100; +const ASM_WORDS = 0x400; +const EXIT_WORDS = 0xA00; +const ENUM_MEMBERS = 0x80; +const TAGGED_ENUMS = 0x10; +const STRUCT_METHODS = 0x20; +const GENERATED_WORDS = 0x2000; +const RANGE_FACTS = 0x20; +const JOIN_PATHS = 0x40; +const O3_REGIONS = 0xA; +// @end-map + +// table bases start in the generated map; growth copies columns without changing indices. + +// interned spelling of each symbol, indexed by symbol id; use lookup(name) to find an id. +char** sname = sname_BASE; +// kind of each symbol: K_FN, K_GVAR, K_GARR, K_CONST, K_KW, K_ENH, K_TYPE or K_DOC. +word skind = skind_BASE; +// value of each symbol, indexed by symbol id: a function code address, variable storage address, constant value, enhancement slot or type record, according to skind. +word sval = sval_BASE; +// packed function signature for each symbol; includes the owned-return bit, parameter count and parameter lifetime tags. +word ssig = ssig_BASE; +// definition and compilation flags for each symbol, such as F_DEF, F_DECL, F_GEN and F_STATIC. +word sflag = sflag_BASE; +// head of unresolved code references for each symbol; references are patched when the symbol is defined. +word sfix = sfix_BASE; +// previous symbol with the same interned name, stored as id plus one; zero ends its shadow chain. +word snext = snext_BASE; +// retained source metadata for functions that can be transformed at runtime. +word fnmeta; +int syntax_host_floor, syntax_host_from; word syntax_host_symbols; +// interned name of each currently active local variable or parameter. +char** lname = lname_BASE; +// frame slot of each local; laddr(lslot[i]) emits its address relative to the current frame. +word lslot = lslot_BASE; +// scope depth where each local was declared; the lifetime analyzer uses it when values move between blocks. +word lhome = lhome_BASE; +// ownership state of each local: plain, m, u, d, dead, child or borrowed. +word lstate = lstate_BASE; +// owner local of a borrowed or container-owned local; lroot(i) follows this chain. +word lowner = lowner_BASE; +// source line of each local's last tracked use; zero means it has not been used. +word lused = lused_BASE; +// nonzero for locals stored in place as fixed arrays or structs; their expression value is their address. +word larr = larr_BASE; +// nonzero when a local is responsible for releasing its owned object. +word lown = lown_BASE; +// code location to patch for each unresolved reference record. +word fsite = fsite_BASE; +// next unresolved reference record, stored as index plus one; zero ends a patch chain. +word fnext = fnext_BASE; +int symcap = NS, loccap = NL, fixcap = NF; + +const word R_TY = rt.ZFP - 16384, R_TYEND = rt.ZFP; +// interned text and type records never move: only their index vectors grow. +char** namehash = R_IH; word poolend = R_POOLEND; +word poolblocks, typeblocks; +int hashcap = IHN, namecount, poolchunk = R_POOLEND - R_POOL; +word typelist = R_TY, tyend = R_TYEND; +int typecap = 1024; + +// growing scratch regions retain offsets used by nested parser frames. +word R_SNAP = R_SNAP_BASE; int R_SNAP_cap = R_QBUF_BASE - R_SNAP_BASE; +word R_QBUF = R_QBUF_BASE; int R_QBUF_cap = R_SRC_BASE - R_QBUF_BASE; +word R_SRC = R_SRC_BASE; int R_SRC_cap = R_SEQ_BASE - R_SRC_BASE; +word R_SEQ = R_SEQ_BASE; int R_SEQ_cap = bname_BASE - R_SEQ_BASE; +word R_CTX = R_CTX_BASE; int R_CTX_cap = R_LOOP_BASE - R_CTX_BASE; +word R_LOOP = R_LOOP_BASE; int R_LOOP_cap = R_ASM - R_LOOP_BASE; +word[60] lpfact_initial; word lpfact = lpfact_initial; int loopfacts_cap = 60; + +// operator columns grow independently of their initial map reservations. +char** bname = bname_BASE; +word bprec = bprec_BASE; +word bseq = bseq_BASE; +word blen = blen_BASE; +char** uname = uname_BASE; +word useq = useq_BASE; +word ulen = ulen_BASE; +char** aname = aname_BASE; +word abin = abin_BASE; +word kops = kops_BASE; +int bincap = bprec_BASE - bname_BASE, uncap = useq_BASE - uname_BASE, asgcap = abin_BASE - aname_BASE, kopcap = R_CTX_BASE - kops_BASE; +// ---- token kinds, symbol kinds and flags ---- + +const int T_EOF = 0, T_NUM = 1, T_ID = 2, T_STR = 3, T_OP = 4, T_FLT = 5, T_BSTR = 6; +const int K_FN = 1, K_GVAR = 2, K_GARR = 3, K_CONST = 4, K_KW = 5, K_ENH = 6, K_TYPE = 7, K_DOC = 8; +const int F_DEF = 1, F_SIG = 2, F_UNTYPED = 4, F_STMT = 8, F_EXPR = 16, F_TOP = 32, F_FALLBACK = 64, F_GEN = 128, F_PRE = 256, F_O3 = 512, F_EXT = 1024, F_TPL = 2048, F_STATIC = 4096, F_DECL = 8192, F_RETOWN = 16384, F_EARLY = 32768; +// variable states for the lifetime analyzer +const int S_PLAIN = 0, S_M = 1, S_U = 2, S_D = 3, S_DEAD = 4, S_CHILD = 5, S_BORROW = 6; +// expression classes: plain word, a new object, or a bare variable (C_VAR + index) +const int C_PLAIN = 0, C_FRESH = 1, C_VAR = 2; +// lvalue kinds +const int LV_NONE = 0, LV_LOCAL = 1, LV_GLOBAL = 2, LV_ELEM = 3, LV_OTHER = 4; +// opcodes +const int O_Q = 0, O_LD = 1, O_ST = 2, O_PICK = 3, O_DROP = 4, O_SWAP = 5, O_CALL = 6, O_JMP = 7; +const int O_JZ = 8, O_RET = 9, O_ADD = 13, O_SUB = 14, O_LT = 20; +const int XOP0 = 22; // first enhancement opcode +const int OP_FLIT = 600, OP_FADD = 601, OP_FSUB = 602, OP_FMUL = 603, OP_FDIV = 604; +const int OP_FEQ = 605, OP_FLT = 606, OP_FLE = 607, OP_I2F = 608, OP_F2I = 609; +const int SEQWORD = -4; // in an operator sequence: the next word is a full-width literal +const int SEQREF = -2; // in an operator sequence: the next word names a symbol + +// ---- state ---- + +int chk_heap; +int nenh; word pooltop; int nsym, ubase, vislo, nfixu, fixfree; +char* module_name; word module_imports; +int nbin, nun, nasg, nkop, seqtop; +char* mnem = "q@!$_~cjzr,?.+-*&|^><%"; +int check_on = 1; // set to 0 to switch the analyzer off for what follows +int tk; word tv; int tglue, tline, line = 1, ch, chkind; word chptr, chbase; int qdone, isp = -1, pendot; +// where the current token sits in its source (chars from the start, -1 for a token list), how long it is, and where the one before it ended +int toff = -1, toklen, tpend = -1, tpline; +int ntok; // tokens taken so far, so a parse word that reads nothing can be caught +int qst, qesc, qp; // srcend's reading state: normal, line comment, block comment, string, char literal +char[1100] tbuf_initial; char* tbuf = tbuf_initial; int tbufcap = 1100; +// doc comments: the comment lines directly above a declaration (no blank line +// between them or before the declaration) are its documentation. the lexer +// collects them in dbuf, dlast is the line of the last one, and dline the +// line of the token that follows them. only when docs are on. +int docs_on; // docs on; keeps them: a symbol "doc name" holds a byte string +char[2100] dbuf_initial; char* dbuf = dbuf_initial; int docbufcap = 2100; int dn, dlast, dline, docpending, dsect; +char[2100] dsnap_initial; char* dsnap = dsnap_initial; int docsnapcap = 2100; int dsn, dnm; char[300] dnmb_initial; char* dnmb = dnmb_initial; int docnamecap = 300; +int pk_tk, pk_tv, pq_n; word[4] pq_tk; word[4] pq_tv; word[4] pq_gl; word[4] pq_ln; // up to 4 tokens of lookahead +int here, hdelta, hlimit, ptop; +int code_chunk = rt.CODE_END; +word image_buffer, image_words; int image_active, image_org, image_capacity, image_size; +int nloc, lbase, fdepth, fslots, fchk, fret, live, bxdepth, epichain, fframe, infn; +// a global that borrows a local owner through g = m*x. each entry is the global's +// symbol, the local, and the block depth of the store; the lend ends when the same +// global is assigned again in that block, and is an error if the block can end first. +word lendrec; int nlend, lendcap, xlend = -1; +int finsym, finfer; // the function being compiled, and whether its owned return is still to be inferred (no .u) +int fn_return_type; // a consistent return type inferred from the function body +// effects come from the real compiler's uses, stores, returns and call edges; unknown bodies stay conservative. +const int EF_READ = 1, EF_WRITE = 2, EF_RETAIN = 4, EF_CONSUME = 8, EF_UNKNOWN = 16, EF_ALIAS = 32, EF_IDENTITY = 64; +word effect_symbols, effect_edges, effect_requirements, effect_current; int effect_floor, effect_argument_base; +word local_origin, local_origin_mask, local_cell, local_captured, local_ended, local_end_info; word effect_value, argorigin_mask; int capture_enabled; word[1] capture_dummy; +int borrow_pending, borrow_depth, borrow_jump = -1, borrow_entry; word borrow_slots; +int argorigin = -1, argtemporary, argowner = -1; word argowner_fact; +int stubgen; // 1 while an extern's generated stub is compiled: the stub gets no hints +word xprojection; int af_mutation_tick; +int xbor = -1; // local a pointer value is borrowed from, or -1 +int mdbg, mdbg_flags; // memdebug: compile checks in, and the flags start() turns on +int lsp, loopbase, ctxsp, snaptop; +int lv, lvk, lvi, kw_expr, xcls, fty; +// interned spellings the parser compares against + +int s_lp, s_rp, s_lb, s_rb, s_lk, s_rk, s_comma, s_semi, s_eq, s_arrow, s_inc, s_dec; +int s_star, s_slash, s_amp, s_colon, s_hash, s_minus, s_andand, s_oror; +int s_dot, s_fn, s_var, s_const, s_else, s_as, s_unchecked, s_immediate, s_m, s_d, s_u; +int s_q, s_case, s_default, s_fovf, s_start, s_main, s_free, s_recycle, s_own, s_qnew, s_enhance, s_micro, s_vm; +int s_frame_floor, s_frame_top, s_frame_node, s_frame_busy, s_frame_guard, s_frame_grow, s_frame_leave; +int s_copy_w; +int s_do, s_while, s_enum, s_include, s_memdebug, s_scrub, s_alloc, s_check, s_memset, s_static; + +// ---- types: kinds, tables and parser state (see the types section) ---- + +const int TY_WORD = 0, TY_INT = 1, TY_CHAR = 2, TY_BOOL = 3, TY_VOID = 4, TY_PTR = 5, TY_ARR = 6; +// dyn: T[?], a = element, n = 1 if fixed +const int TY_FN = 7, TY_STRUCT = 8, TY_VAR = 9, TY_GEN = 10, TY_DYN = 11, TY_FLOAT = 12, TY_BSTR = 13; +const int NTL = 1024; // the first NTL words list every record +// type per local, declared tag per local, type per symbol + +const word LTYPE_BASE = R_ASM + ASM_WORDS, LTAG_BASE = LTYPE_BASE + NL, STYPE_BASE = LTAG_BASE + NL; +word ltype = LTYPE_BASE, ltag = LTAG_BASE, stype = STYPE_BASE; +int tytop, ntl; word tword, tint, tchar, tbool, tvoid, tfloat, tf32, tbstr; +int ckst; char* ckn; // extern: the zc struct a by-value parameter names (ckind), and its name +int tc_on; // the optional typechecker, off unless a program says typecheck on; +// type, literal 0, float, byte string, dry runs (typeof), unique giveaway + +word xty; int xzero, xfloat, xstr, dry, xgive; +// the type facts of annotate mode (see type facts below): the tables, and where the declaration being read starts + +const int NIF = 431, IFP = 24, IFW = 80, IFN = 4096, IFT = 32768; +int ifon; word ifs, ifr; int nif; word ifx; int nifx; word ift; int nift; word ifcur; int ifkp, ifkk, ifcall, ifgk, ifstrict; +word if_edges, if_value, if_local; +int ifrcap = NIF, ifxcap = IFN, iftcap = IFT; +int fldst, fldi; // the one struct a bare address's field name resolved to +char*[8] tpn; int ntp; // type parameters in scope +// reading a return type: unknown names are type parameters declared after the function's name +int tvauto; +// the typedefs of the blocks being compiled: three words each (the symbol, the block depth and the +// compile context it was made in), newest last. a block's end unlinks them from their names. +word tdstk; int ntd, tdcap; +word readonly_types; +int dty, dtag; // result of dtype() +int s_typedef; +int s_lt, s_gt, s_shr, s_le, s_ge, s_eqeq, s_ne, s_plus, s_struct, s_type, s_typecheck, s_docs, s_small, s_std, s_icon, s_on, s_off, s_new, s_new_t; +int s_o3, s_lfa, s_outside, s_new_raw, s_new_oz, s_oob; +int s_ld8, s_st8, s_ld16, s_st16, s_ld32, s_st32; +// ---- o3 region state ---- + +// each promoted slot maps to a register; pending regions use 67-word records and a descriptor buffer. +int o3on, o3nxt, o3one; word[32] o3sl; word[32] o3rg; int no3sl; word[O3_REGIONS * 67] o3pend_initial; word o3pend = o3pend_initial; int o3pendcap = O3_REGIONS, no3pend; word[80] o3dsc; int o3fs, o3na, o3fn0; +// o3free holds registers released by closed scopes; o3cache holds the items pointer and length slots for fixed arrays. +word[8] o3free; int no3free, o3nreg; word[NL] o3cache_initial; word o3cache = o3cache_initial; int argloc, s_hitems, s_hlen, s_fatten; +// operator names used by numeric type checks. +word[12] numops; int nnumops, s_fixed, s_dlen, s_dcap, s_dnew, s_dat, s_dpush, s_dpop, s_dresize, xconst, xcval; +int xlenloc, arrloc, fortest, forstep, ft_iv, ft_bd, ft_ir, ft_nops, fs_ok, xstamp; +// local lower bound / len(array); one proof record per live loop +int nfact, factcap = RANGE_FACTS; word[RANGE_FACTS] floc_initial, flo_initial, flen_initial; word floc = floc_initial, flo = flo_initial, flen = flen_initial; +int s_len_s, s_at_s, s_slice_s, s_cat_s, s_eq_s, s_lt_s, s_le_s, s_put_bs, s_put_s, s_n_to_s, s_new_s; +int mdg; word[8] mdn; // the type parameters of the generic struct whose methods are being compiled +word[8] gbind; int ngbind; // explicit type arguments for the next call: f(...) +// generic functions. the template (type parameters stay variables) is compiled once, as before, +// and its tokens are kept; a call whose type arguments are all known gets an instance, the same +// tokens compiled again with the parameters bound to those types, so new(T) and sizeof(T) +// inside it mean the real type. instances are compiled after the item that asked for them. +int grec; word gbuf; int gn, gcap; // the tokens of the top-level item being read +int gnew, gnew_np; word[8] gnew_nm; // a generic function was just defined +int fn_method_compile, fnpatchseq; +word gtab; int gt_n, gtcap = 64, gicap = 128; word gt_sym, gt_tok, gt_np, gt_module, gt_imports, gt_source; int gt_nm; // the columns are allocated at the first generic function +int gi_n; word gi_t, gi_sym, gi_ar, gi_done; +int ginst; word[8] gnm; word[8] gib; int gng, ginsn; // compiling an instance: the names and what they stand for +int s_this; +const int NCLO = 8; /* initial closure nesting capacity; grows when more closures are compiled */ +const int NCAP = 32; /* initial capture capacity per closure; grows when more variables are captured */ + +// ---- closure compilation state ---- + +// closures being compiled, innermost last: the enclosing function's locals +// (lo .. hi), how many variables the body has captured so far (names and the +// enclosing local each came from), and the body's own hidden env parameter + +int nclo, closure_cap = NCLO, capture_width = NCAP; +word[NCLO] clo_lo_initial; word clo_lo = clo_lo_initial; +word[NCLO] clo_hi_initial; word clo_hi = clo_hi_initial; +word[NCLO] clo_n_initial; word clo_n = clo_n_initial; +word[NCLO] clo_env_initial; word clo_env = clo_env_initial; +word[NCLO] clo_ret_initial; word clo_ret = clo_ret_initial; +char*[256] clo_name_initial; char** clo_name = clo_name_initial; +word[256] clo_loc_initial; word clo_loc = clo_loc_initial; +word[256] clo_via_initial; word clo_via = clo_via_initial; + + +// ---- errors ---- + +// the compiler reports up to 20 errors before it stops. a syntax error +// (die) skips the token it happened on and sets panic, which skips ahead to +// the next ; or } (or the next top-level item) and keeps quiet until then. +// other errors (serr) just report: the parse is still in step. +// +// check mode (cmode) is for the language server, which runs the real compiler +// over a document it holds in memory. nothing is printed and nothing halts: +// each error is written into dgbuf as a record [severity, line, offset, +// length, chars...] and giveup raises chkabort, which makes the lexer return +// end of file so every parsing loop winds down by itself. +int nerr, panic; +// bits kept in a local's ltag for the hints: assigned after its declaration, +// read, declared with let, initialized with a literal; the size of the object +// a new gave it (bits 16 to 39); and how many times it was used (from bit 56) +const int LASG = 0x1000, LREAD = 0x2000, LDECL = 0x4000, LCONST = 0x8000, LCNT1 = 0x100000000000000; +const int NEWTYPED = 0x800000; +word[NL] lpos_initial; word lpos = lpos_initial; word[NL] lreadpos_initial; word lreadpos = lreadpos_initial; word[NL] lnsz_initial; word lnsz = lnsz_initial; word[NL] lleaf_initial; word lleaf = lleaf_initial; word[NL] lrsoff_initial; word lrsoff = lrsoff_initial; word[NL] lrskind_initial; word lrskind = lrskind_initial; int newsz, newleaf, nomagic, callpos; +word[NL] lptr_initial; word lptr = lptr_initial; // annotate mode: the local was indexed or dereferenced, so it holds an address +// storage facts are shared by aliases. they describe one allocation, not a +// variable slot: [length, lifetime, kind, allocation place, end place, owner]. +// lifetime is 0 unknown, 1 live, 2 ended; kind is 1 stack, 2 heap, 3 array handle. +word[NL] lafact_initial; word lafact = lafact_initial; +word[NL] laaddr_initial; word laaddr = laaddr_initial; word xaf, argfact; + +// value previews are separate from optimization facts. each reference keeps +// the value proved at that position: 1 integer, 2 float bits, 3 string, 4 bytes. +word lvkind, lvvalue, lvescaped, svkind, svvalue; int xvk; word xvv; +// local name lookup keeps a head for each spelling and a shadow link per slot. +word[NL] lprev_initial; word lprev = lprev_initial; +word[256] lkeys_initial; word lkeys = lkeys_initial; +word[256] lheads_initial; word lheads = lheads_initial; int lkeycap = 256, lkeyn; +int s_new_k, s_recycle_leaf; +// dgprev: the next record sits at the end of the previous token +// chk_hook, when set, is called after each top-level item of the document in check mode: the language +// server streams diagnostics and drops a check whose source has been replaced +word chk_hook; +int cmode, chkabort, chkfuel; word dgbuf, dggrow; int dgcap, dgpos, dgn, dgopen, dgprev, dgmode; +// expand a diagnostic in progress, keeping its output offset within the buffer. +int dg_expand() { + dgbuf = chk_resize(dgbuf, dgpos + 5 + rt.outn, dgcap * 2); + dgcap = dgcap * 2; rt.outbuf = dgbuf + dgpos + 5; rt.outcap = dgcap - dgpos - 5; + return 0; +} +// diagnostic records for check mode. dg_open starts a record [severity, line, +// offset, length, text length, text...] at the current token (or just past the +// previous one when dgprev is set) and redirects the error text into it with +// outmode 2; dg_close seals it with the length of the text and restores output. +// severity 1 is an error, 2 a warning, and 4 a hint. +// completion: the language server asks what is in scope at a place in the document. probe_off is that place +// (a byte offset; -1 when nobody asks). the check keeps a copy of the locals at the last statement that starts at or +// before it, with whether the lifetime analysis has used each one up, and, when the place is a member name after +// a dot, what is before the dot. the check stops as soon as it has what was asked for +int probe_off = -1, probe_has, probe_n, probe_cap, probe_static; +char** probe_name; word probe_kind, probe_state, probe_type, probe_decl, probe_mem; +char* curinc; int incdepth, dgdrop; int incplace; // how many includes deep the compile is; a record to throw away; the place of the outermost include +int dg_close() { + if (!dgopen) return 0; + if (!dgdrop) { dgbuf[dgpos + 4] = rt.outn; dgpos = dgpos + 5 + rt.outn; dgn++; } + dgdrop = 0; + dgopen = 0; rt.outmode = dgmode; rt.outgrow = dggrow; + return 0; +} +// open a check-mode diagnostic record of the given severity. +int dg_open(int sev) { + dg_close(); + if (dgpos + 205 > dgcap) { + int cap = dgcap; while (cap < dgpos + 205) cap = cap * 2; + dgbuf = chk_resize(dgbuf, dgpos, cap); dgcap = cap; + } + if (dgprev && tpend >= 0) { + dgbuf[dgpos + 1] = tpline; + dgbuf[dgpos + 2] = tpend - 1; + dgbuf[dgpos + 3] = 1; + } + else { dgbuf[dgpos + 1] = tline; dgbuf[dgpos + 2] = toff; dgbuf[dgpos + 3] = toklen; } + dgbuf[dgpos] = sev; + // inside an included file the document has no place for the finding: hints and warnings about a library are + // left out, and an error is shown on the include that brought the file in, with where it really is + if (incdepth > 0) { + dgbuf[dgpos + 1] = incplace & 0xFFFFFF; dgbuf[dgpos + 2] = ((incplace >> 40) & 0x1FFFFF) - 1; dgbuf[dgpos + 3] = (incplace >> 24) & 0xFFFF; + if (sev != 1) dgdrop = 1; + } + rt.outbuf = dgbuf + dgpos + 5; rt.outn = 0; rt.outcap = dgcap - dgpos - 5; + dggrow = rt.outgrow; rt.outgrow = dg_expand; + rt.outmode = 2; dgopen = 1; + if (incdepth > 0 && sev == 1) { rt.put_s("in "); rt.put_s(curinc); rt.put_s(", line "); rt.put_n(tline); rt.put_s(": "); } + return 0; +} +word[256] dgseen_initial; word dgseen = dgseen_initial; int dgseen_cap = 256, dgseen_n; +// start and stop a check run. chk_begin points the diagnostics at a buffer +// the caller owns; chk_end flushes any open record and restores output. +int chk_begin(int buf, int cap) { + cmode = 1; chkabort = 0; chkfuel = 0; dgbuf = buf; dgcap = cap; dgpos = 0; dgn = 0; dgopen = 0; + nerr = 0; panic = 0; dgmode = rt.outmode; rt.fill_w(dgseen, 0, dgseen_cap); dgseen_n = 0; + return 0; +} +// end check mode and put the output back the way it was. +int chk_end() { + dg_close(); + cmode = 0; + rt.outmode = dgmode; + return 0; +} +// a hint is a finding that isn't an error: the program still compiles, and +// only check mode reports it. hint_begin(place) opens the record at a packed +// place (see tpack) and says whether to write the text; dg_close ends it. +// one hint per source position. a growing set keeps large documents linear +// instead of rescanning every previous diagnostic for each local. +int dg_hash(int key, int cap) { return ((key ^ (key >> 21)) * 0x9E37) & (cap - 1); } +int dg_dup() { + int key = ((toff + 1) << 24) | tline, i, k, cap; word old; + if (dgseen_n + dgseen_n >= dgseen_cap) { + old = dgseen; cap = dgseen_cap * 2; dgseen = chk_resize(0, 0, cap); + i = 0; + while (i < dgseen_cap) { + if (old[i]) { k = dg_hash(old[i], cap); while (dgseen[k]) k = (k + 1) & (cap - 1); dgseen[k] = old[i]; } + i++; + } + dgseen_cap = cap; + } + i = dg_hash(key, dgseen_cap); + while (dgseen[i]) { if (dgseen[i] == key) return 1; i = (i + 1) & (dgseen_cap - 1); } + dgseen[i] = key; dgseen_n++; return 0; +} +// hints on; hints small; hints off; set how many hints the code after it gets: +// on uses informational ranges, small uses compact hint markers, and off +// suppresses hints. each directive is kept with its byte +// offset, so a hint is judged by where its code is, not where it was reported. +const int HINT_SMALL = 1, HINT_ALL = 2; +word hl_off, hl_lev; int hl_n, hl_cap = 16; +int hint_set(int level, int at) { + if (!cmode || dry || isp != 0 || chkind != 3) return 0; + if (hl_n && hl_off[hl_n - 1] >= at) return 0; + if (!hl_off) { hl_cap = 16; hl_off = chk_resize(0, 0, hl_cap); hl_lev = chk_resize(0, 0, hl_cap); } + if (hl_n >= hl_cap) { hl_off = chk_resize(hl_off, hl_n, hl_cap * 2); hl_lev = chk_resize(hl_lev, hl_n, hl_cap * 2); hl_cap = hl_cap * 2; } + hl_off[hl_n] = at; hl_lev[hl_n] = level; hl_n++; + return 0; +} +// the hint level at byte offset o: 2 all, 1 small, 0 off +int hint_level(int o) { + int k = hl_n; + while (k) { k--; if (hl_off[k] <= o) return hl_lev[k]; } + return 2; +} +// start a check-mode hint at token position p, or say no when hints are off or +// this kind (HINT_SMALL or HINT_ALL) is not wanted there. +int hint_begin(int p, int kind) { + int sv, ok; + if (!cmode || dry || panic || chkabort || stubgen) return 0; + int level = hint_level(((p >> 40) & 0x1FFFFF) - 1); + if (!level) return 0; + sv = tpack(); tunpack(p); + ok = !dg_dup(); + if (ok) { if (level == 1) dg_open(4); else dg_open(3); ok = dgopen; } + tunpack(sv); + return ok; +} +// error plumbing. errat prints the "zc: line N:" prefix (or opens a record in +// check mode) and counts the error; errend ends the line and gives up after +// 20; giveup prints the total and halts (check mode: sets chkabort, which +// makes the lexer return end of file so the parse unwinds). fatal reports and +// stops at once; serr just reports; die reports and also skips input to the +// next ; or } (panic) so one mistake does not become a screen of errors. +int errat() { + if (cmode) { + dg_open(1); + } else { + rt.put_s("zc: line "); + rt.put_n(tline); + rt.put_s(": "); + } + nerr++; + return 0; +} +// finish an error line; the compiler stops after 20 errors. +int errend() { + if (cmode) { dg_close(); if (nerr >= 20) chkabort = 1; return 0; } + rt.put_c(10); + if (nerr >= 20) { rt.put_s("zc: too many errors, stopping\n"); rt.halt(); } + return 0; +} +// stop compiling after errors and report how many there were. +int giveup() { + if (cmode) { dg_close(); chkabort = 1; return 0; } + rt.put_s("zc: "); rt.put_n(nerr); if (nerr == 1) rt.put_s(" error\n"); else rt.put_s(" errors\n"); rt.halt(); + return 0; +} +// report a fatal error and stop, or record it in check mode. +int fatal(char* msg) { + errat(); rt.put_s(msg); + if (cmode) { dg_close(); chkabort = 1; return 0; } + rt.put_c(10); rt.halt(); + return 0; +} +// report a syntax error at the current token, once per recovery. +int serr(char* msg) { + if (!panic && !dry) { + errat(); + rt.put_s(msg); + errend(); + } + return 0; +} +// report an error that names something, then the message. +int serrn(char* name, char* msg) { + if (!panic && !dry) { + errat(); + rt.put_s(name); + rt.put_s(": "); + rt.put_s(msg); + errend(); + } + return 0; +} +// report an error and skip to the next top-level item. +int die(char* msg) { + serr(msg); + panic = 1; + if (tk == T_EOF) giveup(); + next(); + return 0; +} +// what the parser is looking at, for the "found" part of a message +int tokshow() { + if (tk == T_EOF) { rt.put_s("the end of the file"); return 0; } + if (tk == T_NUM) { rt.put_s("the number "); rt.put_n(tv); return 0; } + if (tk == T_STR) { rt.put_s("a string"); return 0; } + if (tk == T_BSTR) { rt.put_s("a byte string"); return 0; } + if (tk == T_FLT) { rt.put_s("a float"); return 0; } + rt.put_s("'"); rt.put_s(tv); rt.put_s("'"); + return 0; +} +// like die, and says what was found instead +int diefound(char* msg) { + if (!panic && !dry) { + errat(); + rt.put_s(msg); rt.put_s(", found "); tokshow(); + errend(); + } + panic = 1; + if (tk == T_EOF) giveup(); + next(); + return 0; +} +// die with a name shown before the message. +int dien(char* name, char* msg) { + serrn(name, msg); + panic = 1; + if (tk == T_EOF) giveup(); + next(); + return 0; +} +// four classification bits live next to each interned spelling. this works +// for names in later pool chunks too, without indexing by a fixed pool offset. +const int LSW = 1; +word lsets; +int lsmark(int set, int n) { + if (lsets) n[-2] = n[-2] | (1 << set); + return 0; +} +word lsget(int set, int n) { + if (!lsets) return 0; + return (n[-2] >> set) & 1; +} +// name resolution for the language server. while a check runs over the document +// at rsbase, every name a use or a declaration resolves to is written into rsrec +// as a record: the byte offset, kind (RS_LOCAL, RS_PARAM, RS_SYM or RS_FIELD), +// declaration or symbol, and storage facts at that position. a local names its declaration by offset +// (-1 when it has none), a symbol names its kind and flags (see rs_sym). the +// compiler's own scopes decide, so the highlighter and hover see locals and +// parameters first, then globals and built-ins, the way the compiler does. +const int RS_LOCAL = 1, RS_PARAM = 2, RS_SYM = 3, RS_FIELD = 4, RS_CAP = 32768, RS_W = 13; +int lt_on, lt_n, ltcap = 256, ltxtcap = 2048; +word lt_off, lt_at, lt_len, lt_type, ltxt; int ltxn; +int ht_n, htcap = 256; word ht_start, ht_end, ht_type; +int fieldn, fieldcap = 128; word fieldtypes, fieldoffs; char** fieldnames; +int field_note(word t, char* n, int off) { + if (!lt_on || isp != 0 || dry) return 0; + if (!fieldtypes || fieldn == fieldcap) { + if (fieldtypes) fieldcap = fieldcap * 2; + fieldtypes = chk_resize(fieldtypes, fieldn, fieldcap); + fieldnames = chk_resize(fieldnames, fieldn, fieldcap); + fieldoffs = chk_resize(fieldoffs, fieldn, fieldcap); + } + fieldtypes[fieldn] = t; fieldnames[fieldn] = n; fieldoffs[fieldn] = off; fieldn++; + return 0; +} +int field_decl(word t, char* n) { + int k = 0; + while (k < fieldn) { if (fieldtypes[k] == t && fieldnames[k] == n) return fieldoffs[k]; k++; } + return 0 - 1; +} +// where each symbol was defined, for go to definition: sdoff is the byte offset of its name + 1 +// (0 when unknown, as for built-ins) and sdsrc the include path whose text it is in (0 for the document) +word sdoff, sdsrc; +// includes: incdir is the directory of the file being compiled (0 for the program on stdin), increoot the +// workspace folder an editor passes in, inc_hook a function that gives the text of a file the editor +// sent (a host with no files of its own), incres the path an include was found at +char* incdir, increoot, incres, std_buf, icon_buf; char*(char*.m) inc_hook; int std_done, icon_done; +word rsrec, rstmp; word[32] pnoffs; word[32] pnplaces; int rsn, rscap, rsbase, nameoff; +// ownership at a reference: record word 11 holds the local's state (S_* + 1, plus 16 when +// it owns its object) and word 12 the declaration offset (+ 1) of the owner it borrows from. +// transfers are logged as events, EV_W words each: the local's declaration offset, what +// happened (EV_*), its line, and the target's declaration offset (+ 1) when there is one. +const int EV_RETURN = 1, EV_VALUE = 2, EV_UP = 3, EV_DOWN = 4, EV_STORE = 5, EV_GLOBAL = 6, EV_FREE = 7, EV_CAPTURE = 8, EV_MOVE = 9, EV_W = 4; +word evrec; int evn, evcap = 64; +// resize compiler tables. these buffers are collector-managed: m* relinquishes +// scope cleanup, and their local or global pointers keep them visible to gc. +word chk_resize(word old, int count, int capacity) { + word p = m*rt.new(capacity); + p[-4] = p[-4] | rt.F_INTERNAL; + rt.fill_w(p, 0, capacity); + if (old && count) rt.copy_w(p, old, count); + return p; +} +// lexer and documentation buffers grow before storing the next character. +int token_char(int n, int c) { + if (n + 2 > tbufcap) { tbuf = chk_resize(tbuf, n, tbufcap * 2); tbufcap = tbufcap * 2; } + tbuf[n] = c; return n + 1; +} +int doc_char(int c) { + if (dn + 2 > docbufcap) { dbuf = chk_resize(dbuf, dn, docbufcap * 2); docbufcap = docbufcap * 2; } + dbuf[dn] = c; dn++; return 0; +} +int doc_name_room(int need) { + if (need <= docnamecap) return 0; + while (docnamecap < need) docnamecap = docnamecap * 2; + dnmb = chk_resize(dnmb, 0, docnamecap); return 0; +} +// reserve compiler columns at execution time. indices remain valid after a copy. +int reserve_symbols(int need) { + int cap = symcap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + sname = chk_resize(sname, nsym, cap); + skind = chk_resize(skind, nsym, cap); + sval = chk_resize(sval, nsym, cap); + ssig = chk_resize(ssig, nsym, cap); + sflag = chk_resize(sflag, nsym, cap); + sfix = chk_resize(sfix, nsym, cap); + snext = chk_resize(snext, nsym, cap); + stype = chk_resize(stype, nsym, cap); + if (effect_symbols) effect_symbols = chk_resize(effect_symbols, nsym, cap); + if (fnmeta) fnmeta = chk_resize(fnmeta, nsym, cap); + if (ifs) ifs = chk_resize(ifs, symcap, cap); + if (svkind) { svkind = chk_resize(svkind, nsym, cap); svvalue = chk_resize(svvalue, nsym, cap); } + if (sdoff) { sdoff = chk_resize(sdoff, nsym, cap); sdsrc = chk_resize(sdsrc, nsym, cap); } + symcap = cap; return cap; +} +int reserve_locals(int need) { + int cap = loccap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + lname = chk_resize(lname, nloc, cap); + lslot = chk_resize(lslot, nloc, cap); + lhome = chk_resize(lhome, nloc, cap); + lstate = chk_resize(lstate, nloc, cap); + lowner = chk_resize(lowner, nloc, cap); + lused = chk_resize(lused, nloc, cap); + larr = chk_resize(larr, nloc, cap); + lown = chk_resize(lown, nloc, cap); + ltype = chk_resize(ltype, nloc, cap); + ltag = chk_resize(ltag, nloc, cap); + lpos = chk_resize(lpos, nloc, cap); + lreadpos = chk_resize(lreadpos, nloc, cap); + lnsz = chk_resize(lnsz, nloc, cap); + lleaf = chk_resize(lleaf, nloc, cap); + lrsoff = chk_resize(lrsoff, nloc, cap); + lrskind = chk_resize(lrskind, nloc, cap); + lptr = chk_resize(lptr, nloc, cap); + if (if_local) if_local = chk_resize(if_local, nloc, cap); + if (local_origin) local_origin = chk_resize(local_origin, nloc, cap); + if (local_origin_mask) local_origin_mask = chk_resize(local_origin_mask, nloc, cap); + if (local_cell) local_cell = chk_resize(local_cell, nloc, cap); + if (local_captured) local_captured = chk_resize(local_captured, nloc, cap); + if (local_ended) local_ended = chk_resize(local_ended, nloc, cap); + if (local_end_info) local_end_info = chk_resize(local_end_info, nloc, cap); + lafact = chk_resize(lafact, nloc, cap); laaddr = chk_resize(laaddr, nloc, cap); lprev = chk_resize(lprev, nloc, cap); + if (lvkind) { lvkind = chk_resize(lvkind, nloc, cap); lvvalue = chk_resize(lvvalue, nloc, cap); lvescaped = chk_resize(lvescaped, nloc, cap); } + o3cache = chk_resize(o3cache, nloc, cap); + loccap = cap; return cap; +} +int reserve_fixes(int need) { + int cap = fixcap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + fsite = chk_resize(fsite, fixcap, cap); + fnext = chk_resize(fnext, fixcap, cap); + fixcap = cap; return cap; +} +const word R_EXIT_BASE = STYPE_BASE + NS, R_EXITEND = R_EXIT_BASE + EXIT_WORDS; +word R_EXIT = R_EXIT_BASE; int R_EXIT_cap = EXIT_WORDS; +int reserve_R_SNAP(int need) { + int cap = R_SNAP_cap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + R_SNAP = chk_resize(R_SNAP, R_SNAP_cap, cap); R_SNAP_cap = cap; + return cap; +} +int reserve_R_QBUF(int need) { + int cap = R_QBUF_cap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + R_QBUF = chk_resize(R_QBUF, R_QBUF_cap, cap); R_QBUF_cap = cap; + return cap; +} +int reserve_R_SRC(int need) { + int cap = R_SRC_cap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + R_SRC = chk_resize(R_SRC, R_SRC_cap, cap); R_SRC_cap = cap; + return cap; +} +int reserve_R_SEQ(int need) { + int cap = R_SEQ_cap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + R_SEQ = chk_resize(R_SEQ, R_SEQ_cap, cap); R_SEQ_cap = cap; + return cap; +} +int reserve_R_CTX(int need) { + int cap = R_CTX_cap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + R_CTX = chk_resize(R_CTX, R_CTX_cap, cap); R_CTX_cap = cap; + return cap; +} +int reserve_R_LOOP(int need) { + int cap = R_LOOP_cap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + R_LOOP = chk_resize(R_LOOP, R_LOOP_cap, cap); R_LOOP_cap = cap; + if (cap / 8 > loopfacts_cap) { lpfact = chk_resize(lpfact, loopfacts_cap, cap / 8); loopfacts_cap = cap / 8; } + return cap; +} +int reserve_R_EXIT(int need) { + int cap = R_EXIT_cap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + R_EXIT = chk_resize(R_EXIT, R_EXIT_cap, cap); R_EXIT_cap = cap; + return cap; +} +int reserve_binary(int need) { + int cap = bincap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + bname = chk_resize(bname, nbin, cap); + bprec = chk_resize(bprec, nbin, cap); + bseq = chk_resize(bseq, nbin, cap); + blen = chk_resize(blen, nbin, cap); + bincap = cap; return cap; +} +int reserve_unary(int need) { + int cap = uncap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + uname = chk_resize(uname, nun, cap); + useq = chk_resize(useq, nun, cap); + ulen = chk_resize(ulen, nun, cap); + uncap = cap; return cap; +} +int reserve_assignment(int need) { + int cap = asgcap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + aname = chk_resize(aname, nasg, cap); + abin = chk_resize(abin, nasg, cap); + asgcap = cap; return cap; +} +int reserve_operators(int need) { + int cap = kopcap; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + kops = chk_resize(kops, nkop, cap); + kopcap = cap; return cap; +} +// write a record for the name at byte offset off. ref is what a local names (its +// declaration's offset, or -1); sym is the symbol a RS_SYM record names, or -1. +int rs_put(int off, int kind, int ref, int sym) { + int at; + if (!rsrec || !cmode || dry || chkind != 3 || chbase != rsbase || off < 0) return 0; + if (rsn >= rscap) { + rsrec = chk_resize(rsrec, rsn * RS_W, rscap * 2 * RS_W); + rstmp = chk_resize(0, 0, rscap * 2 * RS_W); + rscap = rscap * 2; + } + at = rsn * RS_W; + rsrec[at] = off; rsrec[at + 1] = kind; rsrec[at + 2] = ref; rsrec[at + 3] = sym; + rt.fill_w(rsrec + at + 4, 0, RS_W - 4); + rsn++; + return 0; +} +// the ownership of local i at the record for the name at off +int os_at(int off, int i) { + int at, r, k = rsn; + if (!rsn || !cmode || dry || i < 0 || chkind != 3 || chbase != rsbase) return 0; + while (k && rsrec[(k - 1) * RS_W] != off) k--; + if (!k) return 0; + at = (k - 1) * RS_W; + rsrec[at + 11] = lstate[i] + 1; + if (lown[i]) rsrec[at + 11] = rsrec[at + 11] + 16; + if (lstate[i] == S_CHILD) { r = lroot(i); if (lrsoff[r] >= 0) rsrec[at + 12] = lrsoff[r] + 1; } + return 0; +} +// log a transfer of local i; the last few events are searched so a loop body is not logged twice +int ev_put(int i, int kind, int target) { + int k = evn, n = 0, t = 0; + if (!rsrec || !cmode || dry || chkind != 3 || chbase != rsbase || i < 0 || lrsoff[i] < 0) return 0; + if (target >= 0 && lrsoff[target] >= 0) t = lrsoff[target] + 1; + while (k && n < 8) { + k--; n++; + if (evrec[k * EV_W] == lrsoff[i] && evrec[k * EV_W + 1] == kind && evrec[k * EV_W + 2] == tline) return 0; + } + if (!evrec) { evcap = 64; evrec = chk_resize(0, 0, evcap * EV_W); } + if (evn >= evcap) { evrec = chk_resize(evrec, evn * EV_W, evcap * 2 * EV_W); evcap = evcap * 2; } + evrec[evn * EV_W] = lrsoff[i]; evrec[evn * EV_W + 1] = kind; evrec[evn * EV_W + 2] = tline; evrec[evn * EV_W + 3] = t; + evn++; return 0; +} +// write a record for the name token that is current. +int rs_note(int kind, int ref) { return rs_put(toff, kind, ref, 0 - 1); } +// the flags of every symbol record, read again once the check has defined the +// whole document (a function used before its definition is defined by now). +int rs_fix() { + int i = 0; + while (i < rsn) { + if (rsrec[i * RS_W + 1] == RS_SYM && rsrec[i * RS_W + 3] >= 0) { + int s = rsrec[i * RS_W + 3]; rsrec[i * RS_W + 2] = rs_sym(s); + if (skind[s] == K_CONST) vf_at(i, 1, sval[s]); + } + i++; + } + return 0; +} +// what a symbol is, as a record keeps it: its kind, plus 16 for a symbol the +// document declares (not a built-in) and 32 when that symbol has a definition +int rs_sym(int s) { + int v = skind[s]; + if (s >= ubase) v = v | 16; + if (sflag[s] & F_DEF) v = v | 32; + return v; +} +// copies record a of from to record b of to (the merge sort moves records this way). +int rs_move(word from, int a, word to, int b) { + rt.copy_w(to + b * RS_W, from + a * RS_W, RS_W); + return 0; +} +// sort the records by offset: a bottom-up merge sort through rstmp, so a long +// document costs n log n, whatever order its names come in. +int rs_sort() { + int w = 1, lo, mid, hi, i, j, k, n = rsn; + if (n < 2) return 0; + while (w < n) { + lo = 0; + while (lo < n) { + mid = lo + w; if (mid > n) mid = n; + hi = lo + w * 2; if (hi > n) hi = n; + i = lo; j = mid; k = lo; + while (i < mid && j < hi) { + if (rsrec[j * RS_W] < rsrec[i * RS_W]) { rs_move(rsrec, j, rstmp, k); j++; } + else { rs_move(rsrec, i, rstmp, k); i++; } + k++; + } + while (i < mid) { rs_move(rsrec, i, rstmp, k); i++; k++; } + while (j < hi) { rs_move(rsrec, j, rstmp, k); j++; k++; } + lo = hi; + } + i = 0; + while (i < n * RS_W) { rsrec[i] = rstmp[i]; i++; } + w = w * 2; + } + return 0; +} +// analyzer errors only count in checked, reachable code +int cerr(char* msg) { + if (fchk && live) serr(msg); + return 0; +} +// report an analyzer error about local i, named in the message. +int cerrn(int i, char* msg) { + int root = i, cause = 0; word info = 0; + if (fchk && live) { + if (rt.eq_w(msg, "used after it was given away")) { + root = lroot(i); if (local_end_info) info = local_end_info[i]; + if (!info && local_end_info) info = local_end_info[root]; + if (info) cause = info[2]; else if (local_ended) cause = local_ended[root]; + } + if (cause && !panic && !dry) { + errat(); rt.put_s(lname[i]); rt.put_s(": "); rt.put_s(msg); + rt.put_s("; owner "); if (info) rt.put_s(info[0]); else rt.put_s(lname[root]); + rt.put_s(" was transferred on line "); rt.put_n(cause & 0xFFFFFF); + if (info && info[1]) { rt.put_s(" (declared on line "); rt.put_n(info[1] & 0xFFFFFF); rt.put_c(')'); } + errend(); + } else serrn(lname[i], msg); + } + return 0; +} + +// ---- interning ---- + +// the pool: an open addressing hash table (R_IH) of pointers into a string +// pool (R_POOL). each spelling has two spare words: its symbol-chain head at +// p[-1] and editor classification bits at p[-2]. equal text returns the same +// stable handle, which lookup can use directly. +int hashs(char* s) { + int h = 0; + while (*s) { h = h * 31 + *s; s++; } + return h & (hashcap - 1); +} +// rehash the index without moving strings or their symbol-chain heads. +int reserve_names(int need) { + int cap = hashcap, previous = hashcap, i = 0, h; char* p; char** old; + if (need <= cap) return cap; + while (cap < need) cap = cap * 2; + old = namehash; + namehash = chk_resize(0, 0, cap); + hashcap = cap; + while (i < previous) { + p = old[i]; + if (p) { h = hashs(p); while (namehash[h]) h = (h + 1) & (hashcap - 1); namehash[h] = p; } + i++; + } + return cap; +} +// return the interned copy of a spelling. new chunks preserve every old handle. +char* intern(char* s) { + int h; word p; int n; + if ((namecount + 1) * 2 >= hashcap) reserve_names(hashcap * 2); + h = hashs(s); + while (1) { + p = namehash[h]; + if (!p) break; + if (p[0] == s[0] && rt.eq_w(p, s)) return p; + h = (h + 1) & (hashcap - 1); + } + n = rt.len_w(s); + if (pooltop + n + 3 > poolend) { + if (poolchunk < n + 3) poolchunk = n + 3; + else if (poolchunk < 0x20000000) poolchunk = poolchunk * 2; + p = chk_resize(0, 0, poolchunk + 1); p[0] = poolblocks; poolblocks = p; + pooltop = p + 1; poolend = pooltop + poolchunk; + } + pooltop[0] = 0; pooltop[1] = 0; + p = pooltop + 2; + rt.copy_w(p, s, n + 1); + pooltop = pooltop + n + 3; + namehash[h] = p; namecount++; + return p; +} + +// ---- symbols ---- + +// each interned name keeps the index (+1) of its newest symbol in p[-1], and +// each symbol links to the one it shadows, so lookup is one step. +// remember where symbol s is defined, when the prescan made it before its definition was read +int def_note(int s, int off) { + if (cmode && !dry && !ginst && sdoff && off >= 0 && !sdoff[s]) { sdoff[s] = off + 1; sdsrc[s] = curinc; } + return 0; +} +int addsym(char* name, int kind, int val) { + int i = nsym; + if (module_name && kind != K_KW) name = module_symbol(name); + if (nsym >= symcap) reserve_symbols(nsym + 1); + if (!fnmeta) fnmeta = chk_resize(0, 0, symcap); + nsym++; + sname[i] = name; + skind[i] = kind; + sval[i] = val; + ssig[i] = 0; + sflag[i] = F_DEF; + sfix[i] = 0; + fnmeta[i] = 0; + stype[i] = 0; + snext[i] = name[-1]; + name[-1] = i + 1; + if (cmode && !dry && !sdoff) { sdoff = chk_resize(0, 0, symcap); sdsrc = chk_resize(0, 0, symcap); } + if (sdoff) { + sdoff[i] = 0; sdsrc[i] = 0; + if (cmode && !dry && !ginst && nameoff >= 0) { sdoff[i] = nameoff + 1; sdsrc[i] = curinc; } + } + return i; +} +// while an image builds, names below vislo are hidden, except keywords +int lookup_raw(char* name) { + int i = name[-1] - 1, resident; + if (syntax_host_floor && i >= syntax_host_floor && i < syntax_host_from) { + resident = i; + while (resident >= syntax_host_floor) resident = snext[resident] - 1; + if (resident >= 0) return resident; + if (skind[i] == K_GVAR || skind[i] == K_GARR) serr("a syntax handler cannot access emitted program globals"); + } + while (i >= 0) { + if (i >= vislo || skind[i] == K_KW || skind[i] == K_TYPE) return i; + i = snext[i] - 1; + } + return -1; +} +// qualify a declaration within its file namespace; generated method names retain their prefix. +char* module_symbol(char* name) { + int i = 0, j; char* b; char* q; + while (name[i]) { if (name[i] == '.') return name; i++; } + if (!module_name) return name; + if (((name[0] == 'd' && name[1] == 'o' && name[2] == 'c') || (name[0] == 's' && name[1] == 'i' && name[2] == 'g')) && name[3] == 32) { + q = methname(module_name, name + 4); b = chk_resize(0, 0, rt.len_w(q) + 5); + rt.copy_w(b, name, 4); + j = 0; while (q[j]) { b[j + 4] = q[j]; j++; } b[j + 4] = 0; + return intern(b); + } + return methname(module_name, name); +} +int lookup(char* name) { + int s, i = 0; + if (module_name) { s = lookup_raw(module_symbol(name)); if (s >= 0) return s; } + s = lookup_raw(name); if (s >= 0) return s; + while (name[i]) { if (name[i] == '.') return -1; i++; } + return lookup_raw(methname("rt", name)); +} +// modules loaded from source are tracked independently of each file's imports. +word included_modules; +int module_loaded(char* name) { + word p = included_modules; + while (p) { if (rt.eq_w(p[1], name)) return 1; p = p[0]; } + return 0; +} +int module_known(char* name) { + return module_loaded(name) || rt.eq_w(name, "rt") || rt.eq_w(name, "run") || rt.eq_w(name, "core") || rt.eq_w(name, "lsp") || rt.eq_w(name, "std") || rt.eq_w(name, "icon"); +} +int module_imported(char* name) { + word p = module_imports; + if (module_name && rt.eq_w(name, module_name)) return 1; + while (p) { if (rt.eq_w(p[1], name)) return 1; p = p[0]; } + return 0; +} +// modules reported missing in this unit, so their names are not reported again as undefined +word missing_imports; +// true when name is qualified by a module this unit was already told to include. +int missing_import_name(char* name) { + int i = 0; word p = missing_imports; + while (name[i] && name[i] != '.') i++; + if (!name[i]) return 0; + while (p) { + char* module = p[1]; int k = 0; + while (k < i && module[k] == name[k]) k++; + if (k == i && !module[k]) return 1; + p = p[0]; + } + return 0; +} +// source references require their module's explicit import; compiler-generated calls use lookup directly. +int source_lookup(char* name) { + int i = 0, s; char* owner; + while (name[i] && name[i] != '.') i++; + if (name[i]) { + owner = chk_resize(0, 0, i + 1); rt.copy_w(owner, name, i); owner[i] = 0; + if ((module_known(owner) || module_loaded(owner)) && !module_imported(owner)) { + errat(); rt.put_s("module access requires `include "); rt.put_s(owner); rt.put_s(";`"); errend(); + word link = chk_resize(0, 0, 2); link[0] = missing_imports; link[1] = owner; missing_imports = link; + // the unit already fails; resolving the name anyway keeps later diagnostics about the real code + } + } + if (module_name) { s = lookup_raw(module_symbol(name)); if (s >= 0) return s; } + return lookup_raw(name); +} +// drop the symbols above index to, which ends a scope. +int popsyms(int to) { + while (nsym > to) { nsym--; sname[nsym][-1] = snext[nsym]; } + while (ntd && tdstk[ntd * 3 - 3] >= to) ntd--; // local typedefs made above to went with them + return 0; +} +// look a name up from a plain string. +int lookups(char* s) { return lookup(intern(s)); } + +// patch nodes +int fixnew(int site, int link) { + int i = fixfree; + if (i) fixfree = fnext[i]; + else { nfixu++; i = nfixu; if (i >= fixcap) reserve_fixes(i + 1); } + fsite[i] = site; fnext[i] = link; + return i; +} +// patch every lit in a chain to point at v, and recycle the nodes +int fixall(int head, int v) { + int n; + while (head) { + setlit(fsite[head], v); + n = fnext[head]; + fnext[head] = fixfree; + fixfree = head; + head = n; + } + return 0; +} + +// ---- code emission ---- + +// code goes straight into memory at "here". hdelta shifts where it is written +// (an image build emits at one address and stores at another, see nested +// compiles), and dry turns emission off entirely, which typeof and similar +// queries use to type an expression without producing code. +// reserve contiguous words before recording an address or writing a data block. +// new runtime segments are linked by jumps; earlier code and globals stay put. +int reserve_code(int need) { + word p; int cap, top; + if (dry) return 0; + if (need < 0 || need > 0x7fffffff - 8) fatal("bad code reservation"); + if (image_active) { + if (need <= hlimit - here) return hlimit - here; + cap = image_capacity; + while (cap < here - image_org + need) { + if (cap > (0x7fffffff - image_org) / 2) fatal("image too big"); + cap = cap * 2; + } + image_buffer = chk_resize(image_buffer, here - image_org, cap); + image_capacity = cap; hdelta = image_buffer - image_org; hlimit = image_org + cap; + return hlimit - here; + } + if (need <= hlimit - here - 2) return hlimit - here - 2; + if (here + 1 >= hlimit) fatal("no room for a code link"); + cap = code_chunk; + if (cap < need + 5) cap = need + 5; + p = chk_resize(0, 0, cap); top = p + p[-5] - rt.HDR; + if (rt.syscall(9, 9, top, 0) < top) fatal("code address reservation failed"); + p[0] = rt.code_blocks; p[1] = p + 3; p[2] = top; rt.code_blocks = p; + *here = (p + 3) * 2 + 1; here[1] = O_JMP * 2; + here = p + 3; hlimit = top; + if (cap < 0x20000000) code_chunk = cap * 2; + return hlimit - here - 2; +} +// a region handed to an optimizer must fit in one contiguous segment. +int code_contiguous(int lo, int hi) { + word p = rt.code_blocks; + if (image_active || (lo >= 0 && hi <= rt.CODE_END)) return 1; + while (p) { if (lo >= p[1] && hi <= p[2] - 2) return 1; p = p[0]; } + return 0; +} +int emit(int w) { + if (dry) return 0; + if (w == OP_FLIT * 2) reserve_code(2); else reserve_code(1); + *(here + hdelta) = w; + here++; + return 0; +} +// a vm code word with the low bit set pushes the value in its upper bits +// (v + v + 1); an even word is an opcode (k + k). jmp pushes a target and jumps. +int lit(int v) { + if ((v >> 62) != 0 && (v >> 62) != -1) { + reserve_code(5); + emit((v >> 32) * 2 + 1); emit(0x100000000 * 2 + 1); op(15); + emit((v & 0xFFFFFFFF) * 2 + 1); op(17); + } else emit(v + v + 1); + return 0; +} +// emit one vm opcode. +int op(int k) { emit(k + k); return 0; } +// fill the literal placeholder at address at with value v. +int setlit(int at, int v) { + if (dry) return 0; + if ((v >> 62) != 0 && (v >> 62) != -1) fatal("literal patch too large"); + *(at + hdelta) = v + v + 1; + return 0; +} +// emit a jump to the address t. +int jmp(int t) { + lit(t); + op(O_JMP); + return 0; +} +// a lit that will be patched later; returns its address +int hole() { + int a; + reserve_code(1); a = here; + lit(rt.MW - 1); + return a; +} +// forward references. calling a function that is not defined yet creates a +// symbol with F_DEF clear whose value is the place of its first use (for the +// "never defined" error). each call site emits a placeholder literal and adds +// itself to the symbol's chain (sfix); defining the function runs fixall to +// patch every site. MW-1 is a jump target that halts, so an unpatched call +// stops the machine instead of running garbage. +int fwdfn_at(char* name, int p) { + int s = addsym(name, K_FN, p); + sflag[s] = 0; + return s; +} +// the place of the first use (line, and offset above bit 24), until it's defined +int fwdfn(char* name) { + return fwdfn_at(name, tpack()); +} +// emit a reference to function s, patched later if it is not defined yet. +int fnref(int s) { + if (syntax_host_floor && s >= syntax_host_floor && s < syntax_host_from) { + s = syntax_host_function(s); + if (s < 0) { lit(rt.MW - 1); return 0; } + } + if (sflag[s] & F_DEF) { lit(sval[s]); return 0; } + if (dry) return 0; + reserve_code(1); sfix[s] = fixnew(here, sfix[s]); + lit(rt.MW - 1); // a jump to MW-1 halts if it ever runs unpatched + return 0; +} +// emit a call to the function with this name, making a forward slot if it is not defined. +int callname(char* name) { + int s = lookup(name); + if (s < 0) { name = methname("rt", name); s = lookup(name); } + if (s < 0) s = fwdfn(name); + fnref(s); + op(O_CALL); + return 0; +} +// the address of local slot k: the frame pointer lives in the word ZFP, and a +// local is fp + k. (o3 locals live in cpu registers instead; see vaddr.) +int laddr(int k) { + lit(rt.ZFP); + op(O_LD); + if (k) { lit(k); op(O_ADD); } + return 0; +} +// an o3 variable's address: lfa(k) is fp + k as one enhancement, which the +// jit specializes on the literal k +word vaddr(int i) { + int s; + if (ltag[i] & 16) { + s = lookup(s_lfa); + if (s >= 0 && skind[s] == K_ENH) { + lit(lslot[i]); + op(XOP0 + sval[s]); + return 0; + } + } + laddr(lslot[i]); if (local_cell && local_cell[i]) op(O_LD); return 0; +} +// registers for an o3 region's variables: the cpu has 7. a variable takes the +// one its region freed last (when a scope closed), or the next unused one +// ---- o3: the optimizing region compiler's bookkeeping ---- + +// code inside an o3 region (or a function marked o3) keeps hot locals in the +// cpu's registers. the compiler does not change what it emits for them; it +// records which frame slot maps to which register and hands the finished +// region to the core through install(). the core compiles the whole region +// with those slots held in registers and writes them back to the frame when +// control leaves it. there are 7 registers, so locals past that stay in +// memory. +int o3reset() { + no3free = 0; + o3nreg = 1; + no3sl = 0; + return 0; +} +// a freed register keeps its class. a float home is a d register and an int +// home is an x register, and one var index cannot be both, so a number that +// was a float is only handed out again as a float. +int o3take(int fp) { + int i = 0, k, w = -1; + while (i < no3free) { + k = o3free[i]; + if (fp && k < 0) w = i; + if (!fp && k > 0) w = i; + i++; + } + if (w >= 0) { + k = o3free[w]; + no3free--; + o3free[w] = o3free[no3free]; + if (k < 0) return 0 - k; + return k; + } + if (o3nreg < 8) { o3nreg++; return o3nreg - 1; } + return 0; +} +// true when a type is a plain number that o3 can keep in a register. +int o3plain(word t) { + if (!t) return 0; + if (t[0] == TY_INT || t[0] == TY_CHAR || t[0] == TY_BOOL || t[0] == TY_FLOAT) return 1; + return 0; +} +// move a local into an o3 register when the region can keep it there. +int o3promote(int i) { + int r, fp; + ltag[i] = ltag[i] - (ltag[i] & 1792); + if (!o3on || larr[i] || no3sl >= 32) return 0; + fp = isfl(ltype[i]); + r = o3take(fp); + if (!r) return 0; + ltag[i] = ltag[i] + r * 256; + o3sl[no3sl] = lslot[i]; + if (o3plain(ltype[i])) r = r + 16; // 16 marks a plain number: the core need not write it back before a call + if (fp) r = 0 - r; // the install word is negative when the home is a d register + o3rg[no3sl] = r; no3sl++; + return 0; +} +// a fixed T[?] can't change its items or its length, so o3 code keeps both in +// two hidden variables, loaded when the array variable is set (or when the +// region starts), and indexes through them +int o3mkcache(int i) { + int k; + if (!o3on || ltype[i][0] != TY_DYN || !ltype[i][3] || tbytes(ltype[i][4])) return 0; + k = newloc(s_hitems); fslots++; ltag[k] = 16; o3promote(k); + newloc(s_hlen); fslots++; ltag[k + 1] = 16; o3promote(k + 1); + o3cache[i] = k; + return 0; +} +// reload the o3 register cache of a local from its memory slot. +int o3refresh(int i) { + int k = o3cache[i]; + if (!k) return 0; + vaddr(i); op(O_LD); lit(2); op(O_ADD); op(O_LD); vaddr(k); op(O_ST); + vaddr(i); op(O_LD); op(O_LD); vaddr(k + 1); op(O_ST); + return 0; +} +// hands a region to the cpu: install kind 2 is [-, 2, lo, hi, n, slot, register, ..., fslots, argc, fnstart] +// fslots, argc and the prologue address sit in o3fs, o3na, o3fn0. a bare o3 block leaves the prologue at -1, so a function that starts at address 0 still binds. +int o3region(int lo, int hi, word sl, word rg, int n) { + int i = 0; + if (hdelta || lo >= hi || !code_contiguous(lo, hi)) return 0; + o3dsc[0] = 0; o3dsc[1] = 2; o3dsc[2] = lo; o3dsc[3] = hi; o3dsc[4] = n; + while (i < n) { o3dsc[5 + i + i] = sl[i]; o3dsc[6 + i + i] = rg[i]; i++; } + o3dsc[5 + n + n] = o3fs; o3dsc[6 + n + n] = o3na; o3dsc[7 + n + n] = o3fn0; + rt.install(o3dsc); + return 0; +} +// o3 blocks go to the cpu once their function is done, so every jump in them is final +int o3flush() { + int i = 0; word p; + o3fs = 0; o3na = 0; o3fn0 = -1; /* -1 is not a function; a prologue at address 0 is */ + while (i < no3pend) { p = o3pend + i * 67; o3region(p[0], p[1], p + 3, p + 35, p[2]); i++; } + no3pend = 0; + return 0; +} +// a string literal: the characters (one per word, then a 0) are emitted into +// the code stream behind a jump, and the literal's value is their address +int strlit(char* s) { + int j = hole(), a; + op(O_JMP); + reserve_code(rt.len_w(s) + 1); a = here; + while (*s) { emit(*s); s++; } + emit(0); + setlit(j, here); + lit(a); + return 0; +} + -- cgit v1.2.3