diff options
| author | 2026-10-15 02:37:04 +0330 | |
|---|---|---|
| committer | 2026-10-15 02:40:51 +0330 | |
| commit | b02bdd043673419357eadd6c646b9a77c62e5792 (patch) | |
| tree | 629a8b700ecf64f19d70d2a6b00aa3eb79ade004 | |
| parent | destructors: nothing moves out of a value with a drop: pop on an array with a... (diff) | |
destructors: hint: x: has drop and is live at spawn; the worker's copy drops it too, on x.spawn() of a mailbox struct for each live owned local with a drop; spec 6.15.4, lsp hint check
| -rw-r--r-- | docs/SPEC.md | 2 | ||||
| -rw-r--r-- | src/compiler/builtins.zc | 13 | ||||
| -rw-r--r-- | src/compiler/expr.zc | 1 | ||||
| -rw-r--r-- | tests/data/spawn_hint.zc | 11 | ||||
| -rwxr-xr-x | tests/test.sh | 1 |
5 files changed, 27 insertions, 1 deletions
diff --git a/docs/SPEC.md b/docs/SPEC.md index dc4934a..4406123 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -996,7 +996,7 @@ let guard = drop(heap(0), fn() { unlock(); }); // unl ``` 6.15.2 a value dies, and its drop runs, where 6.5 frees it: at the end of its owner's block or an early exit, at `rt.free`, when its owner is reassigned (6.8.5), or with its container, whose drop runs before its children's. a new value assigned to the owner can have its own drop set on the next statement, like a declaration's. a value with a drop that nothing holds (a temporary) dies at the end of its full statement, with hint: value with drop is never bound; it drops at the end of this statement. an owned local with a drop is not "never used" (6.5.2): holding it is its use. 6.15.3 the collector runs the drop of an unreachable value after its sweep, never during it, and at most once: the value is kept until a later collection finds it unreachable again. a drop that stores `this` somewhere reachable keeps the value alive, and its drop does not run again. there is no collection at exit, so collector-owned values are not dropped then. a freed block forgets its drop, so a block reused at the same address has none. -6.15.4 copying a value with a drop into an actor message is error: x: has drop and can't be copied into a message. +6.15.4 copying a value with a drop into an actor message is error: x: has drop and can't be copied into a message. `spawn` copies the whole vm into the worker process, so both processes hold an owned value that is live at the call and both run its drop: hint: x: has drop and is live at spawn; the worker's copy drops it too. 6.15.5 nothing moves out of a value with a drop, whose drop may need what it holds. a field or element of it is a borrowed projection and can't be given away (error: a borrowed projection cannot transfer ownership; transfer its owner), and `pop` on an array that has a drop, or on an array field of a value with a drop, is error: a: can't move out of a value with drop. there is no `take`. borrowing a child is fine, and an array without a drop hands out its elements with `pop` as before, drops and all. ## 7. compile-time execution diff --git a/src/compiler/builtins.zc b/src/compiler/builtins.zc index 44ed3b1..fb2dcb2 100644 --- a/src/compiler/builtins.zc +++ b/src/compiler/builtins.zc @@ -856,6 +856,19 @@ int tdroppable(word t) { } // local i holds a value with a drop: one set on it, or one its type declares int ldroppable(int i) { return (local_dropset && local_dropset[i]) || tdroppable(ltype[i]); } +// x.spawn() on a mailbox struct snapshots the whole vm into a worker process, so an +// owned value with a drop that is live there is dropped once in each process (6.15.4) +int spawn_hint() { + int i = lbase; + if (!fchk || !local_dropset) return 0; + while (i < nloc) { + if (!empty(lstate[i]) && !isalias(i) && lstate[i] != S_BORROW && ldroppable(i) && hint_begin(tpack(), HINT_SMALL)) { + rt.put_s(lname[i]); rt.put_s(": has drop and is live at spawn; the worker's copy drops it too"); dg_close(); + } + i++; + } + return 0; +} // entering o3 from normal code: the collector waits until the outermost region is left int o3_enter_code() { callname(intern("rt.o3_enter")); op(O_DROP); return 0; } // a fresh value with a drop that nothing holds: it is held in a hidden slot and diff --git a/src/compiler/expr.zc b/src/compiler/expr.zc index 7687394..1cd765b 100644 --- a/src/compiler/expr.zc +++ b/src/compiler/expr.zc @@ -1199,6 +1199,7 @@ word field(int c) { st = xty; if (rt.eq_w(n, "drop") && (is_op(s_lp) || is_op(s_eq)) && (isptr(st) || st == tword) && !(isptr(st) && st[4][0] == TY_STRUCT && tfindfield(st[4], n) >= 0)) return drop_member(c, borrowed); if (rt.eq_w(n, "send") && is_op(s_lp) && isptr(st) && st[4][0] == TY_STRUCT && tfindfield(st[4], intern("__mailbox")) >= 0) send_check = 1; + if (rt.eq_w(n, "spawn") && is_op(s_lp) && isptr(st) && st[4][0] == TY_STRUCT && tfindfield(st[4], intern("__mailbox")) >= 0) spawn_hint(); if (st[0] == TY_ARR || st[0] == TY_DYN) { if (pmatch) probe_member(st); if (is_op(s_lp) && array_method(n)) { xaf = borrowed; return array_member(c, n); } diff --git a/tests/data/spawn_hint.zc b/tests/data/spawn_hint.zc new file mode 100644 index 0000000..b59657d --- /dev/null +++ b/tests/data/spawn_hint.zc @@ -0,0 +1,11 @@ +namespace; +include rt, actor; +struct res { int fd; void drop() { rt.put_s("closed"); } } +actor Echo { int total; int handle(int x) { return x; } } +fn main() { + res* r = rt.new(res); r.fd = 3; + Echo* e = rt.new(Echo); + e.spawn(); + e.close(); + return r.fd; +} diff --git a/tests/test.sh b/tests/test.sh index 9aee6d0..6dfecf1 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -580,6 +580,7 @@ s_lsp() { chk "$($lsp < examples/struct_closure_opt.zc | grep -c error)" "0" "lsp: methods and closures check clean" case "$($lsp < tests/data/drop_hint.zc)" in *"value with drop is never bound; it drops at the end of this statement"*) chk 1 1 "lsp hint: a value with drop that is never bound";; *) chk 0 1 "lsp hint: a value with drop that is never bound";; esac case "$($lsp < tests/data/reassign_hint.zc)" in *"p: reassigning frees the old value"*) chk 1 1 "lsp hint: reassigning an owner frees the old value";; *) chk 0 1 "lsp hint: reassigning an owner frees the old value";; esac + case "$($lsp < tests/data/spawn_hint.zc)" in *"r: has drop and is live at spawn; the worker's copy drops it too"*) chk 1 1 "lsp hint: a value with drop live at spawn";; *) chk 0 1 "lsp hint: a value with drop live at spawn";; esac # a file of a larger build checks on its own: its bodyless declarations are not errors in check mode for f in src/runtime/rt.zc src/runtime/run.zc src/lsp/server.zc; do chk "$($lsp < $f | grep -c ': error:')" "0" "lsp: $f checks alone"; done # the compiler is several files that only make sense together: check them as one document |
