blob: 3fe1a31d70dcf7e2dbf4fc5f441204f0324afcbb [file] [log] [blame]
Andreas Gampe5a4fa822014-03-31 16:50:12 -07001/*
2 * Copyright (C) 2014 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 ART_COMPILER_UTILS_ASSEMBLER_TEST_H_
18#define ART_COMPILER_UTILS_ASSEMBLER_TEST_H_
19
20#include "assembler.h"
21
Andreas Gampeb40c6a72014-05-02 14:25:12 -070022#include "common_runtime_test.h" // For ScratchFile
Andreas Gampe5a4fa822014-03-31 16:50:12 -070023
24#include <cstdio>
25#include <cstdlib>
26#include <fstream>
Andreas Gampe5a4fa822014-03-31 16:50:12 -070027#include <iterator>
28#include <sys/stat.h>
29
30namespace art {
31
Andreas Gampe65bec692015-01-14 12:03:36 -080032// If you want to take a look at the differences between the ART assembler and GCC, set this flag
33// to true. The disassembled files will then remain in the tmp directory.
34static constexpr bool kKeepDisassembledFiles = false;
35
Andreas Gampe851df202014-11-12 14:05:46 -080036// Helper for a constexpr string length.
37constexpr size_t ConstexprStrLen(char const* str, size_t count = 0) {
38 return ('\0' == str[0]) ? count : ConstexprStrLen(str+1, count+1);
39}
40
Andreas Gampeb40c6a72014-05-02 14:25:12 -070041// Use a glocal static variable to keep the same name for all test data. Else we'll just spam the
42// temp directory.
43static std::string tmpnam_;
44
Andreas Gampe849cc5e2014-11-18 13:46:46 -080045enum class RegisterView { // private
46 kUsePrimaryName,
Chao-ying Fud23840d2015-04-07 16:03:04 -070047 kUseSecondaryName,
48 kUseTertiaryName,
49 kUseQuaternaryName,
Andreas Gampe849cc5e2014-11-18 13:46:46 -080050};
51
Andreas Gampe851df202014-11-12 14:05:46 -080052template<typename Ass, typename Reg, typename FPReg, typename Imm>
Andreas Gampe5a4fa822014-03-31 16:50:12 -070053class AssemblerTest : public testing::Test {
54 public:
55 Ass* GetAssembler() {
56 return assembler_.get();
57 }
58
Andreas Gampe851df202014-11-12 14:05:46 -080059 typedef std::string (*TestFn)(AssemblerTest* assembler_test, Ass* assembler);
Andreas Gampe5a4fa822014-03-31 16:50:12 -070060
61 void DriverFn(TestFn f, std::string test_name) {
Andreas Gampe851df202014-11-12 14:05:46 -080062 Driver(f(this, assembler_.get()), test_name);
Andreas Gampe5a4fa822014-03-31 16:50:12 -070063 }
64
65 // This driver assumes the assembler has already been called.
66 void DriverStr(std::string assembly_string, std::string test_name) {
67 Driver(assembly_string, test_name);
68 }
69
70 std::string RepeatR(void (Ass::*f)(Reg), std::string fmt) {
Andreas Gampe851df202014-11-12 14:05:46 -080071 return RepeatTemplatedRegister<Reg>(f,
72 GetRegisters(),
73 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
74 fmt);
75 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -070076
Andreas Gampe851df202014-11-12 14:05:46 -080077 std::string Repeatr(void (Ass::*f)(Reg), std::string fmt) {
78 return RepeatTemplatedRegister<Reg>(f,
79 GetRegisters(),
80 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
81 fmt);
Andreas Gampe5a4fa822014-03-31 16:50:12 -070082 }
83
84 std::string RepeatRR(void (Ass::*f)(Reg, Reg), std::string fmt) {
Andreas Gampe851df202014-11-12 14:05:46 -080085 return RepeatTemplatedRegisters<Reg, Reg>(f,
86 GetRegisters(),
87 GetRegisters(),
88 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
89 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
90 fmt);
91 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -070092
Andreas Gampe851df202014-11-12 14:05:46 -080093 std::string Repeatrr(void (Ass::*f)(Reg, Reg), std::string fmt) {
94 return RepeatTemplatedRegisters<Reg, Reg>(f,
95 GetRegisters(),
96 GetRegisters(),
97 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
98 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
99 fmt);
100 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700101
Chao-ying Fud23840d2015-04-07 16:03:04 -0700102 std::string Repeatrb(void (Ass::*f)(Reg, Reg), std::string fmt) {
103 return RepeatTemplatedRegisters<Reg, Reg>(f,
104 GetRegisters(),
105 GetRegisters(),
106 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
107 &AssemblerTest::GetRegName<RegisterView::kUseQuaternaryName>,
108 fmt);
109 }
110
Andreas Gampe851df202014-11-12 14:05:46 -0800111 std::string RepeatRr(void (Ass::*f)(Reg, Reg), std::string fmt) {
112 return RepeatTemplatedRegisters<Reg, Reg>(f,
113 GetRegisters(),
114 GetRegisters(),
115 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
116 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
117 fmt);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700118 }
119
120 std::string RepeatRI(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes, std::string fmt) {
Andreas Gampe851df202014-11-12 14:05:46 -0800121 return RepeatRegisterImm<RegisterView::kUsePrimaryName>(f, imm_bytes, fmt);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700122 }
123
Andreas Gampe851df202014-11-12 14:05:46 -0800124 std::string Repeatri(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes, std::string fmt) {
125 return RepeatRegisterImm<RegisterView::kUseSecondaryName>(f, imm_bytes, fmt);
126 }
127
128 std::string RepeatFF(void (Ass::*f)(FPReg, FPReg), std::string fmt) {
129 return RepeatTemplatedRegisters<FPReg, FPReg>(f,
130 GetFPRegisters(),
131 GetFPRegisters(),
132 &AssemblerTest::GetFPRegName,
133 &AssemblerTest::GetFPRegName,
134 fmt);
135 }
136
Mark Mendellfb8d2792015-03-31 22:16:59 -0400137 std::string RepeatFFI(void (Ass::*f)(FPReg, FPReg, const Imm&), size_t imm_bytes, std::string fmt) {
138 return RepeatTemplatedRegistersImm<FPReg, FPReg>(f,
139 GetFPRegisters(),
140 GetFPRegisters(),
141 &AssemblerTest::GetFPRegName,
142 &AssemblerTest::GetFPRegName,
143 imm_bytes,
144 fmt);
145 }
146
Andreas Gampe851df202014-11-12 14:05:46 -0800147 std::string RepeatFR(void (Ass::*f)(FPReg, Reg), std::string fmt) {
148 return RepeatTemplatedRegisters<FPReg, Reg>(f,
149 GetFPRegisters(),
150 GetRegisters(),
151 &AssemblerTest::GetFPRegName,
152 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
153 fmt);
154 }
155
156 std::string RepeatFr(void (Ass::*f)(FPReg, Reg), std::string fmt) {
157 return RepeatTemplatedRegisters<FPReg, Reg>(f,
158 GetFPRegisters(),
159 GetRegisters(),
160 &AssemblerTest::GetFPRegName,
161 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
162 fmt);
163 }
164
165 std::string RepeatRF(void (Ass::*f)(Reg, FPReg), std::string fmt) {
166 return RepeatTemplatedRegisters<Reg, FPReg>(f,
167 GetRegisters(),
168 GetFPRegisters(),
169 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
170 &AssemblerTest::GetFPRegName,
171 fmt);
172 }
173
174 std::string RepeatrF(void (Ass::*f)(Reg, FPReg), std::string fmt) {
175 return RepeatTemplatedRegisters<Reg, FPReg>(f,
176 GetRegisters(),
177 GetFPRegisters(),
178 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
179 &AssemblerTest::GetFPRegName,
180 fmt);
181 }
182
183 std::string RepeatI(void (Ass::*f)(const Imm&), size_t imm_bytes, std::string fmt,
184 bool as_uint = false) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700185 std::string str;
Andreas Gampe851df202014-11-12 14:05:46 -0800186 std::vector<int64_t> imms = CreateImmediateValues(imm_bytes, as_uint);
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800187
188 WarnOnCombinations(imms.size());
189
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700190 for (int64_t imm : imms) {
Ian Rogerscf7f1912014-10-22 22:06:39 -0700191 Imm new_imm = CreateImmediate(imm);
192 (assembler_.get()->*f)(new_imm);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700193 std::string base = fmt;
194
Andreas Gampe851df202014-11-12 14:05:46 -0800195 size_t imm_index = base.find(IMM_TOKEN);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700196 if (imm_index != std::string::npos) {
197 std::ostringstream sreg;
198 sreg << imm;
199 std::string imm_string = sreg.str();
Andreas Gampe851df202014-11-12 14:05:46 -0800200 base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700201 }
202
203 if (str.size() > 0) {
204 str += "\n";
205 }
206 str += base;
207 }
208 // Add a newline at the end.
209 str += "\n";
210 return str;
211 }
212
213 // This is intended to be run as a test.
214 bool CheckTools() {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800215 if (!FileExists(FindTool(GetAssemblerCmdName()))) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700216 return false;
217 }
218 LOG(INFO) << "Chosen assembler command: " << GetAssemblerCommand();
219
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800220 if (!FileExists(FindTool(GetObjdumpCmdName()))) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700221 return false;
222 }
223 LOG(INFO) << "Chosen objdump command: " << GetObjdumpCommand();
224
225 // Disassembly is optional.
226 std::string disassembler = GetDisassembleCommand();
227 if (disassembler.length() != 0) {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800228 if (!FileExists(FindTool(GetDisassembleCmdName()))) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700229 return false;
230 }
231 LOG(INFO) << "Chosen disassemble command: " << GetDisassembleCommand();
232 } else {
233 LOG(INFO) << "No disassembler given.";
234 }
235
236 return true;
237 }
238
Andreas Gampe851df202014-11-12 14:05:46 -0800239 // The following functions are public so that TestFn can use them...
240
241 virtual std::vector<Reg*> GetRegisters() = 0;
242
243 virtual std::vector<FPReg*> GetFPRegisters() {
244 UNIMPLEMENTED(FATAL) << "Architecture does not support floating-point registers";
245 UNREACHABLE();
246 }
247
248 // Secondary register names are the secondary view on registers, e.g., 32b on 64b systems.
249 virtual std::string GetSecondaryRegisterName(const Reg& reg ATTRIBUTE_UNUSED) {
250 UNIMPLEMENTED(FATAL) << "Architecture does not support secondary registers";
251 UNREACHABLE();
252 }
253
Chao-ying Fud23840d2015-04-07 16:03:04 -0700254 // Tertiary register names are the tertiary view on registers, e.g., 16b on 64b systems.
255 virtual std::string GetTertiaryRegisterName(const Reg& reg ATTRIBUTE_UNUSED) {
256 UNIMPLEMENTED(FATAL) << "Architecture does not support tertiary registers";
257 UNREACHABLE();
258 }
259
260 // Quaternary register names are the quaternary view on registers, e.g., 8b on 64b systems.
261 virtual std::string GetQuaternaryRegisterName(const Reg& reg ATTRIBUTE_UNUSED) {
262 UNIMPLEMENTED(FATAL) << "Architecture does not support quaternary registers";
263 UNREACHABLE();
264 }
265
Calin Juravle9aec02f2014-11-18 23:06:35 +0000266 std::string GetRegisterName(const Reg& reg) {
267 return GetRegName<RegisterView::kUsePrimaryName>(reg);
268 }
269
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700270 protected:
Andreas Gampe851df202014-11-12 14:05:46 -0800271 explicit AssemblerTest() {}
272
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700273 void SetUp() OVERRIDE {
274 assembler_.reset(new Ass());
275
Andreas Gampeb40c6a72014-05-02 14:25:12 -0700276 // Fake a runtime test for ScratchFile
Andreas Gampe7747c8d2014-08-06 14:53:03 -0700277 CommonRuntimeTest::SetUpAndroidData(android_data_);
Andreas Gampeb40c6a72014-05-02 14:25:12 -0700278
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700279 SetUpHelpers();
280 }
281
Andreas Gampe7747c8d2014-08-06 14:53:03 -0700282 void TearDown() OVERRIDE {
283 // We leave temporaries in case this failed so we can debug issues.
284 CommonRuntimeTest::TearDownAndroidData(android_data_, false);
285 tmpnam_ = "";
286 }
287
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700288 // Override this to set up any architecture-specific things, e.g., register vectors.
289 virtual void SetUpHelpers() {}
290
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700291 // Get the typically used name for this architecture, e.g., aarch64, x86_64, ...
292 virtual std::string GetArchitectureString() = 0;
293
294 // Get the name of the assembler, e.g., "as" by default.
295 virtual std::string GetAssemblerCmdName() {
296 return "as";
297 }
298
299 // Switches to the assembler command. Default none.
300 virtual std::string GetAssemblerParameters() {
301 return "";
302 }
303
304 // Return the host assembler command for this test.
305 virtual std::string GetAssemblerCommand() {
306 // Already resolved it once?
307 if (resolved_assembler_cmd_.length() != 0) {
308 return resolved_assembler_cmd_;
309 }
310
311 std::string line = FindTool(GetAssemblerCmdName());
312 if (line.length() == 0) {
313 return line;
314 }
315
316 resolved_assembler_cmd_ = line + GetAssemblerParameters();
317
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800318 return resolved_assembler_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700319 }
320
321 // Get the name of the objdump, e.g., "objdump" by default.
322 virtual std::string GetObjdumpCmdName() {
323 return "objdump";
324 }
325
326 // Switches to the objdump command. Default is " -h".
327 virtual std::string GetObjdumpParameters() {
328 return " -h";
329 }
330
331 // Return the host objdump command for this test.
332 virtual std::string GetObjdumpCommand() {
333 // Already resolved it once?
334 if (resolved_objdump_cmd_.length() != 0) {
335 return resolved_objdump_cmd_;
336 }
337
338 std::string line = FindTool(GetObjdumpCmdName());
339 if (line.length() == 0) {
340 return line;
341 }
342
343 resolved_objdump_cmd_ = line + GetObjdumpParameters();
344
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800345 return resolved_objdump_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700346 }
347
348 // Get the name of the objdump, e.g., "objdump" by default.
349 virtual std::string GetDisassembleCmdName() {
350 return "objdump";
351 }
352
353 // Switches to the objdump command. As it's a binary, one needs to push the architecture and
354 // such to objdump, so it's architecture-specific and there is no default.
355 virtual std::string GetDisassembleParameters() = 0;
356
357 // Return the host disassembler command for this test.
358 virtual std::string GetDisassembleCommand() {
359 // Already resolved it once?
360 if (resolved_disassemble_cmd_.length() != 0) {
361 return resolved_disassemble_cmd_;
362 }
363
364 std::string line = FindTool(GetDisassembleCmdName());
365 if (line.length() == 0) {
366 return line;
367 }
368
369 resolved_disassemble_cmd_ = line + GetDisassembleParameters();
370
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800371 return resolved_disassemble_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700372 }
373
374 // Create a couple of immediate values up to the number of bytes given.
Andreas Gampe851df202014-11-12 14:05:46 -0800375 virtual std::vector<int64_t> CreateImmediateValues(size_t imm_bytes, bool as_uint = false) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700376 std::vector<int64_t> res;
377 res.push_back(0);
Andreas Gampe851df202014-11-12 14:05:46 -0800378 if (!as_uint) {
379 res.push_back(-1);
380 } else {
381 res.push_back(0xFF);
382 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700383 res.push_back(0x12);
384 if (imm_bytes >= 2) {
385 res.push_back(0x1234);
Andreas Gampe851df202014-11-12 14:05:46 -0800386 if (!as_uint) {
387 res.push_back(-0x1234);
388 } else {
389 res.push_back(0xFFFF);
390 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700391 if (imm_bytes >= 4) {
392 res.push_back(0x12345678);
Andreas Gampe851df202014-11-12 14:05:46 -0800393 if (!as_uint) {
394 res.push_back(-0x12345678);
395 } else {
396 res.push_back(0xFFFFFFFF);
397 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700398 if (imm_bytes >= 6) {
399 res.push_back(0x123456789ABC);
Andreas Gampe851df202014-11-12 14:05:46 -0800400 if (!as_uint) {
401 res.push_back(-0x123456789ABC);
402 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700403 if (imm_bytes >= 8) {
404 res.push_back(0x123456789ABCDEF0);
Andreas Gampe851df202014-11-12 14:05:46 -0800405 if (!as_uint) {
406 res.push_back(-0x123456789ABCDEF0);
407 } else {
408 res.push_back(0xFFFFFFFFFFFFFFFF);
409 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700410 }
411 }
412 }
413 }
414 return res;
415 }
416
417 // Create an immediate from the specific value.
Ian Rogerscf7f1912014-10-22 22:06:39 -0700418 virtual Imm CreateImmediate(int64_t imm_value) = 0;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700419
Andreas Gampe851df202014-11-12 14:05:46 -0800420 template <typename RegType>
421 std::string RepeatTemplatedRegister(void (Ass::*f)(RegType),
422 const std::vector<RegType*> registers,
423 std::string (AssemblerTest::*GetName)(const RegType&),
424 std::string fmt) {
425 std::string str;
426 for (auto reg : registers) {
427 (assembler_.get()->*f)(*reg);
428 std::string base = fmt;
429
430 std::string reg_string = (this->*GetName)(*reg);
431 size_t reg_index;
432 if ((reg_index = base.find(REG_TOKEN)) != std::string::npos) {
433 base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string);
434 }
435
436 if (str.size() > 0) {
437 str += "\n";
438 }
439 str += base;
440 }
441 // Add a newline at the end.
442 str += "\n";
443 return str;
444 }
445
446 template <typename Reg1, typename Reg2>
447 std::string RepeatTemplatedRegisters(void (Ass::*f)(Reg1, Reg2),
448 const std::vector<Reg1*> reg1_registers,
449 const std::vector<Reg2*> reg2_registers,
450 std::string (AssemblerTest::*GetName1)(const Reg1&),
451 std::string (AssemblerTest::*GetName2)(const Reg2&),
452 std::string fmt) {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800453 WarnOnCombinations(reg1_registers.size() * reg2_registers.size());
454
Andreas Gampe851df202014-11-12 14:05:46 -0800455 std::string str;
456 for (auto reg1 : reg1_registers) {
457 for (auto reg2 : reg2_registers) {
458 (assembler_.get()->*f)(*reg1, *reg2);
459 std::string base = fmt;
460
461 std::string reg1_string = (this->*GetName1)(*reg1);
462 size_t reg1_index;
463 while ((reg1_index = base.find(REG1_TOKEN)) != std::string::npos) {
464 base.replace(reg1_index, ConstexprStrLen(REG1_TOKEN), reg1_string);
465 }
466
467 std::string reg2_string = (this->*GetName2)(*reg2);
468 size_t reg2_index;
469 while ((reg2_index = base.find(REG2_TOKEN)) != std::string::npos) {
470 base.replace(reg2_index, ConstexprStrLen(REG2_TOKEN), reg2_string);
471 }
472
473 if (str.size() > 0) {
474 str += "\n";
475 }
476 str += base;
477 }
478 }
479 // Add a newline at the end.
480 str += "\n";
481 return str;
482 }
483
Mark Mendellfb8d2792015-03-31 22:16:59 -0400484 template <typename Reg1, typename Reg2>
485 std::string RepeatTemplatedRegistersImm(void (Ass::*f)(Reg1, Reg2, const Imm&),
486 const std::vector<Reg1*> reg1_registers,
487 const std::vector<Reg2*> reg2_registers,
488 std::string (AssemblerTest::*GetName1)(const Reg1&),
489 std::string (AssemblerTest::*GetName2)(const Reg2&),
490 size_t imm_bytes,
491 std::string fmt) {
492 std::vector<int64_t> imms = CreateImmediateValues(imm_bytes);
493 WarnOnCombinations(reg1_registers.size() * reg2_registers.size() * imms.size());
494
495 std::string str;
496 for (auto reg1 : reg1_registers) {
497 for (auto reg2 : reg2_registers) {
498 for (int64_t imm : imms) {
499 Imm new_imm = CreateImmediate(imm);
500 (assembler_.get()->*f)(*reg1, *reg2, new_imm);
501 std::string base = fmt;
502
503 std::string reg1_string = (this->*GetName1)(*reg1);
504 size_t reg1_index;
505 while ((reg1_index = base.find(REG1_TOKEN)) != std::string::npos) {
506 base.replace(reg1_index, ConstexprStrLen(REG1_TOKEN), reg1_string);
507 }
508
509 std::string reg2_string = (this->*GetName2)(*reg2);
510 size_t reg2_index;
511 while ((reg2_index = base.find(REG2_TOKEN)) != std::string::npos) {
512 base.replace(reg2_index, ConstexprStrLen(REG2_TOKEN), reg2_string);
513 }
514
515 size_t imm_index = base.find(IMM_TOKEN);
516 if (imm_index != std::string::npos) {
517 std::ostringstream sreg;
518 sreg << imm;
519 std::string imm_string = sreg.str();
520 base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
521 }
522
523 if (str.size() > 0) {
524 str += "\n";
525 }
526 str += base;
527 }
528 }
529 }
530 // Add a newline at the end.
531 str += "\n";
532 return str;
533 }
534
Andreas Gampe851df202014-11-12 14:05:46 -0800535 template <RegisterView kRegView>
536 std::string GetRegName(const Reg& reg) {
537 std::ostringstream sreg;
538 switch (kRegView) {
539 case RegisterView::kUsePrimaryName:
540 sreg << reg;
541 break;
542
543 case RegisterView::kUseSecondaryName:
544 sreg << GetSecondaryRegisterName(reg);
545 break;
Chao-ying Fud23840d2015-04-07 16:03:04 -0700546
547 case RegisterView::kUseTertiaryName:
548 sreg << GetTertiaryRegisterName(reg);
549 break;
550
551 case RegisterView::kUseQuaternaryName:
552 sreg << GetQuaternaryRegisterName(reg);
553 break;
Andreas Gampe851df202014-11-12 14:05:46 -0800554 }
555 return sreg.str();
556 }
557
558 std::string GetFPRegName(const FPReg& reg) {
559 std::ostringstream sreg;
560 sreg << reg;
561 return sreg.str();
562 }
563
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800564 // If the assembly file needs a header, return it in a sub-class.
565 virtual const char* GetAssemblyHeader() {
566 return nullptr;
567 }
568
569 void WarnOnCombinations(size_t count) {
570 if (count > kWarnManyCombinationsThreshold) {
571 GTEST_LOG_(WARNING) << "Many combinations (" << count << "), test generation might be slow.";
572 }
573 }
574
575 static constexpr const char* REG_TOKEN = "{reg}";
576 static constexpr const char* REG1_TOKEN = "{reg1}";
577 static constexpr const char* REG2_TOKEN = "{reg2}";
578 static constexpr const char* IMM_TOKEN = "{imm}";
579
580 private:
Andreas Gampe851df202014-11-12 14:05:46 -0800581 template <RegisterView kRegView>
582 std::string RepeatRegisterImm(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes,
583 std::string fmt) {
584 const std::vector<Reg*> registers = GetRegisters();
585 std::string str;
586 std::vector<int64_t> imms = CreateImmediateValues(imm_bytes);
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800587
588 WarnOnCombinations(registers.size() * imms.size());
589
Andreas Gampe851df202014-11-12 14:05:46 -0800590 for (auto reg : registers) {
591 for (int64_t imm : imms) {
592 Imm new_imm = CreateImmediate(imm);
593 (assembler_.get()->*f)(*reg, new_imm);
594 std::string base = fmt;
595
596 std::string reg_string = GetRegName<kRegView>(*reg);
597 size_t reg_index;
598 while ((reg_index = base.find(REG_TOKEN)) != std::string::npos) {
599 base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string);
600 }
601
602 size_t imm_index = base.find(IMM_TOKEN);
603 if (imm_index != std::string::npos) {
604 std::ostringstream sreg;
605 sreg << imm;
606 std::string imm_string = sreg.str();
607 base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
608 }
609
610 if (str.size() > 0) {
611 str += "\n";
612 }
613 str += base;
614 }
615 }
616 // Add a newline at the end.
617 str += "\n";
618 return str;
619 }
620
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700621 // Driver() assembles and compares the results. If the results are not equal and we have a
622 // disassembler, disassemble both and check whether they have the same mnemonics (in which case
623 // we just warn).
624 void Driver(std::string assembly_text, std::string test_name) {
625 EXPECT_NE(assembly_text.length(), 0U) << "Empty assembly";
626
627 NativeAssemblerResult res;
628 Compile(assembly_text, &res, test_name);
629
630 EXPECT_TRUE(res.ok) << res.error_msg;
631 if (!res.ok) {
632 // No way of continuing.
633 return;
634 }
635
636 size_t cs = assembler_->CodeSize();
Ian Rogers700a4022014-05-19 16:49:03 -0700637 std::unique_ptr<std::vector<uint8_t>> data(new std::vector<uint8_t>(cs));
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700638 MemoryRegion code(&(*data)[0], data->size());
639 assembler_->FinalizeInstructions(code);
640
641 if (*data == *res.code) {
642 Clean(&res);
643 } else {
644 if (DisassembleBinaries(*data, *res.code, test_name)) {
645 if (data->size() > res.code->size()) {
Andreas Gampe54e15de2014-08-06 15:31:06 -0700646 // Fail this test with a fancy colored warning being printed.
647 EXPECT_TRUE(false) << "Assembly code is not identical, but disassembly of machine code "
648 "is equal: this implies sub-optimal encoding! Our code size=" << data->size() <<
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700649 ", gcc size=" << res.code->size();
650 } else {
Andreas Gampe54e15de2014-08-06 15:31:06 -0700651 // Otherwise just print an info message and clean up.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700652 LOG(INFO) << "GCC chose a different encoding than ours, but the overall length is the "
653 "same.";
Andreas Gampe54e15de2014-08-06 15:31:06 -0700654 Clean(&res);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700655 }
656 } else {
657 // This will output the assembly.
Nicolas Geoffray102cbed2014-10-15 18:31:05 +0100658 EXPECT_EQ(*res.code, *data) << "Outputs (and disassembly) not identical.";
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700659 }
660 }
661 }
662
663 // Structure to store intermediates and results.
664 struct NativeAssemblerResult {
665 bool ok;
666 std::string error_msg;
667 std::string base_name;
Ian Rogers700a4022014-05-19 16:49:03 -0700668 std::unique_ptr<std::vector<uint8_t>> code;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700669 uintptr_t length;
670 };
671
672 // Compile the assembly file from_file to a binary file to_file. Returns true on success.
673 bool Assemble(const char* from_file, const char* to_file, std::string* error_msg) {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800674 bool have_assembler = FileExists(FindTool(GetAssemblerCmdName()));
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700675 EXPECT_TRUE(have_assembler) << "Cannot find assembler:" << GetAssemblerCommand();
676 if (!have_assembler) {
677 return false;
678 }
679
680 std::vector<std::string> args;
681
Roland Levillain1a28fc42014-11-13 18:03:06 +0000682 // Encaspulate the whole command line in a single string passed to
683 // the shell, so that GetAssemblerCommand() may contain arguments
684 // in addition to the program name.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700685 args.push_back(GetAssemblerCommand());
686 args.push_back("-o");
687 args.push_back(to_file);
688 args.push_back(from_file);
Roland Levillain1a28fc42014-11-13 18:03:06 +0000689 std::string cmd = Join(args, ' ');
690
691 args.clear();
692 args.push_back("/bin/sh");
693 args.push_back("-c");
694 args.push_back(cmd);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700695
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800696 bool success = Exec(args, error_msg);
697 if (!success) {
698 LOG(INFO) << "Assembler command line:";
699 for (std::string arg : args) {
700 LOG(INFO) << arg;
701 }
702 }
703 return success;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700704 }
705
706 // Runs objdump -h on the binary file and extracts the first line with .text.
707 // Returns "" on failure.
708 std::string Objdump(std::string file) {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800709 bool have_objdump = FileExists(FindTool(GetObjdumpCmdName()));
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700710 EXPECT_TRUE(have_objdump) << "Cannot find objdump: " << GetObjdumpCommand();
711 if (!have_objdump) {
712 return "";
713 }
714
715 std::string error_msg;
716 std::vector<std::string> args;
717
Roland Levillain1a28fc42014-11-13 18:03:06 +0000718 // Encaspulate the whole command line in a single string passed to
719 // the shell, so that GetObjdumpCommand() may contain arguments
720 // in addition to the program name.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700721 args.push_back(GetObjdumpCommand());
722 args.push_back(file);
723 args.push_back(">");
724 args.push_back(file+".dump");
725 std::string cmd = Join(args, ' ');
726
727 args.clear();
728 args.push_back("/bin/sh");
729 args.push_back("-c");
730 args.push_back(cmd);
731
732 if (!Exec(args, &error_msg)) {
733 EXPECT_TRUE(false) << error_msg;
734 }
735
736 std::ifstream dump(file+".dump");
737
738 std::string line;
739 bool found = false;
740 while (std::getline(dump, line)) {
741 if (line.find(".text") != line.npos) {
742 found = true;
743 break;
744 }
745 }
746
747 dump.close();
748
749 if (found) {
750 return line;
751 } else {
752 return "";
753 }
754 }
755
756 // Disassemble both binaries and compare the text.
757 bool DisassembleBinaries(std::vector<uint8_t>& data, std::vector<uint8_t>& as,
758 std::string test_name) {
759 std::string disassembler = GetDisassembleCommand();
760 if (disassembler.length() == 0) {
761 LOG(WARNING) << "No dissassembler command.";
762 return false;
763 }
764
765 std::string data_name = WriteToFile(data, test_name + ".ass");
766 std::string error_msg;
767 if (!DisassembleBinary(data_name, &error_msg)) {
768 LOG(INFO) << "Error disassembling: " << error_msg;
769 std::remove(data_name.c_str());
770 return false;
771 }
772
773 std::string as_name = WriteToFile(as, test_name + ".gcc");
774 if (!DisassembleBinary(as_name, &error_msg)) {
775 LOG(INFO) << "Error disassembling: " << error_msg;
776 std::remove(data_name.c_str());
777 std::remove((data_name + ".dis").c_str());
778 std::remove(as_name.c_str());
779 return false;
780 }
781
782 bool result = CompareFiles(data_name + ".dis", as_name + ".dis");
783
Andreas Gampe65bec692015-01-14 12:03:36 -0800784 if (!kKeepDisassembledFiles) {
785 std::remove(data_name.c_str());
786 std::remove(as_name.c_str());
787 std::remove((data_name + ".dis").c_str());
788 std::remove((as_name + ".dis").c_str());
789 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700790
791 return result;
792 }
793
794 bool DisassembleBinary(std::string file, std::string* error_msg) {
795 std::vector<std::string> args;
796
Roland Levillain1a28fc42014-11-13 18:03:06 +0000797 // Encaspulate the whole command line in a single string passed to
798 // the shell, so that GetDisassembleCommand() may contain arguments
799 // in addition to the program name.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700800 args.push_back(GetDisassembleCommand());
801 args.push_back(file);
802 args.push_back("| sed -n \'/<.data>/,$p\' | sed -e \'s/.*://\'");
803 args.push_back(">");
804 args.push_back(file+".dis");
805 std::string cmd = Join(args, ' ');
806
807 args.clear();
808 args.push_back("/bin/sh");
809 args.push_back("-c");
810 args.push_back(cmd);
811
812 return Exec(args, error_msg);
813 }
814
815 std::string WriteToFile(std::vector<uint8_t>& buffer, std::string test_name) {
816 std::string file_name = GetTmpnam() + std::string("---") + test_name;
817 const char* data = reinterpret_cast<char*>(buffer.data());
818 std::ofstream s_out(file_name + ".o");
819 s_out.write(data, buffer.size());
820 s_out.close();
821 return file_name + ".o";
822 }
823
824 bool CompareFiles(std::string f1, std::string f2) {
825 std::ifstream f1_in(f1);
826 std::ifstream f2_in(f2);
827
828 bool result = std::equal(std::istreambuf_iterator<char>(f1_in),
829 std::istreambuf_iterator<char>(),
830 std::istreambuf_iterator<char>(f2_in));
831
832 f1_in.close();
833 f2_in.close();
834
835 return result;
836 }
837
838 // Compile the given assembly code and extract the binary, if possible. Put result into res.
839 bool Compile(std::string assembly_code, NativeAssemblerResult* res, std::string test_name) {
840 res->ok = false;
841 res->code.reset(nullptr);
842
843 res->base_name = GetTmpnam() + std::string("---") + test_name;
844
845 // TODO: Lots of error checking.
846
847 std::ofstream s_out(res->base_name + ".S");
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800848 const char* header = GetAssemblyHeader();
849 if (header != nullptr) {
850 s_out << header;
851 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700852 s_out << assembly_code;
853 s_out.close();
854
855 if (!Assemble((res->base_name + ".S").c_str(), (res->base_name + ".o").c_str(),
856 &res->error_msg)) {
857 res->error_msg = "Could not compile.";
858 return false;
859 }
860
861 std::string odump = Objdump(res->base_name + ".o");
862 if (odump.length() == 0) {
863 res->error_msg = "Objdump failed.";
864 return false;
865 }
866
867 std::istringstream iss(odump);
868 std::istream_iterator<std::string> start(iss);
869 std::istream_iterator<std::string> end;
870 std::vector<std::string> tokens(start, end);
871
872 if (tokens.size() < OBJDUMP_SECTION_LINE_MIN_TOKENS) {
873 res->error_msg = "Objdump output not recognized: too few tokens.";
874 return false;
875 }
876
877 if (tokens[1] != ".text") {
878 res->error_msg = "Objdump output not recognized: .text not second token.";
879 return false;
880 }
881
882 std::string lengthToken = "0x" + tokens[2];
883 std::istringstream(lengthToken) >> std::hex >> res->length;
884
885 std::string offsetToken = "0x" + tokens[5];
886 uintptr_t offset;
887 std::istringstream(offsetToken) >> std::hex >> offset;
888
889 std::ifstream obj(res->base_name + ".o");
890 obj.seekg(offset);
891 res->code.reset(new std::vector<uint8_t>(res->length));
892 obj.read(reinterpret_cast<char*>(&(*res->code)[0]), res->length);
893 obj.close();
894
895 res->ok = true;
896 return true;
897 }
898
899 // Remove temporary files.
900 void Clean(const NativeAssemblerResult* res) {
901 std::remove((res->base_name + ".S").c_str());
902 std::remove((res->base_name + ".o").c_str());
903 std::remove((res->base_name + ".o.dump").c_str());
904 }
905
906 // Check whether file exists. Is used for commands, so strips off any parameters: anything after
907 // the first space. We skip to the last slash for this, so it should work with directories with
908 // spaces.
909 static bool FileExists(std::string file) {
910 if (file.length() == 0) {
911 return false;
912 }
913
914 // Need to strip any options.
915 size_t last_slash = file.find_last_of('/');
916 if (last_slash == std::string::npos) {
917 // No slash, start looking at the start.
918 last_slash = 0;
919 }
920 size_t space_index = file.find(' ', last_slash);
921
922 if (space_index == std::string::npos) {
923 std::ifstream infile(file.c_str());
924 return infile.good();
925 } else {
926 std::string copy = file.substr(0, space_index - 1);
927
928 struct stat buf;
929 return stat(copy.c_str(), &buf) == 0;
930 }
931 }
932
933 static std::string GetGCCRootPath() {
934 return "prebuilts/gcc/linux-x86";
935 }
936
937 static std::string GetRootPath() {
938 // 1) Check ANDROID_BUILD_TOP
939 char* build_top = getenv("ANDROID_BUILD_TOP");
940 if (build_top != nullptr) {
941 return std::string(build_top) + "/";
942 }
943
944 // 2) Do cwd
945 char temp[1024];
946 return getcwd(temp, 1024) ? std::string(temp) + "/" : std::string("");
947 }
948
949 std::string FindTool(std::string tool_name) {
950 // Find the current tool. Wild-card pattern is "arch-string*tool-name".
951 std::string gcc_path = GetRootPath() + GetGCCRootPath();
952 std::vector<std::string> args;
953 args.push_back("find");
954 args.push_back(gcc_path);
955 args.push_back("-name");
956 args.push_back(GetArchitectureString() + "*" + tool_name);
957 args.push_back("|");
958 args.push_back("sort");
959 args.push_back("|");
960 args.push_back("tail");
961 args.push_back("-n");
962 args.push_back("1");
963 std::string tmp_file = GetTmpnam();
964 args.push_back(">");
965 args.push_back(tmp_file);
966 std::string sh_args = Join(args, ' ');
967
968 args.clear();
969 args.push_back("/bin/sh");
970 args.push_back("-c");
971 args.push_back(sh_args);
972
973 std::string error_msg;
974 if (!Exec(args, &error_msg)) {
975 EXPECT_TRUE(false) << error_msg;
976 return "";
977 }
978
979 std::ifstream in(tmp_file.c_str());
980 std::string line;
981 if (!std::getline(in, line)) {
982 in.close();
983 std::remove(tmp_file.c_str());
984 return "";
985 }
986 in.close();
987 std::remove(tmp_file.c_str());
988 return line;
989 }
990
991 // Use a consistent tmpnam, so store it.
992 std::string GetTmpnam() {
993 if (tmpnam_.length() == 0) {
Andreas Gampeb40c6a72014-05-02 14:25:12 -0700994 ScratchFile tmp;
995 tmpnam_ = tmp.GetFilename() + "asm";
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700996 }
997 return tmpnam_;
998 }
999
Andreas Gampe849cc5e2014-11-18 13:46:46 -08001000 static constexpr size_t kWarnManyCombinationsThreshold = 500;
Andreas Gampe851df202014-11-12 14:05:46 -08001001 static constexpr size_t OBJDUMP_SECTION_LINE_MIN_TOKENS = 6;
1002
Ian Rogers700a4022014-05-19 16:49:03 -07001003 std::unique_ptr<Ass> assembler_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -07001004
1005 std::string resolved_assembler_cmd_;
1006 std::string resolved_objdump_cmd_;
1007 std::string resolved_disassemble_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -07001008
Andreas Gampe7747c8d2014-08-06 14:53:03 -07001009 std::string android_data_;
1010
Andreas Gampe851df202014-11-12 14:05:46 -08001011 DISALLOW_COPY_AND_ASSIGN(AssemblerTest);
Andreas Gampe5a4fa822014-03-31 16:50:12 -07001012};
1013
1014} // namespace art
1015
1016#endif // ART_COMPILER_UTILS_ASSEMBLER_TEST_H_