Skip to content

fix(codegen): path-specific drops for struct return from nested scopes#276

Closed
slepp wants to merge 1 commit intomainfrom
refactor/raii-hardening
Closed

fix(codegen): path-specific drops for struct return from nested scopes#276
slepp wants to merge 1 commit intomainfrom
refactor/raii-hardening

Conversation

@slepp
Copy link
Contributor

@slepp slepp commented Mar 19, 2026

Fixes struct return from nested scopes (if/for/while) returning the wrong value and leaking the non-returned variable.

Problem

return StructType{...} from inside nested control flow returned the trailing expression value instead of the early return value, because initReturnFlagAndSlot excluded aggregate types from returnSlot creation. Additionally, non-returned variables were never dropped — a memory leak.

Solution

Lazy returnSlot creation with path-specific drop semantics:

  • ensureReturnSlot(): creates return slot lazily for aggregate types at the function entry block
  • earlyReturnFlag: distinguishes explicit return statements from trailing expressions for drop path selection
  • Pre-scan with hasNestedReturn: detects nested returns before body generation to enable return guards
  • Path-specific drops: scf.if(earlyReturnFlag) with separate exclusion sets per branch
  • Struct alloca promotion: extends declareVariable promotion to struct types (gated by returnSlotIsLazy) with zeroinitializer
  • Struct null-guard: checks first pointer/integer field before calling user-defined Drop on potentially uninitialized structs

Tests

3 new E2E tests covering struct return from if-body, for-loop, and outer-scope variable return. All 530 E2E tests pass.

Return of aggregate types (structs, tuples) from inside if/for/while
previously returned the wrong value — the trailing expression instead
of the early return value — because initReturnFlagAndSlot excluded
aggregate types from returnSlot creation.

This introduces lazy returnSlot creation via ensureReturnSlot() and
path-specific drop semantics that correctly handle both early-return
and normal-flow paths:

- ensureReturnSlot(): lazily creates the return slot at the function
  entry block when a return statement is encountered inside a nested
  SCF region, or eagerly when a pre-scan detects nested returns

- earlyReturnFlag: a new per-function flag set ONLY by explicit return
  statements (not trailing expressions), enabling popDropScope to
  distinguish early return from normal flow

- funcLevelEarlyReturnVarNames: variables referenced only by return
  statements, separate from trailing-expression vars, populated by a
  recursive AST pre-scan that handles else-if chains

- Path-specific drops in popDropScope: when returnSlotIsLazy, emits
  scf.if(earlyReturnFlag) with different exclusion sets per branch

- Struct alloca promotion in declareVariable: extends alloca promotion
  to LLVMStructType when returnSlotIsLazy, with zeroinitializer for
  safe null-guard detection

- Struct null-guard in emitDropEntry: checks first pointer field for
  null (or first integer field for zero in scalar-only structs) before
  calling user-defined Drop
@slepp
Copy link
Contributor Author

slepp commented Mar 21, 2026

Superseded by #284 which applies this fix rebased onto current main (after 6 major merges to the drop/RAII system). The bug was confirmed to still reproduce on main — the RAII leak audit (#277) did not address the aggregate-type returnSlot gap.

@slepp slepp closed this Mar 21, 2026
auto-merge was automatically disabled March 21, 2026 19:47

Pull request was closed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant