1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef PARTITION_ALLOC_PARTITION_ALLOC_BASE_EXPORT_TEMPLATE_H_
6 #define PARTITION_ALLOC_PARTITION_ALLOC_BASE_EXPORT_TEMPLATE_H_
7 
8 // Synopsis
9 //
10 // This header provides macros for using
11 // PA_COMPONENT_EXPORT(PARTITION_ALLOC_BASE) macros with explicit template
12 // instantiation declarations and definitions. Generally, the
13 // PA_COMPONENT_EXPORT(PARTITION_ALLOC_BASE) macros are used at declarations,
14 // and GCC requires them to be used at explicit instantiation declarations, but
15 // MSVC requires __declspec(dllexport) to be used at the explicit instantiation
16 // definitions instead.
17 
18 // Usage
19 //
20 // In a header file, write:
21 //
22 //   extern template class
23 //   PA_EXPORT_TEMPLATE_DECLARE(PA_COMPONENT_EXPORT(PARTITION_ALLOC_BASE))
24 //   foo<bar>;
25 //
26 // In a source file, write:
27 //
28 //   template class
29 //   PA_EXPORT_TEMPLATE_DEFINE(PA_COMPONENT_EXPORT(PARTITION_ALLOC_BASE))
30 //   foo<bar>;
31 
32 // Implementation notes
33 //
34 // On Windows, when building when PA_COMPONENT_EXPORT(PARTITION_ALLOC_BASE)
35 // expands to __declspec(dllexport)), we want the two lines to expand to:
36 //
37 //     extern template class foo<bar>;
38 //     template class PA_COMPONENT_EXPORT(PARTITION_ALLOC_BASE) foo<bar>;
39 //
40 // In all other cases (non-Windows, and Windows when
41 // PA_COMPONENT_EXPORT(PARTITION_ALLOC_BASE) expands to
42 // __declspec(dllimport)), we want:
43 //
44 //     extern template class PA_COMPONENT_EXPORT(PARTITION_ALLOC_BASE) foo<bar>;
45 //     template class foo<bar>;
46 //
47 // The implementation of this header uses some subtle macro semantics to
48 // detect what the provided PA_COMPONENT_EXPORT(PARTITION_ALLOC_BASE) value was
49 // defined as and then to dispatch to appropriate macro definitions.
50 // Unfortunately, MSVC's C preprocessor is rather non-compliant and requires
51 // special care to make it work.
52 //
53 // Issue 1.
54 //
55 //   #define F(x)
56 //   F()
57 //
58 // MSVC emits warning C4003 ("not enough actual parameters for macro
59 // 'F'), even though it's a valid macro invocation.  This affects the
60 // macros below that take just an "export" parameter, because export
61 // may be empty.
62 //
63 // As a workaround, we can add a dummy parameter and arguments:
64 //
65 //   #define F(x,_)
66 //   F(,)
67 //
68 // Issue 2.
69 //
70 //   #define F(x) G##x
71 //   #define Gj() ok
72 //   F(j())
73 //
74 // The correct replacement for "F(j())" is "ok", but MSVC replaces it
75 // with "Gj()".  As a workaround, we can pass the result to an
76 // identity macro to force MSVC to look for replacements again.  (This
77 // is why PA_EXPORT_TEMPLATE_STYLE_3 exists.)
78 
79 #define PA_EXPORT_TEMPLATE_DECLARE(export)                               \
80   PA_EXPORT_TEMPLATE_INVOKE(DECLARE, PA_EXPORT_TEMPLATE_STYLE(export, ), \
81                             export)  // NOLINT
82 #define PA_EXPORT_TEMPLATE_DEFINE(export)                               \
83   PA_EXPORT_TEMPLATE_INVOKE(DEFINE, PA_EXPORT_TEMPLATE_STYLE(export, ), \
84                             export)  // NOLINT
85 
86 // INVOKE is an internal helper macro to perform parameter replacements
87 // and token pasting to chain invoke another macro.  E.g.,
88 //     PA_EXPORT_TEMPLATE_INVOKE(DECLARE, DEFAULT, PA_EXPORT)
89 // will export to call
90 //     PA_EXPORT_TEMPLATE_DECLARE_DEFAULT(PA_EXPORT, )
91 // (but with PA_COMPONENT_EXPORT(PARTITION_ALLOC_BASE) expanded too).
92 #define PA_EXPORT_TEMPLATE_INVOKE(which, style, export) \
93   PA_EXPORT_TEMPLATE_INVOKE_2(which, style, export)
94 #define PA_EXPORT_TEMPLATE_INVOKE_2(which, style, export) \
95   PA_EXPORT_TEMPLATE_##which##_##style(export, )
96 
97 // Default style is to apply the PA_COMPONENT_EXPORT(PARTITION_ALLOC_BASE) macro
98 // at declaration sites.
99 #define PA_EXPORT_TEMPLATE_DECLARE_DEFAULT(export, _) export
100 #define PA_EXPORT_TEMPLATE_DEFINE_DEFAULT(export, _)
101 
102 // The "MSVC hack" style is used when PA_COMPONENT_EXPORT(PARTITION_ALLOC_BASE)
103 // is defined as __declspec(dllexport), which MSVC requires to be used at
104 // definition sites instead.
105 #define PA_EXPORT_TEMPLATE_DECLARE_EXPORT_DLLEXPORT(export, _)
106 #define PA_EXPORT_TEMPLATE_DEFINE_EXPORT_DLLEXPORT(export, _) export
107 
108 // PA_EXPORT_TEMPLATE_STYLE is an internal helper macro that identifies which
109 // export style needs to be used for the provided
110 // PA_COMPONENT_EXPORT(PARTITION_ALLOC_BASE) macro definition.
111 // "", "__attribute__(...)", and "__declspec(dllimport)" are mapped
112 // to "DEFAULT"; while "__declspec(dllexport)" is mapped to "MSVC_HACK".
113 //
114 // It's implemented with token pasting to transform the __attribute__ and
115 // __declspec annotations into macro invocations.  E.g., if
116 // PA_COMPONENT_EXPORT(PARTITION_ALLOC_BASE) is defined as
117 // "__declspec(dllimport)", it undergoes the following sequence of macro
118 // substitutions:
119 //     PA_EXPORT_TEMPLATE_STYLE(PA_EXPORT,)
120 //     PA_EXPORT_TEMPLATE_STYLE_2(__declspec(dllimport),)
121 //     PA_EXPORT_TEMPLATE_STYLE_3(
122 //         PA_EXPORT_TEMPLATE_STYLE_MATCH__declspec(dllimport))
123 //     PA_EXPORT_TEMPLATE_STYLE_MATCH__declspec(dllimport)
124 //     PA_EXPORT_TEMPLATE_STYLE_MATCH_DECLSPEC_dllimport
125 //     DEFAULT
126 #define PA_EXPORT_TEMPLATE_STYLE(export, _) PA_EXPORT_TEMPLATE_STYLE_2(export, )
127 #define PA_EXPORT_TEMPLATE_STYLE_2(export, _) \
128   PA_EXPORT_TEMPLATE_STYLE_3(                 \
129       PA_EXPORT_TEMPLATE_STYLE_MATCH_foj3FJo5StF0OvIzl7oMxA##export)
130 #define PA_EXPORT_TEMPLATE_STYLE_3(style) style
131 
132 // Internal helper macros for PA_EXPORT_TEMPLATE_STYLE.
133 //
134 // XXX: C++ reserves all identifiers containing "__" for the implementation,
135 // but "__attribute__" and "__declspec" already contain "__" and the token-paste
136 // operator can only add characters; not remove them.  To minimize the risk of
137 // conflict with implementations, we include "foj3FJo5StF0OvIzl7oMxA" (a random
138 // 128-bit string, encoded in Base64) in the macro name.
139 #define PA_EXPORT_TEMPLATE_STYLE_MATCH_foj3FJo5StF0OvIzl7oMxA DEFAULT
140 #define PA_EXPORT_TEMPLATE_STYLE_MATCH_foj3FJo5StF0OvIzl7oMxA__attribute__( \
141     ...)                                                                    \
142   DEFAULT
143 #define PA_EXPORT_TEMPLATE_STYLE_MATCH_foj3FJo5StF0OvIzl7oMxA__declspec(arg) \
144   PA_EXPORT_TEMPLATE_STYLE_MATCH_DECLSPEC_##arg
145 
146 // Internal helper macros for PA_EXPORT_TEMPLATE_STYLE.
147 #define PA_EXPORT_TEMPLATE_STYLE_MATCH_DECLSPEC_dllexport EXPORT_DLLEXPORT
148 #define PA_EXPORT_TEMPLATE_STYLE_MATCH_DECLSPEC_dllimport DEFAULT
149 
150 // Sanity checks.
151 //
152 // PA_EXPORT_TEMPLATE_TEST uses the same macro invocation pattern as
153 // PA_EXPORT_TEMPLATE_DECLARE and PA_EXPORT_TEMPLATE_DEFINE do to check that
154 // they're working correctly. When they're working correctly, the sequence of
155 // macro replacements should go something like:
156 //
157 //     PA_EXPORT_TEMPLATE_TEST(DEFAULT, __declspec(dllimport));
158 //
159 //     static_assert(PA_EXPORT_TEMPLATE_INVOKE(TEST_DEFAULT,
160 //         PA_EXPORT_TEMPLATE_STYLE(__declspec(dllimport), ),
161 //         __declspec(dllimport)), "__declspec(dllimport)");
162 //
163 //     static_assert(PA_EXPORT_TEMPLATE_INVOKE(TEST_DEFAULT,
164 //         DEFAULT, __declspec(dllimport)), "__declspec(dllimport)");
165 //
166 //     static_assert(PA_EXPORT_TEMPLATE_TEST_DEFAULT_DEFAULT(
167 //         __declspec(dllimport)), "__declspec(dllimport)");
168 //
169 //     static_assert(true, "__declspec(dllimport)");
170 //
171 // When they're not working correctly, a syntax error should occur instead.
172 #define PA_EXPORT_TEMPLATE_TEST(want, export)                                 \
173   static_assert(PA_EXPORT_TEMPLATE_INVOKE(                                    \
174                     TEST_##want, PA_EXPORT_TEMPLATE_STYLE(export, ), export), \
175                 #export)  // NOLINT
176 #define PA_EXPORT_TEMPLATE_TEST_DEFAULT_DEFAULT(...) true
177 #define PA_EXPORT_TEMPLATE_TEST_EXPORT_DLLEXPORT_EXPORT_DLLEXPORT(...) true
178 
179 PA_EXPORT_TEMPLATE_TEST(DEFAULT, );  // NOLINT
180 PA_EXPORT_TEMPLATE_TEST(DEFAULT, __attribute__((visibility("default"))));
181 PA_EXPORT_TEMPLATE_TEST(EXPORT_DLLEXPORT, __declspec(dllexport));
182 PA_EXPORT_TEMPLATE_TEST(DEFAULT, __declspec(dllimport));
183 
184 #undef PA_EXPORT_TEMPLATE_TEST
185 #undef PA_EXPORT_TEMPLATE_TEST_DEFAULT_DEFAULT
186 #undef PA_EXPORT_TEMPLATE_TEST_EXPORT_DLLEXPORT_EXPORT_DLLEXPORT
187 
188 #endif  // PARTITION_ALLOC_PARTITION_ALLOC_BASE_EXPORT_TEMPLATE_H_
189