Skip to content

Commit

Permalink
Windows on ARM64: Support Visual Studio ABI sret mechanism for non-tr…
Browse files Browse the repository at this point in the history
…ivial 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
  • Loading branch information
hmelder committed Apr 26, 2024
1 parent 51b9a07 commit dc031d2
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 56 deletions.
16 changes: 0 additions & 16 deletions CMake/detect_arch.c

This file was deleted.

17 changes: 2 additions & 15 deletions CMakeLists.txt
Expand Up @@ -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)
Expand Down Expand Up @@ -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()

Expand Down
6 changes: 6 additions & 0 deletions Test/CMakeLists.txt
Expand Up @@ -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.
Expand Down
148 changes: 148 additions & 0 deletions Test/objc_msgSend_WoA64.mm
@@ -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;
}
30 changes: 30 additions & 0 deletions objc/message.h
Expand Up @@ -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
Expand Down

0 comments on commit dc031d2

Please sign in to comment.