| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #ifndef ART_COMPILER_OPTIMIZING_CODEGEN_TEST_UTILS_H_ |
| #define ART_COMPILER_OPTIMIZING_CODEGEN_TEST_UTILS_H_ |
| |
| #include "arch/arm/registers_arm.h" |
| #include "arch/instruction_set.h" |
| #include "arch/x86/registers_x86.h" |
| #include "base/macros.h" |
| #include "code_simulator.h" |
| #include "common_compiler_test.h" |
| #include "graph_checker.h" |
| #include "prepare_for_register_allocation.h" |
| #include "ssa_liveness_analysis.h" |
| |
| #ifdef ART_ENABLE_CODEGEN_arm |
| #include "code_generator_arm_vixl.h" |
| #endif |
| |
| #ifdef ART_ENABLE_CODEGEN_arm64 |
| #include "code_generator_arm64.h" |
| #endif |
| |
| #ifdef ART_ENABLE_CODEGEN_riscv64 |
| #include "code_generator_riscv64.h" |
| #endif |
| |
| #ifdef ART_ENABLE_CODEGEN_x86 |
| #include "code_generator_x86.h" |
| #endif |
| |
| #ifdef ART_ENABLE_CODEGEN_x86_64 |
| #include "code_generator_x86_64.h" |
| #endif |
| |
| namespace art HIDDEN { |
| |
| using CreateCodegenFn = CodeGenerator* (*)(HGraph*, const CompilerOptions&); |
| |
| class CodegenTargetConfig { |
| public: |
| CodegenTargetConfig(InstructionSet isa, CreateCodegenFn create_codegen) |
| : isa_(isa), create_codegen_(create_codegen) { |
| } |
| InstructionSet GetInstructionSet() const { return isa_; } |
| CodeGenerator* CreateCodeGenerator(HGraph* graph, const CompilerOptions& compiler_options) const { |
| return create_codegen_(graph, compiler_options); |
| } |
| |
| private: |
| InstructionSet isa_; |
| CreateCodegenFn create_codegen_; |
| }; |
| |
| #ifdef ART_ENABLE_CODEGEN_arm |
| // Special ARM code generator for codegen testing in a limited code |
| // generation environment (i.e. with no runtime support). |
| // |
| // Note: If we want to exercise certains HIR constructions |
| // (e.g. reference field load in Baker read barrier configuration) in |
| // codegen tests in the future, we should also: |
| // - save the Thread Register (R9) and possibly the Marking Register |
| // (R8) before entering the generated function (both registers are |
| // callee-save in AAPCS); |
| // - set these registers to meaningful values before or upon entering |
| // the generated function (so that generated code using them is |
| // correct); |
| // - restore their original values before leaving the generated |
| // function. |
| |
| // Provide our own codegen, that ensures the C calling conventions |
| // are preserved. Currently, ART and C do not match as R4 is caller-save |
| // in ART, and callee-save in C. Alternatively, we could use or write |
| // the stub that saves and restores all registers, but it is easier |
| // to just overwrite the code generator. |
| class TestCodeGeneratorARMVIXL : public arm::CodeGeneratorARMVIXL { |
| public: |
| TestCodeGeneratorARMVIXL(HGraph* graph, const CompilerOptions& compiler_options) |
| : arm::CodeGeneratorARMVIXL(graph, compiler_options) { |
| AddAllocatedCoreRegister(arm::R6); |
| AddAllocatedCoreRegister(arm::R7); |
| blocked_registers_.AddCoreRegisterSet((1u << arm::R4) | (1u << arm::R6) | (1u << arm::R7)); |
| } |
| |
| void MaybeGenerateMarkingRegisterCheck([[maybe_unused]] int code, |
| [[maybe_unused]] Location temp_loc) override { |
| // When turned on, the marking register checks in |
| // CodeGeneratorARMVIXL::MaybeGenerateMarkingRegisterCheck expects the |
| // Thread Register and the Marking Register to be set to |
| // meaningful values. This is not the case in codegen testing, so |
| // just disable them entirely here (by doing nothing in this |
| // method). |
| } |
| }; |
| #endif |
| |
| #ifdef ART_ENABLE_CODEGEN_arm64 |
| // Special ARM64 code generator for codegen testing in a limited code |
| // generation environment (i.e. with no runtime support). |
| // |
| // Note: If we want to exercise certains HIR constructions |
| // (e.g. reference field load in Baker read barrier configuration) in |
| // codegen tests in the future, we should also: |
| // - save the Thread Register (X19) and possibly the Marking Register |
| // (X20) before entering the generated function (both registers are |
| // callee-save in AAPCS64); |
| // - set these registers to meaningful values before or upon entering |
| // the generated function (so that generated code using them is |
| // correct); |
| // - restore their original values before leaving the generated |
| // function. |
| class TestCodeGeneratorARM64 : public arm64::CodeGeneratorARM64 { |
| public: |
| TestCodeGeneratorARM64(HGraph* graph, const CompilerOptions& compiler_options) |
| : arm64::CodeGeneratorARM64(graph, compiler_options) {} |
| |
| void MaybeGenerateMarkingRegisterCheck([[maybe_unused]] int codem, |
| [[maybe_unused]] Location temp_loc) override { |
| // When turned on, the marking register checks in |
| // CodeGeneratorARM64::MaybeGenerateMarkingRegisterCheck expect the |
| // Thread Register and the Marking Register to be set to |
| // meaningful values. This is not the case in codegen testing, so |
| // just disable them entirely here (by doing nothing in this |
| // method). |
| } |
| }; |
| #endif |
| |
| #ifdef ART_ENABLE_CODEGEN_x86 |
| class TestCodeGeneratorX86 : public x86::CodeGeneratorX86 { |
| public: |
| TestCodeGeneratorX86(HGraph* graph, const CompilerOptions& compiler_options) |
| : x86::CodeGeneratorX86(graph, compiler_options) { |
| // Save edi, we need it for getting enough registers for long multiplication. |
| AddAllocatedCoreRegister(x86::EDI); |
| // Block EBX because it's a callee-save register in C, but caller-save for ART. |
| // Make EDI available. |
| RegisterSet blocked_registers = RegisterSet::Empty(); |
| blocked_registers.AddCoreRegisterSet( |
| (blocked_registers_.GetCoreRegisterSet() | (1u << x86::EBX)) & ~(1u << x86::EDI)); |
| blocked_registers.AddFpuRegisterSet(blocked_registers_.GetFpuRegisterSet()); |
| blocked_registers_ = blocked_registers; |
| } |
| }; |
| #endif |
| |
| // Check that the current runtime ISA matches the target ISA. |
| static bool DoesHardwareSupportISA(InstructionSet target_isa) { |
| return (target_isa == kRuntimeISA) |
| // Handle the special case of ARM, with two instructions sets (ARM32 and Thumb-2). |
| || (kRuntimeISA == InstructionSet::kArm && target_isa == InstructionSet::kThumb2); |
| } |
| |
| // Check that the current runtime ISA matches the target ISA and the ISA features requested are |
| // available on the hardware. |
| static bool CanExecuteOnHardware(const CodeGenerator& codegen) { |
| const InstructionSetFeatures* isa_features = |
| codegen.GetCompilerOptions().GetInstructionSetFeatures(); |
| return DoesHardwareSupportISA(codegen.GetInstructionSet()) |
| && InstructionSetFeatures::FromHwcap()->HasAtLeast(isa_features); |
| } |
| |
| static constexpr size_t kDefaultStackSize = 1 * MB; |
| |
| static bool CanExecuteISA(InstructionSet target_isa) { |
| return DoesHardwareSupportISA(target_isa) || BasicCodeSimulator::CanSimulate(target_isa); |
| } |
| |
| static bool CanExecute(const CodeGenerator& codegen) { |
| return CanExecuteOnHardware(codegen) || |
| BasicCodeSimulator::CanSimulate(codegen.GetInstructionSet()); |
| } |
| |
| template <typename Expected> |
| inline static Expected SimulatorExecute(BasicCodeSimulator* simulator, Expected (*f)()); |
| |
| template <> |
| inline bool SimulatorExecute<bool>(BasicCodeSimulator* simulator, bool (*f)()) { |
| simulator->RunFrom(reinterpret_cast<intptr_t>(f)); |
| return simulator->GetCReturnBool(); |
| } |
| |
| template <> |
| inline int32_t SimulatorExecute<int32_t>(BasicCodeSimulator* simulator, int32_t (*f)()) { |
| simulator->RunFrom(reinterpret_cast<intptr_t>(f)); |
| return simulator->GetCReturnInt32(); |
| } |
| |
| template <> |
| inline int64_t SimulatorExecute<int64_t>(BasicCodeSimulator* simulator, int64_t (*f)()) { |
| simulator->RunFrom(reinterpret_cast<intptr_t>(f)); |
| return simulator->GetCReturnInt64(); |
| } |
| |
| template <typename Expected> |
| static void VerifyGeneratedCode(const CodeGenerator& codegen, |
| Expected (*f)(), |
| bool has_result, |
| Expected expected) { |
| ASSERT_TRUE(CanExecute(codegen)) << "Target isa is not executable."; |
| |
| // Verify on simulator. |
| InstructionSet target_isa = codegen.GetInstructionSet(); |
| if (BasicCodeSimulator::CanSimulate(target_isa)) { |
| // Use basic simulator: for the gtests we don't have runtime started, so won't have entrypoints |
| // initialized. |
| std::unique_ptr<BasicCodeSimulator> simulator( |
| CreateBasicCodeSimulator(codegen.GetInstructionSet(), kDefaultStackSize)); |
| Expected result = SimulatorExecute<Expected>(simulator.get(), f); |
| |
| if (has_result) { |
| ASSERT_EQ(expected, result); |
| } |
| } |
| |
| // Verify on hardware. |
| if (CanExecuteOnHardware(codegen)) { |
| Expected result = f(); |
| if (has_result) { |
| ASSERT_EQ(expected, result); |
| } |
| } |
| } |
| |
| template <typename Expected> |
| static void Run(const CodeGenerator& codegen, |
| bool has_result, |
| Expected expected) { |
| InstructionSet target_isa = codegen.GetInstructionSet(); |
| |
| struct CodeHolder : CommonCompilerTestImpl { |
| protected: |
| ClassLinker* GetClassLinker() override { return nullptr; } |
| Runtime* GetRuntime() override { return nullptr; } |
| }; |
| CodeHolder code_holder; |
| const void* method_code = |
| code_holder.MakeExecutable(codegen.GetCode(), ArrayRef<const uint8_t>(), target_isa); |
| |
| using fptr = Expected (*)(); |
| fptr f = reinterpret_cast<fptr>(reinterpret_cast<uintptr_t>(method_code)); |
| VerifyGeneratedCode(codegen, f, has_result, expected); |
| } |
| |
| static void ValidateGraph(HGraph* graph) { |
| GraphChecker graph_checker(graph); |
| graph_checker.Run(); |
| if (!graph_checker.IsValid()) { |
| for (const std::string& error : graph_checker.GetErrors()) { |
| std::cout << error << std::endl; |
| } |
| } |
| ASSERT_TRUE(graph_checker.IsValid()); |
| } |
| |
| inline void GenerateCode(CodeGenerator* codegen, |
| HGraph* graph, |
| const std::function<void(HGraph*)>& hook_before_codegen) { |
| { |
| ScopedArenaAllocator local_allocator(graph->GetArenaStack()); |
| SsaLivenessAnalysis liveness(graph, codegen, &local_allocator); |
| PrepareForRegisterAllocation(graph, codegen->GetCompilerOptions()).Run(); |
| liveness.Analyze(); |
| std::unique_ptr<RegisterAllocator> register_allocator = |
| RegisterAllocator::Create(&local_allocator, codegen, liveness); |
| register_allocator->AllocateRegisters(); |
| } |
| hook_before_codegen(graph); |
| codegen->Compile(); |
| } |
| |
| template <typename Expected> |
| static void RunCodeNoCheck(CodeGenerator* codegen, |
| HGraph* graph, |
| const std::function<void(HGraph*)>& hook_before_codegen, |
| bool has_result, |
| Expected expected) { |
| GenerateCode(codegen, graph, hook_before_codegen); |
| Run(*codegen, has_result, expected); |
| } |
| |
| template <typename Expected> |
| static void RunCode(CodeGenerator* codegen, |
| HGraph* graph, |
| std::function<void(HGraph*)> hook_before_codegen, |
| bool has_result, |
| Expected expected) { |
| ValidateGraph(graph); |
| RunCodeNoCheck(codegen, graph, hook_before_codegen, has_result, expected); |
| } |
| |
| template <typename Expected> |
| static void RunCode(CodegenTargetConfig target_config, |
| const CompilerOptions& compiler_options, |
| HGraph* graph, |
| std::function<void(HGraph*)> hook_before_codegen, |
| bool has_result, |
| Expected expected) { |
| std::unique_ptr<CodeGenerator> codegen(target_config.CreateCodeGenerator(graph, |
| compiler_options)); |
| RunCode(codegen.get(), graph, hook_before_codegen, has_result, expected); |
| } |
| |
| #ifdef ART_ENABLE_CODEGEN_arm |
| inline CodeGenerator* create_codegen_arm_vixl32(HGraph* graph, const CompilerOptions& compiler_options) { |
| return new (graph->GetAllocator()) TestCodeGeneratorARMVIXL(graph, compiler_options); |
| } |
| #endif |
| |
| #ifdef ART_ENABLE_CODEGEN_arm64 |
| inline CodeGenerator* create_codegen_arm64(HGraph* graph, const CompilerOptions& compiler_options) { |
| return new (graph->GetAllocator()) TestCodeGeneratorARM64(graph, compiler_options); |
| } |
| #endif |
| |
| #ifdef ART_ENABLE_CODEGEN_riscv64 |
| inline CodeGenerator* create_codegen_riscv64(HGraph*, const CompilerOptions&) { return nullptr; } |
| #endif |
| |
| #ifdef ART_ENABLE_CODEGEN_x86 |
| inline CodeGenerator* create_codegen_x86(HGraph* graph, const CompilerOptions& compiler_options) { |
| return new (graph->GetAllocator()) TestCodeGeneratorX86(graph, compiler_options); |
| } |
| #endif |
| |
| #ifdef ART_ENABLE_CODEGEN_x86_64 |
| inline CodeGenerator* create_codegen_x86_64(HGraph* graph, const CompilerOptions& compiler_options) { |
| return new (graph->GetAllocator()) x86_64::CodeGeneratorX86_64(graph, compiler_options); |
| } |
| #endif |
| |
| } // namespace art |
| |
| #endif // ART_COMPILER_OPTIMIZING_CODEGEN_TEST_UTILS_H_ |