blob: 81566a1d3e2d75ce0a49e84044a0db7733f72b6b [file]
/*
* 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_