Class ThrowStatementGenerator

java.lang.Object
org.ek9lang.compiler.phase7.generator.AbstractGenerator
org.ek9lang.compiler.phase7.generator.ThrowStatementGenerator
All Implemented Interfaces:
Function<EK9Parser.ThrowStatementContext, List<IRInstr>>

public final class ThrowStatementGenerator extends AbstractGenerator implements Function<EK9Parser.ThrowStatementContext, List<IRInstr>>
Generates IR for throw statements.

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.