fix for Ruby build fails due to inlining functions with block label address taken

Bob Wilson bob.wilson at apple.com
Wed Jun 25 15:23:03 PDT 2014


> On Jun 25, 2014, at 2:49 PM, Eric Christopher <echristo at gmail.com> wrote:
> 
> On Wed, Jun 25, 2014 at 2:46 PM, Bob Wilson <bob.wilson at apple.com> wrote:
>> On Jun 25, 2014, at 2:30 PM, Gerolf Hoflehner <ghoflehner at apple.com> wrote:
>> 
>> I noticed one side-effect of this patch: we no longer optimize fully cases
>> like indirect-goto.c test in CodeGen below.
>> Since foo and foo2 are no longer inlined constant folding of the result in
>> main no longer happens.
>> 
>> Does anyone have an issue with this?
>> 
>> 
>> I don’t. We’ve never attempted to inline functions containing indirect
>> branches. I’m surprised that your testcase works now, but to really do it
>> correctly, we would need to check that there were no remaining uses of the
>> blockaddress that might escape the function. I’m not aware of any code that
>> does that check, and without it, it’s not a safe optimization.
>> 
>> 
> 
> Oh, wait, I see what the testcase is doing now. I'd thought it was
> inlining into something that had blockaddr rather than the other way
> around.
> 
> What does the commit for the testcase say about it?

It appears to be a new testcase that Gerolf created to demonstrate the issue he was asking about. I went looking for the commit and couldn’t find it anywhere.

> 
> Assuming it doesn't say anything enlightening then no, no issue here either.
> 
> -eric
> 
>> Thanks
>> Gerolf
>> 
>> // RUN: %clang_cc1 -triple i386-unknown-unknown -O3 -emit-llvm -o - %s |
>> grep "ret i32 2520"
>> 
>> static int foo(unsigned i) {
>>  void *addrs[] = { &&L1, &&L2, &&L3, &&L4, &&L5 };
>>  int res = 1;
>> 
>>  goto *addrs[i];
>> L5: res *= 11;
>> L4: res *= 7;
>> L3: res *= 5;
>> L2: res *= 3;
>> L1: res *= 2;
>>  return res;
>> }
>> 
>> static int foo2(unsigned i) {
>>  static const void *addrs[] = { &&L1, &&L2, &&L3, &&L4, &&L5 };
>>  int res = 1;
>> 
>>  goto *addrs[i];
>> L5: res *= 11;
>> L4: res *= 7;
>> L3: res *= 5;
>> L2: res *= 3;
>> L1: res *= 2;
>>  return res;
>> }
>> 
>> int main() {
>>  return foo(3)+foo2(4);
>> }
>> 
>> 
>> IR before:
>> 
>> ; Function Attrs: nounwind readnone
>> define i32 @main() #0 {
>> entry:
>>  ret i32 2520
>> }
>> 
>> 
>> IR now:
>> 
>> ; Function Attrs: nounwind readnone
>> define i32 @main() #0 {
>> entry:
>>  %call = tail call fastcc i32 @foo(i32 3)
>>  %call1 = tail call fastcc i32 @foo2(i32 4)
>>  %add = add nsw i32 %call1, %call
>>  ret i32 %add
>> }
>> 
>> ; Function Attrs: nounwind readnone
>> define internal fastcc i32 @foo(i32 %i) #0 {
>> entry:
>>  br label %L1
>> 
>> L1:                                               ; preds = %entry
>>  ret i32 210
>> }
>> 
>> ; Function Attrs: nounwind readnone
>> define internal fastcc i32 @foo2(i32 %i) #0 {
>> entry:
>>  br label %L1
>> 
>> L1:                                               ; preds = %entry
>>  ret i32 2310
>> }
>> 
>> 
>> On Jun 25, 2014, at 11:32 AM, Bob Wilson <bob.wilson at apple.com> wrote:
>> 
>> 
>> On Jun 25, 2014, at 10:47 AM, Gerolf Hoflehner <ghoflehner at apple.com> wrote:
>> 
>> Hi
>> 
>> there have been reports on Ruby segfaults triggered by inlining functions
>> with block label address taken like
>> 
>> 
>> //Ruby code snippet
>> vm_exec_core() {
>>  finish_insn_seq_0 = &&INSN_LABEL_finish;
>> INSN_LABEL_finish:
>>  ;
>> }
>> 
>> This kind of scenario can also happen when LLVM picks a subset of blocks for
>> inlining,
>> which is the case with the actual code in the Ruby environment.
>> 
>> Background:
>> LLVM suppresses inlining for such functions when there is an indirect
>> branch. The attached
>> patch does so even when there is no indirect branch. Note that user code
>> like above would not
>> make much sense: using the global for jumping across function boundaries
>> would be illegal.
>> 
>> Why is there a segfault:
>> 
>> In the snipped above the block with the label is recognized as dead. So it
>> is eliminated. Instead
>> of a block address the cloner stores a constant (sic!) into the global
>> resulting in the segfault (when
>> the global is used in a goto).
>> 
>> Why did it work in the past then:
>> 
>> By luck. In older versions vm_exec_core was also inlined but the label
>> address used
>> was the block label address in vm_exec_core. So the global jump ended up in
>> the original function
>> rather than in the caller which accidentally happened to work.
>> 
>> 
>> -Gerolf
>> 
>> <0001-Suppress-inlining-when-the-block-address-is-taken.patch>
>> 
>> 
>> The change is fine, but I still have more comments about comments….
>> 
>> Inlining function with block label address taken can cause
>> many problem and requires a rich infrastructure to support
>> including escape analysis. At this point the safest approach
>> to address these problems is by blocking inlining from
>> happening.
>> 
>> 
>> Can you please put more of the context into the commit message? You’ve got
>> some good background in the email above, and it would be useful to have that
>> in the commit.
>> 
>> 
>> rdar://17245966
>> ---
>> lib/Analysis/IPA/InlineCost.cpp        | 21 ++++++++++++++++++++-
>> test/Transforms/Inline/blockaddress.ll |  5 +++--
>> 2 files changed, 23 insertions(+), 3 deletions(-)
>> 
>> diff --git a/lib/Analysis/IPA/InlineCost.cpp
>> b/lib/Analysis/IPA/InlineCost.cpp
>> index 4536583..b079f46 100644
>> --- a/lib/Analysis/IPA/InlineCost.cpp
>> +++ b/lib/Analysis/IPA/InlineCost.cpp
>> @@ -1097,6 +1097,16 @@ bool CallAnalyzer::analyzeCall(CallSite CS) {
>>     BasicBlock *BB = BBWorklist[Idx];
>>     if (BB->empty())
>>       continue;
>> +    // Disallow inlining of functions when the address of a label is
>> +    // taken. Usually when a block address is taken there is also
>> +    // an indirect branch which effectively suppresses inlining.
>> +    // But when we partially inline and only take a subset of the blocks
>> +    // into account there may not be an indirect branch. As cautionary
>> +    // measure we suppress inlining when there is a block label address
>> taken.
>> +    // See also the comments to visitIndirectBrInst() and isInlineViable().
>> +
>> +    if (BB->hasAddressTaken())
>> +      return false;
>> 
>> 
>> Please remove the blank line between the comment and the code.
>> 
>> You should remove the comment sentence beginning with “As cautionary
>> measure…”. It is not just a cautionary measure — it fixes a serious
>> miscompile of Ruby.
>> 
>> I’m not sure it is necessary to cross-reference the comments in the other
>> functions, but regardless, you should update the visitIndirectBrInst comment
>> to remove this sentence: "And as a QOI issue, if someone is using a
>> blockaddress without an indirectbr, and that reference somehow ends up in
>> another function or global, we probably don't want to inline this function.”
>> With your change here, that comment is no longer relevant.
>> 
>> 
>>     // Analyze the cost of this block. If we blow through the threshold,
>> this
>>     // returns false, and we can bail on out.
>> 
>> @@ -1279,7 +1289,16 @@ bool InlineCostAnalysis::isInlineViable(Function &F)
>> {
>>                                    Attribute::ReturnsTwice);
>>   for (Function::iterator BI = F.begin(), BE = F.end(); BI != BE; ++BI) {
>>     // Disallow inlining of functions which contain an indirect branch.
>> -    if (isa<IndirectBrInst>(BI->getTerminator()))
>> +    // Also disallow inlining of functions when the address of a label is
>> +    // taken. Usually when a block address is taken there is also
>> +    // an indirect branch which effectively suppresses inlining.
>> +    // But when there is no indirect branch the user wrote a function
>> +    // that takes the label address but does not jump to it within
>> +    // the function. This does not make much sense and could point
>> +    // to code that is not legal (eg. resulting in jump across function
>> +    // boundaries). The check here at least prevents inlining of such code.
>> +    // See also the comments to visitIndirectBrInst() and in analyzeCall().
>> +    if (isa<IndirectBrInst>(BI->getTerminator()) || BI->hasAddressTaken())
>>       return false;
>> 
>>     for (BasicBlock::iterator II = BI->begin(), IE = BI->end(); II != IE;
>> 
>> 
>> This comment seems a little verbose to me. I would at least remove the
>> cross-references to comments in other functions.
>> 
>> 
>> 





More information about the llvm-commits mailing list