• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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