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

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

Closed
hmelder opened this issue Apr 10, 2024 · 24 comments

Comments

@hmelder
Copy link
Contributor

hmelder commented Apr 10, 2024

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
@EugeneZelenko EugeneZelenko added clang Clang issues not falling into any other category platform:windows objective-c++ and removed new issue labels Apr 10, 2024
@hmelder
Copy link
Contributor Author

hmelder commented Apr 10, 2024

I am not quite sure where this "remapping" happens. Probably related to #86384

@hmelder
Copy link
Contributor Author

hmelder commented Apr 10, 2024

@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
Copy link
Contributor

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
Copy link
Contributor Author

hmelder commented Apr 10, 2024

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
Copy link
Contributor Author

hmelder commented Apr 10, 2024

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

@davidchisnall
Copy link
Contributor

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

@hmelder
Copy link
Contributor Author

hmelder commented Apr 10, 2024

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
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_{Bar�i}16@0:8"
   add	x2, x2, :lo12:".objc_selector_bar_{Bar�i}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_{abc�qdq}16@0:8"
   add	x1, x1, :lo12:".objc_selector_fooStruct_{abc�qdq}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
Copy link
Contributor

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
Copy link
Contributor Author

hmelder commented Apr 10, 2024

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
Copy link
Contributor

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
Copy link
Contributor Author

hmelder commented Apr 10, 2024

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

@davidchisnall
Copy link
Contributor

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

@efriedma-quic
Copy link
Collaborator

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

@hmelder
Copy link
Contributor Author

hmelder commented Apr 10, 2024

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
Copy link
Contributor Author

hmelder commented Apr 11, 2024

I am now working on a patch for CodeGen and libobjc2

@davidchisnall
Copy link
Contributor

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
Copy link
Contributor Author

hmelder commented Apr 11, 2024

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
Copy link
Contributor Author

hmelder commented Apr 13, 2024

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.

davidchisnall pushed a commit that referenced this issue Apr 25, 2024
…8671)

Linked to gnustep/libobjc2#289.

More information can be found in issue: #88273.

My solution involves creating a new message-send function for this
calling convention when targeting MSVC. Additional information is
available in the libobjc2 pull request.

I am unsure whether we should check for a runtime version where
objc_msgSend_stret2_np is guaranteed to be present or leave it as is,
considering it remains a critical bug. What are your thoughts about this
@davidchisnall?
@efriedma-quic efriedma-quic added this to the LLVM 18.X Release milestone Apr 25, 2024
@efriedma-quic
Copy link
Collaborator

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

@davidchisnall
Copy link
Contributor

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

@triplef
Copy link
Member

triplef commented Apr 26, 2024

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
Copy link
Collaborator

/cherry-pick 3dcd2cc

llvmbot pushed a commit to llvmbot/llvm-project that referenced this issue Apr 26, 2024
…vm#88671)

Linked to gnustep/libobjc2#289.

More information can be found in issue: llvm#88273.

My solution involves creating a new message-send function for this
calling convention when targeting MSVC. Additional information is
available in the libobjc2 pull request.

I am unsure whether we should check for a runtime version where
objc_msgSend_stret2_np is guaranteed to be present or leave it as is,
considering it remains a critical bug. What are your thoughts about this
@davidchisnall?

(cherry picked from commit 3dcd2cc)
@llvmbot llvmbot closed this as completed Apr 26, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Apr 26, 2024

/pull-request #90176

@EugeneZelenko EugeneZelenko added clang:codegen release:backport and removed clang Clang issues not falling into any other category labels Apr 26, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Apr 26, 2024

@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:

#import &lt;Foundation/Foundation.h&gt;

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
-&gt; 27           auto pair = [Test bar]; // crashes
   28
   29           NSLog(@"Success");
   30
(lldb) di
crash.exe`main:
    0x7ff738e6100c &lt;+0&gt;:   sub    sp, sp, #<!-- -->0x20
    0x7ff738e61010 &lt;+4&gt;:   str    x19, [sp, #<!-- -->0x10]
    0x7ff738e61014 &lt;+8&gt;:   str    x30, [sp, #<!-- -->0x18]
    0x7ff738e61018 &lt;+12&gt;:  mov    x0, #<!-- -->0x2c ; =44
    0x7ff738e6101c &lt;+16&gt;:  adrp   x19, 2
    0x7ff738e61020 &lt;+20&gt;:  movk   x0, #<!-- -->0xe000, lsl #<!-- -->16
    0x7ff738e61024 &lt;+24&gt;:  ldr    x19, [x19, #<!-- -->0x5e8]
    0x7ff738e61028 &lt;+28&gt;:  movk   x0, #<!-- -->0x66cd, lsl #<!-- -->32
    0x7ff738e6102c &lt;+32&gt;:  movk   x0, #<!-- -->0x9197, lsl #<!-- -->48
    0x7ff738e61030 &lt;+36&gt;:  blr    x19
    0x7ff738e61034 &lt;+40&gt;:  adrp   x8, 5
    0x7ff738e61038 &lt;+44&gt;:  adrp   x2, 5
    0x7ff738e6103c &lt;+48&gt;:  add    x2, x2, #<!-- -->0x10 ; __start_.objcrt$PCR
    0x7ff738e61040 &lt;+52&gt;:  ldr    x1, [x8]
    0x7ff738e61044 &lt;+56&gt;:  add    x0, sp, #<!-- -->0xc
-&gt;  0x7ff738e61048 &lt;+60&gt;:  bl     0x7ff738e62384 ; objc_msgSend_stret
    0x7ff738e6104c &lt;+64&gt;:  mov    x0, #<!-- -->0x803c ; =32828
    0x7ff738e61050 &lt;+68&gt;:  movk   x0, #<!-- -->0xbcf9, lsl #<!-- -->16
    0x7ff738e61054 &lt;+72&gt;:  movk   x0, #<!-- -->0x1e3c, lsl #<!-- -->32
    0x7ff738e61058 &lt;+76&gt;:  movk   x0, #<!-- -->0xa7d7, lsl #<!-- -->48
    0x7ff738e6105c &lt;+80&gt;:  blr    x19
    0x7ff738e61060 &lt;+84&gt;:  mov    w0, wzr
    0x7ff738e61064 &lt;+88&gt;:  ldr    x30, [sp, #<!-- -->0x18]
    0x7ff738e61068 &lt;+92&gt;:  ldr    x19, [sp, #<!-- -->0x10]
    0x7ff738e6106c &lt;+96&gt;:  add    sp, sp, #<!-- -->0x20
    0x7ff738e61070 &lt;+100&gt;: 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=&lt;unavailable&gt;, argv=&lt;unavailable&gt;) at ObjcCXXObjectReturnTest.mm:27:14
   24  	{
   25  		NSLog(@"Hello");
   26
-&gt; 27  		auto pair = [Test bar]; // crashes
   28
   29  		NSLog(@"Success");
   30
(lldb) di
ObjcCXXObjectReturnTest`main:
    0xaaaaaaab0d9c &lt;+0&gt;:  stp    x29, x30, [sp, #-0x10]!
    0xaaaaaaab0da0 &lt;+4&gt;:  mov    x29, sp
    0xaaaaaaab0da4 &lt;+8&gt;:  mov    x0, #<!-- -->0x2c ; =44
    0xaaaaaaab0da8 &lt;+12&gt;: movk   x0, #<!-- -->0xe000, lsl #<!-- -->16
    0xaaaaaaab0dac &lt;+16&gt;: movk   x0, #<!-- -->0x66cd, lsl #<!-- -->32
    0xaaaaaaab0db0 &lt;+20&gt;: movk   x0, #<!-- -->0x9197, lsl #<!-- -->48
    0xaaaaaaab0db4 &lt;+24&gt;: bl     0xaaaaaaab0e90 ; symbol stub for: NSLog
    0xaaaaaaab0db8 &lt;+28&gt;: adrp   x8, 17
    0xaaaaaaab0dbc &lt;+32&gt;: nop
    0xaaaaaaab0dc0 &lt;+36&gt;: adr    x1, 0xaaaaaaad12b0 ; ObjcCXXObjectReturnTest.PT_LOAD[3].__objc_selectors + 0
    0xaaaaaaab0dc4 &lt;+40&gt;: ldr    x8, [x8, #<!-- -->0xd0]
    0xaaaaaaab0dc8 &lt;+44&gt;: ldr    x0, [x8]
-&gt;  0xaaaaaaab0dcc &lt;+48&gt;: bl     0xaaaaaaab0ea0 ; symbol stub for: objc_msgSend
    0xaaaaaaab0dd0 &lt;+52&gt;: mov    x0, #<!-- -->0x803c ; =32828
    0xaaaaaaab0dd4 &lt;+56&gt;: movk   x0, #<!-- -->0xbcf9, lsl #<!-- -->16
    0xaaaaaaab0dd8 &lt;+60&gt;: movk   x0, #<!-- -->0x1e3c, lsl #<!-- -->32
    0xaaaaaaab0ddc &lt;+64&gt;: movk   x0, #<!-- -->0xa7d7, lsl #<!-- -->48
    0xaaaaaaab0de0 &lt;+68&gt;: bl     0xaaaaaaab0e90 ; symbol stub for: NSLog
    0xaaaaaaab0de4 &lt;+72&gt;: mov    w0, wzr
    0xaaaaaaab0de8 &lt;+76&gt;: ldp    x29, x30, [sp], #<!-- -->0x10
    0xaaaaaaab0dec &lt;+80&gt;: 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

tstellar pushed a commit to llvmbot/llvm-project that referenced this issue Apr 29, 2024
…vm#88671)

Linked to gnustep/libobjc2#289.

More information can be found in issue: llvm#88273.

My solution involves creating a new message-send function for this
calling convention when targeting MSVC. Additional information is
available in the libobjc2 pull request.

I am unsure whether we should check for a runtime version where
objc_msgSend_stret2_np is guaranteed to be present or leave it as is,
considering it remains a critical bug. What are your thoughts about this
@davidchisnall?

(cherry picked from commit 3dcd2cc)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

No branches or pull requests

6 participants