blob: 6f8b3012a414834f5327ca32b5c9055cb86959ef [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
126 std::string RepeatFR(void (Ass::*f)(FPReg, Reg), std::string fmt) {
127 return RepeatTemplatedRegisters<FPReg, Reg>(f,
128 GetFPRegisters(),
129 GetRegisters(),
130 &AssemblerTest::GetFPRegName,
131 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
132 fmt);
133 }
134
135 std::string RepeatFr(void (Ass::*f)(FPReg, Reg), std::string fmt) {
136 return RepeatTemplatedRegisters<FPReg, Reg>(f,
137 GetFPRegisters(),
138 GetRegisters(),
139 &AssemblerTest::GetFPRegName,
140 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
141 fmt);
142 }
143
144 std::string RepeatRF(void (Ass::*f)(Reg, FPReg), std::string fmt) {
145 return RepeatTemplatedRegisters<Reg, FPReg>(f,
146 GetRegisters(),
147 GetFPRegisters(),
148 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
149 &AssemblerTest::GetFPRegName,
150 fmt);
151 }
152
153 std::string RepeatrF(void (Ass::*f)(Reg, FPReg), std::string fmt) {
154 return RepeatTemplatedRegisters<Reg, FPReg>(f,
155 GetRegisters(),
156 GetFPRegisters(),
157 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
158 &AssemblerTest::GetFPRegName,
159 fmt);
160 }
161
162 std::string RepeatI(void (Ass::*f)(const Imm&), size_t imm_bytes, std::string fmt,
163 bool as_uint = false) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700164 std::string str;
Andreas Gampe851df202014-11-12 14:05:46 -0800165 std::vector<int64_t> imms = CreateImmediateValues(imm_bytes, as_uint);
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800166
167 WarnOnCombinations(imms.size());
168
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700169 for (int64_t imm : imms) {
Ian Rogerscf7f1912014-10-22 22:06:39 -0700170 Imm new_imm = CreateImmediate(imm);
171 (assembler_.get()->*f)(new_imm);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700172 std::string base = fmt;
173
Andreas Gampe851df202014-11-12 14:05:46 -0800174 size_t imm_index = base.find(IMM_TOKEN);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700175 if (imm_index != std::string::npos) {
176 std::ostringstream sreg;
177 sreg << imm;
178 std::string imm_string = sreg.str();
Andreas Gampe851df202014-11-12 14:05:46 -0800179 base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700180 }
181
182 if (str.size() > 0) {
183 str += "\n";
184 }
185 str += base;
186 }
187 // Add a newline at the end.
188 str += "\n";
189 return str;
190 }
191
192 // This is intended to be run as a test.
193 bool CheckTools() {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800194 if (!FileExists(FindTool(GetAssemblerCmdName()))) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700195 return false;
196 }
197 LOG(INFO) << "Chosen assembler command: " << GetAssemblerCommand();
198
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800199 if (!FileExists(FindTool(GetObjdumpCmdName()))) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700200 return false;
201 }
202 LOG(INFO) << "Chosen objdump command: " << GetObjdumpCommand();
203
204 // Disassembly is optional.
205 std::string disassembler = GetDisassembleCommand();
206 if (disassembler.length() != 0) {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800207 if (!FileExists(FindTool(GetDisassembleCmdName()))) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700208 return false;
209 }
210 LOG(INFO) << "Chosen disassemble command: " << GetDisassembleCommand();
211 } else {
212 LOG(INFO) << "No disassembler given.";
213 }
214
215 return true;
216 }
217
Andreas Gampe851df202014-11-12 14:05:46 -0800218 // The following functions are public so that TestFn can use them...
219
220 virtual std::vector<Reg*> GetRegisters() = 0;
221
222 virtual std::vector<FPReg*> GetFPRegisters() {
223 UNIMPLEMENTED(FATAL) << "Architecture does not support floating-point registers";
224 UNREACHABLE();
225 }
226
227 // Secondary register names are the secondary view on registers, e.g., 32b on 64b systems.
228 virtual std::string GetSecondaryRegisterName(const Reg& reg ATTRIBUTE_UNUSED) {
229 UNIMPLEMENTED(FATAL) << "Architecture does not support secondary registers";
230 UNREACHABLE();
231 }
232
Calin Juravle9aec02f2014-11-18 23:06:35 +0000233 std::string GetRegisterName(const Reg& reg) {
234 return GetRegName<RegisterView::kUsePrimaryName>(reg);
235 }
236
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700237 protected:
Andreas Gampe851df202014-11-12 14:05:46 -0800238 explicit AssemblerTest() {}
239
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700240 void SetUp() OVERRIDE {
241 assembler_.reset(new Ass());
242
Andreas Gampeb40c6a72014-05-02 14:25:12 -0700243 // Fake a runtime test for ScratchFile
Andreas Gampe7747c8d2014-08-06 14:53:03 -0700244 CommonRuntimeTest::SetUpAndroidData(android_data_);
Andreas Gampeb40c6a72014-05-02 14:25:12 -0700245
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700246 SetUpHelpers();
247 }
248
Andreas Gampe7747c8d2014-08-06 14:53:03 -0700249 void TearDown() OVERRIDE {
250 // We leave temporaries in case this failed so we can debug issues.
251 CommonRuntimeTest::TearDownAndroidData(android_data_, false);
252 tmpnam_ = "";
253 }
254
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700255 // Override this to set up any architecture-specific things, e.g., register vectors.
256 virtual void SetUpHelpers() {}
257
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700258 // Get the typically used name for this architecture, e.g., aarch64, x86_64, ...
259 virtual std::string GetArchitectureString() = 0;
260
261 // Get the name of the assembler, e.g., "as" by default.
262 virtual std::string GetAssemblerCmdName() {
263 return "as";
264 }
265
266 // Switches to the assembler command. Default none.
267 virtual std::string GetAssemblerParameters() {
268 return "";
269 }
270
271 // Return the host assembler command for this test.
272 virtual std::string GetAssemblerCommand() {
273 // Already resolved it once?
274 if (resolved_assembler_cmd_.length() != 0) {
275 return resolved_assembler_cmd_;
276 }
277
278 std::string line = FindTool(GetAssemblerCmdName());
279 if (line.length() == 0) {
280 return line;
281 }
282
283 resolved_assembler_cmd_ = line + GetAssemblerParameters();
284
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800285 return resolved_assembler_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700286 }
287
288 // Get the name of the objdump, e.g., "objdump" by default.
289 virtual std::string GetObjdumpCmdName() {
290 return "objdump";
291 }
292
293 // Switches to the objdump command. Default is " -h".
294 virtual std::string GetObjdumpParameters() {
295 return " -h";
296 }
297
298 // Return the host objdump command for this test.
299 virtual std::string GetObjdumpCommand() {
300 // Already resolved it once?
301 if (resolved_objdump_cmd_.length() != 0) {
302 return resolved_objdump_cmd_;
303 }
304
305 std::string line = FindTool(GetObjdumpCmdName());
306 if (line.length() == 0) {
307 return line;
308 }
309
310 resolved_objdump_cmd_ = line + GetObjdumpParameters();
311
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800312 return resolved_objdump_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700313 }
314
315 // Get the name of the objdump, e.g., "objdump" by default.
316 virtual std::string GetDisassembleCmdName() {
317 return "objdump";
318 }
319
320 // Switches to the objdump command. As it's a binary, one needs to push the architecture and
321 // such to objdump, so it's architecture-specific and there is no default.
322 virtual std::string GetDisassembleParameters() = 0;
323
324 // Return the host disassembler command for this test.
325 virtual std::string GetDisassembleCommand() {
326 // Already resolved it once?
327 if (resolved_disassemble_cmd_.length() != 0) {
328 return resolved_disassemble_cmd_;
329 }
330
331 std::string line = FindTool(GetDisassembleCmdName());
332 if (line.length() == 0) {
333 return line;
334 }
335
336 resolved_disassemble_cmd_ = line + GetDisassembleParameters();
337
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800338 return resolved_disassemble_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700339 }
340
341 // Create a couple of immediate values up to the number of bytes given.
Andreas Gampe851df202014-11-12 14:05:46 -0800342 virtual std::vector<int64_t> CreateImmediateValues(size_t imm_bytes, bool as_uint = false) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700343 std::vector<int64_t> res;
344 res.push_back(0);
Andreas Gampe851df202014-11-12 14:05:46 -0800345 if (!as_uint) {
346 res.push_back(-1);
347 } else {
348 res.push_back(0xFF);
349 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700350 res.push_back(0x12);
351 if (imm_bytes >= 2) {
352 res.push_back(0x1234);
Andreas Gampe851df202014-11-12 14:05:46 -0800353 if (!as_uint) {
354 res.push_back(-0x1234);
355 } else {
356 res.push_back(0xFFFF);
357 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700358 if (imm_bytes >= 4) {
359 res.push_back(0x12345678);
Andreas Gampe851df202014-11-12 14:05:46 -0800360 if (!as_uint) {
361 res.push_back(-0x12345678);
362 } else {
363 res.push_back(0xFFFFFFFF);
364 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700365 if (imm_bytes >= 6) {
366 res.push_back(0x123456789ABC);
Andreas Gampe851df202014-11-12 14:05:46 -0800367 if (!as_uint) {
368 res.push_back(-0x123456789ABC);
369 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700370 if (imm_bytes >= 8) {
371 res.push_back(0x123456789ABCDEF0);
Andreas Gampe851df202014-11-12 14:05:46 -0800372 if (!as_uint) {
373 res.push_back(-0x123456789ABCDEF0);
374 } else {
375 res.push_back(0xFFFFFFFFFFFFFFFF);
376 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700377 }
378 }
379 }
380 }
381 return res;
382 }
383
384 // Create an immediate from the specific value.
Ian Rogerscf7f1912014-10-22 22:06:39 -0700385 virtual Imm CreateImmediate(int64_t imm_value) = 0;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700386
Andreas Gampe851df202014-11-12 14:05:46 -0800387 template <typename RegType>
388 std::string RepeatTemplatedRegister(void (Ass::*f)(RegType),
389 const std::vector<RegType*> registers,
390 std::string (AssemblerTest::*GetName)(const RegType&),
391 std::string fmt) {
392 std::string str;
393 for (auto reg : registers) {
394 (assembler_.get()->*f)(*reg);
395 std::string base = fmt;
396
397 std::string reg_string = (this->*GetName)(*reg);
398 size_t reg_index;
399 if ((reg_index = base.find(REG_TOKEN)) != std::string::npos) {
400 base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string);
401 }
402
403 if (str.size() > 0) {
404 str += "\n";
405 }
406 str += base;
407 }
408 // Add a newline at the end.
409 str += "\n";
410 return str;
411 }
412
413 template <typename Reg1, typename Reg2>
414 std::string RepeatTemplatedRegisters(void (Ass::*f)(Reg1, Reg2),
415 const std::vector<Reg1*> reg1_registers,
416 const std::vector<Reg2*> reg2_registers,
417 std::string (AssemblerTest::*GetName1)(const Reg1&),
418 std::string (AssemblerTest::*GetName2)(const Reg2&),
419 std::string fmt) {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800420 WarnOnCombinations(reg1_registers.size() * reg2_registers.size());
421
Andreas Gampe851df202014-11-12 14:05:46 -0800422 std::string str;
423 for (auto reg1 : reg1_registers) {
424 for (auto reg2 : reg2_registers) {
425 (assembler_.get()->*f)(*reg1, *reg2);
426 std::string base = fmt;
427
428 std::string reg1_string = (this->*GetName1)(*reg1);
429 size_t reg1_index;
430 while ((reg1_index = base.find(REG1_TOKEN)) != std::string::npos) {
431 base.replace(reg1_index, ConstexprStrLen(REG1_TOKEN), reg1_string);
432 }
433
434 std::string reg2_string = (this->*GetName2)(*reg2);
435 size_t reg2_index;
436 while ((reg2_index = base.find(REG2_TOKEN)) != std::string::npos) {
437 base.replace(reg2_index, ConstexprStrLen(REG2_TOKEN), reg2_string);
438 }
439
440 if (str.size() > 0) {
441 str += "\n";
442 }
443 str += base;
444 }
445 }
446 // Add a newline at the end.
447 str += "\n";
448 return str;
449 }
450
Andreas Gampe851df202014-11-12 14:05:46 -0800451 template <RegisterView kRegView>
452 std::string GetRegName(const Reg& reg) {
453 std::ostringstream sreg;
454 switch (kRegView) {
455 case RegisterView::kUsePrimaryName:
456 sreg << reg;
457 break;
458
459 case RegisterView::kUseSecondaryName:
460 sreg << GetSecondaryRegisterName(reg);
461 break;
462 }
463 return sreg.str();
464 }
465
466 std::string GetFPRegName(const FPReg& reg) {
467 std::ostringstream sreg;
468 sreg << reg;
469 return sreg.str();
470 }
471
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800472 // If the assembly file needs a header, return it in a sub-class.
473 virtual const char* GetAssemblyHeader() {
474 return nullptr;
475 }
476
477 void WarnOnCombinations(size_t count) {
478 if (count > kWarnManyCombinationsThreshold) {
479 GTEST_LOG_(WARNING) << "Many combinations (" << count << "), test generation might be slow.";
480 }
481 }
482
483 static constexpr const char* REG_TOKEN = "{reg}";
484 static constexpr const char* REG1_TOKEN = "{reg1}";
485 static constexpr const char* REG2_TOKEN = "{reg2}";
486 static constexpr const char* IMM_TOKEN = "{imm}";
487
488 private:
Andreas Gampe851df202014-11-12 14:05:46 -0800489 template <RegisterView kRegView>
490 std::string RepeatRegisterImm(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes,
491 std::string fmt) {
492 const std::vector<Reg*> registers = GetRegisters();
493 std::string str;
494 std::vector<int64_t> imms = CreateImmediateValues(imm_bytes);
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800495
496 WarnOnCombinations(registers.size() * imms.size());
497
Andreas Gampe851df202014-11-12 14:05:46 -0800498 for (auto reg : registers) {
499 for (int64_t imm : imms) {
500 Imm new_imm = CreateImmediate(imm);
501 (assembler_.get()->*f)(*reg, new_imm);
502 std::string base = fmt;
503
504 std::string reg_string = GetRegName<kRegView>(*reg);
505 size_t reg_index;
506 while ((reg_index = base.find(REG_TOKEN)) != std::string::npos) {
507 base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string);
508 }
509
510 size_t imm_index = base.find(IMM_TOKEN);
511 if (imm_index != std::string::npos) {
512 std::ostringstream sreg;
513 sreg << imm;
514 std::string imm_string = sreg.str();
515 base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
516 }
517
518 if (str.size() > 0) {
519 str += "\n";
520 }
521 str += base;
522 }
523 }
524 // Add a newline at the end.
525 str += "\n";
526 return str;
527 }
528
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700529 // Driver() assembles and compares the results. If the results are not equal and we have a
530 // disassembler, disassemble both and check whether they have the same mnemonics (in which case
531 // we just warn).
532 void Driver(std::string assembly_text, std::string test_name) {
533 EXPECT_NE(assembly_text.length(), 0U) << "Empty assembly";
534
535 NativeAssemblerResult res;
536 Compile(assembly_text, &res, test_name);
537
538 EXPECT_TRUE(res.ok) << res.error_msg;
539 if (!res.ok) {
540 // No way of continuing.
541 return;
542 }
543
544 size_t cs = assembler_->CodeSize();
Ian Rogers700a4022014-05-19 16:49:03 -0700545 std::unique_ptr<std::vector<uint8_t>> data(new std::vector<uint8_t>(cs));
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700546 MemoryRegion code(&(*data)[0], data->size());
547 assembler_->FinalizeInstructions(code);
548
549 if (*data == *res.code) {
550 Clean(&res);
551 } else {
552 if (DisassembleBinaries(*data, *res.code, test_name)) {
553 if (data->size() > res.code->size()) {
Andreas Gampe54e15de2014-08-06 15:31:06 -0700554 // Fail this test with a fancy colored warning being printed.
555 EXPECT_TRUE(false) << "Assembly code is not identical, but disassembly of machine code "
556 "is equal: this implies sub-optimal encoding! Our code size=" << data->size() <<
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700557 ", gcc size=" << res.code->size();
558 } else {
Andreas Gampe54e15de2014-08-06 15:31:06 -0700559 // Otherwise just print an info message and clean up.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700560 LOG(INFO) << "GCC chose a different encoding than ours, but the overall length is the "
561 "same.";
Andreas Gampe54e15de2014-08-06 15:31:06 -0700562 Clean(&res);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700563 }
564 } else {
565 // This will output the assembly.
Nicolas Geoffray102cbed2014-10-15 18:31:05 +0100566 EXPECT_EQ(*res.code, *data) << "Outputs (and disassembly) not identical.";
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700567 }
568 }
569 }
570
571 // Structure to store intermediates and results.
572 struct NativeAssemblerResult {
573 bool ok;
574 std::string error_msg;
575 std::string base_name;
Ian Rogers700a4022014-05-19 16:49:03 -0700576 std::unique_ptr<std::vector<uint8_t>> code;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700577 uintptr_t length;
578 };
579
580 // Compile the assembly file from_file to a binary file to_file. Returns true on success.
581 bool Assemble(const char* from_file, const char* to_file, std::string* error_msg) {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800582 bool have_assembler = FileExists(FindTool(GetAssemblerCmdName()));
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700583 EXPECT_TRUE(have_assembler) << "Cannot find assembler:" << GetAssemblerCommand();
584 if (!have_assembler) {
585 return false;
586 }
587
588 std::vector<std::string> args;
589
Roland Levillain1a28fc42014-11-13 18:03:06 +0000590 // Encaspulate the whole command line in a single string passed to
591 // the shell, so that GetAssemblerCommand() may contain arguments
592 // in addition to the program name.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700593 args.push_back(GetAssemblerCommand());
594 args.push_back("-o");
595 args.push_back(to_file);
596 args.push_back(from_file);
Roland Levillain1a28fc42014-11-13 18:03:06 +0000597 std::string cmd = Join(args, ' ');
598
599 args.clear();
600 args.push_back("/bin/sh");
601 args.push_back("-c");
602 args.push_back(cmd);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700603
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800604 bool success = Exec(args, error_msg);
605 if (!success) {
606 LOG(INFO) << "Assembler command line:";
607 for (std::string arg : args) {
608 LOG(INFO) << arg;
609 }
610 }
611 return success;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700612 }
613
614 // Runs objdump -h on the binary file and extracts the first line with .text.
615 // Returns "" on failure.
616 std::string Objdump(std::string file) {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800617 bool have_objdump = FileExists(FindTool(GetObjdumpCmdName()));
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700618 EXPECT_TRUE(have_objdump) << "Cannot find objdump: " << GetObjdumpCommand();
619 if (!have_objdump) {
620 return "";
621 }
622
623 std::string error_msg;
624 std::vector<std::string> args;
625
Roland Levillain1a28fc42014-11-13 18:03:06 +0000626 // Encaspulate the whole command line in a single string passed to
627 // the shell, so that GetObjdumpCommand() may contain arguments
628 // in addition to the program name.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700629 args.push_back(GetObjdumpCommand());
630 args.push_back(file);
631 args.push_back(">");
632 args.push_back(file+".dump");
633 std::string cmd = Join(args, ' ');
634
635 args.clear();
636 args.push_back("/bin/sh");
637 args.push_back("-c");
638 args.push_back(cmd);
639
640 if (!Exec(args, &error_msg)) {
641 EXPECT_TRUE(false) << error_msg;
642 }
643
644 std::ifstream dump(file+".dump");
645
646 std::string line;
647 bool found = false;
648 while (std::getline(dump, line)) {
649 if (line.find(".text") != line.npos) {
650 found = true;
651 break;
652 }
653 }
654
655 dump.close();
656
657 if (found) {
658 return line;
659 } else {
660 return "";
661 }
662 }
663
664 // Disassemble both binaries and compare the text.
665 bool DisassembleBinaries(std::vector<uint8_t>& data, std::vector<uint8_t>& as,
666 std::string test_name) {
667 std::string disassembler = GetDisassembleCommand();
668 if (disassembler.length() == 0) {
669 LOG(WARNING) << "No dissassembler command.";
670 return false;
671 }
672
673 std::string data_name = WriteToFile(data, test_name + ".ass");
674 std::string error_msg;
675 if (!DisassembleBinary(data_name, &error_msg)) {
676 LOG(INFO) << "Error disassembling: " << error_msg;
677 std::remove(data_name.c_str());
678 return false;
679 }
680
681 std::string as_name = WriteToFile(as, test_name + ".gcc");
682 if (!DisassembleBinary(as_name, &error_msg)) {
683 LOG(INFO) << "Error disassembling: " << error_msg;
684 std::remove(data_name.c_str());
685 std::remove((data_name + ".dis").c_str());
686 std::remove(as_name.c_str());
687 return false;
688 }
689
690 bool result = CompareFiles(data_name + ".dis", as_name + ".dis");
691
Andreas Gampe65bec692015-01-14 12:03:36 -0800692 if (!kKeepDisassembledFiles) {
693 std::remove(data_name.c_str());
694 std::remove(as_name.c_str());
695 std::remove((data_name + ".dis").c_str());
696 std::remove((as_name + ".dis").c_str());
697 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700698
699 return result;
700 }
701
702 bool DisassembleBinary(std::string file, std::string* error_msg) {
703 std::vector<std::string> args;
704
Roland Levillain1a28fc42014-11-13 18:03:06 +0000705 // Encaspulate the whole command line in a single string passed to
706 // the shell, so that GetDisassembleCommand() may contain arguments
707 // in addition to the program name.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700708 args.push_back(GetDisassembleCommand());
709 args.push_back(file);
710 args.push_back("| sed -n \'/<.data>/,$p\' | sed -e \'s/.*://\'");
711 args.push_back(">");
712 args.push_back(file+".dis");
713 std::string cmd = Join(args, ' ');
714
715 args.clear();
716 args.push_back("/bin/sh");
717 args.push_back("-c");
718 args.push_back(cmd);
719
720 return Exec(args, error_msg);
721 }
722
723 std::string WriteToFile(std::vector<uint8_t>& buffer, std::string test_name) {
724 std::string file_name = GetTmpnam() + std::string("---") + test_name;
725 const char* data = reinterpret_cast<char*>(buffer.data());
726 std::ofstream s_out(file_name + ".o");
727 s_out.write(data, buffer.size());
728 s_out.close();
729 return file_name + ".o";
730 }
731
732 bool CompareFiles(std::string f1, std::string f2) {
733 std::ifstream f1_in(f1);
734 std::ifstream f2_in(f2);
735
736 bool result = std::equal(std::istreambuf_iterator<char>(f1_in),
737 std::istreambuf_iterator<char>(),
738 std::istreambuf_iterator<char>(f2_in));
739
740 f1_in.close();
741 f2_in.close();
742
743 return result;
744 }
745
746 // Compile the given assembly code and extract the binary, if possible. Put result into res.
747 bool Compile(std::string assembly_code, NativeAssemblerResult* res, std::string test_name) {
748 res->ok = false;
749 res->code.reset(nullptr);
750
751 res->base_name = GetTmpnam() + std::string("---") + test_name;
752
753 // TODO: Lots of error checking.
754
755 std::ofstream s_out(res->base_name + ".S");
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800756 const char* header = GetAssemblyHeader();
757 if (header != nullptr) {
758 s_out << header;
759 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700760 s_out << assembly_code;
761 s_out.close();
762
763 if (!Assemble((res->base_name + ".S").c_str(), (res->base_name + ".o").c_str(),
764 &res->error_msg)) {
765 res->error_msg = "Could not compile.";
766 return false;
767 }
768
769 std::string odump = Objdump(res->base_name + ".o");
770 if (odump.length() == 0) {
771 res->error_msg = "Objdump failed.";
772 return false;
773 }
774
775 std::istringstream iss(odump);
776 std::istream_iterator<std::string> start(iss);
777 std::istream_iterator<std::string> end;
778 std::vector<std::string> tokens(start, end);
779
780 if (tokens.size() < OBJDUMP_SECTION_LINE_MIN_TOKENS) {
781 res->error_msg = "Objdump output not recognized: too few tokens.";
782 return false;
783 }
784
785 if (tokens[1] != ".text") {
786 res->error_msg = "Objdump output not recognized: .text not second token.";
787 return false;
788 }
789
790 std::string lengthToken = "0x" + tokens[2];
791 std::istringstream(lengthToken) >> std::hex >> res->length;
792
793 std::string offsetToken = "0x" + tokens[5];
794 uintptr_t offset;
795 std::istringstream(offsetToken) >> std::hex >> offset;
796
797 std::ifstream obj(res->base_name + ".o");
798 obj.seekg(offset);
799 res->code.reset(new std::vector<uint8_t>(res->length));
800 obj.read(reinterpret_cast<char*>(&(*res->code)[0]), res->length);
801 obj.close();
802
803 res->ok = true;
804 return true;
805 }
806
807 // Remove temporary files.
808 void Clean(const NativeAssemblerResult* res) {
809 std::remove((res->base_name + ".S").c_str());
810 std::remove((res->base_name + ".o").c_str());
811 std::remove((res->base_name + ".o.dump").c_str());
812 }
813
814 // Check whether file exists. Is used for commands, so strips off any parameters: anything after
815 // the first space. We skip to the last slash for this, so it should work with directories with
816 // spaces.
817 static bool FileExists(std::string file) {
818 if (file.length() == 0) {
819 return false;
820 }
821
822 // Need to strip any options.
823 size_t last_slash = file.find_last_of('/');
824 if (last_slash == std::string::npos) {
825 // No slash, start looking at the start.
826 last_slash = 0;
827 }
828 size_t space_index = file.find(' ', last_slash);
829
830 if (space_index == std::string::npos) {
831 std::ifstream infile(file.c_str());
832 return infile.good();
833 } else {
834 std::string copy = file.substr(0, space_index - 1);
835
836 struct stat buf;
837 return stat(copy.c_str(), &buf) == 0;
838 }
839 }
840
841 static std::string GetGCCRootPath() {
842 return "prebuilts/gcc/linux-x86";
843 }
844
845 static std::string GetRootPath() {
846 // 1) Check ANDROID_BUILD_TOP
847 char* build_top = getenv("ANDROID_BUILD_TOP");
848 if (build_top != nullptr) {
849 return std::string(build_top) + "/";
850 }
851
852 // 2) Do cwd
853 char temp[1024];
854 return getcwd(temp, 1024) ? std::string(temp) + "/" : std::string("");
855 }
856
857 std::string FindTool(std::string tool_name) {
858 // Find the current tool. Wild-card pattern is "arch-string*tool-name".
859 std::string gcc_path = GetRootPath() + GetGCCRootPath();
860 std::vector<std::string> args;
861 args.push_back("find");
862 args.push_back(gcc_path);
863 args.push_back("-name");
864 args.push_back(GetArchitectureString() + "*" + tool_name);
865 args.push_back("|");
866 args.push_back("sort");
867 args.push_back("|");
868 args.push_back("tail");
869 args.push_back("-n");
870 args.push_back("1");
871 std::string tmp_file = GetTmpnam();
872 args.push_back(">");
873 args.push_back(tmp_file);
874 std::string sh_args = Join(args, ' ');
875
876 args.clear();
877 args.push_back("/bin/sh");
878 args.push_back("-c");
879 args.push_back(sh_args);
880
881 std::string error_msg;
882 if (!Exec(args, &error_msg)) {
883 EXPECT_TRUE(false) << error_msg;
884 return "";
885 }
886
887 std::ifstream in(tmp_file.c_str());
888 std::string line;
889 if (!std::getline(in, line)) {
890 in.close();
891 std::remove(tmp_file.c_str());
892 return "";
893 }
894 in.close();
895 std::remove(tmp_file.c_str());
896 return line;
897 }
898
899 // Use a consistent tmpnam, so store it.
900 std::string GetTmpnam() {
901 if (tmpnam_.length() == 0) {
Andreas Gampeb40c6a72014-05-02 14:25:12 -0700902 ScratchFile tmp;
903 tmpnam_ = tmp.GetFilename() + "asm";
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700904 }
905 return tmpnam_;
906 }
907
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800908 static constexpr size_t kWarnManyCombinationsThreshold = 500;
Andreas Gampe851df202014-11-12 14:05:46 -0800909 static constexpr size_t OBJDUMP_SECTION_LINE_MIN_TOKENS = 6;
910
Ian Rogers700a4022014-05-19 16:49:03 -0700911 std::unique_ptr<Ass> assembler_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700912
913 std::string resolved_assembler_cmd_;
914 std::string resolved_objdump_cmd_;
915 std::string resolved_disassemble_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700916
Andreas Gampe7747c8d2014-08-06 14:53:03 -0700917 std::string android_data_;
918
Andreas Gampe851df202014-11-12 14:05:46 -0800919 DISALLOW_COPY_AND_ASSIGN(AssemblerTest);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700920};
921
922} // namespace art
923
924#endif // ART_COMPILER_UTILS_ASSEMBLER_TEST_H_