• 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 #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