aboutsummaryrefslogtreecommitdiff
path: root/src/compiler
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-15 02:35:04 +0330
committerGravatar zinc <zinc@downsta.ir> 2026-10-15 02:40:51 +0330
commit31ae48845492c12f2745c7f4c0cd2ea448e0942a (patch)
treebcd26f483f4774fbcbae9c621050b10b3b53f295 /src/compiler
parentgenmap: default genome reservation 0x70000 words (the genome had 197 words le... (diff)
destructors: reassigning an m owner that still holds a value frees the old value first, running its drop (right side first, then the free, then the store), with hint: x: reassigning frees the old value; a new value that is part of the old one is an error; u, d and captured owners keep the leak error; the new value's size, leaf type and drop are recorded like a declaration's; spec 6.8.5, 6.15.2
Diffstat (limited to 'src/compiler')
-rw-r--r--src/compiler/analyzer.zc35
-rw-r--r--src/compiler/base.zc1
-rw-r--r--src/compiler/expr.zc11
3 files changed, 43 insertions, 4 deletions
diff --git a/src/compiler/analyzer.zc b/src/compiler/analyzer.zc
index 34c0a8b..666a89d 100644
--- a/src/compiler/analyzer.zc
+++ b/src/compiler/analyzer.zc
@@ -987,12 +987,12 @@ int local_hints(int from) {
if (!cmode || dry || panic || chkabort || stubgen) return 0;
while (i < nloc) {
f = ltag[i];
- if (lrskind[i] == RS_PARAM && lname[i] != s_this && !rt.eq_w(lname[i], "_") && !(f & LREAD)) {
+ if (lrskind[i] == RS_PARAM && lname[i] != s_this && !rt.eq_w(lname[i], "_") && !(f & (LREAD | LWASREAD))) {
if (hint_begin(lpos[i], HINT_SMALL)) {
rt.put_s("unused parameter `"); rt.put_s(lname[i]); rt.put_s("`"); dg_close();
}
} else if ((f & LDECL) && !larr[i] && !rt.eq_w(lname[i], "_")) { // _ discards its value
- if (!(f & LREAD)) hint_name(i, " is never read", HINT_SMALL);
+ if (!(f & (LREAD | LWASREAD))) hint_name(i, " is never read", HINT_SMALL);
else if ((f & LCONST) && !(f & LASG) && !(ltype[i] && treadonly(ltype[i]))) hint_name(i, " is never reassigned; make it a const", HINT_ALL);
else if (fchk && lstate[i] == S_M && lown[i] && lnsz[i] > 0 && lnsz[i] <= 64) hint_name(i, " never leaves this function; a stack buffer (let name[n]) avoids the heap", HINT_SMALL);
else if (fchk && lstate[i] == S_M && lown[i] && lused[i] && tline - lused[i] >= 4) hint_name(i, " is last used several lines above the end of its block; close the block sooner so it is freed sooner", HINT_ALL);
@@ -1347,6 +1347,36 @@ int emitown(int p) {
if (o3on && hasptr(xty) && !xstamp) { lit(xty); callname(s_fatten); }
return 0;
}
+// reassigning local i while it still owns a value (6.8.5): the right side is
+// already on the stack, so free the old value here, running its drop, before the
+// store; then assigncheck sees an empty owner. only an m owner that a closure
+// doesn't capture is freed: a u or d object still has to leave. a new value that
+// comes from the old one (a child, a field, a borrow of it) would be freed with it.
+int asg_quiet;
+int reassign_free(int i, int rc, int bor) {
+ word t = xty, af = xaf; int st = xstr, fl = xfloat, j = 0 - 1;
+ if (!fchk || lstate[i] != S_M || isalias(i) || (local_captured && local_captured[i])) return 0;
+ if (rc >= C_VAR && tracked(rc - C_VAR)) j = rc - C_VAR;
+ if ((j >= 0 && lroot(j) == i) || (bor >= 0 && tracked(bor) && lroot(bor) == i)) {
+ cerrn(i, "reassigning frees the old value, and the new value is part of it");
+ asg_quiet = 1; // assigncheck says nothing more about this store
+ return 0;
+ }
+ if (hint_begin(tpack(), HINT_SMALL)) { rt.put_s(lname[i]); rt.put_s(": reassigning frees the old value"); dg_close(); }
+ ev_put(i, EV_FREE, -1);
+ if (o3on && ldroppable(i)) cerrn(i, "has drop and can't end inside an o3 region");
+ // freevar without its clearing store: the new value is stored next, through
+ // the address taken before the right side (a captured local's slot holds its cell)
+ vaddr(i); op(O_LD);
+ if (lleaf[i] && !o3on && !dry && lookup(s_recycle_leaf) >= 0) { lit(lleaf[i]); lit(szclass(lnsz[i])); callname(s_recycle_leaf); }
+ else callname(s_recycle);
+ op(O_DROP);
+ end_owner(i);
+ if (local_dropset) local_dropset[i] = 0;
+ lnsz[i] = 0; lleaf[i] = 0;
+ xty = t; xaf = af; xstr = st; xfloat = fl; xbor = bor;
+ return 1;
+}
// a store of class rc into an lvalue of kind k (local or container index i)
int assigncheck(int k, int i, int rc) {
int st, j, r, js, bor, origin = effect_origin(rc);
@@ -1367,6 +1397,7 @@ int assigncheck(int k, int i, int rc) {
if (k == LV_LOCAL) {
st = lstate[i];
if (isalias(i)) st = S_PLAIN; // a borrow isn't an owner; overwriting it drops the alias
+ if (asg_quiet) { asg_quiet = 0; return C_PLAIN; } // reassign_free reported it
if (j < 0 && rc != C_FRESH) {
if (!empty(st)) cerrn(i, "still owns an object; overwriting it would leak it");
else if (bor >= 0 && bor != i && (tracked(bor) || larr[bor])) { lstate[i] = S_CHILD; lowner[i] = bor; lown[i] = 0; }
diff --git a/src/compiler/base.zc b/src/compiler/base.zc
index 99ee10f..a8dc19f 100644
--- a/src/compiler/base.zc
+++ b/src/compiler/base.zc
@@ -391,6 +391,7 @@ int nerr, panic;
// 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
diff --git a/src/compiler/expr.zc b/src/compiler/expr.zc
index e6acfc8..7687394 100644
--- a/src/compiler/expr.zc
+++ b/src/compiler/expr.zc
@@ -1868,8 +1868,8 @@ 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();
+ xlend = -1; newsz = -1; newleaf = 0;
+ rc = expr(); rval(); fk = drop_tail == here;
if (k != LV_LOCAL) af_escape(xaf);
fcoerce(lt);
if (k == LV_LOCAL) {
@@ -1881,6 +1881,13 @@ int expr() {
if (cmode && !dry && k == LV_GLOBAL) { vf_symbols(); svkind[i] = xvk; svvalue[i] = xvv; }
else if (k != LV_LOCAL) vf_effect();
if (!compat(lt, xty)) tcerr("assignment", lt, xty);
+ if (k == LV_LOCAL) {
+ reassign_free(i, rc, xbor);
+ if (rc == C_FRESH && fchk) { // the new value's size and drop, as a declaration records them
+ lnsz[i] = newsz; lleaf[i] = newleaf; if (local_dropset) local_dropset[i] = fk;
+ if (ltag[i] & LREAD) ltag[i] = (ltag[i] - LREAD) | LWASREAD;
+ }
+ }
if (a) { lit(0); op(O_PICK); vaddr(i); op(O_ST); o3refresh(i); }
else {
if (o3on && k != LV_ELEM && hasptr(lt)) { lit(lt); callname(s_fatten); } // o3 data going somewhere normal code can see