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