blob: 1fadb916fc3dbf8895d6f451f6ee1779fb2c87bd [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
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700229 protected:
Andreas Gampe851df202014-11-12 14:05:46 -0800230 explicit AssemblerTest() {}
231
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700232 void SetUp() OVERRIDE {
233 assembler_.reset(new Ass());
234
Andreas Gampeb40c6a72014-05-02 14:25:12 -0700235 // Fake a runtime test for ScratchFile
Andreas Gampe7747c8d2014-08-06 14:53:03 -0700236 CommonRuntimeTest::SetUpAndroidData(android_data_);
Andreas Gampeb40c6a72014-05-02 14:25:12 -0700237
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700238 SetUpHelpers();
239 }
240
Andreas Gampe7747c8d2014-08-06 14:53:03 -0700241 void TearDown() OVERRIDE {
242 // We leave temporaries in case this failed so we can debug issues.
243 CommonRuntimeTest::TearDownAndroidData(android_data_, false);
244 tmpnam_ = "";
245 }
246
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700247 // Override this to set up any architecture-specific things, e.g., register vectors.
248 virtual void SetUpHelpers() {}
249
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700250 // Get the typically used name for this architecture, e.g., aarch64, x86_64, ...
251 virtual std::string GetArchitectureString() = 0;
252
253 // Get the name of the assembler, e.g., "as" by default.
254 virtual std::string GetAssemblerCmdName() {
255 return "as";
256 }
257
258 // Switches to the assembler command. Default none.
259 virtual std::string GetAssemblerParameters() {
260 return "";
261 }
262
263 // Return the host assembler command for this test.
264 virtual std::string GetAssemblerCommand() {
265 // Already resolved it once?
266 if (resolved_assembler_cmd_.length() != 0) {
267 return resolved_assembler_cmd_;
268 }
269
270 std::string line = FindTool(GetAssemblerCmdName());
271 if (line.length() == 0) {
272 return line;
273 }
274
275 resolved_assembler_cmd_ = line + GetAssemblerParameters();
276
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800277 return resolved_assembler_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700278 }
279
280 // Get the name of the objdump, e.g., "objdump" by default.
281 virtual std::string GetObjdumpCmdName() {
282 return "objdump";
283 }
284
285 // Switches to the objdump command. Default is " -h".
286 virtual std::string GetObjdumpParameters() {
287 return " -h";
288 }
289
290 // Return the host objdump command for this test.
291 virtual std::string GetObjdumpCommand() {
292 // Already resolved it once?
293 if (resolved_objdump_cmd_.length() != 0) {
294 return resolved_objdump_cmd_;
295 }
296
297 std::string line = FindTool(GetObjdumpCmdName());
298 if (line.length() == 0) {
299 return line;
300 }
301
302 resolved_objdump_cmd_ = line + GetObjdumpParameters();
303
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800304 return resolved_objdump_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700305 }
306
307 // Get the name of the objdump, e.g., "objdump" by default.
308 virtual std::string GetDisassembleCmdName() {
309 return "objdump";
310 }
311
312 // Switches to the objdump command. As it's a binary, one needs to push the architecture and
313 // such to objdump, so it's architecture-specific and there is no default.
314 virtual std::string GetDisassembleParameters() = 0;
315
316 // Return the host disassembler command for this test.
317 virtual std::string GetDisassembleCommand() {
318 // Already resolved it once?
319 if (resolved_disassemble_cmd_.length() != 0) {
320 return resolved_disassemble_cmd_;
321 }
322
323 std::string line = FindTool(GetDisassembleCmdName());
324 if (line.length() == 0) {
325 return line;
326 }
327
328 resolved_disassemble_cmd_ = line + GetDisassembleParameters();
329
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800330 return resolved_disassemble_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700331 }
332
333 // Create a couple of immediate values up to the number of bytes given.
Andreas Gampe851df202014-11-12 14:05:46 -0800334 virtual std::vector<int64_t> CreateImmediateValues(size_t imm_bytes, bool as_uint = false) {
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700335 std::vector<int64_t> res;
336 res.push_back(0);
Andreas Gampe851df202014-11-12 14:05:46 -0800337 if (!as_uint) {
338 res.push_back(-1);
339 } else {
340 res.push_back(0xFF);
341 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700342 res.push_back(0x12);
343 if (imm_bytes >= 2) {
344 res.push_back(0x1234);
Andreas Gampe851df202014-11-12 14:05:46 -0800345 if (!as_uint) {
346 res.push_back(-0x1234);
347 } else {
348 res.push_back(0xFFFF);
349 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700350 if (imm_bytes >= 4) {
351 res.push_back(0x12345678);
Andreas Gampe851df202014-11-12 14:05:46 -0800352 if (!as_uint) {
353 res.push_back(-0x12345678);
354 } else {
355 res.push_back(0xFFFFFFFF);
356 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700357 if (imm_bytes >= 6) {
358 res.push_back(0x123456789ABC);
Andreas Gampe851df202014-11-12 14:05:46 -0800359 if (!as_uint) {
360 res.push_back(-0x123456789ABC);
361 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700362 if (imm_bytes >= 8) {
363 res.push_back(0x123456789ABCDEF0);
Andreas Gampe851df202014-11-12 14:05:46 -0800364 if (!as_uint) {
365 res.push_back(-0x123456789ABCDEF0);
366 } else {
367 res.push_back(0xFFFFFFFFFFFFFFFF);
368 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700369 }
370 }
371 }
372 }
373 return res;
374 }
375
376 // Create an immediate from the specific value.
Ian Rogerscf7f1912014-10-22 22:06:39 -0700377 virtual Imm CreateImmediate(int64_t imm_value) = 0;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700378
Andreas Gampe851df202014-11-12 14:05:46 -0800379 template <typename RegType>
380 std::string RepeatTemplatedRegister(void (Ass::*f)(RegType),
381 const std::vector<RegType*> registers,
382 std::string (AssemblerTest::*GetName)(const RegType&),
383 std::string fmt) {
384 std::string str;
385 for (auto reg : registers) {
386 (assembler_.get()->*f)(*reg);
387 std::string base = fmt;
388
389 std::string reg_string = (this->*GetName)(*reg);
390 size_t reg_index;
391 if ((reg_index = base.find(REG_TOKEN)) != std::string::npos) {
392 base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string);
393 }
394
395 if (str.size() > 0) {
396 str += "\n";
397 }
398 str += base;
399 }
400 // Add a newline at the end.
401 str += "\n";
402 return str;
403 }
404
405 template <typename Reg1, typename Reg2>
406 std::string RepeatTemplatedRegisters(void (Ass::*f)(Reg1, Reg2),
407 const std::vector<Reg1*> reg1_registers,
408 const std::vector<Reg2*> reg2_registers,
409 std::string (AssemblerTest::*GetName1)(const Reg1&),
410 std::string (AssemblerTest::*GetName2)(const Reg2&),
411 std::string fmt) {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800412 WarnOnCombinations(reg1_registers.size() * reg2_registers.size());
413
Andreas Gampe851df202014-11-12 14:05:46 -0800414 std::string str;
415 for (auto reg1 : reg1_registers) {
416 for (auto reg2 : reg2_registers) {
417 (assembler_.get()->*f)(*reg1, *reg2);
418 std::string base = fmt;
419
420 std::string reg1_string = (this->*GetName1)(*reg1);
421 size_t reg1_index;
422 while ((reg1_index = base.find(REG1_TOKEN)) != std::string::npos) {
423 base.replace(reg1_index, ConstexprStrLen(REG1_TOKEN), reg1_string);
424 }
425
426 std::string reg2_string = (this->*GetName2)(*reg2);
427 size_t reg2_index;
428 while ((reg2_index = base.find(REG2_TOKEN)) != std::string::npos) {
429 base.replace(reg2_index, ConstexprStrLen(REG2_TOKEN), reg2_string);
430 }
431
432 if (str.size() > 0) {
433 str += "\n";
434 }
435 str += base;
436 }
437 }
438 // Add a newline at the end.
439 str += "\n";
440 return str;
441 }
442
Andreas Gampe851df202014-11-12 14:05:46 -0800443 template <RegisterView kRegView>
444 std::string GetRegName(const Reg& reg) {
445 std::ostringstream sreg;
446 switch (kRegView) {
447 case RegisterView::kUsePrimaryName:
448 sreg << reg;
449 break;
450
451 case RegisterView::kUseSecondaryName:
452 sreg << GetSecondaryRegisterName(reg);
453 break;
454 }
455 return sreg.str();
456 }
457
458 std::string GetFPRegName(const FPReg& reg) {
459 std::ostringstream sreg;
460 sreg << reg;
461 return sreg.str();
462 }
463
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800464 // If the assembly file needs a header, return it in a sub-class.
465 virtual const char* GetAssemblyHeader() {
466 return nullptr;
467 }
468
469 void WarnOnCombinations(size_t count) {
470 if (count > kWarnManyCombinationsThreshold) {
471 GTEST_LOG_(WARNING) << "Many combinations (" << count << "), test generation might be slow.";
472 }
473 }
474
475 static constexpr const char* REG_TOKEN = "{reg}";
476 static constexpr const char* REG1_TOKEN = "{reg1}";
477 static constexpr const char* REG2_TOKEN = "{reg2}";
478 static constexpr const char* IMM_TOKEN = "{imm}";
479
480 private:
Andreas Gampe851df202014-11-12 14:05:46 -0800481 template <RegisterView kRegView>
482 std::string RepeatRegisterImm(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes,
483 std::string fmt) {
484 const std::vector<Reg*> registers = GetRegisters();
485 std::string str;
486 std::vector<int64_t> imms = CreateImmediateValues(imm_bytes);
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800487
488 WarnOnCombinations(registers.size() * imms.size());
489
Andreas Gampe851df202014-11-12 14:05:46 -0800490 for (auto reg : registers) {
491 for (int64_t imm : imms) {
492 Imm new_imm = CreateImmediate(imm);
493 (assembler_.get()->*f)(*reg, new_imm);
494 std::string base = fmt;
495
496 std::string reg_string = GetRegName<kRegView>(*reg);
497 size_t reg_index;
498 while ((reg_index = base.find(REG_TOKEN)) != std::string::npos) {
499 base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string);
500 }
501
502 size_t imm_index = base.find(IMM_TOKEN);
503 if (imm_index != std::string::npos) {
504 std::ostringstream sreg;
505 sreg << imm;
506 std::string imm_string = sreg.str();
507 base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
508 }
509
510 if (str.size() > 0) {
511 str += "\n";
512 }
513 str += base;
514 }
515 }
516 // Add a newline at the end.
517 str += "\n";
518 return str;
519 }
520
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700521 // Driver() assembles and compares the results. If the results are not equal and we have a
522 // disassembler, disassemble both and check whether they have the same mnemonics (in which case
523 // we just warn).
524 void Driver(std::string assembly_text, std::string test_name) {
525 EXPECT_NE(assembly_text.length(), 0U) << "Empty assembly";
526
527 NativeAssemblerResult res;
528 Compile(assembly_text, &res, test_name);
529
530 EXPECT_TRUE(res.ok) << res.error_msg;
531 if (!res.ok) {
532 // No way of continuing.
533 return;
534 }
535
536 size_t cs = assembler_->CodeSize();
Ian Rogers700a4022014-05-19 16:49:03 -0700537 std::unique_ptr<std::vector<uint8_t>> data(new std::vector<uint8_t>(cs));
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700538 MemoryRegion code(&(*data)[0], data->size());
539 assembler_->FinalizeInstructions(code);
540
541 if (*data == *res.code) {
542 Clean(&res);
543 } else {
544 if (DisassembleBinaries(*data, *res.code, test_name)) {
545 if (data->size() > res.code->size()) {
Andreas Gampe54e15de2014-08-06 15:31:06 -0700546 // Fail this test with a fancy colored warning being printed.
547 EXPECT_TRUE(false) << "Assembly code is not identical, but disassembly of machine code "
548 "is equal: this implies sub-optimal encoding! Our code size=" << data->size() <<
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700549 ", gcc size=" << res.code->size();
550 } else {
Andreas Gampe54e15de2014-08-06 15:31:06 -0700551 // Otherwise just print an info message and clean up.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700552 LOG(INFO) << "GCC chose a different encoding than ours, but the overall length is the "
553 "same.";
Andreas Gampe54e15de2014-08-06 15:31:06 -0700554 Clean(&res);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700555 }
556 } else {
557 // This will output the assembly.
Nicolas Geoffray102cbed2014-10-15 18:31:05 +0100558 EXPECT_EQ(*res.code, *data) << "Outputs (and disassembly) not identical.";
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700559 }
560 }
561 }
562
563 // Structure to store intermediates and results.
564 struct NativeAssemblerResult {
565 bool ok;
566 std::string error_msg;
567 std::string base_name;
Ian Rogers700a4022014-05-19 16:49:03 -0700568 std::unique_ptr<std::vector<uint8_t>> code;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700569 uintptr_t length;
570 };
571
572 // Compile the assembly file from_file to a binary file to_file. Returns true on success.
573 bool Assemble(const char* from_file, const char* to_file, std::string* error_msg) {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800574 bool have_assembler = FileExists(FindTool(GetAssemblerCmdName()));
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700575 EXPECT_TRUE(have_assembler) << "Cannot find assembler:" << GetAssemblerCommand();
576 if (!have_assembler) {
577 return false;
578 }
579
580 std::vector<std::string> args;
581
Roland Levillain1a28fc42014-11-13 18:03:06 +0000582 // Encaspulate the whole command line in a single string passed to
583 // the shell, so that GetAssemblerCommand() may contain arguments
584 // in addition to the program name.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700585 args.push_back(GetAssemblerCommand());
586 args.push_back("-o");
587 args.push_back(to_file);
588 args.push_back(from_file);
Roland Levillain1a28fc42014-11-13 18:03:06 +0000589 std::string cmd = Join(args, ' ');
590
591 args.clear();
592 args.push_back("/bin/sh");
593 args.push_back("-c");
594 args.push_back(cmd);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700595
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800596 bool success = Exec(args, error_msg);
597 if (!success) {
598 LOG(INFO) << "Assembler command line:";
599 for (std::string arg : args) {
600 LOG(INFO) << arg;
601 }
602 }
603 return success;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700604 }
605
606 // Runs objdump -h on the binary file and extracts the first line with .text.
607 // Returns "" on failure.
608 std::string Objdump(std::string file) {
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800609 bool have_objdump = FileExists(FindTool(GetObjdumpCmdName()));
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700610 EXPECT_TRUE(have_objdump) << "Cannot find objdump: " << GetObjdumpCommand();
611 if (!have_objdump) {
612 return "";
613 }
614
615 std::string error_msg;
616 std::vector<std::string> args;
617
Roland Levillain1a28fc42014-11-13 18:03:06 +0000618 // Encaspulate the whole command line in a single string passed to
619 // the shell, so that GetObjdumpCommand() may contain arguments
620 // in addition to the program name.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700621 args.push_back(GetObjdumpCommand());
622 args.push_back(file);
623 args.push_back(">");
624 args.push_back(file+".dump");
625 std::string cmd = Join(args, ' ');
626
627 args.clear();
628 args.push_back("/bin/sh");
629 args.push_back("-c");
630 args.push_back(cmd);
631
632 if (!Exec(args, &error_msg)) {
633 EXPECT_TRUE(false) << error_msg;
634 }
635
636 std::ifstream dump(file+".dump");
637
638 std::string line;
639 bool found = false;
640 while (std::getline(dump, line)) {
641 if (line.find(".text") != line.npos) {
642 found = true;
643 break;
644 }
645 }
646
647 dump.close();
648
649 if (found) {
650 return line;
651 } else {
652 return "";
653 }
654 }
655
656 // Disassemble both binaries and compare the text.
657 bool DisassembleBinaries(std::vector<uint8_t>& data, std::vector<uint8_t>& as,
658 std::string test_name) {
659 std::string disassembler = GetDisassembleCommand();
660 if (disassembler.length() == 0) {
661 LOG(WARNING) << "No dissassembler command.";
662 return false;
663 }
664
665 std::string data_name = WriteToFile(data, test_name + ".ass");
666 std::string error_msg;
667 if (!DisassembleBinary(data_name, &error_msg)) {
668 LOG(INFO) << "Error disassembling: " << error_msg;
669 std::remove(data_name.c_str());
670 return false;
671 }
672
673 std::string as_name = WriteToFile(as, test_name + ".gcc");
674 if (!DisassembleBinary(as_name, &error_msg)) {
675 LOG(INFO) << "Error disassembling: " << error_msg;
676 std::remove(data_name.c_str());
677 std::remove((data_name + ".dis").c_str());
678 std::remove(as_name.c_str());
679 return false;
680 }
681
682 bool result = CompareFiles(data_name + ".dis", as_name + ".dis");
683
Andreas Gampe851df202014-11-12 14:05:46 -0800684 // If you want to take a look at the differences between the ART assembler and GCC, comment
685 // out the removal code.
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800686// std::remove(data_name.c_str());
687// std::remove(as_name.c_str());
688// std::remove((data_name + ".dis").c_str());
689// std::remove((as_name + ".dis").c_str());
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700690
691 return result;
692 }
693
694 bool DisassembleBinary(std::string file, std::string* error_msg) {
695 std::vector<std::string> args;
696
Roland Levillain1a28fc42014-11-13 18:03:06 +0000697 // Encaspulate the whole command line in a single string passed to
698 // the shell, so that GetDisassembleCommand() may contain arguments
699 // in addition to the program name.
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700700 args.push_back(GetDisassembleCommand());
701 args.push_back(file);
702 args.push_back("| sed -n \'/<.data>/,$p\' | sed -e \'s/.*://\'");
703 args.push_back(">");
704 args.push_back(file+".dis");
705 std::string cmd = Join(args, ' ');
706
707 args.clear();
708 args.push_back("/bin/sh");
709 args.push_back("-c");
710 args.push_back(cmd);
711
712 return Exec(args, error_msg);
713 }
714
715 std::string WriteToFile(std::vector<uint8_t>& buffer, std::string test_name) {
716 std::string file_name = GetTmpnam() + std::string("---") + test_name;
717 const char* data = reinterpret_cast<char*>(buffer.data());
718 std::ofstream s_out(file_name + ".o");
719 s_out.write(data, buffer.size());
720 s_out.close();
721 return file_name + ".o";
722 }
723
724 bool CompareFiles(std::string f1, std::string f2) {
725 std::ifstream f1_in(f1);
726 std::ifstream f2_in(f2);
727
728 bool result = std::equal(std::istreambuf_iterator<char>(f1_in),
729 std::istreambuf_iterator<char>(),
730 std::istreambuf_iterator<char>(f2_in));
731
732 f1_in.close();
733 f2_in.close();
734
735 return result;
736 }
737
738 // Compile the given assembly code and extract the binary, if possible. Put result into res.
739 bool Compile(std::string assembly_code, NativeAssemblerResult* res, std::string test_name) {
740 res->ok = false;
741 res->code.reset(nullptr);
742
743 res->base_name = GetTmpnam() + std::string("---") + test_name;
744
745 // TODO: Lots of error checking.
746
747 std::ofstream s_out(res->base_name + ".S");
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800748 const char* header = GetAssemblyHeader();
749 if (header != nullptr) {
750 s_out << header;
751 }
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700752 s_out << assembly_code;
753 s_out.close();
754
755 if (!Assemble((res->base_name + ".S").c_str(), (res->base_name + ".o").c_str(),
756 &res->error_msg)) {
757 res->error_msg = "Could not compile.";
758 return false;
759 }
760
761 std::string odump = Objdump(res->base_name + ".o");
762 if (odump.length() == 0) {
763 res->error_msg = "Objdump failed.";
764 return false;
765 }
766
767 std::istringstream iss(odump);
768 std::istream_iterator<std::string> start(iss);
769 std::istream_iterator<std::string> end;
770 std::vector<std::string> tokens(start, end);
771
772 if (tokens.size() < OBJDUMP_SECTION_LINE_MIN_TOKENS) {
773 res->error_msg = "Objdump output not recognized: too few tokens.";
774 return false;
775 }
776
777 if (tokens[1] != ".text") {
778 res->error_msg = "Objdump output not recognized: .text not second token.";
779 return false;
780 }
781
782 std::string lengthToken = "0x" + tokens[2];
783 std::istringstream(lengthToken) >> std::hex >> res->length;
784
785 std::string offsetToken = "0x" + tokens[5];
786 uintptr_t offset;
787 std::istringstream(offsetToken) >> std::hex >> offset;
788
789 std::ifstream obj(res->base_name + ".o");
790 obj.seekg(offset);
791 res->code.reset(new std::vector<uint8_t>(res->length));
792 obj.read(reinterpret_cast<char*>(&(*res->code)[0]), res->length);
793 obj.close();
794
795 res->ok = true;
796 return true;
797 }
798
799 // Remove temporary files.
800 void Clean(const NativeAssemblerResult* res) {
801 std::remove((res->base_name + ".S").c_str());
802 std::remove((res->base_name + ".o").c_str());
803 std::remove((res->base_name + ".o.dump").c_str());
804 }
805
806 // Check whether file exists. Is used for commands, so strips off any parameters: anything after
807 // the first space. We skip to the last slash for this, so it should work with directories with
808 // spaces.
809 static bool FileExists(std::string file) {
810 if (file.length() == 0) {
811 return false;
812 }
813
814 // Need to strip any options.
815 size_t last_slash = file.find_last_of('/');
816 if (last_slash == std::string::npos) {
817 // No slash, start looking at the start.
818 last_slash = 0;
819 }
820 size_t space_index = file.find(' ', last_slash);
821
822 if (space_index == std::string::npos) {
823 std::ifstream infile(file.c_str());
824 return infile.good();
825 } else {
826 std::string copy = file.substr(0, space_index - 1);
827
828 struct stat buf;
829 return stat(copy.c_str(), &buf) == 0;
830 }
831 }
832
833 static std::string GetGCCRootPath() {
834 return "prebuilts/gcc/linux-x86";
835 }
836
837 static std::string GetRootPath() {
838 // 1) Check ANDROID_BUILD_TOP
839 char* build_top = getenv("ANDROID_BUILD_TOP");
840 if (build_top != nullptr) {
841 return std::string(build_top) + "/";
842 }
843
844 // 2) Do cwd
845 char temp[1024];
846 return getcwd(temp, 1024) ? std::string(temp) + "/" : std::string("");
847 }
848
849 std::string FindTool(std::string tool_name) {
850 // Find the current tool. Wild-card pattern is "arch-string*tool-name".
851 std::string gcc_path = GetRootPath() + GetGCCRootPath();
852 std::vector<std::string> args;
853 args.push_back("find");
854 args.push_back(gcc_path);
855 args.push_back("-name");
856 args.push_back(GetArchitectureString() + "*" + tool_name);
857 args.push_back("|");
858 args.push_back("sort");
859 args.push_back("|");
860 args.push_back("tail");
861 args.push_back("-n");
862 args.push_back("1");
863 std::string tmp_file = GetTmpnam();
864 args.push_back(">");
865 args.push_back(tmp_file);
866 std::string sh_args = Join(args, ' ');
867
868 args.clear();
869 args.push_back("/bin/sh");
870 args.push_back("-c");
871 args.push_back(sh_args);
872
873 std::string error_msg;
874 if (!Exec(args, &error_msg)) {
875 EXPECT_TRUE(false) << error_msg;
876 return "";
877 }
878
879 std::ifstream in(tmp_file.c_str());
880 std::string line;
881 if (!std::getline(in, line)) {
882 in.close();
883 std::remove(tmp_file.c_str());
884 return "";
885 }
886 in.close();
887 std::remove(tmp_file.c_str());
888 return line;
889 }
890
891 // Use a consistent tmpnam, so store it.
892 std::string GetTmpnam() {
893 if (tmpnam_.length() == 0) {
Andreas Gampeb40c6a72014-05-02 14:25:12 -0700894 ScratchFile tmp;
895 tmpnam_ = tmp.GetFilename() + "asm";
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700896 }
897 return tmpnam_;
898 }
899
Andreas Gampe849cc5e2014-11-18 13:46:46 -0800900 static constexpr size_t kWarnManyCombinationsThreshold = 500;
Andreas Gampe851df202014-11-12 14:05:46 -0800901 static constexpr size_t OBJDUMP_SECTION_LINE_MIN_TOKENS = 6;
902
Ian Rogers700a4022014-05-19 16:49:03 -0700903 std::unique_ptr<Ass> assembler_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700904
905 std::string resolved_assembler_cmd_;
906 std::string resolved_objdump_cmd_;
907 std::string resolved_disassemble_cmd_;
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700908
Andreas Gampe7747c8d2014-08-06 14:53:03 -0700909 std::string android_data_;
910
Andreas Gampe851df202014-11-12 14:05:46 -0800911 DISALLOW_COPY_AND_ASSIGN(AssemblerTest);
Andreas Gampe5a4fa822014-03-31 16:50:12 -0700912};
913
914} // namespace art
915
916#endif // ART_COMPILER_UTILS_ASSEMBLER_TEST_H_