Skip to content

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

Merged
slepp merged 1 commit intomainfrom
fix/struct-return-nested-scope
Mar 21, 2026
Merged

fix(codegen): path-specific drops for struct return from nested scopes#284
slepp merged 1 commit intomainfrom
fix/struct-return-nested-scope

Conversation

@slepp
Copy link
Contributor

@slepp slepp commented Mar 21, 2026

Supersedes #276 (rebased onto current main after 6 major merges).

Problem

return StructType{...} from inside nested control flow (if/for/while) 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 scanReturns/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 field before calling user-defined Drop on potentially uninitialized structs
  • FunctionGenerationScope: updated to save/restore new state fields

Tests

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

Closes #276

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

`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.

The fix introduces lazy returnSlot creation with path-specific drop
semantics:

- ensureReturnSlot(): creates return slot lazily for aggregate types
  at the function entry block when a nested return is encountered
- earlyReturnFlag: distinguishes explicit return statements from
  trailing expressions for drop path selection
- Pre-scan with scanReturns/hasNestedReturn: detects nested returns
  before body generation to enable return guards
- Path-specific drops in popDropScope: scf.if(earlyReturnFlag) with
  separate exclusion sets per branch — early-return path excludes
  return-expression vars, normal path excludes trailing-expression vars
- Struct alloca promotion: extends declareVariable promotion to struct
  types when returnSlotIsLazy, with zeroinitializer
- Struct null-guard: checks first pointer field before calling
  user-defined Drop on potentially uninitialized structs
- collectExcludeVarsFromBlock: extended to recurse into nested control
  flow (if/for/while/match) to find return statements
- FunctionGenerationScope: updated to save/restore returnSlotIsLazy
  and earlyReturnFlag alongside existing return state
@slepp slepp enabled auto-merge (squash) March 21, 2026 20:03
@slepp slepp merged commit 298c00c into main Mar 21, 2026
12 checks passed
@slepp slepp deleted the fix/struct-return-nested-scope branch March 21, 2026 22:25
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