1 /*
2  * Copyright (C) 2023 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_BACKEND_X86_64_MACHINE_INSN_INTRINSICS_H_
18 #define BERBERIS_BACKEND_X86_64_MACHINE_INSN_INTRINSICS_H_
19 
20 #include <string>
21 #include <tuple>
22 #include <type_traits>
23 #include <variant>
24 
25 #include "berberis/backend/code_emitter.h"
26 #include "berberis/backend/common/machine_ir.h"
27 #include "berberis/backend/x86_64/code_debug.h"
28 #include "berberis/backend/x86_64/code_emit.h"
29 #include "berberis/backend/x86_64/machine_ir.h"
30 #include "berberis/backend/x86_64/machine_ir_builder.h"
31 #include "berberis/base/dependent_false.h"
32 #include "berberis/base/stringprintf.h"
33 #include "berberis/intrinsics/intrinsics_args.h"
34 #include "berberis/intrinsics/intrinsics_bindings.h"
35 
36 namespace berberis::x86_64 {
37 
38 // tuple_cat for types, to help remove filtered out types below.
39 template <typename... Ts>
40 using tuple_cat_t = decltype(std::tuple_cat(std::declval<Ts>()...));
41 
42 // Predicate to determine whether type T has a RegisterClass alias.
43 template <class, class = void>
44 struct has_reg_class_impl : std::false_type {};
45 template <class T>
46 struct has_reg_class_impl<T, std::void_t<typename T::RegisterClass>> : std::true_type {};
47 template <typename T>
48 using has_reg_class_t = has_reg_class_impl<T>;
49 
50 // Filter out types from Ts... that do not satisfy the predicate, collect them
51 // into a tuple.
52 template <template <typename> typename Predicate, typename... Ts>
53 using filter_t =
54     tuple_cat_t<std::conditional_t<Predicate<Ts>::value, std::tuple<Ts>, std::tuple<>>...>;
55 
56 // Convert Binding into constructor argument(s).
57 template <typename T, typename = void>
58 struct ConstructorArg;
59 
60 // Immediates expand into their class type.
61 template <typename T>
62 struct ConstructorArg<ArgTraits<T>, std::enable_if_t<ArgTraits<T>::Class::kIsImmediate, void>> {
63   using type = std::tuple<typename ArgTraits<T>::Class::Type>;
64 };
65 
66 // Mem ops expand into base register and disp.
67 template <typename T>
68 struct ConstructorArg<ArgTraits<T>,
69                       std::enable_if_t<!ArgTraits<T>::Class::kIsImmediate &&
70                                            ArgTraits<T>::RegisterClass::kAsRegister == 'm',
71                                        void>> {
72   static_assert(
73       std::is_same_v<typename ArgTraits<T>::Usage, intrinsics::bindings::DefEarlyClobber>);
74   // Need to emit base register AND disp.
75   using type = std::tuple<MachineReg, int32_t>;
76 };
77 
78 // Everything else expands into a MachineReg.
79 template <typename T>
80 struct ConstructorArg<ArgTraits<T>,
81                       std::enable_if_t<!ArgTraits<T>::Class::kIsImmediate &&
82                                            ArgTraits<T>::RegisterClass::kAsRegister != 'm',
83                                        void>> {
84   using type = std::tuple<MachineReg>;
85 };
86 
87 template <typename T>
88 using constructor_one_arg_t = typename ConstructorArg<ArgTraits<T>>::type;
89 
90 // Use this alias to generate constructor Args from bindings via the AsmCallInfo::MachineInsn
91 // alias. The tuple args will be extracted by the tuple specialization on MachineInsn below.
92 template <typename... T>
93 using constructor_args_t = tuple_cat_t<constructor_one_arg_t<T>...>;
94 
95 // Predicate to determine whether type T is a memory access arg.
96 template <class, class = void>
97 struct is_mem_impl : std::false_type {};
98 template <class T>
99 struct is_mem_impl<
100     T,
101     std::enable_if_t<!T::Class::kIsImmediate && T::RegisterClass::kAsRegister == 'm', void>>
102     : std::true_type {};
103 template <typename T>
104 using is_mem_t = is_mem_impl<T>;
105 
106 template <typename... Bindings>
107 constexpr size_t mem_count_v = std::tuple_size_v<filter_t<is_mem_t, ArgTraits<Bindings>...>>;
108 
109 template <size_t N, typename... Bindings>
110 constexpr bool has_n_mem_v = mem_count_v<Bindings...> > (N - 1);
111 
112 template <typename AsmCallInfo, auto kMnemo, auto kOpcode, typename Args, typename... Bindings>
113 class MachineInsn;
114 
115 // Use specialization to extract the tuple parameter pack generated from constructor_args_t above.
116 template <typename AsmCallInfo,
117           auto kMnemo,
118           auto kOpcode,
119           typename... CtorArgs,
120           typename... Bindings>
121 class MachineInsn<AsmCallInfo, kMnemo, kOpcode, std::tuple<CtorArgs...>, Bindings...> final
122     : public MachineInsnX86_64 {
123  private:
124   template <typename>
125   struct GenMachineInsnInfoT;
126   // We want to filter out any bindings that are not used for Register args.
127   using RegBindings = filter_t<has_reg_class_t, ArgTraits<Bindings>...>;
128 
129  public:
130   // This static simplifies constructing this MachineInsn in intrinsic implementations.
131   static constexpr MachineInsn* (MachineIRBuilder::*kGenFunc)(CtorArgs...) =
132       &MachineIRBuilder::template Gen<MachineInsn>;
133 
134   explicit MachineInsn(CtorArgs... args) : MachineInsnX86_64(&kInfo) {
135     ProcessArgs<0 /* reg_idx */, 0 /* mem_idx */, false /* is_disp */, Bindings...>(args...);
136   }
137 
138   static constexpr MachineInsnInfo kInfo = GenMachineInsnInfoT<RegBindings>::value;
139 
140   static constexpr int NumRegOperands() { return kInfo.num_reg_operands; }
141   static constexpr const MachineRegKind& RegKindAt(int i) { return kInfo.reg_kinds[i]; }
142 
143   std::string GetDebugString() const override {
144     std::string s(kMnemo);
145     ProcessDebugString<Bindings...>(&s);
146     return s;
147   }
148 
149   void Emit(CodeEmitter* as) const override {
150     std::apply(AsmCallInfo::kMacroInstruction,
151                std::tuple_cat(std::tuple<CodeEmitter&>{*as},
152                               EmitArgs<0 /* reg_idx */, 0 /* mem_idx */, Bindings...>()));
153   }
154 
155   int32_t disp2() const { return disp2_; }
156   void set_disp2(int32_t val) { disp2_ = val; }
157 
158  private:
159   int32_t disp2_;
160 
161   // TODO(b/260725458): Use inline template lambda instead after C++20 becomes available.
162   template <size_t, size_t, bool, typename...>
163   void ProcessArgs() {}
164 
165   template <size_t reg_idx,
166             size_t mem_idx,
167             bool is_disp,
168             typename B,
169             typename... BindingsRest,
170             typename T,
171             typename... Args>
172   void ProcessArgs(T arg, Args... args) {
173     if constexpr (ArgTraits<B>::Class::kIsImmediate) {
174       this->set_imm(arg);
175       ProcessArgs<reg_idx, mem_idx, false, BindingsRest..., Args...>(args...);
176     } else if constexpr (ArgTraits<B>::RegisterClass::kAsRegister == 'm' && !is_disp) {
177       // Only tmp memory args are supported.
178       static_assert(ArgTraits<B>::arg_info.arg_type == ArgInfo::TMP_ARG);
179       this->SetRegAt(reg_idx, arg);
180       // Note that mem is non incr'ed. We want to process the disp portion next. This is
181       // why `is_disp` is set to `true` here and we keep the binding `B`. This can't be done
182       // in a single pass because they're each a different `arg`.
183       ProcessArgs<reg_idx + 1, mem_idx, true, B, BindingsRest..., Args...>(args...);
184     } else if constexpr (ArgTraits<B>::RegisterClass::kAsRegister == 'm' && is_disp) {
185       static_assert(ArgTraits<B>::arg_info.arg_type == ArgInfo::TMP_ARG);
186       if constexpr (mem_idx == 0) {
187         this->set_disp(arg);
188       } else if constexpr (mem_idx == 1) {
189         this->set_disp2(arg);
190       }
191       // We finished processing the mem binding, reset is_disp and incr mem_idx.
192       ProcessArgs<reg_idx, mem_idx + 1, false, BindingsRest..., Args...>(args...);
193     } else if constexpr (std::is_same_v<MachineReg, T>) {
194       this->SetRegAt(reg_idx, arg);
195       ProcessArgs<reg_idx + 1, mem_idx, false, BindingsRest..., Args...>(args...);
196     } else {
197       static_assert(kDependentTypeFalse<T>);
198     }
199   }
200 
201   static constexpr auto GetInsnKind() {
202     if constexpr (AsmCallInfo::kSideEffects) {
203       return kMachineInsnSideEffects;
204     } else {
205       return kMachineInsnDefault;
206     }
207   }
208 
209   template <typename T, typename = void>
210   struct RegInfo;
211   template <typename T>
212   struct RegInfo<T, std::enable_if_t<T::RegisterClass::kAsRegister != 'm', void>> {
213     static constexpr auto kRegClass = &T::RegisterClass::template kRegClass<MachineInsnX86_64>;
214     static constexpr auto kRegKind =
215         intrinsics::bindings::kRegKind<typename T::Usage, berberis::MachineRegKind>;
216   };
217   template <typename T>
218   struct RegInfo<T, std::enable_if_t<T::RegisterClass::kAsRegister == 'm', void>> {
219     static_assert(std::is_same_v<typename T::Usage, intrinsics::bindings::DefEarlyClobber>);
220     static constexpr auto kRegClass = &kGeneralReg32;
221     static constexpr auto kRegKind = MachineRegKind::kUse;
222   };
223 
224   template <typename... T>
225   struct GenMachineInsnInfoT<std::tuple<T...>> {
226     static constexpr MachineInsnInfo value = MachineInsnInfo(
227         {kOpcode, sizeof...(T), {{RegInfo<T>::kRegClass, RegInfo<T>::kRegKind}...}, GetInsnKind()});
228   };
229 
230   template <typename... Args>
231   void ProcessDebugString(std::string* s) const {
232     *s +=
233         " " + ProcessDebugStringArgs<0 /* arg_idx */, 0 /* reg_idx */, 0 /* mem_idx */, Args...>();
234     if (this->recovery_pc()) {
235       *s += StringPrintf(" <0x%" PRIxPTR ">", this->recovery_pc());
236     }
237   }
238 
239   // TODO(b/260725458): Use inline template lambda instead after C++20 becomes available.
240   template <>
241   void ProcessDebugString<>(std::string*) const {}
242 
243   template <size_t arg_idx, size_t reg_idx, size_t mem_idx, typename T, typename... Args>
244   std::string ProcessDebugStringArgs() const {
245     std::string prefix;
246     if constexpr (arg_idx > 0) {
247       prefix = ", ";
248     }
249     if constexpr (ArgTraits<T>::Class::kIsImmediate) {
250       return prefix + GetImmOperandDebugString(this) +
251              ProcessDebugStringArgs<arg_idx + 1, reg_idx, mem_idx, Args...>();
252     } else if constexpr (ArgTraits<T>::Class::kAsRegister == 'm') {
253       if constexpr (mem_idx == 0) {
254         return prefix + GetBaseDispMemOperandDebugString(this, reg_idx) +
255                ProcessDebugStringArgs<arg_idx + 1, reg_idx + 1, mem_idx + 1, Args...>();
256       } else if constexpr (mem_idx == 1) {
257         return prefix +
258                StringPrintf(
259                    "[%s + 0x%x]", GetRegOperandDebugString(this, reg_idx).c_str(), disp2()) +
260                ProcessDebugStringArgs<arg_idx + 1, reg_idx + 1, mem_idx + 1, Args...>();
261       } else {
262         static_assert(kDependentValueFalse<mem_idx>);
263       }
264     } else if constexpr (ArgTraits<T>::RegisterClass::kIsImplicitReg) {
265       return prefix + GetImplicitRegOperandDebugString(this, reg_idx) +
266              ProcessDebugStringArgs<arg_idx + 1, reg_idx + 1, mem_idx, Args...>();
267     } else {
268       return prefix + GetRegOperandDebugString(this, reg_idx) +
269              ProcessDebugStringArgs<arg_idx + 1, reg_idx + 1, mem_idx, Args...>();
270     }
271   }
272 
273   template <size_t, size_t, size_t>
274   std::string ProcessDebugStringArgs() const {
275     return "";
276   }
277 
278   // TODO(b/260725458): Use inline template lambda instead after C++20 becomes available.
279   template <size_t, size_t>
280   auto EmitArgs() const {
281     return std::tuple{};
282   }
283 
284   template <size_t reg_idx, size_t mem_idx, typename T, typename... Args>
285   auto EmitArgs() const {
286     if constexpr (ArgTraits<T>::Class::kIsImmediate) {
287       return std::tuple_cat(
288           std::tuple{static_cast<constructor_one_arg_t<T>>(MachineInsnX86_64::imm())},
289           EmitArgs<reg_idx, mem_idx, Args...>());
290     } else if constexpr (ArgTraits<T>::RegisterClass::kAsRegister == 'x') {
291       return std::tuple_cat(std::tuple{GetXReg(this->RegAt(reg_idx))},
292                             EmitArgs<reg_idx + 1, mem_idx, Args...>());
293     } else if constexpr (ArgTraits<T>::RegisterClass::kAsRegister == 'r' ||
294                          ArgTraits<T>::RegisterClass::kAsRegister == 'q') {
295       return std::tuple_cat(std::tuple{GetGReg(this->RegAt(reg_idx))},
296                             EmitArgs<reg_idx + 1, mem_idx, Args...>());
297     } else if constexpr (ArgTraits<T>::RegisterClass::kAsRegister == 'm' &&
298                          std::is_same_v<typename ArgTraits<T>::Usage,
299                                         intrinsics::bindings::DefEarlyClobber>) {
300       if constexpr (mem_idx == 0) {
301         return std::tuple_cat(std::tuple{Assembler::Operand{.base = GetGReg(this->RegAt(reg_idx)),
302                                                             .disp = static_cast<int32_t>(disp())}},
303                               EmitArgs<reg_idx + 1, mem_idx + 1, Args...>());
304       } else if constexpr (mem_idx == 1) {
305         return std::tuple_cat(std::tuple{Assembler::Operand{.base = GetGReg(this->RegAt(reg_idx)),
306                                                             .disp = static_cast<int32_t>(disp2())}},
307                               EmitArgs<reg_idx + 1, mem_idx + 1, Args...>());
308       } else {
309         static_assert(kDependentTypeFalse<T>);
310       }
311     } else if constexpr (ArgTraits<T>::RegisterClass::kIsImplicitReg) {
312       return EmitArgs<reg_idx, mem_idx, Args...>();
313     } else {
314       static_assert(kDependentTypeFalse<T>);
315     }
316   }
317 };
318 
319 }  // namespace berberis::x86_64
320 
321 #endif  // BERBERIS_BACKEND_X86_64_MACHINE_INSN_INTRINSICS_H_
322