[LLVMbugs] [Bug 14116] New: Inliner incorrectly combines cleanup and catch landing pads

bugzilla-daemon at llvm.org bugzilla-daemon at llvm.org
Thu Oct 18 05:05:58 PDT 2012


http://llvm.org/bugs/show_bug.cgi?id=14116

             Bug #: 14116
           Summary: Inliner incorrectly combines cleanup and catch landing
                    pads
           Product: new-bugs
           Version: trunk
          Platform: PC
        OS/Version: All
            Status: NEW
          Severity: enhancement
          Priority: P
         Component: new bugs
        AssignedTo: unassignedbugs at nondot.org
        ReportedBy: csdavec at swan.ac.uk
                CC: llvmbugs at cs.uiuc.edu
    Classification: Unclassified


The LLVM inliner combines catch and cleanups into a single landing pad and then
merges their tails.  This is problematic, because some personality functions
(e.g. GNU Objective-C) return different values in exception register 0
depending on whether the landing pad is a catch or a cleanup.  Consider the
following example:

define i32 @x() uwtable {
entry:
  %retval = alloca i32, align 4
  %x = alloca i32, align 4
  %exn.slot = alloca i8*
  %ehselector.slot = alloca i32
  invoke void @objc_exception_throw(i8* null)
          to label %invoke.cont unwind label %lpad

invoke.cont:                                      ; preds = %entry
  unreachable

lpad:                                             ; preds = %entry
  %0 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)*
@__gnu_objc_personality_v0 to i8*)
          cleanup
  %1 = extractvalue { i8*, i32 } %0, 0
  store i8* %1, i8** %exn.slot
  %2 = extractvalue { i8*, i32 } %0, 1
  store i32 %2, i32* %ehselector.slot
  %3 = bitcast i32* %x to i8*
  invoke void @foo(i8* %3)
          to label %invoke.cont1 unwind label %terminate.lpad

invoke.cont1:                                     ; preds = %lpad
  br label %eh.resume

return:                                           ; No predecessors!
  %4 = load i32* %retval
  ret i32 %4

eh.resume:                                        ; preds = %invoke.cont1
  %exn = load i8** %exn.slot
  %sel = load i32* %ehselector.slot
  %lpad.val = insertvalue { i8*, i32 } undef, i8* %exn, 0
  %lpad.val2 = insertvalue { i8*, i32 } %lpad.val, i32 %sel, 1
  resume { i8*, i32 } %lpad.val2

terminate.lpad:                                   ; preds = %lpad
  %5 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)*
@__gnu_objc_personality_v0 to i8*)
          catch i8* null
  call void @abort() noreturn nounwind
  unreachable
}

declare void @foo(i8*)

declare void @objc_exception_throw(i8*)

declare i32 @__gnu_objc_personality_v0(...)

declare void @abort()

define i32 @y() uwtable {
entry:
  %finally.exn = alloca i8*
  %finally.for-eh = alloca i1
  %exn.slot = alloca i8*
  %ehselector.slot = alloca i32
  %cleanup.dest.slot = alloca i32
  store i1 false, i1* %finally.for-eh
  %call = invoke i32 @x()
          to label %invoke.cont unwind label %lpad

invoke.cont:                                      ; preds = %entry
  store i32 0, i32* %cleanup.dest.slot
  br label %cleanup

cleanup:                                          ; preds = %invoke.cont,
%finally.catchall
  %cleanup.dest.saved = load i32* %cleanup.dest.slot
  %0 = load i32* @z, align 4
  %inc = add nsw i32 %0, 1
  store i32 %inc, i32* @z, align 4
  %finally.shouldthrow = load i1* %finally.for-eh
  br i1 %finally.shouldthrow, label %finally.rethrow, label %finally.cont

finally.rethrow:                                  ; preds = %cleanup
  %1 = load i8** %finally.exn
  call void @objc_exception_throw(i8* %1)
  unreachable

finally.cont:                                     ; preds = %cleanup
  store i32 %cleanup.dest.saved, i32* %cleanup.dest.slot
  %cleanup.dest = load i32* %cleanup.dest.slot
  switch i32 %cleanup.dest, label %unreachable [
    i32 0, label %cleanup.cont
    i32 2, label %unreachable
  ]

cleanup.cont:                                     ; preds = %finally.cont
  ret i32 1

lpad:                                             ; preds = %entry
  %2 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)*
@__gnu_objc_personality_v0 to i8*)
          catch i8* null
  %3 = extractvalue { i8*, i32 } %2, 0
  store i8* %3, i8** %exn.slot
  %4 = extractvalue { i8*, i32 } %2, 1
  store i32 %4, i32* %ehselector.slot
  br label %finally.catchall

finally.catchall:                                 ; preds = %lpad
  %exn = load i8** %exn.slot
  store i8* %exn, i8** %finally.exn
  store i1 true, i1* %finally.for-eh
  store i32 2, i32* %cleanup.dest.slot
  br label %cleanup

unreachable:                                      ; preds = %finally.cont,
%finally.cont
  unreachable
}

The inner function, x(), has a cleanup.  The outer has a catch.  The cleanup
rethrows the exception with a resume instruction.  The outer function, y(), has
a catchall.  This rethrows the exception with objc_exception_throw().  

When the inliner has run, the following version of y() is produced:


define i32 @y() uwtable optsize {
entry:
  %x.i = alloca i32, align 4
  %0 = bitcast i32* %x.i to i8*
  call void @llvm.lifetime.start(i64 -1, i8* %0)
  invoke void @objc_exception_throw(i8* null)
          to label %invoke.cont.i unwind label %lpad.i

invoke.cont.i:                                    ; preds = %entry
  unreachable

lpad.i:                                           ; preds = %entry
  %1 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)*
@__gnu_objc_personality_v0 to i8*)
          cleanup
          catch i8* null
  invoke void @foo(i8* %0) optsize
          to label %finally.rethrow unwind label %terminate.lpad.i

terminate.lpad.i:                                 ; preds = %lpad.i
  %2 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)*
@__gnu_objc_personality_v0 to i8*)
          catch i8* null
  call void @abort() noreturn nounwind
  unreachable

finally.rethrow:                                  ; preds = %lpad.i
  %3 = extractvalue { i8*, i32 } %1, 0
  %4 = load i32* @z, align 4, !tbaa !0
  %inc = add nsw i32 %4, 1
  store i32 %inc, i32* @z, align 4, !tbaa !0
  call void @objc_exception_throw(i8* %3)
  unreachable
}

x() has been inlined into the first basic block, so the invoke of the function
throwing the exception (objc_exception_throw()) becomes the terminator for that
block.  So far, so good.  The landing pad for this now has a cleanup and then a
catchall, which is a bit redundant because either of these will always be
matched.  The call to foo() in the landing pad is the cleanup code from x(),
which is correct.  This then branches to finally.rethrow, however, which then
calls objc_exception_throw().  This call is only valid if we landed with the
catchall, but because a cleanup will always be matched first we actually landed
with the cleanup.  

The resume instruction has been completely removed.  The correct behaviour in
this case would be simply to remove the cleanup - there is a catchall and so
the cleanup is completely redundant.  In the more general case, if we have
multiple rethrow blocks with different mechanisms then we should be wrapping
them in a branch.  The finally.rethrow block in this case should look like
something like this:


finally.rethrow:                                  ; preds = %lpad.i
  %3 = extractvalue { i8*, i32 } %1, 0
  %4 = load i32* @z, align 4, !tbaa !0
  %inc = add nsw i32 %4, 1
  store i32 %inc, i32* @z, align 4, !tbaa !0
  %5 = extractvalue { i8*, i32 } %1, 1
  switch i32 %5, label %unreachable [
    i32 0, label %cleanup.cont
    i32 1, label %rethrow
  ]

rethrow:
  call void @objc_exception_throw(i8* %3)
  unreachable

cleanup.cont:
  resume { i8*, i32 } %1

-- 
Configure bugmail: http://llvm.org/bugs/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
You are on the CC list for the bug.



More information about the llvm-bugs mailing list