When EmitExpr generates code for A + B + C (string concat), the
intermediate temp T1 = A + B (from _StringConcat) starts with
refcnt=0. Before this fix that temp was silently abandoned:
calling _StringRelease(T1) with refcnt=0 would decrement to -1
(the IMMORTAL sentinel), so the string was never freed and the
compiler slowly exhausted all available memory when processing
large source files.
The correct pattern is to temporarily own the intermediate before
the second concat and release it after, so _StringConcat has a
chance to copy its bytes first:
_StringAddRef(T1) ; 0 -> 1 (temporary ownership)
T2 = _StringConcat(T1, C) ; copies T1's bytes into T2
_StringRelease(T1) ; 1 -> 0 -> freed
The same ownership pattern is applied to the right operand when
it is itself an unowned string expression (TBinaryExpr, TFuncCallExpr,
or TMethodCallExpr).
Also mirrors EmitMethodCall var-param forwarding fix (IsVarParam
check for both ObjExpr path and main path) and the previously-landed
EmitVarArgAddr, IsGlobal, vtable-in-ctor-with-args fixes to the hand
source (tests/blaise-compiler.pas).
With this fix stage-2 (blaise-compiler compiled by itself) compiles
the full hand source in 17s using 23MB RSS instead of hitting the OOM
killer at 47GB.