Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SjLjEHPrepare] Fix callsite problem #67264

Closed
wants to merge 2 commits into from

Conversation

kaz7
Copy link
Contributor

@kaz7 kaz7 commented Sep 24, 2023

Fix callsite problem caused by b61fd7f. Original implementation inserts no-action (callsite == -1) marker to only not-throw call and resume instructions. At b61fd7f, it changes to insert if I.mayThrow() returns true, but this causes wrong insertions of no-action marker just before invoke instructions like "invoke cxa_bad_type." As a result, SjLj routine could not handle exceptions if the exceptions and try/catch are in the identical function like test_aux_runtime.pass.cpp. Also add a regression test for VE.

Fix callsite problem caused by b61fd7f.  Original implementation inserts
no-action (callsite == -1) marker to only not-throw call and resume
instructions.  At b61fd7f, it changes to insert if I.mayThrow() returns
true, but this causes wrong insertions of no-action marker just before
invoke instructions like "invoke cxa_bad_type."  As a result, SjLj routine
could not handle exceptions if the exceptions and try/catch are in the
identical function like test_aux_runtime.pass.cpp.  Also add a regression
test for VE.
@llvmbot
Copy link
Collaborator

llvmbot commented Sep 24, 2023

@llvm/pr-subscribers-libunwind

Changes

Fix callsite problem caused by b61fd7f. Original implementation inserts no-action (callsite == -1) marker to only not-throw call and resume instructions. At b61fd7f, it changes to insert if I.mayThrow() returns true, but this causes wrong insertions of no-action marker just before invoke instructions like "invoke cxa_bad_type." As a result, SjLj routine could not handle exceptions if the exceptions and try/catch are in the identical function like test_aux_runtime.pass.cpp. Also add a regression test for VE.


Full diff: https://github.com/llvm/llvm-project/pull/67264.diff

2 Files Affected:

  • (modified) llvm/lib/CodeGen/SjLjEHPrepare.cpp (+13-3)
  • (added) llvm/test/CodeGen/VE/Scalar/sjlj-bad-cast.ll (+102)
diff --git a/llvm/lib/CodeGen/SjLjEHPrepare.cpp b/llvm/lib/CodeGen/SjLjEHPrepare.cpp
index 7994821ae7c0a14..d244fc3e66d472b 100644
--- a/llvm/lib/CodeGen/SjLjEHPrepare.cpp
+++ b/llvm/lib/CodeGen/SjLjEHPrepare.cpp
@@ -442,9 +442,19 @@ bool SjLjEHPrepare::setupEntryBlockAndCallSites(Function &F) {
   for (BasicBlock &BB : F) {
     if (&BB == &F.front())
       continue;
-    for (Instruction &I : BB)
-      if (I.mayThrow())
-        insertCallSiteStore(&I, -1);
+    for (Instruction &I : BB) {
+      // Partially revert b61fd7f modifications.  Stop using "I.mayThrow()"
+      // here.  Using it inserts no-action marker to just before invoke
+      // instructions like "invoke void @__cxa_bad_cast()".  That means
+      // tests having exception and try/catch in the identical function
+      // are broken.  For example, tests like test_aux_runtime.pass.cpp.
+      if (CallInst *CI = dyn_cast<CallInst>(&I)) {
+        if (!CI->doesNotThrow())
+          insertCallSiteStore(CI, -1);
+      } else if (ResumeInst *RI = dyn_cast<ResumeInst>(&I)) {
+        insertCallSiteStore(RI, -1);
+      }
+    }
   }
 
   // Register the function context and make sure it's known to not throw
diff --git a/llvm/test/CodeGen/VE/Scalar/sjlj-bad-cast.ll b/llvm/test/CodeGen/VE/Scalar/sjlj-bad-cast.ll
new file mode 100644
index 000000000000000..05001df63c0819c
--- /dev/null
+++ b/llvm/test/CodeGen/VE/Scalar/sjlj-bad-cast.ll
@@ -0,0 +1,102 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 2
+; RUN: llc < %s -mtriple=ve --exception-model=sjlj | FileCheck %s
+
+;;; Test for SjLjEHPrepare.cpp.
+;;; This checks whether only correct resumeLocation (1) is inserted just
+;;; before invoking __cxa_bad_cast.
+
+@_ZTISt8bad_cast = external dso_local constant ptr
+@_ZTVN10__cxxabiv117__class_type_infoE = external dso_local global [0 x ptr]
+@_ZTSZ15bad_typeid_testvE1A = internal constant [23 x i8] c"Z15bad_typeid_testvE1A\00", align 1
+@_ZTSZ13bad_cast_testvE1A = internal constant [21 x i8] c"Z13bad_cast_testvE1A\00", align 1
+@_ZTIZ13bad_cast_testvE1A = internal constant { ptr, ptr } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 2), ptr @_ZTSZ13bad_cast_testvE1A }, align 8
+@_ZTSZ13bad_cast_testvE1B = internal constant [21 x i8] c"Z13bad_cast_testvE1B\00", align 1
+@_ZTIZ13bad_cast_testvE1B = internal constant { ptr, ptr } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 2), ptr @_ZTSZ13bad_cast_testvE1B }, align 8
+@_ZTVN10__cxxabiv121__vmi_class_type_infoE = external dso_local global [0 x ptr]
+@_ZTSZ13bad_cast_testvE1D = internal constant [21 x i8] c"Z13bad_cast_testvE1D\00", align 1
+@_ZTIZ13bad_cast_testvE1D = internal constant { ptr, ptr, i32, i32, ptr, i64, ptr, i64 } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv121__vmi_class_type_infoE, i64 2), ptr @_ZTSZ13bad_cast_testvE1D, i32 0, i32 2, ptr @_ZTIZ13bad_cast_testvE1A, i64 -6141, ptr @_ZTIZ13bad_cast_testvE1B, i64 0 }, align 8
+
+; Function Attrs: mustprogress noinline optnone
+define dso_local noundef zeroext i1 @_Z13bad_cast_testv() #0 personality ptr @__gxx_personality_sj0 {
+; CHECK-LABEL: _Z13bad_cast_testv:
+; CHECK:       # %dynamic_cast.bad_cast
+; CHECK-NEXT:    or %s0, 1, (0)1
+; CHECK-NEXT:    st %s0, -96(, %s9)
+; CHECK-NEXT:  .Ltmp{{[0-9]*}}:
+; CHECK-NEXT:    lea %s0, __cxa_bad_cast@lo
+; CHECK-NEXT:    and %s0, %s0, (32)0
+; CHECK-NEXT:    lea.sl %s12, __cxa_bad_cast@hi(, %s0)
+entry:
+  %retval = alloca i1, align 1
+;  %d = alloca %class.D, align 8
+  %bp = alloca ptr, align 8
+  %dr = alloca ptr, align 8
+  %exn.slot = alloca ptr, align 8
+  %ehselector.slot = alloca i32, align 4
+  %0 = alloca ptr, align 8
+;  call void @_ZZ13bad_cast_testvEN1DC1Ev(ptr noundef nonnull align 8 dereferenceable(8) %d) #7
+;  store ptr %d, ptr %bp, align 8
+  %1 = load ptr, ptr %bp, align 8
+  %2 = call ptr @__dynamic_cast(ptr %1, ptr @_ZTIZ13bad_cast_testvE1B, ptr @_ZTIZ13bad_cast_testvE1D, i64 -2) #7
+  %3 = icmp eq ptr %2, null
+  br i1 %3, label %dynamic_cast.bad_cast, label %dynamic_cast.end
+
+dynamic_cast.bad_cast:                            ; preds = %entry
+  invoke void @__cxa_bad_cast() #6
+          to label %invoke.cont unwind label %lpad
+
+invoke.cont:                                      ; preds = %dynamic_cast.bad_cast
+  unreachable
+
+dynamic_cast.end:                                 ; preds = %entry
+  store ptr %2, ptr %dr, align 8
+  %4 = load ptr, ptr %dr, align 8
+  br label %try.cont
+
+lpad:                                             ; preds = %dynamic_cast.bad_cast
+  %5 = landingpad { ptr, i32 }
+          catch ptr @_ZTISt8bad_cast
+  %6 = extractvalue { ptr, i32 } %5, 0
+  store ptr %6, ptr %exn.slot, align 8
+  %7 = extractvalue { ptr, i32 } %5, 1
+  store i32 %7, ptr %ehselector.slot, align 4
+  br label %catch.dispatch
+
+catch.dispatch:                                   ; preds = %lpad
+  %sel = load i32, ptr %ehselector.slot, align 4
+  %8 = call i32 @llvm.eh.typeid.for(ptr @_ZTISt8bad_cast) #7
+  %matches = icmp eq i32 %sel, %8
+  br i1 %matches, label %catch, label %eh.resume
+
+catch:                                            ; preds = %catch.dispatch
+  %exn = load ptr, ptr %exn.slot, align 8
+  %9 = call ptr @__cxa_begin_catch(ptr %exn) #7
+  store ptr %9, ptr %0, align 8
+  store i1 true, ptr %retval, align 1
+  call void @__cxa_end_catch()
+  br label %return
+
+try.cont:                                         ; preds = %dynamic_cast.end
+  store i1 false, ptr %retval, align 1
+  br label %return
+
+return:                                           ; preds = %try.cont, %catch
+  %10 = load i1, ptr %retval, align 1
+  ret i1 %10
+
+eh.resume:                                        ; preds = %catch.dispatch
+  %exn1 = load ptr, ptr %exn.slot, align 8
+  %sel2 = load i32, ptr %ehselector.slot, align 4
+  %lpad.val = insertvalue { ptr, i32 } poison, ptr %exn1, 0
+  %lpad.val3 = insertvalue { ptr, i32 } %lpad.val, i32 %sel2, 1
+  resume { ptr, i32 } %lpad.val3
+}
+
+; Function Attrs: nounwind memory(read)
+declare dso_local ptr @__dynamic_cast(ptr, ptr, ptr, i64) #4
+declare dso_local void @__cxa_bad_cast()
+; Function Attrs: nounwind memory(none)
+declare i32 @llvm.eh.typeid.for(ptr) #2
+declare dso_local ptr @__cxa_begin_catch(ptr)
+declare dso_local void @__cxa_end_catch()
+declare dso_local i32 @__gxx_personality_sj0(...)

if (I.mayThrow())
insertCallSiteStore(&I, -1);
for (Instruction &I : BB) {
// Partially revert b61fd7f modifications. Stop using "I.mayThrow()"
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't refer to past revert, explain what this is

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok. It's little tough for me. I still don't understand the detail of mayThrow. I'll try to explain the problem when I have time. Thank you.

The situation is something like this. Existing code inserts no-action mark (callsite == -1) just before "invoke __cxa_bad_cast" instructions because mayThrow function return true for such invoking instructions. As a result, following instructions sequence is generated.

                                          # call _Unwind_SjLj_Register
        lea %s0, _Unwind_SjLj_Register@lo
        and %s0, %s0, (32)0
        lea.sl %s12, _Unwind_SjLj_Register@hi(, %s0)
        lea %s0, -104(, %s9)
        bsic %s10, (, %s12)
...
.LBB0_1:                                # %dynamic_cast.bad_cast
        or %s0, 1, (0)1              # store 1 by "insertCallSiteStore(Invokes[I], I + 1);" in previous loop.
        st %s0, -96(, %s9)
        or %s0, -1, (0)1             # store -1 by "insertCallSiteStore(&I, -1);" by loop I try to modify.
        st %s0, -96(, %s9)
.Ltmp0:
        lea %s0, __cxa_bad_cast@lo     # callsite is -1, so nobody catch this bad cast
        and %s0, %s0, (32)0
        lea.sl %s12, __cxa_bad_cast@hi(, %s0)
        bsic %s10, (, %s12)

I just notice that this problem was starting after b61fd7f. It describes "No functional change is intended.", so I just thought reverting it partially is good enough. But not. I'll try to explain the problem later like I said above.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've updated in-source comments.

// instructions like "invoke void @__cxa_bad_cast()". That means
// tests having exception and try/catch in the identical function
// are broken. For example, tests like test_aux_runtime.pass.cpp.
if (CallInst *CI = dyn_cast<CallInst>(&I)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be CallBase

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the correction.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, it's not. We want to check only CallInst. We don't want to check InvokeInst here.

@kaz7 kaz7 requested a review from arsenm September 27, 2023 01:47
@kaz7
Copy link
Contributor Author

kaz7 commented Oct 4, 2023

ping

if (CallInst *CI = dyn_cast<CallInst>(&I)) {
if (!CI->doesNotThrow())
insertCallSiteStore(CI, -1);
} else if (ResumeInst *RI = dyn_cast<ResumeInst>(&I)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This else if chain will miss the more exotic call types (i.e. callbr).


catch: ; preds = %catch.dispatch
%exn = load ptr, ptr %exn.slot, align 8
%9 = call ptr @__cxa_begin_catch(ptr %exn) #7
Copy link
Contributor

Choose a reason for hiding this comment

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

Use named values in tests

@_ZTIZ13bad_cast_testvE1D = internal constant { ptr, ptr, i32, i32, ptr, i64, ptr, i64 } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv121__vmi_class_type_infoE, i64 2), ptr @_ZTSZ13bad_cast_testvE1D, i32 0, i32 2, ptr @_ZTIZ13bad_cast_testvE1A, i64 -6141, ptr @_ZTIZ13bad_cast_testvE1B, i64 0 }, align 8

; Function Attrs: mustprogress noinline optnone
define dso_local noundef zeroext i1 @_Z13bad_cast_testv() #0 personality ptr @__gxx_personality_sj0 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this be reduced? There are a lot of globals here. You also can probably drop most of the attributes

@kaz7 kaz7 closed this by deleting the head repository May 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants