blob: 2b5512077ea060e9c5b82927d84ff062b1f4608a [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 Gampe851df202014-11-12 14:05:46 -080032// Helper for a constexpr string length.
33constexpr size_t ConstexprStrLen(char const* str, size_t count = 0) {
34 return ('\0' == str[0]) ? count : ConstexprStrLen(str+1, count+1);
35}
36
Andreas Gampeb40c6a72014-05-02 14:25:12 -070037// Use a glocal static variable to keep the same name for all test data. Else we'll just spam the
38// temp directory.
39static std::string tmpnam_;
40
Andreas Gampe849cc5e2014-11-18 13:46:46 -080041enum class RegisterView { // private
42 kUsePrimaryName,
43 kUseSecondaryName
44};
45
Andreas Gampe851df202014-11-12 14:05:46 -080046template<typename Ass, typename Reg, typename FPReg, typename Imm>
Andreas Gampe5a4fa822014-03-31 16:50:12 -070047class AssemblerTest : public testing::Test {
48 public:
49 Ass* GetAssembler() {
50 return assembler_.get();
51 }
52
Andreas Gampe851df202014-11-12 14:05:46 -080053 typedef std::string (*TestFn)(AssemblerTest* assembler_test, Ass* assembler);
Andreas Gampe5a4fa822014-03-31 16:50:12 -070054
55 void DriverFn(TestFn f, std::string test_name) {
Andreas Gampe851df202014-11-12 14:05:46 -080056 Driver(f(this, assembler_.get()), test_name);
Andreas Gampe5a4fa822014-03-31 16:50:12 -070057 }
58
59 // This driver assumes the assembler has already been called.
60 void DriverStr(std::string assembly_string, std::string test_name) {
61 Driver(assembly_string, test_name);
62 }
63
64 std::string RepeatR(void (Ass::*f)(Reg), std::string fmt) {
Andreas Gampe851df202014-11-12 14:05:46 -080065 return RepeatTemplatedRegister<Reg>(f,
66 GetRegisters(),
67 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
68 fmt);
69 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -070070
Andreas Gampe851df202014-11-12 14:05:46 -080071 std::string Repeatr(void (Ass::*f)(Reg), std::string fmt) {
72 return RepeatTemplatedRegister<Reg>(f,
73 GetRegisters(),
74 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
75 fmt);
Andreas Gampe5a4fa822014-03-31 16:50:12 -070076 }
77
78 std::string RepeatRR(void (Ass::*f)(Reg, Reg), std::string fmt) {
Andreas Gampe851df202014-11-12 14:05:46 -080079 return RepeatTemplatedRegisters<Reg, Reg>(f,
80 GetRegisters(),
81 GetRegisters(),
82 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
83 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
84 fmt);
85 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -070086
Andreas Gampe851df202014-11-12 14:05:46 -080087 std::string Repeatrr(void (Ass::*f)(Reg, Reg), std::string fmt) {
88 return RepeatTemplatedRegisters<Reg, Reg>(f,
89 GetRegisters(),
90 GetRegisters(),
91 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
92 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
93 fmt);
94 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -070095
Andreas Gampe851df202014-11-12 14:05:46 -080096 std::string RepeatRr(void (Ass::*f)(Reg, Reg), std::string fmt) {
97 return RepeatTemplatedRegisters<Reg, Reg>(f,
98 GetRegisters(),
99 GetRegisters(),
100 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
101 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
102 fmt);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700103 }
104
105 std::string RepeatRI(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes, std::string fmt) {
Andreas Gampe851df202014-11-12 14:05:46 -0800106 return RepeatRegisterImm<RegisterView::kUsePrimaryName>(f, imm_bytes, fmt);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700107 }
108
Andreas Gampe851df202014-11-12 14:05:46 -0800109 std::string Repeatri(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes, std::string fmt) {
110 return RepeatRegisterImm<RegisterView::kUseSecondaryName>(f, imm_bytes, fmt);
111 }
112
113 std::string RepeatFF(void (Ass::*f)(FPReg, FPReg), std::string fmt) {
114 return RepeatTemplatedRegisters<FPReg, FPReg>(f,
115 GetFPRegisters(),
116 GetFPRegisters(),
117 &AssemblerTest::GetFPRegName,
118 &AssemblerTest::GetFPRegName,
119 fmt);
120 }
121
122 std::string RepeatFR(void (Ass::*f)(FPReg, Reg), std::string fmt) {
123 return RepeatTemplatedRegisters<FPReg, Reg>(f,
124 GetFPRegisters(),
125 GetRegisters(),
126 &AssemblerTest::GetFPRegName,
127 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
128 fmt);
129 }
130
131 std::string RepeatFr(void (Ass::*f)(FPReg, Reg), std::string fmt) {
132 return RepeatTemplatedRegisters<FPReg, Reg>(f,
133 GetFPRegisters(),
134 GetRegisters(),
135 &AssemblerTest::GetFPRegName,
136 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
137 fmt);
138 }
139
140 std::string RepeatRF(void (Ass::*f)(Reg, FPReg), std::string fmt) {
141 return RepeatTemplatedRegisters<Reg, FPReg>(f,
142 GetRegisters(),
143 GetFPRegisters(),
144 &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
145 &AssemblerTest::GetFPRegName,
146 fmt);
147 }
148
149 std::string RepeatrF(void (Ass::*f)(Reg, FPReg), std::string fmt) {
150 return RepeatTemplatedRegisters<Reg, FPReg>(f,
151 GetRegisters(),
152 GetFPRegisters(),
153 &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
154 &AssemblerTest::GetFPRegName,
155 fmt);
156 }
157
158 std::string RepeatI(void (Ass::*f)(const Imm&), size_t imm_bytes, std::string fmt,
159 bool as_uint = false) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700160 std::string str;
Andreas Gampe851df202014-11-12 14:05:46 -0800161 std::vector<int64_t> imms = CreateImmediateValues(imm_bytes, as_uint);
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800162
163 WarnOnCombinations(imms.size());
164
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700165 for (int64_t imm : imms) {
Ian Rogerscf7f1912014-10-22 22:06:39 -0700166 Imm new_imm = CreateImmediate(imm);
167 (assembler_.get()->*f)(new_imm);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700168 std::string base = fmt;
169
Andreas Gampe851df202014-11-12 14:05:46 -0800170 size_t imm_index = base.find(IMM_TOKEN);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700171 if (imm_index != std::string::npos) {
172 std::ostringstream sreg;
173 sreg << imm;
174 std::string imm_string = sreg.str();
Andreas Gampe851df202014-11-12 14:05:46 -0800175 base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700176 }
177
178 if (str.size() > 0) {
179 str += "\n";
180 }
181 str += base;
182 }
183 // Add a newline at the end.
184 str += "\n";
185 return str;
186 }
187
188 // This is intended to be run as a test.
189 bool CheckTools() {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800190 if (!FileExists(FindTool(GetAssemblerCmdName()))) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700191 return false;
192 }
193 LOG(INFO) << "Chosen assembler command: " << GetAssemblerCommand();
194
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800195 if (!FileExists(FindTool(GetObjdumpCmdName()))) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700196 return false;
197 }
198 LOG(INFO) << "Chosen objdump command: " << GetObjdumpCommand();
199
200 // Disassembly is optional.
201 std::string disassembler = GetDisassembleCommand();
202 if (disassembler.length() != 0) {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800203 if (!FileExists(FindTool(GetDisassembleCmdName()))) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700204 return false;
205 }
206 LOG(INFO) << "Chosen disassemble command: " << GetDisassembleCommand();
207 } else {
208 LOG(INFO) << "No disassembler given.";
209 }
210
211 return true;
212 }
213
Andreas Gampe851df202014-11-12 14:05:46 -0800214 // The following functions are public so that TestFn can use them...
215
216 virtual std::vector<Reg*> GetRegisters() = 0;
217
218 virtual std::vector<FPReg*> GetFPRegisters() {
219 UNIMPLEMENTED(FATAL) << "Architecture does not support floating-point registers";
220 UNREACHABLE();
221 }
222
223 // Secondary register names are the secondary view on registers, e.g., 32b on 64b systems.
224 virtual std::string GetSecondaryRegisterName(const Reg& reg ATTRIBUTE_UNUSED) {
225 UNIMPLEMENTED(FATAL) << "Architecture does not support secondary registers";
226 UNREACHABLE();
227 }
228
Calin Juravle9aec02f2014-11-18 23:06:35 +0000229 std::string GetRegisterName(const Reg& reg) {
230 return GetRegName<RegisterView::kUsePrimaryName>(reg);
231 }
232
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700233 protected:
Andreas Gampe851df202014-11-12 14:05:46 -0800234 explicit AssemblerTest() {}
235
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700236 void SetUp() OVERRIDE {
237 assembler_.reset(new Ass());
238
Andreas Gampeb40c6a72014-05-02 14:25:12 -0700239 // Fake a runtime test for ScratchFile
Andreas Gampe7747c8d2014-08-06 14:53:03 -0700240 CommonRuntimeTest::SetUpAndroidData(android_data_);
Andreas Gampeb40c6a72014-05-02 14:25:12 -0700241
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700242 SetUpHelpers();
243 }
244
Andreas Gampe7747c8d2014-08-06 14:53:03 -0700245 void TearDown() OVERRIDE {
246 // We leave temporaries in case this failed so we can debug issues.
247 CommonRuntimeTest::TearDownAndroidData(android_data_, false);
248 tmpnam_ = "";
249 }
250
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700251 // Override this to set up any architecture-specific things, e.g., register vectors.
252 virtual void SetUpHelpers() {}
253
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700254 // Get the typically used name for this architecture, e.g., aarch64, x86_64, ...
255 virtual std::string GetArchitectureString() = 0;
256
257 // Get the name of the assembler, e.g., "as" by default.
258 virtual std::string GetAssemblerCmdName() {
259 return "as";
260 }
261
262 // Switches to the assembler command. Default none.
263 virtual std::string GetAssemblerParameters() {
264 return "";
265 }
266
267 // Return the host assembler command for this test.
268 virtual std::string GetAssemblerCommand() {
269 // Already resolved it once?
270 if (resolved_assembler_cmd_.length() != 0) {
271 return resolved_assembler_cmd_;
272 }
273
274 std::string line = FindTool(GetAssemblerCmdName());
275 if (line.length() == 0) {
276 return line;
277 }
278
279 resolved_assembler_cmd_ = line + GetAssemblerParameters();
280
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800281 return resolved_assembler_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700282 }
283
284 // Get the name of the objdump, e.g., "objdump" by default.
285 virtual std::string GetObjdumpCmdName() {
286 return "objdump";
287 }
288
289 // Switches to the objdump command. Default is " -h".
290 virtual std::string GetObjdumpParameters() {
291 return " -h";
292 }
293
294 // Return the host objdump command for this test.
295 virtual std::string GetObjdumpCommand() {
296 // Already resolved it once?
297 if (resolved_objdump_cmd_.length() != 0) {
298 return resolved_objdump_cmd_;
299 }
300
301 std::string line = FindTool(GetObjdumpCmdName());
302 if (line.length() == 0) {
303 return line;
304 }
305
306 resolved_objdump_cmd_ = line + GetObjdumpParameters();
307
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800308 return resolved_objdump_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700309 }
310
311 // Get the name of the objdump, e.g., "objdump" by default.
312 virtual std::string GetDisassembleCmdName() {
313 return "objdump";
314 }
315
316 // Switches to the objdump command. As it's a binary, one needs to push the architecture and
317 // such to objdump, so it's architecture-specific and there is no default.
318 virtual std::string GetDisassembleParameters() = 0;
319
320 // Return the host disassembler command for this test.
321 virtual std::string GetDisassembleCommand() {
322 // Already resolved it once?
323 if (resolved_disassemble_cmd_.length() != 0) {
324 return resolved_disassemble_cmd_;
325 }
326
327 std::string line = FindTool(GetDisassembleCmdName());
328 if (line.length() == 0) {
329 return line;
330 }
331
332 resolved_disassemble_cmd_ = line + GetDisassembleParameters();
333
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800334 return resolved_disassemble_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700335 }
336
337 // Create a couple of immediate values up to the number of bytes given.
Andreas Gampe851df202014-11-12 14:05:46 -0800338 virtual std::vector<int64_t> CreateImmediateValues(size_t imm_bytes, bool as_uint = false) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700339 std::vector<int64_t> res;
340 res.push_back(0);
Andreas Gampe851df202014-11-12 14:05:46 -0800341 if (!as_uint) {
342 res.push_back(-1);
343 } else {
344 res.push_back(0xFF);
345 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700346 res.push_back(0x12);
347 if (imm_bytes >= 2) {
348 res.push_back(0x1234);
Andreas Gampe851df202014-11-12 14:05:46 -0800349 if (!as_uint) {
350 res.push_back(-0x1234);
351 } else {
352 res.push_back(0xFFFF);
353 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700354 if (imm_bytes >= 4) {
355 res.push_back(0x12345678);
Andreas Gampe851df202014-11-12 14:05:46 -0800356 if (!as_uint) {
357 res.push_back(-0x12345678);
358 } else {
359 res.push_back(0xFFFFFFFF);
360 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700361 if (imm_bytes >= 6) {
362 res.push_back(0x123456789ABC);
Andreas Gampe851df202014-11-12 14:05:46 -0800363 if (!as_uint) {
364 res.push_back(-0x123456789ABC);
365 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700366 if (imm_bytes >= 8) {
367 res.push_back(0x123456789ABCDEF0);
Andreas Gampe851df202014-11-12 14:05:46 -0800368 if (!as_uint) {
369 res.push_back(-0x123456789ABCDEF0);
370 } else {
371 res.push_back(0xFFFFFFFFFFFFFFFF);
372 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700373 }
374 }
375 }
376 }
377 return res;
378 }
379
380 // Create an immediate from the specific value.
Ian Rogerscf7f1912014-10-22 22:06:39 -0700381 virtual Imm CreateImmediate(int64_t imm_value) = 0;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700382
Andreas Gampe851df202014-11-12 14:05:46 -0800383 template <typename RegType>
384 std::string RepeatTemplatedRegister(void (Ass::*f)(RegType),
385 const std::vector<RegType*> registers,
386 std::string (AssemblerTest::*GetName)(const RegType&),
387 std::string fmt) {
388 std::string str;
389 for (auto reg : registers) {
390 (assembler_.get()->*f)(*reg);
391 std::string base = fmt;
392
393 std::string reg_string = (this->*GetName)(*reg);
394 size_t reg_index;
395 if ((reg_index = base.find(REG_TOKEN)) != std::string::npos) {
396 base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string);
397 }
398
399 if (str.size() > 0) {
400 str += "\n";
401 }
402 str += base;
403 }
404 // Add a newline at the end.
405 str += "\n";
406 return str;
407 }
408
409 template <typename Reg1, typename Reg2>
410 std::string RepeatTemplatedRegisters(void (Ass::*f)(Reg1, Reg2),
411 const std::vector<Reg1*> reg1_registers,
412 const std::vector<Reg2*> reg2_registers,
413 std::string (AssemblerTest::*GetName1)(const Reg1&),
414 std::string (AssemblerTest::*GetName2)(const Reg2&),
415 std::string fmt) {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800416 WarnOnCombinations(reg1_registers.size() * reg2_registers.size());
417
Andreas Gampe851df202014-11-12 14:05:46 -0800418 std::string str;
419 for (auto reg1 : reg1_registers) {
420 for (auto reg2 : reg2_registers) {
421 (assembler_.get()->*f)(*reg1, *reg2);
422 std::string base = fmt;
423
424 std::string reg1_string = (this->*GetName1)(*reg1);
425 size_t reg1_index;
426 while ((reg1_index = base.find(REG1_TOKEN)) != std::string::npos) {
427 base.replace(reg1_index, ConstexprStrLen(REG1_TOKEN), reg1_string);
428 }
429
430 std::string reg2_string = (this->*GetName2)(*reg2);
431 size_t reg2_index;
432 while ((reg2_index = base.find(REG2_TOKEN)) != std::string::npos) {
433 base.replace(reg2_index, ConstexprStrLen(REG2_TOKEN), reg2_string);
434 }
435
436 if (str.size() > 0) {
437 str += "\n";
438 }
439 str += base;
440 }
441 }
442 // Add a newline at the end.
443 str += "\n";
444 return str;
445 }
446
Andreas Gampe851df202014-11-12 14:05:46 -0800447 template <RegisterView kRegView>
448 std::string GetRegName(const Reg& reg) {
449 std::ostringstream sreg;
450 switch (kRegView) {
451 case RegisterView::kUsePrimaryName:
452 sreg << reg;
453 break;
454
455 case RegisterView::kUseSecondaryName:
456 sreg << GetSecondaryRegisterName(reg);
457 break;
458 }
459 return sreg.str();
460 }
461
462 std::string GetFPRegName(const FPReg& reg) {
463 std::ostringstream sreg;
464 sreg << reg;
465 return sreg.str();
466 }
467
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800468 // If the assembly file needs a header, return it in a sub-class.
469 virtual const char* GetAssemblyHeader() {
470 return nullptr;
471 }
472
473 void WarnOnCombinations(size_t count) {
474 if (count > kWarnManyCombinationsThreshold) {
475 GTEST_LOG_(WARNING) << "Many combinations (" << count << "), test generation might be slow.";
476 }
477 }
478
479 static constexpr const char* REG_TOKEN = "{reg}";
480 static constexpr const char* REG1_TOKEN = "{reg1}";
481 static constexpr const char* REG2_TOKEN = "{reg2}";
482 static constexpr const char* IMM_TOKEN = "{imm}";
483
484 private:
Andreas Gampe851df202014-11-12 14:05:46 -0800485 template <RegisterView kRegView>
486 std::string RepeatRegisterImm(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes,
487 std::string fmt) {
488 const std::vector<Reg*> registers = GetRegisters();
489 std::string str;
490 std::vector<int64_t> imms = CreateImmediateValues(imm_bytes);
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800491
492 WarnOnCombinations(registers.size() * imms.size());
493
Andreas Gampe851df202014-11-12 14:05:46 -0800494 for (auto reg : registers) {
495 for (int64_t imm : imms) {
496 Imm new_imm = CreateImmediate(imm);
497 (assembler_.get()->*f)(*reg, new_imm);
498 std::string base = fmt;
499
500 std::string reg_string = GetRegName<kRegView>(*reg);
501 size_t reg_index;
502 while ((reg_index = base.find(REG_TOKEN)) != std::string::npos) {
503 base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string);
504 }
505
506 size_t imm_index = base.find(IMM_TOKEN);
507 if (imm_index != std::string::npos) {
508 std::ostringstream sreg;
509 sreg << imm;
510 std::string imm_string = sreg.str();
511 base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
512 }
513
514 if (str.size() > 0) {
515 str += "\n";
516 }
517 str += base;
518 }
519 }
520 // Add a newline at the end.
521 str += "\n";
522 return str;
523 }
524
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700525 // Driver() assembles and compares the results. If the results are not equal and we have a
526 // disassembler, disassemble both and check whether they have the same mnemonics (in which case
527 // we just warn).
528 void Driver(std::string assembly_text, std::string test_name) {
529 EXPECT_NE(assembly_text.length(), 0U) << "Empty assembly";
530
531 NativeAssemblerResult res;
532 Compile(assembly_text, &res, test_name);
533
534 EXPECT_TRUE(res.ok) << res.error_msg;
535 if (!res.ok) {
536 // No way of continuing.
537 return;
538 }
539
540 size_t cs = assembler_->CodeSize();
Ian Rogers700a4022014-05-19 16:49:03 -0700541 std::unique_ptr<std::vector<uint8_t>> data(new std::vector<uint8_t>(cs));
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700542 MemoryRegion code(&(*data)[0], data->size());
543 assembler_->FinalizeInstructions(code);
544
545 if (*data == *res.code) {
546 Clean(&res);
547 } else {
548 if (DisassembleBinaries(*data, *res.code, test_name)) {
549 if (data->size() > res.code->size()) {
Andreas Gampe54e15de2014-08-06 15:31:06 -0700550 // Fail this test with a fancy colored warning being printed.
551 EXPECT_TRUE(false) << "Assembly code is not identical, but disassembly of machine code "
552 "is equal: this implies sub-optimal encoding! Our code size=" << data->size() <<
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700553 ", gcc size=" << res.code->size();
554 } else {
Andreas Gampe54e15de2014-08-06 15:31:06 -0700555 // Otherwise just print an info message and clean up.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700556 LOG(INFO) << "GCC chose a different encoding than ours, but the overall length is the "
557 "same.";
Andreas Gampe54e15de2014-08-06 15:31:06 -0700558 Clean(&res);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700559 }
560 } else {
561 // This will output the assembly.
Nicolas Geoffray102cbed2014-10-15 18:31:05 +0100562 EXPECT_EQ(*res.code, *data) << "Outputs (and disassembly) not identical.";
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700563 }
564 }
565 }
566
567 // Structure to store intermediates and results.
568 struct NativeAssemblerResult {
569 bool ok;
570 std::string error_msg;
571 std::string base_name;
Ian Rogers700a4022014-05-19 16:49:03 -0700572 std::unique_ptr<std::vector<uint8_t>> code;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700573 uintptr_t length;
574 };
575
576 // Compile the assembly file from_file to a binary file to_file. Returns true on success.
577 bool Assemble(const char* from_file, const char* to_file, std::string* error_msg) {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800578 bool have_assembler = FileExists(FindTool(GetAssemblerCmdName()));
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700579 EXPECT_TRUE(have_assembler) << "Cannot find assembler:" << GetAssemblerCommand();
580 if (!have_assembler) {
581 return false;
582 }
583
584 std::vector<std::string> args;
585
Roland Levillain1a28fc42014-11-13 18:03:06 +0000586 // Encaspulate the whole command line in a single string passed to
587 // the shell, so that GetAssemblerCommand() may contain arguments
588 // in addition to the program name.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700589 args.push_back(GetAssemblerCommand());
590 args.push_back("-o");
591 args.push_back(to_file);
592 args.push_back(from_file);
Roland Levillain1a28fc42014-11-13 18:03:06 +0000593 std::string cmd = Join(args, ' ');
594
595 args.clear();
596 args.push_back("/bin/sh");
597 args.push_back("-c");
598 args.push_back(cmd);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700599
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800600 bool success = Exec(args, error_msg);
601 if (!success) {
602 LOG(INFO) << "Assembler command line:";
603 for (std::string arg : args) {
604 LOG(INFO) << arg;
605 }
606 }
607 return success;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700608 }
609
610 // Runs objdump -h on the binary file and extracts the first line with .text.
611 // Returns "" on failure.
612 std::string Objdump(std::string file) {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800613 bool have_objdump = FileExists(FindTool(GetObjdumpCmdName()));
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700614 EXPECT_TRUE(have_objdump) << "Cannot find objdump: " << GetObjdumpCommand();
615 if (!have_objdump) {
616 return "";
617 }
618
619 std::string error_msg;
620 std::vector<std::string> args;
621
Roland Levillain1a28fc42014-11-13 18:03:06 +0000622 // Encaspulate the whole command line in a single string passed to
623 // the shell, so that GetObjdumpCommand() may contain arguments
624 // in addition to the program name.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700625 args.push_back(GetObjdumpCommand());
626 args.push_back(file);
627 args.push_back(">");
628 args.push_back(file+".dump");
629 std::string cmd = Join(args, ' ');
630
631 args.clear();
632 args.push_back("/bin/sh");
633 args.push_back("-c");
634 args.push_back(cmd);
635
636 if (!Exec(args, &error_msg)) {
637 EXPECT_TRUE(false) << error_msg;
638 }
639
640 std::ifstream dump(file+".dump");
641
642 std::string line;
643 bool found = false;
644 while (std::getline(dump, line)) {
645 if (line.find(".text") != line.npos) {
646 found = true;
647 break;
648 }
649 }
650
651 dump.close();
652
653 if (found) {
654 return line;
655 } else {
656 return "";
657 }
658 }
659
660 // Disassemble both binaries and compare the text.
661 bool DisassembleBinaries(std::vector<uint8_t>& data, std::vector<uint8_t>& as,
662 std::string test_name) {
663 std::string disassembler = GetDisassembleCommand();
664 if (disassembler.length() == 0) {
665 LOG(WARNING) << "No dissassembler command.";
666 return false;
667 }
668
669 std::string data_name = WriteToFile(data, test_name + ".ass");
670 std::string error_msg;
671 if (!DisassembleBinary(data_name, &error_msg)) {
672 LOG(INFO) << "Error disassembling: " << error_msg;
673 std::remove(data_name.c_str());
674 return false;
675 }
676
677 std::string as_name = WriteToFile(as, test_name + ".gcc");
678 if (!DisassembleBinary(as_name, &error_msg)) {
679 LOG(INFO) << "Error disassembling: " << error_msg;
680 std::remove(data_name.c_str());
681 std::remove((data_name + ".dis").c_str());
682 std::remove(as_name.c_str());
683 return false;
684 }
685
686 bool result = CompareFiles(data_name + ".dis", as_name + ".dis");
687
Andreas Gampe851df202014-11-12 14:05:46 -0800688 // If you want to take a look at the differences between the ART assembler and GCC, comment
689 // out the removal code.
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800690// std::remove(data_name.c_str());
691// std::remove(as_name.c_str());
692// std::remove((data_name + ".dis").c_str());
693// std::remove((as_name + ".dis").c_str());
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700694
695 return result;
696 }
697
698 bool DisassembleBinary(std::string file, std::string* error_msg) {
699 std::vector<std::string> args;
700
Roland Levillain1a28fc42014-11-13 18:03:06 +0000701 // Encaspulate the whole command line in a single string passed to
702 // the shell, so that GetDisassembleCommand() may contain arguments
703 // in addition to the program name.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700704 args.push_back(GetDisassembleCommand());
705 args.push_back(file);
706 args.push_back("| sed -n \'/<.data>/,$p\' | sed -e \'s/.*://\'");
707 args.push_back(">");
708 args.push_back(file+".dis");
709 std::string cmd = Join(args, ' ');
710
711 args.clear();
712 args.push_back("/bin/sh");
713 args.push_back("-c");
714 args.push_back(cmd);
715
716 return Exec(args, error_msg);
717 }
718
719 std::string WriteToFile(std::vector<uint8_t>& buffer, std::string test_name) {
720 std::string file_name = GetTmpnam() + std::string("---") + test_name;
721 const char* data = reinterpret_cast<char*>(buffer.data());
722 std::ofstream s_out(file_name + ".o");
723 s_out.write(data, buffer.size());
724 s_out.close();
725 return file_name + ".o";
726 }
727
728 bool CompareFiles(std::string f1, std::string f2) {
729 std::ifstream f1_in(f1);
730 std::ifstream f2_in(f2);
731
732 bool result = std::equal(std::istreambuf_iterator<char>(f1_in),
733 std::istreambuf_iterator<char>(),
734 std::istreambuf_iterator<char>(f2_in));
735
736 f1_in.close();
737 f2_in.close();
738
739 return result;
740 }
741
742 // Compile the given assembly code and extract the binary, if possible. Put result into res.
743 bool Compile(std::string assembly_code, NativeAssemblerResult* res, std::string test_name) {
744 res->ok = false;
745 res->code.reset(nullptr);
746
747 res->base_name = GetTmpnam() + std::string("---") + test_name;
748
749 // TODO: Lots of error checking.
750
751 std::ofstream s_out(res->base_name + ".S");
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800752 const char* header = GetAssemblyHeader();
753 if (header != nullptr) {
754 s_out << header;
755 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700756 s_out << assembly_code;
757 s_out.close();
758
759 if (!Assemble((res->base_name + ".S").c_str(), (res->base_name + ".o").c_str(),
760 &res->error_msg)) {
761 res->error_msg = "Could not compile.";
762 return false;
763 }
764
765 std::string odump = Objdump(res->base_name + ".o");
766 if (odump.length() == 0) {
767 res->error_msg = "Objdump failed.";
768 return false;
769 }
770
771 std::istringstream iss(odump);
772 std::istream_iterator<std::string> start(iss);
773 std::istream_iterator<std::string> end;
774 std::vector<std::string> tokens(start, end);
775
776 if (tokens.size() < OBJDUMP_SECTION_LINE_MIN_TOKENS) {
777 res->error_msg = "Objdump output not recognized: too few tokens.";
778 return false;
779 }
780
781 if (tokens[1] != ".text") {
782 res->error_msg = "Objdump output not recognized: .text not second token.";
783 return false;
784 }
785
786 std::string lengthToken = "0x" + tokens[2];
787 std::istringstream(lengthToken) >> std::hex >> res->length;
788
789 std::string offsetToken = "0x" + tokens[5];
790 uintptr_t offset;
791 std::istringstream(offsetToken) >> std::hex >> offset;
792
793 std::ifstream obj(res->base_name + ".o");
794 obj.seekg(offset);
795 res->code.reset(new std::vector<uint8_t>(res->length));
796 obj.read(reinterpret_cast<char*>(&(*res->code)[0]), res->length);
797 obj.close();
798
799 res->ok = true;
800 return true;
801 }
802
803 // Remove temporary files.
804 void Clean(const NativeAssemblerResult* res) {
805 std::remove((res->base_name + ".S").c_str());
806 std::remove((res->base_name + ".o").c_str());
807 std::remove((res->base_name + ".o.dump").c_str());
808 }
809
810 // Check whether file exists. Is used for commands, so strips off any parameters: anything after
811 // the first space. We skip to the last slash for this, so it should work with directories with
812 // spaces.
813 static bool FileExists(std::string file) {
814 if (file.length() == 0) {
815 return false;
816 }
817
818 // Need to strip any options.
819 size_t last_slash = file.find_last_of('/');
820 if (last_slash == std::string::npos) {
821 // No slash, start looking at the start.
822 last_slash = 0;
823 }
824 size_t space_index = file.find(' ', last_slash);
825
826 if (space_index == std::string::npos) {
827 std::ifstream infile(file.c_str());
828 return infile.good();
829 } else {
830 std::string copy = file.substr(0, space_index - 1);
831
832 struct stat buf;
833 return stat(copy.c_str(), &buf) == 0;
834 }
835 }
836
837 static std::string GetGCCRootPath() {
838 return "prebuilts/gcc/linux-x86";
839 }
840
841 static std::string GetRootPath() {
842 // 1) Check ANDROID_BUILD_TOP
843 char* build_top = getenv("ANDROID_BUILD_TOP");
844 if (build_top != nullptr) {
845 return std::string(build_top) + "/";
846 }
847
848 // 2) Do cwd
849 char temp[1024];
850 return getcwd(temp, 1024) ? std::string(temp) + "/" : std::string("");
851 }
852
853 std::string FindTool(std::string tool_name) {
854 // Find the current tool. Wild-card pattern is "arch-string*tool-name".
855 std::string gcc_path = GetRootPath() + GetGCCRootPath();
856 std::vector<std::string> args;
857 args.push_back("find");
858 args.push_back(gcc_path);
859 args.push_back("-name");
860 args.push_back(GetArchitectureString() + "*" + tool_name);
861 args.push_back("|");
862 args.push_back("sort");
863 args.push_back("|");
864 args.push_back("tail");
865 args.push_back("-n");
866 args.push_back("1");
867 std::string tmp_file = GetTmpnam();
868 args.push_back(">");
869 args.push_back(tmp_file);
870 std::string sh_args = Join(args, ' ');
871
872 args.clear();
873 args.push_back("/bin/sh");
874 args.push_back("-c");
875 args.push_back(sh_args);
876
877 std::string error_msg;
878 if (!Exec(args, &error_msg)) {
879 EXPECT_TRUE(false) << error_msg;
880 return "";
881 }
882
883 std::ifstream in(tmp_file.c_str());
884 std::string line;
885 if (!std::getline(in, line)) {
886 in.close();
887 std::remove(tmp_file.c_str());
888 return "";
889 }
890 in.close();
891 std::remove(tmp_file.c_str());
892 return line;
893 }
894
895 // Use a consistent tmpnam, so store it.
896 std::string GetTmpnam() {
897 if (tmpnam_.length() == 0) {
Andreas Gampeb40c6a72014-05-02 14:25:12 -0700898 ScratchFile tmp;
899 tmpnam_ = tmp.GetFilename() + "asm";
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700900 }
901 return tmpnam_;
902 }
903
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800904 static constexpr size_t kWarnManyCombinationsThreshold = 500;
Andreas Gampe851df202014-11-12 14:05:46 -0800905 static constexpr size_t OBJDUMP_SECTION_LINE_MIN_TOKENS = 6;
906
Ian Rogers700a4022014-05-19 16:49:03 -0700907 std::unique_ptr<Ass> assembler_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700908
909 std::string resolved_assembler_cmd_;
910 std::string resolved_objdump_cmd_;
911 std::string resolved_disassemble_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700912
Andreas Gampe7747c8d2014-08-06 14:53:03 -0700913 std::string android_data_;
914
Andreas Gampe851df202014-11-12 14:05:46 -0800915 DISALLOW_COPY_AND_ASSIGN(AssemblerTest);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700916};
917
918} // namespace art
919
920#endif // ART_COMPILER_UTILS_ASSEMBLER_TEST_H_