blob: b13edb68bf13f1d1d812bd9369f493cc108321e8 [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,
47 kUseSecondaryName
48};
49
Andreas Gampe851df202014-11-12 14:05:46 -080050template<typename Ass, typename Reg, typename FPReg, typename Imm>
Andreas Gampe5a4fa822014-03-31 16:50:12 -070051class AssemblerTest : public testing::Test {
52 public:
53 Ass* GetAssembler() {
54 return assembler_.get();
55 }
56
Andreas Gampe851df202014-11-12 14:05:46 -080057 typedef std::string (*TestFn)(AssemblerTest* assembler_test, Ass* assembler);
Andreas Gampe5a4fa822014-03-31 16:50:12 -070058
59 void DriverFn(TestFn f, std::string test_name) {
Andreas Gampe851df202014-11-12 14:05:46 -080060 Driver(f(this, assembler_.get()), test_name);
Andreas Gampe5a4fa822014-03-31 16:50:12 -070061 }
62
63 // This driver assumes the assembler has already been called.
64 void DriverStr(std::string assembly_string, std::string test_name) {
65 Driver(assembly_string, test_name);
66 }
67
68 std::string RepeatR(void (Ass::*f)(Reg), std::string fmt) {
Andreas Gampe851df202014-11-12 14:05:46 -080069 return RepeatTemplatedRegister<Reg>(f,
70 GetRegisters(),
71 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
72 fmt);
73 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -070074
Andreas Gampe851df202014-11-12 14:05:46 -080075 std::string Repeatr(void (Ass::*f)(Reg), std::string fmt) {
76 return RepeatTemplatedRegister<Reg>(f,
77 GetRegisters(),
78 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
79 fmt);
Andreas Gampe5a4fa822014-03-31 16:50:12 -070080 }
81
82 std::string RepeatRR(void (Ass::*f)(Reg, Reg), std::string fmt) {
Andreas Gampe851df202014-11-12 14:05:46 -080083 return RepeatTemplatedRegisters<Reg, Reg>(f,
84 GetRegisters(),
85 GetRegisters(),
86 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
87 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
88 fmt);
89 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -070090
Andreas Gampe851df202014-11-12 14:05:46 -080091 std::string Repeatrr(void (Ass::*f)(Reg, Reg), std::string fmt) {
92 return RepeatTemplatedRegisters<Reg, Reg>(f,
93 GetRegisters(),
94 GetRegisters(),
95 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
96 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
97 fmt);
98 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -070099
Andreas Gampe851df202014-11-12 14:05:46 -0800100 std::string RepeatRr(void (Ass::*f)(Reg, Reg), std::string fmt) {
101 return RepeatTemplatedRegisters<Reg, Reg>(f,
102 GetRegisters(),
103 GetRegisters(),
104 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
105 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
106 fmt);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700107 }
108
109 std::string RepeatRI(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes, std::string fmt) {
Andreas Gampe851df202014-11-12 14:05:46 -0800110 return RepeatRegisterImm<RegisterView::kUsePrimaryName>(f, imm_bytes, fmt);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700111 }
112
Andreas Gampe851df202014-11-12 14:05:46 -0800113 std::string Repeatri(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes, std::string fmt) {
114 return RepeatRegisterImm<RegisterView::kUseSecondaryName>(f, imm_bytes, fmt);
115 }
116
117 std::string RepeatFF(void (Ass::*f)(FPReg, FPReg), std::string fmt) {
118 return RepeatTemplatedRegisters<FPReg, FPReg>(f,
119 GetFPRegisters(),
120 GetFPRegisters(),
121 &AssemblerTest::GetFPRegName,
122 &AssemblerTest::GetFPRegName,
123 fmt);
124 }
125
Mark Mendellfb8d2792015-03-31 22:16:59 -0400126 std::string RepeatFFI(void (Ass::*f)(FPReg, FPReg, const Imm&), size_t imm_bytes, std::string fmt) {
127 return RepeatTemplatedRegistersImm<FPReg, FPReg>(f,
128 GetFPRegisters(),
129 GetFPRegisters(),
130 &AssemblerTest::GetFPRegName,
131 &AssemblerTest::GetFPRegName,
132 imm_bytes,
133 fmt);
134 }
135
Andreas Gampe851df202014-11-12 14:05:46 -0800136 std::string RepeatFR(void (Ass::*f)(FPReg, Reg), std::string fmt) {
137 return RepeatTemplatedRegisters<FPReg, Reg>(f,
138 GetFPRegisters(),
139 GetRegisters(),
140 &AssemblerTest::GetFPRegName,
141 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
142 fmt);
143 }
144
145 std::string RepeatFr(void (Ass::*f)(FPReg, Reg), std::string fmt) {
146 return RepeatTemplatedRegisters<FPReg, Reg>(f,
147 GetFPRegisters(),
148 GetRegisters(),
149 &AssemblerTest::GetFPRegName,
150 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
151 fmt);
152 }
153
154 std::string RepeatRF(void (Ass::*f)(Reg, FPReg), std::string fmt) {
155 return RepeatTemplatedRegisters<Reg, FPReg>(f,
156 GetRegisters(),
157 GetFPRegisters(),
158 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
159 &AssemblerTest::GetFPRegName,
160 fmt);
161 }
162
163 std::string RepeatrF(void (Ass::*f)(Reg, FPReg), std::string fmt) {
164 return RepeatTemplatedRegisters<Reg, FPReg>(f,
165 GetRegisters(),
166 GetFPRegisters(),
167 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
168 &AssemblerTest::GetFPRegName,
169 fmt);
170 }
171
172 std::string RepeatI(void (Ass::*f)(const Imm&), size_t imm_bytes, std::string fmt,
173 bool as_uint = false) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700174 std::string str;
Andreas Gampe851df202014-11-12 14:05:46 -0800175 std::vector<int64_t> imms = CreateImmediateValues(imm_bytes, as_uint);
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800176
177 WarnOnCombinations(imms.size());
178
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700179 for (int64_t imm : imms) {
Ian Rogerscf7f1912014-10-22 22:06:39 -0700180 Imm new_imm = CreateImmediate(imm);
181 (assembler_.get()->*f)(new_imm);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700182 std::string base = fmt;
183
Andreas Gampe851df202014-11-12 14:05:46 -0800184 size_t imm_index = base.find(IMM_TOKEN);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700185 if (imm_index != std::string::npos) {
186 std::ostringstream sreg;
187 sreg << imm;
188 std::string imm_string = sreg.str();
Andreas Gampe851df202014-11-12 14:05:46 -0800189 base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700190 }
191
192 if (str.size() > 0) {
193 str += "\n";
194 }
195 str += base;
196 }
197 // Add a newline at the end.
198 str += "\n";
199 return str;
200 }
201
202 // This is intended to be run as a test.
203 bool CheckTools() {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800204 if (!FileExists(FindTool(GetAssemblerCmdName()))) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700205 return false;
206 }
207 LOG(INFO) << "Chosen assembler command: " << GetAssemblerCommand();
208
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800209 if (!FileExists(FindTool(GetObjdumpCmdName()))) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700210 return false;
211 }
212 LOG(INFO) << "Chosen objdump command: " << GetObjdumpCommand();
213
214 // Disassembly is optional.
215 std::string disassembler = GetDisassembleCommand();
216 if (disassembler.length() != 0) {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800217 if (!FileExists(FindTool(GetDisassembleCmdName()))) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700218 return false;
219 }
220 LOG(INFO) << "Chosen disassemble command: " << GetDisassembleCommand();
221 } else {
222 LOG(INFO) << "No disassembler given.";
223 }
224
225 return true;
226 }
227
Andreas Gampe851df202014-11-12 14:05:46 -0800228 // The following functions are public so that TestFn can use them...
229
230 virtual std::vector<Reg*> GetRegisters() = 0;
231
232 virtual std::vector<FPReg*> GetFPRegisters() {
233 UNIMPLEMENTED(FATAL) << "Architecture does not support floating-point registers";
234 UNREACHABLE();
235 }
236
237 // Secondary register names are the secondary view on registers, e.g., 32b on 64b systems.
238 virtual std::string GetSecondaryRegisterName(const Reg& reg ATTRIBUTE_UNUSED) {
239 UNIMPLEMENTED(FATAL) << "Architecture does not support secondary registers";
240 UNREACHABLE();
241 }
242
Calin Juravle9aec02f2014-11-18 23:06:35 +0000243 std::string GetRegisterName(const Reg& reg) {
244 return GetRegName<RegisterView::kUsePrimaryName>(reg);
245 }
246
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700247 protected:
Andreas Gampe851df202014-11-12 14:05:46 -0800248 explicit AssemblerTest() {}
249
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700250 void SetUp() OVERRIDE {
251 assembler_.reset(new Ass());
252
Andreas Gampeb40c6a72014-05-02 14:25:12 -0700253 // Fake a runtime test for ScratchFile
Andreas Gampe7747c8d2014-08-06 14:53:03 -0700254 CommonRuntimeTest::SetUpAndroidData(android_data_);
Andreas Gampeb40c6a72014-05-02 14:25:12 -0700255
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700256 SetUpHelpers();
257 }
258
Andreas Gampe7747c8d2014-08-06 14:53:03 -0700259 void TearDown() OVERRIDE {
260 // We leave temporaries in case this failed so we can debug issues.
261 CommonRuntimeTest::TearDownAndroidData(android_data_, false);
262 tmpnam_ = "";
263 }
264
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700265 // Override this to set up any architecture-specific things, e.g., register vectors.
266 virtual void SetUpHelpers() {}
267
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700268 // Get the typically used name for this architecture, e.g., aarch64, x86_64, ...
269 virtual std::string GetArchitectureString() = 0;
270
271 // Get the name of the assembler, e.g., "as" by default.
272 virtual std::string GetAssemblerCmdName() {
273 return "as";
274 }
275
276 // Switches to the assembler command. Default none.
277 virtual std::string GetAssemblerParameters() {
278 return "";
279 }
280
281 // Return the host assembler command for this test.
282 virtual std::string GetAssemblerCommand() {
283 // Already resolved it once?
284 if (resolved_assembler_cmd_.length() != 0) {
285 return resolved_assembler_cmd_;
286 }
287
288 std::string line = FindTool(GetAssemblerCmdName());
289 if (line.length() == 0) {
290 return line;
291 }
292
293 resolved_assembler_cmd_ = line + GetAssemblerParameters();
294
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800295 return resolved_assembler_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700296 }
297
298 // Get the name of the objdump, e.g., "objdump" by default.
299 virtual std::string GetObjdumpCmdName() {
300 return "objdump";
301 }
302
303 // Switches to the objdump command. Default is " -h".
304 virtual std::string GetObjdumpParameters() {
305 return " -h";
306 }
307
308 // Return the host objdump command for this test.
309 virtual std::string GetObjdumpCommand() {
310 // Already resolved it once?
311 if (resolved_objdump_cmd_.length() != 0) {
312 return resolved_objdump_cmd_;
313 }
314
315 std::string line = FindTool(GetObjdumpCmdName());
316 if (line.length() == 0) {
317 return line;
318 }
319
320 resolved_objdump_cmd_ = line + GetObjdumpParameters();
321
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800322 return resolved_objdump_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700323 }
324
325 // Get the name of the objdump, e.g., "objdump" by default.
326 virtual std::string GetDisassembleCmdName() {
327 return "objdump";
328 }
329
330 // Switches to the objdump command. As it's a binary, one needs to push the architecture and
331 // such to objdump, so it's architecture-specific and there is no default.
332 virtual std::string GetDisassembleParameters() = 0;
333
334 // Return the host disassembler command for this test.
335 virtual std::string GetDisassembleCommand() {
336 // Already resolved it once?
337 if (resolved_disassemble_cmd_.length() != 0) {
338 return resolved_disassemble_cmd_;
339 }
340
341 std::string line = FindTool(GetDisassembleCmdName());
342 if (line.length() == 0) {
343 return line;
344 }
345
346 resolved_disassemble_cmd_ = line + GetDisassembleParameters();
347
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800348 return resolved_disassemble_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700349 }
350
351 // Create a couple of immediate values up to the number of bytes given.
Andreas Gampe851df202014-11-12 14:05:46 -0800352 virtual std::vector<int64_t> CreateImmediateValues(size_t imm_bytes, bool as_uint = false) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700353 std::vector<int64_t> res;
354 res.push_back(0);
Andreas Gampe851df202014-11-12 14:05:46 -0800355 if (!as_uint) {
356 res.push_back(-1);
357 } else {
358 res.push_back(0xFF);
359 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700360 res.push_back(0x12);
361 if (imm_bytes >= 2) {
362 res.push_back(0x1234);
Andreas Gampe851df202014-11-12 14:05:46 -0800363 if (!as_uint) {
364 res.push_back(-0x1234);
365 } else {
366 res.push_back(0xFFFF);
367 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700368 if (imm_bytes >= 4) {
369 res.push_back(0x12345678);
Andreas Gampe851df202014-11-12 14:05:46 -0800370 if (!as_uint) {
371 res.push_back(-0x12345678);
372 } else {
373 res.push_back(0xFFFFFFFF);
374 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700375 if (imm_bytes >= 6) {
376 res.push_back(0x123456789ABC);
Andreas Gampe851df202014-11-12 14:05:46 -0800377 if (!as_uint) {
378 res.push_back(-0x123456789ABC);
379 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700380 if (imm_bytes >= 8) {
381 res.push_back(0x123456789ABCDEF0);
Andreas Gampe851df202014-11-12 14:05:46 -0800382 if (!as_uint) {
383 res.push_back(-0x123456789ABCDEF0);
384 } else {
385 res.push_back(0xFFFFFFFFFFFFFFFF);
386 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700387 }
388 }
389 }
390 }
391 return res;
392 }
393
394 // Create an immediate from the specific value.
Ian Rogerscf7f1912014-10-22 22:06:39 -0700395 virtual Imm CreateImmediate(int64_t imm_value) = 0;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700396
Andreas Gampe851df202014-11-12 14:05:46 -0800397 template <typename RegType>
398 std::string RepeatTemplatedRegister(void (Ass::*f)(RegType),
399 const std::vector<RegType*> registers,
400 std::string (AssemblerTest::*GetName)(const RegType&),
401 std::string fmt) {
402 std::string str;
403 for (auto reg : registers) {
404 (assembler_.get()->*f)(*reg);
405 std::string base = fmt;
406
407 std::string reg_string = (this->*GetName)(*reg);
408 size_t reg_index;
409 if ((reg_index = base.find(REG_TOKEN)) != std::string::npos) {
410 base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string);
411 }
412
413 if (str.size() > 0) {
414 str += "\n";
415 }
416 str += base;
417 }
418 // Add a newline at the end.
419 str += "\n";
420 return str;
421 }
422
423 template <typename Reg1, typename Reg2>
424 std::string RepeatTemplatedRegisters(void (Ass::*f)(Reg1, Reg2),
425 const std::vector<Reg1*> reg1_registers,
426 const std::vector<Reg2*> reg2_registers,
427 std::string (AssemblerTest::*GetName1)(const Reg1&),
428 std::string (AssemblerTest::*GetName2)(const Reg2&),
429 std::string fmt) {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800430 WarnOnCombinations(reg1_registers.size() * reg2_registers.size());
431
Andreas Gampe851df202014-11-12 14:05:46 -0800432 std::string str;
433 for (auto reg1 : reg1_registers) {
434 for (auto reg2 : reg2_registers) {
435 (assembler_.get()->*f)(*reg1, *reg2);
436 std::string base = fmt;
437
438 std::string reg1_string = (this->*GetName1)(*reg1);
439 size_t reg1_index;
440 while ((reg1_index = base.find(REG1_TOKEN)) != std::string::npos) {
441 base.replace(reg1_index, ConstexprStrLen(REG1_TOKEN), reg1_string);
442 }
443
444 std::string reg2_string = (this->*GetName2)(*reg2);
445 size_t reg2_index;
446 while ((reg2_index = base.find(REG2_TOKEN)) != std::string::npos) {
447 base.replace(reg2_index, ConstexprStrLen(REG2_TOKEN), reg2_string);
448 }
449
450 if (str.size() > 0) {
451 str += "\n";
452 }
453 str += base;
454 }
455 }
456 // Add a newline at the end.
457 str += "\n";
458 return str;
459 }
460
Mark Mendellfb8d2792015-03-31 22:16:59 -0400461 template <typename Reg1, typename Reg2>
462 std::string RepeatTemplatedRegistersImm(void (Ass::*f)(Reg1, Reg2, const Imm&),
463 const std::vector<Reg1*> reg1_registers,
464 const std::vector<Reg2*> reg2_registers,
465 std::string (AssemblerTest::*GetName1)(const Reg1&),
466 std::string (AssemblerTest::*GetName2)(const Reg2&),
467 size_t imm_bytes,
468 std::string fmt) {
469 std::vector<int64_t> imms = CreateImmediateValues(imm_bytes);
470 WarnOnCombinations(reg1_registers.size() * reg2_registers.size() * imms.size());
471
472 std::string str;
473 for (auto reg1 : reg1_registers) {
474 for (auto reg2 : reg2_registers) {
475 for (int64_t imm : imms) {
476 Imm new_imm = CreateImmediate(imm);
477 (assembler_.get()->*f)(*reg1, *reg2, new_imm);
478 std::string base = fmt;
479
480 std::string reg1_string = (this->*GetName1)(*reg1);
481 size_t reg1_index;
482 while ((reg1_index = base.find(REG1_TOKEN)) != std::string::npos) {
483 base.replace(reg1_index, ConstexprStrLen(REG1_TOKEN), reg1_string);
484 }
485
486 std::string reg2_string = (this->*GetName2)(*reg2);
487 size_t reg2_index;
488 while ((reg2_index = base.find(REG2_TOKEN)) != std::string::npos) {
489 base.replace(reg2_index, ConstexprStrLen(REG2_TOKEN), reg2_string);
490 }
491
492 size_t imm_index = base.find(IMM_TOKEN);
493 if (imm_index != std::string::npos) {
494 std::ostringstream sreg;
495 sreg << imm;
496 std::string imm_string = sreg.str();
497 base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
498 }
499
500 if (str.size() > 0) {
501 str += "\n";
502 }
503 str += base;
504 }
505 }
506 }
507 // Add a newline at the end.
508 str += "\n";
509 return str;
510 }
511
Andreas Gampe851df202014-11-12 14:05:46 -0800512 template <RegisterView kRegView>
513 std::string GetRegName(const Reg& reg) {
514 std::ostringstream sreg;
515 switch (kRegView) {
516 case RegisterView::kUsePrimaryName:
517 sreg << reg;
518 break;
519
520 case RegisterView::kUseSecondaryName:
521 sreg << GetSecondaryRegisterName(reg);
522 break;
523 }
524 return sreg.str();
525 }
526
527 std::string GetFPRegName(const FPReg& reg) {
528 std::ostringstream sreg;
529 sreg << reg;
530 return sreg.str();
531 }
532
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800533 // If the assembly file needs a header, return it in a sub-class.
534 virtual const char* GetAssemblyHeader() {
535 return nullptr;
536 }
537
538 void WarnOnCombinations(size_t count) {
539 if (count > kWarnManyCombinationsThreshold) {
540 GTEST_LOG_(WARNING) << "Many combinations (" << count << "), test generation might be slow.";
541 }
542 }
543
544 static constexpr const char* REG_TOKEN = "{reg}";
545 static constexpr const char* REG1_TOKEN = "{reg1}";
546 static constexpr const char* REG2_TOKEN = "{reg2}";
547 static constexpr const char* IMM_TOKEN = "{imm}";
548
549 private:
Andreas Gampe851df202014-11-12 14:05:46 -0800550 template <RegisterView kRegView>
551 std::string RepeatRegisterImm(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes,
552 std::string fmt) {
553 const std::vector<Reg*> registers = GetRegisters();
554 std::string str;
555 std::vector<int64_t> imms = CreateImmediateValues(imm_bytes);
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800556
557 WarnOnCombinations(registers.size() * imms.size());
558
Andreas Gampe851df202014-11-12 14:05:46 -0800559 for (auto reg : registers) {
560 for (int64_t imm : imms) {
561 Imm new_imm = CreateImmediate(imm);
562 (assembler_.get()->*f)(*reg, new_imm);
563 std::string base = fmt;
564
565 std::string reg_string = GetRegName<kRegView>(*reg);
566 size_t reg_index;
567 while ((reg_index = base.find(REG_TOKEN)) != std::string::npos) {
568 base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string);
569 }
570
571 size_t imm_index = base.find(IMM_TOKEN);
572 if (imm_index != std::string::npos) {
573 std::ostringstream sreg;
574 sreg << imm;
575 std::string imm_string = sreg.str();
576 base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
577 }
578
579 if (str.size() > 0) {
580 str += "\n";
581 }
582 str += base;
583 }
584 }
585 // Add a newline at the end.
586 str += "\n";
587 return str;
588 }
589
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700590 // Driver() assembles and compares the results. If the results are not equal and we have a
591 // disassembler, disassemble both and check whether they have the same mnemonics (in which case
592 // we just warn).
593 void Driver(std::string assembly_text, std::string test_name) {
594 EXPECT_NE(assembly_text.length(), 0U) << "Empty assembly";
595
596 NativeAssemblerResult res;
597 Compile(assembly_text, &res, test_name);
598
599 EXPECT_TRUE(res.ok) << res.error_msg;
600 if (!res.ok) {
601 // No way of continuing.
602 return;
603 }
604
605 size_t cs = assembler_->CodeSize();
Ian Rogers700a4022014-05-19 16:49:03 -0700606 std::unique_ptr<std::vector<uint8_t>> data(new std::vector<uint8_t>(cs));
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700607 MemoryRegion code(&(*data)[0], data->size());
608 assembler_->FinalizeInstructions(code);
609
610 if (*data == *res.code) {
611 Clean(&res);
612 } else {
613 if (DisassembleBinaries(*data, *res.code, test_name)) {
614 if (data->size() > res.code->size()) {
Andreas Gampe54e15de2014-08-06 15:31:06 -0700615 // Fail this test with a fancy colored warning being printed.
616 EXPECT_TRUE(false) << "Assembly code is not identical, but disassembly of machine code "
617 "is equal: this implies sub-optimal encoding! Our code size=" << data->size() <<
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700618 ", gcc size=" << res.code->size();
619 } else {
Andreas Gampe54e15de2014-08-06 15:31:06 -0700620 // Otherwise just print an info message and clean up.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700621 LOG(INFO) << "GCC chose a different encoding than ours, but the overall length is the "
622 "same.";
Andreas Gampe54e15de2014-08-06 15:31:06 -0700623 Clean(&res);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700624 }
625 } else {
626 // This will output the assembly.
Nicolas Geoffray102cbed2014-10-15 18:31:05 +0100627 EXPECT_EQ(*res.code, *data) << "Outputs (and disassembly) not identical.";
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700628 }
629 }
630 }
631
632 // Structure to store intermediates and results.
633 struct NativeAssemblerResult {
634 bool ok;
635 std::string error_msg;
636 std::string base_name;
Ian Rogers700a4022014-05-19 16:49:03 -0700637 std::unique_ptr<std::vector<uint8_t>> code;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700638 uintptr_t length;
639 };
640
641 // Compile the assembly file from_file to a binary file to_file. Returns true on success.
642 bool Assemble(const char* from_file, const char* to_file, std::string* error_msg) {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800643 bool have_assembler = FileExists(FindTool(GetAssemblerCmdName()));
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700644 EXPECT_TRUE(have_assembler) << "Cannot find assembler:" << GetAssemblerCommand();
645 if (!have_assembler) {
646 return false;
647 }
648
649 std::vector<std::string> args;
650
Roland Levillain1a28fc42014-11-13 18:03:06 +0000651 // Encaspulate the whole command line in a single string passed to
652 // the shell, so that GetAssemblerCommand() may contain arguments
653 // in addition to the program name.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700654 args.push_back(GetAssemblerCommand());
655 args.push_back("-o");
656 args.push_back(to_file);
657 args.push_back(from_file);
Roland Levillain1a28fc42014-11-13 18:03:06 +0000658 std::string cmd = Join(args, ' ');
659
660 args.clear();
661 args.push_back("/bin/sh");
662 args.push_back("-c");
663 args.push_back(cmd);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700664
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800665 bool success = Exec(args, error_msg);
666 if (!success) {
667 LOG(INFO) << "Assembler command line:";
668 for (std::string arg : args) {
669 LOG(INFO) << arg;
670 }
671 }
672 return success;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700673 }
674
675 // Runs objdump -h on the binary file and extracts the first line with .text.
676 // Returns "" on failure.
677 std::string Objdump(std::string file) {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800678 bool have_objdump = FileExists(FindTool(GetObjdumpCmdName()));
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700679 EXPECT_TRUE(have_objdump) << "Cannot find objdump: " << GetObjdumpCommand();
680 if (!have_objdump) {
681 return "";
682 }
683
684 std::string error_msg;
685 std::vector<std::string> args;
686
Roland Levillain1a28fc42014-11-13 18:03:06 +0000687 // Encaspulate the whole command line in a single string passed to
688 // the shell, so that GetObjdumpCommand() may contain arguments
689 // in addition to the program name.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700690 args.push_back(GetObjdumpCommand());
691 args.push_back(file);
692 args.push_back(">");
693 args.push_back(file+".dump");
694 std::string cmd = Join(args, ' ');
695
696 args.clear();
697 args.push_back("/bin/sh");
698 args.push_back("-c");
699 args.push_back(cmd);
700
701 if (!Exec(args, &error_msg)) {
702 EXPECT_TRUE(false) << error_msg;
703 }
704
705 std::ifstream dump(file+".dump");
706
707 std::string line;
708 bool found = false;
709 while (std::getline(dump, line)) {
710 if (line.find(".text") != line.npos) {
711 found = true;
712 break;
713 }
714 }
715
716 dump.close();
717
718 if (found) {
719 return line;
720 } else {
721 return "";
722 }
723 }
724
725 // Disassemble both binaries and compare the text.
726 bool DisassembleBinaries(std::vector<uint8_t>& data, std::vector<uint8_t>& as,
727 std::string test_name) {
728 std::string disassembler = GetDisassembleCommand();
729 if (disassembler.length() == 0) {
730 LOG(WARNING) << "No dissassembler command.";
731 return false;
732 }
733
734 std::string data_name = WriteToFile(data, test_name + ".ass");
735 std::string error_msg;
736 if (!DisassembleBinary(data_name, &error_msg)) {
737 LOG(INFO) << "Error disassembling: " << error_msg;
738 std::remove(data_name.c_str());
739 return false;
740 }
741
742 std::string as_name = WriteToFile(as, test_name + ".gcc");
743 if (!DisassembleBinary(as_name, &error_msg)) {
744 LOG(INFO) << "Error disassembling: " << error_msg;
745 std::remove(data_name.c_str());
746 std::remove((data_name + ".dis").c_str());
747 std::remove(as_name.c_str());
748 return false;
749 }
750
751 bool result = CompareFiles(data_name + ".dis", as_name + ".dis");
752
Andreas Gampe65bec692015-01-14 12:03:36 -0800753 if (!kKeepDisassembledFiles) {
754 std::remove(data_name.c_str());
755 std::remove(as_name.c_str());
756 std::remove((data_name + ".dis").c_str());
757 std::remove((as_name + ".dis").c_str());
758 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700759
760 return result;
761 }
762
763 bool DisassembleBinary(std::string file, std::string* error_msg) {
764 std::vector<std::string> args;
765
Roland Levillain1a28fc42014-11-13 18:03:06 +0000766 // Encaspulate the whole command line in a single string passed to
767 // the shell, so that GetDisassembleCommand() may contain arguments
768 // in addition to the program name.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700769 args.push_back(GetDisassembleCommand());
770 args.push_back(file);
771 args.push_back("| sed -n \'/<.data>/,$p\' | sed -e \'s/.*://\'");
772 args.push_back(">");
773 args.push_back(file+".dis");
774 std::string cmd = Join(args, ' ');
775
776 args.clear();
777 args.push_back("/bin/sh");
778 args.push_back("-c");
779 args.push_back(cmd);
780
781 return Exec(args, error_msg);
782 }
783
784 std::string WriteToFile(std::vector<uint8_t>& buffer, std::string test_name) {
785 std::string file_name = GetTmpnam() + std::string("---") + test_name;
786 const char* data = reinterpret_cast<char*>(buffer.data());
787 std::ofstream s_out(file_name + ".o");
788 s_out.write(data, buffer.size());
789 s_out.close();
790 return file_name + ".o";
791 }
792
793 bool CompareFiles(std::string f1, std::string f2) {
794 std::ifstream f1_in(f1);
795 std::ifstream f2_in(f2);
796
797 bool result = std::equal(std::istreambuf_iterator<char>(f1_in),
798 std::istreambuf_iterator<char>(),
799 std::istreambuf_iterator<char>(f2_in));
800
801 f1_in.close();
802 f2_in.close();
803
804 return result;
805 }
806
807 // Compile the given assembly code and extract the binary, if possible. Put result into res.
808 bool Compile(std::string assembly_code, NativeAssemblerResult* res, std::string test_name) {
809 res->ok = false;
810 res->code.reset(nullptr);
811
812 res->base_name = GetTmpnam() + std::string("---") + test_name;
813
814 // TODO: Lots of error checking.
815
816 std::ofstream s_out(res->base_name + ".S");
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800817 const char* header = GetAssemblyHeader();
818 if (header != nullptr) {
819 s_out << header;
820 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700821 s_out << assembly_code;
822 s_out.close();
823
824 if (!Assemble((res->base_name + ".S").c_str(), (res->base_name + ".o").c_str(),
825 &res->error_msg)) {
826 res->error_msg = "Could not compile.";
827 return false;
828 }
829
830 std::string odump = Objdump(res->base_name + ".o");
831 if (odump.length() == 0) {
832 res->error_msg = "Objdump failed.";
833 return false;
834 }
835
836 std::istringstream iss(odump);
837 std::istream_iterator<std::string> start(iss);
838 std::istream_iterator<std::string> end;
839 std::vector<std::string> tokens(start, end);
840
841 if (tokens.size() < OBJDUMP_SECTION_LINE_MIN_TOKENS) {
842 res->error_msg = "Objdump output not recognized: too few tokens.";
843 return false;
844 }
845
846 if (tokens[1] != ".text") {
847 res->error_msg = "Objdump output not recognized: .text not second token.";
848 return false;
849 }
850
851 std::string lengthToken = "0x" + tokens[2];
852 std::istringstream(lengthToken) >> std::hex >> res->length;
853
854 std::string offsetToken = "0x" + tokens[5];
855 uintptr_t offset;
856 std::istringstream(offsetToken) >> std::hex >> offset;
857
858 std::ifstream obj(res->base_name + ".o");
859 obj.seekg(offset);
860 res->code.reset(new std::vector<uint8_t>(res->length));
861 obj.read(reinterpret_cast<char*>(&(*res->code)[0]), res->length);
862 obj.close();
863
864 res->ok = true;
865 return true;
866 }
867
868 // Remove temporary files.
869 void Clean(const NativeAssemblerResult* res) {
870 std::remove((res->base_name + ".S").c_str());
871 std::remove((res->base_name + ".o").c_str());
872 std::remove((res->base_name + ".o.dump").c_str());
873 }
874
875 // Check whether file exists. Is used for commands, so strips off any parameters: anything after
876 // the first space. We skip to the last slash for this, so it should work with directories with
877 // spaces.
878 static bool FileExists(std::string file) {
879 if (file.length() == 0) {
880 return false;
881 }
882
883 // Need to strip any options.
884 size_t last_slash = file.find_last_of('/');
885 if (last_slash == std::string::npos) {
886 // No slash, start looking at the start.
887 last_slash = 0;
888 }
889 size_t space_index = file.find(' ', last_slash);
890
891 if (space_index == std::string::npos) {
892 std::ifstream infile(file.c_str());
893 return infile.good();
894 } else {
895 std::string copy = file.substr(0, space_index - 1);
896
897 struct stat buf;
898 return stat(copy.c_str(), &buf) == 0;
899 }
900 }
901
902 static std::string GetGCCRootPath() {
903 return "prebuilts/gcc/linux-x86";
904 }
905
906 static std::string GetRootPath() {
907 // 1) Check ANDROID_BUILD_TOP
908 char* build_top = getenv("ANDROID_BUILD_TOP");
909 if (build_top != nullptr) {
910 return std::string(build_top) + "/";
911 }
912
913 // 2) Do cwd
914 char temp[1024];
915 return getcwd(temp, 1024) ? std::string(temp) + "/" : std::string("");
916 }
917
918 std::string FindTool(std::string tool_name) {
919 // Find the current tool. Wild-card pattern is "arch-string*tool-name".
920 std::string gcc_path = GetRootPath() + GetGCCRootPath();
921 std::vector<std::string> args;
922 args.push_back("find");
923 args.push_back(gcc_path);
924 args.push_back("-name");
925 args.push_back(GetArchitectureString() + "*" + tool_name);
926 args.push_back("|");
927 args.push_back("sort");
928 args.push_back("|");
929 args.push_back("tail");
930 args.push_back("-n");
931 args.push_back("1");
932 std::string tmp_file = GetTmpnam();
933 args.push_back(">");
934 args.push_back(tmp_file);
935 std::string sh_args = Join(args, ' ');
936
937 args.clear();
938 args.push_back("/bin/sh");
939 args.push_back("-c");
940 args.push_back(sh_args);
941
942 std::string error_msg;
943 if (!Exec(args, &error_msg)) {
944 EXPECT_TRUE(false) << error_msg;
945 return "";
946 }
947
948 std::ifstream in(tmp_file.c_str());
949 std::string line;
950 if (!std::getline(in, line)) {
951 in.close();
952 std::remove(tmp_file.c_str());
953 return "";
954 }
955 in.close();
956 std::remove(tmp_file.c_str());
957 return line;
958 }
959
960 // Use a consistent tmpnam, so store it.
961 std::string GetTmpnam() {
962 if (tmpnam_.length() == 0) {
Andreas Gampeb40c6a72014-05-02 14:25:12 -0700963 ScratchFile tmp;
964 tmpnam_ = tmp.GetFilename() + "asm";
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700965 }
966 return tmpnam_;
967 }
968
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800969 static constexpr size_t kWarnManyCombinationsThreshold = 500;
Andreas Gampe851df202014-11-12 14:05:46 -0800970 static constexpr size_t OBJDUMP_SECTION_LINE_MIN_TOKENS = 6;
971
Ian Rogers700a4022014-05-19 16:49:03 -0700972 std::unique_ptr<Ass> assembler_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700973
974 std::string resolved_assembler_cmd_;
975 std::string resolved_objdump_cmd_;
976 std::string resolved_disassemble_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700977
Andreas Gampe7747c8d2014-08-06 14:53:03 -0700978 std::string android_data_;
979
Andreas Gampe851df202014-11-12 14:05:46 -0800980 DISALLOW_COPY_AND_ASSIGN(AssemblerTest);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700981};
982
983} // namespace art
984
985#endif // ART_COMPILER_UTILS_ASSEMBLER_TEST_H_