diff --git a/CMake/detect_arch.c b/CMake/detect_arch.c deleted file mode 100644 index 03df356e..00000000 --- a/CMake/detect_arch.c +++ /dev/null @@ -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 \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index b3ad99d1..6a148058 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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($<$:-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() diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt index 7743cdc8..f2161e22 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -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. diff --git a/Test/objc_msgSend_WoA64.mm b/Test/objc_msgSend_WoA64.mm new file mode 100644 index 00000000..b2f42efd --- /dev/null +++ b/Test/objc_msgSend_WoA64.mm @@ -0,0 +1,148 @@ +#include +#include +#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; +} diff --git a/objc/message.h b/objc/message.h index d5df0c11..ee4fba1d 100644 --- a/objc/message.h +++ b/objc/message.h @@ -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 diff --git a/objc_msgSend.aarch64.S b/objc_msgSend.aarch64.S index e9e30ba3..a41cba56 100644 --- a/objc_msgSend.aarch64.S +++ b/objc_msgSend.aarch64.S @@ -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 \ No newline at end of file