1 /*
2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include <climits>
17
18 #include <gtest/gtest.h>
19 #include <random>
20 #include <sstream>
21
22 #include "libpandabase/os/filesystem.h"
23 #include "mem/base_mem_stats.h"
24 #include "mem/code_allocator.h"
25 #include "mem/pool_manager.h"
26 #include "os/filesystem.h"
27 #include "target/asm_printer.h"
28
29 // NOLINTNEXTLINE(fuchsia-statically-constructed-objects)
30 static std::string g_outputDir = "asm_output";
31
32 // Debug print to stdout
33 #if ENABLE_DEBUG_STDOUT_PRINT
34 #define STDOUT_PRINT
35 #endif // ENABLE_DEBUG_STDOUT_PRINT
36
37 namespace ark::compiler {
38
39 enum ParamCount { ONE = 1, TWO = 2 };
40
41 template <ParamCount PARAMS>
42 using EncodeFuncType =
43 std::conditional_t<PARAMS == ParamCount::TWO, void (Encoder::*)(Reg, Reg, Reg), void (Encoder::*)(Reg, Reg)>;
44
45 template <Arch ARCH, ParamCount PARAMS>
46 class PrinterTest : public testing::TestWithParam<std::pair<std::string_view, EncodeFuncType<PARAMS>>> {
47 public:
48 using Base = testing::TestWithParam<std::pair<std::string_view, EncodeFuncType<PARAMS>>>;
49
PrinterTest()50 PrinterTest()
51 {
52 // NOLINTNEXTLINE(readability-magic-numbers)
53 ark::mem::MemConfig::Initialize(64_MB, 64_MB, 64_MB, 32_MB, 0U, 0U);
54 #ifdef STDOUT_PRINT
55 curr_stream_ = &std::cout;
56 #else
57 currStream_ = new std::stringstream();
58 #endif
59 PoolManager::Initialize();
60 allocator_ = new ArenaAllocator(SpaceType::SPACE_TYPE_COMPILER);
61 // Create printer
62 encoder_ = Encoder::Create(allocator_, ARCH, true);
63 encoder_->InitMasm();
64 regfile_ = RegistersDescription::Create(allocator_, ARCH);
65 SetArch();
66 memStats_ = new BaseMemStats();
67 codeAlloc_ = new (std::nothrow) CodeAllocator(memStats_);
68 // Create dir if it is not exist
69 auto execPath = ark::os::file::File::GetExecutablePath();
70 ASSERT(execPath);
71 execPath_ = execPath.Value();
72 os::CreateDirectories(execPath_ + "/" + g_outputDir);
73 os::CreateDirectories(GetDir());
74 }
75
~PrinterTest()76 ~PrinterTest() override
77 {
78 Logger::Destroy();
79 encoder_->~Encoder();
80 delete allocator_;
81 delete codeAlloc_;
82 delete memStats_;
83 PoolManager::Finalize();
84 ark::mem::MemConfig::Finalize();
85 delete currStream_;
86 }
87
SetArch()88 void SetArch()
89 {
90 bool pabi = false;
91 bool osr = false;
92 bool dyn = false;
93 bool print = true;
94 #ifdef PANDA_COMPILER_TARGET_AARCH32
95 if constexpr (ARCH == Arch::AARCH32) {
96 dirSuffix_ = "aarch32";
97 auto enc = reinterpret_cast<aarch32::Aarch32Assembly *>(encoder_);
98 enc->SetStream(currStream_);
99 callconv_ = CallingConvention::Create(allocator_, enc, regfile_, ARCH, pabi, osr, dyn, print);
100 enc->GetEncoder()->SetRegfile(regfile_);
101 }
102 #endif
103 #ifdef PANDA_COMPILER_TARGET_AARCH64
104 if constexpr (ARCH == Arch::AARCH64) {
105 dirSuffix_ = "aarch64";
106 auto enc = reinterpret_cast<aarch64::Aarch64Assembly *>(encoder_);
107 enc->SetStream(currStream_);
108 callconv_ = CallingConvention::Create(allocator_, enc, regfile_, ARCH, pabi, osr, dyn, print);
109 enc->GetEncoder()->SetRegfile(regfile_);
110 }
111 #endif
112 #ifdef PANDA_COMPILER_TARGET_X86_64
113 if constexpr (ARCH == Arch::X86_64) {
114 dirSuffix_ = "amd64";
115 auto enc = reinterpret_cast<amd64::Amd64Assembly *>(encoder_);
116 enc->SetStream(currStream_);
117 callconv_ = CallingConvention::Create(allocator_, enc, regfile_, ARCH, pabi, osr, dyn, print);
118 enc->GetEncoder()->SetRegfile(regfile_);
119 }
120 #endif
121 }
122
123 NO_MOVE_SEMANTIC(PrinterTest);
124 NO_COPY_SEMANTIC(PrinterTest);
125
GetCodeAllocator()126 CodeAllocator *GetCodeAllocator()
127 {
128 return codeAlloc_;
129 }
130
GetDir()131 std::string GetDir()
132 {
133 return execPath_ + "/" + g_outputDir + "/" + dirSuffix_ + "/";
134 }
135
ResetCodeAllocator(void * ptr,size_t size)136 void ResetCodeAllocator(void *ptr, size_t size)
137 {
138 os::mem::MapRange<std::byte> memRange(static_cast<std::byte *>(ptr), size);
139 memRange.MakeReadWrite();
140 delete codeAlloc_;
141 codeAlloc_ = new (std::nothrow) CodeAllocator(memStats_);
142 }
143
GetAllocator()144 ArenaAllocator *GetAllocator()
145 {
146 return allocator_;
147 }
148
GetEncoder()149 Encoder *GetEncoder()
150 {
151 return encoder_;
152 }
153
GetRegfile()154 RegistersDescription *GetRegfile()
155 {
156 return regfile_;
157 }
158
GetCallconv()159 CallingConvention *GetCallconv()
160 {
161 return callconv_;
162 }
163
GetCursor()164 size_t GetCursor()
165 {
166 return currCursor_;
167 }
168
169 // Warning! Do not use multiply times with different types!
GetParameter(TypeInfo type,size_t id=0U)170 Reg GetParameter(TypeInfo type, size_t id = 0U)
171 {
172 ASSERT(id < 4U);
173 if (type.IsFloat()) {
174 return Reg(id, type);
175 }
176 if constexpr (ARCH == Arch::AARCH32) {
177 // special offset for double-reg param
178 if (id == 1U && type.GetSize() == DOUBLE_WORD_SIZE) {
179 return Target::Current().GetParamReg(2U, type);
180 }
181 }
182 return Target::Current().GetParamReg(id, type);
183 }
184
PreWork()185 void PreWork()
186 {
187 // Curor need to encode multiply tests due one execution
188 currCursor_ = 0;
189 encoder_->SetCursorOffset(0U);
190
191 [[maybe_unused]] std::string funcName = "test_" + GetTestName();
192 #ifdef PANDA_COMPILER_TARGET_AARCH32
193 if constexpr (ARCH == Arch::AARCH32) {
194 auto enc = reinterpret_cast<aarch32::Aarch32Assembly *>(encoder_);
195 enc->EmitFunctionName(reinterpret_cast<const void *>(funcName.c_str()));
196 }
197 #endif
198 #ifdef PANDA_COMPILER_TARGET_AARCH64
199 if constexpr (ARCH == Arch::AARCH64) {
200 auto enc = reinterpret_cast<aarch64::Aarch64Assembly *>(encoder_);
201 enc->EmitFunctionName(reinterpret_cast<const void *>(funcName.c_str()));
202 }
203 #endif
204 #ifdef PANDA_COMPILER_TARGET_X86_64
205 if constexpr (ARCH == Arch::X86_64) {
206 auto enc = reinterpret_cast<amd64::Amd64Assembly *>(encoder_);
207 enc->EmitFunctionName(reinterpret_cast<const void *>(funcName.c_str()));
208 }
209 #endif
210 callconv_->GeneratePrologue(FrameInfo::FullPrologue());
211 }
212
PostWork()213 void PostWork()
214 {
215 auto param = Target::Current().GetParamReg(0U);
216 auto returnReg = Target::Current().GetReturnReg();
217 if (param.GetId() != returnReg.GetId()) {
218 GetEncoder()->EncodeMov(returnReg, param);
219 }
220 callconv_->GenerateEpilogue(FrameInfo::FullPrologue(), []() {});
221 encoder_->Finalize();
222 }
223
DoTest(TypeInfo typeInfo,std::string_view opName,EncodeFuncType<PARAMS> encodeFunc)224 void DoTest(TypeInfo typeInfo, std::string_view opName, EncodeFuncType<PARAMS> encodeFunc)
225 {
226 SetTestName(std::string {opName} + "_" + std::to_string(typeInfo.GetSize()));
227 PreWork();
228 if constexpr (PARAMS == ParamCount::TWO) {
229 auto param1 = GetParameter(typeInfo, 0U);
230 auto param2 = GetParameter(typeInfo, 1U);
231 (GetEncoder()->*encodeFunc)(param1, param1, param2);
232 } else {
233 auto param = GetParameter(typeInfo);
234 (GetEncoder()->*encodeFunc)(param, param);
235 }
236 PostWork();
237 EXPECT_TRUE(GetEncoder()->GetResult());
238 }
239
DoTest()240 void DoTest()
241 {
242 auto [opName, func] = Base::GetParam();
243 for (auto typeId : {TypeInfo::INT8, TypeInfo::INT16, TypeInfo::INT32, TypeInfo::INT64}) {
244 DoTest(TypeInfo {typeId}, opName, func);
245 }
246 SetTestName(std::string {opName});
247 FinalizeTest();
248 }
249
250 #ifdef STDOUT_PRINT
GetStream()251 std::ostream *GetStream()
252 #else
253 std::stringstream *GetStream()
254 #endif
255 {
256 return currStream_;
257 }
258
SetTestName(std::string name)259 void SetTestName(std::string name)
260 {
261 testName_ = std::move(name);
262 }
263
GetTestName()264 std::string GetTestName()
265 {
266 return testName_;
267 }
268
FinalizeTest()269 void FinalizeTest()
270 {
271 // Make them separate!
272 std::string filename = GetTestName() + ".S";
273 std::ofstream asmFile;
274 asmFile.open(GetDir() + "/" + filename);
275 // Test must generate asembly-flie
276 ASSERT(asmFile.is_open());
277 // Compile by assembler
278 #ifndef STDOUT_PRINT
279 asmFile << GetStream()->str();
280 #endif
281 asmFile.close();
282 }
283
284 private:
285 ArenaAllocator *allocator_ {nullptr};
286 Encoder *encoder_ {nullptr};
287 RegistersDescription *regfile_ {nullptr};
288 // Callconv for printing
289 CallingConvention *cc_ {nullptr};
290 // Callconv for masm initialization
291 CallingConvention *callconv_ {nullptr};
292 CodeAllocator *codeAlloc_ {nullptr};
293 BaseMemStats *memStats_ {nullptr};
294 size_t currCursor_ {0U};
295 #ifdef STDOUT_PRINT
296 std::ostream *curr_stream_;
297 #else
298 std::stringstream *currStream_;
299 #endif
300 std::string testName_;
301 std::string execPath_;
302 std::string dirSuffix_;
303 };
304
Values1()305 static auto Values1()
306 {
307 std::vector<std::pair<std::string_view, EncodeFuncType<ONE>>> singleFunctions {{"mov", &Encoder::EncodeMov},
308 {"neg", &Encoder::EncodeNeg},
309 {"abs", &Encoder::EncodeAbs},
310 {"not", &Encoder::EncodeNot}};
311 return testing::ValuesIn(singleFunctions);
312 }
313
Values2()314 static auto Values2()
315 {
316 std::vector<std::pair<std::string_view, EncodeFuncType<TWO>>> doubleFunctions {
317 {"add", &Encoder::EncodeAdd}, {"sub", &Encoder::EncodeSub}, {"mul", &Encoder::EncodeMul},
318 {"shl", &Encoder::EncodeShl}, {"shr", &Encoder::EncodeShr}, {"ashr", &Encoder::EncodeAShr},
319 {"and", &Encoder::EncodeAnd}, {"or", &Encoder::EncodeOr}, {"xor", &Encoder::EncodeXor}};
320 return testing::ValuesIn(doubleFunctions);
321 }
322
323 struct GetGTestName {
324 template <typename T>
operator ()ark::compiler::GetGTestName325 std::string operator()(const T &value)
326 {
327 return std::string {"test_"} + std::string {value.param.first};
328 }
329 };
330
331 // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
332 #define CREATE_TEST(ARCH, PARAMS) \
333 using Printer_##ARCH##_##PARAMS = PrinterTest<Arch::ARCH, static_cast<ParamCount>(PARAMS)>; \
334 TEST_P(Printer_##ARCH##_##PARAMS, Test) \
335 { \
336 DoTest(); \
337 } \
338 INSTANTIATE_TEST_SUITE_P(Printer_##ARCH##_##PARAMS, Printer_##ARCH##_##PARAMS, Values##PARAMS(), GetGTestName {})
339
340 #ifdef PANDA_COMPILER_TARGET_X86_64
341 CREATE_TEST(X86_64, 1);
342 CREATE_TEST(X86_64, 2);
343 #endif
344
345 #ifdef PANDA_COMPILER_TARGET_AARCH64
346 CREATE_TEST(AARCH64, 1);
347 CREATE_TEST(AARCH64, 2);
348 #endif
349
350 #ifdef PANDA_COMPILER_TARGET_AARCH32
351 CREATE_TEST(AARCH32, 1);
352 CREATE_TEST(AARCH32, 2);
353 #endif
354
355 } // namespace ark::compiler
356