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_GUEST_ABI_GUEST_ABI_ARCH_H_ 18 #define BERBERIS_GUEST_ABI_GUEST_ABI_ARCH_H_ 19 20 #include <cstdint> 21 #include <type_traits> 22 23 #include "berberis/base/bit_util.h" 24 #include "berberis/calling_conventions/calling_conventions_riscv64.h" // IWYU pragma: export. 25 #include "berberis/guest_abi/guest_type.h" 26 27 namespace berberis { 28 29 class GuestAbi { 30 public: 31 enum CallingConventionsVariant { 32 kLp64, // Soft float. 33 kLp64d, // Hardware float and double. 34 kDefaultAbi = kLp64d 35 }; 36 37 template <typename, CallingConventionsVariant = kDefaultAbi, typename = void> 38 class GuestArgument; 39 40 template <typename IntegerType, CallingConventionsVariant kCallingConventionsVariant> 41 class alignas(sizeof(uint64_t)) GuestArgument<IntegerType, 42 kCallingConventionsVariant, 43 std::enable_if_t<std::is_integral_v<IntegerType>>> { 44 public: 45 using Type = IntegerType; GuestArgument(const IntegerType & value)46 GuestArgument(const IntegerType& value) : value_(Box(value)) {} GuestArgument(IntegerType && value)47 GuestArgument(IntegerType&& value) : value_(Box(value)) {} 48 GuestArgument() = default; 49 GuestArgument(const GuestArgument&) = default; 50 GuestArgument(GuestArgument&&) = default; 51 GuestArgument& operator=(const GuestArgument&) = default; 52 GuestArgument& operator=(GuestArgument&&) = default; 53 ~GuestArgument() = default; IntegerType()54 operator IntegerType() const { return Unbox(value_); } 55 #define ARITHMETIC_ASSIGNMENT_OPERATOR(x) x## = 56 #define DEFINE_ARITHMETIC_ASSIGNMENT_OPERATOR(x) \ 57 GuestArgument& operator ARITHMETIC_ASSIGNMENT_OPERATOR(x)(const GuestArgument& data) { \ 58 value_ = Box(Unbox(value_) x Unbox(data.value_)); \ 59 return *this; \ 60 } \ 61 GuestArgument& operator ARITHMETIC_ASSIGNMENT_OPERATOR(x)(GuestArgument&& data) { \ 62 value_ = Box(Unbox(value_) x Unbox(data.value_)); \ 63 return *this; \ 64 } 65 DEFINE_ARITHMETIC_ASSIGNMENT_OPERATOR(+) 66 DEFINE_ARITHMETIC_ASSIGNMENT_OPERATOR(-) 67 DEFINE_ARITHMETIC_ASSIGNMENT_OPERATOR(*) 68 DEFINE_ARITHMETIC_ASSIGNMENT_OPERATOR(/) 69 DEFINE_ARITHMETIC_ASSIGNMENT_OPERATOR(%) 70 DEFINE_ARITHMETIC_ASSIGNMENT_OPERATOR(^) 71 DEFINE_ARITHMETIC_ASSIGNMENT_OPERATOR(&) 72 DEFINE_ARITHMETIC_ASSIGNMENT_OPERATOR(|) 73 #undef DEFINE_ARITHMETIC_ASSIGNMENT_OPERATOR 74 #undef ARITHMETIC_ASSIGNMENT_OPERATOR 75 76 private: Box(IntegerType value)77 static constexpr uint64_t Box(IntegerType value) { 78 if constexpr (sizeof(IntegerType) == sizeof(uint64_t)) { 79 return static_cast<uint64_t>(value); 80 } else if constexpr (std::is_signed_v<IntegerType>) { 81 // Signed integers are simply sign-extended to 64 bits. 82 return static_cast<uint64_t>(static_cast<int64_t>(value)); 83 } else { 84 // Unsigned integers are first zero-extended to 32 bits then sign-extended to 64 bits. This 85 // generally results in the high bits being set to 0, but the high bits of 32-bit integers 86 // with a 1 in the high bit will be set to 1. 87 return static_cast<uint64_t>( 88 static_cast<int64_t>(static_cast<int32_t>(static_cast<uint32_t>(value)))); 89 } 90 } 91 Unbox(uint64_t value)92 static constexpr IntegerType Unbox(uint64_t value) { 93 // Integer narrowing correctly unboxes at any size. 94 return static_cast<IntegerType>(value); 95 } 96 97 uint64_t value_ = 0; 98 }; 99 100 template <typename EnumType, CallingConventionsVariant kCallingConventionsVariant> 101 class alignas(sizeof(uint64_t)) GuestArgument<EnumType, 102 kCallingConventionsVariant, 103 std::enable_if_t<std::is_enum_v<EnumType>>> { 104 public: 105 using Type = EnumType; GuestArgument(const EnumType & value)106 GuestArgument(const EnumType& value) : value_(Box(value)) {} GuestArgument(EnumType && value)107 GuestArgument(EnumType&& value) : value_(Box(value)) {} 108 GuestArgument() = default; 109 GuestArgument(const GuestArgument&) = default; 110 GuestArgument(GuestArgument&&) = default; 111 GuestArgument& operator=(const GuestArgument&) = default; 112 GuestArgument& operator=(GuestArgument&&) = default; 113 ~GuestArgument() = default; EnumType()114 operator EnumType() const { return Unbox(value_); } 115 116 private: 117 using UnderlyingType = std::underlying_type_t<EnumType>; 118 Box(EnumType value)119 static constexpr UnderlyingType Box(EnumType value) { 120 return static_cast<UnderlyingType>(value); 121 } 122 Unbox(UnderlyingType value)123 static constexpr EnumType Unbox(UnderlyingType value) { return static_cast<EnumType>(value); } 124 125 GuestArgument<UnderlyingType, kCallingConventionsVariant> value_; 126 }; 127 128 template <typename FloatingPointType> 129 class alignas(sizeof(uint64_t)) 130 GuestArgument<FloatingPointType, 131 kLp64, 132 std::enable_if_t<std::is_floating_point_v<FloatingPointType>>> { 133 public: 134 using Type = FloatingPointType; GuestArgument(const FloatingPointType & value)135 GuestArgument(const FloatingPointType& value) : value_(Box(value)) {} GuestArgument(FloatingPointType && value)136 GuestArgument(FloatingPointType&& value) : value_(Box(value)) {} 137 GuestArgument() = default; 138 GuestArgument(const GuestArgument&) = default; 139 GuestArgument(GuestArgument&&) = default; 140 GuestArgument& operator=(const GuestArgument&) = default; 141 GuestArgument& operator=(GuestArgument&&) = default; 142 ~GuestArgument() = default; FloatingPointType()143 operator FloatingPointType() const { return Unbox(value_); } 144 145 private: 146 // Floating-point arguments in integer registers do not require NaN boxing. They are stored in 147 // the lower bits of the 64-bit integer register with the high bits undefined. Bit casting and 148 // unsigned narrowing/widening conversions are sufficient. 149 Box(FloatingPointType value)150 static constexpr uint64_t Box(FloatingPointType value) { 151 if constexpr (sizeof(FloatingPointType) == sizeof(uint64_t)) { 152 return bit_cast<uint64_t>(value); 153 } else if constexpr (sizeof(FloatingPointType) == sizeof(uint32_t)) { 154 return static_cast<uint64_t>(bit_cast<uint32_t>(value)); 155 } else { 156 FATAL("Unsupported floating-point argument width"); 157 } 158 } 159 Unbox(uint64_t value)160 static constexpr FloatingPointType Unbox(uint64_t value) { 161 if constexpr (sizeof(FloatingPointType) == sizeof(uint64_t)) { 162 return bit_cast<FloatingPointType>(value); 163 } else if constexpr (sizeof(FloatingPointType) == sizeof(uint32_t)) { 164 return bit_cast<FloatingPointType>(static_cast<uint32_t>(value)); 165 } else { 166 FATAL("Unsupported floating-point argument width"); 167 } 168 } 169 170 uint64_t value_ = 0; 171 }; 172 173 template <typename FloatingPointType> 174 class alignas(sizeof(uint64_t)) 175 GuestArgument<FloatingPointType, 176 kLp64d, 177 std::enable_if_t<std::is_floating_point_v<FloatingPointType>>> { 178 public: 179 using Type = FloatingPointType; GuestArgument(const FloatingPointType & value)180 GuestArgument(const FloatingPointType& value) : value_(Box(value)) {} GuestArgument(FloatingPointType && value)181 GuestArgument(FloatingPointType&& value) : value_(Box(value)) {} 182 GuestArgument() = default; 183 GuestArgument(const GuestArgument&) = default; 184 GuestArgument(GuestArgument&&) = default; 185 GuestArgument& operator=(const GuestArgument&) = default; 186 GuestArgument& operator=(GuestArgument&&) = default; 187 ~GuestArgument() = default; FloatingPointType()188 operator FloatingPointType() const { return Unbox(value_); } 189 190 private: 191 // Floating-point arguments passed in floating-point registers require NaN boxing when they are 192 // narrower than 64 bits. The argument is stored in the lower bits of the 64-bit floating-point 193 // register with the high bits set to 1. 194 Box(FloatingPointType value)195 static constexpr uint64_t Box(FloatingPointType value) { 196 if constexpr (sizeof(FloatingPointType) == sizeof(uint64_t)) { 197 return bit_cast<uint64_t>(value); 198 } else if constexpr (sizeof(FloatingPointType) == sizeof(uint32_t)) { 199 return bit_cast<uint32_t>(value) | kNanBoxFloat32; 200 } else { 201 FATAL("Unsupported floating-point argument width"); 202 } 203 } 204 Unbox(uint64_t value)205 static constexpr FloatingPointType Unbox(uint64_t value) { 206 if constexpr (sizeof(FloatingPointType) == sizeof(uint64_t)) { 207 return bit_cast<Type>(value); 208 } else if constexpr (sizeof(FloatingPointType) == sizeof(uint32_t)) { 209 // Integer narrowing removes the NaN box. 210 return bit_cast<Type>(static_cast<uint32_t>(value)); 211 } else { 212 FATAL("Unsupported floating-point argument width"); 213 } 214 } 215 216 static constexpr uint64_t kNanBoxFloat32 = 0xffff'ffff'0000'0000ULL; 217 218 uint64_t value_ = 0; 219 }; 220 221 protected: 222 enum class ArgumentClass { kInteger, kFp, kLargeStruct }; 223 224 template <typename Type, CallingConventionsVariant = kDefaultAbi, typename = void> 225 struct GuestArgumentInfo; 226 227 template <typename IntegerType, CallingConventionsVariant kCallingConventionsVariant> 228 struct GuestArgumentInfo<IntegerType, 229 kCallingConventionsVariant, 230 std::enable_if_t<std::is_integral_v<IntegerType>>> { 231 // Integers wider than 16 bytes are not supported. They do not appear in the public Android 232 // API. 233 static_assert(sizeof(IntegerType) <= 16); 234 constexpr static ArgumentClass kArgumentClass = ArgumentClass::kInteger; 235 constexpr static unsigned kSize = sizeof(IntegerType); 236 // Use sizeof, not alignof for kAlignment because all integer types are naturally aligned on 237 // RISC-V, which is not guaranteed to be true for the host. 238 constexpr static unsigned kAlignment = sizeof(IntegerType); 239 using GuestType = GuestArgument<IntegerType, kCallingConventionsVariant>; 240 using HostType = GuestType; 241 }; 242 243 template <typename EnumType, CallingConventionsVariant kCallingConventionsVariant> 244 struct GuestArgumentInfo<EnumType, 245 kCallingConventionsVariant, 246 std::enable_if_t<std::is_enum_v<EnumType>>> 247 : GuestArgumentInfo<std::underlying_type_t<EnumType>> { 248 using GuestType = GuestArgument<EnumType, kCallingConventionsVariant>; 249 using HostType = GuestType; 250 }; 251 252 template <typename PointeeType, CallingConventionsVariant kCallingConventionsVariant> 253 struct GuestArgumentInfo<PointeeType*, kCallingConventionsVariant> { 254 constexpr static ArgumentClass kArgumentClass = ArgumentClass::kInteger; 255 constexpr static unsigned kSize = 8; 256 constexpr static unsigned kAlignment = 8; 257 using GuestType = GuestType<PointeeType*>; 258 using HostType = PointeeType*; 259 }; 260 261 template <typename ResultType, 262 typename... ArgumentType, 263 CallingConventionsVariant kCallingConventionsVariant> 264 struct GuestArgumentInfo<ResultType (*)(ArgumentType...), kCallingConventionsVariant> { 265 constexpr static ArgumentClass kArgumentClass = ArgumentClass::kInteger; 266 constexpr static unsigned kSize = 8; 267 constexpr static unsigned kAlignment = 8; 268 using GuestType = GuestType<ResultType (*)(ArgumentType...)>; 269 using HostType = ResultType (*)(ArgumentType...); 270 }; 271 272 template <> 273 struct GuestArgumentInfo<float, kLp64> { 274 constexpr static ArgumentClass kArgumentClass = ArgumentClass::kInteger; 275 constexpr static unsigned kSize = 4; 276 constexpr static unsigned kAlignment = 4; 277 using GuestType = GuestArgument<float, kLp64>; 278 using HostType = GuestType; 279 }; 280 281 template <> 282 struct GuestArgumentInfo<float, kLp64d> { 283 constexpr static ArgumentClass kArgumentClass = ArgumentClass::kFp; 284 constexpr static unsigned kSize = 4; 285 constexpr static unsigned kAlignment = 4; 286 using GuestType = GuestArgument<float, kLp64d>; 287 using HostType = GuestType; 288 }; 289 290 template <> 291 struct GuestArgumentInfo<double, kLp64> { 292 constexpr static ArgumentClass kArgumentClass = ArgumentClass::kInteger; 293 constexpr static unsigned kSize = 8; 294 constexpr static unsigned kAlignment = 8; 295 using GuestType = GuestArgument<double, kLp64>; 296 using HostType = GuestType; 297 }; 298 299 template <> 300 struct GuestArgumentInfo<double, kLp64d> { 301 constexpr static ArgumentClass kArgumentClass = ArgumentClass::kFp; 302 constexpr static unsigned kSize = 8; 303 constexpr static unsigned kAlignment = 8; 304 using GuestType = GuestArgument<double, kLp64d>; 305 using HostType = GuestType; 306 }; 307 308 // Structures larger than 16 bytes are passed by reference. 309 template <typename LargeStructType, CallingConventionsVariant kCallingConventionsVariant> 310 struct GuestArgumentInfo< 311 LargeStructType, 312 kCallingConventionsVariant, 313 std::enable_if_t<std::is_class_v<LargeStructType> && (sizeof(LargeStructType) > 16)>> { 314 constexpr static ArgumentClass kArgumentClass = ArgumentClass::kLargeStruct; 315 constexpr static unsigned kSize = 8; 316 constexpr static unsigned kAlignment = 8; 317 // Although the structure is passed by reference, keep the type of the underlying structure 318 // here. This is for passing the structure as a function argument. It is easier to pass 319 // constant structures using this declaration than adding const to the pointee type of a 320 // pointer. 321 using GuestType = GuestType<LargeStructType>; 322 using HostType = LargeStructType; 323 }; 324 }; 325 326 } // namespace berberis 327 328 #endif // BERBERIS_GUEST_ABI_GUEST_ABI_ARCH_H_ 329