Class ThrowStatementGenerator
- All Implemented Interfaces:
Function<EK9Parser.ThrowStatementContext, List<IRInstr>>
Grammar support: throwStatement: THROW (call | identifierReference)
Two forms: 1. throw Exception("message") - constructor call expression 2. throw exceptionVariable - variable reference
Key behaviors: - For identifier: Applies RETAIN (ownership transfer) then THROW - THROW is a terminating instruction (transfers control to exception mechanism) - SCOPE_EXIT after throw is unreachable in normal flow (backend executes during unwinding)
ARC Ownership Transfer Semantics: Following the Producer/Consumer pattern from EK9_ARC_OWNERSHIP_TRANSFER_PATTERN.md:
The two forms differ, and the difference is NOT cosmetic — it is what makes the count balance:
throw <identifier> - the variable's declaration scope ALREADY owns a reference: (at declaration) RETAIN + SCOPE_REGISTER // scope owns it RETAIN (no SCOPE_REGISTER) // +1 for the transfer THROW (unwinding) SCOPE_EXIT // releases the declaration scope's reference (catch) REFERENCE (no RETAIN) + SCOPE_REGISTER; catch scope exit releases -> 0 throw <Type>(...) - a freshly CONSTRUCTED exception nobody else owns: CALL <ctor> // producer result is born +1 (the ARC +1 convention) THROW // that +1 IS the transfer - NO extra RETAIN (catch) REFERENCE (no RETAIN) + SCOPE_REGISTER; catch scope exit releases -> 0
Result: the exception reaches the catch handler at refcount 1 in BOTH forms, and is released on catch scope exit.
Why the constructed form must NOT also RETAIN. The extra RETAIN in the identifier form exists
to survive the declaration scope's release during unwinding. A constructed exception has no
declaration scope and is never SCOPE_REGISTERed, so there is no such release to survive: retaining it
leaves the constructor's original +1 with no owner. On the JVM this is invisible (RETAIN is a no-op
and the GC collects), but on the ARC/LLVM backend it orphans the exception object and everything it
owns - measured as ~4 leaked objects per thrown exception, on BOTH the setjmp and the
invoke/landingpad native paths. See docs/llvm-native/EK9_NATIVE_EXCEPTION_UNWIND_DESIGN.md
§10.3 and PanicThrowInstrs, which had the same defect.
-
Field Summary
Fields inherited from class AbstractGenerator
constantCopyOnAccessLoader, debugInfoCreator, instructionBuilder, MAX_MESSAGE_LENGTH, stackContext, synthesisedCallMetaData, typeNameOrException -
Constructor Summary
ConstructorsConstructorDescriptionThrowStatementGenerator(IRGenerationContext stackContext, GeneratorSet generators) Constructor accepting injected GeneratorSet for access to sub-generators. -
Method Summary
Methods inherited from class AbstractGenerator
addCoverageProbe, createTempVariable, createTempVariableFromContext, extractReturnType, getRecordedSymbolOrException, processBlockStatements, processBlockStatements, truncateMessage, truncateMessage
-
Constructor Details
-
ThrowStatementGenerator
ThrowStatementGenerator(IRGenerationContext stackContext, GeneratorSet generators) Constructor accepting injected GeneratorSet for access to sub-generators.
-
-
Method Details
-
apply
- Specified by:
applyin interfaceFunction<EK9Parser.ThrowStatementContext, List<IRInstr>>
-