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