llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
28.79k stars 11.9k forks source link

Objective-C++ Wrong Assignment of arguments to registers on Windows ARM64 when returning an instance of C++ Class. #88273

Closed hmelder closed 6 months ago

hmelder commented 6 months ago

The libobjc2 objc_msgSend implementation for aarch64 expects the receiver and selector to be passed in x0 and x1 respectively.

On Windows ARM64, the pointer to the (uninitialised) instance of the C++ Class is assigned to x0, shifting receiver and selector into x1 and x2. This results in a crash.

This does not happen on Ubuntu aarch64, as seen in the lldb snippet below.

I am using the GNUstep Windows SDK from https://github.com/gnustep/tools-windows-msvc.

Here is the test code:

#import <Foundation/Foundation.h>

class Bar
{
public:
    Bar(int i) : m_i(i) {}
private:
    int m_i;
};

@interface Test : NSObject
@end

@implementation Test

+ (Bar)bar
{
    return Bar(42);
}

@end

int main(int argc, char *argv[])
{
    NSLog(@"Hello");

    auto pair = [Test bar]; // crashes

    NSLog(@"Success");

    return 0;
}

Windows 11 ARM64

Machine Information

WindowsBuildLabEx: 22621.1.arm64fre.ni_release.220506-1250
WindowsProductName: Windows 10 Pro
OSDisplayVersion: 23H2
WindowsKit: 10.0.22621.0

Clang Version

clang version 18.1.3
Target: aarch64-pc-windows-msvc
Thread model: posix
InstalledDir: C:\Program Files\LLVM\bin

Build Command (Inside MSYS2)

clang crash.mm -o crash.exe -g -gcodeview `gnustep-config --objc-flags` `gnustep-config --base-libs` -Xclang -fobjc-dispatch-method=non-legacy

LLDB

Process 6408 stopped
* thread #1, stop reason = breakpoint 2.1
    frame #0: 0x00007ff738e61048 crash.exe`main(argc=867171612, argv=0x00007ff738e640b0) at crash.mm:27
   24   {
   25           NSLog(@"Hello");
   26
-> 27           auto pair = [Test bar]; // crashes
   28
   29           NSLog(@"Success");
   30
(lldb) di
crash.exe`main:
    0x7ff738e6100c <+0>:   sub    sp, sp, #0x20
    0x7ff738e61010 <+4>:   str    x19, [sp, #0x10]
    0x7ff738e61014 <+8>:   str    x30, [sp, #0x18]
    0x7ff738e61018 <+12>:  mov    x0, #0x2c ; =44
    0x7ff738e6101c <+16>:  adrp   x19, 2
    0x7ff738e61020 <+20>:  movk   x0, #0xe000, lsl #16
    0x7ff738e61024 <+24>:  ldr    x19, [x19, #0x5e8]
    0x7ff738e61028 <+28>:  movk   x0, #0x66cd, lsl #32
    0x7ff738e6102c <+32>:  movk   x0, #0x9197, lsl #48
    0x7ff738e61030 <+36>:  blr    x19
    0x7ff738e61034 <+40>:  adrp   x8, 5
    0x7ff738e61038 <+44>:  adrp   x2, 5
    0x7ff738e6103c <+48>:  add    x2, x2, #0x10 ; __start_.objcrt$PCR
    0x7ff738e61040 <+52>:  ldr    x1, [x8]
    0x7ff738e61044 <+56>:  add    x0, sp, #0xc
->  0x7ff738e61048 <+60>:  bl     0x7ff738e62384 ; objc_msgSend_stret
    0x7ff738e6104c <+64>:  mov    x0, #0x803c ; =32828
    0x7ff738e61050 <+68>:  movk   x0, #0xbcf9, lsl #16
    0x7ff738e61054 <+72>:  movk   x0, #0x1e3c, lsl #32
    0x7ff738e61058 <+76>:  movk   x0, #0xa7d7, lsl #48
    0x7ff738e6105c <+80>:  blr    x19
    0x7ff738e61060 <+84>:  mov    w0, wzr
    0x7ff738e61064 <+88>:  ldr    x30, [sp, #0x18]
    0x7ff738e61068 <+92>:  ldr    x19, [sp, #0x10]
    0x7ff738e6106c <+96>:  add    sp, sp, #0x20
    0x7ff738e61070 <+100>: ret
(lldb) register read
General Purpose Registers:
        x0 = 0x000000e233affd1c
        x1 = 0x00007ff738e640b0  $_OBJC_CLASS_Test
        x2 = 0x00007ff738e66010  __start_.objcrt$PCR
        x3 = 0x0000000010000000
        x4 = 0x0000000000000150
        x5 = 0x000000006d9d3bcf
        x6 = 0x00007ff8b4129000  NlsAnsiCodePage + 26720
        x7 = 0x5d4b1dcd6d097720
        x8 = 0x00007ff738e66000  __start_.objcrt$CAL
        x9 = 0x0000000000000000
       x10 = 0x000000007ffe0380
       x11 = 0x0000000000000000
       x12 = 0x0000000000000000
       x13 = 0xa2e64eada2e64ead
       x14 = 0x0000000000000001
       x15 = 0x0000000000000070
       x16 = 0x0000000080000001
       x17 = 0x00005859193355a3
       x18 = 0x000000e233970000
       x19 = 0x00007ff857942570  gnustep-base-1_29.dll`NSLog at NSLog.m:293
       x20 = 0x000002a841621080
       x21 = 0x0000000000000000
       x22 = 0x0000000000000000
       x23 = 0x0000000000000000
       x24 = 0x0000000000000000
       x25 = 0x0000000000000000
       x26 = 0x0000000000000000
       x27 = 0x0000000000000000
       x28 = 0x0000000000000000
        fp = 0x000000e233affd30
        lr = 0x00007ff738e61034  crash.exe`main + 40 at crash.mm:27
        sp = 0x000000e233affd10
        pc = 0x00007ff738e61048  crash.exe`main + 60 at crash.mm:27
      cpsr = 0x80000000

(lldb)

Ubuntu 23.10 aarch64

Clang Version

Ubuntu clang version 18.1.3 (++20240322073236+ef6d1ec07c69-1~exp1~20240322193248.98)
Target: aarch64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

Build Command

clang-18 ObjcCXXObjectReturnTest.mm -o ObjcCXXObjectReturnTest -g `gnustep-config --objc-flags` `gnustep-config --base-libs` -Xclang -fobjc-dispatch-method=non-legacy -fuse-ld=lld-18
* thread #1, name = 'ObjcCXXObjectRe', stop reason = breakpoint 2.1
    frame #0: 0x0000aaaaaaab0dcc ObjcCXXObjectReturnTest`main(argc=<unavailable>, argv=<unavailable>) at ObjcCXXObjectReturnTest.mm:27:14
   24   {
   25       NSLog(@"Hello");
   26
-> 27       auto pair = [Test bar]; // crashes
   28
   29       NSLog(@"Success");
   30
(lldb) di
ObjcCXXObjectReturnTest`main:
    0xaaaaaaab0d9c <+0>:  stp    x29, x30, [sp, #-0x10]!
    0xaaaaaaab0da0 <+4>:  mov    x29, sp
    0xaaaaaaab0da4 <+8>:  mov    x0, #0x2c ; =44
    0xaaaaaaab0da8 <+12>: movk   x0, #0xe000, lsl #16
    0xaaaaaaab0dac <+16>: movk   x0, #0x66cd, lsl #32
    0xaaaaaaab0db0 <+20>: movk   x0, #0x9197, lsl #48
    0xaaaaaaab0db4 <+24>: bl     0xaaaaaaab0e90 ; symbol stub for: NSLog
    0xaaaaaaab0db8 <+28>: adrp   x8, 17
    0xaaaaaaab0dbc <+32>: nop
    0xaaaaaaab0dc0 <+36>: adr    x1, 0xaaaaaaad12b0 ; ObjcCXXObjectReturnTest.PT_LOAD[3].__objc_selectors + 0
    0xaaaaaaab0dc4 <+40>: ldr    x8, [x8, #0xd0]
    0xaaaaaaab0dc8 <+44>: ldr    x0, [x8]
->  0xaaaaaaab0dcc <+48>: bl     0xaaaaaaab0ea0 ; symbol stub for: objc_msgSend
    0xaaaaaaab0dd0 <+52>: mov    x0, #0x803c ; =32828
    0xaaaaaaab0dd4 <+56>: movk   x0, #0xbcf9, lsl #16
    0xaaaaaaab0dd8 <+60>: movk   x0, #0x1e3c, lsl #32
    0xaaaaaaab0ddc <+64>: movk   x0, #0xa7d7, lsl #48
    0xaaaaaaab0de0 <+68>: bl     0xaaaaaaab0e90 ; symbol stub for: NSLog
    0xaaaaaaab0de4 <+72>: mov    w0, wzr
    0xaaaaaaab0de8 <+76>: ldp    x29, x30, [sp], #0x10
    0xaaaaaaab0dec <+80>: ret
(lldb) register read
General Purpose Registers:
        x0 = 0x0000aaaaaaad11a0  ._OBJC_CLASS_Test
        x1 = 0x0000aaaaaaad12b0  ObjcCXXObjectReturnTest.PT_LOAD[3].__objc_selectors + 0
        x2 = 0x0000000000000007
        x3 = 0x0000aaaaaaad2010
        x4 = 0x0000000000000004
        x5 = 0x0000aaaaaaebc7f0
        x6 = 0xb3c97132ac52cb5b
        x7 = 0x0000fffff7e1f3a8  ._OBJC_CLASS_GSMutableString
        x8 = 0x0000aaaaaaad12d0  ObjcCXXObjectReturnTest`._OBJC_REF_CLASS_Test
        x9 = 0x0000000000000000
       x10 = 0x0000fffff7f884cc  libobjc.so.4.6`objc_slot_lookup_super2 + 264
       x11 = 0x0000000000000003
       x12 = 0x0000fffff7f87bd0  libobjc.so.4.6`objc_msg_lookup_sender + 100
       x13 = 0x0000000000000003
       x14 = 0x000000000042a2d5
       x15 = 0x0000ffffffff8b88
       x16 = 0x0000fffff7fb00d0
       x17 = 0x0000fffff76d47f0  libc.so.6`free
       x18 = 0x0000000000000034
       x19 = 0x0000fffffffff0d8
       x20 = 0x0000000000000001
       x21 = 0x0000aaaaaaac0ed8
       x22 = 0x0000aaaaaaab0d9c  ObjcCXXObjectReturnTest`main at ObjcCXXObjectReturnTest.mm:24
       x23 = 0x0000fffffffff0e8
       x24 = 0x0000fffff7ffdb90  ld-linux-aarch64.so.1`_rtld_global_ro
       x25 = 0x0000000000000000
       x26 = 0x0000fffff7ffe008  _rtld_global
       x27 = 0x0000aaaaaaac0ed8
       x28 = 0x0000000000000000
        fp = 0x0000ffffffffef50
        lr = 0x0000aaaaaaab0db8  ObjcCXXObjectReturnTest`main + 28 at ObjcCXXObjectReturnTest.mm:27:14
        sp = 0x0000ffffffffef50
        pc = 0x0000aaaaaaab0dcc  ObjcCXXObjectReturnTest`main + 48 at ObjcCXXObjectReturnTest.mm:27:14
      cpsr = 0x40001000
hmelder commented 6 months ago

I am not quite sure where this "remapping" happens. Probably related to https://github.com/llvm/llvm-project/issues/86384

hmelder commented 6 months ago

@davidchisnall If x0 is used in the MSVC ABI when returning C++ objects, this means it would only be practical to have something like objc_msgSendCXX (where x1 is receiver and x2 selector) as further remapping of arguments before msgSend will break the ABI when tail-calling IMP.

When returning large structures, x8 is used as defined in the ABI and objc_msgSendStret works just fine.

davidchisnall commented 6 months ago

This looks like a result of calling the non-sret version when it should be calling the sret one. This is probably due to the change in the MSVC ABI a few years ago to support NVRO.

hmelder commented 6 months ago

This looks like a result of calling the non-sret version when it should be calling the sret one

But the sret version was called. It is just that sret also expects receiver in x0 and selector in x1.

->  0x7ff738e61048 <+60>:  bl     0x7ff738e62384 ; objc_msgSend_stret
hmelder commented 6 months ago

But we cannot change this because normal struct returns (of the correct size) use x8 and not x0 for the pointer to mem.

davidchisnall commented 6 months ago

Hmm, does the Visual Studio ABI have different registers for sret C++ classes vs C ones?

hmelder commented 6 months ago

Here an extended example.

#import <Foundation/Foundation.h>

struct abc {
    long long a;
    double b;
    long long c;
};

class Bar
{
public:
    Bar(int i) : m_i(i) {}
private:
    int m_i;
};

@interface Test : NSObject
@end

@implementation Test

+ (int)foo
{
    return 42;
}

+ (struct abc)fooStruct
{
    struct abc a;

    a.a = 1;
    a.b = 2.0;
    a.c = 3;

    return a;
}

+ (Bar)bar
{
    return Bar(42);
}

@end

int main(int argc, char *argv[])
{
    auto pair = [Test bar]; // crashes
    auto fooStruct = [Test fooStruct];
    return 0;
}

Both calls use objc_msgSendSret.

  1. auto pair = [Test bar];: x0: Implicit argument, x1: Receiver, x2: Selector
  2. auto fooStruct = [Test fooStruct]: x0: Receiver, x1: Selector

    Assembly of main function ```asm main: // @main .Lfunc_begin3: .cv_func_id 4 .cv_loc 4 1 48 0 // crash.mm:48:0 .seh_proc main // %bb.0: //DEBUG_VALUE: main:argv <- $x1 //DEBUG_VALUE: main:argc <- $w0 sub sp, sp, #64 .seh_stackalloc 64 stp x19, x20, [sp, #32] // 16-byte Folded Spill .seh_save_regp x19, 32 str x30, [sp, #48] // 8-byte Folded Spill .seh_save_reg x30, 48 .seh_endprologue mov x0, #44 // =0x2c .Ltmp6: //DEBUG_VALUE: main:argc <- [DW_OP_LLVM_entry_value 1] $w0 .cv_loc 4 1 49 0 // crash.mm:49:0 adrp x19, __imp_NSLog movk x0, #57344, lsl #16 ldr x19, [x19, :lo12:__imp_NSLog] movk x0, #26317, lsl #32 movk x0, #37271, lsl #48 blr x19 .Ltmp7: //DEBUG_VALUE: main:argv <- [DW_OP_LLVM_entry_value 1] $x1 .cv_loc 4 1 51 0 // crash.mm:51:0 adrp x20, ($_OBJC_REF_CLASS_Test) adrp x2, ".objc_selector_bar_{Bari}16@0:8" add x2, x2, :lo12:".objc_selector_bar_{Bari}16@0:8" ldr x1, [x20, :lo12:($_OBJC_REF_CLASS_Test)] add x0, sp, #60 bl objc_msgSend_stret .cv_loc 4 1 52 0 // crash.mm:52:0 ldr x0, [x20, :lo12:($_OBJC_REF_CLASS_Test)] adrp x1, ".objc_selector_fooStruct_{abcqdq}16@0:8" add x1, x1, :lo12:".objc_selector_fooStruct_{abcqdq}16@0:8" add x8, sp, #8 bl objc_msgSend_stret mov x0, #32828 // =0x803c movk x0, #48377, lsl #16 movk x0, #7740, lsl #32 movk x0, #42967, lsl #48 .cv_loc 4 1 54 0 // crash.mm:54:0 blr x19 .cv_loc 4 1 56 0 // crash.mm:56:0 mov w0, wzr .seh_startepilogue ldr x30, [sp, #48] // 8-byte Folded Reload .seh_save_reg x30, 48 ldp x19, x20, [sp, #32] // 16-byte Folded Reload .seh_save_regp x19, 32 add sp, sp, #64 .seh_stackalloc 64 .seh_endepilogue ret ```
davidchisnall commented 6 months ago

Hmm, from the assembly dump, it looks like the call frame is set up how I'd expect from the old version of the ABI (small struct returned in registers), but it's calling the sret version.

Clang tries to determine whether the sret pointer is used here, which should match whether it emits the sret argument. Can you show me the LLVM IR for the main function here?

hmelder commented 6 months ago

Clang tries to determine whether the sret pointer is used here, which should match whether it emits the sret argument.

Thank you :)

Can you show me the LLVM IR for the main function here

Sure!

LLVM IR of Extended Example ``` ; ModuleID = 'crash.mm' source_filename = "crash.mm" target datalayout = "e-m:w-p:64:64-i32:32-i64:64-i128:128-n32:64-S128" target triple = "aarch64-pc-windows-msvc19.40.33721" %.objc_section_sentinel = type <{}> %struct.abc = type { i64, double, i64 } %class.Bar = type { i32 } $.objcv2_load_function = comdat any $.objc_sel_name_foo = comdat any $".objc_sel_types_i16@0:8" = comdat any $".objc_selector_foo_i16@0:8" = comdat any $.objc_sel_name_fooStruct = comdat any $".objc_sel_types_{abc\02qdq}16@0:8" = comdat any $".objc_selector_fooStruct_{abc\02qdq}16@0:8" = comdat any $.objc_sel_name_bar = comdat any $".objc_sel_types_{Bar\02i}16@0:8" = comdat any $".objc_selector_bar_{Bar\02i}16@0: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 @0 = private unnamed_addr constant [5 x i8] c"Test\00", align 1 @.objc_sel_name_foo = linkonce_odr hidden constant [4 x i8] c"foo\00", comdat @".objc_sel_types_i16@0:8" = linkonce_odr hidden constant [8 x i8] c"i16@0:8\00", comdat @".objc_selector_foo_i16@0:8" = linkonce_odr hidden global { ptr, ptr } { ptr @.objc_sel_name_foo, ptr @".objc_sel_types_i16@0:8" }, section ".objcrt$SEL$m", comdat, align 8 @1 = private unnamed_addr constant [8 x i8] c"i16@0:8\00", align 1 @.objc_sel_name_fooStruct = linkonce_odr hidden constant [10 x i8] c"fooStruct\00", comdat @".objc_sel_types_{abc\02qdq}16@0:8" = linkonce_odr hidden constant [16 x i8] c"{abc=qdq}16@0:8\00", comdat @".objc_selector_fooStruct_{abc\02qdq}16@0:8" = linkonce_odr hidden global { ptr, ptr } { ptr @.objc_sel_name_fooStruct, ptr @".objc_sel_types_{abc\02qdq}16@0:8" }, section ".objcrt$SEL$m", comdat, align 8 @2 = private unnamed_addr constant [16 x i8] c"{abc=qdq}16@0:8\00", align 1 @.objc_sel_name_bar = linkonce_odr hidden constant [4 x i8] c"bar\00", comdat @".objc_sel_types_{Bar\02i}16@0:8" = linkonce_odr hidden constant [14 x i8] c"{Bar=i}16@0:8\00", comdat @".objc_selector_bar_{Bar\02i}16@0:8" = linkonce_odr hidden global { ptr, ptr } { ptr @.objc_sel_name_bar, ptr @".objc_sel_types_{Bar\02i}16@0:8" }, section ".objcrt$SEL$m", comdat, align 8 @3 = private unnamed_addr constant [14 x i8] c"{Bar=i}16@0:8\00", align 1 @.objc_method_list = internal global { ptr, i32, i64, [3 x { ptr, ptr, ptr }] } { ptr null, i32 3, i64 24, [3 x { ptr, ptr, ptr }] [{ ptr, ptr, ptr } { ptr @_c_Test__foo, ptr @".objc_selector_foo_i16@0:8", ptr @1 }, { ptr, ptr, ptr } { ptr @_c_Test__fooStruct, ptr @".objc_selector_fooStruct_{abc\02qdq}16@0:8", ptr @2 }, { ptr, ptr, ptr } { ptr @_c_Test__bar, ptr @".objc_selector_bar_{Bar\02i}16@0:8", ptr @3 }] }, align 8 @"$_OBJC_METACLASS_Test" = internal global { ptr, ptr, ptr, i32, i32, i32, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, i32, ptr } { ptr null, ptr null, ptr @0, i32 0, i32 1, i32 0, ptr null, ptr @.objc_method_list, ptr null, ptr null, ptr null, ptr null, ptr null, ptr null, ptr null, i32 0, ptr null }, align 8 @"$_OBJC_CLASS_NSObject" = external dllimport global ptr @"$_OBJC_CLASS_Test" = global { ptr, ptr, ptr, i32, i32, i32, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, i32, ptr } { ptr @"$_OBJC_METACLASS_Test", ptr null, ptr @0, i32 0, i32 0, i32 0, ptr null, ptr null, ptr null, ptr null, ptr null, ptr null, ptr null, ptr null, ptr null, i32 0, ptr null }, align 8 @"$_OBJC_REF_CLASS_Test" = local_unnamed_addr global ptr @"$_OBJC_CLASS_Test", section ".objcrt$CLR$m" @"$_OBJC_INIT_CLASS_Test" = global ptr @"$_OBJC_CLASS_Test", section ".objcrt$CLS$m" @"__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 @.objc_early_init_ptr = internal constant ptr @.objc_early_init, section ".CRT$XCLb" @llvm.used = appending global [3 x ptr] [ptr @"$_OBJC_INIT_CLASS_Test", ptr @.objc_ctor, ptr @.objc_early_init_ptr], section "llvm.metadata" @llvm.compiler.used = appending global [1 x ptr] [ptr @.objcv2_load_function], section "llvm.metadata" ; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) uwtable define internal noundef i32 @_c_Test__foo(ptr nocapture readnone %0, ptr nocapture readnone %1) #0 !dbg !36 { tail call void @llvm.dbg.value(metadata ptr poison, metadata !44, metadata !DIExpression()), !dbg !47 tail call void @llvm.dbg.value(metadata ptr poison, metadata !46, metadata !DIExpression()), !dbg !47 ret i32 42, !dbg !48 } ; Function Attrs: mustprogress nocallback nofree nosync nounwind speculatable willreturn memory(none) declare void @llvm.dbg.declare(metadata, metadata, metadata) #1 ; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write) uwtable define internal void @_c_Test__fooStruct(ptr dead_on_unwind noalias nocapture writable writeonly sret(%struct.abc) align 8 %0, ptr nocapture readnone %1, ptr nocapture readnone %2) #2 !dbg !49 { tail call void @llvm.dbg.value(metadata ptr poison, metadata !60, metadata !DIExpression()), !dbg !63 tail call void @llvm.dbg.value(metadata ptr poison, metadata !61, metadata !DIExpression()), !dbg !63 call void @llvm.dbg.declare(metadata ptr %0, metadata !62, metadata !DIExpression()), !dbg !64 store i64 1, ptr %0, align 8, !dbg !65 %4 = getelementptr inbounds %struct.abc, ptr %0, i64 0, i32 1, !dbg !66 store double 2.000000e+00, ptr %4, align 8, !dbg !66 %5 = getelementptr inbounds %struct.abc, ptr %0, i64 0, i32 2, !dbg !67 store i64 3, ptr %5, align 8, !dbg !67 ret void, !dbg !68 } ; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write) uwtable define internal void @_c_Test__bar(ptr dead_on_unwind inreg noalias nocapture writable writeonly sret(%class.Bar) align 4 %0, ptr nocapture readnone %1, ptr nocapture readnone %2) #2 !dbg !69 { tail call void @llvm.dbg.value(metadata ptr poison, metadata !73, metadata !DIExpression()), !dbg !75 tail call void @llvm.dbg.value(metadata ptr poison, metadata !74, metadata !DIExpression()), !dbg !75 tail call void @llvm.dbg.value(metadata i32 42, metadata !76, metadata !DIExpression()), !dbg !81 tail call void @llvm.dbg.value(metadata ptr %0, metadata !79, metadata !DIExpression()), !dbg !81 store i32 42, ptr %0, align 4, !dbg !83 ret void, !dbg !84 } ; Function Attrs: mustprogress norecurse uwtable define dso_local noundef i32 @main(i32 noundef %0, ptr nocapture noundef readnone %1) local_unnamed_addr #3 !dbg !85 { %3 = alloca %class.Bar, align 4, !DIAssignID !96 call void @llvm.dbg.assign(metadata i1 undef, metadata !94, metadata !DIExpression(), metadata !96, metadata ptr %3, metadata !DIExpression()), !dbg !97 %4 = alloca %struct.abc, align 8, !DIAssignID !98 call void @llvm.dbg.assign(metadata i1 undef, metadata !95, metadata !DIExpression(), metadata !98, metadata ptr %4, metadata !DIExpression()), !dbg !97 tail call void @llvm.dbg.value(metadata ptr %1, metadata !92, metadata !DIExpression()), !dbg !97 tail call void @llvm.dbg.value(metadata i32 %0, metadata !93, metadata !DIExpression()), !dbg !97 tail call void (ptr, ...) @NSLog(ptr noundef nonnull inttoptr (i64 -7955777182314266580 to ptr)), !dbg !99 call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %3) #8, !dbg !100 %5 = load ptr, ptr @"$_OBJC_REF_CLASS_Test", align 8, !dbg !100 call void @objc_msgSend_stret(ptr dead_on_unwind inreg nonnull writable sret(%class.Bar) align 4 %3, ptr noundef %5, ptr noundef nonnull @".objc_selector_bar_{Bar\02i}16@0:8"), !dbg !100, !GNUObjCMessageSend !101 call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %4) #8, !dbg !102 %6 = load ptr, ptr @"$_OBJC_REF_CLASS_Test", align 8, !dbg !102 call void @objc_msgSend_stret(ptr dead_on_unwind nonnull writable sret(%struct.abc) align 8 %4, ptr noundef %6, ptr noundef nonnull @".objc_selector_fooStruct_{abc\02qdq}16@0:8"), !dbg !102, !GNUObjCMessageSend !103 call void (ptr, ...) @NSLog(ptr noundef nonnull inttoptr (i64 -6352575503165456324 to ptr)), !dbg !104 call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %4) #8, !dbg !105 call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %3) #8, !dbg !105 ret i32 0, !dbg !106 } declare dllimport void @NSLog(ptr noundef, ...) local_unnamed_addr #4 ; Function Attrs: mustprogress nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #5 declare dso_local ptr @objc_msgSend_stret(ptr, ...) local_unnamed_addr ; Function Attrs: mustprogress nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #5 define linkonce_odr hidden void @.objcv2_load_function() comdat { tail call void @__objc_load(ptr nonnull @.objc_init) ret void } declare dso_local void @__objc_load(ptr) local_unnamed_addr ; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(write, argmem: none, inaccessiblemem: none) define internal void @.objc_early_init() #6 { store ptr @"$_OBJC_CLASS_NSObject", ptr getelementptr inbounds ({ ptr, ptr, ptr, i32, i32, i32, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, i32, ptr }, ptr @"$_OBJC_CLASS_Test", i64 0, i32 1), align 8 ret void } ; Function Attrs: mustprogress nocallback nofree nosync nounwind speculatable willreturn memory(none) declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #1 ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) declare void @llvm.dbg.value(metadata, metadata, metadata) #7 attributes #0 = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) uwtable "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+fp-armv8,+neon,+v8a,-fmv" } attributes #1 = { mustprogress nocallback nofree nosync nounwind speculatable willreturn memory(none) } attributes #2 = { mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write) uwtable "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+fp-armv8,+neon,+v8a,-fmv" } attributes #3 = { mustprogress norecurse uwtable "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+fp-armv8,+neon,+v8a,-fmv" } attributes #4 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+fp-armv8,+neon,+v8a,-fmv" } attributes #5 = { mustprogress nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } attributes #6 = { mustprogress nofree norecurse nosync nounwind willreturn memory(write, argmem: none, inaccessiblemem: none) } attributes #7 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } attributes #8 = { nounwind } !llvm.dbg.cu = !{!0} !llvm.linker.options = !{!27, !27, !28} !llvm.module.flags = !{!29, !30, !31, !32, !33, !34} !llvm.ident = !{!35} !0 = distinct !DICompileUnit(language: DW_LANG_ObjC_plus_plus, file: !1, producer: "clang version 18.1.3", isOptimized: true, runtimeVersion: 2, emissionKind: FullDebug, retainedTypes: !2, imports: !21, splitDebugInlining: false, nameTableKind: None) !1 = !DIFile(filename: "crash.mm", directory: "C:\\Users\\hugo\\Documents\\stdPairReturn", checksumkind: CSK_MD5, checksum: "5d3aaac556c23b3eb6aac380e3de5607") !2 = !{!3, !11} !3 = distinct !DICompositeType(tag: DW_TAG_class_type, name: "Bar", file: !1, line: 9, size: 32, flags: DIFlagTypePassByValue | DIFlagNonTrivial, elements: !4, identifier: ".?AVBar@@") !4 = !{!5, !7} !5 = !DIDerivedType(tag: DW_TAG_member, name: "m_i", scope: !3, file: !1, line: 14, baseType: !6, size: 32) !6 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) !7 = !DISubprogram(name: "Bar", scope: !3, file: !1, line: 12, type: !8, scopeLine: 12, flags: DIFlagPublic | DIFlagPrototyped, spFlags: DISPFlagOptimized) !8 = !DISubroutineType(types: !9) !9 = !{null, !10, !6} !10 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !3, size: 64, flags: DIFlagArtificial | DIFlagObjectPointer) !11 = !DICompositeType(tag: DW_TAG_structure_type, name: "Test", scope: !1, file: !1, line: 17, size: 64, flags: DIFlagObjcClassComplete, elements: !12, runtimeLang: DW_LANG_ObjC_plus_plus) !12 = !{!13} !13 = !DIDerivedType(tag: DW_TAG_inheritance, scope: !11, baseType: !14, extraData: i32 0) !14 = !DICompositeType(tag: DW_TAG_structure_type, name: "NSObject", scope: !1, file: !15, line: 306, size: 64, elements: !16, runtimeLang: DW_LANG_ObjC_plus_plus) !15 = !DIFile(filename: "C:/GNUstep/arm64/Release/include\\Foundation/NSObject.h", directory: "", checksumkind: CSK_MD5, checksum: "17f181ea164e0c0c06450ad9e9895a8e") !16 = !{!17} !17 = !DIDerivedType(tag: DW_TAG_member, name: "isa", scope: !15, file: !15, line: 313, baseType: !18, size: 64, flags: DIFlagProtected) !18 = !DIDerivedType(tag: DW_TAG_typedef, name: "Class", file: !1, baseType: !19) !19 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !20, size: 64) !20 = !DICompositeType(tag: DW_TAG_structure_type, name: "objc_class", file: !1, flags: DIFlagFwdDecl) !21 = !{!22} !22 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !0, entity: !23, file: !24, line: 23) !23 = !DIDerivedType(tag: DW_TAG_typedef, name: "nullptr_t", scope: !25, file: !24, line: 21, baseType: !26) !24 = !DIFile(filename: "C:\\Program Files\\LLVM\\lib\\clang\\18\\include\\__stddef_nullptr_t.h", directory: "", checksumkind: CSK_MD5, checksum: "fcdf9e85141bd5a6c72fdd46606ac8b9") !25 = !DINamespace(name: "std", scope: null) !26 = !DIBasicType(tag: DW_TAG_unspecified_type, name: "decltype(nullptr)") !27 = !{!"/DEFAULTLIB:uuid.lib"} !28 = !{!"/FAILIFMISMATCH:\22_CRT_STDIO_ISO_WIDE_SPECIFIERS=0\22"} !29 = !{i32 2, !"CodeView", i32 1} !30 = !{i32 2, !"Debug Info Version", i32 3} !31 = !{i32 1, !"wchar_size", i32 2} !32 = !{i32 8, !"PIC Level", i32 2} !33 = !{i32 7, !"uwtable", i32 2} !34 = !{i32 7, !"debug-info-assignment-tracking", i1 true} !35 = !{!"clang version 18.1.3"} !36 = distinct !DISubprogram(name: "+[Test foo]", scope: !1, file: !1, line: 22, type: !37, scopeLine: 22, flags: DIFlagPrototyped, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !43) !37 = !DISubroutineType(types: !38) !38 = !{!6, !39, !40} !39 = !DIDerivedType(tag: DW_TAG_typedef, name: "Class", file: !1, baseType: !19, flags: DIFlagArtificial | DIFlagObjectPointer) !40 = !DIDerivedType(tag: DW_TAG_typedef, name: "SEL", file: !1, baseType: !41, flags: DIFlagArtificial) !41 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !42, size: 64) !42 = !DICompositeType(tag: DW_TAG_structure_type, name: "objc_selector", file: !1, flags: DIFlagFwdDecl) !43 = !{!44, !46} !44 = !DILocalVariable(name: "_cmd", arg: 2, scope: !36, type: !45, flags: DIFlagArtificial) !45 = !DIDerivedType(tag: DW_TAG_typedef, name: "SEL", file: !1, baseType: !41) !46 = !DILocalVariable(name: "self", arg: 1, scope: !36, type: !18, flags: DIFlagArtificial | DIFlagObjectPointer) !47 = !DILocation(line: 0, scope: !36) !48 = !DILocation(line: 24, scope: !36) !49 = distinct !DISubprogram(name: "+[Test fooStruct]", scope: !1, file: !1, line: 27, type: !50, scopeLine: 27, flags: DIFlagPrototyped, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !59) !50 = !DISubroutineType(types: !51) !51 = !{!52, !39, !40} !52 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "abc", file: !1, line: 3, size: 192, flags: DIFlagTypePassByValue, elements: !53, identifier: ".?AUabc@@") !53 = !{!54, !56, !58} !54 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !52, file: !1, line: 4, baseType: !55, size: 64) !55 = !DIBasicType(name: "long long", size: 64, encoding: DW_ATE_signed) !56 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !52, file: !1, line: 5, baseType: !57, size: 64, offset: 64) !57 = !DIBasicType(name: "double", size: 64, encoding: DW_ATE_float) !58 = !DIDerivedType(tag: DW_TAG_member, name: "c", scope: !52, file: !1, line: 6, baseType: !55, size: 64, offset: 128) !59 = !{!60, !61, !62} !60 = !DILocalVariable(name: "_cmd", arg: 2, scope: !49, type: !45, flags: DIFlagArtificial) !61 = !DILocalVariable(name: "self", arg: 1, scope: !49, type: !18, flags: DIFlagArtificial | DIFlagObjectPointer) !62 = !DILocalVariable(name: "a", scope: !49, file: !1, line: 29, type: !52) !63 = !DILocation(line: 0, scope: !49) !64 = !DILocation(line: 29, scope: !49) !65 = !DILocation(line: 31, scope: !49) !66 = !DILocation(line: 32, scope: !49) !67 = !DILocation(line: 33, scope: !49) !68 = !DILocation(line: 35, scope: !49) !69 = distinct !DISubprogram(name: "+[Test bar]", scope: !1, file: !1, line: 38, type: !70, scopeLine: 38, flags: DIFlagPrototyped, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !72) !70 = !DISubroutineType(types: !71) !71 = !{!3, !39, !40} !72 = !{!73, !74} !73 = !DILocalVariable(name: "_cmd", arg: 2, scope: !69, type: !45, flags: DIFlagArtificial) !74 = !DILocalVariable(name: "self", arg: 1, scope: !69, type: !18, flags: DIFlagArtificial | DIFlagObjectPointer) !75 = !DILocation(line: 0, scope: !69) !76 = !DILocalVariable(name: "i", arg: 2, scope: !77, file: !1, line: 12, type: !6) !77 = distinct !DISubprogram(name: "Bar", linkageName: "??0Bar@@QEAA@H@Z", scope: !3, file: !1, line: 12, type: !8, scopeLine: 12, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, declaration: !7, retainedNodes: !78) !78 = !{!76, !79} !79 = !DILocalVariable(name: "this", arg: 1, scope: !77, type: !80, flags: DIFlagArtificial | DIFlagObjectPointer) !80 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !3, size: 64) !81 = !DILocation(line: 0, scope: !77, inlinedAt: !82) !82 = distinct !DILocation(line: 40, scope: !69) !83 = !DILocation(line: 12, scope: !77, inlinedAt: !82) !84 = !DILocation(line: 40, scope: !69) !85 = distinct !DISubprogram(name: "main", scope: !1, file: !1, line: 47, type: !86, scopeLine: 48, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !91) !86 = !DISubroutineType(types: !87) !87 = !{!6, !6, !88} !88 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !89, size: 64) !89 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !90, size: 64) !90 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char) !91 = !{!92, !93, !94, !95} !92 = !DILocalVariable(name: "argv", arg: 2, scope: !85, file: !1, line: 47, type: !88) !93 = !DILocalVariable(name: "argc", arg: 1, scope: !85, file: !1, line: 47, type: !6) !94 = !DILocalVariable(name: "pair", scope: !85, file: !1, line: 51, type: !3) !95 = !DILocalVariable(name: "fooStruct", scope: !85, file: !1, line: 52, type: !52) !96 = distinct !DIAssignID() !97 = !DILocation(line: 0, scope: !85) !98 = distinct !DIAssignID() !99 = !DILocation(line: 49, scope: !85) !100 = !DILocation(line: 51, scope: !85) !101 = !{!"bar", !"Test", i1 true} !102 = !DILocation(line: 52, scope: !85) !103 = !{!"fooStruct", !"Test", i1 true} !104 = !DILocation(line: 54, scope: !85) !105 = !DILocation(line: 57, scope: !85) !106 = !DILocation(line: 56, scope: !85) ```
davidchisnall commented 6 months ago

That’s weird. Both of the calls have an sret parameter emitted by the front end, but somehow the second one isn’t using it in the back end?

hmelder commented 6 months ago

Maybe when mapping the parameters to registers, the Visual Studio ABI uses x8 for normal structs and x0 for C++ sret?

davidchisnall commented 6 months ago

I guess we may need another variant of the message send for that calling convention.

efriedma-quic commented 6 months ago

See https://reviews.llvm.org/D60348

hmelder commented 6 months ago

For return-by-value that cannot be passed via registers, the caller shall reserve a block of memory of sufficient size and alignment to hold the result. The address of the memory block shall be passed as an additional argument to the function in x8 for POD type, or in x0 (or x1 if $this is passed in x0) for non-POD type.

I guess we do not need to care about this: x1 if $this is passed in x0, and only the case where non-POD (POD meaning plain old data) types are returned in x0.

  // In AAPCS, an SRet is passed in X8, not X0 like a normal pointer parameter.
  // However, on windows, in some circumstances, the SRet is passed in X0 or X1
  // instead.  The presence of the inreg attribute indicates that SRet is
  // passed in the alternative register (X0 or X1), not X8:
  // - X0 for non-instance methods.
  // - X1 for instance methods.

  // The "sret" attribute identifies indirect returns.
  // The "inreg" attribute identifies non-aggregate types.
  // The position of the "sret" attribute identifies instance/non-instance
  // methods.
  // "sret" on argument 0 means non-instance methods.
  // "sret" on argument 1 means instance methods.

This means after detecting sret here, we additionally check if the inreg attribute is set on argument 0. If set, a new msgSend function (objc_msgSendSret2_np, objc_msgSendSretCXX_np, or perhaps objc_msgSendSretInreg_np?) with different parameter mapping is used instead.

Thank you @efriedma-quic for the link. It was very helpful!

hmelder commented 6 months ago

I am now working on a patch for CodeGen and libobjc2

davidchisnall commented 6 months ago

I don't think you need to patch libobjc2. We don't touch x8 except to spill and reload it when calling C), so it should be fine to use the non-stret version for calls that treat x8 as (effectively) an argument register.

We need to make sure that, in CodeGen, we treat both as structure returns for the purpose of zeroing the return in the caller. It doesn't matter that objc_msgSend zeroes the normal return registers, they're caller-save anyway.

hmelder commented 6 months ago

so it should be fine to use the non-stret version for calls that treat x8 as (effectively) an argument register.

That would mean we still need to modify the objc_msgSendStret, as it currently expects receiver to be in x0 and selector in x1.

hmelder commented 6 months ago

And this in turn would break behaviour when using previous clang versions. I think there is no other way around then to just have a second stret msgSend.

efriedma-quic commented 6 months ago

Do you think this makes sense to cherry-pick to 18.x?

davidchisnall commented 6 months ago

Probably nice to have. We’ll do a new point release of the runtime to include the new function.

triplef commented 6 months ago

For us it would be helpful to have an official Clang 18 release with this fix and also #86384, so cherry-picking to 18.x would be appreciated if possible.

efriedma-quic commented 6 months ago

/cherry-pick 3dcd2cc

llvmbot commented 6 months ago

/pull-request llvm/llvm-project#90176

llvmbot commented 6 months ago

@llvm/issue-subscribers-clang-codegen

Author: Hugo Melder (hmelder)

The libobjc2 `objc_msgSend` implementation for aarch64 expects the receiver and selector to be passed in x0 and x1 respectively. On Windows ARM64, the pointer to the (uninitialised) instance of the C++ Class is assigned to x0, shifting receiver and selector into x1 and x2. This results in a crash. This does not happen on Ubuntu aarch64, as seen in the lldb snippet below. I am using the GNUstep Windows SDK from https://github.com/gnustep/tools-windows-msvc. Here is the test code: ```objc++ #import <Foundation/Foundation.h> class Bar { public: Bar(int i) : m_i(i) {} private: int m_i; }; @interface Test : NSObject @end @implementation Test + (Bar)bar { return Bar(42); } @end int main(int argc, char *argv[]) { NSLog(@"Hello"); auto pair = [Test bar]; // crashes NSLog(@"Success"); return 0; } ``` ## Windows 11 ARM64 Machine Information ```sh WindowsBuildLabEx: 22621.1.arm64fre.ni_release.220506-1250 WindowsProductName: Windows 10 Pro OSDisplayVersion: 23H2 WindowsKit: 10.0.22621.0 ``` Clang Version ```sh clang version 18.1.3 Target: aarch64-pc-windows-msvc Thread model: posix InstalledDir: C:\Program Files\LLVM\bin ``` Build Command (Inside MSYS2) ```sh clang crash.mm -o crash.exe -g -gcodeview `gnustep-config --objc-flags` `gnustep-config --base-libs` -Xclang -fobjc-dispatch-method=non-legacy ``` LLDB ```gdb Process 6408 stopped * thread #1, stop reason = breakpoint 2.1 frame #0: 0x00007ff738e61048 crash.exe`main(argc=867171612, argv=0x00007ff738e640b0) at crash.mm:27 24 { 25 NSLog(@"Hello"); 26 -> 27 auto pair = [Test bar]; // crashes 28 29 NSLog(@"Success"); 30 (lldb) di crash.exe`main: 0x7ff738e6100c <+0>: sub sp, sp, #0x20 0x7ff738e61010 <+4>: str x19, [sp, #0x10] 0x7ff738e61014 <+8>: str x30, [sp, #0x18] 0x7ff738e61018 <+12>: mov x0, #0x2c ; =44 0x7ff738e6101c <+16>: adrp x19, 2 0x7ff738e61020 <+20>: movk x0, #0xe000, lsl #16 0x7ff738e61024 <+24>: ldr x19, [x19, #0x5e8] 0x7ff738e61028 <+28>: movk x0, #0x66cd, lsl #32 0x7ff738e6102c <+32>: movk x0, #0x9197, lsl #48 0x7ff738e61030 <+36>: blr x19 0x7ff738e61034 <+40>: adrp x8, 5 0x7ff738e61038 <+44>: adrp x2, 5 0x7ff738e6103c <+48>: add x2, x2, #0x10 ; __start_.objcrt$PCR 0x7ff738e61040 <+52>: ldr x1, [x8] 0x7ff738e61044 <+56>: add x0, sp, #0xc -> 0x7ff738e61048 <+60>: bl 0x7ff738e62384 ; objc_msgSend_stret 0x7ff738e6104c <+64>: mov x0, #0x803c ; =32828 0x7ff738e61050 <+68>: movk x0, #0xbcf9, lsl #16 0x7ff738e61054 <+72>: movk x0, #0x1e3c, lsl #32 0x7ff738e61058 <+76>: movk x0, #0xa7d7, lsl #48 0x7ff738e6105c <+80>: blr x19 0x7ff738e61060 <+84>: mov w0, wzr 0x7ff738e61064 <+88>: ldr x30, [sp, #0x18] 0x7ff738e61068 <+92>: ldr x19, [sp, #0x10] 0x7ff738e6106c <+96>: add sp, sp, #0x20 0x7ff738e61070 <+100>: ret (lldb) register read General Purpose Registers: x0 = 0x000000e233affd1c x1 = 0x00007ff738e640b0 $_OBJC_CLASS_Test x2 = 0x00007ff738e66010 __start_.objcrt$PCR x3 = 0x0000000010000000 x4 = 0x0000000000000150 x5 = 0x000000006d9d3bcf x6 = 0x00007ff8b4129000 NlsAnsiCodePage + 26720 x7 = 0x5d4b1dcd6d097720 x8 = 0x00007ff738e66000 __start_.objcrt$CAL x9 = 0x0000000000000000 x10 = 0x000000007ffe0380 x11 = 0x0000000000000000 x12 = 0x0000000000000000 x13 = 0xa2e64eada2e64ead x14 = 0x0000000000000001 x15 = 0x0000000000000070 x16 = 0x0000000080000001 x17 = 0x00005859193355a3 x18 = 0x000000e233970000 x19 = 0x00007ff857942570 gnustep-base-1_29.dll`NSLog at NSLog.m:293 x20 = 0x000002a841621080 x21 = 0x0000000000000000 x22 = 0x0000000000000000 x23 = 0x0000000000000000 x24 = 0x0000000000000000 x25 = 0x0000000000000000 x26 = 0x0000000000000000 x27 = 0x0000000000000000 x28 = 0x0000000000000000 fp = 0x000000e233affd30 lr = 0x00007ff738e61034 crash.exe`main + 40 at crash.mm:27 sp = 0x000000e233affd10 pc = 0x00007ff738e61048 crash.exe`main + 60 at crash.mm:27 cpsr = 0x80000000 (lldb) ``` ## Ubuntu 23.10 aarch64 Clang Version ```sh Ubuntu clang version 18.1.3 (++20240322073236+ef6d1ec07c69-1~exp1~20240322193248.98) Target: aarch64-unknown-linux-gnu Thread model: posix InstalledDir: /usr/bin ``` Build Command ```sh clang-18 ObjcCXXObjectReturnTest.mm -o ObjcCXXObjectReturnTest -g `gnustep-config --objc-flags` `gnustep-config --base-libs` -Xclang -fobjc-dispatch-method=non-legacy -fuse-ld=lld-18 ``` ```gdb * thread #1, name = 'ObjcCXXObjectRe', stop reason = breakpoint 2.1 frame #0: 0x0000aaaaaaab0dcc ObjcCXXObjectReturnTest`main(argc=<unavailable>, argv=<unavailable>) at ObjcCXXObjectReturnTest.mm:27:14 24 { 25 NSLog(@"Hello"); 26 -> 27 auto pair = [Test bar]; // crashes 28 29 NSLog(@"Success"); 30 (lldb) di ObjcCXXObjectReturnTest`main: 0xaaaaaaab0d9c <+0>: stp x29, x30, [sp, #-0x10]! 0xaaaaaaab0da0 <+4>: mov x29, sp 0xaaaaaaab0da4 <+8>: mov x0, #0x2c ; =44 0xaaaaaaab0da8 <+12>: movk x0, #0xe000, lsl #16 0xaaaaaaab0dac <+16>: movk x0, #0x66cd, lsl #32 0xaaaaaaab0db0 <+20>: movk x0, #0x9197, lsl #48 0xaaaaaaab0db4 <+24>: bl 0xaaaaaaab0e90 ; symbol stub for: NSLog 0xaaaaaaab0db8 <+28>: adrp x8, 17 0xaaaaaaab0dbc <+32>: nop 0xaaaaaaab0dc0 <+36>: adr x1, 0xaaaaaaad12b0 ; ObjcCXXObjectReturnTest.PT_LOAD[3].__objc_selectors + 0 0xaaaaaaab0dc4 <+40>: ldr x8, [x8, #0xd0] 0xaaaaaaab0dc8 <+44>: ldr x0, [x8] -> 0xaaaaaaab0dcc <+48>: bl 0xaaaaaaab0ea0 ; symbol stub for: objc_msgSend 0xaaaaaaab0dd0 <+52>: mov x0, #0x803c ; =32828 0xaaaaaaab0dd4 <+56>: movk x0, #0xbcf9, lsl #16 0xaaaaaaab0dd8 <+60>: movk x0, #0x1e3c, lsl #32 0xaaaaaaab0ddc <+64>: movk x0, #0xa7d7, lsl #48 0xaaaaaaab0de0 <+68>: bl 0xaaaaaaab0e90 ; symbol stub for: NSLog 0xaaaaaaab0de4 <+72>: mov w0, wzr 0xaaaaaaab0de8 <+76>: ldp x29, x30, [sp], #0x10 0xaaaaaaab0dec <+80>: ret (lldb) register read General Purpose Registers: x0 = 0x0000aaaaaaad11a0 ._OBJC_CLASS_Test x1 = 0x0000aaaaaaad12b0 ObjcCXXObjectReturnTest.PT_LOAD[3].__objc_selectors + 0 x2 = 0x0000000000000007 x3 = 0x0000aaaaaaad2010 x4 = 0x0000000000000004 x5 = 0x0000aaaaaaebc7f0 x6 = 0xb3c97132ac52cb5b x7 = 0x0000fffff7e1f3a8 ._OBJC_CLASS_GSMutableString x8 = 0x0000aaaaaaad12d0 ObjcCXXObjectReturnTest`._OBJC_REF_CLASS_Test x9 = 0x0000000000000000 x10 = 0x0000fffff7f884cc libobjc.so.4.6`objc_slot_lookup_super2 + 264 x11 = 0x0000000000000003 x12 = 0x0000fffff7f87bd0 libobjc.so.4.6`objc_msg_lookup_sender + 100 x13 = 0x0000000000000003 x14 = 0x000000000042a2d5 x15 = 0x0000ffffffff8b88 x16 = 0x0000fffff7fb00d0 x17 = 0x0000fffff76d47f0 libc.so.6`free x18 = 0x0000000000000034 x19 = 0x0000fffffffff0d8 x20 = 0x0000000000000001 x21 = 0x0000aaaaaaac0ed8 x22 = 0x0000aaaaaaab0d9c ObjcCXXObjectReturnTest`main at ObjcCXXObjectReturnTest.mm:24 x23 = 0x0000fffffffff0e8 x24 = 0x0000fffff7ffdb90 ld-linux-aarch64.so.1`_rtld_global_ro x25 = 0x0000000000000000 x26 = 0x0000fffff7ffe008 _rtld_global x27 = 0x0000aaaaaaac0ed8 x28 = 0x0000000000000000 fp = 0x0000ffffffffef50 lr = 0x0000aaaaaaab0db8 ObjcCXXObjectReturnTest`main + 28 at ObjcCXXObjectReturnTest.mm:27:14 sp = 0x0000ffffffffef50 pc = 0x0000aaaaaaab0dcc ObjcCXXObjectReturnTest`main + 48 at ObjcCXXObjectReturnTest.mm:27:14 cpsr = 0x40001000 ```