aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-15 11:29:26 +0330
committerGravatar zinc <zinc@downsta.ir> 2026-10-15 11:29:26 +0330
commita3f769ea02b5686084c5b3c2a382b9934b535050 (patch)
tree47caed7e1bc49dbb9ee7aecd76f88c2048963f21 /src
parentmerge work/stdio: std.Io and one-level module completion (diff)
parentxref: baseline records the borrow globals (end_why, end_whom, arg_site, local... (diff)
merge work/borrow: borrow returns before definition, borrows clause, borrow messages and LSP diagnostics, mkgenome header mismatch values
Diffstat (limited to 'src')
-rw-r--r--src/compiler/analyzer.zc80
-rw-r--r--src/compiler/base.zc54
-rw-r--r--src/compiler/builtins.zc8
-rw-r--r--src/compiler/decl.zc17
-rw-r--r--src/compiler/expr.zc20
-rw-r--r--src/compiler/types.zc32
-rw-r--r--src/lsp/server.zc30
-rw-r--r--src/tools/mkgenome.c30
8 files changed, 237 insertions, 34 deletions
diff --git a/src/compiler/analyzer.zc b/src/compiler/analyzer.zc
index 42cb7b5..ff6a6c1 100644
--- a/src/compiler/analyzer.zc
+++ b/src/compiler/analyzer.zc
@@ -83,6 +83,51 @@ int effect_specialize(int original, int instance, word requirements, word edges)
while (e && e != edges) { if (e[3] == original) e[3] = instance; e = e[0]; }
return 0;
}
+// borrow returns of functions called before their body. such a call's result
+// may turn out to alias an argument's owner, which is only known once the body
+// has been read. the result's local keeps a shadow record (callee, the mask of
+// parameters that got the owner, the owner, the token after the call, so only a call that is the whole right side counts); a
+// use of the local after its owner ended is remembered, and effect_finish
+// reports it when the callee's return aliases one of those parameters.
+word shadow_new(int callee, int mask, int owner) {
+ word e;
+ if (!fchk || !live || dry || owner < 0 || !mask) return 0;
+ e = talloc(4); e[0] = callee; e[1] = mask; e[2] = owner; e[3] = tpack();
+ return e;
+}
+// the local i is assigned: it takes the shadow of a forward call that is its whole right side.
+int shadow_take(int i) {
+ if (!local_shadow) return 0;
+ local_shadow[i] = 0;
+ // the local read just before is the target of this assignment, not a use
+ if (effect_shadows && effect_shadows[10] == i && effect_shadows[11] == lreadpos[i]) effect_shadows = effect_shadows[0];
+ if (xshadow && xshadow[3] == tpack()) local_shadow[i] = xshadow;
+ xshadow = 0; return 0;
+}
+int shadow_use(int i) {
+ word s = local_shadow[i], e; int o = s[2];
+ if (!fchk || !live || lstate[o] != S_DEAD) return 0;
+ local_shadow[i] = 0;
+ e = talloc(12); e[10] = i; e[11] = tpack(); e[0] = effect_shadows; e[1] = s[0]; e[2] = s[1]; e[3] = tpack(); e[4] = curinc; e[5] = effect_floor;
+ e[6] = lname[i]; e[7] = 0; if (local_end_info) e[7] = local_end_info[o]; e[8] = lname[o]; e[9] = local_ended[o];
+ effect_shadows = e; return 0;
+}
+int shadow_finish(int from) {
+ word e = effect_shadows, next; int flags, k;
+ effect_shadows = 0;
+ while (e) {
+ next = e[0];
+ if (e[5] < from) { e[0] = effect_shadows; effect_shadows = e; e = next; continue; }
+ flags = 0; k = 0;
+ while (k < 24) { if ((e[2] >> k) & 1) flags = flags | effect_get(e[1], k); k++; }
+ if ((flags & (EF_ALIAS | EF_IDENTITY)) && !(flags & EF_UNKNOWN) && !dry) {
+ tunpack(e[3]); curinc = e[4]; panic = 0;
+ errat(); ended_message(e[6], e[7], e[8], e[9]); rt.put_s("; "); rt.put_s(sname[e[1]]); rt.put_s(", defined after this call, returns a borrow of its argument"); errend();
+ }
+ e = next;
+ }
+ return 0;
+}
int effect_finish(int from) {
word e, r, next; int changed = 1, flags, prior, save = tpack(), inc = curinc, i = 0;
if (chkabort) return 0;
@@ -110,6 +155,7 @@ int effect_finish(int from) {
}
e = next;
}
+ shadow_finish(from);
tunpack(save); curinc = inc; return 0;
}
int borrow_begin() { return 0; }
@@ -393,6 +439,7 @@ int newloc(int n) {
if (!local_captured) local_captured = chk_resize(0, 0, loccap); local_captured[i] = 0;
if (!local_ended) local_ended = chk_resize(0, 0, loccap); local_ended[i] = 0;
if (!local_end_info) local_end_info = chk_resize(0, 0, loccap); local_end_info[i] = 0;
+ if (!local_shadow) local_shadow = chk_resize(0, 0, loccap); local_shadow[i] = 0;
lpos[i] = 0;
lreadpos[i] = 0;
lowner[i] = 0;
@@ -949,10 +996,17 @@ int lend_drop(int dep, int from) {
return 0;
}
// retire existing aliases together with their owner. dead aliases do not keep later loop snapshots live.
+// end_why and end_whom say how the next end_owner ends its owner, for the message
+// of a later use: 0 transferred, 1 freed, 2 given to a .d parameter of end_whom,
+// 3 moved into end_whom, 4 stored in a container, 5 returned, 6 stored in a global
+// or untracked memory, 7 freed by reassigning it, 8 pushed into end_whom.
+int end_why; char* end_whom;
int end_owner(int i) {
- int j = lbase; word info = talloc(3);
+ int j = lbase; word info = talloc(6);
lend_check(i);
- info[0] = lname[i]; info[1] = lpos[i]; info[2] = tpack();
+ info[0] = lname[i]; info[1] = lpos[i]; info[2] = tpack(); info[3] = end_why; info[4] = end_whom;
+ info[5] = lreadpos[i]; if (!info[5]) info[5] = info[2]; // the owner's own token where it ended, for the editor
+ end_why = 0; end_whom = 0;
local_ended[i] = info[2]; local_end_info[i] = info; lstate[i] = S_DEAD;
while (j < nloc) {
if (j != i && isalias(j) && lroot(j) == i) { local_ended[j] = info[2]; local_end_info[j] = info; lstate[j] = S_DEAD; }
@@ -1345,6 +1399,8 @@ int recast(int c, int t) {
else lstate[i] = t;
return c;
}
+// where the argument argcheck is looking at goes: 0 unknown, 1 a push, else the callee's symbol + 2
+int arg_site;
// an argument for a parameter tagged t (0 plain, 1 m, 2 d)
int argcheck(int c, int t) {
int i, st;
@@ -1373,7 +1429,12 @@ int argcheck(int c, int t) {
touch(i);
return 0;
}
- if (st == S_D || ((st == S_M || st == S_U) && lown[i])) { end_owner(i); luse(i); xgive = 1; ev_put(i, EV_DOWN, -1); return 0; }
+ if (st == S_D || ((st == S_M || st == S_U) && lown[i])) {
+ if (arg_site >= 2 && arg_site - 2 < ubase && (sname[arg_site - 2] == s_free || sname[arg_site - 2] == s_recycle)) end_why = 1;
+ else if (arg_site == 1) end_why = 8;
+ else if (arg_site >= 2) { end_why = 2; end_whom = sname[arg_site - 2]; }
+ end_owner(i); luse(i); xgive = 1; ev_put(i, EV_DOWN, -1); return 0;
+ }
if (st == S_M) cerrn(i, "is m; write d*x to give it away");
else if (st == S_U) cerrn(i, "is u; write d*x to give it away instead of returning it");
else if (st == S_CHILD) cerrn(i, "belongs to a container and can't be given away alone");
@@ -1437,7 +1498,7 @@ int reassign_free(int i, int rc, int bor) {
if (lleaf[i] && !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);
+ end_why = 7; 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;
@@ -1446,6 +1507,7 @@ int reassign_free(int i, int rc, int bor) {
// 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);
+ if (k == LV_LOCAL) shadow_take(i); else xshadow = 0;
if (k == LV_LOCAL && local_origin) { local_origin[i] = origin; local_origin_mask[i] = local_origin_mask[i] | effect_mask(rc); }
if (k == LV_ELEM) { effect_note_mask(effect_mask(C_VAR + i), EF_WRITE); }
if (k != LV_LOCAL && isptr(xty)) effect_note_mask(effect_mask(rc), EF_RETAIN);
@@ -1491,7 +1553,7 @@ int assigncheck(int k, int i, int rc) {
lstate[i] = S_M; lused[i] = 0; lown[i] = 1;
lnsz[i] = lnsz[j]; lleaf[i] = lleaf[j];
local_ended[i] = 0; local_end_info[i] = 0;
- end_owner(j); luse(j); ev_put(j, EV_MOVE, i);
+ end_why = 3; end_whom = lname[i]; end_owner(j); luse(j); ev_put(j, EV_MOVE, i);
if (ltag[i] & 15) lstate[i] = ltag[i] & 15;
return C_PLAIN;
}
@@ -1499,7 +1561,7 @@ int assigncheck(int k, int i, int rc) {
lstate[i] = S_M;
lused[i] = 0;
lown[i] = 1;
- end_owner(j);
+ end_why = 3; end_whom = lname[i]; end_owner(j);
luse(j); ev_put(j, EV_UP, i);
if (ltag[i] & 15) lstate[i] = ltag[i] & 15;
return C_PLAIN;
@@ -1523,7 +1585,7 @@ int assigncheck(int k, int i, int rc) {
if (js == S_U) {
cerrn(j, "is u; it has to leave through the block above, not into a borrowed object");
}
- else { end_owner(j); luse(j); ev_put(j, EV_STORE, i); emitown(i); } // storing it gives it away
+ else { end_why = 4; end_whom = lname[i]; end_owner(j); luse(j); ev_put(j, EV_STORE, i); emitown(i); } // storing it gives it away
} else if (js != st) cerrn(j, "its tag doesn't match its container's (both have to be m, u or d)");
else if (lhome[j] < lhome[r]) cerrn(j, "lives in an outer block than the container it's stored into");
else { lstate[j] = S_CHILD; lowner[j] = i; luse(j); ev_put(j, EV_STORE, i); emitown(i); }
@@ -1535,7 +1597,7 @@ int assigncheck(int k, int i, int rc) {
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); }
+ if (lstate[j] == S_D || lstate[j] == S_U || (lstate[j] == S_M && lown[j])) { ev_put(j, EV_GLOBAL, -1); end_why = 6; 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)");
}
return C_PLAIN;
@@ -1582,7 +1644,7 @@ int retcheck(int c) {
}
if (fret) {
if (i >= 0) {
- if (lstate[i] == S_U) { end_owner(i); luse(i); ev_put(i, EV_RETURN, -1); }
+ if (lstate[i] == S_U) { end_why = 5; end_owner(i); luse(i); ev_put(i, EV_RETURN, -1); }
else cerrn(i, "has to be u to be returned");
}
return 0;
diff --git a/src/compiler/base.zc b/src/compiler/base.zc
index dc786b5..7e4b5bc 100644
--- a/src/compiler/base.zc
+++ b/src/compiler/base.zc
@@ -254,7 +254,7 @@ 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;
+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.
@@ -415,6 +415,11 @@ int s_new_k, s_recycle_leaf;
// 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);
@@ -433,8 +438,22 @@ int dg_expand() {
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;
@@ -470,7 +489,7 @@ word[256] dgseen_initial; word dgseen = dgseen_initial; int dgseen_cap = 256, dg
// 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;
+ 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;
}
@@ -758,6 +777,7 @@ int reserve_locals(int need) {
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);
@@ -958,6 +978,27 @@ 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) {
+ 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); }
+ 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;
@@ -967,13 +1008,8 @@ int cerrn(int i, char* msg) {
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);
+ if (cause && !panic && !dry) { errat(); dg_place(lreadpos[i]); ended_message(lname[i], info, lname[root], cause); errend(); }
+ else serrn(lname[i], msg);
}
return 0;
}
diff --git a/src/compiler/builtins.zc b/src/compiler/builtins.zc
index be8c3b4..f40e223 100644
--- a/src/compiler/builtins.zc
+++ b/src/compiler/builtins.zc
@@ -736,7 +736,7 @@ int array_push(word t) {
effect_note_mask(effect_value, EF_WRITE); af_mutate(xaf, tpack()); nogrow(t);
if (t[0] == TY_DYN && isagg(t[4])) serr("push takes scalar or reference elements; use resize for aggregates");
c = expr(); rval(); if (t[0] == TY_DYN) fcoerce(t[4]);
- if (isptr(xty) || xty == tword) effect_note_mask(effect_mask(c), EF_RETAIN); argcheck(c, 2);
+ if (isptr(xty) || xty == tword) effect_note_mask(effect_mask(c), EF_RETAIN); arg_site = 1; argcheck(c, 2); arg_site = 0;
if (t[0] == TY_DYN && !compat(t[4], xty)) tcerr("push", t[4], xty);
if (o3on && t[0] == TY_DYN && hasptr(t[4])) { lit(t[4]); callname(s_fatten); }
expect(s_rp);
@@ -1119,6 +1119,7 @@ int kw_extern() {
}
}
expect(s_rp);
+ int borrowed = borrows_clause(0, np, 1);
if (accept(s_eq)) {
if (tk != T_STR) { die("expected the c name as a string"); return 0; }
cname = tv;
@@ -1194,7 +1195,10 @@ int kw_extern() {
ebn10(i);
i++;
}
- ebs(") { let b = "); ebn10(a); ebs(";");
+ ebs(")");
+ // the stub keeps the declaration's borrows clause, so calls see it in the signature
+ if (borrowed) { ebs(" borrows "); i = 0; g = 0; while (i < np) { if ((borrowed >> i) & 1) { if (g) ebs(", "); ebs("p"); ebn10(i); g = 1; } i++; } }
+ ebs(" { let b = "); ebn10(a); ebs(";");
// the native descriptor borrows an integer address, not ownership of r.
// clear that scratch address after the call so it cannot keep the result rooted.
if (sret) { ebs(" "); ebs(rn); ebs("* r = rt.new("); ebs(rn); ebs("); b[3] = core.addr_b(r) >> 3;"); }
diff --git a/src/compiler/decl.zc b/src/compiler/decl.zc
index dfcff4f..52d08c7 100644
--- a/src/compiler/decl.zc
+++ b/src/compiler/decl.zc
@@ -282,6 +282,18 @@ int body() {
// (this is also where a function's signature is packed into ssig and its type
// into stype, checked against any earlier prototype or prescan result, and
// where the o3 region for an o3 function is handed to the core)
+// a body against its borrows clause: it may return a borrow only of the parameters the clause names.
+int borrows_check(int s, int np, char** pn, word pty) {
+ int mask = sig_borrows(ssig[s]), i = 0;
+ if (!mask || !fchk || !effect_current || effect_current[0] != 1) return 0;
+ while (i < np && i < 24) {
+ if (!((mask >> i) & 1) && (effect_current[26 + i] & (EF_ALIAS | EF_IDENTITY)) && (isptr(pty[i]) || pty[i] == tword)) {
+ if (!panic && !dry) { errat(); rt.put_s(sname[s]); rt.put_s(": returns a borrow of "); if (pn[i]) rt.put_s(pn[i]); else rt.put_n(i); rt.put_s(", but its borrows clause doesn't name it"); errend(); }
+ }
+ i++;
+ }
+ return 0;
+}
int fndef(int chk, int rty, int ret, int n) {
int s, np = 0, tags = 0, t, sig, i; char*[24] pn; word[24] pt; word[24] pty; int typed = 0, ft, explicit_rty = !!rty, o3f = o3nxt, lo, hi, pk, lt, lend, lfl = tline, redef, amb = 0, nonm = 0;
o3nxt = 0;
@@ -348,6 +360,10 @@ int fndef(int chk, int rty, int ret, int n) {
amb = amb >> 1; i++;
}
sig = ret + np + np + tags * 64;
+ lo = borrows_clause(pn, np, 1);
+ // a definition after a prototype keeps the prototype's borrows clause
+ if (!lo && (sflag[s] & F_SIG) && !(sflag[s] & F_PRE)) lo = sig_borrows(ssig[s]);
+ sig = sig + (lo << SIG_BORROWS);
if ((sflag[s] & F_SIG) && !(sflag[s] & F_PRE)) {
if (ssig[s] != sig) serrn(n, "doesn't match its earlier declaration");
}
@@ -408,6 +424,7 @@ int fndef(int chk, int rty, int ret, int n) {
i = 0; while (i < np) { t = lbase + i; o3mkcache(t); o3refresh(t); i++; }
}
body();
+ borrows_check(s, np, pn, pty);
if (!explicit_rty && fn_return_type > 0 && fn_return_type != tword) {
stype[s] = tfn(fn_return_type, np, pty, ssig[s]);
sig_attach(n, fn_return_type, np, pn, pty, 1);
diff --git a/src/compiler/expr.zc b/src/compiler/expr.zc
index 7471556..a6cc55c 100644
--- a/src/compiler/expr.zc
+++ b/src/compiler/expr.zc
@@ -63,7 +63,7 @@ int stmtonly() {
// fat asks for every pointer argument to be fattened first, for o3 code
// calling code that is not o3.)
int args(int tags, word ft, int bs, int fat) {
- int np = 0, c, pt, t, cs = ifcall, freeing = 0, harmless = 0; word first = 0; int origin = -1, anytemp = 0, current, tag, place, parameter_base = effect_argument_base, owner = -1, current_owner, owner_seen = 0, aliases; word origins = 0, current_mask, owner_fact = 0;
+ int np = 0, c, pt, t, cs = ifcall, freeing = 0, harmless = 0; word first = 0; int origin = -1, anytemp = 0, current, tag, place, parameter_base = effect_argument_base, owner = -1, current_owner, owner_seen = 0, aliases; word origins = 0, current_mask, owner_fact = 0; int owner_mask = 0, borrowed = callee_borrows(cs, ft);
if (cs >= 0 && cs < ubase) {
freeing = sname[cs] == s_free || sname[cs] == s_recycle;
harmless = sname[cs] == s_put_s || rt.eq_w(sname[cs], "rt.put_n") || rt.eq_w(sname[cs], "rt.put_c") || rt.eq_w(sname[cs], "len") || rt.eq_w(sname[cs], "cap");
@@ -88,9 +88,11 @@ int args(int tags, word ft, int bs, int fat) {
if (c >= C_VAR && tracked(c - C_VAR)) current_owner = lroot(c - C_VAR);
if (tag <= 1 && current_owner >= 0 && tracked(current_owner)) effect_require_mode(cs, np + parameter_base, place, 1);
aliases = effect_get(cs, np + parameter_base);
+ if (borrowed) { aliases = 0; if (np + parameter_base < 12 && ((borrowed >> (np + parameter_base)) & 1)) aliases = EF_ALIAS; }
if (aliases & (EF_ALIAS | EF_IDENTITY | EF_UNKNOWN)) {
- if (!owner_seen) { owner = current_owner; owner_fact = xaf; owner_seen = 1; }
+ if (!owner_seen) { owner = current_owner; owner_fact = xaf; owner_seen = 1; owner_mask = 0; }
else if (owner != current_owner) { owner = -1; owner_fact = 0; }
+ if (owner >= 0 && owner == current_owner && np + parameter_base < 24) owner_mask = owner_mask | (1 << (np + parameter_base));
}
if (fchk && c == C_FRESH && tag != 2) {
if (!(cs < 0 && tag == 1)) effect_require(cs, np + parameter_base, place); emit_borrow_hold(); c = C_PLAIN; anytemp = 1;
@@ -110,7 +112,7 @@ int args(int tags, word ft, int bs, int fat) {
}
if (ft && np + parameter_base < ft[3]) fcoerce(subst(ft[7 + np + parameter_base], bs));
t = xty;
- argcheck(c, (tags >> (np + np)) & 3);
+ arg_site = 0; if (cs >= 0) arg_site = cs + 2; argcheck(c, (tags >> (np + np)) & 3); arg_site = 0;
// o3 data handed to normal code
if (fat && hasptr(t)) {
lit(t);
@@ -127,7 +129,7 @@ int args(int tags, word ft, int bs, int fat) {
}
}
expect(s_rp);
- argfact = first; argorigin = origin; argorigin_mask = origins; argtemporary = anytemp; argowner = owner; argowner_fact = owner_fact; xaf = 0; vf_effect();
+ argfact = first; argorigin = origin; argorigin_mask = origins; argtemporary = anytemp; argowner = owner; argowner_mask = owner_mask; argowner_fact = owner_fact; xaf = 0; vf_effect();
return np;
}
// memdebug records the source line on the next new, via the runtime global
@@ -487,13 +489,16 @@ int call(int s) {
xlenloc = -1; xstamp = 0;
if (o3on && (sflag[s] & F_O3) && ft && hasptr(xty)) xstamp = 1;
borrow_depth--; effect_value = 0;
- if (!ret && (isptr(xty) || xty == tword)) {
+ if (!ret && (isptr(xty) || xty == tword || callee_borrows(original, ft))) {
effect_value = origins;
- if (owner >= 0 && (isptr(xty) || (effect_get(original, 0) & EF_IDENTITY))) xbor = owner;
+ if (owner >= 0 && (isptr(xty) || (effect_get(original, 0) & EF_IDENTITY) || callee_borrows(original, ft))) xbor = owner;
if (owner_fact && (effect_get(original, 0) & EF_IDENTITY)) xaf = owner_fact;
if (origin >= 0 && owner < 0) { int oi = lbase; while (oi < nloc) { if (lrskind[oi] == RS_PARAM && oi - lbase == origin) { xbor = oi; break; } oi++; } }
if (temporary) { int ai = 0, aliases = 0; while (ai < np) { aliases = aliases | effect_get(original, ai); ai++; } if (aliases & (EF_ALIAS | EF_UNKNOWN)) xaf = af_new(-1, 5, cp); }
}
+ // a result from a function whose body comes later is checked once the body is read
+ xshadow = 0;
+ if (!ret && xbor < 0 && owner >= 0 && original >= ubase && !(sflag[original] & (F_DEF | F_EXT)) && tracked(owner) && (isptr(xty) || xty == tword)) xshadow = shadow_new(original, argowner_mask, owner);
if_value = 0;
if (ifs && !dry) {
word result_node = if_get(original);
@@ -1009,6 +1014,7 @@ int primary() {
if (i >= 0) {
if (if_local) if_value = if_local[i];
lreadpos[i] = tpack();
+ if (local_shadow && local_shadow[i]) shadow_use(i);
luse(i); effect_note_mask(effect_mask(C_VAR + i), EF_READ);
rs_note(lrskind[i], lrsoff[i]); os_at(toff, i);
xaf = lafact[i]; af_record(toff, xaf);
@@ -1588,7 +1594,7 @@ int postfix_tail(int c) {
xty = tword;
if (ft) xty = subst(ft[4], bs);
if (ft && (ft[5] & 1)) c = C_FRESH;
- else if (isptr(xty) || xty == tword) { effect_value = call_origins; if (isptr(xty)) xbor = call_owner; if (temporary) xaf = af_new(-1, 5, place); }
+ else if (isptr(xty) || xty == tword) { effect_value = call_origins; if (isptr(xty) || (ft && sig_borrows(ft[5]))) xbor = call_owner; if (temporary) xaf = af_new(-1, 5, place); }
xfloat = 0; if (isfl(xty)) xfloat = 1; xstr = 0;
continue;
}
diff --git a/src/compiler/types.zc b/src/compiler/types.zc
index 81547a5..322d0fd 100644
--- a/src/compiler/types.zc
+++ b/src/compiler/types.zc
@@ -105,6 +105,35 @@ word tsized(char* name, int bytes, int sgn) {
return t;
}
// the function type with this return, parameter list and signature.
+// ---- borrow returns ----
+// `borrows s` after a parameter list says the result is a borrow of parameter s
+// (several, comma separated, in a declaration). a parameter is named, or given by
+// its position from 0 where it has no name (an extern, a function type). the mask
+// lives in bits 50 to 61 of the signature, so a function type carries it too.
+const int SIG_BORROWS = 50;
+int sig_borrows(int sig) { return (sig >> SIG_BORROWS) & 4095; }
+// the declared borrow mask of a call's callee: its symbol's signature, or the function type.
+int callee_borrows(int s, word ft) {
+ if (s >= 0 && s < nsym && (sflag[s] & F_SIG)) return sig_borrows(ssig[s]);
+ if (ft && ft[0] == TY_FN) return sig_borrows(ft[5]);
+ return 0;
+}
+int borrows_clause(char** names, int np, int many) {
+ int mask = 0, k;
+ if (tk != T_ID || !rt.eq_w(tv, "borrows")) return 0;
+ next();
+ while (1) {
+ k = 0 - 1;
+ if (tk == T_NUM) { if (tv >= 0 && tv < np) k = tv; }
+ else if (tk == T_ID && names) { int j = 0; while (j < np) { if (names[j] && rt.eq_w(names[j], tv)) k = j; j++; } }
+ if (k < 0) { serr("borrows names a parameter, by name or by its position from 0"); next(); return mask; }
+ if (k >= 12) serr("only the first 12 parameters can be borrowed by the result");
+ else mask = mask | (1 << k);
+ next();
+ if (!many || !accept(s_comma)) break;
+ }
+ return mask;
+}
word tfn(int ret, int np, word ps, int sig) {
int i = 0, j; word t;
while (i < ntl) {
@@ -388,7 +417,8 @@ int ptype() {
}
}
expect(s_rp);
- t = tfn(t, np, ps, np + np + tags * 64);
+ tag = borrows_clause(0, np, 0);
+ t = tfn(t, np, ps, np + np + tags * 64 + (tag << SIG_BORROWS));
}
ht_note(start, toff, t);
return t;
diff --git a/src/lsp/server.zc b/src/lsp/server.zc
index 6f4ce67..65881a9 100644
--- a/src/lsp/server.zc
+++ b/src/lsp/server.zc
@@ -258,6 +258,35 @@ int wpos(int off) {
wc('}');
return 0;
}
+// a packed compiler place (tpack) as an lsp location in document gd.
+int wplace(int p) {
+ int o = ((p >> 40) & 0x1FFFFF) - 1, n = (p >> 24) & 0xFFFF;
+ if (o < 0) o = 0; if (o > plen) o = plen;
+ if (n < 1) n = 1; if (o + n > plen) n = plen - o;
+ ws("{\"uri\":"); wjs(duri[gd], dulen[gd]); ws(",\"range\":{\"start\":"); wpos(o); ws(",\"end\":"); wpos(o + n); ws("}}");
+ return 0;
+}
+// the related places the compiler recorded for the record at offset at (run.dg_related):
+// where the owner ended and where it was declared, as relatedInformation.
+int diag_related(int at) {
+ int k = 0, why; word e; char* what;
+ while (k < run.dgreln) {
+ e = run.dgrel + k * run.DGREL_W;
+ if (e[0] == at) {
+ why = e[3]; what = " ended here";
+ if (why == 1) what = " was freed here"; else if (why == 2) what = " was given to a .d parameter here";
+ else if (why == 3) what = " was moved here"; else if (why == 4) what = " was stored into a container here";
+ else if (why == 5) what = " was returned here"; else if (why == 6) what = " was stored in a global here";
+ else if (why == 7) what = " was freed by this reassignment"; else if (why == 8) what = " was pushed into an array here";
+ ws(",\"relatedInformation\":[{\"location\":"); wplace(e[1]); ws(",\"message\":\"owner "); ws(e[4]); ws(what); ws("\"}");
+ if (e[2]) { ws(",{\"location\":"); wplace(e[2]); ws(",\"message\":\"owner "); ws(e[4]); ws(" is declared here\"}"); }
+ wc(']');
+ return 0;
+ }
+ k++;
+ }
+ return 0;
+}
// writes the publishDiagnostics message for document gd from dgb. each record
// becomes a range (the whole line when the compiler only knew the line), a
// severity and its message.
@@ -295,6 +324,7 @@ int gen_diag() {
ws("{\"range\":{\"start\":"); wpos(o); ws(",\"end\":"); wpos(e);
ws("},\"severity\":"); wn(sev); ws(",\"source\":\"zc\",\"message\":");
wjs(dgb + at + 5, n);
+ diag_related(at);
wc('}');
at = at + 5 + n;
}
diff --git a/src/tools/mkgenome.c b/src/tools/mkgenome.c
index 9720760..051de62 100644
--- a/src/tools/mkgenome.c
+++ b/src/tools/mkgenome.c
@@ -40,15 +40,33 @@ int main(int argc, char **argv)
}
}
if (ferror(stdin)) { fputs("mkgenome: read failed\n", stderr); return 1; }
- int ok = n >= 8 * 7 && n % 8 == 0;
+ int ok = n >= 8 * 7 && n % 8 == 0, bad[3] = {0, 0, 0};
+ uint64_t got3[3] = {0, 0, 0}, want[3] = {(uint64_t)FTOP * 2 + 1, (uint64_t)ZFP * 2 + 1, 4};
for (int i = 0; ok && i < 3; i++) {
- uint64_t u = 0;
- for (int k = 0; k < 8; k++) u |= (uint64_t)b[i * 8 + k] << (k * 8);
- if (u != (i == 0 ? (uint64_t)FTOP * 2 + 1 : i == 1 ? (uint64_t)ZFP * 2 + 1 : 4)) ok = 0;
+ for (int k = 0; k < 8; k++) got3[i] |= (uint64_t)b[i * 8 + k] << (k * 8);
+ if (got3[i] != want[i]) bad[i] = 1;
}
+ int text = n >= 4 && !memcmp(b, "zc: ", 4);
+ if (ok && (bad[0] || bad[1] || bad[2])) ok = 0;
if (!ok) {
- if (n >= 4 && !memcmp(b, "zc: ", 4)) fwrite(b, 1, n, stderr);
- fprintf(stderr, n % 8 || n < 8 * 7 ? "mkgenome: incomplete genome\n" : "mkgenome: input is not a genome for this map\n");
+ if (text) fwrite(b, 1, n, stderr);
+ if (n % 8 || n < 8 * 7) { fputs("mkgenome: incomplete genome\n", stderr); return 1; }
+ fputs("mkgenome: input is not a genome for this map\n", stderr);
+ if (!text) {
+ /* the header: FTOP and ZFP as literal words (2v+1), then 4. say which differ
+ and by what, since a mismatch usually means rt.zc and the map disagree */
+ static const char *names[3] = {"FTOP (frame stack top)", "ZFP (zero page)", "the header's third word"};
+ for (int i = 0; i < 3; i++) {
+ if (!bad[i]) continue;
+ if (i < 2 && (got3[i] & 1)) fprintf(stderr, " word %d, %s: image has 0x%llX, the map has 0x%llX\n", i, names[i], (unsigned long long)(got3[i] >> 1), (unsigned long long)(want[i] >> 1));
+ else if (i < 2) fprintf(stderr, " word %d, %s: image has the raw word 0x%llX, not a literal; the map has 0x%llX\n", i, names[i], (unsigned long long)got3[i], (unsigned long long)(want[i] >> 1));
+ else fprintf(stderr, " word 2, %s: image has 0x%llX, expected 4\n", names[i], (unsigned long long)got3[i]);
+ }
+ if (bad[0] || bad[1])
+ fputs(" the compiler read a different layout than the one in build/core/map: check that FTOP and ZFP in\n"
+ " src/runtime/rt.zc match the map (a stale src/runtime/rt.zc, e.g. a zip unpacked over an older\n"
+ " checkout, or a stale build/core/map: run make clean, then make)\n", stderr);
+ }
return 1;
}
if (fwrite(b, 1, n, stdout) != n || fflush(stdout)) { fputs("mkgenome: write failed\n", stderr); return 1; }