This is an archive of the discontinued LLVM Phabricator instance.

[funcattrs] check reachability to improve noreturn
ClosedPublic

Authored by nickdesaulniers on Feb 11 2022, 11:49 AM.

Details

Summary

There was a fixme in the code pertaining to attributing functions as
noreturn. By using reachability, if none of the blocks that are
reachable from the entry return, then the function is noreturn.

Previously, the code only checked if any blocks returned. If they're
unreachable, then they don't matter.

This improves codegen for the Linux kernel.

Fixes: https://github.com/ClangBuiltLinux/linux/issues/1563

Diff Detail

Event Timeline

nickdesaulniers requested review of this revision.Feb 11 2022, 11:49 AM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 11 2022, 11:49 AM

One thing I was curious of; does this recalculate a new DominatorTree for each function?

I noticed some other usages of DominatorTree first would query Pass::getAnalysisIfAvailable<DominatorTreeWrapperPass>()). Should I be doing that, too? (will require plumbing the Pass object through a few static functions, but isn't too bad).

nikic added a subscriber: nikic.

Yes, you'd want to fetch the DominatorTree from the FunctionAnalysisManager. Though as you're only interested in reachability and not actual dominance, you don't actually need a DT here. You can simply walk over the reachable blocks rather than all blocks.

Yes, you'd want to fetch the DominatorTree from the FunctionAnalysisManager. Though as you're only interested in reachability and not actual dominance, you don't actually need a DT here. You can simply walk over the reachable blocks rather than all blocks.

Isn't DominatorTree necessary to compute reachability?

Yes, you'd want to fetch the DominatorTree from the FunctionAnalysisManager. Though as you're only interested in reachability and not actual dominance, you don't actually need a DT here. You can simply walk over the reachable blocks rather than all blocks.

Isn't DominatorTree necessary to compute reachability?

You can traverse all reachable blocks by adding successors to a worklist and checking a visited set. Or as a very lazy way, iterate over depth_first(F) or breath_first(F), though those provide additional guarantees not strictly needed here (I don't think we have a helper for plain iteration over reachable blocks).

helper for plain iteration over reachable blocks

Maybe it could be useful to create one now?

nikic added inline comments.Feb 11 2022, 1:03 PM
llvm/lib/Transforms/IPO/FunctionAttrs.cpp
1618

I don't think this is possible for a function with a definition?

1629–1631
nickdesaulniers marked 2 inline comments as done.
  • simplify insert as per @nikic
llvm/lib/Transforms/IPO/FunctionAttrs.cpp
1626

Probably want to keep the fixme about recursion.

  • retain modified fixme
nikic accepted this revision.Feb 11 2022, 1:22 PM

LGTM

llvm/lib/Transforms/IPO/FunctionAttrs.cpp
1619

SmallPtrSet, you don't care about iteration order here.

1627–1630

A bit better to avoid pushing to the worklist in the first place.

This revision is now accepted and ready to land.Feb 11 2022, 1:22 PM
  • use SmallPtrSet, avoid push if visited
nickdesaulniers marked 2 inline comments as done.Feb 11 2022, 1:45 PM
nickdesaulniers retitled this revision from [funcattrs] use DominatorTree to improve noreturn to [funcattrs] check reachability to improve noreturn.
nickdesaulniers edited the summary of this revision. (Show Details)
  • update description
rnk added a comment.Feb 14 2022, 11:29 AM

Do we know which pass introduces unreachable code without cleaning it up? That seems like a missed optimization, possibly a phase ordering issue, or we could tack on a removeUnreachableBlocks to whichever pass is modifying the CFG in the first place.

nickdesaulniers added a comment.EditedFeb 14 2022, 12:38 PM

Do we know which pass introduces unreachable code without cleaning it up? That seems like a missed optimization, possibly a phase ordering issue, or we could tack on a removeUnreachableBlocks to whichever pass is modifying the CFG in the first place.

In this particular case, there is no pass introducing an unreachable; the unreachable is explicit in the source.

Because there's no analog for __attribute__((noreturn)) for inline asm, the Linux kernel frequently uses the pattern:

asm ("<instruction that will trap>");
__builtin_unreachable();
rnk accepted this revision.Feb 14 2022, 2:02 PM

Do we know which pass introduces unreachable code without cleaning it up? That seems like a missed optimization, possibly a phase ordering issue, or we could tack on a removeUnreachableBlocks to whichever pass is modifying the CFG in the first place.

In this particular case, there is no pass introducing an unreachable; the unreachable is explicit in the source.

It sounds like the phase ordering issue is that functionattrs runs before simplifycfg, and the frontend emits unreachable basic blocks. Of course, simplifycfg also benefits from functionattrs, so it's not trivial to fix. I think this is a known issue, no need to do anything about it now.

This revision was landed with ongoing or failed builds.Feb 14 2022, 2:02 PM
This revision was automatically updated to reflect the committed changes.

rnk accepted this revision.

apologies, I had applied this with arc patch, rebased, rebuilt, retested, and pushed before I saw the additional +1; sorry it didn't make it into the commit message, but I appreciate the review!