Records with value semantics may contain fields with reference semantics
(String, dynamic array, interface, nested managed record). The QBE
backend's field walkers only covered the String and Class cases, so:
* A record whose field is a dynamic array leaked the buffer on every
copy and return-by-value — _DynArrayAddRef/_DynArrayRelease were not
invoked from EmitRecordCopy or EmitRecordReleaseFields, and there
was no scope-exit release for dyn-array locals.
* A class with a record field whose own fields were managed (String,
Class, dyn-array, interface, or a deeper record) leaked those
sub-fields when the class was released — _FieldCleanup_<T> skipped
record-typed fields entirely.
* Interface fields inside records were never copied or released.
This commit:
* Adds _DynArrayAddRef / _DynArrayRelease to blaise_arc.pas alongside
the existing _StringAddRef/_StringRelease and _ClassAddRef/_ClassRelease.
They walk the existing [refcount:4][length:4] dyn-array buffer header
(layout defined by _DynArraySetLength in blaise_str.pas), are nil-safe
and immortal-safe (rc = -1), and use _AtomicAddInt32 / _AtomicSubInt32
(lock xadd) so the refcount stays sound under threading — matching the
other ARC primitives.
* Extends EmitRecordCopy, EmitRecordReleaseFields, and
EmitFieldCleanupFn to handle tyDynArray, tyInterface, and nested
tyRecord fields (the latter by recursing through the shared
EmitRecordReleaseFields walker).
* Routes tyDynArray field assignments through the existing IsArc
path so a field write retains the new buffer and releases the old.
* Adds tyDynArray to EmitArcCleanup + EmitExcPathArcCleanup so
dyn-array locals balance their first-assignment retain at scope
exit (and on exception paths).
* Adds tyDynArray to EmitAssignment for variable-to-variable
assignment so b := a between dyn-array vars is refcount-symmetric.
Adds two regression tests in cp.test.e2e.records.pas:
* TestRun_Record_DynArrayField_ReturnByValue_NoLeak — 5000-iter
return-by-value of a record with a dyn-array field; asserts the
buffer contents survive (proves AddRef/Release pairing) and the
program runs to completion.
* TestRun_Class_RecordField_NestedClass_FullCleanup — class -> record
-> class -> record -> class chain with each Create/Destroy bumping
a global AliveCount; after 100 iterations AliveCount must be 0,
proving _FieldCleanup recurses through nested record fields.
TestRunner: OK (2663 tests). Self-bootstrap from a master-built stage-1
through stage-2/3/4 reaches a clean stage-3 == stage-4 IR fixpoint.