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 #include "gtest/gtest.h"
18
19 #include <cstdint>
20 #include <type_traits>
21
22 #include "berberis/assembler/machine_code.h"
23 #include "berberis/assembler/x86_64.h"
24 #include "berberis/base/dependent_false.h"
25 #include "berberis/intrinsics/guest_cpu_flags.h"
26 #include "berberis/intrinsics/intrinsics.h"
27 #include "berberis/intrinsics/macro_assembler.h"
28 #include "berberis/intrinsics/simd_register.h"
29 #include "berberis/intrinsics/vector_intrinsics.h"
30 #include "berberis/runtime_primitives/platform.h"
31
32 #include "inline_intrinsic.h"
33
34 namespace berberis {
35
36 namespace {
37
38 class RegAlloc {
39 public:
AllocTempReg()40 static x86_64::Assembler::Register AllocTempReg() { return x86_64::Assembler::rax; }
41
AllocTempSimdReg()42 static x86_64::Assembler::XMMRegister AllocTempSimdReg() { return x86_64::Assembler::xmm0; }
43 };
44
45 // Helper to split function proto into args and result in the specialization.
46 template <typename Type>
47 class TryInlineIntrinsicWithTestParams {};
48
49 template <typename Result, typename... Args>
50 class TryInlineIntrinsicWithTestParams<Result (*)(Args...)> {
51 public:
52 template <auto kFunction, typename... ExplicitArgs>
Call(MacroAssembler<x86_64::Assembler> * as,ExplicitArgs &&...args)53 static bool Call(MacroAssembler<x86_64::Assembler>* as, ExplicitArgs&&... args) {
54 return Call<kFunction>(
55 as, std::make_index_sequence<sizeof...(Args)>{}, std::forward<ExplicitArgs>(args)...);
56 }
57
58 private:
59 template <auto kFunction, std::size_t... I, typename... ExplicitArgs>
Call(MacroAssembler<x86_64::Assembler> * as,std::integer_sequence<std::size_t,I...>,ExplicitArgs &&...args)60 static bool Call(MacroAssembler<x86_64::Assembler>* as,
61 std::integer_sequence<std::size_t, I...>,
62 ExplicitArgs&&... args) {
63 RegAlloc reg_alloc;
64 return inline_intrinsic::TryInlineIntrinsic<kFunction>(
65 *as,
66 [®_alloc]() { return reg_alloc.AllocTempReg(); },
67 [®_alloc]() { return reg_alloc.AllocTempSimdReg(); },
68 AllocResult(),
69 AllocArg<Args, I>(std::tuple{std::forward<ExplicitArgs>(args)...})...);
70 }
71
72 using Float32 = intrinsics::Float32;
73 using Float64 = intrinsics::Float64;
AllocResult()74 static auto AllocResult() {
75 if constexpr (std::is_same_v<Result, std::tuple<uint32_t>> ||
76 std::is_same_v<Result, std::tuple<int32_t>> ||
77 std::is_same_v<Result, std::tuple<uint64_t>> ||
78 std::is_same_v<Result, std::tuple<int64_t>>) {
79 return x86_64::Assembler::rax;
80 } else if constexpr (std::is_same_v<Result, std::tuple<SIMD128Register, uint32_t>>) {
81 return std::make_tuple(x86_64::Assembler::xmm0, x86_64::Assembler::rax);
82 } else if constexpr (std::is_same_v<Result, std::tuple<SIMD128Register>> ||
83 std::is_same_v<Result, std::tuple<Float32>> ||
84 std::is_same_v<Result, std::tuple<Float64>>) {
85 return x86_64::Assembler::xmm0;
86 } else {
87 static_assert(kDependentTypeFalse<Result>);
88 }
89 }
90
91 template <typename Arg, std::size_t I, typename ExplicitArgs>
AllocArg(ExplicitArgs explicit_args)92 static auto AllocArg(ExplicitArgs explicit_args) {
93 if constexpr (I < std::tuple_size_v<ExplicitArgs>) {
94 return Arg{std::get<I>(explicit_args)};
95 } else if constexpr (std::is_integral_v<Arg>) {
96 Arg value = Arg{};
97 return value;
98 } else if constexpr (std::is_same_v<Arg, SIMD128Register> || std::is_same_v<Arg, Float32> ||
99 std::is_same_v<Arg, Float64>) {
100 return x86_64::Assembler::xmm0;
101 } else {
102 static_assert(kDependentTypeFalse<Arg>);
103 }
104 }
105 };
106
107 // Syntax sugar.
108 #define TEST_SUPPORTED(func, ...) \
109 EXPECT_TRUE((TryInlineIntrinsicWithTestParams<decltype(&func)>::template Call<&func>( \
110 &as __VA_OPT__(, ) __VA_ARGS__)))
111
112 #define TEST_UNSUPPORTED(func, ...) \
113 EXPECT_FALSE((TryInlineIntrinsicWithTestParams<decltype(&func)>::template Call<&func>( \
114 &as __VA_OPT__(, ) __VA_ARGS__)))
115
TEST(InlineIntrinsicRiscv64Test,SupportedInstructions)116 TEST(InlineIntrinsicRiscv64Test, SupportedInstructions) {
117 MachineCode machine_code;
118 MacroAssembler<x86_64::Assembler> as(&machine_code);
119 TEST_SUPPORTED((intrinsics::FMul<intrinsics::Float64>), int8_t{FPFlags::DYN});
120 TEST_UNSUPPORTED((intrinsics::FMul<intrinsics::Float64>), int8_t{FPFlags::RNE});
121 TEST_SUPPORTED((intrinsics::FMul<intrinsics::Float32>), int8_t{FPFlags::DYN});
122 TEST_UNSUPPORTED((intrinsics::FMul<intrinsics::Float32>), int8_t{FPFlags::RNE});
123 TEST_SUPPORTED((intrinsics::FMulHostRounding<intrinsics::Float64>));
124 TEST_SUPPORTED((intrinsics::FAdd<intrinsics::Float64>), int8_t{FPFlags::DYN});
125 TEST_UNSUPPORTED((intrinsics::FAdd<intrinsics::Float64>), int8_t{FPFlags::RNE});
126 TEST_SUPPORTED((intrinsics::FAdd<intrinsics::Float32>), int8_t{FPFlags::DYN});
127 TEST_UNSUPPORTED((intrinsics::FAdd<intrinsics::Float32>), int8_t{FPFlags::RNE});
128 TEST_SUPPORTED((intrinsics::FAddHostRounding<intrinsics::Float64>));
129 TEST_SUPPORTED((intrinsics::FSub<intrinsics::Float64>), int8_t{FPFlags::DYN});
130 TEST_UNSUPPORTED((intrinsics::FSub<intrinsics::Float64>), int8_t{FPFlags::RNE});
131 TEST_SUPPORTED((intrinsics::FSub<intrinsics::Float32>), int8_t{FPFlags::DYN});
132 TEST_UNSUPPORTED((intrinsics::FSub<intrinsics::Float32>), int8_t{FPFlags::RNE});
133 TEST_SUPPORTED((intrinsics::FSubHostRounding<intrinsics::Float64>));
134 TEST_SUPPORTED((intrinsics::FDiv<intrinsics::Float64>), int8_t{FPFlags::DYN});
135 TEST_UNSUPPORTED((intrinsics::FDiv<intrinsics::Float64>), int8_t{FPFlags::RNE});
136 TEST_SUPPORTED((intrinsics::FDiv<intrinsics::Float32>), int8_t{FPFlags::DYN});
137 TEST_UNSUPPORTED((intrinsics::FDiv<intrinsics::Float32>), int8_t{FPFlags::RNE});
138 TEST_SUPPORTED((intrinsics::FDivHostRounding<intrinsics::Float64>));
139 TEST_SUPPORTED((intrinsics::FCvtFloatToInteger<int64_t, intrinsics::Float64>),
140 int8_t{FPFlags::DYN});
141 TEST_UNSUPPORTED((intrinsics::FCvtFloatToInteger<int64_t, intrinsics::Float64>),
142 int8_t{FPFlags::RNE});
143 TEST_SUPPORTED((intrinsics::FCvtFloatToIntegerHostRounding<int64_t, intrinsics::Float64>));
144 TEST_SUPPORTED((intrinsics::FCvtFloatToInteger<int64_t, intrinsics::Float32>),
145 int8_t{FPFlags::DYN});
146 TEST_UNSUPPORTED((intrinsics::FCvtFloatToInteger<int64_t, intrinsics::Float32>),
147 int8_t{FPFlags::RNE});
148 TEST_SUPPORTED((intrinsics::FCvtFloatToIntegerHostRounding<int64_t, intrinsics::Float32>));
149 TEST_SUPPORTED((intrinsics::FCvtFloatToInteger<int32_t, intrinsics::Float64>),
150 int8_t{FPFlags::DYN});
151 TEST_UNSUPPORTED((intrinsics::FCvtFloatToInteger<int32_t, intrinsics::Float64>),
152 int8_t{FPFlags::RNE});
153 TEST_SUPPORTED((intrinsics::FCvtFloatToIntegerHostRounding<int32_t, intrinsics::Float64>));
154 TEST_SUPPORTED((intrinsics::FCvtFloatToInteger<int32_t, intrinsics::Float32>),
155 int8_t{FPFlags::DYN});
156 TEST_UNSUPPORTED((intrinsics::FCvtFloatToInteger<int32_t, intrinsics::Float32>),
157 int8_t{FPFlags::RNE});
158 TEST_SUPPORTED((intrinsics::FCvtFloatToIntegerHostRounding<int32_t, intrinsics::Float32>));
159 }
160
161 #undef TEST_SUPPORTED
162 #undef TEST_UNSUPPORTED
163
164 } // namespace
165
166 } // namespace berberis
167