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

Use C++ exceptions unconditionally for Objective-C[++] on MinGW #267

Merged
merged 1 commit into from
Jan 10, 2024

Conversation

qmfrederik
Copy link
Collaborator

@qmfrederik qmfrederik commented Jan 5, 2024

Alternative implementation of #260. Requires changes to clang - see qmfrederik/llvm-project@88ce1ba

@qmfrederik
Copy link
Collaborator Author

Current status:

  • Some of the tests fail to build because objc_setUncaughtExceptionHandler is not implemented
  • The try/catch clause in objc_msgSend.m seems to be working, although the assert assert((TestCls == e) && "Exceptions propagate out of +initialize"); is failing; if I disable that assert the test passes.

@davidchisnall
Copy link
Member

I presume that we can use the implementation from here on MinGW for the first point?

OBJC_PUBLIC extern "C" objc_uncaught_exception_handler objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler handler)

@qmfrederik
Copy link
Collaborator Author

qmfrederik commented Jan 5, 2024

OK, I've added a dummy implementation of objc_setUncaughtExceptionHandler. I assume we'll need a different implementation for _objc_unhandled_exception_filter?

A lot of tests are currently failing like this:

terminate called after throwing an instance of '@id'

For example:

# ./Test/ExceptionTest.exe
terminate called after throwing an instance of '@id'

Any thoughts on what could cause that?

@davidchisnall
Copy link
Member

Can you trace into the C++ personality function? If clang is correctly generating the LSDA, then this should work. Actually, try sticking a breakpoint on:

https://github.com/gnustep/libobjc2/blob/3e115c522a8a544447d5a41cca4df14e1b398e87/objcxx_eh.cc#L319C1-L319C1

And:

https://github.com/gnustep/libobjc2/blob/3e115c522a8a544447d5a41cca4df14e1b398e87/objcxx_eh.cc#L353C22-L353C22

These should be being called by the C++ personality function to check for matches.

The error message you're seeing is the one that happens if the C++ runtime throws an exception and nothing catches it (unwind reaches the end of the stack).

@qmfrederik
Copy link
Collaborator Author

From #260 (comment)

The try/catch clause in objc_msgSend.m seems to be working, although the assert assert((TestCls == e) && "Exceptions propagate out of +initialize"); is failing; if I disable that assert the test passes.

Thanks! Looks like we're getting close. Propagating out of +initialize requires that they're thrown through C frames with attribute((cleanup)). Can you check whether that works with MinGW with just C and C++ (call C++ -> C -> C++, use attribute((cleanup)) in C, check you can throw an exception out)? It's possible that we need to add an extra compiler flag to the compilation unit that handles initialize.

It's a bit surprising to me that the exception seems to be thrown and code continues executing.

This doesn't reproduce after rebuilding, but leaving the comment here for future reference.

@qmfrederik
Copy link
Collaborator Author

I added additional logging and this is the current output (contradicting my previous comment):


vagrant@DESKTOP-RNTVKUC MINGW64 ~/git/libobjc2/build
# ./Test/objc_msgSend.exe 
gnustep::libobjc::__objc_id_type_info::__do_catch: Entering
Assertion failed: (TestCls == e) && "Exceptions propagate out of +initialize", file C:/tools/msys64/home/vagrant/git/libobjc2/Test/objc_msgSend.m, line 186

vagrant@DESKTOP-RNTVKUC MINGW64 ~/git/libobjc2/build
# ./Test/ExceptionTest.exe
gnustep::libobjc::__objc_id_type_info::__do_catch: Entering
Assertion failed: object_getClass(x) == [Test class], file C:/tools/msys64/home/vagrant/git/libobjc2/Test/ExceptionTest.m, line 40

@davidchisnall
Copy link
Member

Can you step through the do-catch methods and see where the match is failing?

@qmfrederik
Copy link
Collaborator Author

qmfrederik commented Jan 5, 2024

OK - some progress. The actual exception object wasn't being copied in objc_exception_throw - that should now be implemented, see https://github.com/gnustep/libobjc2/pull/267/files#diff-5803607d1fc7fe6f76ab1f6e98c4c6e5b373acb8bb6c103cd3fccaef42abd215R527-R528

With that, most tests seem to pass; only ExceptionTest and UnexpectedException fail:

94% tests passed, 10 tests failed out of 178

Total Test time (real) =  26.78 sec

The following tests FAILED:
          3 - alias_legacy (Failed)
          4 - alias_legacy_optimised (Failed)
         35 - ExceptionTest (Failed)
         36 - ExceptionTest_optimised (Failed)
         37 - ExceptionTest_legacy (Failed)
         38 - ExceptionTest_legacy_optimised (Failed)
        155 - UnexpectedException (Failed)
        156 - UnexpectedException_optimised (Failed)
        157 - UnexpectedException_legacy (Failed)
        158 - UnexpectedException_legacy_optimised (Failed)

@qmfrederik
Copy link
Collaborator Author

w.r.t. ExceptionTest failing, it looks like this may be related to rethrowing an exception

Here's the output (with a bunch of additional logging statements):

throw: Throwing exception
objc_exception_throw: Entering
objc_exception_throw: Throwing exception 0x5465eea8 of type Test
gnustep::libobjc::__objc_id_type_info::__do_catch: Entering
gnustep::libobjc::__objc_id_type_info::__do_catch: Type info @id
gnustep::libobjc::__objc_id_type_info::__do_catch: got id type info for 0x5465eea8, returning true
eh_cleanup: Releasing exception 0x5465eea8
rethrow_id: Rethrowing exception
objc_exception_throw: Entering
objc_exception_throw: Throwing exception 0x5465eea8 of type Test
gnustep::libobjc::__objc_class_type_info::__do_catch: Entering
gnustep::libobjc::__objc_class_type_info::__do_catch: Type info @id
gnustep::libobjc::__objc_class_type_info::__do_catch: object is an id type or class type and we're in Apple compat mode
gnustep::libobjc::__objc_class_type_info::__do_catch: got type Test
gnustep::libobjc::__objc_class_type_info::__do_catch: found is YES
gnustep::libobjc::__objc_class_type_info::__do_catch: found matching type
gnustep::libobjc::__objc_class_type_info::__do_catch: returning 1; *obj is 0x5465eea8
terminate called after throwing an instance of '@id'
__do_upcast: Entering
eh_cleanup: Releasing exception 0x5465eea8

@qmfrederik
Copy link
Collaborator Author

For objc_setUncaughtExceptionHandler: it looks like SetUnhandledExceptionFilter does not work on mingw64 but AddVectoredExceptionHandler does. There's not a lot of information on the subject; e.g. ocaml/ocaml#938 or https://sourceforge.net/p/mingw-w64/support-requests/29/ (from 2010!)

@davidchisnall
Copy link
Member

Okay, it looks as if the first throw and catch is working. That's a good start: throwing an ObjC object and catching id is the simplest case. The second throw looks as if it's going through the same code paths as first, which I don't think is correct. Rethrowing should be done via __cxa_rethrow. The simplest thing would probably be to modify this:

https://github.com/qmfrederik/llvm-project/blob/88ce1ba8d714c5f56e3697151119a85a4fbafcf0/clang/lib/CodeGen/CGObjCGNU.cpp#L822

To remove the not-MinGW condition and then implement objc_exception_rethrow to just call __cxa_throw.

Can you share the LLVM IR for compiling this with the modified clang?

@qmfrederik
Copy link
Collaborator Author

Here's the clang change: llvm/llvm-project@d68db09
And the libobjc2 change: 4970a82

That gives this output:

throw: Throwing exception
objc_exception_throw: Entering
objc_exception_throw: Throwing exception 0x9db0eea8 of type Test
objc_exception_rethrow: Entering
terminate called without an active exception

Looks like the application now halts earlier. I'll get you the intermediate representation in a follow-up.

@qmfrederik
Copy link
Collaborator Author

Here's the intermediate representation (assuming I did this right):

; ModuleID = '../Test/ExceptionTest.m'
source_filename = "../Test/ExceptionTest.m"
target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-w64-windows-gnu"

%.objc_section_sentinel = type <{}>

$.objcv2_load_function = comdat any

$.objc_sel_name_new = comdat any

$".objc_sel_types_\0116\010:8" = comdat any

$".objc_selector_new_\0116\010:8" = comdat any

$.objc_sel_name_class = comdat any

$".objc_sel_types_#16\010:8" = comdat any

$".objc_selector_class_#16\010:8" = comdat any

$__objc_eh_typename_Test = comdat any

$__objc_eh_typename_NSString = comdat any

$.objc_sel_name_dealloc = comdat any

$".objc_sel_types_v16\010:8" = comdat any

$".objc_selector_dealloc_v16\010:8" = comdat any

$"__start_.objcrt$SEL" = comdat any

$"__stop.objcrt$SEL" = comdat any

$"__start_.objcrt$CLS" = comdat any

$"__stop.objcrt$CLS" = comdat any

$"__start_.objcrt$CLR" = comdat any

$"__stop.objcrt$CLR" = comdat any

$"__start_.objcrt$CAT" = comdat any

$"__stop.objcrt$CAT" = comdat any

$"__start_.objcrt$PCL" = comdat any

$"__stop.objcrt$PCL" = comdat any

$"__start_.objcrt$PCR" = comdat any

$"__stop.objcrt$PCR" = comdat any

$"__start_.objcrt$CAL" = comdat any

$"__stop.objcrt$CAL" = comdat any

$"__start_.objcrt$STR" = comdat any

$"__stop.objcrt$STR" = comdat any

$.objc_init = comdat any

$.objc_ctor = comdat any

@finallyEntered = dso_local global i32 0, align 4
@cleanupRun = dso_local global i32 0, align 4
@idRethrown = dso_local global i32 0, align 4
@catchallRethrown = dso_local global i32 0, align 4
@testCaught = dso_local global i32 0, align 4
@wrongMatch = dso_local global i32 0, align 4
@.str = private unnamed_addr constant [17 x i8] c"cleanupRun == NO\00", align 1
@.str.1 = private unnamed_addr constant [24 x i8] c"../Test/ExceptionTest.m\00", align 1
@.str.2 = private unnamed_addr constant [27 x i8] c"throw: Throwing exception\0A\00", align 1
@"$_OBJC_REF_CLASS_Test" = external global ptr
@.objc_sel_name_new = linkonce_odr hidden constant [4 x i8] c"new\00", comdat
@".objc_sel_types_\0116\010:8" = linkonce_odr hidden constant [8 x i8] c"@16@0:8\00", comdat
@".objc_selector_new_\0116\010:8" = linkonce_odr hidden global { ptr, ptr } { ptr @.objc_sel_name_new, ptr @".objc_sel_types_\0116\010:8" }, section ".objcrt$SEL$m", comdat, align 8
@__objc_id_type_info = external global ptr
@.objc_sel_name_class = linkonce_odr hidden constant [6 x i8] c"class\00", comdat
@".objc_sel_types_#16\010:8" = linkonce_odr hidden constant [8 x i8] c"#16@0:8\00", comdat
@".objc_selector_class_#16\010:8" = linkonce_odr hidden global { ptr, ptr } { ptr @.objc_sel_name_class, ptr @".objc_sel_types_#16\010:8" }, section ".objcrt$SEL$m", comdat, align 8
@.str.3 = private unnamed_addr constant [35 x i8] c"object_getClass(x) == [Test class]\00", align 1
@.str.4 = private unnamed_addr constant [34 x i8] c"rethrow_id: Rethrowing exception\0A\00", align 1
@_ZTVN7gnustep7libobjc22__objc_class_type_infoE = external constant ptr
@__objc_eh_typename_Test = linkonce_odr constant [5 x i8] c"Test\00", comdat
@__objc_eh_typeinfo_Test = linkonce_odr global { ptr, ptr } { ptr getelementptr (ptr, ptr @_ZTVN7gnustep7libobjc22__objc_class_type_infoE, i32 2), ptr @__objc_eh_typename_Test }, align 8
@.str.5 = private unnamed_addr constant [36 x i8] c"rethrow_test: Rethrowing exception\0A\00", align 1
@.str.6 = private unnamed_addr constant [32 x i8] c"rethrow_test: in @catch (id x)\0A\00", align 1
@.str.7 = private unnamed_addr constant [30 x i8] c"0 && \22should not be reached!\22\00", align 1
@.str.8 = private unnamed_addr constant [31 x i8] c"rethrow_test: in @catch (...)\0A\00", align 1
@.str.9 = private unnamed_addr constant [11 x i8] c"testCaught\00", align 1
@.str.10 = private unnamed_addr constant [40 x i8] c"rethrow_catchall: Rethrowing exception\0A\00", align 1
@__objc_eh_typename_NSString = linkonce_odr constant [9 x i8] c"NSString\00", comdat
@__objc_eh_typeinfo_NSString = linkonce_odr global { ptr, ptr } { ptr getelementptr (ptr, ptr @_ZTVN7gnustep7libobjc22__objc_class_type_infoE, i32 2), ptr @__objc_eh_typename_NSString }, align 8
@.str.11 = private unnamed_addr constant [22 x i8] c"finallyEntered == YES\00", align 1
@.str.12 = private unnamed_addr constant [18 x i8] c"cleanupRun == YES\00", align 1
@.str.13 = private unnamed_addr constant [18 x i8] c"idRethrown == YES\00", align 1
@.str.14 = private unnamed_addr constant [24 x i8] c"catchallRethrown == YES\00", align 1
@.str.15 = private unnamed_addr constant [17 x i8] c"wrongMatch == NO\00", align 1
@.objc_sel_name_dealloc = linkonce_odr hidden constant [8 x i8] c"dealloc\00", comdat
@".objc_sel_types_v16\010:8" = linkonce_odr hidden constant [8 x i8] c"v16@0:8\00", comdat
@".objc_selector_dealloc_v16\010:8" = linkonce_odr hidden global { ptr, ptr } { ptr @.objc_sel_name_dealloc, ptr @".objc_sel_types_v16\010:8" }, section ".objcrt$SEL$m", comdat, align 8
@"__start_.objcrt$SEL" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$SEL$a", comdat, align 8
@"__stop.objcrt$SEL" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$SEL$z", comdat, align 8
@"__start_.objcrt$CLS" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$CLS$a", comdat, align 8
@"__stop.objcrt$CLS" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$CLS$z", comdat, align 8
@"__start_.objcrt$CLR" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$CLR$a", comdat, align 8
@"__stop.objcrt$CLR" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$CLR$z", comdat, align 8
@"__start_.objcrt$CAT" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$CAT$a", comdat, align 8
@"__stop.objcrt$CAT" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$CAT$z", comdat, align 8
@"__start_.objcrt$PCL" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$PCL$a", comdat, align 8
@"__stop.objcrt$PCL" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$PCL$z", comdat, align 8
@"__start_.objcrt$PCR" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$PCR$a", comdat, align 8
@"__stop.objcrt$PCR" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$PCR$z", comdat, align 8
@"__start_.objcrt$CAL" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$CAL$a", comdat, align 8
@"__stop.objcrt$CAL" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$CAL$z", comdat, align 8
@"__start_.objcrt$STR" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$STR$a", comdat, align 8
@"__stop.objcrt$STR" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$STR$z", comdat, align 8
@.objc_init = linkonce_odr hidden global { i64, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr } { i64 0, ptr @"__start_.objcrt$SEL", ptr @"__stop.objcrt$SEL", ptr @"__start_.objcrt$CLS", ptr @"__stop.objcrt$CLS", ptr @"__start_.objcrt$CLR", ptr @"__stop.objcrt$CLR", ptr @"__start_.objcrt$CAT", ptr @"__stop.objcrt$CAT", ptr @"__start_.objcrt$PCL", ptr @"__stop.objcrt$PCL", ptr @"__start_.objcrt$PCR", ptr @"__stop.objcrt$PCR", ptr @"__start_.objcrt$CAL", ptr @"__stop.objcrt$CAL", ptr @"__start_.objcrt$STR", ptr @"__stop.objcrt$STR" }, comdat, align 8
@.objc_ctor = linkonce hidden global ptr @.objcv2_load_function, section ".CRT$XCLz", comdat
@llvm.used = appending global [1 x ptr] [ptr @.objc_ctor], section "llvm.metadata"
@llvm.compiler.used = appending global [1 x ptr] [ptr @.objcv2_load_function], section "llvm.metadata"

; Function Attrs: noinline optnone uwtable
define dso_local void @runCleanup(ptr noundef %0) #0 {
  %2 = alloca ptr, align 8
  store ptr %0, ptr %2, align 8
  %3 = load i32, ptr @cleanupRun, align 4
  %4 = icmp eq i32 %3, 0
  br i1 %4, label %7, label %5

5:                                                ; preds = %1
  call void @_assert(ptr noundef @.str, ptr noundef @.str.1, i32 noundef 18) #7
  unreachable

6:                                                ; No predecessors!
  br label %7

7:                                                ; preds = %6, %1
  %8 = phi i1 [ true, %1 ], [ false, %6 ]
  %9 = zext i1 %8 to i32
  store i32 1, ptr @cleanupRun, align 4
  ret void
}

; Function Attrs: noreturn
declare dllimport void @_assert(ptr noundef, ptr noundef, i32 noundef) #1

; Function Attrs: noinline optnone uwtable
define dso_local i32 @throw() #0 {
  %1 = call ptr @__acrt_iob_func(i32 noundef 2)
  %2 = call i32 (ptr, ptr, ...) @fprintf(ptr noundef %1, ptr noundef @.str.2)
  %3 = load ptr, ptr @"$_OBJC_REF_CLASS_Test", align 8
  %4 = call ptr @objc_msgSend(ptr noundef %3, ptr noundef @".objc_selector_new_\0116\010:8"), !GNUObjCMessageSend !5
  call void @objc_exception_throw(ptr %4) #7
  unreachable
}

; Function Attrs: noinline nounwind optnone uwtable
define internal i32 @fprintf(ptr noundef %0, ptr noundef nonnull %1, ...) #2 {
  %3 = alloca ptr, align 8
  %4 = alloca ptr, align 8
  %5 = alloca i32, align 4
  %6 = alloca ptr, align 8
  store ptr %0, ptr %3, align 8
  store ptr %1, ptr %4, align 8
  call void @llvm.va_start(ptr %6)
  %7 = load ptr, ptr %3, align 8
  %8 = load ptr, ptr %4, align 8
  %9 = load ptr, ptr %6, align 8
  %10 = call i32 @__mingw_vfprintf(ptr noundef %7, ptr noundef %8, ptr noundef %9) #8
  store i32 %10, ptr %5, align 4
  call void @llvm.va_end(ptr %6)
  %11 = load i32, ptr %5, align 4
  ret i32 %11
}

declare dllimport ptr @__acrt_iob_func(i32 noundef) #3

declare dso_local ptr @objc_msgSend(ptr, ...)

declare dso_local void @objc_exception_throw(ptr)

; Function Attrs: noinline optnone uwtable
define dso_local i32 @finally() #0 personality ptr @__gxx_personality_seh0 {
  %1 = alloca i32, align 4
  %2 = alloca i1, align 1
  %3 = alloca ptr, align 8
  %4 = alloca i32, align 4
  %5 = alloca i32, align 4
  %6 = load i32, ptr %1, align 4
  store i1 false, ptr %2, align 1
  %7 = invoke i32 @throw()
          to label %8 unwind label %17

8:                                                ; preds = %0
  store i32 0, ptr %5, align 4
  br label %9

9:                                                ; preds = %8, %21
  %10 = load i32, ptr %5, align 4
  store i32 1, ptr @finallyEntered, align 4
  %11 = load i1, ptr %2, align 1
  br i1 %11, label %12, label %14

12:                                               ; preds = %9
  invoke void @objc_exception_rethrow()
          to label %13 unwind label %22

13:                                               ; preds = %12
  unreachable

14:                                               ; preds = %9
  store i32 %10, ptr %5, align 4
  %15 = load i32, ptr %5, align 4
  switch i32 %15, label %34 [
    i32 0, label %16
    i32 2, label %34
  ]

16:                                               ; preds = %14
  store i32 1, ptr %5, align 4
  call void @runCleanup(ptr noundef %1)
  ret i32 0

17:                                               ; preds = %0
  %18 = landingpad { ptr, i32 }
          catch ptr null
  %19 = extractvalue { ptr, i32 } %18, 0
  store ptr %19, ptr %3, align 8
  %20 = extractvalue { ptr, i32 } %18, 1
  store i32 %20, ptr %4, align 4
  br label %21

21:                                               ; preds = %17
  store i1 true, ptr %2, align 1
  store i32 2, ptr %5, align 4
  br label %9

22:                                               ; preds = %12
  %23 = landingpad { ptr, i32 }
          cleanup
  %24 = extractvalue { ptr, i32 } %23, 0
  store ptr %24, ptr %3, align 8
  %25 = extractvalue { ptr, i32 } %23, 1
  store i32 %25, ptr %4, align 4
  invoke void @runCleanup(ptr noundef %1)
          to label %26 unwind label %32

26:                                               ; preds = %22
  br label %27

27:                                               ; preds = %26
  %28 = load ptr, ptr %3, align 8
  %29 = load i32, ptr %4, align 4
  %30 = insertvalue { ptr, i32 } poison, ptr %28, 0
  %31 = insertvalue { ptr, i32 } %30, i32 %29, 1
  resume { ptr, i32 } %31

32:                                               ; preds = %22
  %33 = landingpad { ptr, i32 }
          catch ptr null
  call void @abort() #9
  unreachable

34:                                               ; preds = %14, %14
  unreachable
}

declare dso_local void @objc_exception_rethrow()

declare dso_local i32 @__gxx_personality_seh0(...)

declare dso_local void @abort()

; Function Attrs: noinline optnone uwtable
define dso_local i32 @rethrow_id() #0 personality ptr @__gxx_personality_seh0 {
  %1 = alloca ptr, align 8
  %2 = alloca i32, align 4
  %3 = alloca ptr, align 8
  %4 = invoke i32 @finally()
          to label %5 unwind label %7

5:                                                ; preds = %0
  br label %6

6:                                                ; preds = %5
  ret i32 0

7:                                                ; preds = %0
  %8 = landingpad { ptr, i32 }
          catch ptr @__objc_id_type_info
  %9 = extractvalue { ptr, i32 } %8, 0
  store ptr %9, ptr %1, align 8
  %10 = extractvalue { ptr, i32 } %8, 1
  store i32 %10, ptr %2, align 4
  br label %11

11:                                               ; preds = %7
  %12 = load i32, ptr %2, align 4
  %13 = call i32 @llvm.eh.typeid.for(ptr @__objc_id_type_info) #8
  %14 = icmp eq i32 %12, %13
  br i1 %14, label %15, label %29

15:                                               ; preds = %11
  %16 = load ptr, ptr %1, align 8
  store ptr %16, ptr %3, align 8
  %17 = load ptr, ptr %3, align 8
  %18 = call ptr @object_getClass(ptr noundef %17)
  %19 = load ptr, ptr @"$_OBJC_REF_CLASS_Test", align 8
  %20 = call ptr @objc_msgSend(ptr noundef %19, ptr noundef @".objc_selector_class_#16\010:8"), !GNUObjCMessageSend !6
  %21 = icmp eq ptr %18, %20
  br i1 %21, label %24, label %22

22:                                               ; preds = %15
  call void @_assert(ptr noundef @.str.3, ptr noundef @.str.1, i32 noundef 42) #7
  unreachable

23:                                               ; No predecessors!
  br label %24

24:                                               ; preds = %23, %15
  %25 = phi i1 [ true, %15 ], [ false, %23 ]
  %26 = zext i1 %25 to i32
  store i32 1, ptr @idRethrown, align 4
  %27 = call ptr @__acrt_iob_func(i32 noundef 2)
  %28 = call i32 (ptr, ptr, ...) @fprintf(ptr noundef %27, ptr noundef @.str.4)
  call void @objc_exception_throw(ptr %16) #7
  unreachable

29:                                               ; preds = %11
  %30 = load ptr, ptr %1, align 8
  %31 = load i32, ptr %2, align 4
  %32 = insertvalue { ptr, i32 } poison, ptr %30, 0
  %33 = insertvalue { ptr, i32 } %32, i32 %31, 1
  resume { ptr, i32 } %33
}

; Function Attrs: nounwind memory(none)
declare i32 @llvm.eh.typeid.for(ptr) #4

declare dllimport ptr @object_getClass(ptr noundef) #3

; Function Attrs: noinline optnone uwtable
define dso_local i32 @rethrow_test() #0 personality ptr @__gxx_personality_seh0 {
  %1 = alloca i32, align 4
  %2 = alloca ptr, align 8
  %3 = alloca i32, align 4
  %4 = alloca ptr, align 8
  %5 = alloca ptr, align 8
  %6 = invoke i32 @rethrow_id()
          to label %7 unwind label %10

7:                                                ; preds = %0
  br label %8

8:                                                ; preds = %7
  %9 = load i32, ptr %1, align 4
  ret i32 %9

10:                                               ; preds = %0
  %11 = landingpad { ptr, i32 }
          catch ptr @__objc_eh_typeinfo_Test
          catch ptr @__objc_id_type_info
          catch ptr null
  %12 = extractvalue { ptr, i32 } %11, 0
  store ptr %12, ptr %2, align 8
  %13 = extractvalue { ptr, i32 } %11, 1
  store i32 %13, ptr %3, align 4
  br label %14

14:                                               ; preds = %10
  %15 = load i32, ptr %3, align 4
  %16 = call i32 @llvm.eh.typeid.for(ptr @__objc_eh_typeinfo_Test) #8
  %17 = icmp eq i32 %15, %16
  br i1 %17, label %21, label %18

18:                                               ; preds = %14
  %19 = call i32 @llvm.eh.typeid.for(ptr @__objc_id_type_info) #8
  %20 = icmp eq i32 %15, %19
  br i1 %20, label %25, label %29

21:                                               ; preds = %14
  %22 = load ptr, ptr %2, align 8
  store ptr %22, ptr %4, align 8
  store i32 1, ptr @testCaught, align 4
  %23 = call ptr @__acrt_iob_func(i32 noundef 2)
  %24 = call i32 (ptr, ptr, ...) @fprintf(ptr noundef %23, ptr noundef @.str.5)
  call void @objc_exception_throw(ptr %22) #7
  unreachable

25:                                               ; preds = %18
  %26 = load ptr, ptr %2, align 8
  store ptr %26, ptr %5, align 8
  %27 = call ptr @__acrt_iob_func(i32 noundef 2)
  %28 = call i32 (ptr, ptr, ...) @fprintf(ptr noundef %27, ptr noundef @.str.6)
  call void @_assert(ptr noundef @.str.7, ptr noundef @.str.1, i32 noundef 61) #7
  unreachable

29:                                               ; preds = %18
  %30 = load ptr, ptr %2, align 8
  %31 = call ptr @__acrt_iob_func(i32 noundef 2)
  %32 = call i32 (ptr, ptr, ...) @fprintf(ptr noundef %31, ptr noundef @.str.8)
  call void @_assert(ptr noundef @.str.7, ptr noundef @.str.1, i32 noundef 66) #7
  unreachable
}

; Function Attrs: noinline optnone uwtable
define dso_local i32 @rethrow_catchall() #0 personality ptr @__gxx_personality_seh0 {
  %1 = alloca ptr, align 8
  %2 = alloca i32, align 4
  %3 = invoke i32 @rethrow_test()
          to label %4 unwind label %6

4:                                                ; preds = %0
  br label %5

5:                                                ; preds = %4
  ret i32 0

6:                                                ; preds = %0
  %7 = landingpad { ptr, i32 }
          catch ptr null
  %8 = extractvalue { ptr, i32 } %7, 0
  store ptr %8, ptr %1, align 8
  %9 = extractvalue { ptr, i32 } %7, 1
  store i32 %9, ptr %2, align 4
  br label %10

10:                                               ; preds = %6
  %11 = load ptr, ptr %1, align 8
  %12 = load i32, ptr @testCaught, align 4
  %13 = icmp ne i32 %12, 0
  br i1 %13, label %16, label %14

14:                                               ; preds = %10
  call void @_assert(ptr noundef @.str.9, ptr noundef @.str.1, i32 noundef 74) #7
  unreachable

15:                                               ; No predecessors!
  br label %16

16:                                               ; preds = %15, %10
  %17 = phi i1 [ true, %10 ], [ false, %15 ]
  %18 = zext i1 %17 to i32
  store i32 1, ptr @catchallRethrown, align 4
  %19 = call ptr @__acrt_iob_func(i32 noundef 2)
  %20 = call i32 (ptr, ptr, ...) @fprintf(ptr noundef %19, ptr noundef @.str.10)
  call void @objc_exception_throw(ptr %11) #7
  unreachable
}

; Function Attrs: noinline optnone uwtable
define dso_local i32 @not_matched_catch() #0 personality ptr @__gxx_personality_seh0 {
  %1 = alloca ptr, align 8
  %2 = alloca i32, align 4
  %3 = alloca ptr, align 8
  %4 = invoke i32 @rethrow_catchall()
          to label %5 unwind label %7

5:                                                ; preds = %0
  br label %6

6:                                                ; preds = %5, %15
  ret i32 0

7:                                                ; preds = %0
  %8 = landingpad { ptr, i32 }
          catch ptr @__objc_eh_typeinfo_NSString
  %9 = extractvalue { ptr, i32 } %8, 0
  store ptr %9, ptr %1, align 8
  %10 = extractvalue { ptr, i32 } %8, 1
  store i32 %10, ptr %2, align 4
  br label %11

11:                                               ; preds = %7
  %12 = load i32, ptr %2, align 4
  %13 = call i32 @llvm.eh.typeid.for(ptr @__objc_eh_typeinfo_NSString) #8
  %14 = icmp eq i32 %12, %13
  br i1 %14, label %15, label %17

15:                                               ; preds = %11
  %16 = load ptr, ptr %1, align 8
  store ptr %16, ptr %3, align 8
  store i32 1, ptr @wrongMatch, align 4
  br label %6

17:                                               ; preds = %11
  %18 = load ptr, ptr %1, align 8
  %19 = load i32, ptr %2, align 4
  %20 = insertvalue { ptr, i32 } poison, ptr %18, 0
  %21 = insertvalue { ptr, i32 } %20, i32 %19, 1
  resume { ptr, i32 } %21
}

; Function Attrs: noinline optnone uwtable
define dso_local i32 @main() #0 personality ptr @__gxx_personality_seh0 {
  %1 = alloca i32, align 4
  %2 = alloca ptr, align 8
  %3 = alloca i32, align 4
  %4 = alloca ptr, align 8
  store i32 0, ptr %1, align 4
  %5 = invoke i32 @rethrow_catchall()
          to label %6 unwind label %8

6:                                                ; preds = %0
  br label %7

7:                                                ; preds = %6, %60
  ret i32 0

8:                                                ; preds = %0
  %9 = landingpad { ptr, i32 }
          catch ptr @__objc_id_type_info
  %10 = extractvalue { ptr, i32 } %9, 0
  store ptr %10, ptr %2, align 8
  %11 = extractvalue { ptr, i32 } %9, 1
  store i32 %11, ptr %3, align 4
  br label %12

12:                                               ; preds = %8
  %13 = load i32, ptr %3, align 4
  %14 = call i32 @llvm.eh.typeid.for(ptr @__objc_id_type_info) #8
  %15 = icmp eq i32 %13, %14
  br i1 %15, label %16, label %64

16:                                               ; preds = %12
  %17 = load ptr, ptr %2, align 8
  store ptr %17, ptr %4, align 8
  %18 = load i32, ptr @finallyEntered, align 4
  %19 = icmp eq i32 %18, 1
  br i1 %19, label %22, label %20

20:                                               ; preds = %16
  call void @_assert(ptr noundef @.str.11, ptr noundef @.str.1, i32 noundef 99) #7
  unreachable

21:                                               ; No predecessors!
  br label %22

22:                                               ; preds = %21, %16
  %23 = phi i1 [ true, %16 ], [ false, %21 ]
  %24 = zext i1 %23 to i32
  %25 = load i32, ptr @cleanupRun, align 4
  %26 = icmp eq i32 %25, 1
  br i1 %26, label %29, label %27

27:                                               ; preds = %22
  call void @_assert(ptr noundef @.str.12, ptr noundef @.str.1, i32 noundef 100) #7
  unreachable

28:                                               ; No predecessors!
  br label %29

29:                                               ; preds = %28, %22
  %30 = phi i1 [ true, %22 ], [ false, %28 ]
  %31 = zext i1 %30 to i32
  %32 = load i32, ptr @idRethrown, align 4
  %33 = icmp eq i32 %32, 1
  br i1 %33, label %36, label %34

34:                                               ; preds = %29
  call void @_assert(ptr noundef @.str.13, ptr noundef @.str.1, i32 noundef 101) #7
  unreachable

35:                                               ; No predecessors!
  br label %36

36:                                               ; preds = %35, %29
  %37 = phi i1 [ true, %29 ], [ false, %35 ]
  %38 = zext i1 %37 to i32
  %39 = load i32, ptr @catchallRethrown, align 4
  %40 = icmp eq i32 %39, 1
  br i1 %40, label %43, label %41

41:                                               ; preds = %36
  call void @_assert(ptr noundef @.str.14, ptr noundef @.str.1, i32 noundef 102) #7
  unreachable

42:                                               ; No predecessors!
  br label %43

43:                                               ; preds = %42, %36
  %44 = phi i1 [ true, %36 ], [ false, %42 ]
  %45 = zext i1 %44 to i32
  %46 = load i32, ptr @wrongMatch, align 4
  %47 = icmp eq i32 %46, 0
  br i1 %47, label %50, label %48

48:                                               ; preds = %43
  call void @_assert(ptr noundef @.str.15, ptr noundef @.str.1, i32 noundef 103) #7
  unreachable

49:                                               ; No predecessors!
  br label %50

50:                                               ; preds = %49, %43
  %51 = phi i1 [ true, %43 ], [ false, %49 ]
  %52 = zext i1 %51 to i32
  %53 = load ptr, ptr %4, align 8
  %54 = call ptr @object_getClass(ptr noundef %53)
  %55 = load ptr, ptr @"$_OBJC_REF_CLASS_Test", align 8
  %56 = call ptr @objc_msgSend(ptr noundef %55, ptr noundef @".objc_selector_class_#16\010:8"), !GNUObjCMessageSend !6
  %57 = icmp eq ptr %54, %56
  br i1 %57, label %60, label %58

58:                                               ; preds = %50
  call void @_assert(ptr noundef @.str.3, ptr noundef @.str.1, i32 noundef 104) #7
  unreachable

59:                                               ; No predecessors!
  br label %60

60:                                               ; preds = %59, %50
  %61 = phi i1 [ true, %50 ], [ false, %59 ]
  %62 = zext i1 %61 to i32
  %63 = load ptr, ptr %4, align 8
  call void @objc_msgSend(ptr noundef %63, ptr noundef @".objc_selector_dealloc_v16\010:8"), !GNUObjCMessageSend !7
  br label %7

64:                                               ; preds = %12
  %65 = load ptr, ptr %2, align 8
  %66 = load i32, ptr %3, align 4
  %67 = insertvalue { ptr, i32 } poison, ptr %65, 0
  %68 = insertvalue { ptr, i32 } %67, i32 %66, 1
  resume { ptr, i32 } %68
}

; Function Attrs: nocallback nofree nosync nounwind willreturn
declare void @llvm.va_start(ptr) #5

; Function Attrs: nounwind
declare dso_local i32 @__mingw_vfprintf(ptr noundef, ptr noundef, ptr noundef) #6

; Function Attrs: nocallback nofree nosync nounwind willreturn
declare void @llvm.va_end(ptr) #5

define linkonce_odr hidden void @.objcv2_load_function() comdat {
  call void @__objc_load(ptr @.objc_init)
  ret void
}

declare dso_local void @__objc_load(ptr)

attributes #0 = { noinline optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { noreturn "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #2 = { noinline nounwind optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #3 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #4 = { nounwind memory(none) }
attributes #5 = { nocallback nofree nosync nounwind willreturn }
attributes #6 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #7 = { noreturn }
attributes #8 = { nounwind }
attributes #9 = { noreturn nounwind }

!llvm.module.flags = !{!0, !1, !2, !3}
!llvm.ident = !{!4}

!0 = !{i32 1, !"wchar_size", i32 2}
!1 = !{i32 8, !"PIC Level", i32 2}
!2 = !{i32 7, !"uwtable", i32 2}
!3 = !{i32 1, !"MaxTLSAlign", i32 65536}
!4 = !{!"clang version 18.0.0git"}
!5 = !{!"new", !"Test", i1 true}
!6 = !{!"class", !"Test", i1 true}
!7 = !{!"dealloc", !"", i1 false}

@davidchisnall
Copy link
Member

Hmm, it looks as if clang isn't emitting the begin and end catch calls. In a finally block, I think it should be calling the _Unwind_rethrow thing (I think it was before the last change?). If I'm reading the IR correctly, @finally is emitted as a cleanup (which seems right) and so isn't rethrowing, it's resuming unwinding.

I'm a bit confused because it looks as if the finally code should be emitting the begin / end carch blocks:

https://github.com/llvm/llvm-project/blob/ab073cbccb6e79d8b65a286e8948bc1f07c7c09b/clang/lib/CodeGen/CGObjCRuntime.cpp#L157

Can you see what's going on there?

@qmfrederik
Copy link
Collaborator Author

OK - so we were entering the if(usesSEHExceptions) block which does not emit the try/catch blocks. Looks like we want a slightly different setup from the existing ones, where try/catch blocks are emitted and objc_exception_rethrow is called, like this: llvm/llvm-project@b983406 ?

This causes the test to proceed a bit further:

throw: Throwing exception
objc_exception_throw: Entering
objc_exception_throw: Throwing exception 0xfe10eea8 of type Test
objc_exception_rethrow: Entering
gnustep::libobjc::__objc_id_type_info::__do_catch: Entering
gnustep::libobjc::__objc_id_type_info::__do_catch: Type info @id
gnustep::libobjc::__objc_id_type_info::__do_catch: got id type info for 0xfe10eea8, returning true
rethrow_id: Rethrowing exception
objc_exception_throw: Entering
objc_exception_throw: Throwing exception 0xfe10eea8 of type Test
gnustep::libobjc::__objc_class_type_info::__do_catch: Entering
gnustep::libobjc::__objc_class_type_info::__do_catch: Type info @id
gnustep::libobjc::__objc_class_type_info::__do_catch: object is an id type or class type and we're in Apple compat mode
gnustep::libobjc::__objc_class_type_info::__do_catch: got type Test
gnustep::libobjc::__objc_class_type_info::__do_catch: found is YES
gnustep::libobjc::__objc_class_type_info::__do_catch: found matching type
gnustep::libobjc::__objc_class_type_info::__do_catch: returning 1; *obj is 0xfe10eea8
eh_cleanup: Releasing exception 0xfe10eea8
rethrow_test: Rethrowing exception
objc_exception_throw: Entering
objc_exception_throw: Throwing exception 0xfe10eea8 of type Test
eh_cleanup: Releasing exception 0xfe10eea8
rethrow_catchall: Rethrowing exception
objc_exception_throw: Entering
objc_exception_throw: Throwing exception 0xfe10ef70 of type (null)
Segmentation fault

Looks like the final block as a wrong pointer to the exception (I assume it should have been 0xfe10eea8 but we have 0xfe10ef70 instead)? Also, I've seen to different definitions for objc_exception_rethrow : void objc_exception_rethrow(void*) and void objc_exception_rethrow(void)?

@qmfrederik
Copy link
Collaborator Author

The segfault comes from the logging code which calls object_getClass; if I disable the logging code the test will fail on assert(object_getClass(x) == [Test class]); all of this I assume because something goes wrong here:

	@catch(...)
	{
		@throw;
	}

resulting in an invalid exception object being thrown?

@qmfrederik
Copy link
Collaborator Author

OK - so with llvm/llvm-project@53132da I get all tests, except for UnexpectedException, to pass. ExceptionReThrowFn was set to objc_exception_throw instead of objc_exception_rethrow, which probably explained the behavior we've seen earlier.

@davidchisnall
Copy link
Member

Nice! Can you add a clang test and raise a PR? I’ll review it.

You can base the test on this:

https://github.com/llvm/llvm-project/blob/main/clang/test/CodeGenObjC/exceptions-nonfragile.m

Pass some different target triples and runtime versions and make sure that it does the right thing in each case. You can run a single test with llvm-lit.

@qmfrederik
Copy link
Collaborator Author

Will do, thanks. Here's all current changes squashed into a single commit: llvm/llvm-project@fa97083

@qmfrederik
Copy link
Collaborator Author

@davidchisnall Here you go: llvm/llvm-project#77255

@davidchisnall
Copy link
Member

Any ideas why the uncaught exception tests are failing? Does the VEH hook not do the right thing?

@qmfrederik
Copy link
Collaborator Author

The hook is not implemented yet, all it does (for now) is to return EXCEPTION_CONTINUE_SEARCH.

I haven't had the time yet to read up on the semantics of Vectored Exception Handlers (the documentation seems sparse) and the expected behavior of objc_setUncaughtExceptionHandler.

Where I left it at the moment:

  • Seems VEHs are invoked whenever an exception is raised (not just when it is unhandled); so probably need to do some filtering?
  • Should the callback provided by objc_setUncaughtExceptionHandler be called for all exceptions (including C++) or only ObjC exceptions? I assume only for ObjC exceptions as it has an id exception argument? How can we reliably know the exception is an ObjC exception? Do we need to pass additional metadata to __cxa_throw (not just an id)?

@davidchisnall
Copy link
Member

Seems VEHs are invoked whenever an exception is raised (not just when it is unhandled); so probably need to do some filtering?

I think they're called before SEH, which is why I was unsure how it worked. I'm not sure if you get an unhandled-exception exception that you can catch with VEH?

Should the callback provided by objc_setUncaughtExceptionHandler be called for all exceptions (including C++) or only ObjC exceptions?

Well, that gets complicated. Probably only for ObjC ones, because C++ already has it defined that it calls std::terminate on unwind failure.

It would be nice to call std::set_terminate and wrap the existing handler in one that calls the ObjC handler if we're in the middle of an ObjC exception throw, but I don't think that's actually possible.

I assume only for ObjC exceptions as it has an id exception argument? How can we reliably know the exception is an ObjC exception? Do we need to pass additional metadata to __cxa_throw (not just an id)

I think the type info is embedded in the thrown object. It's a std::type_info subclass and, for ObjC, we use exactly one so you can just check if the id type info is thrown.

The corner case here is that you can use throw in Objective-C++ to throw an Objective-C object. This will throw it with one of the other type infos. I don't know whether I'd expect that to call the unhandled handler or not. Worth checking what macOS does.

@qmfrederik
Copy link
Collaborator Author

@davidchisnall Looks like the easiest way to do is to:

  • Verify the current exception is a GCC exception
  • Use __cxa_rethrow() (guarded by __cxa_current_exception_type()) to rethrow the exception
  • Use @catch(id) to get the underlying exception

I've pushed a commit which implements this.

Feels a bit clunky (but it's similar to what the Apple runtime does); but I couldn't find an easier way to get this information. I believe ex->ExceptionInformation[0] is a _Unwind_Exception, but that only exposes an exception_class and not the actual exception?

@davidchisnall
Copy link
Member

That’s probably easier. The unwind exception holds a pointer to the C++ exception, which holds the type info and has the object appended, but different C++ runtimes have subtly different versions of this structure. Given that this is a very slow path (basically only ever used to pop up a diagnostic window when a thread crashes) it’ probably fine for it to do the rethrow thing.

@qmfrederik
Copy link
Collaborator Author

@davidchisnall I've made some last changes to this PR:

  • I've finalized the unexpected exception handler (to avoid infinite recursion)
  • I've disabled the old ABI on MinGW because some tests were failing
  • I've removed most of the logging code, as it mainly cluttered the code (I can revert that particular commit if you feel it's worth keeping it).

With that, all tests on MinGW now pass (100% tests passed, 0 tests failed out of 98):

Test project C:/tools/msys64/home/vagrant/git/libobjc2/build
      Start  1: alias
 1/98 Test  #1: alias ......................................   Passed    0.12 sec
      Start  2: alias_optimised
 2/98 Test  #2: alias_optimised ............................   Passed    0.02 sec
      Start  3: alignTest
 3/98 Test  #3: alignTest ..................................   Passed    0.02 sec
      Start  4: alignTest_optimised
 4/98 Test  #4: alignTest_optimised ........................   Passed    0.02 sec
      Start  5: AllocatePair
 5/98 Test  #5: AllocatePair ...............................   Passed    0.02 sec
      Start  6: AllocatePair_optimised
 6/98 Test  #6: AllocatePair_optimised .....................   Passed    0.03 sec
      Start  7: AssociatedObject
 7/98 Test  #7: AssociatedObject ...........................   Passed    0.03 sec
      Start  8: AssociatedObject_optimised
 8/98 Test  #8: AssociatedObject_optimised .................   Passed    0.03 sec
      Start  9: AssociatedObject2
 9/98 Test  #9: AssociatedObject2 ..........................   Passed    0.03 sec
      Start 10: AssociatedObject2_optimised
10/98 Test #10: AssociatedObject2_optimised ................   Passed    0.03 sec
      Start 11: BlockImpTest
11/98 Test #11: BlockImpTest ...............................   Passed    0.03 sec
      Start 12: BlockImpTest_optimised
12/98 Test #12: BlockImpTest_optimised .....................   Passed    0.03 sec
      Start 13: BlockTest_arc
13/98 Test #13: BlockTest_arc ..............................   Passed    0.03 sec
      Start 14: BlockTest_arc_optimised
14/98 Test #14: BlockTest_arc_optimised ....................   Passed    0.07 sec
      Start 15: ConstantString
15/98 Test #15: ConstantString .............................   Passed    0.05 sec
      Start 16: ConstantString_optimised
16/98 Test #16: ConstantString_optimised ...................   Passed    0.05 sec
      Start 17: Category
17/98 Test #17: Category ...................................   Passed    0.02 sec
18/98 Test #18: Category_optimised .........................   Passed    0.02 sec
      Start 19: ExceptionTest
19/98 Test #19: ExceptionTest ..............................   Passed    0.03 sec
      Start 20: ExceptionTest_optimised
20/98 Test #20: ExceptionTest_optimised ....................   Passed    0.03 sec
      Start 21: FastARC
21/98 Test #21: FastARC ....................................   Passed    0.03 sec
      Start 22: FastARC_optimised
22/98 Test #22: FastARC_optimised ..........................   Passed    0.04 sec
      Start 23: FastARCPool
23/98 Test #23: FastARCPool ................................   Passed    0.03 sec
      Start 24: FastARCPool_optimised
24/98 Test #24: FastARCPool_optimised ......................   Passed    0.03 sec
      Start 25: FastRefCount
25/98 Test #25: FastRefCount ...............................   Passed    0.03 sec
      Start 26: FastRefCount_optimised
26/98 Test #26: FastRefCount_optimised .....................   Passed    0.02 sec
      Start 27: Forward
27/98 Test #27: Forward ....................................   Passed    0.03 sec
      Start 28: Forward_optimised
28/98 Test #28: Forward_optimised ..........................   Passed    0.03 sec
      Start 29: ManyManySelectors
29/98 Test #29: ManyManySelectors ..........................   Passed    1.46 sec
      Start 30: ManyManySelectors_optimised
30/98 Test #30: ManyManySelectors_optimised ................   Passed    1.45 sec
      Start 31: NestedExceptions
31/98 Test #31: NestedExceptions ...........................   Passed    0.02 sec
      Start 32: NestedExceptions_optimised
32/98 Test #32: NestedExceptions_optimised .................   Passed    0.02 sec
      Start 33: PropertyAttributeTest
33/98 Test #33: PropertyAttributeTest ......................   Passed    0.02 sec
      Start 34: PropertyAttributeTest_optimised
34/98 Test #34: PropertyAttributeTest_optimised ............   Passed    0.03 sec
      Start 35: ProtocolExtendedProperties
35/98 Test #35: ProtocolExtendedProperties .................   Passed    0.02 sec
      Start 36: ProtocolExtendedProperties_optimised
36/98 Test #36: ProtocolExtendedProperties_optimised .......   Passed    0.03 sec
      Start 37: PropertyIntrospectionTest
37/98 Test #37: PropertyIntrospectionTest ..................   Passed    0.09 sec
      Start 38: PropertyIntrospectionTest_optimised
38/98 Test #38: PropertyIntrospectionTest_optimised ........   Passed    0.03 sec
      Start 39: ProtocolCreation
39/98 Test #39: ProtocolCreation ...........................   Passed    0.04 sec
      Start 40: ProtocolCreation_optimised
40/98 Test #40: ProtocolCreation_optimised .................   Passed    0.03 sec
      Start 41: ResurrectInDealloc_arc
41/98 Test #41: ResurrectInDealloc_arc .....................   Passed    0.02 sec
      Start 42: ResurrectInDealloc_arc_optimised
42/98 Test #42: ResurrectInDealloc_arc_optimised ...........   Passed    0.02 sec
      Start 43: RuntimeTest
43/98 Test #43: RuntimeTest ................................   Passed    0.02 sec
      Start 44: RuntimeTest_optimised
44/98 Test #44: RuntimeTest_optimised ......................   Passed    0.02 sec
      Start 45: SuperMethodMissing
45/98 Test #45: SuperMethodMissing .........................   Passed    0.05 sec
      Start 46: SuperMethodMissing_optimised
46/98 Test #46: SuperMethodMissing_optimised ...............   Passed    0.04 sec
      Start 47: WeakBlock_arc
47/98 Test #47: WeakBlock_arc ..............................   Passed    0.04 sec
      Start 48: WeakBlock_arc_optimised
48/98 Test #48: WeakBlock_arc_optimised ....................   Passed    0.05 sec
      Start 49: WeakRefLoad
49/98 Test #49: WeakRefLoad ................................   Passed    0.02 sec
      Start 50: WeakRefLoad_optimised
50/98 Test #50: WeakRefLoad_optimised ......................   Passed    0.02 sec
      Start 51: WeakReferences_arc
51/98 Test #51: WeakReferences_arc .........................   Passed    2.52 sec
      Start 52: WeakReferences_arc_optimised
52/98 Test #52: WeakReferences_arc_optimised ...............   Passed    2.84 sec
      Start 53: WeakImportClass
53/98 Test #53: WeakImportClass ............................   Passed    0.02 sec
      Start 54: WeakImportClass_optimised
54/98 Test #54: WeakImportClass_optimised ..................   Passed    0.02 sec
      Start 55: ivar_arc
55/98 Test #55: ivar_arc ...................................   Passed    0.02 sec
      Start 56: ivar_arc_optimised
56/98 Test #56: ivar_arc_optimised .........................   Passed    0.02 sec
      Start 57: ivar_atomic
57/98 Test #57: ivar_atomic ................................   Passed    0.02 sec
      Start 58: ivar_atomic_optimised
58/98 Test #58: ivar_atomic_optimised ......................   Passed    0.02 sec
      Start 59: IVarOverlap
59/98 Test #59: IVarOverlap ................................   Passed    0.03 sec
      Start 60: IVarOverlap_optimised
60/98 Test #60: IVarOverlap_optimised ......................   Passed    0.03 sec
      Start 61: IVarSuperclassOverlap
61/98 Test #61: IVarSuperclassOverlap ......................   Passed    0.04 sec
      Start 62: IVarSuperclassOverlap_optimised
62/98 Test #62: IVarSuperclassOverlap_optimised ............   Passed    0.02 sec
      Start 63: objc_msgSend
63/98 Test #63: objc_msgSend ...............................   Passed    0.03 sec
      Start 64: objc_msgSend_optimised
64/98 Test #64: objc_msgSend_optimised .....................   Passed    0.02 sec
      Start 65: msgInterpose
65/98 Test #65: msgInterpose ...............................   Passed    0.03 sec
      Start 66: msgInterpose_optimised
66/98 Test #66: msgInterpose_optimised .....................   Passed    0.02 sec
      Start 67: NilException
67/98 Test #67: NilException ...............................   Passed    0.02 sec
      Start 68: NilException_optimised
68/98 Test #68: NilException_optimised .....................   Passed    0.03 sec
      Start 69: MethodArguments
69/98 Test #69: MethodArguments ............................   Passed    0.02 sec
      Start 70: MethodArguments_optimised
70/98 Test #70: MethodArguments_optimised ..................   Passed    0.03 sec
      Start 71: zeroSizedIVar
71/98 Test #71: zeroSizedIVar ..............................   Passed    0.02 sec
      Start 72: zeroSizedIVar_optimised
72/98 Test #72: zeroSizedIVar_optimised ....................   Passed    0.02 sec
      Start 73: exchange
73/98 Test #73: exchange ...................................   Passed    0.02 sec
      Start 74: exchange_optimised
74/98 Test #74: exchange_optimised .........................   Passed    0.03 sec
      Start 75: hash_table_delete
75/98 Test #75: hash_table_delete ..........................   Passed    0.02 sec
      Start 76: hash_table_delete_optimised
76/98 Test #76: hash_table_delete_optimised ................   Passed    0.02 sec
      Start 77: hash_test
77/98 Test #77: hash_test ..................................   Passed    2.02 sec
      Start 78: hash_test_optimised
78/98 Test #78: hash_test_optimised ........................   Passed    0.58 sec
      Start 79: setSuperclass
79/98 Test #79: setSuperclass ..............................   Passed    0.02 sec
      Start 80: setSuperclass_optimised
80/98 Test #80: setSuperclass_optimised ....................   Passed    0.03 sec
      Start 81: UnexpectedException
81/98 Test #81: UnexpectedException ........................   Passed    0.05 sec
      Start 82: UnexpectedException_optimised
82/98 Test #82: UnexpectedException_optimised ..............   Passed    0.08 sec
      Start 83: ARCTest_arc
83/98 Test #83: ARCTest_arc ................................   Passed    0.02 sec
      Start 84: ARCTest_arc_optimised
84/98 Test #84: ARCTest_arc_optimised ......................   Passed    0.02 sec
      Start 85: PropertyIntrospectionTest2_arc
85/98 Test #85: PropertyIntrospectionTest2_arc .............   Passed    0.02 sec
      Start 86: PropertyIntrospectionTest2_arc_optimised
86/98 Test #86: PropertyIntrospectionTest2_arc_optimised ...   Passed    0.02 sec
      Start 87: category_properties
87/98 Test #87: category_properties ........................   Passed    0.02 sec
      Start 88: category_properties_optimised
88/98 Test #88: category_properties_optimised ..............   Passed    0.03 sec
      Start 89: CXXExceptions
89/98 Test #89: CXXExceptions ..............................   Passed    0.02 sec
      Start 90: CXXExceptions_optimised
90/98 Test #90: CXXExceptions_optimised ....................   Passed    0.02 sec
      Start 91: ForwardDeclareProtocolAccess
91/98 Test #91: ForwardDeclareProtocolAccess ...............   Passed    0.02 sec
      Start 92: ForwardDeclareProtocolAccess_optimised
92/98 Test #92: ForwardDeclareProtocolAccess_optimised .....   Passed    0.02 sec
      Start 93: ObjCXXEHInterop
93/98 Test #93: ObjCXXEHInterop ............................   Passed    0.02 sec
      Start 94: ObjCXXEHInterop_optimised
94/98 Test #94: ObjCXXEHInterop_optimised ..................   Passed    0.02 sec
      Start 95: ObjCXXEHInteropTwice
95/98 Test #95: ObjCXXEHInteropTwice .......................   Passed    0.02 sec
      Start 96: ObjCXXEHInteropTwice_optimised
96/98 Test #96: ObjCXXEHInteropTwice_optimised .............   Passed    0.02 sec
      Start 97: ObjCXXEHInterop_arc
97/98 Test #97: ObjCXXEHInterop_arc ........................   Passed    0.02 sec
      Start 98: ObjCXXEHInterop_arc_optimised
98/98 Test #98: ObjCXXEHInterop_arc_optimised ..............   Passed    0.02 sec

100% tests passed, 0 tests failed out of 98

Total Test time (real) =  14.17 sec

I think this PR is now in a pretty good shape, let me know if there's anything else you need from me.

Copy link
Member

@davidchisnall davidchisnall left a comment

Choose a reason for hiding this comment

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

Looks mostly fine. I don’t understand the VEH bits fully (I thought those handlers happened first, why isn’t it called for every thrown exception?) so a few commends explaining the model would be nice for the next person. Also a couple of changes needed to make it work properly in multithreaded contexts.

eh_win32_mingw.m Outdated
extern void __cxa_rethrow();

BOOL handler_installed = NO;
BOOL in_handler = NO;
Copy link
Member

Choose a reason for hiding this comment

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

These probably need to be thread local.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

in_handler: yes, handler_installed tracks the call to AddVectoredExceptionHandler which should called only once per process?

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense, yes.

objc_uncaught_exception_handler previousHandler = __atomic_exchange_n(&_objc_unexpected_exception, handler, __ATOMIC_SEQ_CST);

// Add a vectored exception handler to support the hook. We only need to do this once.
if (!handler_installed) {
Copy link
Member

Choose a reason for hiding this comment

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

This probably also needs to be atomic.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm - this is to protect against a race condition where objc_setUncaughtExceptionHandler might be called from multiple threads at the same time? I guess we could add a mutex around this block but is that a realistic scenario?

Copy link
Member

Choose a reason for hiding this comment

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

It's allowed by the API.

@davidchisnall
Copy link
Member

Please can you squash before you merge?

@qmfrederik
Copy link
Collaborator Author

@davidchisnall I left some additional notes, and I updated UnexpectedException.m to assert that handler is invoked for unhandled exceptions only.

@qmfrederik
Copy link
Collaborator Author

Please can you squash before you merge?

I squashed but don't have merge permissions in this repo ;-)

@davidchisnall
Copy link
Member

Please can you squash before you merge?

I squashed but don't have merge permissions in this repo ;-)

Fixed.

@qmfrederik
Copy link
Collaborator Author

Cool, thanks!

@qmfrederik qmfrederik merged commit 6528090 into gnustep:master Jan 10, 2024
47 checks passed
@qmfrederik qmfrederik deleted the cxx-exceptions branch January 10, 2024 14:00
davidchisnall pushed a commit to llvm/llvm-project that referenced this pull request Jan 10, 2024
The GNUstep Objective C runtime (libobjc2) is adding support for the GNU
ABI on Windows (more specifically, MinGW). The libobjc2 runtime uses C++
exceptions in that configuration; this PR updates clang to act
accordingly.

The corresponding change to libobjc2 is here:
gnustep/libobjc2#267
justinfargnoli pushed a commit to justinfargnoli/llvm-project that referenced this pull request Jan 28, 2024
The GNUstep Objective C runtime (libobjc2) is adding support for the GNU
ABI on Windows (more specifically, MinGW). The libobjc2 runtime uses C++
exceptions in that configuration; this PR updates clang to act
accordingly.

The corresponding change to libobjc2 is here:
gnustep/libobjc2#267
@qmfrederik qmfrederik restored the cxx-exceptions branch February 7, 2024 09:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants