Compiler:
- TRecordTypeDesc gains HasDestroyMethod boolean property, set by the
semantic analyser whenever a class (regular or generic instantiation)
declares a 'Destroy' method.
- EmitFieldCleanupFn emits a call to $<TypeName>_Destroy before the
ARC field-release loop when HasDestroyMethod is set, giving classes a
hook to free raw resources (malloc buffers etc.) at refcount zero.
RTL (Generics.Collections):
- TList<T> and TDictionary<K,V> replace 'procedure Free' with
'procedure Destroy'. Destroy frees the internal raw buffers (FData /
FKeys / FValues) and nils them, making re-entrant calls safe.
Free is no longer user-defined on these classes so List.Free uses
the built-in ARC release path (_ClassRelease + slot nil-out).
tests/phase3_milestone.pas:
- TIntList and TStrIntDict migrated to Destroy (removed FreeMem(Self)
which was unsafe under universal ARC on TObject).
Tests (718 total, 0 failures):
- cp.test.arc: 3 new IR tests for Destroy dispatch in field cleanup fn
(non-generic, absent-on-class-without-Destroy, generic instantiation).
- cp.test.e2e: 4 new end-to-end / valgrind tests — ClassDestroy frees
buffer, TList ARC lifecycle, Phase3Milestone stdout and valgrind-clean.
The exception frame was allocated as alloc16 32 (32 × 16 = 512 bytes on paper,
but QBE's alloc16 N means N items of 16 bytes: so 32 × 16 = 512 is actually
correct). Wait - re-reading: alloc16 N in QBE allocates N bytes aligned to 16.
So alloc16 32 was only 32 bytes — far less than sizeof(BlaiseExcFrame) which
needs ~216 bytes on Linux x86_64 (200-byte jmp_buf + two pointer fields).
setjmp writes its full jmp_buf into the undersized slot, silently corrupting
whatever sat above it on the stack: saved registers, local variables, and the
virtual method pointer loaded for virtual dispatch. Any try block followed by
a virtual method call in expression position crashed with a bad function pointer.
Fix: alloc16 32 → alloc16 512 in both EmitTryFinallyStmt and EmitTryExceptStmt,
matching the RTL contract documented in blaise_exc.c lines 8-13.
Two new regression tests assert alloc16 512 appears in emitted IR for both
try forms. 628 tests pass (was 626).
Also adds tests/phase2_milestone.pas: linked list with virtual dispatch,
inheritance, try/finally, and 'is' type test — zero valgrind leaks.
Updates design.adoc Phase 3 status table to reflect current implementation.
Four bugs fixed on the path to the milestone:
1. Non-generic class fields resolved with FindType instead of
FindTypeOrInstantiate — typed pointer fields (^Integer, ^string) in
concrete classes failed with 'Unknown type'.
2. Integer/Boolean/Int64 local variables not zero-initialised — QBE
validator rejected 'slot read but never stored to' for variables
written only through var-param pointers.
3. Class method var-param signature wrong — EmitMethodDef emitted the
param type (w for Integer) rather than 'l' (pointer) for var params;
EmitParamAllocs spilled the pointer with storew instead of storel.
4. Method call sites did not pass addresses for var-param arguments —
EmitMethodCallExpr always called EmitExpr (value load) rather than
forwarding %_var_Name (address) for IsVarParam params.
Milestone result (valgrind --leak-check=full):
7 allocs, 7 frees, 0 bytes in use at exit, ERROR SUMMARY: 0 errors
Symbol table:
- Added tyClass kind and NewClassType factory; TRecordTypeDesc now
accepts an optional kind parameter so records and classes share the
same descriptor with distinct semantics.
Semantic analyser:
- AnalyseTypeDecls runs before PushScope so type symbols land in global
scope and survive PopScope.
- Handles TRecordTypeDef and TClassTypeDef; sets IsConstructorCall on
TypeName.Create expressions and IsClassAccess on class-variable field
access and assignment nodes.
Code generator (QBE IR):
- Class variables: 8-byte pointer slot, zeroed on entry.
- Constructor calls: malloc(sizeof fields), store pointer.
- Class field access/write: load pointer, add field offset, load/store.
- ARC string assignment: AddRef new value, Release old value, then store.
- Block exit: Release every string variable in scope (EmitStringCleanup).
Lexer / Parser / AST:
- Added tkClass keyword and ParseClassDef; FieldDecl parsing refactored
into ParseFieldDecl(AFields) shared by both record and class.
- AST gains TClassTypeDef, and IsConstructorCall/IsClassAccess flags on
TFieldAccessExpr and TFieldAssignment.
RTL:
- blaise_arc.c: Phase 2 no-op stubs for _StringAddRef / _StringRelease.
- rtl/Makefile: builds blaise_rtl.a; `make install` copies it next to
the compiler binary for automatic discovery by FindRTL.
- Blaise.pas driver: FindRTL checks BLAISE_RTL env var then binary dir;
links the RTL archive when present.
Tests:
- 180 unit tests (records ×23, classes ×22, ARC ×9, codegen ×9, …).
- 4 end-to-end integration tests in tests/integration/test_arc_strings.sh
covering string assignment, two-var cleanup, reassignment cycle, and
empty-program linkage.
Docs:
- design.adoc updated with Phase 2 implementation status table.
- docs/grammar.ebnf: new authoritative EBNF grammar for the Blaise
language as implemented, including ARC semantic annotations.