namespace run; include rt; // the compiler pokes at headers and raw memory on purpose and runs at full speed: no index bounds checks unbounded; // ============================================================================ // 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, F_GUESS = 65536; // 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 bound_on = 1; // set to 0 by unbounded; to switch index bounds checks 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 // line tables: lines on; (the default) records, for each function compiled, its entry, its end and the // source line of each statement, as a line block among the code that rt.line_blocks chains (see // rt.zc). lt_buf holds the pairs of the functions being compiled, innermost last, each as a frame // [outer frame, entry, name, file, 0, offset << 32 | line ...]; lt_base is the innermost frame. // lt_head is the newest block of an image being built, whose own rt.line_blocks it will become. int lines_on = 1; // //:lines NAME (see mark_line): the markers the lexer has met, [lines before the file, NAME] each, and the // one in force, which a top-level item applies when it is reached word lt_marks; int lt_nmarks, lt_markcap, lt_next_mark; char* lt_mark_file; int lt_mark_base; word lt_buf, lt_head, lt_file_at; int lt_top, lt_cap, lt_base = 0 - 1, lt_file_img; char* lt_file; 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, fbnd, bnxt, 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, local_shadow, xshadow, effect_shadows; int argowner_mask; word effect_value, argorigin_mask; int capture_enabled; word[1] capture_dummy; // drops (docs/design/destructors.md): the code position just past a drop form (a fresh // value with a drop still on the stack there), the type `this` gets in the next closure, // a local whose drop is set, a mailbox send whose arguments are checked, and o3 block depth. int drop_tail = -1, send_check, o3blk; word closure_this, local_dropset; 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; // defer: the statements registered and not yet run, DFR_W words each (the block // depth of the defer, its token list, whether the analyzer was on, whether compiling // it failed, so the error is reported once, and the locals there were at the defer), // newest last. dfr_base is where the current function's entries start. dfr_on is set // while a deferred statement is compiled; dfr_lsp and dfr_ctx say where its own loops // and function begin. const int DFR_MAX = 256, DFR_W = 5; word[1280] dfr; int ndfr, dfr_base, dfr_on, dfr_lsp, dfr_ctx; 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_unbounded, 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_lines, 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, s_bchk, s_bnd; 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. // hold blocks: hold_cur is the rank of the block being compiled (0 outside one), fhold the // rank of the function being compiled, hold_desc the word that will point at the block's // table of shared variables, hold_count the blocks so far. a shared variable's symbol has // F_SHARED and its block's rank above it in sflag. const int F_SHARED = 131072, F_SHARED_RANK = 262144; int hold_cur, fhold, hold_count, hold_desc, hold_nshared; word[128] hold_tab; 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_ck, ft_ln, ft_nops, fs_ok, xstamp; int xub, xubat = 0 - 1; // an upper bound of the value just compiled (oub), valid while here == xubat // what each live loop's body may change, and its hoisted checks (one 56-word // record per loop depth, the first 64 depths; see body_scan and hoist_guard) const int LPI_N = 64, LPI_W = 60; word lpinfo; // allocated at the first loop // --explain-checks: on, the unit's source (to turn offsets into columns), its // file name and the function being compiled int xc_on; word xc_src; char* xc_fn = ""; char* xc_file = ""; int xc_hook; // names body_scan looks for int sx_push, sx_pop, sx_resize, sx_defer, sx_asm, sx_quote, sx_new, sx_int, sx_char, sx_bool, sx_float, sx_if, sx_else, sx_while, sx_for, sx_switch, sx_do; // 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 LWASREAD = 0x10000; // an earlier value was read: a fresh value assigned over it clears LREAD, so its drop can still be set (6.15.1) 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; // related places of check-mode records, for the editor: DGREL_W words each, [the record's // offset in dgbuf, the packed place where the owner ended, the packed place of the owner's // declaration, how it ended (end_why), the owner's name]. reset by chk_begin. const int DGREL_W = 5; word dgrel; int dgreln, dgrelcap; // 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 // an open record's range becomes the packed place p (the use itself, not the token after it). int dg_place(int p) { if (!cmode || !dgopen || incdepth > 0 || !p || (p & 0xFFFFFF) != tline) return 0; dgbuf[dgpos + 1] = p & 0xFFFFFF; dgbuf[dgpos + 2] = ((p >> 40) & 0x1FFFFF) - 1; dgbuf[dgpos + 3] = (p >> 24) & 0xFFFF; return 0; } int dg_related(int end_place, int decl_place, int why, char* owner) { if (!cmode || !dgopen || dgdrop || incdepth > 0) return 0; if (dgreln == dgrelcap) { int cap = dgrelcap * 2; if (!cap) cap = 16; dgrel = chk_resize(dgrel, dgreln * DGREL_W, cap * DGREL_W); dgrelcap = cap; } word e = dgrel + dgreln * DGREL_W; e[0] = dgpos; e[1] = end_place; e[2] = decl_place; e[3] = why; e[4] = owner; dgreln++; return 0; } int dg_close() { if (!dgopen) return 0; if (dgdrop && dgreln && dgrel[(dgreln - 1) * DGREL_W] == dgpos) dgreln--; // its record is thrown away 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; dgreln = 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; } // stop with exit status 1 after compile errors (a runtime trap keeps rt.halt). int errexit() { rt.syscall(rt.SYS_EXIT, 1, 0, 0); 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"); errexit(); } 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"); errexit(); 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); errexit(); 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_dropset) local_dropset = chk_resize(local_dropset, 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); if (local_shadow) local_shadow = chk_resize(local_shadow, 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; } // how an owner ended, from end_owner's record: the use, the owner, what ended it and where. // "r: used after it was given away; r borrows t, and t was freed on line 5 (t declared on line 4)" int ended_message(char* use, word info, char* root, int cause, int own) { char* owner = root; int why = 0; char* whom = 0; if (info) { owner = info[0]; why = info[3]; whom = info[4]; dg_related(info[5], info[1], why, owner); } if (why == 9) { rt.put_s(use); rt.put_s(": "); if (rt.eq_w(use, owner)) rt.put_s("was freed"); else { if (own) rt.put_s("belongs to "); else rt.put_s("borrows from "); rt.put_s(owner); rt.put_s(", which was freed"); } rt.put_s(" at the end of its block (line "); rt.put_n(cause & 0xFFFFFF); rt.put_c(')'); return 0; } rt.put_s(use); rt.put_s(": used after it was given away; "); if (!rt.eq_w(use, owner)) { rt.put_s(use); rt.put_s(" borrows "); rt.put_s(owner); rt.put_s(", and "); } rt.put_s(owner); if (why == 1) rt.put_s(" was freed"); else if (why == 2) { rt.put_s(" was given to "); rt.put_s(whom); rt.put_s("'s .d parameter"); } else if (why == 3) { rt.put_s(" was moved into "); rt.put_s(whom); } else if (why == 4) { rt.put_s(" was stored into "); rt.put_s(whom); } else if (why == 5) rt.put_s(" was returned"); else if (why == 6) rt.put_s(" was stored in a global or untracked memory"); else if (why == 7) rt.put_s(" was freed by reassigning it"); else if (why == 8) rt.put_s(" was pushed into an array"); else rt.put_s(" was transferred"); rt.put_s(" on line "); rt.put_n(cause & 0xFFFFFF); if (info && info[1]) { rt.put_s(" ("); if (!rt.eq_w(use, owner)) { rt.put_s(owner); rt.put_c(' '); } rt.put_s("declared on line "); rt.put_n(info[1] & 0xFFFFFF); rt.put_c(')'); } 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(); dg_place(lreadpos[i]); ended_message(lname[i], info, lname[root], cause, lown[i]); 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; } // ---- line tables ---- // make room for n more words of pairs. int lt_room(int n) { int cap = lt_cap; if (lt_top + n <= cap) return 0; if (cap < 256) cap = 256; while (lt_top + n > cap) cap = cap * 2; lt_buf = chk_resize(lt_buf, lt_top, cap); lt_cap = cap; return 0; } // start recording the function whose code begins at entry, named name, with its header on line. // returns the frame to give lt_end, or -1 when no table is being made (lines off, a dry run, // check mode). int lt_begin(int entry, char* name, int line) { int b; if (!lines_on || dry || cmode) return 0 - 1; lt_room(8); b = lt_top; lt_buf[b] = lt_base; lt_buf[b + 1] = entry; lt_buf[b + 2] = name; lt_buf[b + 3] = curinc; lt_buf[b + 4] = 0; if (!curinc) { lt_buf[b + 3] = lt_mark_file; lt_buf[b + 4] = lt_mark_base; } // the main source may be marked as a file of its own lt_top = b + 5; lt_base = b; lt_buf[lt_top] = line - lt_buf[b + 4]; lt_top++; // the code before the first statement belongs to the header's line return b; } // a statement begins at the current token, and its code at here. int lt_stmt() { int off, n; if (lt_base < 0 || dry) return 0; off = here - lt_buf[lt_base + 1]; if (off < 0) return 0; lt_room(2); if (lt_top > lt_base + 5 && (lt_buf[lt_top - 1] >> 32) == off) lt_top--; // same place: the later statement is the one n = tline - lt_buf[lt_base + 4]; if (lt_top > lt_base + 5 && (lt_buf[lt_top - 1] & 4294967295) == n) return 0; lt_buf[lt_top] = off * 4294967296 + n; lt_top++; return 0; } // the file name in a block: the include's path, or for the main source. one copy of the // bytes is shared by the blocks that follow it. int lt_file_data(char* f) { if (!f) f = ""; if (lt_file_at && lt_file_img == image_active && rt.eq_w(lt_file, f)) return lt_file_at; lt_file_at = bstrdata(f); lt_file_img = image_active; lt_file = chk_resize(0, 0, rt.len_w(f) + 1); rt.copy_w(lt_file, f, rt.len_w(f) + 1); return lt_file_at; } // finish the function begun at frame b, whose code ends at end: put its line block among the code // and chain it. a block is [next, entry, end, name, file, count, pairs...]. int lt_end(int b, int end) { int entry, count, i = 0, name, file, blk, need; word head; char* nm; char* fl; if (b < 0) return 0; entry = lt_buf[b + 1]; nm = lt_buf[b + 2]; fl = lt_buf[b + 3]; count = lt_top - (b + 5); if (!nm) nm = ""; if (!fl) fl = ""; if (end >= entry && !dry) { need = 8 + count + ((rt.len_w(nm) + 7) >> 3) + 2 + ((rt.len_w(fl) + 7) >> 3) + 2; reserve_code(need); name = bstrdata(nm); file = lt_file_data(fl); if (image_active) head = lt_head; else head = rt.line_blocks; blk = here; emit(head); emit(entry); emit(end); emit(name); emit(file); emit(count); while (i < count) { emit(lt_buf[b + 5 + i]); i++; } if (image_active) lt_head = blk; else rt.line_blocks = blk; } lt_top = b; lt_base = lt_buf[b]; return 0; } // at the top level, a marker that comes before the item takes effect: lines are on, in its file. // a marker starts a new file, so the file switches of the one before it (unbounded; and // unchecked; in run.zc or rt.zc) end there: a program joined to the runtime is checked. int lt_apply_marks() { if (incdepth || ginst) return 0; while (lt_next_mark < lt_nmarks && lt_marks[lt_next_mark * 2] < tline) { lt_mark_base = lt_marks[lt_next_mark * 2]; lt_mark_file = lt_marks[lt_next_mark * 2 + 1]; lines_on = 1; lt_next_mark++; bound_on = 1; check_on = 1; } return 0; } // 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; }