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

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

Merged
merged 9 commits into from
Apr 26, 2024
16 changes: 0 additions & 16 deletions CMake/detect_arch.c

This file was deleted.

17 changes: 2 additions & 15 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
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}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can’t remember why this was needed, are you sure it no longer is?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is still an issue when using CMake x86_64 on Windows on ARM64, as CMake reports AMD64 while using an ARM64 compiler.

The problem with this hack is that it searches for any occurrence in the output of the compiler. Other paths (like /c/Program Files/python3-arm/) are erroneously selected.

I think we should stick to the standard CMAKE_SYSTEM_PROCESSOR variable, but put a warning in README.md or INSTALL. CMake is not willing to fix this broken behaviour on Windows.


# 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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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