Windows on ARM64: Support Visual Studio ABI sret mechanism for non-trivial data types (#289)

* Add objc_msgSend_stret2

* Guard and Export objc_msgSend_stret2

* Remove architecture hackery in CMake

* Add objc_msgSend test for WoA64

* Add doc comment for objc_msgSend_stret2
main
Hugo Melder 2 years ago committed by GitHub
parent 51b9a076f0
commit dc031d2741
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,16 +0,0 @@
// detect_arch.c
#if defined(__aarch64__)
#error aarch64
#elif defined(__arm__)
#error arm
#elif defined(__i386__)
#error i386
#elif defined(__x86_64__)
#error x86_64
#elif defined(__powerpc64__)
#error powerpc64
#elif defined(__powerpc__)
#error powerpc
#else
#error unknown
#endif

@ -41,20 +41,7 @@ if (MSVC)
set(objc_LINK_FLAGS "/DEBUG /INCREMENTAL:NO ${objc_LINK_FLAGS}")
endif()
# Get Architecture without relying on CMake
try_compile(
COMPILE_SUCCESS
${CMAKE_BINARY_DIR}
${CMAKE_SOURCE_DIR}/CMake/detect_arch.c
OUTPUT_VARIABLE COMPILE_OUTPUT
)
if(NOT COMPILE_SUCCESS)
string(REGEX MATCH "(aarch64|arm|i386|x86_64|powerpc64|powerpc|unknown)" ARCHITECTURE ${COMPILE_OUTPUT})
endif()
set(ARCHITECTURE ${ARCHITECTURE} CACHE STRING "Architecture Type")
message(STATUS "Architecture: ${ARCHITECTURE}")
message(STATUS "Architecture as detected by CMake: ${CMAKE_SYSTEM_PROCESSOR}")
# Build configuration
add_compile_definitions(GNUSTEP __OBJC_RUNTIME_INTERNAL__=1)
@ -188,7 +175,7 @@ add_compile_options($<$<STREQUAL:${CMAKE_SYSTEM_PROCESSOR},i686>:-march=i586>)
# which is used in safe caching.
# You must also update the guard in objc/runtime.h, when updating
# this macro.
if (ARCHITECTURE STREQUAL "powerpc")
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "ppcle")
add_definitions(-DNO_SAFE_CACHING)
endif()

@ -64,6 +64,12 @@ if (WIN32)
set(ENABLE_ALL_OBJC_ARC_TESTS On)
endif()
endif()
if (CMAKE_SYSTEM_PROCESSOR STREQUAL ARM64)
list(APPEND TESTS
objc_msgSend_WoA64.mm
)
endif()
else ()
# Don't run the tests that are specific to Itanium-style exceptions on
# Windows.

@ -0,0 +1,148 @@
#include <string.h>
#include <assert.h>
#include "../objc/runtime.h"
#include "../objc/hooks.h"
// Pass and return for type size <= 8 bytes.
struct S1 {
int a[2];
};
// Pass and return hfa <= 8 bytes
struct F1 {
float a[2];
};
// Pass and return type size <= 16 bytes
struct S2 {
int a[4];
};
// Pass and return for type size > 16 bytes.
struct S3 {
int a[5];
};
// Pass and return aggregate (of size < 16 bytes) with non-trivial destructor.
// Sret and inreg: Returned in x0
struct S4 {
int a[3];
~S4();
};
S4::~S4() {
}
// Pass and return an object with a user-provided constructor (passed directly,
// returned indirectly)
struct S5 {
S5();
int x;
};
S5::S5() {
x = 42;
}
Class TestCls;
#ifdef __has_attribute
#if __has_attribute(objc_root_class)
__attribute__((objc_root_class))
#endif
#endif
@interface MsgTest { id isa; } @end
@implementation MsgTest
+ (S1) smallS1 {
assert(TestCls == self);
assert(strcmp("smallS1", sel_getName(_cmd)) == 0);
S1 x;
x.a[0] = 0;
x.a[1] = 1;
return x;
}
+ (F1) smallF1 {
assert(TestCls == self);
assert(strcmp("smallF1", sel_getName(_cmd)) == 0);
F1 x;
x.a[0] = 0.2f;
x.a[1] = 0.5f;
return x;
}
+ (S2) smallS2 {
assert(TestCls == self);
assert(strcmp("smallS2", sel_getName(_cmd)) == 0);
S2 x;
for (int i = 0; i < 4; i++) {
x.a[i] = i;
}
return x;
}
+ (S3) stretS3 {
assert(TestCls == self);
assert(strcmp("stretS3", sel_getName(_cmd)) == 0);
S3 x;
for (int i = 0; i < 5; i++) {
x.a[i] = i;
}
return x;
}
+ (S4) stretInRegS4 {
assert(TestCls == self);
assert(strcmp("stretInRegS4", sel_getName(_cmd)) == 0);
S4 x;
for (int i = 0; i < 3; i++) {
x.a[i] = i;
}
return x;
}
+ (S5) stretInRegS5 {
assert(TestCls == self);
assert(strcmp("stretInRegS5", sel_getName(_cmd)) == 0);
return S5();
}
@end
int main(int argc, char *argv[]) {
#ifdef __GNUSTEP_MSGSEND__
TestCls = objc_getClass("MsgTest");
// Returned in x0
S1 ret = ((S1(*)(id, SEL))objc_msgSend)(TestCls, @selector(smallS1));
assert(ret.a[0] == 0);
assert(ret.a[1] == 1);
F1 retF1 = ((F1(*)(id, SEL))objc_msgSend)(TestCls, @selector(smallF1));
assert(retF1.a[0] == 0.2f);
assert(retF1.a[1] == 0.5f);
// Returned in x0 and x1
S2 ret2 = ((S2(*)(id, SEL))objc_msgSend)(TestCls, @selector(smallS2));
for (int i = 0; i < 4; i++) {
assert(ret2.a[i] == i);
}
// Indirect result register x8 used
S3 ret3 = ((S3(*)(id, SEL))objc_msgSend_stret)(TestCls, @selector(stretS3));
for (int i = 0; i < 5; i++) {
assert(ret3.a[i] == i);
}
// Stret with inreg. Returned in x0.
S4 ret4 = ((S4(*)(id, SEL))objc_msgSend_stret2)(TestCls, @selector(stretInRegS4));
for (int i = 0; i < 3; i++) {
assert(ret4.a[i] == i);
}
// Stret with inreg. Returned in x0.
S5 ret5 = ((S5(*)(id, SEL))objc_msgSend_stret2)(TestCls, @selector(stretInRegS5));
assert(ret5.x == 42);
return 0;
#endif // __GNUSTEP_MSGSEND__
return 77;
}

@ -55,6 +55,36 @@ id objc_msgSend_stret(id self, SEL _cmd, ...);
#else
void objc_msgSend_stret(id self, SEL _cmd, ...);
#endif
/**
* Standard message sending function. This function must be cast to the
* correct types for the function before use. The first argument is the
* receiver and the second the selector.
*
* Note that this function is only available on Windows ARM64. For a more
* portable solution to sending arbitrary messages, consider using
* objc_msg_lookup_sender() and then calling the returned IMP directly.
*
* This version of the function is used on Windows ARM64 for all messages
* that return a non-trivial data types (e.g C++ classes or structures with
* user-defined constructors) that is not returned in registers.
* Be aware that calling conventions differ between operating systems even
* within the same architecture, so take great care if using this function for
* small (two integer) structures.
*
* Why does objc_msgSend_stret2 exist?
* In AAPCS, an SRet is passed in x8, not x0 like a normal pointer parameter.
* On Windows, this is only the case for POD (plain old data) types. Non trivial
* types with constructors and destructors are passed in x0 on sret.
*/
OBJC_PUBLIC
#if defined(_WIN32) && defined(__ARM_ARCH_ISA_A64)
# ifdef __cplusplus
id objc_msgSend_stret2(id self, SEL _cmd, ...);
# else
void objc_msgSend_stret2(id self, SEL _cmd, ...);
# endif
#endif
/**
* Standard message sending function. This function must be cast to the
* correct types for the function before use. The first argument is the

@ -33,8 +33,8 @@
# define EH_START
# define EH_END
# define EH_START_AT_OFFSET .seh_proc objc_msgSend
# define EH_END_AT_OFFSET .seh_endproc objc_msgSend
# define EH_START_AT_OFFSET(x) .seh_proc x
# define EH_END_AT_OFFSET(x) .seh_endproc x
# define EH_END_PROLOGUE .seh_endprologue
# define EH_START_EPILOGUE .seh_startepilogue
@ -52,8 +52,8 @@
// The following directives are either not
// needed or not available with CFI
# define EH_START_AT_OFFSET
# define EH_END_AT_OFFSET
# define EH_START_AT_OFFSET(x)
# define EH_END_AT_OFFSET(x)
# define EH_END_PROLOGUE
# define EH_START_EPILOGUE
# define EH_END_EPILOGUE
@ -63,26 +63,18 @@
# define EH_NOP
#endif
.globl CDECL(objc_msgSend_fpret)
TYPE_DIRECTIVE(CDECL(objc_msgSend_fpret), %function)
.globl CDECL(objc_msgSend)
TYPE_DIRECTIVE(CDECL(objc_msgSend), %function)
.globl CDECL(objc_msgSend_stret)
TYPE_DIRECTIVE(CDECL(objc_msgSend_stret), %function)
CDECL(objc_msgSend):
CDECL(objc_msgSend_fpret):
CDECL(objc_msgSend_stret):
.macro MSGSEND fnname receiver, sel
EH_START
cbz x0, 4f // Skip everything if the receiver is nil
cbz \receiver, 4f // Skip everything if the receiver is nil
// Jump to 6: if this is a small object
ubfx x9, x0, #0, #SMALLOBJ_BITS
ubfx x9, \receiver, #0, #SMALLOBJ_BITS
cbnz x9, 6f
ldr x9, [x0] // Load class to x9 if not a small int
ldr x9, [\receiver] // Load class to x9 if not a small int
1:
ldr x9, [x9, #DTABLE_OFFSET] // Dtable -> x9
ldr w10, [x1] // selector->index -> x10
ldr w10, [\sel] // selector->index -> x10
ldr w11, [x9, #SHIFT_OFFSET] // dtable->shift -> x11
cmp x11, #8 // If this is a small dtable, jump to the
@ -109,12 +101,12 @@ CDECL(objc_msgSend_stret):
br x9 // Tail-call the method
4: // Nil receiver
mov x0, #0
mov v0.d[0], x0
mov v0.d[1], x0
mov \receiver, #0
mov v0.d[0], \receiver
mov v0.d[1], \receiver
br lr
5: // Slow lookup
EH_START_AT_OFFSET
EH_START_AT_OFFSET(\fnname)
// Save anything that will be clobbered by
// the call.
@ -143,7 +135,7 @@ CDECL(objc_msgSend_stret):
add fp, sp, 192 // Adjust frame pointer
EH_ADD_FP(192)
stp x0, x8, [sp, #-16]! // it's convenient if x0 is spilled at sp
stp \receiver, x8, [sp, #-16]! // it's convenient if \receiver is spilled at sp
EH_STACK_ALLOC(16) // stp performed pre-indexing by sp-16
EH_END_PROLOGUE
@ -159,7 +151,7 @@ CDECL(objc_msgSend_stret):
// the address of the receiver
mov x0, sp // &self, _cmd in arguments
mov x1, x1
mov x1, \sel
bl CDECL(slowMsgLookup) // This is the only place where the EH directives
// have to be accurate...
mov x9, x0 // IMP -> x9
@ -185,11 +177,11 @@ CDECL(objc_msgSend_stret):
EH_SAVE_FP_LR(208)
// Post-increment sp += ARGUMENT_SPILL_SIZE +16
ldp x0, x8, [sp], #(ARGUMENT_SPILL_SIZE + 16)
ldp \receiver, x8, [sp], #(ARGUMENT_SPILL_SIZE + 16)
EH_STACK_ALLOC((ARGUMENT_SPILL_SIZE + 16))
EH_END_EPILOGUE
EH_END_AT_OFFSET
EH_END_AT_OFFSET(\fnname)
br x9
6:
@ -204,8 +196,34 @@ CDECL(objc_msgSend_stret):
b 1b
EH_END
.endm
.globl CDECL(objc_msgSend_fpret)
TYPE_DIRECTIVE(CDECL(objc_msgSend_fpret), %function)
.globl CDECL(objc_msgSend)
TYPE_DIRECTIVE(CDECL(objc_msgSend), %function)
.globl CDECL(objc_msgSend_stret)
TYPE_DIRECTIVE(CDECL(objc_msgSend_stret), %function)
CDECL(objc_msgSend):
CDECL(objc_msgSend_fpret):
CDECL(objc_msgSend_stret):
MSGSEND objc_msgSend, x0, x1
/*
In AAPCS, an SRet is passed in x8, not x0 like a normal pointer parameter.
On Windows, this is only the case for POD (plain old data) types. Non trivial
types with constructors and destructors are passed in x0 on sret.
We thus need two objc_msgSend functions on Windows on ARM64 for Sret:
1. objc_msgSend_stret for POD Sret
2. objc_msgSend_stret2 for non-trivial Sret (like C++ class instances)
*/
#ifdef _WIN32
.globl CDECL(objc_msgSend_stret2)
TYPE_DIRECTIVE(CDECL(objc_msgSend_stret2), %function)
CDECL(objc_msgSend_stret2):
MSGSEND objc_msgSend_stret2, x1, x2
.text
.def objc_msgSend;
.scl 2;
@ -219,9 +237,14 @@ CDECL(objc_msgSend_stret):
.scl 2;
.type 32;
.endef
.def objc_msgSend_stret2;
.scl 2;
.type 32;
.endef
.section .drectve,"yn"
.ascii " /EXPORT:objc_msgSend"
.ascii " /EXPORT:objc_msgSend_fpret"
.ascii " /EXPORT:objc_msgSend_stret"
.ascii " /EXPORT:objc_msgSend_stret2"
#endif
Loading…
Cancel
Save