Skip to content

ssa: terminate empty defer dispatch blocks - #2213

Closed
zhouguangyuan0718 wants to merge 2 commits into
xgo-dev:mainfrom
zhouguangyuan0718:agent/ssa-empty-defer-dispatch
Closed

ssa: terminate empty defer dispatch blocks#2213
zhouguangyuan0718 wants to merge 2 commits into
xgo-dev:mainfrom
zhouguangyuan0718:agent/ssa-empty-defer-dispatch

Conversation

@zhouguangyuan0718

Copy link
Copy Markdown
Contributor

Summary

  • position the builder at the defer processing block when no deferred statements were registered
  • keep the generated control-flow graph terminated in explicit defer-stack functions
  • verify the regression case with LLVM module verification

This is the main-update compatibility fix extracted independently from #2196. It has no dependency on the process-isolation series.

Size

  • 2 files
  • 11 additions, 1 deletion

Tests

  • go test ./ssa -run ^TestExplicitDeferStackDrainWithoutLoopCases$ -count=1
  • git diff --check

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

The fix is correct and minimal. When endDefer runs with no recorded defer statements (n == 0), the for i := n-1; i >= 0 loop never executes, so the builder is never repositioned onto procBlk. The trailing SetThreadDefer / IndirectJump (eh.go:597-599) would then be emitted into a stale/wrong block, producing invalid IR. Setting the builder to procBlk for the n == 0 case mirrors the n == 1 path (where the loop leaves the builder on rethsNext[1] == procBlk), so this is the right target block.

The test change is a meaningful regression guard: TestExplicitDeferStackDrainWithoutLoopCases now materializes a real recover block and adds an llvm.VerifyModule assertion (eh_defer_test.go:95-97) that would fail against the un-fixed code. The added github.com/xgo-dev/llvm import and VerifyModule(..., ReturnStatusAction) pattern match existing test conventions in this package.

Security and performance: no concerns — this is cold-path compile-time codegen bookkeeping.

Note: tests could not be executed in this environment (LLVM C headers unavailable), so verification is by code inspection only.

One minor maintainability suggestion is left inline.

Comment thread ssa/eh.go
b.Jump(rethNext)
}
}
if n == 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The n == 0 branch encodes a subtle, load-bearing invariant with no comment: the n >= 1 loop above implicitly leaves the builder positioned on rethsNext[1] (which equals procBlk when n == 1, since the final i == 0 iteration skips its Jump), so the trailing SetThreadDefer / IndirectJump land in procBlk. When n == 0 the loop never runs and nothing sets the insert point, so this branch is required for those instructions to be emitted into procBlk.

Consider a one-line comment, e.g.:

// No defer stmts: the loop above never runs, so position the builder
// on procBlk directly before emitting the tail (SetThreadDefer/IndirectJump).
if n == 0 {
    b.SetBlockEx(procBlk, AtEnd, true)
}

This matches the inline-comment style already used nearby (e.g. eh.go:601) and prevents a future maintainer from mistaking the branch for dead/redundant code.

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@zhouguangyuan0718

Copy link
Copy Markdown
Contributor Author

Closing as an unproven/unreachable frontend case.

The regression test constructs the low-level builder state directly (SetRecover + ssa:deferstack + RunDefers) without any Go source defer or defer-stack drain. The real Go frontend preserves a stronger invariant:

  • a normal source defer lowers through Builder.Defer, which appends an entry to self.stmts;
  • a range-over-func defer makes rangeFuncCallNeedsDeferDrain insert DeferStackDrain, which also appends a drainer entry;
  • without a source defer, x/tools removes the unused synthetic ssa:deferstack, and no relevant RunDefers path is emitted.

We were unable to produce a Go source fixture that reaches endDefer with n == 0. Main and the existing real-source rangefunc/defer suite remain green. The current verifier failure therefore demonstrates only an artificially assembled internal state, not a compiler bug reachable from Go. We can reopen with a baseline-failing Go-level reproducer if one is found later.

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