File: FnPtr.h

package info (click to toggle)
storm-lang 0.7.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 51,984 kB
  • sloc: ansic: 261,420; cpp: 140,270; sh: 14,877; perl: 9,846; python: 2,525; lisp: 2,504; asm: 860; makefile: 678; pascal: 70; java: 52; xml: 37; awk: 12
file content (136 lines) | stat: -rw-r--r-- 4,028 bytes parent folder | download | duplicates (2)
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