1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
#pragma once
#include "Platform.h"
#if defined(VISUAL_STUDIO) && defined(X86)
/**
* On X86 on Windows, pointers to member functions are always pointers. If they refer to a virtual
* function, they simply point to a piece of code that performs the virtual dispatch.
*/
template <class T>
inline const void *address(T fn) {
return (const void *&)fn;
}
template <class Fn>
inline Fn asMemberPtr(const void *fn) {
Fn result;
memset(&result, 0, sizeof(result));
memcpy(&result, &fn, sizeof(fn));
return result;
}
#elif defined(VISUAL_STUDIO) && defined(X64)
/**
* This is currently unexplored, but likely works as above.
*/
template <class T>
inline const void *address(T fn) {
return (const void *&)fn;
}
template <class Fn>
inline Fn asMemberPtr(const void *fn) {
Fn result;
memset(&result, 0, sizeof(result));
memcpy(&result, &fn, sizeof(fn));
return result;
}
#elif defined(GCC) && defined(POSIX) && defined(X64)
/**
* On GCC for Unix and X64, a member function pointer is actually 2 machine words. The first
* (64-bit) word is where the actual pointer is located, while the second word contains an
* offset to the vtable. Since we truncate function pointers to 1 machine word, the vtable
* pointer is unfortunatly lost. This is, however, fine in Storm since we know that the
* vtable is always located at offset 0. This is known since all objects with virtual
* function inherit from an object for which this is true, and we do not support multiple
* (virtual) inheritance.
*
* GCC abuses the function pointer a bit with regards to virtual dispatch as well. The
* function pointer is either a plain pointer to the machine code to execute (always aligned
* to at least 2 bytes) or an offset into the vtable of the object. The first case is easy:
* just call the function at the other end of the pointer. In case the function pointer is a
* vtable offset, we need to perform the vtable lookup through the object's vtable. We can
* distinguish between the two cases by examining the least significant bit of the
* pointer. If it is set (ie. if the address is odd), the pointer is actually an offset into
* the vtable +1. Otherwise it is a pointer.
*
* This was derived by examining the assembler output from GCC when compiling the file
* Experiments/membercall.cpp with 'g++ -S membercall.cpp'.
*/
template <class T>
inline const void *address(T fn) {
return reinterpret_cast<const void *>(fn);
}
template <class Fn>
inline Fn asMemberPtr(const void *fn) {
Fn to;
memset(&to, 0, sizeof(Fn));
memcpy(&to, &fn, sizeof(void *));
return to;
}
#elif defined(GCC) && defined(POSIX) && defined(ARM64)
/**
* On GCC for Unix and ARM64, a member function pointer is 2 machine words as on X64. On
* ARM, however, the first word is always the pointer (due to pointer authentication, I
* think), and the offset is tagged instead.
*
* As on X64, we can assume that the offset is zero.
*
* Since we need to keep track of whether or not pointers refer to vtable offsets, we use
* the same representation as on X64 for our void pointers (we don't use pointer
* authentication, nor THUMB instructions, so we can assume pointers are aligned).
*/
template <class T>
inline const void *address(T fn) {
struct {
size_t data;
size_t offset;
} ptr;
if (sizeof(fn) >= sizeof(ptr)) {
memcpy(&ptr, &fn, sizeof(ptr));
// Move the tag from "offset" into "data".
ptr.data |= ptr.offset & 0x1;
} else {
memcpy(&ptr, &fn, sizeof(fn));
// No tag in this case, just a regular pointer.
}
return reinterpret_cast<const void *>(ptr.data);
}
template <class Fn>
inline Fn asMemberPtr(const void *fn) {
struct {
size_t data;
size_t offset;
} ptr;
// Extract the tag back into the offset.
ptr.data = size_t(fn) & ~size_t(1);
ptr.offset = size_t(fn) & 0x1;
Fn result;
memset(&result, 0, sizeof(result));
memcpy(&result, &ptr, min(sizeof(result), sizeof(ptr)));
return result;
}
#else
#error "Please implement handling of member pointers for your platform!"
#endif
|