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 "call_intrinsic.h"
20
21 #include <cstdint>
22 #include <tuple>
23
24 #include "berberis/assembler/machine_code.h"
25 #include "berberis/backend/code_emitter.h"
26 #include "berberis/backend/x86_64/code_gen.h"
27 #include "berberis/backend/x86_64/machine_ir.h"
28 #include "berberis/backend/x86_64/machine_ir_builder.h"
29 #include "berberis/backend/x86_64/machine_ir_check.h"
30 #include "berberis/base/arena_alloc.h"
31 #include "berberis/base/bit_util.h"
32 #include "berberis/code_gen_lib/code_gen_lib.h"
33 #include "berberis/test_utils/scoped_exec_region.h"
34
35 namespace berberis {
36
37 namespace {
38
39 class ExecTest {
40 public:
41 ExecTest() = default;
42
Init(x86_64::MachineIR * machine_ir)43 void Init(x86_64::MachineIR* machine_ir) {
44 auto* jump = machine_ir->template NewInsn<PseudoJump>(0);
45 machine_ir->bb_list().back()->insn_list().push_back(jump);
46
47 EXPECT_EQ(x86_64::CheckMachineIR(*machine_ir), x86_64::kMachineIRCheckSuccess);
48
49 MachineCode machine_code;
50 CodeEmitter as(
51 &machine_code, machine_ir->FrameSize(), machine_ir->NumBasicBlocks(), machine_ir->arena());
52
53 // We need to set exit_label_for_testing before Emit, which checks it.
54 auto* exit_label = as.MakeLabel();
55 as.set_exit_label_for_testing(exit_label);
56
57 // Save callee saved regs.
58 as.Push(as.rbp);
59 as.Push(as.rbx);
60 as.Push(as.r12);
61 as.Push(as.r13);
62 as.Push(as.r14);
63 as.Push(as.r15);
64 // Align stack for calls.
65 as.Subq(as.rsp, 8);
66
67 x86_64::GenCode(machine_ir, &machine_code, x86_64::GenCodeParams{.skip_emit = true});
68 machine_ir->Emit(&as);
69
70 as.Bind(exit_label);
71
72 as.Addq(as.rsp, 8);
73 // Restore callee saved regs.
74 as.Pop(as.r15);
75 as.Pop(as.r14);
76 as.Pop(as.r13);
77 as.Pop(as.r12);
78 as.Pop(as.rbx);
79 as.Pop(as.rbp);
80
81 as.Ret();
82
83 as.Finalize();
84
85 exec_.Init(&machine_code);
86 }
87
Exec() const88 void Exec() const { exec_.get<void()>()(); }
89
90 private:
91 ScopedExecRegion exec_;
92 };
93
CopyU64(uint64_t)94 __attribute__((naked)) std::tuple<uint64_t> CopyU64(uint64_t) {
95 asm(R"(
96 movq %rdi, %rax
97 ret
98 )");
99 }
100
101 template <typename IntrinsicFunc,
102 typename T,
103 typename std::enable_if_t<std::is_integral_v<T>, bool> = true>
CallOneArgumentIntrinsicUseIntegral(IntrinsicFunc func,T argument,uint64_t * result)104 void CallOneArgumentIntrinsicUseIntegral(IntrinsicFunc func, T argument, uint64_t* result) {
105 Arena arena;
106 x86_64::MachineIR machine_ir(&arena);
107
108 x86_64::MachineIRBuilder builder(&machine_ir);
109 builder.StartBasicBlock(machine_ir.NewBasicBlock());
110 MachineReg flag_register = builder.ir()->AllocVReg();
111 MachineReg result_register = builder.ir()->AllocVReg();
112 MachineReg result_value_addr_reg = builder.ir()->AllocVReg();
113
114 CallIntrinsicImpl(&builder, func, result_register, flag_register, argument);
115
116 builder.Gen<x86_64::MovqRegImm>(result_value_addr_reg, bit_cast<uintptr_t>(result));
117 builder.Gen<x86_64::MovqMemBaseDispReg>(result_value_addr_reg, 0, result_register);
118
119 ExecTest test;
120 test.Init(&machine_ir);
121 test.Exec();
122 }
123
124 template <typename IntrinsicFunc>
CallOneArgumentIntrinsicUseRegister(IntrinsicFunc func,uint64_t argument,uint64_t * result)125 void CallOneArgumentIntrinsicUseRegister(IntrinsicFunc func, uint64_t argument, uint64_t* result) {
126 Arena arena;
127 x86_64::MachineIR machine_ir(&arena);
128
129 x86_64::MachineIRBuilder builder(&machine_ir);
130 builder.StartBasicBlock(machine_ir.NewBasicBlock());
131 MachineReg flag_register = builder.ir()->AllocVReg();
132 MachineReg argument_register = builder.ir()->AllocVReg();
133 MachineReg result_register = builder.ir()->AllocVReg();
134 MachineReg result_value_addr_reg = builder.ir()->AllocVReg();
135
136 builder.Gen<x86_64::MovqRegImm>(argument_register, argument);
137
138 CallIntrinsicImpl(&builder, func, result_register, flag_register, argument_register);
139
140 builder.Gen<x86_64::MovqRegImm>(result_value_addr_reg, bit_cast<uintptr_t>(result));
141 builder.Gen<x86_64::MovqMemBaseDispReg>(result_value_addr_reg, 0, result_register);
142
143 ExecTest test;
144 test.Init(&machine_ir);
145 test.Exec();
146 }
147
TEST(HeavyOptimizerCallIntrinsicTest,U32Result)148 TEST(HeavyOptimizerCallIntrinsicTest, U32Result) {
149 uint64_t result = 0;
150 CallOneArgumentIntrinsicUseRegister(reinterpret_cast<std::tuple<uint32_t> (*)(uint64_t)>(CopyU64),
151 0xaaaa'bbbb'cccc'eeffULL,
152 &result);
153 EXPECT_EQ(result, 0xffff'ffff'cccc'eeffULL);
154
155 result = 0;
156 CallOneArgumentIntrinsicUseRegister(reinterpret_cast<std::tuple<uint32_t> (*)(uint64_t)>(CopyU64),
157 0xaaaa'bbbb'5ccc'eeffULL,
158 &result);
159 EXPECT_EQ(result, 0x5ccc'eeffULL);
160 }
161
TEST(HeavyOptimizerCallIntrinsicTest,I32Result)162 TEST(HeavyOptimizerCallIntrinsicTest, I32Result) {
163 uint64_t result = 0;
164 CallOneArgumentIntrinsicUseRegister(reinterpret_cast<std::tuple<int32_t> (*)(uint64_t)>(CopyU64),
165 0xaaaa'bbbb'cccc'eeffULL,
166 &result);
167 EXPECT_EQ(result, 0xffff'ffff'cccc'eeffULL);
168
169 result = 0;
170 CallOneArgumentIntrinsicUseRegister(
171 reinterpret_cast<std::tuple<int32_t> (*)(uint64_t)>(CopyU64), 0xcccc'eeffULL, &result);
172 EXPECT_EQ(result, 0xffff'ffff'cccc'eeffULL);
173 }
174
TEST(HeavyOptimizerCallIntrinsicTest,ZeroExtendU8Arg)175 TEST(HeavyOptimizerCallIntrinsicTest, ZeroExtendU8Arg) {
176 uint64_t result = 0;
177 CallOneArgumentIntrinsicUseRegister(reinterpret_cast<std::tuple<uint64_t> (*)(uint8_t)>(CopyU64),
178 0xaaaa'bbbb'cccc'eeffULL,
179 &result);
180 EXPECT_EQ(result, 0xffULL);
181
182 result = 0;
183 CallOneArgumentIntrinsicUseIntegral(reinterpret_cast<std::tuple<uint64_t> (*)(uint8_t)>(CopyU64),
184 static_cast<uint8_t>(0xff),
185 &result);
186 EXPECT_EQ(result, 0xffULL);
187 }
188
TEST(HeavyOptimizerCallIntrinsicTest,ZeroExtendU16Arg)189 TEST(HeavyOptimizerCallIntrinsicTest, ZeroExtendU16Arg) {
190 uint64_t result = 0;
191 CallOneArgumentIntrinsicUseRegister(reinterpret_cast<std::tuple<uint64_t> (*)(uint16_t)>(CopyU64),
192 0xaaaa'bbbb'cccc'eeffULL,
193 &result);
194 EXPECT_EQ(result, 0xeeffULL);
195
196 result = 0;
197 CallOneArgumentIntrinsicUseRegister(
198 reinterpret_cast<std::tuple<uint64_t> (*)(uint16_t)>(CopyU64), 0xeeffULL, &result);
199 EXPECT_EQ(result, 0xeeffULL);
200
201 result = 0;
202 CallOneArgumentIntrinsicUseIntegral(reinterpret_cast<std::tuple<uint64_t> (*)(uint16_t)>(CopyU64),
203 static_cast<uint16_t>(0xaaaa'bbbb'cccc'eeffULL),
204 &result);
205 EXPECT_EQ(result, 0xeeffULL);
206
207 result = 0;
208 CallOneArgumentIntrinsicUseIntegral(reinterpret_cast<std::tuple<uint64_t> (*)(uint16_t)>(CopyU64),
209 static_cast<uint16_t>(0xeeff),
210 &result);
211 EXPECT_EQ(result, 0xeeffULL);
212 }
213
TEST(HeavyOptimizerCallIntrinsicTest,SignExtendU32Arg)214 TEST(HeavyOptimizerCallIntrinsicTest, SignExtendU32Arg) {
215 uint64_t result = 0;
216 CallOneArgumentIntrinsicUseRegister(reinterpret_cast<std::tuple<uint64_t> (*)(uint32_t)>(CopyU64),
217 0xaaaa'bbbb'cccc'eeffULL,
218 &result);
219 EXPECT_EQ(result, 0xffff'ffff'cccc'eeffULL);
220
221 result = 0;
222 CallOneArgumentIntrinsicUseIntegral(reinterpret_cast<std::tuple<uint64_t> (*)(uint32_t)>(CopyU64),
223 static_cast<uint32_t>(0xcccc'eeff),
224 &result);
225 EXPECT_EQ(result, 0xffff'ffff'cccc'eeffULL);
226 }
227
TEST(HeavyOptimizerCallIntrinsicTest,SignExtendI8Arg)228 TEST(HeavyOptimizerCallIntrinsicTest, SignExtendI8Arg) {
229 uint64_t result = 0;
230 CallOneArgumentIntrinsicUseRegister(reinterpret_cast<std::tuple<uint64_t> (*)(int8_t)>(CopyU64),
231 0xaaaa'bbbb'cccc'eeffULL,
232 &result);
233 EXPECT_EQ(result, 0xffff'ffff'ffff'ffffULL);
234
235 result = 0;
236 CallOneArgumentIntrinsicUseIntegral(reinterpret_cast<std::tuple<uint64_t> (*)(int8_t)>(CopyU64),
237 static_cast<int8_t>(0xff),
238 &result);
239 EXPECT_EQ(result, 0xffff'ffff'ffff'ffffULL);
240 }
241
TEST(HeavyOptimizerCallIntrinsicTest,SignExtendI16Arg)242 TEST(HeavyOptimizerCallIntrinsicTest, SignExtendI16Arg) {
243 uint64_t result = 0;
244 CallOneArgumentIntrinsicUseRegister(reinterpret_cast<std::tuple<uint64_t> (*)(int16_t)>(CopyU64),
245 0xaaaa'bbbb'cccc'eeffULL,
246 &result);
247 EXPECT_EQ(result, 0xffff'ffff'ffff'eeffULL);
248
249 result = 0;
250 CallOneArgumentIntrinsicUseIntegral(reinterpret_cast<std::tuple<uint64_t> (*)(int16_t)>(CopyU64),
251 static_cast<int16_t>(0xeeffULL),
252 &result);
253 EXPECT_EQ(result, 0xffff'ffff'ffff'eeffULL);
254 }
255
TEST(HeavyOptimizerCallIntrinsicTest,SignExtendI32Arg)256 TEST(HeavyOptimizerCallIntrinsicTest, SignExtendI32Arg) {
257 uint64_t result = 0;
258 CallOneArgumentIntrinsicUseRegister(reinterpret_cast<std::tuple<uint64_t> (*)(int32_t)>(CopyU64),
259 0xaaaa'bbbb'cccc'eeffULL,
260 &result);
261 EXPECT_EQ(result, 0xffff'ffff'cccc'eeffULL);
262
263 result = 0;
264 CallOneArgumentIntrinsicUseIntegral(reinterpret_cast<std::tuple<uint64_t> (*)(int32_t)>(CopyU64),
265 static_cast<int32_t>(0xcccc'eeff),
266 &result);
267 EXPECT_EQ(result, 0xffff'ffff'cccc'eeffULL);
268 }
269
270 } // namespace
271
272 } // namespace berberis
273