From 05f7b7e09ad503e4eac4f757de0d7329174f6437 Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 05:55:19 +0000 Subject: analyzer: close the ownership gaps that stopped the compiler compiling itself an owned local can now move into a released owner declared in the same or an outer block, and g = m*x lends a local owner to a global until the global is assigned again in the same block. leaving that block, giving the local away, or returning while the lend is open is an error. recasting a plain local now names it and says it has no ownership to recast. methdef and syntax_resident use the supported spellings, so make genome reaches its fixpoint from a clean checkout. runerr.sh reads the first // expect: line, since the include headers moved it off line 1. --- src/compiler/compiler.zc | 80 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 6 deletions(-) (limited to 'src/compiler') diff --git a/src/compiler/compiler.zc b/src/compiler/compiler.zc index 2a05c58..c33005d 100644 --- a/src/compiler/compiler.zc +++ b/src/compiler/compiler.zc @@ -231,6 +231,10 @@ 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. @@ -2729,9 +2733,62 @@ int lroot(int i) { while (lstate[i] == S_CHILD) i = lowner[i]; return i; } +// lend a local owner to global s for the rest of the current block. +int lend_add(int s, int i) { + if (nlend * 3 + 3 > lendcap) { if (!lendcap) lendcap = 48; else lendcap = lendcap * 2; lendrec = chk_resize(lendrec, nlend * 3, lendcap); } + lendrec[nlend * 3] = s; lendrec[nlend * 3 + 1] = i; lendrec[nlend * 3 + 2] = fdepth; nlend++; + return 0; +} +// remove entry k, keeping the others in order. +int lend_remove(int k) { + if (k + 1 < nlend) rt.copy_w(lendrec + k * 3, lendrec + k * 3 + 3, (nlend - k - 1) * 3); + nlend--; + return 0; +} +// an assignment to global s in the block that lent it ends the lend. +int lend_clear(int s) { + int k = nlend; + while (k > 0) { k--; if (lendrec[k * 3] == s && lendrec[k * 3 + 2] == fdepth && lendrec[k * 3 + 1] >= lbase) lend_remove(k); } + return 0; +} +// report local i still lent to the global of entry k. +int lend_err(int k, char* msg) { + int i = lendrec[k * 3 + 1]; + if (!fchk || !live || panic || dry) return 0; + errat(); rt.put_s(lname[i]); rt.put_s(": is lent to global "); rt.put_s(sname[lendrec[k * 3]]); rt.put_s(msg); errend(); + return 0; +} +// local i is about to be given away or released: no global may still borrow it. +int lend_check(int i) { + int k = 0; + while (k < nlend) { + if (lendrec[k * 3 + 1] == i && i >= lbase) { lend_err(k, "; assign the global again before giving the local away"); lend_remove(k); } + else k++; + } + return 0; +} +// leaving every block at depth dep or deeper: a lend made there must have ended. +int lend_exit(int dep) { + int k = 0; + while (k < nlend) { + if (lendrec[k * 3 + 2] >= dep && lendrec[k * 3 + 1] >= lbase) lend_err(k, "; assign the global again before the block that lent it ends"); + k++; + } + return 0; +} +// forget the lends made at depth dep or deeper, or by locals from index from onward. +int lend_drop(int dep, int from) { + int k = 0; + while (k < nlend) { + if ((lendrec[k * 3 + 2] >= dep && lendrec[k * 3 + 1] >= lbase) || lendrec[k * 3 + 1] >= from) lend_remove(k); + else k++; + } + return 0; +} // retire existing aliases together with their owner. dead aliases do not keep later loop snapshots live. int end_owner(int i) { int j = lbase; word info = talloc(3); + lend_check(i); info[0] = lname[i]; info[1] = lpos[i]; info[2] = tpack(); local_ended[i] = info[2]; local_end_info[i] = info; lstate[i] = S_DEAD; while (j < nloc) { @@ -2795,6 +2852,7 @@ int freevar(int i) { int exit_vars(int dep, int final) { int i = nloc, st; if (!fchk || !live) return 0; + lend_exit(dep); while (i > lbase) { i--; if (lhome[i] < dep) continue; @@ -2868,6 +2926,7 @@ int block_close(int save) { i = lbase; unused_check(save); exit_vars(fdepth, 1); + lend_drop(fdepth, save); alias_end(fdepth, save); while (i < save) { if (lhome[i] >= fdepth) { lstate[i] = S_DEAD; lhome[i] = fdepth - 1; } i++; } local_drop(save); @@ -3107,7 +3166,7 @@ int recast(int c, int t) { int i, st; if (c == C_FRESH && t == S_M) return C_PLAIN; // m*f(): the unnamed result is let go on purpose, so it can be discarded if (!fchk) return c; - if (c < C_VAR) { cerr("only a named owned object can be recast"); return c; } + if (c < C_VAR) { cerr("only a named owned object can be recast; this expression has no owner"); return c; } i = c - C_VAR; st = lstate[i]; luse(i); @@ -3229,7 +3288,7 @@ int assigncheck(int k, int i, int rc) { if (js == S_BORROW || isalias(j)) { lstate[i] = S_CHILD; lowner[i] = lroot(j); lown[i] = 0; touch(j); return C_PLAIN; } // replace a released owner by moving a same-scope owner into its empty slot. // fresh declarations still cannot copy owners; the source and its aliases expire. - if (st == S_DEAD && lown[i] && js == S_M && lown[j] && lhome[i] == lhome[j] + if (st == S_DEAD && lown[i] && js == S_M && lown[j] && lhome[j] >= lhome[i] && !local_captured[i] && !local_captured[j]) { lstate[i] = S_M; lused[i] = 0; lown[i] = 1; lnsz[i] = lnsz[j]; lleaf[i] = lleaf[j]; @@ -3274,6 +3333,9 @@ int assigncheck(int k, int i, int rc) { } // a global, or memory the analyzer doesn't track: the collector takes it // an owner (d, u, or an m local that owns its object) is transferred as if by u*x + if (k == LV_GLOBAL) lend_clear(i); + if (j >= 0 && k == LV_GLOBAL && xlend == j && lstate[j] == S_M && lown[j]) { xlend = -1; touch(j); lend_add(i, j); return C_PLAIN; } + xlend = -1; if (j >= 0) { if (lstate[j] == S_D || lstate[j] == S_U || (lstate[j] == S_M && lown[j])) { ev_put(j, EV_GLOBAL, -1); end_owner(j); luse(j); } else cerrn(j, "is not an owner, so it can't be stored in a global or in untracked memory (the collector takes it over)"); @@ -4185,7 +4247,7 @@ int methdef(char* sn, char* mn, word q, int stat, int typed, int rty, int rtag) if (prefix) rt.copy_w(joined + 1, r + 1, 3); rt.copy_w(joined + 1 + prefix * 3, return_tokens + 1, return_tokens[0] * 3); rt.copy_w(joined + 1 + (prefix + return_tokens[0]) * 3, r + 1 + (prefix + 1) * 3, (r[0] - prefix - 1) * 3); - word previous = m*r; r = d*joined; rt.free(return_tokens); rt.free(buffer); rt.free(previous); + rt.free(r); r = joined; rt.free(return_tokens); rt.free(buffer); } src_push(1, r, 0, 1); next(); @@ -7084,9 +7146,12 @@ int unary() { if (pk_tk == T_OP && pk_tv == s_star && pq_gl[0]) { t = S_M; if (tv == s_u) t = S_U; if (tv == s_d) t = S_D; next(); expect(s_star); - c = unary(); rval(); + c = unary(); place = -1; if (lvk == LV_LOCAL) place = lvi; rval(); lvk = LV_NONE; - return recast(c, t); + if (c < C_VAR && c != C_FRESH && place >= 0 && fchk) { cerrn(place, "isn't an owned object, so there is no ownership to recast"); return c; } + c = recast(c, t); + if (t == S_M && c >= C_VAR && lstate[c - C_VAR] == S_M && lown[c - C_VAR]) xlend = c - C_VAR; + return c; } } if (tk == T_OP && tv == s_lp && caststart()) { // (int)x, (node*)x: same bits, the type changes @@ -7334,6 +7399,7 @@ int expr() { next(); a = o3on && k == LV_LOCAL && (ltag[i] & 16) && !larr[i]; if (a) op(O_DROP); // an o3 variable: its address comes after the value, so it never waits on the stack across a branch + xlend = -1; rc = expr(); rval(); if (k != LV_LOCAL) af_escape(xaf); fcoerce(lt); @@ -8456,6 +8522,7 @@ int ctx_pop() { f = R_CTX + ctxsp * CTX_WORDS; local_drop(f[0]); fdrop(nloc); + lend_drop(1 << 30, f[0]); lbase = f[1]; fdepth = f[2]; fslots = f[3]; @@ -8484,6 +8551,7 @@ int ctx_pop() { int fn_begin(int chk, int ret) { capture_enabled = 0; effect_value = 0; effect_current = 0; borrow_slots = 0; borrow_pending = 0; borrow_depth = 0; borrow_jump = -1; borrow_entry = 0; if (cmode && svkind && !dry) rt.fill_w(svkind, 0, nsym); xvk = 0; + lend_drop(1 << 30, nloc); lbase = nloc; fdepth = 1; fslots = 0; fchk = chk && check_on; fret = ret; fty = 0; finfer = 0; fn_return_type = 0; live = 1; bxdepth = 0; epichain = 0; infn = 1; loopbase = lsp; lv = 0; lvk = LV_NONE; xbor = -1; return 0; @@ -9168,7 +9236,7 @@ int syntax_resident(int symbol) { word block = chk_resize(0, 0, 4096); syntax_host_symbols = chk_resize(0, 0, from); syntax_host_floor = ubase; syntax_host_from = from; top = block + block[-5] - rt.HDR; - block[0] = rt.code_blocks; block[1] = block + 3; block[2] = top; rt.code_blocks = m*block; + block[0] = rt.code_blocks; block[1] = block + 3; block[2] = top; rt.code_blocks = block; if (rt.syscall(9, 9, top, 0) < top) fatal("syntax code address reservation failed"); here = block + 3; hdelta = 0; hlimit = top; image_active = 0; vislo = 0; clone = syntax_host_function(symbol); if (clone >= 0 && (sflag[clone] & F_DEF)) entry = sval[clone]; -- cgit v1.2.3