diff options
| author | 2026-10-14 23:10:49 +0330 | |
|---|---|---|
| committer | 2026-10-14 23:10:49 +0330 | |
| commit | 8af3174b04f18d858653ad70669dced71f5eaa99 (patch) | |
| tree | 741ad2afd7cf3b39be763d17eea6a8c01c9101e7 /docs/SPEC.md | |
| parent | docs: destructors: clear drop entry and bits on free; planned reuse test (diff) | |
destructors: heap(v), drop(v, f) / v.drop(f) / v.drop = f, struct drop methods, collector drops after the sweep (once, resurrection), freed blocks forget their drop, temporaries drop at statement end with a hint, actor send check; o3: no collection inside a region (pending until the outermost exit), no drops set or ended inside o3; spec 3.11.6, 4.10.3-4, 6.15
Diffstat (limited to 'docs/SPEC.md')
| -rw-r--r-- | docs/SPEC.md | 16 |
1 files changed, 16 insertions, 0 deletions
diff --git a/docs/SPEC.md b/docs/SPEC.md index aad7ff6..7177b14 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -467,6 +467,8 @@ pn((int)(p + 1) - (int)p); // 1: p + 1 moves one word 3.11.5 `new(T)` without `rt.` is accepted as `rt.new(T)`; the name `new` alone is the only rt name that resolves unqualified (any other, `put_c(65)`, is error: put_c is used but never defined; did you mean to use rt.put_c?). +3.11.6 `heap(e)` copies the value of `e` into a fresh owned heap block sized to it and returns a pointer to the block (a fresh object, 6.1): one word for a scalar or pointer (`heap(10)` is an `int*`), the characters and the terminating 0 for a `char*` word string (`heap("hi")` is a `char*`), and `sizeof(T)` words for a struct or array value (`heap(v)` with `v` a `foo` is a `foo*`). pointer fields of a copied struct are copied as plain words. a struct whose body declares `drop` gives the block its type drop (6.15). `heap` is found only when no variable or function of that name is in scope. + ### 3.12 `sizeof` and `typeof` 3.12.1 `sizeof(T)` and `sizeof(e)` give the size in words. the expression form is compiled but never run: `int i = 0; int n = sizeof(i++);` leaves `i` 0 and `n` 1. a `u8[100]` is 13 words, a struct of four `u8` fields is 1. @@ -622,6 +624,9 @@ switch (s) { case circle(r): pn(r); case rect(w, h): pn(w * h); case dot: ps("do o3 int sum(fixed int[?] a) { int s = 0; for (int i = 0; i < len(a); i++) s += a[i]; return s; } ``` +4.10.3 no collection runs inside an o3 region, from any code path. an allocation that would collect grows the heap instead and leaves a collection pending; an explicit `rt.gc()` inside the region is pending too. when the outermost region is left (the end of an `o3 { }` block, a `return` out of it, or the return of an o3 function called from normal code), the pending collection runs once. if the heap can't grow, the allocation is a runtime trap (out of heap memory, exit status 2). +4.10.4 drops (6.15) don't run inside o3: setting a drop inside an o3 region is error: a drop can't be set inside an o3 region, and ending the lifetime of a value with a drop inside an o3 region, by `rt.free`, at a scope end, or as a temporary, is error: x: has drop and can't end inside an o3 region. a drop the compiler can't see (unchecked code) that comes to run inside o3 stops the program: zy: a drop can't run inside an o3 region. + ### 4.11 memdebug `memdebug on;` turns on heap header checks and allocation-site tracking from this point in the file (top level) or from this point in the function (statement); the runtime enables the checks before `main` for the top-level form. `memdebug scrub;` additionally fills freed payloads with 57005; `memdebug off;` stops. with checks on, a use after free prints `zy: use after free from line N`, a double free prints `zy: double free from line N` and returns, a stray address prints `zy: bad heap pointer`. `rt.memreport()` prints a line `leak: line N W words` per live recorded object (W is the n words new asked for, not the block's payload) and returns the count. @@ -982,6 +987,17 @@ the `tests/errors/*.zc` files are exact reproductions; a verifier can run `bash the compiler also prints non-error findings as `zc: line N: warning: ...` (a switch that does not cover a tagged enum, a constant index out of range of a `T[?]`, a use after free provable at compile time, ...). warnings do not stop the program. +### 6.15 drops + +6.15.1 any heap value can have a drop: a `fn()` that runs once, when the value dies. three forms set it: `drop(v, f)` and `v.drop(f)` give `v` back (same type and ownership), and `v.drop = f;` is a statement. inside `f`, `this` is the value, borrowed. a drop is set once (error: x: drop is already set) and before the value is first read, assigned through or passed (error: x: drop set after the value was used). a struct body may declare `void drop() { ... }`: it is the default drop of every value of that type made by `new(T)` or `heap`; when a value has both, its own drop runs first, then the type's. +``` +rt.put_n(*heap(15).drop(fn() { rt.put_s(" bye "); rt.put_n(*this); })); // 15 bye 15 +let guard = drop(heap(0), fn() { unlock(); }); // unlocks at block end +``` +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`, or with its container, whose drop runs before its children'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. + ## 7. compile-time execution the compiler lives in vm memory with the program. functions and globals of the compiler are callable through the module `run` (`include run;`). |
