This is an archive of the discontinued LLVM Phabricator instance.

[FuncAttrs] Infer noreturn
ClosedPublic

Authored by aeubanks on Dec 30 2020, 7:02 AM.

Details

Summary

A function is noreturn if all blocks terminating with a ReturnInst
contain a call to a noreturn function. Skip looking at naked functions
since there may be asm that returns.

This can be further refined in the future by checking unreachable blocks
and taking into account recursion. It looks like the attributor pass
does this, but that is not yet enabled by default.

This seems to help with code size under the new PM since PruneEH does
not run under the new PM, missing opportunities to mark some functions
noreturn, which in turn doesn't allow simplifycfg to clean up dead code.
https://bugs.llvm.org/show_bug.cgi?id=46858.

Diff Detail

Event Timeline

aeubanks created this revision.Dec 30 2020, 7:02 AM
aeubanks requested review of this revision.Dec 30 2020, 7:02 AM
Herald added a reviewer: baziotis. · View Herald Transcript
Herald added a project: Restricted Project. · View Herald Transcript
madhur13490 added inline comments.
llvm/lib/Transforms/IPO/FunctionAttrs.cpp
1400

I think you can actually avoid this variable. You can declare "FoundNoReturnCall" outside the loops and define it in each iteration of the outer loop. If (!FoundNoReturnCall) in outer loop then break and if (FoundNoReturnCall) outside the loop then F->setDoesNotReturn(). CanReturn is acting as an accumulator here but if you declare FoundNoReturnCall outside the loop then it can act for the same purpose.

1409–1411

Can combine them to one condition in some helper function and call it here. This will be more readable and maintenable

aeubanks updated this revision to Diff 314138.Dec 30 2020, 12:01 PM

factor out determining if instruction returns

aeubanks marked an inline comment as done.Dec 30 2020, 12:03 PM
aeubanks added inline comments.
llvm/lib/Transforms/IPO/FunctionAttrs.cpp
1400

I'm not following, could you post the code?
This is basically an all(any()) where the all means "every basic block must" and the any means "any instruction in the block doesn't return". I believe you need two variables for all(any()).

aeubanks updated this revision to Diff 314153.Dec 30 2020, 3:57 PM

make some variable names clearer

madhur13490 added inline comments.Dec 31 2020, 2:44 AM
llvm/lib/Transforms/IPO/FunctionAttrs.cpp
1400

Ah! I misremembered noreturn as any(any). But I guess you can still do it with one variable if you have unsetDoesNotReturn in llvm::Function. Something like below,

bool Changed = false;
   F->setDoesNotReturn();
   for (BasicBlock &BB : *F) {
     if (!isa<ReturnInst>(BB.getTerminator()))
       continue;
     bool BasicBlockCanReachReturn = true;
     for (Instruction &I : BB) {
       if (instructionDoesNotReturn(I)) {
         BasicBlockCanReachReturn = false;
         break;
       }
     }
     if (BasicBlockCanReachReturn) {
       F->unsetDoesNotReturn();
       Changed = true;
       break;
     }
   }

The method is not currently present but I see no reason for ignoring it. I see removeFnAttr being use to one can add such method. More generally, there can be methods like changeNoReturnAttrib(bool) which changes the state to the input bool; if changeNoReturnAttrib(true) then it adds the attribute, if changeNoReturnAttrib(false) it remove the attribute - of course after checking redundancy.

However, I think we can continue for now.

aeubanks added a reviewer: rnk.Jan 4 2021, 3:31 PM
rnk accepted this revision.Jan 5 2021, 11:36 AM

lgtm

llvm/lib/Transforms/IPO/FunctionAttrs.cpp
1415–1430

This is code golf, but there is this code style rule here to consider:
https://llvm.org/docs/CodingStandards.html#turn-predicate-loops-into-predicate-functions

This code could be:

// A BB can only return if it ends in ReturnInst and does not contain any calls to noreturn functions.
static bool canBasicBlockReturn(const BasicBlock &BB) {
  if (isa<ReturnInst>(BB.getTerminator()))
    return llvm::none_of(BB, instructionDoesNotReturn);
  return false;
}

...

if (llvm::none_of(F, canBasicBlockReturn)) {
  Changed = true;
  F->setDoesNotReturn();
}

Feel free to implement or disregard.

This revision is now accepted and ready to land.Jan 5 2021, 11:36 AM
aeubanks updated this revision to Diff 314689.Jan 5 2021, 12:31 PM

clean up code

This revision was landed with ongoing or failed builds.Jan 5 2021, 1:26 PM
This revision was automatically updated to reflect the committed changes.

Hi folks, I'm afraid an 8% regression in h264ref (SPEC INT 2006) bisects to this patch. This in on AArch64 with -flto on a Neoverse-N1 based system, but I wouldn't be surprised if this showed up elsewhere too.

How do people feel about reverting the patch while we investigate the regression? To start with, I'll try to find out why this has caused the drop in performance: it doesn't seem immediately obvious to me.

Hi folks, I'm afraid an 8% regression in h264ref (SPEC INT 2006) bisects to this patch. This in on AArch64 with -flto on a Neoverse-N1 based system, but I wouldn't be surprised if this showed up elsewhere too.

How do people feel about reverting the patch while we investigate the regression? To start with, I'll try to find out why this has caused the drop in performance: it doesn't seem immediately obvious to me.

I really can't see this change causing performance regressions. It only infers more precise function attributes. Could you do some initial investigation or come up with a repro? Maybe an optimization remark that doesn't fire after this change?

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

To clarify, when you say

However, I think we can continue for now.

do you mean the current patch looks good or that

Hi folks, I'm afraid an 8% regression in h264ref (SPEC INT 2006) bisects to this patch. This in on AArch64 with -flto on a Neoverse-N1 based system, but I wouldn't be surprised if this showed up elsewhere too.

How do people feel about reverting the patch while we investigate the regression? To start with, I'll try to find out why this has caused the drop in performance: it doesn't seem immediately obvious to me.

I really can't see this change causing performance regressions. It only infers more precise function attributes. Could you do some initial investigation or come up with a repro? Maybe an optimization remark that doesn't fire after this change?

FWIW, I have seen similar things in the past. Better information leads to regressions because of heuristics firing later on. The "bug" is somewhere else and only exposed.

Could you do some initial investigation or come up with a repro? Maybe an optimization remark that doesn't fire after this change?

Yes, I will do an initial investigation, hopefully resulting in a reproducer. It is always a bit of a challenge with LTO benchmarks, but the regression is large enough that it should be easy to spot.

markus added a subscriber: markus.Jan 13 2021, 6:14 AM

Not terribly important but isn't

A function is noreturn if all blocks terminating with a ReturnInst contain a call to a noreturn function.

slightly in conflict with the Lint pass containing the following "Unusual" warning?

void Lint::visitReturnInst(ReturnInst &I) {
  Function *F = I.getParent()->getParent();
  Assert(!F->doesNotReturn(),
         "Unusual: Return statement in function with noreturn attribute", &I);

  if (Value *V = I.getReturnValue()) {
    Value *Obj = findValue(V, /*OffsetOk=*/true);
    Assert(!isa<AllocaInst>(Obj), "Unusual: Returning alloca value", &I);
  }
}

Well I guess "Unusual" is not really a warning but still it kind of indicates that something is off. So maybe that Lint printout should be removed or updated somehow?

Not terribly important but isn't

A function is noreturn if all blocks terminating with a ReturnInst contain a call to a noreturn function.

slightly in conflict with the Lint pass containing the following "Unusual" warning?

FWIW, that is not an accurate definition of noreturn since we have endless loops and the calles might not always
be noreturn but just for the parameters passed.

void Lint::visitReturnInst(ReturnInst &I) {
  Function *F = I.getParent()->getParent();
  Assert(!F->doesNotReturn(),
         "Unusual: Return statement in function with noreturn attribute", &I);

  if (Value *V = I.getReturnValue()) {
    Value *Obj = findValue(V, /*OffsetOk=*/true);
    Assert(!isa<AllocaInst>(Obj), "Unusual: Returning alloca value", &I);
  }
}

Well I guess "Unusual" is not really a warning but still it kind of indicates that something is off. So maybe that Lint printout should be removed or updated somehow?

I'd leave the warning if we replace all ReturnInst with unreachable. Otherwise it is "unusual".

Could you do some initial investigation or come up with a repro? Maybe an optimization remark that doesn't fire after this change?

Yes, I will do an initial investigation, hopefully resulting in a reproducer. It is always a bit of a challenge with LTO benchmarks, but the regression is large enough that it should be easy to spot.

This appears to be a (very painful) second order effect that is causing an increase in branch misses in one of the loops in h264ref. There is no codegen difference in the area, only a slight change in alignment. So nothing to do with this patch really, just bad luck.