blob: 2c13dd514a74484fafc1ac9e32d9b334e37101bc [file] [log] [blame]
//===-- Target.cpp ----------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "../Target.h"
#include "AArch64.h"
#include "AArch64RegisterInfo.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#if defined(__aarch64__) && defined(__linux__)
#include <sys/prctl.h> // For PR_PAC_* constants
#ifndef PR_PAC_APIAKEY
#define PR_PAC_APIAKEY (1UL << 0)
#endif
#ifndef PR_PAC_APIBKEY
#define PR_PAC_APIBKEY (1UL << 1)
#endif
#ifndef PR_PAC_APDAKEY
#define PR_PAC_APDAKEY (1UL << 2)
#endif
#ifndef PR_PAC_APDBKEY
#define PR_PAC_APDBKEY (1UL << 3)
#endif
#endif
#define GET_AVAILABLE_OPCODE_CHECKER
#include "AArch64GenInstrInfo.inc"
namespace llvm {
namespace exegesis {
static unsigned getLoadImmediateOpcode(unsigned RegBitWidth) {
switch (RegBitWidth) {
case 32:
return AArch64::MOVi32imm;
case 64:
return AArch64::MOVi64imm;
}
llvm_unreachable("Invalid Value Width");
}
// Generates instruction to load an immediate value into a register.
static MCInst loadImmediate(MCRegister Reg, unsigned RegBitWidth,
const APInt &Value) {
assert(Value.getBitWidth() <= RegBitWidth &&
"Value must fit in the Register");
return MCInstBuilder(getLoadImmediateOpcode(RegBitWidth))
.addReg(Reg)
.addImm(Value.getZExtValue());
}
static MCInst loadZPRImmediate(MCRegister Reg, unsigned RegBitWidth,
const APInt &Value) {
assert(Value.getZExtValue() < (1 << 7) &&
"Value must be in the range of the immediate opcode");
return MCInstBuilder(AArch64::DUP_ZI_D)
.addReg(Reg)
.addImm(Value.getZExtValue())
.addImm(0);
}
static MCInst loadPPRImmediate(MCRegister Reg, unsigned RegBitWidth,
const APInt &Value) {
// For PPR, we typically use PTRUE instruction to set predicate registers
return MCInstBuilder(AArch64::PTRUE_B)
.addReg(Reg)
.addImm(31); // All lanes true for 16 bits
}
// Generates instructions to load an immediate value into an FPCR register.
static std::vector<MCInst>
loadFPCRImmediate(MCRegister Reg, unsigned RegBitWidth, const APInt &Value) {
MCRegister TempReg = AArch64::X8;
MCInst LoadImm = MCInstBuilder(AArch64::MOVi64imm).addReg(TempReg).addImm(0);
MCInst MoveToFPCR =
MCInstBuilder(AArch64::MSR).addImm(AArch64SysReg::FPCR).addReg(TempReg);
return {LoadImm, MoveToFPCR};
}
// Fetch base-instruction to load an FP immediate value into a register.
static unsigned getLoadFPImmediateOpcode(unsigned RegBitWidth) {
switch (RegBitWidth) {
case 16:
return AArch64::FMOVH0; // FMOVHi;
case 32:
return AArch64::FMOVS0; // FMOVSi;
case 64:
return AArch64::MOVID; // FMOVDi;
case 128:
return AArch64::MOVIv2d_ns;
}
llvm_unreachable("Invalid Value Width");
}
// Generates instruction to load an FP immediate value into a register.
static MCInst loadFPImmediate(MCRegister Reg, unsigned RegBitWidth,
const APInt &Value) {
assert(Value.getZExtValue() == 0 && "Expected initialisation value 0");
MCInst Instructions =
MCInstBuilder(getLoadFPImmediateOpcode(RegBitWidth)).addReg(Reg);
if (RegBitWidth >= 64)
Instructions.addOperand(MCOperand::createImm(Value.getZExtValue()));
return Instructions;
}
#include "AArch64GenExegesis.inc"
namespace {
// Use X19 as the loop counter register since it's a callee-saved register
// that's available for temporary use.
constexpr const MCPhysReg kDefaultLoopCounterReg = AArch64::X19;
class ExegesisAArch64Target : public ExegesisTarget {
public:
ExegesisAArch64Target()
: ExegesisTarget(AArch64CpuPfmCounters, AArch64_MC::isOpcodeAvailable) {}
Error randomizeTargetMCOperand(const Instruction &Instr, const Variable &Var,
MCOperand &AssignedValue,
const BitVector &ForbiddenRegs) const override;
private:
std::vector<MCInst> setRegTo(const MCSubtargetInfo &STI, MCRegister Reg,
const APInt &Value) const override {
if (AArch64::GPR32RegClass.contains(Reg))
return {loadImmediate(Reg, 32, Value)};
if (AArch64::GPR64RegClass.contains(Reg))
return {loadImmediate(Reg, 64, Value)};
if (AArch64::PPRRegClass.contains(Reg))
return {loadPPRImmediate(Reg, 16, Value)};
if (AArch64::FPR8RegClass.contains(Reg))
return {loadFPImmediate(Reg - AArch64::B0 + AArch64::D0, 64, Value)};
if (AArch64::FPR16RegClass.contains(Reg))
return {loadFPImmediate(Reg, 16, Value)};
if (AArch64::FPR32RegClass.contains(Reg))
return {loadFPImmediate(Reg, 32, Value)};
if (AArch64::FPR64RegClass.contains(Reg))
return {loadFPImmediate(Reg, 64, Value)};
if (AArch64::FPR128RegClass.contains(Reg))
return {loadFPImmediate(Reg, 128, Value)};
if (AArch64::ZPRRegClass.contains(Reg))
return {loadZPRImmediate(Reg, 128, Value)};
if (Reg == AArch64::FPCR)
return {loadFPCRImmediate(Reg, 32, Value)};
errs() << "setRegTo is not implemented, results will be unreliable\n";
return {};
}
MCRegister getDefaultLoopCounterRegister(const Triple &) const override {
return kDefaultLoopCounterReg;
}
void decrementLoopCounterAndJump(MachineBasicBlock &MBB,
MachineBasicBlock &TargetMBB,
const MCInstrInfo &MII,
MCRegister LoopRegister) const override {
// subs LoopRegister, LoopRegister, #1
BuildMI(&MBB, DebugLoc(), MII.get(AArch64::SUBSXri))
.addDef(LoopRegister)
.addUse(LoopRegister)
.addImm(1) // Subtract 1
.addImm(0); // No shift amount
// b.ne TargetMBB
BuildMI(&MBB, DebugLoc(), MII.get(AArch64::Bcc))
.addImm(AArch64CC::NE)
.addMBB(&TargetMBB);
}
// Registers that should not be selected for use in snippets.
const MCPhysReg UnavailableRegisters[1] = {kDefaultLoopCounterReg};
ArrayRef<MCPhysReg> getUnavailableRegisters() const override {
return UnavailableRegisters;
}
bool matchesArch(Triple::ArchType Arch) const override {
return Arch == Triple::aarch64 || Arch == Triple::aarch64_be;
}
void addTargetSpecificPasses(PassManagerBase &PM) const override {
// Function return is a pseudo-instruction that needs to be expanded
PM.add(createAArch64ExpandPseudoPass());
}
};
Error ExegesisAArch64Target::randomizeTargetMCOperand(
const Instruction &Instr, const Variable &Var, MCOperand &AssignedValue,
const BitVector &ForbiddenRegs) const {
const Operand &Op = Instr.getPrimaryOperand(Var);
const auto OperandType = Op.getExplicitOperandInfo().OperandType;
// NOTE: To resolve "Not all operands were initialized by snippet generator"
// Requires OperandType to be defined for such opcode's operands in AArch64
// tablegen files. And omit introduced OperandType(s).
// Hacky Fix: Defaulting all OPERAND_UNKNOWN to immediate value 0 works with a
// limitation that it introduces illegal instruction error for system
// instructions. System instructions will need to be omitted with OperandType
// or opcode specific values to avoid generating invalid encodings or
// unreliable benchmark results for these system-level instructions.
// Implement opcode-specific immediate value handling for system instrs:
// - MRS/MSR: Use valid system register encodings (e.g., NZCV, FPCR, FPSR)
// - MSRpstatesvcrImm1: Use valid PSTATE field encodings (e.g., SPSel,
// DAIFSet)
// - SYSLxt/SYSxt: Use valid system instruction encodings with proper
// CRn/CRm/op values
// - UDF: Use valid undefined instruction immediate ranges (0-65535)
switch (OperandType) {
// MSL (Masking Shift Left) imm operand for 32-bit splatted SIMD constants
// Correspond to AArch64InstructionSelector::tryAdvSIMDModImm321s()
case llvm::AArch64::OPERAND_SHIFT_MSL: {
// There are two valid encodings:
// - Type 7: imm at [15:8], [47:40], shift = 264 (0x108) → msl #8
// - Type 8: imm at [23:16], [55:48], shift = 272 (0x110) → msl #16
// Corresponds AArch64_AM::encodeAdvSIMDModImmType7()
// But, v2s_msl and v4s_msl instructions accept either form,
// Thus, Arbitrarily chosing 264 (msl #8) for simplicity.
AssignedValue = MCOperand::createImm(264);
return Error::success();
}
case llvm::AArch64::OPERAND_IMPLICIT_IMM_0:
AssignedValue = MCOperand::createImm(0);
return Error::success();
case MCOI::OperandType::OPERAND_PCREL:
AssignedValue = MCOperand::createImm(8);
return Error::success();
default:
break;
}
return make_error<Failure>(
Twine("Unimplemented operand type: MCOI::OperandType:")
.concat(Twine(static_cast<int>(OperandType))));
}
} // namespace
static ExegesisTarget *getTheExegesisAArch64Target() {
static ExegesisAArch64Target Target;
return &Target;
}
void InitializeAArch64ExegesisTarget() {
ExegesisTarget::registerTarget(getTheExegesisAArch64Target());
}
} // namespace exegesis
} // namespace llvm