aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-15 17:03:03 +0330
committerGravatar zinc <zinc@downsta.ir> 2026-10-15 17:03:03 +0330
commit7cb983bbea98d2038b2a8015a6d1f1afd40991c9 (patch)
treeec3d88a15801207fc6d8d3c6d142617ffec85460 /tests
parentmerge work/borrow: borrow returns before definition, borrows clause, borrow m... (diff)
parentbench: rss probe path fix (diff)
merge work/actors: wasm seed bootstrap (seed/MANIFEST), shared copy-on-write actor memory, top-level hold blocks on all backends and lsp
Diffstat (limited to 'tests')
-rw-r--r--tests/bench/bench_bytes.sh2
-rw-r--r--tests/bench/bench_share.sh2
-rw-r--r--tests/bench/bench_strings.sh2
-rw-r--r--tests/bench/rss/a0.zc11
-rw-r--r--tests/bench/rss/a10.zc11
-rw-r--r--tests/bench/rss/a100.zc11
-rw-r--r--tests/bench/rss/measure.py13
-rw-r--r--tests/drivers/hold_lsp_test.js54
-rw-r--r--tests/drivers/hold_test.py28
-rw-r--r--tests/drivers/imagekit.py2
-rw-r--r--tests/hold/accept/isolated_globals.zc18
-rw-r--r--tests/hold/accept/monitor.zc15
-rw-r--r--tests/hold/accept/race.zc21
-rw-r--r--tests/hold/accept/rank_order.zc7
-rw-r--r--tests/hold/accept/reentrant.zc12
-rw-r--r--tests/hold/reject/actor_handler.zc8
-rw-r--r--tests/hold/reject/closure.zc5
-rw-r--r--tests/hold/reject/nested.zc5
-rw-r--r--tests/hold/reject/o3.zc5
-rw-r--r--tests/hold/reject/order.zc7
-rw-r--r--tests/hold/reject/outside.zc5
-rw-r--r--tests/hold/reject/pointer.zc5
-rwxr-xr-xtests/test.sh25
-rw-r--r--tests/xref.baseline8
24 files changed, 272 insertions, 10 deletions
diff --git a/tests/bench/bench_bytes.sh b/tests/bench/bench_bytes.sh
index 775b71d..c509da4 100644
--- a/tests/bench/bench_bytes.sh
+++ b/tests/bench/bench_bytes.sh
@@ -10,7 +10,7 @@ T=$(mktemp -d)
trap 'rm -rf "$T"' EXIT
$CC -O2 -w src/tools/genmap.c -o "$T/genmap" || exit 1
GENMAP="$T/genmap" bash src/tools/automap.sh run "$T/run" src/runtime/run.zc tests/programs/bench_bytes.zc >/dev/null || exit 1
-cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/genome.h "$T/run/"
+cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/hold.h src/core/genome.h "$T/run/"
$CC -O2 -w -DJTYPE=int "$T/run/core.c" "$T/run/io_linux.c" -o "$T/zy" || exit 1
$CC -O2 -w "$T/run/micro.c" "$T/run/port_linux.c" -o "$T/zym" || exit 1
vms=("$T/zy" "$T/zym"); names=("interp" "micro")
diff --git a/tests/bench/bench_share.sh b/tests/bench/bench_share.sh
index 5cb1bfd..c47f5f5 100644
--- a/tests/bench/bench_share.sh
+++ b/tests/bench/bench_share.sh
@@ -8,7 +8,7 @@ T=$(mktemp -d)
trap 'rm -rf "$T"' EXIT
$CC -O2 -w src/tools/genmap.c -o "$T/genmap" || exit 1
"$T/genmap" --preset run -o "$T/run" >/dev/null || exit 1
-cp src/tools/zc0.c src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/genome.h "$T/run/"
+cp src/tools/zc0.c src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/hold.h src/core/genome.h "$T/run/"
$CC -O1 -w "$T/run/zc0.c" -o "$T/zc0" || exit 1
cat "$T/run/rt.zc" src/core/core.zc src/runtime/run.zc tests/programs/bench_share.zc | "$T/zc0" > "$T/run/genome.bin" || exit 1
$CC -O2 -w -DJTYPE=int "$T/run/core.c" "$T/run/io_linux.c" -o "$T/zy" || exit 1
diff --git a/tests/bench/bench_strings.sh b/tests/bench/bench_strings.sh
index b14bf57..8bf3b77 100644
--- a/tests/bench/bench_strings.sh
+++ b/tests/bench/bench_strings.sh
@@ -8,7 +8,7 @@ T=$(mktemp -d)
trap 'rm -rf "$T"' EXIT
$CC -O2 -w src/tools/genmap.c -o "$T/genmap" || exit 1
"$T/genmap" --preset run -o "$T/run" >/dev/null || exit 1
-cp src/tools/zc0.c src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/genome.h "$T/run/"
+cp src/tools/zc0.c src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/hold.h src/core/genome.h "$T/run/"
$CC -O1 -w "$T/run/zc0.c" -o "$T/zc0" || exit 1
cat "$T/run/rt.zc" src/core/core.zc src/runtime/run.zc tests/programs/bench_strings.zc | "$T/zc0" > "$T/run/genome.bin" || exit 1
$CC -O2 -w -DJTYPE=int "$T/run/core.c" "$T/run/io_linux.c" -o "$T/zy" || exit 1
diff --git a/tests/bench/rss/a0.zc b/tests/bench/rss/a0.zc
new file mode 100644
index 0000000..b8555d0
--- /dev/null
+++ b/tests/bench/rss/a0.zc
@@ -0,0 +1,11 @@
+namespace;
+include rt, actor;
+typecheck on;
+int work(int n) { rt.syscall(48, 500, 0, 0); return n + 1; }
+fn main() {
+ let n = 0, i = 0, sum = 0;
+ actor.Actor<int, int>*[101] as;
+ while (i < n) { as[i] = rt.new(actor.Actor<int, int>); as[i].spawn(work); as[i].recv(fn(int v) { sum = sum + v; return 0; }); as[i].send(i); i++; }
+ i = 0; while (i < n) { as[i].await(); as[i].close(); i++; }
+ rt.put_n(sum); rt.put_c(10); return 0;
+}
diff --git a/tests/bench/rss/a10.zc b/tests/bench/rss/a10.zc
new file mode 100644
index 0000000..aa09ce1
--- /dev/null
+++ b/tests/bench/rss/a10.zc
@@ -0,0 +1,11 @@
+namespace;
+include rt, actor;
+typecheck on;
+int work(int n) { rt.syscall(48, 500, 0, 0); return n + 1; }
+fn main() {
+ let n = 10, i = 0, sum = 0;
+ actor.Actor<int, int>*[101] as;
+ while (i < n) { as[i] = rt.new(actor.Actor<int, int>); as[i].spawn(work); as[i].recv(fn(int v) { sum = sum + v; return 0; }); as[i].send(i); i++; }
+ i = 0; while (i < n) { as[i].await(); as[i].close(); i++; }
+ rt.put_n(sum); rt.put_c(10); return 0;
+}
diff --git a/tests/bench/rss/a100.zc b/tests/bench/rss/a100.zc
new file mode 100644
index 0000000..f869026
--- /dev/null
+++ b/tests/bench/rss/a100.zc
@@ -0,0 +1,11 @@
+namespace;
+include rt, actor;
+typecheck on;
+int work(int n) { rt.syscall(48, 500, 0, 0); return n + 1; }
+fn main() {
+ let n = 100, i = 0, sum = 0;
+ actor.Actor<int, int>*[101] as;
+ while (i < n) { as[i] = rt.new(actor.Actor<int, int>); as[i].spawn(work); as[i].recv(fn(int v) { sum = sum + v; return 0; }); as[i].send(i); i++; }
+ i = 0; while (i < n) { as[i].await(); as[i].close(); i++; }
+ rt.put_n(sum); rt.put_c(10); return 0;
+}
diff --git a/tests/bench/rss/measure.py b/tests/bench/rss/measure.py
new file mode 100644
index 0000000..b4ad889
--- /dev/null
+++ b/tests/bench/rss/measure.py
@@ -0,0 +1,13 @@
+import sys,subprocess,resource,os
+repo=sys.argv[1]
+for b in ['zy','zyi','zym']:
+ row=[b]
+ for n in [0,10,100]:
+ pid=os.fork()
+ if pid==0:
+ fd=os.open(os.path.dirname(os.path.abspath(__file__))+f'/a{n}.zc',os.O_RDONLY); os.dup2(fd,0)
+ nul=os.open('/dev/null',os.O_WRONLY); os.dup2(nul,1)
+ os.chdir(repo); os.execv(f'{repo}/build/bin/{b}',[b])
+ _,st,ru=os.wait4(pid,0)
+ row.append(f'{ru.ru_maxrss/1024:.1f}' + ('' if st==0 else f'(st{st})'))
+ print(' '.join(row))
diff --git a/tests/drivers/hold_lsp_test.js b/tests/drivers/hold_lsp_test.js
new file mode 100644
index 0000000..65d8154
--- /dev/null
+++ b/tests/drivers/hold_lsp_test.js
@@ -0,0 +1,54 @@
+// hold blocks in the language server: hover on hold, shared and a shared variable,
+// completion offers the keywords and marks shared variables, and a use outside the
+// block is a diagnostic. usage: node tests/drivers/hold_lsp_test.js path/to/zclsp
+const TIMEOUT_SCALE = Number(process.env.ZC_TEST_TIMEOUT_SCALE) || 1;
+const assert = require('assert');
+const cp = require('child_process');
+const server = process.argv[2];
+const uri = 'file:///hold.zc';
+const frame = m => { const body = Buffer.from(JSON.stringify(m)); return Buffer.concat([Buffer.from('Content-Length: ' + body.length + '\r\n\r\n'), body]); };
+function session(text, request) {
+ const messages = [
+ { jsonrpc: '2.0', id: 1, method: 'initialize', params: {} },
+ { jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, languageId: 'zc', text } } },
+ ];
+ if (request) messages.push(Object.assign({ jsonrpc: '2.0', id: 2 }, request));
+ messages.push({ jsonrpc: '2.0', method: 'exit' });
+ const result = cp.spawnSync(server, [], { input: Buffer.concat(messages.map(frame)), timeout: 15000 * TIMEOUT_SCALE, maxBuffer: 8 << 20 });
+ assert.strictEqual(result.status, 0, String(result.stderr));
+ const out = []; let data = result.stdout;
+ while (data.length) {
+ const end = data.indexOf('\r\n\r\n');
+ const count = Number(/Content-Length:\s*(\d+)/i.exec(data.subarray(0, end).toString())[1]);
+ out.push(JSON.parse(data.subarray(end + 4, end + 4 + count).toString()));
+ data = data.subarray(end + 4 + count);
+ }
+ return out;
+}
+function at(text, needle, nth = 0, delta = 0) {
+ let o = -1; for (let i = 0; i <= nth; i++) o = text.indexOf(needle, o + 1);
+ assert.ok(o >= 0, needle); o += delta;
+ const before = text.slice(0, o);
+ return { line: before.split('\n').length - 1, character: before.length - before.lastIndexOf('\n') - 1 };
+}
+const hover = (text, needle, nth) => { const r = session(text, { method: 'textDocument/hover', params: { textDocument: { uri }, position: at(text, needle, nth) } }).find(m => m.id === 2); return r && r.result && r.result.contents.value || ''; };
+const good = 'namespace;\ninclude rt;\nhold {\n shared let count = 0;\n fn inc() { count = count + 1; return count; }\n}\nfn main() { return inc(); }\n';
+let h = hover(good, 'hold');
+assert.ok(h.includes('monitor') && h.includes('lock'), 'hover on hold explains the block: ' + h);
+h = hover(good, 'shared');
+assert.ok(h.includes('every actor'), 'hover on shared: ' + h);
+h = hover(good, 'count', 1);
+assert.ok(h.includes('shared in hold block 1'), 'hover on a shared variable names its block: ' + h);
+const bad = 'namespace;\ninclude rt;\nhold { shared let count; fn inc() { count++; return count; } }\nfn main() { count = 2; return inc(); }\n';
+const diags = session(bad).filter(m => m.method === 'textDocument/publishDiagnostics').flatMap(m => m.params.diagnostics);
+assert.ok(diags.some(d => d.message.includes('count: is shared in a hold block') && d.range.start.line === 3), 'use outside the block is a diagnostic on its line: ' + JSON.stringify(diags));
+const partial = 'namespace;\ninclude rt;\nhold { shared let count; fn inc() { count++; return count; } }\nho\nfn main() { return inc(); }\n';
+let r = session(partial, { method: 'textDocument/completion', params: { textDocument: { uri }, position: at(partial, '\nho', 0, 3) } }).find(m => m.id === 2);
+let items = (r.result.items || r.result);
+assert.ok(items.some(i => i.label === 'hold'), 'completion offers hold');
+const partial2 = 'namespace;\ninclude rt;\nhold { shared let count; fn inc() { cou; return count; } }\nfn main() { return inc(); }\n';
+r = session(partial2, { method: 'textDocument/completion', params: { textDocument: { uri }, position: at(partial2, 'cou;', 0, 3) } }).find(m => m.id === 2);
+items = (r.result.items || r.result);
+const it = items.find(i => i.label === 'count');
+assert.ok(it && String(it.detail).startsWith('shared'), 'completion marks a shared variable: ' + JSON.stringify(it));
+console.log('hold lsp: 6 checks passed');
diff --git a/tests/drivers/hold_test.py b/tests/drivers/hold_test.py
new file mode 100644
index 0000000..1f6ea39
--- /dev/null
+++ b/tests/drivers/hold_test.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+# hold blocks (spec 4.16): accepted programs print what their first line expects, rejected
+# ones report the error their first line names (exit 1), and trapping ones stop with
+# exit 2 and the message named. usage: hold_test.py [VM]
+from pathlib import Path
+import subprocess, sys
+root = Path(__file__).resolve().parents[2]
+vm = sys.argv[1] if len(sys.argv) > 1 else 'build/bin/zy'
+count = bad = 0
+for case in sorted((root / 'tests/hold').glob('*/*.zc')):
+ src = case.read_text(); first = src.splitlines()[0]
+ p = subprocess.run([str(root / vm) if not Path(vm).is_absolute() else vm], input=src, text=True, capture_output=True, cwd=root, timeout=60)
+ out = p.stdout + p.stderr
+ if first.startswith('// expect: '):
+ ok = p.returncode == 0 and p.stdout.strip() == first[11:].strip()
+ elif first.startswith('// error: '):
+ ok = p.returncode == 1 and first[10:].strip() in out
+ elif first.startswith('// trap: '):
+ ok = p.returncode == 2 and first[9:].strip() in out
+ else:
+ ok = False
+ count += 1
+ if not ok:
+ bad += 1; print(f'FAIL {case.relative_to(root)}: rc={p.returncode} out={out[:300]!r}')
+ else:
+ print(f'ok {case.relative_to(root)}')
+print(f'{count - bad}/{count} hold fixtures passed')
+sys.exit(1 if bad or not count else 0)
diff --git a/tests/drivers/imagekit.py b/tests/drivers/imagekit.py
index 6cc083f..5df4810 100644
--- a/tests/drivers/imagekit.py
+++ b/tests/drivers/imagekit.py
@@ -9,7 +9,7 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
DEFAULT_MAP = ROOT / 'build' / 'core' / 'map' / 'memmap.h'
MICRO_SOURCES = ['src/micro/micro.c', 'src/micro/micro_port.h', 'src/micro/port_linux.c',
- 'src/core/fops.h', 'src/core/bops.h', 'src/core/stack.h', 'src/core/genome.h']
+ 'src/core/fops.h', 'src/core/bops.h', 'src/core/stack.h', 'src/core/hold.h', 'src/core/genome.h']
def compiler():
diff --git a/tests/hold/accept/isolated_globals.zc b/tests/hold/accept/isolated_globals.zc
new file mode 100644
index 0000000..08a3e5c
--- /dev/null
+++ b/tests/hold/accept/isolated_globals.zc
@@ -0,0 +1,18 @@
+// expect: 0 3
+// a plain global is each actor's own; only a shared variable of a hold block is seen by all
+namespace;
+include rt, actor;
+typecheck on;
+let plain = 0;
+hold { shared let seen = 0; fn note() { seen++; return seen; } fn seen_now() { return seen; } }
+int work(int n) { plain = plain + n; note(); return plain; }
+fn main() {
+ actor.Actor<int,int>* a = rt.new(actor.Actor<int,int>), b = rt.new(actor.Actor<int,int>), c = rt.new(actor.Actor<int,int>);
+ a.spawn(work); b.spawn(work); c.spawn(work);
+ a.send(5); b.send(6); c.send(7);
+ a.recv(fn(int n) { return 0; }); b.recv(fn(int n) { return 0; }); c.recv(fn(int n) { return 0; });
+ a.await(); b.await(); c.await();
+ rt.put_n(plain); rt.put_c(' '); rt.put_n(seen_now()); rt.put_c(10);
+ a.close(); b.close(); c.close();
+ return 0;
+}
diff --git a/tests/hold/accept/monitor.zc b/tests/hold/accept/monitor.zc
new file mode 100644
index 0000000..7dfe6f0
--- /dev/null
+++ b/tests/hold/accept/monitor.zc
@@ -0,0 +1,15 @@
+// expect: 902
+namespace;
+include rt;
+hold {
+ shared let c = 5;
+ shared int[3] hist;
+ fn bump(int k) { c = c + k; hist[0]++; return c; }
+ fn twice(int k) { bump(k); return bump(k); }
+ fn get() { return c * 100 + hist[0]; }
+}
+fn main() {
+ twice(2);
+ rt.put_n(get()); rt.put_c(10);
+ return 0;
+}
diff --git a/tests/hold/accept/race.zc b/tests/hold/accept/race.zc
new file mode 100644
index 0000000..f7fdb61
--- /dev/null
+++ b/tests/hold/accept/race.zc
@@ -0,0 +1,21 @@
+// expect: 90000
+// many actors increment one shared counter through a read, then a write; the hold block's lock makes every increment count
+namespace;
+include rt, actor;
+typecheck on;
+hold {
+ shared let count = 0;
+ fn inc() { let v = count; count = v + 1; return v; }
+ fn total() { return count; }
+}
+int work(int n) { let i = 0; while (i < n) { inc(); i++; } return n; }
+fn main() {
+ let i = 0; actor.Actor<int,int>*[?] xs = rt.new(actor.Actor<int,int>*[?]);
+ while (i < 8) { actor.Actor<int,int>* a = rt.new(actor.Actor<int,int>); a.spawn(work); push(xs, a); i++; }
+ i = 0; while (i < 8) { xs[i].send(10000); i++; }
+ work(10000);
+ i = 0; while (i < 8) { xs[i].recv(fn(int n) { return 0; }); xs[i].await(); i++; }
+ rt.put_n(total()); rt.put_c(10);
+ i = 0; while (i < 8) { xs[i].close(); i++; }
+ return 0;
+}
diff --git a/tests/hold/accept/rank_order.zc b/tests/hold/accept/rank_order.zc
new file mode 100644
index 0000000..9fc903a
--- /dev/null
+++ b/tests/hold/accept/rank_order.zc
@@ -0,0 +1,7 @@
+// expect: 10
+namespace;
+include rt;
+int gb();
+hold { shared let a; fn ga() { a++; return gb(); } }
+hold { shared let b; fn gb() { b++; return b * 10; } }
+fn main() { rt.put_n(ga()); rt.put_c(10); return 0; }
diff --git a/tests/hold/accept/reentrant.zc b/tests/hold/accept/reentrant.zc
new file mode 100644
index 0000000..f0c8293
--- /dev/null
+++ b/tests/hold/accept/reentrant.zc
@@ -0,0 +1,12 @@
+// expect: 3 1
+// a function of a block calling another of the same block doesn't take the lock again, and a later block may be entered from an earlier one
+namespace;
+include rt;
+int later();
+hold {
+ shared let depth, calls;
+ fn inner() { calls++; return depth; }
+ fn outer() { depth = depth + 1; inner(); inner(); inner(); return calls; }
+}
+hold { shared let seen; fn later() { seen++; return seen; } }
+fn main() { rt.put_n(outer()); rt.put_c(' '); rt.put_n(later()); rt.put_c(10); return 0; }
diff --git a/tests/hold/reject/actor_handler.zc b/tests/hold/reject/actor_handler.zc
new file mode 100644
index 0000000..5548cad
--- /dev/null
+++ b/tests/hold/reject/actor_handler.zc
@@ -0,0 +1,8 @@
+// error: total: is shared in a hold block; only the functions written inside that block can use it
+// shared state reached from an actor's handler without going through its hold block
+namespace;
+include rt, actor;
+typecheck on;
+hold { shared let total; fn add(int n) { total = total + n; return total; } }
+int work(int n) { total = total + n; return n; }
+fn main() { actor.Actor<int,int>* a = rt.new(actor.Actor<int,int>); a.spawn(work); a.send(1); a.close(); return 0; }
diff --git a/tests/hold/reject/closure.zc b/tests/hold/reject/closure.zc
new file mode 100644
index 0000000..5c082d2
--- /dev/null
+++ b/tests/hold/reject/closure.zc
@@ -0,0 +1,5 @@
+// error: c: is shared; a closure can't use it
+namespace;
+include rt;
+hold { shared let c; fn a() { let f = fn() { return c; }; return f(); } }
+fn main() { return a(); }
diff --git a/tests/hold/reject/nested.zc b/tests/hold/reject/nested.zc
new file mode 100644
index 0000000..b8d38a6
--- /dev/null
+++ b/tests/hold/reject/nested.zc
@@ -0,0 +1,5 @@
+// error: hold blocks don't nest
+namespace;
+include rt;
+hold { shared let c; hold { shared let d; } }
+fn main() { return 0; }
diff --git a/tests/hold/reject/o3.zc b/tests/hold/reject/o3.zc
new file mode 100644
index 0000000..87c2dd5
--- /dev/null
+++ b/tests/hold/reject/o3.zc
@@ -0,0 +1,5 @@
+// error: f: is in a hold block and can't be o3
+namespace;
+include rt;
+hold { shared let c; o3 int f(int k) { return k; } }
+fn main() { return f(1); }
diff --git a/tests/hold/reject/order.zc b/tests/hold/reject/order.zc
new file mode 100644
index 0000000..9a2943b
--- /dev/null
+++ b/tests/hold/reject/order.zc
@@ -0,0 +1,7 @@
+// trap: was entered while
+// blocks are entered in declaration order; going back to an earlier block while a later one is held stops the program
+namespace;
+include rt;
+hold { shared let a; fn ga() { a++; return a; } }
+hold { shared let b; fn gb() { b++; return ga(); } }
+fn main() { rt.put_n(gb()); rt.put_c(10); return 0; }
diff --git a/tests/hold/reject/outside.zc b/tests/hold/reject/outside.zc
new file mode 100644
index 0000000..a202898
--- /dev/null
+++ b/tests/hold/reject/outside.zc
@@ -0,0 +1,5 @@
+// error: c: is shared in a hold block; only the functions written inside that block can use it
+namespace;
+include rt;
+hold { shared let c; fn a() { c++; return c; } }
+fn main() { c = 3; return a(); }
diff --git a/tests/hold/reject/pointer.zc b/tests/hold/reject/pointer.zc
new file mode 100644
index 0000000..6306a62
--- /dev/null
+++ b/tests/hold/reject/pointer.zc
@@ -0,0 +1,5 @@
+// error: name: is shared, so it holds numbers
+namespace;
+include rt;
+hold { shared char* name = "x"; fn a() { return 0; } }
+fn main() { return a(); }
diff --git a/tests/test.sh b/tests/test.sh
index 6d8453e..c31cfb7 100755
--- a/tests/test.sh
+++ b/tests/test.sh
@@ -273,6 +273,9 @@ s_interp() {
python3 tests/drivers/actor_test.py $T/zy > $T/actor.out 2>&1; r=$?
grep -v '^ok' $T/actor.out
chk $r 0 "native isolated actor acceptance, rejection and regression fixtures"
+ python3 tests/drivers/hold_test.py $T/zy > $T/hold.out 2>&1; r=$?
+ grep -v '^ok' $T/hold.out
+ chk $r 0 "native hold blocks: monitors, rejections, a race between actors, rank order"
chk "$(ZC_ARG_TEST=ok $T/zy tests/programs/args.zc alpha beta < tests/programs/args.zc)" "4 <eval> beta 1 1" "interp the command line reaches argv, after <eval>"
chk "$($T/zy < tests/programs/cabi.zc | md5sum)" "$(md5sum < tests/programs/cabi.expected)" "interp native calls and callbacks (extern, cfunc)"
chk "$($T/zy < tests/programs/cabi_o3.zc | md5sum)" "$(md5sum < tests/programs/cabi_o3.expected)" "interp native calls from o3 code, more arguments than registers"
@@ -298,6 +301,9 @@ s_micro() {
python3 tests/drivers/actor_test.py $T/zym > $T/actor.out 2>&1; r=$?
grep -v '^ok' $T/actor.out
chk $r 0 "micro isolated actor acceptance, rejection and regression fixtures"
+ python3 tests/drivers/hold_test.py $T/zym > $T/hold.out 2>&1; r=$?
+ grep -v '^ok' $T/hold.out
+ chk $r 0 "micro hold blocks: monitors, rejections, a race between actors, rank order"
if command -v node >/dev/null && command -v tsc >/dev/null && node src/js/mklayout.js --map $MAP/memmap.h --out $T/jsvm >/dev/null \
&& cp $ROOT/s1/genome.bin $T/jsvm/ && tsc src/js/tinyvm.ts --target es2020 --lib es2020,dom --module commonjs --outDir $T/jsvm; then
python3 tests/drivers/fuzz_compiler.py $ROOT/zy $T/zym --js $T/jsvm/tinyvm.js > $T/fuzz.out 2>&1; r=$?
@@ -331,10 +337,10 @@ s_small() {
cmp -s $T/small/memmap.h $T/small-flags/memmap.h && cmp -s $T/small/rt.zc $T/small-flags/rt.zc \
&& cmp -s $T/small/compiler.zc $T/small-flags/compiler.zc && cmp -s $T/small/map.txt $T/small-flags/map.txt \
&& chk 1 1 "compiler flags reproduce the small map" || chk 0 1 "compiler flags reproduce the small map"
- cp src/tools/zc0.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/genome.h $T/small/
+ cp src/tools/zc0.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/hold.h src/core/genome.h $T/small/
$CC -O1 -w -fwrapv $T/small/zc0.c -o $T/zc0s || exit 1
cat $T/small/rt.zc src/core/core.zc $T/small/compiler.zc | $T/zc0s > $T/small/genome.bin || exit 1
- cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/genome.h $T/small/
+ cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/hold.h src/core/genome.h $T/small/
$CC -pthread -DZY_THREADS -O2 -w -fwrapv $T/small/micro.c $T/small/port_linux.c -o $T/zyms || exit 1
$CC -pthread -O2 -w -fwrapv -DJTYPE=int $T/small/core.c $T/small/io_linux.c -o $T/zys || exit 1
$CC -ffreestanding -w -c $T/small/micro.c -o $T/micro_s.o && $CC -ffreestanding -w -c $T/small/port_stub.c -o $T/stub_s.o && chk 1 1 "small microcore compiles freestanding" || chk 0 1 "small microcore compiles freestanding"
@@ -375,14 +381,14 @@ s_runonly() {
# its own run map: the run-only image is built against this one, and the
# small-machine section lays out its own rather than sharing this
$T/genmap --preset run -o $T/run || exit 1
- cp src/tools/zc0.c src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/genome.h $T/run/
+ cp src/tools/zc0.c src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/hold.h src/core/genome.h $T/run/
$CC -O1 -w -fwrapv $T/run/zc0.c -o $T/zc0r || exit 1
runonly() { # $1 label, $2 source. the program is already in the image, so it reads no source.
# the map is sized to the program: src/tools/automap.sh lays it out for the image's own length
local a b d=$T/run-$1 input=$2
if [ "$1" = gc ]; then input=$T/collector-progress.zc; sed -E 's/f >= [0-9]+/f > 0/' "$2" > "$input"; fi
GENMAP=$T/genmap bash src/tools/automap.sh run $d src/runtime/run.zc "$input" >/dev/null || { chk 0 1 "run $1 builds"; return; }
- cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/genome.h $d/
+ cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/hold.h src/core/genome.h $d/
$CC -pthread -DZY_THREADS -O2 -w -fwrapv $d/micro.c $d/port_linux.c -o $T/zymr || { chk 0 1 "run $1 links"; return; }
a=$($T/zy < "$input" 2>&1 | sed "s#<stdin>#$input#g" | tr '\n' '/')
b=$($T/zymr </dev/null 2>&1 | tr '\n' '/')
@@ -407,7 +413,7 @@ s_runonly() {
runonly zc0bstr tests/programs/zc0bstr.zc
runonly zc0umd tests/programs/zc0umd.zc
runonly wiring examples/wiring.zc
- d=$T/run-echo; GENMAP=$T/genmap bash src/tools/automap.sh run $d src/runtime/run.zc examples/echo.zc >/dev/null && cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/genome.h $d/ \
+ d=$T/run-echo; GENMAP=$T/genmap bash src/tools/automap.sh run $d src/runtime/run.zc examples/echo.zc >/dev/null && cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/hold.h src/core/genome.h $d/ \
&& $CC -pthread -DZY_THREADS -O2 -w -fwrapv $d/micro.c $d/port_linux.c -o $T/zymr \
&& chk "$(printf 'abc\n' | $T/zymr | tr '\n' '/')" "$( (cat examples/echo.zc; printf '`abc\n') | $T/zy | tr '\n' '/')" "run echo" \
|| chk 0 1 "run echo"
@@ -526,6 +532,9 @@ s_jit() {
bash tests/drivers/jit_x64_test.sh $T/zyi $T/zyx > $T/jit_x64.out 2>&1; r=$?
grep '^DIFF' $T/jit_x64.out
chk $r 0 "x86-64 jit agrees with the interpreter: $(tail -n 1 $T/jit_x64.out)"
+ python3 tests/drivers/hold_test.py $T/zyx > $T/hold_x64.out 2>&1; r=$?
+ grep -v '^ok' $T/hold_x64.out
+ chk $r 0 "x86-64 jit hold blocks"
chk "$(ZYNOENH=1 $T/zyx < tests/programs/enh_ops.zc | md5sum)" "$(md5sum < tests/programs/enh_ops.expected)" "x86-64 jit with enhancements called through xrun (ZYNOENH=1)"
chk "$(ZYTRACE=1 $T/zyx < tests/programs/o3.zc 2>&1 | grep -c 'bytes of x86-64')" "7" "x86-64 jit compiles the o3 regions it can (one has too many live values)"
chk "$(ZYTRACE=1 $T/zyx < tests/programs/o3_freelist.zc 2>&1 | grep -c ' inline at ')" "11" "x86-64 jit inlines new_k and recycle_leaf in compiled o3 regions"
@@ -546,6 +555,9 @@ s_llvm() {
if [ $llvm_ok = 1 ]; then
run $T/zyl "llvm"
chk "$(ZYTRACE=1 $T/zyl < tests/programs/o3.zc 2>&1 | grep -c 'compiled by llvm')" "8" "llvm compiles every o3 region"
+ python3 tests/drivers/hold_test.py $T/zyl > $T/hold.out 2>&1; r=$?
+ grep -v '^ok' $T/hold.out
+ chk $r 0 "llvm hold blocks"
printf '#!/bin/sh\nZYHOT=1 exec %s "$@"\n' $T/zyl > $T/llall; chmod +x $T/llall
chk "$($T/zyl < tests/programs/structval.zc)" "34 3520 18 325 810 340" "llvm structs by value (the call goes through the interpreter's native call)"
chk "$($T/zyl < tests/programs/structret.zc)" "3 4 3.500000 4.000000 24 8 1.000000 3.500000" "llvm structs returned from a c function"
@@ -634,6 +646,9 @@ s_lsp() {
node tests/drivers/codeactions_client_test.js > $T/codeactions-client.out 2>&1; r=$?
grep -v '^ok' $T/codeactions-client.out
chk $r 0 "lsp: versioned import quick-fix edits"
+ node tests/drivers/hold_lsp_test.js $lsp > $T/holdlsp.out 2>&1; r=$?
+ cat $T/holdlsp.out | grep -v passed
+ chk $r 0 "lsp hold blocks: hover, completion and diagnostics"
node tests/drivers/completion_test.js $lsp > $T/completion.out 2>&1; r=$?
grep -v '^ok' $T/completion.out
chk $r 0 "lsp: fuzzy completions, signatures, scope and expired lifetimes"
diff --git a/tests/xref.baseline b/tests/xref.baseline
index 26b6931..79e948b 100644
--- a/tests/xref.baseline
+++ b/tests/xref.baseline
@@ -161,6 +161,7 @@ fbnd chk_reset closure ctx_pop fn_begin kw_unbounded
fchk chk_reset ctx_pop defer_one fn_begin kw_unchecked
fdepth block blockexpr casebody chk_reset ctx_pop fn_begin kw_for kw_switch substmt
fframe chk_reset ctx_pop prologue
+fhold fndef
fieldcap field_note
fieldn field_note types_init
fieldnames field_note
@@ -236,6 +237,11 @@ hl_lev hint_set
hl_n chk_unit hint_set
hl_off hint_set
hlimit build_image_grow chk_unit reserve_code start syntax_resident
+hold_count kw_hold
+hold_cur kw_hold
+hold_desc kw_hold
+hold_nshared kw_hold
+hold_tab kw_hold
ht_end ht_note
ht_n ht_note
ht_start ht_note
@@ -630,7 +636,7 @@ sdsrc addsym def_note reserve_symbols
send_check args field
seqtop seqasm zc_init
sfix addsym enhdef fndef fnref reserve_symbols
-sflag add_kw addsym blob_load call enhdef fndef fndone fwdfn_at ginstance kw_extern kw_struct methcall methdecl prescan_fn reserve_symbols retcheck
+sflag add_kw addsym blob_load call enhdef fndef fndone fwdfn_at ginstance kw_extern kw_hold kw_struct methcall methdecl prescan_fn reserve_symbols retcheck
sgb sig_attach
sgb_initial
sigcap sig_attach