Give an interface value one defined lifetime operation. if_delete (A/X = value
pointer) treats delete null as a no-op, validates the object half with the
heap's always-on check before any destructor address is read, deletes a live
object exactly once through obj_delete, and clears the value so a repeated
delete is a no-op. A refusal is C=1 with the new IF_ERR_FREED, and nothing is
called or freed. Aliases stay ordinary copies; the heap validation, not a
reference count, is what makes an outliving alias detectable.
src/runtime/ifelife.s (new): if_deletesrc/runtime/heap.s: heap_check (exported validation subset of heap_free)src/common/interfaces.inc: IF_ERR_FREED = 6tests/runtime/lifetime-test.s (new), scripts/build-lifetime-test.ps1,
scripts/verify-lifetime-test.ps1 (new)tests/semantic/objects.n8, tests/semantic/objects.expect (new)tests/parser/parser-test.s: OBJECTS.N8 fixture, count 43scripts/build-parser-test.ps1: objects.n8 expectation entrydocs/decisions.md (D147), docs/architecture.md (8.4),
docs/memory-map.md (lifetime rows)planning/claims/E06-S06.mdbuild/delete through an interface invokes the destructor and frees once:
phase 1 allocates a live object through obj_new, deletes it by calling
if_delete on a value whose table is tab_foo, and requires
destroy_count = 1, if_error = IF_OK, heap_free_total = $1000, and the
four value bytes zero. Phase 3 does the same for an object reached through a
second interface table and checks the destructor count rises to exactly 2.delete null-interface is a no-op: phase 2 deletes a zero value and a
value with a nonzero table but zero object, both with C=0, no destructor call
(destroy_count still 1) and no error.if_error = IF_ERR_NULL and no method run (hit_count
still 0), then shows the next call on a live value succeeds and bumps
hit_count to 1. if_call itself is unchanged by this story; if_delete
reports C=1 only for a value that is not live.IF_ERR_FREED, destroy_count still 2 and the free capacity unchanged, so a
freed object's destructor is never read. Phase 4 copies the value to an alias
first, deletes through the original value, and requires the alias to be
refused the same way with the destructor count still 3 and the free capacity
back to $1000. Detection is the always-on
heap_check (range, used bit, even size, $A55A magic) that also guards
heap_free, which is the mechanism architecture §8 promises; poisoning
remains optional.objects.n8 covers multiple interfaces end to end: the semantic fixture
declares Printable and Resettable, a Counter implementing both and a
Ticker, and uses interface variables for calls and for delete; the parser
accepts it with zero diagnostics and dumps 5 types, 10 members and 3 interface
slots.obj_new/obj_delete);
both are complete. The dispatch path is untouched: if_call was not modified,
so lifetime cannot regress call behaviour.if_delete lives in its own module so the value/dispatch fixtures do not link
the heap, objects or construction code; heap_check lives in heap.s so the
validation rule exists once rather than as a second copy for deletion.delete through an interface is still compiler
work; this story fixes and proves the runtime contract.heap_check first failed to assemble with three out-of-range branches; the
short branches now fall through to jmp trampolines, matching the project's
±127-byte convention.POINT_OK val_foo); the zero check became a callable chk_zero.MARK macro reloads A, so
it must run before the descriptor is loaded, not between the load and
obj_new.val_null
held whatever RAM had; the fixture now writes its null values explicitly,
which is also a reminder that generated programs must zero their own state.scripts/verify-lifetime-test.ps1 (x128, fresh D81),
scripts/verify-parser-test.ps1 (43 semantic/parser fixtures),
git diff --check.LIFETIME OK; its PRG ends $2C85, below the $4000 code ceiling. The
parser suite reports verified parser: 43 fixtures, with objects.n8
accepted as 5 types, 10 members, 3 interface slots and the lowest expression
stack pointer $A7.if_delete trusts a value whose object half is live, so a
forged value whose object is a live block of another class reaches that
class's destructor through obj_delete's normal descriptor read; the value's
table half is deliberately ignored, so deleting through a stale table is not
detected; and a value that is not four bytes in RAM cannot be cleared, which
the emitter must respect when delete is wired into code generation.Powered by TurnKey Linux.