diff options
| author | 2026-10-15 00:17:37 +0330 | |
|---|---|---|
| committer | 2026-10-15 00:17:37 +0330 | |
| commit | 24b6dfd8e8e1ba678e79e1789583f78b3e2dda9d (patch) | |
| tree | c1b38498ac2bc642d4bb8cbf9fe0f246ece4e14e /docs/design/destructors.md | |
| parent | rt: the free paths take one F_HASDROP test, and dropfree does the drop and th... (diff) | |
docs: destructors: the free paths clear the entry in dropfree, right after the drop and before the children
Diffstat (limited to 'docs/design/destructors.md')
| -rw-r--r-- | docs/design/destructors.md | 4 |
1 files changed, 2 insertions, 2 deletions
diff --git a/docs/design/destructors.md b/docs/design/destructors.md index 777db2c..0691228 100644 --- a/docs/design/destructors.md +++ b/docs/design/destructors.md @@ -165,7 +165,7 @@ untyped `rt.new(n)` blocks take a drop like any other value, so there is no sepa - **fast paths stay correct for free.** `recycle_leaf` already sends a block to the general `recycle` unless `(f & F_LOW) == 1`, so a block with `F_HASDROP` set can't take the leaf fast path even if the compiler guessed wrong. the collector's sweep tests `(b[1] & 7) == 1` for an unmarked live block; it adds `F_HASDROP` (or a type default) as the test for the finalize list. - **values without drop pay one test.** the runtime's free paths (unchecked `rt.free`, children found by the walk, the collector) add `if (hdr & F_HASDROP || (t = p[-1]) > 0 && t[DROP]) ...`, cheap next to `scan_ptr`. where the analyzer can see that a local never had a drop set and its type has no default, it emits today's free code, including `recycle_leaf`. - **moving blocks.** the heap never moves a live block on its own: it is a buddy allocator in `rt.zc` (`balloc`/`bfree`), the collector marks and sweeps in place with no compaction, and `core.c` only offers word fill/copy primitives (syscall cases 18 and 19) with no relocation. the one place a payload moves is a reallocation: `dgrow` (behind `push`, `dresize`, byte arrays) allocates a bigger items block, copies, and frees the old one. any such resize must move the side-table entry and the `F_HASDROP` bit to the new block before freeing the old one, and that free must not run the drop. a future `rt.realloc` follows the same rule. -- **clearing on free.** when a block is freed, at the same point where `release` clears the requested size n (bits 32..62 of `p[-4]`) on quarantine, and in `recycle`/`recycle_leaf`/`bfree` when the block goes back on a free list, its side-table entry is removed and `F_HASDROP` and `F_DROPPED` are cleared. this happens after the drop has run. so a reused address never inherits a drop from the block that was there before. +- **clearing on free.** when `release` or `recycle` frees a block (and `recycle_leaf` falls back to `recycle` for a block with the bit), its side-table entry is removed and `F_HASDROP` and `F_DROPPED` are cleared right after its drop returns, before the walk over its children (`dropfree`: `dropnow`, then `dclear`). the block is already past its drop, so nothing needs the entry while the children go. so a reused address never inherits a drop from the block that was there before. - **the collector.** one extra test per unmarked block in the sweep, a finalize list that grows like the mark stack (`reserve_mark`), and one extra mark root. - **genome.** the parser adds `heap`, the three drop forms, and the type-default method. the analyzer adds set-once/set-early, the no-move-out rule, the never-bound hint, the reassignment free, and the actor send check. the runtime adds the drop test in three places, the side table, and the finalize list. a rough guess is several hundred words; zc-lines should measure the fixpoint once code exists. @@ -175,7 +175,7 @@ untyped `rt.new(n)` blocks take a drop like any other value, so there is no sepa ## what is implemented -- runtime (`rt.zc`): `dtab` side table ([key, value drop + 1, type drop + 1], open hash, `F_STACK` so it is kept but not scanned), `set_drop`, `set_tdrop`, `dropnow`, `dclear`, `dmove` (used by `dgrow`); `F_HASDROP` 16, `F_DROPPED` 32 and `F_FINAL` 64 (collector found it unreachable). `release` and `recycle` run the drop first and clear the entry when the block goes back; `recycle_leaf` already falls back to `recycle` for a block with the bit. `gc` is `gc_sweep` (mark, `gc_drops` between mark and sweep, sweep) then `gc_finalize`, which runs the drops outside the collector's frame. +- runtime (`rt.zc`): `dtab` side table ([key, value drop + 1, type drop + 1], open hash, `F_STACK` so it is kept but not scanned), `set_drop`, `set_tdrop`, `dropnow`, `dropfree`, `dclear`, `dmove` (used by `dgrow`); `F_HASDROP` 16, `F_DROPPED` 32 and `F_FINAL` 64 (collector found it unreachable). `release` and `recycle` take one `F_HASDROP` test and call `dropfree`, which runs the drop and then clears the entry, before they walk the children; `recycle_leaf` already falls back to `recycle` for a block with the bit. `gc` is `gc_sweep` (mark, `gc_drops` between mark and sweep, sweep) then `gc_finalize`, which runs the drops outside the collector's frame. - `gc_drops`: drops of marked values are marked (to a fixpoint); each unmarked value whose drop hasn't run gets `F_FINAL` and is kept with what it reaches; a candidate reached by another candidate waits for a later collection, so parents drop before children. - o3: `rt.o3_enter`/`rt.o3_leave` around `o3 { }` blocks and calls from normal code into o3 functions; `gc` inside sets `gc_pending`, the outermost leave runs it. - compiler: `heap(e)`, the three drop forms (a closure there gets `this`), the struct `drop` method as type drop (`set_tdrop` after `new(T)` and `heap`), set-once and set-early on locals, temporaries held in a hidden slot until the statement ends with a hint, the actor `send` check, the o3 errors. |
