1 /*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #ifndef BERBERIS_INTRINSICS_INTRINSICS_ARGS_H_
18 #define BERBERIS_INTRINSICS_INTRINSICS_ARGS_H_
19
20 #include <cstddef>
21 #include <cstdio>
22
23 #include "berberis/base/checks.h"
24
25 namespace berberis {
26
27 // Helper classes for the EmbedAsmInstruction "construction class".
28 //
29 // Constructor of this class takes prescribed arguments from IR insn and
30 // calls a single assembler [macro]instruction. It creates required scratch
31 // register allocations and proper MOVs to preserve semantic of the IR insn.
32 //
33 // Name of a helper class describes argument of the assembler
34 // [macro]instruction.
35 //
36 // You could find many examples in intrinsics_x86.h file but EmbedAsmInstruction
37 // is not x86-specific: in theory it should work with any MachineIRBuilder.
38 //
39 // Each argument must by of one of the following types:
40 //
41 // InArg<N> - argument comes from <N>th source of the IR insn.
42 // Must be "use" argument of the assembler [macro]instruction.
43 // Note: please don't use this argument for specific register
44 // classes (such as "RDX" or "RCX"). If one operation returns
45 // result, e.g., in "RCX" and another one accepts it in "RCX"
46 // then register allocator will not be able to satisfy such
47 // requirements. Use InTmpArg<N> for such instructions.
48 //
49 // OutArg<N> - argument comes from <N>th destination of the IR insn.
50 // Must be "def" or "def_early_clobber" argument of the assembler
51 // [macro]instruction.
52 //
53 // OutTmpArg<N> - argument is copied from temporary register to <N>th
54 // destination of the IR insn.
55 // Must be "def" or "def_early_clobber" argument of the
56 // assembler [macro]instruction.
57 //
58 // InOutArg<N, M> - argument is copied from <N>th source of the IR insn to
59 // <M>th destination of the IR insn, then is passed it to the
60 // [macro]instruction.
61 // Must be "use_def" argument of the assembler
62 // [macro]instruction.
63 //
64 // InOutTmpArg<N, M> - argument is copied from <N>th source of the IR insn to
65 // temporary register, then it's passed to the
66 // [macro]instruction. Result is copied to the
67 // <M>th destination of the IR insn.
68 // Must be "use_def" argument of the assembler
69 // [macro]instruction.
70 //
71 // InTmpArg<N> - argument is copied from <N>th source of the IR insn to the
72 // temporary register, then is passed to the [macro]instruction.
73 // Must be "use_def" argument of the assembler
74 // [macro]instruction.
75 //
76 // ImmArg<N, uintXX_t> - argument is 8bit/16bit/32bit/64bit immediate and
77 // comes as <N>th source of IR insn.
78 //
79 // TmpArg - argument is temporary register allocated for the
80 // [macro]instruction.
81 //
82 // TODO(khim): investigate feasibility of adding unconditional copying of
83 // arguments and results. This way we could remove classes InOutTmpArg/InTmpArg
84 // and, more importantly, make sure InArg vs InTmpArg mixup will not lead to
85 // hard to debug errors.
86
87 struct ArgInfo {
88 public:
89 enum ArgType {
90 IN_ARG,
91 IN_TMP_ARG,
92 OUT_ARG,
93 OUT_TMP_ARG,
94 IN_OUT_ARG,
95 IN_OUT_TMP_ARG,
96 TMP_ARG,
97 IMM_ARG
98 } arg_type;
HaveInputArgInfo99 friend constexpr bool HaveInput(const ArgInfo& arg) {
100 return arg.arg_type == ArgInfo::IN_ARG ||
101 arg.arg_type == ArgInfo::IN_TMP_ARG ||
102 arg.arg_type == ArgInfo::IN_OUT_ARG ||
103 arg.arg_type == ArgInfo::IN_OUT_TMP_ARG;
104 }
HaveOutputArgInfo105 friend constexpr bool HaveOutput(const ArgInfo& arg) {
106 return arg.arg_type == ArgInfo::IN_OUT_ARG ||
107 arg.arg_type == ArgInfo::IN_OUT_TMP_ARG ||
108 arg.arg_type == OUT_ARG ||
109 arg.arg_type == OUT_TMP_ARG;
110 }
IsImmediateArgInfo111 friend constexpr bool IsImmediate(const ArgInfo& arg) {
112 return arg.arg_type == IMM_ARG;
113 }
IsTemporaryArgInfo114 friend constexpr bool IsTemporary(const ArgInfo& arg) {
115 return arg.arg_type == TMP_ARG;
116 }
117 const int from = 0;
118 const int to = 0;
119 };
120
121 template <int N, typename RegisterClass = void, typename Usage = void>
122 class InArg;
123
124 template <int N, typename RegisterClass = void, typename Usage = void>
125 class OutArg;
126
127 template <int N, typename RegisterClass = void, typename Usage = void>
128 class OutTmpArg;
129
130 template <int N, int M, typename RegisterClass = void, typename Usage = void>
131 class InOutArg;
132
133 template <int N, int M, typename RegisterClass = void, typename Usage = void>
134 class InOutTmpArg;
135
136 template <int N, typename RegisterClass = void, typename Usage = void>
137 class InTmpArg;
138
139 template <int N, typename ImmType, typename ImmediateClass = void>
140 class ImmArg;
141
142 template <typename RegisterClass = void, typename Usage = void>
143 class TmpArg;
144
145 template <typename ArgInfo>
146 class ArgTraits;
147
148 template <int N, typename RegisterClassType, typename UsageType>
149 class ArgTraits<InArg<N, RegisterClassType, UsageType>> {
150 public:
151 using Class = RegisterClassType;
152 using RegisterClass = RegisterClassType;
153 using Usage = UsageType;
154 static constexpr ArgInfo arg_info{.arg_type = ArgInfo::IN_ARG, .from = N};
155 };
156
157 template <int N, typename RegisterClassType, typename UsageType>
158 class ArgTraits<OutArg<N, RegisterClassType, UsageType>> {
159 public:
160 using Class = RegisterClassType;
161 using RegisterClass = RegisterClassType;
162 using Usage = UsageType;
163 static constexpr ArgInfo arg_info{.arg_type = ArgInfo::OUT_ARG, .to = N};
164 };
165
166 template <int N, typename RegisterClassType, typename UsageType>
167 class ArgTraits<OutTmpArg<N, RegisterClassType, UsageType>> {
168 public:
169 using Class = RegisterClassType;
170 using RegisterClass = RegisterClassType;
171 using Usage = UsageType;
172 static constexpr ArgInfo arg_info{.arg_type = ArgInfo::OUT_TMP_ARG, .to = N};
173 };
174
175 template <int N, int M, typename RegisterClassType, typename UsageType>
176 class ArgTraits<InOutArg<N, M, RegisterClassType, UsageType>> {
177 public:
178 using Class = RegisterClassType;
179 using RegisterClass = RegisterClassType;
180 using Usage = UsageType;
181 static constexpr ArgInfo arg_info{.arg_type = ArgInfo::IN_OUT_ARG, .from = N, .to = M};
182 };
183
184 template <int N, int M, typename RegisterClassType, typename UsageType>
185 class ArgTraits<InOutTmpArg<N, M, RegisterClassType, UsageType>> {
186 public:
187 using Class = RegisterClassType;
188 using RegisterClass = RegisterClassType;
189 using Usage = UsageType;
190 static constexpr ArgInfo arg_info{.arg_type = ArgInfo::IN_OUT_TMP_ARG, .from = N, .to = M};
191 };
192
193 template <int N, typename RegisterClassType, typename UsageType>
194 class ArgTraits<InTmpArg<N, RegisterClassType, UsageType>> {
195 public:
196 using Class = RegisterClassType;
197 using RegisterClass = RegisterClassType;
198 using Usage = UsageType;
199 static constexpr ArgInfo arg_info{.arg_type = ArgInfo::IN_TMP_ARG, .from = N};
200 };
201
202 template <int N, typename ImmType, typename ImmediateClassType>
203 class ArgTraits<ImmArg<N, ImmType, ImmediateClassType>> {
204 public:
205 using Class = ImmediateClassType;
206 using ImmediateClass = ImmediateClassType;
207 static constexpr ArgInfo arg_info{.arg_type = ArgInfo::IMM_ARG, .from = N};
208 };
209
210 template <typename RegisterClassType, typename UsageType>
211 class ArgTraits<TmpArg<RegisterClassType, UsageType>> {
212 public:
213 using Class = RegisterClassType;
214 using RegisterClass = RegisterClassType;
215 using Usage = UsageType;
216 static constexpr ArgInfo arg_info{.arg_type = ArgInfo::TMP_ARG};
217 };
218
219 // We couldn't use standard "throw std::logic_error(...)" approach here because that code is
220 // compiled with -fno-exceptions. Thankfully printf(...) produces very similar error messages.
221 //
222 // See https://stackoverflow.com/questions/8626055/c11-static-assert-within-constexpr-function
223 // is you need an explanation for how basic technique works.
224 template <typename MachineInsn, int arguments_count>
IsCompatible(const ArgInfo * arguments)225 constexpr bool IsCompatible(const ArgInfo* arguments) {
226 int reg_arguments = 0;
227 for (size_t argument = 0; argument < arguments_count; ++argument) {
228 if (arguments[argument].arg_type != ArgInfo::IMM_ARG) {
229 if ((arguments[argument].arg_type == ArgInfo::IN_ARG) &&
230 MachineInsn::RegKindAt(reg_arguments).IsDef()) {
231 fprintf(stderr, "Incorrect use of InArg for argument %d", argument);
232 return false;
233 } else if ((arguments[argument].arg_type == ArgInfo::IN_TMP_ARG) &&
234 !MachineInsn::RegKindAt(reg_arguments).IsDef() &&
235 !IsFixedRegClass(MachineInsn::RegKindAt(reg_arguments).RegClass())) {
236 fprintf(stderr, "Inefficient use of InTmpArg for argument %d", argument);
237 return false;
238 } else if ((arguments[argument].arg_type == ArgInfo::OUT_ARG) &&
239 IsFixedRegClass(MachineInsn::RegKindAt(reg_arguments).RegClass())) {
240 fprintf(stderr, "Incorrect use of OutArg for argument %d", argument);
241 return false;
242 } else if ((arguments[argument].arg_type == ArgInfo::OUT_TMP_ARG) &&
243 !IsFixedRegClass(MachineInsn::RegKindAt(reg_arguments).RegClass())) {
244 fprintf(stderr, "Inefficient use of OutTmpArg for argument %d", argument);
245 return false;
246 } else if ((arguments[argument].arg_type == ArgInfo::IN_OUT_ARG) &&
247 IsFixedRegClass(MachineInsn::RegKindAt(reg_arguments).RegClass())) {
248 fprintf(stderr, "Incorrect use of InOutArg for argument %d", argument);
249 return false;
250 } else if ((arguments[argument].arg_type == ArgInfo::IN_OUT_TMP_ARG) &&
251 !IsFixedRegClass(MachineInsn::RegKindAt(reg_arguments).RegClass())) {
252 fprintf(stderr, "Inefficient use of InOutTmpArg for argument %d", argument);
253 return false;
254 }
255 if (HaveInput(arguments[argument]) &&
256 !MachineInsn::RegKindAt(reg_arguments).IsInput()) {
257 fprintf(stderr, "Argument %d does not accept input!", argument);
258 return false;
259 } else if (!HaveInput(arguments[argument]) &&
260 MachineInsn::RegKindAt(reg_arguments).IsInput()) {
261 fprintf(stderr, "Argument %d requires valid input!", argument);
262 return false;
263 }
264 ++reg_arguments;
265 }
266 }
267 if (MachineInsn::NumRegOperands() != reg_arguments) {
268 fprintf(stderr,
269 "expected %d arguments, got %d arguments",
270 MachineInsn::NumRegOperands(),
271 reg_arguments);
272 return false;
273 }
274 return true;
275 }
276
277 template <typename MachineInsn, typename... Args>
IsCompatible()278 constexpr bool IsCompatible() {
279 const ArgInfo arguments[] = {ArgTraits<Args>::arg_info...};
280 // Note: we couldn't pass arguments as an array into IsCompatible by reference
281 // because this would cause compilation error in case where we have no arguments.
282 //
283 // Pass pointer and element count instead.
284 return IsCompatible<MachineInsn, sizeof...(Args)>(arguments);
285 }
286
287 template <typename MachineIRBuilder, typename Arg>
288 class ArgGetterSetter;
289
290 template <typename Instruction, typename... Args>
291 class EmbedAsmInstruction {
292 public:
293 template <typename MachineIRBuilder, typename IntrinsicInsn>
EmbedAsmInstruction(MachineIRBuilder * builder,const IntrinsicInsn * insn)294 EmbedAsmInstruction(MachineIRBuilder* builder, const IntrinsicInsn* insn) {
295 static_assert(IsCompatible<Instruction, Args...>(), "Incompatible intrinsic embedding");
296 builder->template Gen<Instruction>(ArgGetterSetter<MachineIRBuilder, Args>(builder, insn)...);
297 }
298 };
299
300 } // namespace berberis
301
302 #endif // BERBERIS_INTRINSICS_INTRINSICS_ARGS_H_
303