aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-15 21:44:40 +0330
committerGravatar zinc <zinc@downsta.ir> 2026-10-15 21:44:40 +0330
commitf3dcfd9805e1bd23b002f648d4391ec397122d9c (patch)
tree91acab7da09c9225a2d838791d70e5c2e7db54b9 /src
parentmerge work/small-oob: bytes test out-of-bounds read (diff)
parentexamples/actors_demo: print the fan-out replies in worker order, not arrival ... (diff)
merge work/known-bugs: constant-expression precedence, local const in constant expressions, duplicate case labels, unterminated strings, droppable cycles in one collection, js vm exit, docs, actors_demo orderHEADmain
Diffstat (limited to 'src')
-rw-r--r--src/compiler/base.zc3
-rw-r--r--src/compiler/expr.zc68
-rw-r--r--src/compiler/lex.zc6
-rw-r--r--src/compiler/stmt.zc14
-rw-r--r--src/js/tinyvm.ts5
-rw-r--r--src/runtime/rt.zc14
-rw-r--r--src/tools/zc0.c40
7 files changed, 118 insertions, 32 deletions
diff --git a/src/compiler/base.zc b/src/compiler/base.zc
index a209f2b..3135b39 100644
--- a/src/compiler/base.zc
+++ b/src/compiler/base.zc
@@ -400,7 +400,7 @@ int nerr, panic;
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] 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] lkval_initial; word lkval = lkval_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].
@@ -772,6 +772,7 @@ int reserve_locals(int need) {
lreadpos = chk_resize(lreadpos, nloc, cap);
lnsz = chk_resize(lnsz, nloc, cap);
lleaf = chk_resize(lleaf, nloc, cap);
+ lkval = chk_resize(lkval, nloc, cap);
lrsoff = chk_resize(lrsoff, nloc, cap);
lrskind = chk_resize(lrskind, nloc, cap);
lptr = chk_resize(lptr, nloc, cap);
diff --git a/src/compiler/expr.zc b/src/compiler/expr.zc
index 2935a9b..e437d2f 100644
--- a/src/compiler/expr.zc
+++ b/src/compiler/expr.zc
@@ -1072,6 +1072,7 @@ int primary() {
if (isfl(ltype[i])) xfloat = 1;
xstr = getstr(i); if (xty == tbstr) xstr = 1;
lv = 1; lvk = LV_LOCAL; lvi = i;
+ if ((ltag[i] & LCONST) && treadonly(xty)) { xconst = 1; xcval = lkval[i]; } // a local const's value is known (spec 3.14.2)
if (isalias(i)) { touch(i); xbor = lroot(i); return C_VAR + i; }
if (tracked(i) || lrskind[i] == RS_PARAM || (local_origin_mask && local_origin_mask[i])) return C_VAR + i;
return C_PLAIN;
@@ -1762,17 +1763,31 @@ int unary() {
// level tighter and emit its code sequence from the operator table. && and ||
// short-circuit with jumps; floats get their own opcodes; a * or / by a
// constant that is a power of two gets a shift hint in check mode.
+// fold a binary operator on two known integers; *ok is set to 0 when it can't be folded
+int cfold(int o, int a, int b, word ok) {
+ if (o == bname[0]) return a * b;
+ if (o == bname[1] || o == bname[2]) { if (b == 0) { ok[0] = 0; return 0; } if (o == bname[1]) return a / b; return a % b; }
+ if (o == bname[3]) return a + b;
+ if (o == bname[4]) return a - b;
+ if (o == bname[5]) return a << b;
+ if (o == bname[6]) return a >> b;
+ if (o == bname[13]) return a & b;
+ if (o == bname[14]) return a ^ b;
+ if (o == bname[15]) return a | b;
+ ok[0] = 0; return 0;
+}
int if_comparison(int name) {
return name == s_eqeq || name == s_ne || name == s_lt || name == s_gt || name == s_le || name == s_ge;
}
int bin(int minp) {
- int c = unary(), b, p, a, j, lt, lb, rb, src, lf, rf, ls, rs, fresh, top = 0, il = -1, opp, lu, ru, xu; word borrowed, rightborrow, left_mask, right_mask, infer_left, infer_right;
+ int lk = 0, lkv = 0, c = unary(), b, p, a, j, lt, lb, rb, src, lf, rf, ls, rs, fresh, top = 0, il = -1, opp, lu, ru, xu; word borrowed, rightborrow, left_mask, right_mask, infer_left, infer_right;
if (fortest == 1) { fortest = 2; top = 1; ft_nops = 0; }
while (tk == T_OP) {
b = binfind(tv);
if (b < 0) return c;
p = bprec[b];
if (p < minp) return c;
+ lk = 0;
lb = -1;
if (c >= C_VAR && tracked(c - C_VAR)) lb = lroot(c - C_VAR); else lb = xbor;
borrowed = xaf; left_mask = effect_mask(c); lf = xfloat; ls = xstr; fresh = 0;
@@ -1807,6 +1822,7 @@ int bin(int minp) {
setlit(j, here);
xfloat = 0; xstr = 0;
} else {
+ lk = xconst && !lf && !ls; lkv = xcval;
c = bin(p + 1); infer_right = if_value; rightborrow = xaf; right_mask = effect_mask(c);
rb = -1;
if (c >= C_VAR && tracked(c - C_VAR)) rb = lroot(c - C_VAR); else rb = xbor;
@@ -1818,6 +1834,7 @@ int bin(int minp) {
shifthint(opp, xcval);
}
rf = xfloat; rs = xstr;
+ if (lk && xconst && !rf && !rs && !isfl(xty)) { lk = 2; lkv = cfold(bname[b], lkv, xcval, &lk); }
if (isfl(xty)) rf = 1;
if ((ls || rs) && (bname[b] == s_plus || bname[b] == s_eqeq || bname[b] == s_ne || bname[b] == s_lt || bname[b] == s_gt || bname[b] == s_le || bname[b] == s_ge)) {
if (!(ls && rs)) serr("a byte string needs a byte string on the other side");
@@ -1856,6 +1873,7 @@ int bin(int minp) {
if (xu > 0x40000000) xu = 0 - 1;
xub = xu; xubat = here;
lv = 0; lvk = LV_NONE; c = C_PLAIN; xzero = 0; xconst = 0; xlenloc = -1; xstamp = 0; xaf = 0; xvk = 0;
+ if (lk == 2 && blen[b] > 0) { xconst = 1; xcval = lkv; } // both sides constant: the value is known
effect_value = 0; if (isptr(xty) || xty == tword) effect_value = left_mask | right_mask;
if (fresh) c = C_FRESH;
if (borrowed && borrowed[2] == 5 && (isptr(xty) || xty == tword) && (bname[b] == s_plus || bname[b] == s_minus)) xaf = borrowed;
@@ -2032,7 +2050,7 @@ int blockexpr() {
// constant expressions, evaluated while compiling: numbers, named constants,
// addresses of globals and functions, unary minus and ~, parentheses, and the
-// operators + - * | & << >>. used for array lengths, enum values, const
+// operators * / % + - << >> & ^ | at c precedence. used for array lengths, enum values, const
// initializers and asm operands.
word cprim() {
int v, s, mc;
@@ -2040,6 +2058,10 @@ word cprim() {
if (accept(s_minus)) return 0 - cprim();
if (tk == T_OP && tv == uname[2]) { next(); return ~cprim(); }
if (accept(s_lp)) { v = cexpr(); expect(s_rp); return v; }
+ if (tk == T_ID && nloc > 0) { // a local const with a constant initializer (spec 3.14.2)
+ s = findloc(tv);
+ if (s >= 0 && (ltag[s] & LCONST) && !larr[s] && ltype[s] && treadonly(ltype[s])) { next(); return lkval[s]; }
+ }
if (tk == T_ID) {
s = lookup(tv);
if (mstruct && !ginst) { mc = lookup(methname(mstruct, tv)); if (mc >= 0 && skind[mc] == K_CONST) s = mc; }
@@ -2065,20 +2087,38 @@ word cprim() {
die("expected a constant");
return 0;
}
-// compile an expression: a primary, then the operators that follow it.
-int cexpr() {
- int v = cprim(), o;
+// a constant expression with c precedence: * / % above + -, above << >>,
+// above &, above ^, above | (spec 3.14). binary operators by precedence
+// climbing, each level reading its right side one level tighter.
+int cprec(int o) {
+ if (o == bname[0] || o == bname[1] || o == bname[2]) return 10;
+ if (o == bname[3] || o == bname[4]) return 9;
+ if (o == bname[5] || o == bname[6]) return 8;
+ if (o == bname[13]) return 5;
+ if (o == bname[14]) return 4;
+ if (o == bname[15]) return 3;
+ return 0;
+}
+int cbin(int minp) {
+ int v = cprim(), o, p, w;
while (tk == T_OP) {
- o = tv;
- if (o == bname[3]) { next(); v = v + cprim(); }
- else if (o == bname[4]) { next(); v = v - cprim(); }
- else if (o == bname[0]) { next(); v = v * cprim(); }
- else if (o == bname[15]) { next(); v = v | cprim(); }
- else if (o == bname[13]) { next(); v = v & cprim(); }
- else if (o == bname[5]) { next(); v = v << cprim(); }
- else if (o == bname[6]) { next(); v = v >> cprim(); }
- else return v;
+ o = tv; p = cprec(o);
+ if (p == 0 || p < minp) return v;
+ next(); w = cbin(p + 1);
+ if (o == bname[0]) v = v * w;
+ else if (o == bname[1] || o == bname[2]) {
+ if (w == 0) { die("division by zero in a constant expression"); return 0; }
+ if (o == bname[1]) v = v / w; else v = v % w;
+ }
+ else if (o == bname[3]) v = v + w;
+ else if (o == bname[4]) v = v - w;
+ else if (o == bname[5]) v = v << w;
+ else if (o == bname[6]) v = v >> w;
+ else if (o == bname[13]) v = v & w;
+ else if (o == bname[14]) v = v ^ w;
+ else v = v | w;
}
return v;
}
+int cexpr() { return cbin(1); }
diff --git a/src/compiler/lex.zc b/src/compiler/lex.zc
index 712eff8..95415a6 100644
--- a/src/compiler/lex.zc
+++ b/src/compiler/lex.zc
@@ -533,8 +533,9 @@ int lex1() {
while (isidc(ch)) { n = token_char(n, ch); nextch(); }
tbuf[n] = 0;
if (n == 1 && tbuf[0] == 'b' && ch == 34) {
- n = 0; nextch();
+ n = 0; dg = line; nextch();
while (ch >= 0 && ch != 34) { v = escch(); n = token_char(n, v); }
+ if (ch < 0) { tline = dg; serr("unterminated string literal"); } // spec 1.4.4
nextch();
tbuf[n] = 0;
tk = T_BSTR; tv = intern(tbuf); return 0;
@@ -556,8 +557,9 @@ int lex1() {
return 0;
}
if (ch == 34) {
- nextch();
+ dg = line; nextch();
while (ch >= 0 && ch != 34) { v = escch(); n = token_char(n, v); }
+ if (ch < 0) { tline = dg; serr("unterminated string literal"); } // spec 1.4.4
nextch();
tbuf[n] = 0;
tk = T_STR; tv = intern(tbuf); return 0;
diff --git a/src/compiler/stmt.zc b/src/compiler/stmt.zc
index 007306c..79e5fb8 100644
--- a/src/compiler/stmt.zc
+++ b/src/compiler/stmt.zc
@@ -144,7 +144,7 @@ int localdecl(int typed) {
newsz = -1; newleaf = 0; if (tk == T_NUM) nomagic = 1;
if (t && t[0] == TY_DYN && is_op(s_lb)) c = array_dynamic_literal(t); else c = expr();
rval(); nomagic = 0; kc = xconst && !xfloat;
- if (kc) ltag[i] = ltag[i] | LCONST;
+ if (kc) { ltag[i] = ltag[i] | LCONST; lkval[i] = xcval; }
if (c == C_FRESH) { lnsz[i] = newsz; lleaf[i] = newleaf; if (drop_tail == here) local_dropset[i] = 1; }
if (t) fcoerce(t);
vaddr(i);
@@ -566,11 +566,19 @@ int casebody(int s0, int live0) {
live = 0;
return 0;
}
+// a case label seen twice in one switch is an error (spec 4.7.2): the second
+// arm could never run. the first 256 labels of a switch are checked.
+int case_seen(word seen, int n, int v) {
+ int i = 0;
+ while (i < n) { if (seen[i] == v) { serr("duplicate case label"); return n; } i++; }
+ if (n < 256) { seen[n] = v; n++; }
+ return n;
+}
// compile a switch statement.
int kw_switch() {
int sl = tline;
int save = nloc, tmp, c, s0, live0, defent = -1, skip, nxt, bj, v, a = -1, st, tg = 0, cov = 0, gi, nb = 0, j, arm, bi;
- word[8] pbn; word[8] pbl;
+ word[8] pbn; word[8] pbl; word[256] seen; int nseen = 0;
stmtonly();
next();
expect(s_lp); c = expr(); st = xty; rval(); plainuse(c); expect(s_rp);
@@ -609,6 +617,7 @@ int kw_switch() {
expect(s_rp);
if (nb && nb != pm_nf[gi]) serr("a case binds every field of its member"); // member() with no names matches by tag (spec 4.7.3)
v = pm_tag[gi];
+ nseen = case_seen(seen, nseen, v);
if (v < 63) cov = cov | (1 << v);
laddr(lslot[tmp]); op(O_LD); op(O_LD); lit(v); op(O_SUB);
bj = fixnew(hole(), bj); op(O_JZ);
@@ -633,6 +642,7 @@ int kw_switch() {
} else {
while (1) {
v = cexpr();
+ nseen = case_seen(seen, nseen, v);
if (tg && v >= 0 && v < 63) cov = cov | (1 << v);
laddr(lslot[tmp]); op(O_LD);
if (tg) op(O_LD);
diff --git a/src/js/tinyvm.ts b/src/js/tinyvm.ts
index 51b390b..5ee29c7 100644
--- a/src/js/tinyvm.ts
+++ b/src/js/tinyvm.ts
@@ -179,6 +179,7 @@ export class TinyVM {
should resume it after its event loop has run, whether or not input came. */
public polling: boolean = false;
private outputBuffer: number[] = [];
+ private exitCode: number | null = null;
private onOutput?: (byte: number) => void;
private onMessage?: (msg: string) => void;
@@ -439,7 +440,8 @@ export class TinyVM {
return -1n;
case 6: // time
return BigInt(Math.floor(Date.now() / 1000));
- case 7: // exit
+ case 7: // exit: the run stops here with this status, as the native core's exit() does
+ this.exitCode = Number(BigInt.asIntN(32, a)) & 255;
return 0n;
case 8: // rand
return BigInt(Math.floor(Math.random() * 0x7FFFFFFF));
@@ -781,6 +783,7 @@ export class TinyVM {
mem = this.mem; bytes = this.bytes; view = this.view; mw = this.mw; // a grow may have moved them
mem[Number(a)] = result;
s = this.rs; r = this.rr;
+ if (this.exitCode !== null) { const code = this.exitCode; this.exitCode = null; return code; }
break;
} else if (Number(a) >= 0 && Number(a) < mw) {
mem[Number(a)] = b;
diff --git a/src/runtime/rt.zc b/src/runtime/rt.zc
index 149138b..4f96320 100644
--- a/src/runtime/rt.zc
+++ b/src/runtime/rt.zc
@@ -428,7 +428,7 @@ const int F_SHARE = 8;
// table (dtab), its drop has run (it never runs again), and the collector found it
// unreachable and runs its drop once the sweep is over.
const int F_HASDROP = 16, F_DROPPED = 32, F_FINAL = 64, F_CYCLE = 128; // F_CYCLE: during gc_drops only, a candidate its own contents reach
-int o3_depth, gc_pending;
+int o3_depth, gc_pending, gc_cyc; // gc_cyc: the last sweep found a cycle with drops to run
// flags live in these bits; the size class is above them.
const int F_LOW = 65535;
int hbump; word[32] freel; int gc_runs, gc_freed, gc_auto = 1;
@@ -1456,7 +1456,7 @@ int o3_leave() {
// an unreachable value with a drop that hasn't run is kept for one more cycle and
// flagged F_FINAL, unless another such value reaches it (its parent drops first and
// it waits for a later collection) and it doesn't reach itself (a cycle drops one
-// value per collection). nothing runs user code here.
+// value per sweep, and gc() repeats the sweep until the cycle is done). nothing runs user code here.
int gc_drops() {
int i, again = 1; word k;
while (again) {
@@ -1486,9 +1486,9 @@ int gc_drops() {
k = dtab[i * 3];
if (k > 1 && (k[-4] & F_FINAL)) {
// reached from another candidate: later. one in a cycle has nothing outside it
- // to drop first, so it drops now (it is marked, so kept), and the rest of the
- // cycle in later collections, one each, never twice
- if ((k[-4] & 7) == 1) mark(k); else if (!(k[-4] & F_CYCLE)) k[-4] = k[-4] & ~F_FINAL;
+ // to drop first, so it drops now (it is marked, so kept); gc() then sweeps again
+ // for the rest of the cycle, one each, never twice
+ if ((k[-4] & 7) == 1) mark(k); else if (!(k[-4] & F_CYCLE)) k[-4] = k[-4] & ~F_FINAL; else gc_cyc = 1;
k[-4] = k[-4] & ~F_CYCLE;
}
i++;
@@ -1549,8 +1549,12 @@ unchecked int gc_sweep() {
unchecked int gc() {
int n;
if (o3_depth) { gc_pending = 1; return 0; }
+ gc_cyc = 0;
n = gc_sweep();
if (dcount) gc_finalize();
+ // an unreachable cycle with drops finishes in this collection: each sweep drops one
+ // member (nothing outside the cycle has to go first), and every value drops once
+ while (gc_cyc) { gc_cyc = 0; n = n + gc_sweep(); if (dcount) gc_finalize(); }
return n;
}
// the number of live heap objects, for tests and leak checks.
diff --git a/src/tools/zc0.c b/src/tools/zc0.c
index 6c62abf..229734c 100644
--- a/src/tools/zc0.c
+++ b/src/tools/zc0.c
@@ -396,16 +396,42 @@ static int64_t cprim(void)
if (tk == T_ID && (s = find(ts)) && (s->kind == K_CONST || (s->kind != K_GVAR && s->defd))) { next(); return s->val; }
die("constant expected"); return 0;
}
-static int64_t cexpr(void)
+/* c precedence (spec 3.14.1): * / % above + -, above << >>, above &, ^, | */
+static int cprec(void)
{
- int64_t v = cprim();
- for (;;) {
- if (accept("+")) v += cprim(); else if (accept("-")) v -= cprim();
- else if (accept("*")) v *= cprim(); else if (accept("|")) v |= cprim();
- else if (accept("&")) v &= cprim(); else if (accept("<<")) v <<= cprim();
- else if (accept(">>")) v >>= cprim(); else return v;
+ if (is("*") || is("/") || is("%")) return 10;
+ if (is("+") || is("-")) return 9;
+ if (is("<<") || is(">>")) return 8;
+ if (is("&")) return 5;
+ if (is("^")) return 4;
+ if (is("|")) return 3;
+ return 0;
+}
+static int64_t cbin(int minp)
+{
+ int64_t v = cprim(), w; int p; char o[3];
+ while ((p = cprec()) && p >= minp) {
+ o[0] = ts[0]; o[1] = ts[1]; o[2] = 0;
+ next(); w = cbin(p + 1);
+ switch (o[0]) {
+ case '*': v *= w; break;
+ case '/': case '%':
+ if (w == 0) { die("division by zero in a constant expression"); return 0; }
+ if (w == -1) v = o[0] == '/' ? (int64_t)(0 - (uint64_t)v) : 0;
+ else v = o[0] == '/' ? v / w : v % w;
+ break;
+ case '+': v += w; break;
+ case '-': v -= w; break;
+ case '<': v = (int64_t)((uint64_t)v << (w & 63)); break;
+ case '>': v >>= (w & 63); break;
+ case '&': v &= w; break;
+ case '^': v ^= w; break;
+ default: v |= w; break;
+ }
}
+ return v;
}
+static int64_t cexpr(void) { return cbin(1); }
/* ---- types ----
zc0 has no types: a type is read and thrown away. all it has to know is the