This transition was missed off the switch, but is already supported (see the test for the expected behavior).
1222 lines
49 KiB
C++
1222 lines
49 KiB
C++
//===- MachineSMEABIPass.cpp ----------------------------------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This pass implements the SME ABI requirements for ZA state. This includes
|
|
// implementing the lazy (and agnostic) ZA state save schemes around calls.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This pass works by collecting instructions that require ZA to be in a
|
|
// specific state (e.g., "ACTIVE" or "SAVED") and inserting the necessary state
|
|
// transitions to ensure ZA is in the required state before instructions. State
|
|
// transitions represent actions such as setting up or restoring a lazy save.
|
|
// Certain points within a function may also have predefined states independent
|
|
// of any instructions, for example, a "shared_za" function is always entered
|
|
// and exited in the "ACTIVE" state.
|
|
//
|
|
// To handle ZA state across control flow, we make use of edge bundling. This
|
|
// assigns each block an "incoming" and "outgoing" edge bundle (representing
|
|
// incoming and outgoing edges). Initially, these are unique to each block;
|
|
// then, in the process of forming bundles, the outgoing bundle of a block is
|
|
// joined with the incoming bundle of all successors. The result is that each
|
|
// bundle can be assigned a single ZA state, which ensures the state required by
|
|
// all a blocks' successors is the same, and that each basic block will always
|
|
// be entered with the same ZA state. This eliminates the need for splitting
|
|
// edges to insert state transitions or "phi" nodes for ZA states.
|
|
//
|
|
// See below for a simple example of edge bundling.
|
|
//
|
|
// The following shows a conditionally executed basic block (BB1):
|
|
//
|
|
// if (cond)
|
|
// BB1
|
|
// BB2
|
|
//
|
|
// Initial Bundles Joined Bundles
|
|
//
|
|
// ┌──0──┐ ┌──0──┐
|
|
// │ BB0 │ │ BB0 │
|
|
// └──1──┘ └──1──┘
|
|
// ├───────┐ ├───────┐
|
|
// ▼ │ ▼ │
|
|
// ┌──2──┐ │ ─────► ┌──1──┐ │
|
|
// │ BB1 │ ▼ │ BB1 │ ▼
|
|
// └──3──┘ ┌──4──┐ └──1──┘ ┌──1──┐
|
|
// └───►4 BB2 │ └───►1 BB2 │
|
|
// └──5──┘ └──2──┘
|
|
//
|
|
// On the left are the initial per-block bundles, and on the right are the
|
|
// joined bundles (which are the result of the EdgeBundles analysis).
|
|
|
|
#include "AArch64InstrInfo.h"
|
|
#include "AArch64MachineFunctionInfo.h"
|
|
#include "AArch64Subtarget.h"
|
|
#include "MCTargetDesc/AArch64AddressingModes.h"
|
|
#include "llvm/ADT/BitmaskEnum.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/CodeGen/EdgeBundles.h"
|
|
#include "llvm/CodeGen/LivePhysRegs.h"
|
|
#include "llvm/CodeGen/MachineBasicBlock.h"
|
|
#include "llvm/CodeGen/MachineFunctionPass.h"
|
|
#include "llvm/CodeGen/MachineOptimizationRemarkEmitter.h"
|
|
#include "llvm/CodeGen/MachineRegisterInfo.h"
|
|
#include "llvm/CodeGen/TargetRegisterInfo.h"
|
|
|
|
using namespace llvm;
|
|
|
|
#define DEBUG_TYPE "aarch64-machine-sme-abi"
|
|
|
|
namespace {
|
|
|
|
// Note: For agnostic ZA, we assume the function is always entered/exited in the
|
|
// "ACTIVE" state -- this _may_ not be the case (since OFF is also a
|
|
// possibility, but for the purpose of placing ZA saves/restores, that does not
|
|
// matter).
|
|
enum ZAState : uint8_t {
|
|
// Any/unknown state (not valid)
|
|
ANY = 0,
|
|
|
|
// ZA is in use and active (i.e. within the accumulator)
|
|
ACTIVE,
|
|
|
|
// ZA is active, but ZT0 has been saved.
|
|
// This handles the edge case of sharedZA && !sharesZT0.
|
|
ACTIVE_ZT0_SAVED,
|
|
|
|
// A ZA save has been set up or committed (i.e. ZA is dormant or off)
|
|
// If the function uses ZT0 it must also be saved.
|
|
LOCAL_SAVED,
|
|
|
|
// ZA has been committed to the lazy save buffer of the current function.
|
|
// If the function uses ZT0 it must also be saved.
|
|
// ZA is off.
|
|
LOCAL_COMMITTED,
|
|
|
|
// The ZA/ZT0 state on entry to the function.
|
|
ENTRY,
|
|
|
|
// ZA is off.
|
|
OFF,
|
|
|
|
// The number of ZA states (not a valid state)
|
|
NUM_ZA_STATE
|
|
};
|
|
|
|
/// A bitmask enum to record live physical registers that the "emit*" routines
|
|
/// may need to preserve. Note: This only tracks registers we may clobber.
|
|
enum LiveRegs : uint8_t {
|
|
None = 0,
|
|
NZCV = 1 << 0,
|
|
W0 = 1 << 1,
|
|
W0_HI = 1 << 2,
|
|
X0 = W0 | W0_HI,
|
|
LLVM_MARK_AS_BITMASK_ENUM(/* LargestValue = */ W0_HI)
|
|
};
|
|
|
|
/// Holds the virtual registers live physical registers have been saved to.
|
|
struct PhysRegSave {
|
|
LiveRegs PhysLiveRegs;
|
|
Register StatusFlags = AArch64::NoRegister;
|
|
Register X0Save = AArch64::NoRegister;
|
|
};
|
|
|
|
/// Contains the needed ZA state (and live registers) at an instruction. That is
|
|
/// the state ZA must be in _before_ "InsertPt".
|
|
struct InstInfo {
|
|
ZAState NeededState{ZAState::ANY};
|
|
MachineBasicBlock::iterator InsertPt;
|
|
LiveRegs PhysLiveRegs = LiveRegs::None;
|
|
};
|
|
|
|
/// Contains the needed ZA state for each instruction in a block. Instructions
|
|
/// that do not require a ZA state are not recorded.
|
|
struct BlockInfo {
|
|
SmallVector<InstInfo> Insts;
|
|
ZAState FixedEntryState{ZAState::ANY};
|
|
ZAState DesiredIncomingState{ZAState::ANY};
|
|
ZAState DesiredOutgoingState{ZAState::ANY};
|
|
LiveRegs PhysLiveRegsAtEntry = LiveRegs::None;
|
|
LiveRegs PhysLiveRegsAtExit = LiveRegs::None;
|
|
};
|
|
|
|
/// Contains the needed ZA state information for all blocks within a function.
|
|
struct FunctionInfo {
|
|
SmallVector<BlockInfo> Blocks;
|
|
std::optional<MachineBasicBlock::iterator> AfterSMEProloguePt;
|
|
LiveRegs PhysLiveRegsAfterSMEPrologue = LiveRegs::None;
|
|
};
|
|
|
|
/// State/helpers that is only needed when emitting code to handle
|
|
/// saving/restoring ZA.
|
|
class EmitContext {
|
|
public:
|
|
EmitContext() = default;
|
|
|
|
/// Get or create a TPIDR2 block in \p MF.
|
|
int getTPIDR2Block(MachineFunction &MF) {
|
|
if (TPIDR2BlockFI)
|
|
return *TPIDR2BlockFI;
|
|
MachineFrameInfo &MFI = MF.getFrameInfo();
|
|
TPIDR2BlockFI = MFI.CreateStackObject(16, Align(16), false);
|
|
return *TPIDR2BlockFI;
|
|
}
|
|
|
|
/// Get or create agnostic ZA buffer pointer in \p MF.
|
|
Register getAgnosticZABufferPtr(MachineFunction &MF) {
|
|
if (AgnosticZABufferPtr != AArch64::NoRegister)
|
|
return AgnosticZABufferPtr;
|
|
Register BufferPtr =
|
|
MF.getInfo<AArch64FunctionInfo>()->getEarlyAllocSMESaveBuffer();
|
|
AgnosticZABufferPtr =
|
|
BufferPtr != AArch64::NoRegister
|
|
? BufferPtr
|
|
: MF.getRegInfo().createVirtualRegister(&AArch64::GPR64RegClass);
|
|
return AgnosticZABufferPtr;
|
|
}
|
|
|
|
int getZT0SaveSlot(MachineFunction &MF) {
|
|
if (ZT0SaveFI)
|
|
return *ZT0SaveFI;
|
|
MachineFrameInfo &MFI = MF.getFrameInfo();
|
|
ZT0SaveFI = MFI.CreateSpillStackObject(64, Align(16));
|
|
return *ZT0SaveFI;
|
|
}
|
|
|
|
/// Returns true if the function must allocate a ZA save buffer on entry. This
|
|
/// will be the case if, at any point in the function, a ZA save was emitted.
|
|
bool needsSaveBuffer() const {
|
|
assert(!(TPIDR2BlockFI && AgnosticZABufferPtr) &&
|
|
"Cannot have both a TPIDR2 block and agnostic ZA buffer");
|
|
return TPIDR2BlockFI || AgnosticZABufferPtr != AArch64::NoRegister;
|
|
}
|
|
|
|
private:
|
|
std::optional<int> ZT0SaveFI;
|
|
std::optional<int> TPIDR2BlockFI;
|
|
Register AgnosticZABufferPtr = AArch64::NoRegister;
|
|
};
|
|
|
|
StringRef getZAStateString(ZAState State) {
|
|
#define MAKE_CASE(V) \
|
|
case V: \
|
|
return #V;
|
|
switch (State) {
|
|
MAKE_CASE(ZAState::ANY)
|
|
MAKE_CASE(ZAState::ACTIVE)
|
|
MAKE_CASE(ZAState::ACTIVE_ZT0_SAVED)
|
|
MAKE_CASE(ZAState::LOCAL_SAVED)
|
|
MAKE_CASE(ZAState::LOCAL_COMMITTED)
|
|
MAKE_CASE(ZAState::ENTRY)
|
|
MAKE_CASE(ZAState::OFF)
|
|
default:
|
|
llvm_unreachable("Unexpected ZAState");
|
|
}
|
|
#undef MAKE_CASE
|
|
}
|
|
|
|
static bool isZAorZTRegOp(const TargetRegisterInfo &TRI,
|
|
const MachineOperand &MO) {
|
|
if (!MO.isReg() || !MO.getReg().isPhysical())
|
|
return false;
|
|
return any_of(TRI.subregs_inclusive(MO.getReg()), [](const MCPhysReg &SR) {
|
|
return AArch64::MPR128RegClass.contains(SR) ||
|
|
AArch64::ZTRRegClass.contains(SR);
|
|
});
|
|
}
|
|
|
|
/// Returns the required ZA state needed before \p MI and an iterator pointing
|
|
/// to where any code required to change the ZA state should be inserted.
|
|
static std::pair<ZAState, MachineBasicBlock::iterator>
|
|
getInstNeededZAState(const TargetRegisterInfo &TRI, MachineInstr &MI,
|
|
SMEAttrs SMEFnAttrs) {
|
|
MachineBasicBlock::iterator InsertPt(MI);
|
|
|
|
// Note: InOutZAUsePseudo, RequiresZASavePseudo, and RequiresZT0SavePseudo are
|
|
// intended to mark the position immediately before a call. Due to
|
|
// SelectionDAG constraints, these markers occur after the ADJCALLSTACKDOWN,
|
|
// so we use std::prev(InsertPt) to get the position before the call.
|
|
|
|
if (MI.getOpcode() == AArch64::InOutZAUsePseudo)
|
|
return {ZAState::ACTIVE, std::prev(InsertPt)};
|
|
|
|
// Note: If we need to save both ZA and ZT0 we use RequiresZASavePseudo.
|
|
if (MI.getOpcode() == AArch64::RequiresZASavePseudo)
|
|
return {ZAState::LOCAL_SAVED, std::prev(InsertPt)};
|
|
|
|
// If we only need to save ZT0 there's two cases to consider:
|
|
// 1. The function has ZA state (that we don't need to save).
|
|
// - In this case we switch to the "ACTIVE_ZT0_SAVED" state.
|
|
// This only saves ZT0.
|
|
// 2. The function does not have ZA state
|
|
// - In this case we switch to "LOCAL_COMMITTED" state.
|
|
// This saves ZT0 and turns ZA off.
|
|
if (MI.getOpcode() == AArch64::RequiresZT0SavePseudo) {
|
|
return {SMEFnAttrs.hasZAState() ? ZAState::ACTIVE_ZT0_SAVED
|
|
: ZAState::LOCAL_COMMITTED,
|
|
std::prev(InsertPt)};
|
|
}
|
|
|
|
if (MI.isReturn()) {
|
|
bool ZAOffAtReturn = SMEFnAttrs.hasPrivateZAInterface();
|
|
return {ZAOffAtReturn ? ZAState::OFF : ZAState::ACTIVE, InsertPt};
|
|
}
|
|
|
|
for (auto &MO : MI.operands()) {
|
|
if (isZAorZTRegOp(TRI, MO))
|
|
return {ZAState::ACTIVE, InsertPt};
|
|
}
|
|
|
|
return {ZAState::ANY, InsertPt};
|
|
}
|
|
|
|
struct MachineSMEABI : public MachineFunctionPass {
|
|
inline static char ID = 0;
|
|
|
|
MachineSMEABI(CodeGenOptLevel OptLevel = CodeGenOptLevel::Default)
|
|
: MachineFunctionPass(ID), OptLevel(OptLevel) {}
|
|
|
|
bool runOnMachineFunction(MachineFunction &MF) override;
|
|
|
|
StringRef getPassName() const override { return "Machine SME ABI pass"; }
|
|
|
|
void getAnalysisUsage(AnalysisUsage &AU) const override {
|
|
AU.setPreservesCFG();
|
|
AU.addRequired<EdgeBundlesWrapperLegacy>();
|
|
AU.addRequired<MachineOptimizationRemarkEmitterPass>();
|
|
AU.addRequired<LibcallLoweringInfoWrapper>();
|
|
AU.addPreservedID(MachineLoopInfoID);
|
|
AU.addPreservedID(MachineDominatorsID);
|
|
MachineFunctionPass::getAnalysisUsage(AU);
|
|
}
|
|
|
|
/// Collects the needed ZA state (and live registers) before each instruction
|
|
/// within the machine function.
|
|
FunctionInfo collectNeededZAStates(SMEAttrs SMEFnAttrs);
|
|
|
|
/// Assigns each edge bundle a ZA state based on the desired states of
|
|
/// incoming and outgoing blocks in the bundle.
|
|
SmallVector<ZAState> assignBundleZAStates(const EdgeBundles &Bundles,
|
|
const FunctionInfo &FnInfo);
|
|
|
|
/// Inserts code to handle changes between ZA states within the function.
|
|
/// E.g., ACTIVE -> LOCAL_SAVED will insert code required to save ZA.
|
|
void insertStateChanges(EmitContext &, const FunctionInfo &FnInfo,
|
|
const EdgeBundles &Bundles,
|
|
ArrayRef<ZAState> BundleStates);
|
|
|
|
void addSMELibCall(MachineInstrBuilder &MIB, RTLIB::Libcall LC,
|
|
CallingConv::ID ExpectedCC);
|
|
|
|
void emitZT0SaveRestore(EmitContext &, MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI, bool IsSave);
|
|
|
|
// Emission routines for private and shared ZA functions (using lazy saves).
|
|
void emitSMEPrologue(MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI);
|
|
void emitRestoreLazySave(EmitContext &, MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI,
|
|
LiveRegs PhysLiveRegs);
|
|
void emitSetupLazySave(EmitContext &, MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI);
|
|
void emitAllocateLazySaveBuffer(EmitContext &, MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI);
|
|
void emitZAMode(MachineBasicBlock &MBB, MachineBasicBlock::iterator MBBI,
|
|
bool ClearTPIDR2, bool On);
|
|
|
|
// Emission routines for agnostic ZA functions.
|
|
void emitSetupFullZASave(MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI,
|
|
LiveRegs PhysLiveRegs);
|
|
// Emit a "full" ZA save or restore. It is "full" in the sense that this
|
|
// function will emit a call to __arm_sme_save or __arm_sme_restore, which
|
|
// handles saving and restoring both ZA and ZT0.
|
|
void emitFullZASaveRestore(EmitContext &, MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI,
|
|
LiveRegs PhysLiveRegs, bool IsSave);
|
|
void emitAllocateFullZASaveBuffer(EmitContext &, MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI,
|
|
LiveRegs PhysLiveRegs);
|
|
|
|
/// Attempts to find an insertion point before \p Inst where the status flags
|
|
/// are not live. If \p Inst is `Block.Insts.end()` a point before the end of
|
|
/// the block is found.
|
|
std::pair<MachineBasicBlock::iterator, LiveRegs>
|
|
findStateChangeInsertionPoint(MachineBasicBlock &MBB, const BlockInfo &Block,
|
|
SmallVectorImpl<InstInfo>::const_iterator Inst);
|
|
void emitStateChange(EmitContext &, MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI, ZAState From,
|
|
ZAState To, LiveRegs PhysLiveRegs);
|
|
|
|
// Helpers for switching between lazy/full ZA save/restore routines.
|
|
void emitZASave(EmitContext &Context, MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI, LiveRegs PhysLiveRegs) {
|
|
if (AFI->getSMEFnAttrs().hasAgnosticZAInterface())
|
|
return emitFullZASaveRestore(Context, MBB, MBBI, PhysLiveRegs,
|
|
/*IsSave=*/true);
|
|
return emitSetupLazySave(Context, MBB, MBBI);
|
|
}
|
|
void emitZARestore(EmitContext &Context, MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI, LiveRegs PhysLiveRegs) {
|
|
if (AFI->getSMEFnAttrs().hasAgnosticZAInterface())
|
|
return emitFullZASaveRestore(Context, MBB, MBBI, PhysLiveRegs,
|
|
/*IsSave=*/false);
|
|
return emitRestoreLazySave(Context, MBB, MBBI, PhysLiveRegs);
|
|
}
|
|
void emitAllocateZASaveBuffer(EmitContext &Context, MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI,
|
|
LiveRegs PhysLiveRegs) {
|
|
if (AFI->getSMEFnAttrs().hasAgnosticZAInterface())
|
|
return emitAllocateFullZASaveBuffer(Context, MBB, MBBI, PhysLiveRegs);
|
|
return emitAllocateLazySaveBuffer(Context, MBB, MBBI);
|
|
}
|
|
|
|
/// Collects the reachable calls from \p MBBI marked with \p Marker. This is
|
|
/// intended to be used to emit lazy save remarks. Note: This stops at the
|
|
/// first marked call along any path.
|
|
void collectReachableMarkedCalls(const MachineBasicBlock &MBB,
|
|
MachineBasicBlock::const_iterator MBBI,
|
|
SmallVectorImpl<const MachineInstr *> &Calls,
|
|
unsigned Marker) const;
|
|
|
|
void emitCallSaveRemarks(const MachineBasicBlock &MBB,
|
|
MachineBasicBlock::const_iterator MBBI, DebugLoc DL,
|
|
unsigned Marker, StringRef RemarkName,
|
|
StringRef SaveName) const;
|
|
|
|
void emitError(const Twine &Message) {
|
|
LLVMContext &Context = MF->getFunction().getContext();
|
|
Context.emitError(MF->getName() + ": " + Message);
|
|
}
|
|
|
|
/// Save live physical registers to virtual registers.
|
|
PhysRegSave createPhysRegSave(LiveRegs PhysLiveRegs, MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI, DebugLoc DL);
|
|
/// Restore physical registers from a save of their previous values.
|
|
void restorePhyRegSave(const PhysRegSave &RegSave, MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI, DebugLoc DL);
|
|
|
|
private:
|
|
CodeGenOptLevel OptLevel = CodeGenOptLevel::Default;
|
|
|
|
MachineFunction *MF = nullptr;
|
|
const AArch64Subtarget *Subtarget = nullptr;
|
|
const AArch64RegisterInfo *TRI = nullptr;
|
|
const AArch64FunctionInfo *AFI = nullptr;
|
|
const AArch64InstrInfo *TII = nullptr;
|
|
const LibcallLoweringInfo *LLI = nullptr;
|
|
|
|
MachineOptimizationRemarkEmitter *ORE = nullptr;
|
|
MachineRegisterInfo *MRI = nullptr;
|
|
MachineLoopInfo *MLI = nullptr;
|
|
};
|
|
|
|
static LiveRegs getPhysLiveRegs(LiveRegUnits const &LiveUnits) {
|
|
LiveRegs PhysLiveRegs = LiveRegs::None;
|
|
if (!LiveUnits.available(AArch64::NZCV))
|
|
PhysLiveRegs |= LiveRegs::NZCV;
|
|
// We have to track W0 and X0 separately as otherwise things can get
|
|
// confused if we attempt to preserve X0 but only W0 was defined.
|
|
if (!LiveUnits.available(AArch64::W0))
|
|
PhysLiveRegs |= LiveRegs::W0;
|
|
if (!LiveUnits.available(AArch64::W0_HI))
|
|
PhysLiveRegs |= LiveRegs::W0_HI;
|
|
return PhysLiveRegs;
|
|
}
|
|
|
|
static void setPhysLiveRegs(LiveRegUnits &LiveUnits, LiveRegs PhysLiveRegs) {
|
|
if (PhysLiveRegs & LiveRegs::NZCV)
|
|
LiveUnits.addReg(AArch64::NZCV);
|
|
if (PhysLiveRegs & LiveRegs::W0)
|
|
LiveUnits.addReg(AArch64::W0);
|
|
if (PhysLiveRegs & LiveRegs::W0_HI)
|
|
LiveUnits.addReg(AArch64::W0_HI);
|
|
}
|
|
|
|
[[maybe_unused]] bool isCallStartOpcode(unsigned Opc) {
|
|
switch (Opc) {
|
|
case AArch64::TLSDESC_CALLSEQ:
|
|
case AArch64::TLSDESC_AUTH_CALLSEQ:
|
|
case AArch64::ADJCALLSTACKDOWN:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
FunctionInfo MachineSMEABI::collectNeededZAStates(SMEAttrs SMEFnAttrs) {
|
|
assert((SMEFnAttrs.hasAgnosticZAInterface() || SMEFnAttrs.hasZT0State() ||
|
|
SMEFnAttrs.hasZAState()) &&
|
|
"Expected function to have ZA/ZT0 state!");
|
|
|
|
SmallVector<BlockInfo> Blocks(MF->getNumBlockIDs());
|
|
LiveRegs PhysLiveRegsAfterSMEPrologue = LiveRegs::None;
|
|
std::optional<MachineBasicBlock::iterator> AfterSMEProloguePt;
|
|
|
|
for (MachineBasicBlock &MBB : *MF) {
|
|
BlockInfo &Block = Blocks[MBB.getNumber()];
|
|
|
|
if (MBB.isEntryBlock()) {
|
|
// Entry block:
|
|
Block.FixedEntryState = ZAState::ENTRY;
|
|
} else if (MBB.isEHPad()) {
|
|
// EH entry block:
|
|
Block.FixedEntryState = ZAState::LOCAL_COMMITTED;
|
|
}
|
|
|
|
LiveRegUnits LiveUnits(*TRI);
|
|
LiveUnits.addLiveOuts(MBB);
|
|
|
|
Block.PhysLiveRegsAtExit = getPhysLiveRegs(LiveUnits);
|
|
auto FirstTerminatorInsertPt = MBB.getFirstTerminator();
|
|
auto FirstNonPhiInsertPt = MBB.getFirstNonPHI();
|
|
for (MachineInstr &MI : reverse(MBB)) {
|
|
MachineBasicBlock::iterator MBBI(MI);
|
|
LiveUnits.stepBackward(MI);
|
|
LiveRegs PhysLiveRegs = getPhysLiveRegs(LiveUnits);
|
|
// The SMEStateAllocPseudo marker is added to a function if the save
|
|
// buffer was allocated in SelectionDAG. It marks the end of the
|
|
// allocation -- which is a safe point for this pass to insert any TPIDR2
|
|
// block setup.
|
|
if (MI.getOpcode() == AArch64::SMEStateAllocPseudo) {
|
|
AfterSMEProloguePt = MBBI;
|
|
PhysLiveRegsAfterSMEPrologue = PhysLiveRegs;
|
|
}
|
|
// Note: We treat Agnostic ZA as inout_za with an alternate save/restore.
|
|
auto [NeededState, InsertPt] = getInstNeededZAState(*TRI, MI, SMEFnAttrs);
|
|
assert((InsertPt == MBBI || isCallStartOpcode(InsertPt->getOpcode())) &&
|
|
"Unexpected state change insertion point!");
|
|
// TODO: Do something to avoid state changes where NZCV is live.
|
|
if (MBBI == FirstTerminatorInsertPt)
|
|
Block.PhysLiveRegsAtExit = PhysLiveRegs;
|
|
if (MBBI == FirstNonPhiInsertPt)
|
|
Block.PhysLiveRegsAtEntry = PhysLiveRegs;
|
|
if (NeededState != ZAState::ANY)
|
|
Block.Insts.push_back({NeededState, InsertPt, PhysLiveRegs});
|
|
}
|
|
|
|
// Reverse vector (as we had to iterate backwards for liveness).
|
|
std::reverse(Block.Insts.begin(), Block.Insts.end());
|
|
|
|
// Record the desired states on entry/exit of this block. These are the
|
|
// states that would not incur a state transition.
|
|
if (!Block.Insts.empty()) {
|
|
Block.DesiredIncomingState = Block.Insts.front().NeededState;
|
|
Block.DesiredOutgoingState = Block.Insts.back().NeededState;
|
|
}
|
|
}
|
|
|
|
return FunctionInfo{std::move(Blocks), AfterSMEProloguePt,
|
|
PhysLiveRegsAfterSMEPrologue};
|
|
}
|
|
|
|
/// Assigns each edge bundle a ZA state based on the desired states of incoming
|
|
/// and outgoing blocks in the bundle.
|
|
SmallVector<ZAState>
|
|
MachineSMEABI::assignBundleZAStates(const EdgeBundles &Bundles,
|
|
const FunctionInfo &FnInfo) {
|
|
SmallVector<ZAState> BundleStates(Bundles.getNumBundles());
|
|
for (unsigned I = 0, E = Bundles.getNumBundles(); I != E; ++I) {
|
|
std::optional<ZAState> BundleState;
|
|
for (unsigned BlockID : Bundles.getBlocks(I)) {
|
|
const BlockInfo &Block = FnInfo.Blocks[BlockID];
|
|
// Check if the block is an incoming block in the bundle. Note: We skip
|
|
// Block.FixedEntryState != ANY to ignore EH pads (which are only
|
|
// reachable via exceptions).
|
|
if (Block.FixedEntryState != ZAState::ANY ||
|
|
Bundles.getBundle(BlockID, /*Out=*/false) != I)
|
|
continue;
|
|
|
|
// Pick a state that matches all incoming blocks. Fall back to "ACTIVE" if
|
|
// any incoming state doesn't match. This will hoist the state from
|
|
// incoming blocks to outgoing blocks.
|
|
if (!BundleState)
|
|
BundleState = Block.DesiredIncomingState;
|
|
else if (BundleState != Block.DesiredIncomingState)
|
|
BundleState = ZAState::ACTIVE;
|
|
}
|
|
|
|
if (!BundleState || BundleState == ZAState::ANY)
|
|
BundleState = ZAState::ACTIVE;
|
|
|
|
BundleStates[I] = *BundleState;
|
|
}
|
|
|
|
return BundleStates;
|
|
}
|
|
|
|
std::pair<MachineBasicBlock::iterator, LiveRegs>
|
|
MachineSMEABI::findStateChangeInsertionPoint(
|
|
MachineBasicBlock &MBB, const BlockInfo &Block,
|
|
SmallVectorImpl<InstInfo>::const_iterator Inst) {
|
|
LiveRegs PhysLiveRegs;
|
|
MachineBasicBlock::iterator InsertPt;
|
|
if (Inst != Block.Insts.end()) {
|
|
InsertPt = Inst->InsertPt;
|
|
PhysLiveRegs = Inst->PhysLiveRegs;
|
|
} else {
|
|
InsertPt = MBB.getFirstTerminator();
|
|
PhysLiveRegs = Block.PhysLiveRegsAtExit;
|
|
}
|
|
|
|
if (PhysLiveRegs == LiveRegs::None)
|
|
return {InsertPt, PhysLiveRegs}; // Nothing to do (no live regs).
|
|
|
|
// Find the previous state change. We can not move before this point.
|
|
MachineBasicBlock::iterator PrevStateChangeI;
|
|
if (Inst == Block.Insts.begin()) {
|
|
PrevStateChangeI = MBB.begin();
|
|
} else {
|
|
// Note: `std::prev(Inst)` is the previous InstInfo. We only create an
|
|
// InstInfo object for instructions that require a specific ZA state, so the
|
|
// InstInfo is the site of the previous state change in the block (which can
|
|
// be several MIs earlier).
|
|
PrevStateChangeI = std::prev(Inst)->InsertPt;
|
|
}
|
|
|
|
// Note: LiveUnits will only accurately track X0 and NZCV.
|
|
LiveRegUnits LiveUnits(*TRI);
|
|
setPhysLiveRegs(LiveUnits, PhysLiveRegs);
|
|
auto BestCandidate = std::make_pair(InsertPt, PhysLiveRegs);
|
|
for (MachineBasicBlock::iterator I = InsertPt; I != PrevStateChangeI; --I) {
|
|
// Don't move before/into a call (which may have a state change before it).
|
|
if (I->getOpcode() == TII->getCallFrameDestroyOpcode() || I->isCall())
|
|
break;
|
|
LiveUnits.stepBackward(*I);
|
|
LiveRegs CurrentPhysLiveRegs = getPhysLiveRegs(LiveUnits);
|
|
// Find places where NZCV is available, but keep looking for locations where
|
|
// both NZCV and X0 are available, which can avoid some copies.
|
|
if (!(CurrentPhysLiveRegs & LiveRegs::NZCV))
|
|
BestCandidate = {I, CurrentPhysLiveRegs};
|
|
if (CurrentPhysLiveRegs == LiveRegs::None)
|
|
break;
|
|
}
|
|
return BestCandidate;
|
|
}
|
|
|
|
void MachineSMEABI::insertStateChanges(EmitContext &Context,
|
|
const FunctionInfo &FnInfo,
|
|
const EdgeBundles &Bundles,
|
|
ArrayRef<ZAState> BundleStates) {
|
|
for (MachineBasicBlock &MBB : *MF) {
|
|
const BlockInfo &Block = FnInfo.Blocks[MBB.getNumber()];
|
|
ZAState InState = BundleStates[Bundles.getBundle(MBB.getNumber(),
|
|
/*Out=*/false)];
|
|
|
|
ZAState CurrentState = Block.FixedEntryState;
|
|
if (CurrentState == ZAState::ANY)
|
|
CurrentState = InState;
|
|
|
|
for (auto &Inst : Block.Insts) {
|
|
if (CurrentState != Inst.NeededState) {
|
|
auto [InsertPt, PhysLiveRegs] =
|
|
findStateChangeInsertionPoint(MBB, Block, &Inst);
|
|
emitStateChange(Context, MBB, InsertPt, CurrentState, Inst.NeededState,
|
|
PhysLiveRegs);
|
|
CurrentState = Inst.NeededState;
|
|
}
|
|
}
|
|
|
|
if (MBB.succ_empty())
|
|
continue;
|
|
|
|
ZAState OutState =
|
|
BundleStates[Bundles.getBundle(MBB.getNumber(), /*Out=*/true)];
|
|
if (CurrentState != OutState) {
|
|
auto [InsertPt, PhysLiveRegs] =
|
|
findStateChangeInsertionPoint(MBB, Block, Block.Insts.end());
|
|
emitStateChange(Context, MBB, InsertPt, CurrentState, OutState,
|
|
PhysLiveRegs);
|
|
}
|
|
}
|
|
}
|
|
|
|
static DebugLoc getDebugLoc(MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI) {
|
|
if (MBB.empty())
|
|
return DebugLoc();
|
|
return MBBI != MBB.end() ? MBBI->getDebugLoc() : MBB.back().getDebugLoc();
|
|
}
|
|
|
|
/// Finds the first call (as determined by MachineInstr::isCall()) starting from
|
|
/// \p MBBI in \p MBB marked with \p Marker (which is a marker opcode such as
|
|
/// RequiresZASavePseudo). If a marked call is found, it is pushed to \p Calls
|
|
/// and the function returns true.
|
|
static bool findMarkedCall(const MachineBasicBlock &MBB,
|
|
MachineBasicBlock::const_iterator MBBI,
|
|
SmallVectorImpl<const MachineInstr *> &Calls,
|
|
unsigned Marker, unsigned CallDestroyOpcode) {
|
|
auto IsMarker = [&](auto &MI) { return MI.getOpcode() == Marker; };
|
|
auto MarkerInst = std::find_if(MBBI, MBB.end(), IsMarker);
|
|
if (MarkerInst == MBB.end())
|
|
return false;
|
|
MachineBasicBlock::const_iterator I = MarkerInst;
|
|
while (++I != MBB.end()) {
|
|
if (I->isCall() || I->getOpcode() == CallDestroyOpcode)
|
|
break;
|
|
}
|
|
if (I != MBB.end() && I->isCall())
|
|
Calls.push_back(&*I);
|
|
// Note: This function always returns true if a "Marker" was found.
|
|
return true;
|
|
}
|
|
|
|
void MachineSMEABI::collectReachableMarkedCalls(
|
|
const MachineBasicBlock &StartMBB,
|
|
MachineBasicBlock::const_iterator StartInst,
|
|
SmallVectorImpl<const MachineInstr *> &Calls, unsigned Marker) const {
|
|
assert(Marker == AArch64::InOutZAUsePseudo ||
|
|
Marker == AArch64::RequiresZASavePseudo ||
|
|
Marker == AArch64::RequiresZT0SavePseudo);
|
|
unsigned CallDestroyOpcode = TII->getCallFrameDestroyOpcode();
|
|
if (findMarkedCall(StartMBB, StartInst, Calls, Marker, CallDestroyOpcode))
|
|
return;
|
|
|
|
SmallPtrSet<const MachineBasicBlock *, 4> Visited;
|
|
SmallVector<const MachineBasicBlock *> Worklist(StartMBB.succ_rbegin(),
|
|
StartMBB.succ_rend());
|
|
while (!Worklist.empty()) {
|
|
const MachineBasicBlock *MBB = Worklist.pop_back_val();
|
|
auto [_, Inserted] = Visited.insert(MBB);
|
|
if (!Inserted)
|
|
continue;
|
|
|
|
if (!findMarkedCall(*MBB, MBB->begin(), Calls, Marker, CallDestroyOpcode))
|
|
Worklist.append(MBB->succ_rbegin(), MBB->succ_rend());
|
|
}
|
|
}
|
|
|
|
static StringRef getCalleeName(const MachineInstr &CallInst) {
|
|
assert(CallInst.isCall() && "expected a call");
|
|
for (const MachineOperand &MO : CallInst.operands()) {
|
|
if (MO.isSymbol())
|
|
return MO.getSymbolName();
|
|
if (MO.isGlobal())
|
|
return MO.getGlobal()->getName();
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void MachineSMEABI::emitCallSaveRemarks(const MachineBasicBlock &MBB,
|
|
MachineBasicBlock::const_iterator MBBI,
|
|
DebugLoc DL, unsigned Marker,
|
|
StringRef RemarkName,
|
|
StringRef SaveName) const {
|
|
auto SaveRemark = [&](DebugLoc DL, const MachineBasicBlock &MBB) {
|
|
return MachineOptimizationRemarkAnalysis("sme", RemarkName, DL, &MBB);
|
|
};
|
|
StringRef StateName = Marker == AArch64::RequiresZT0SavePseudo ? "ZT0" : "ZA";
|
|
ORE->emit([&] {
|
|
return SaveRemark(DL, MBB) << SaveName << " of " << StateName
|
|
<< " emitted in '" << MF->getName() << "'";
|
|
});
|
|
if (!ORE->allowExtraAnalysis("sme"))
|
|
return;
|
|
SmallVector<const MachineInstr *> CallsRequiringSaves;
|
|
collectReachableMarkedCalls(MBB, MBBI, CallsRequiringSaves, Marker);
|
|
for (const MachineInstr *CallInst : CallsRequiringSaves) {
|
|
auto R = SaveRemark(CallInst->getDebugLoc(), *CallInst->getParent());
|
|
R << "call";
|
|
if (StringRef CalleeName = getCalleeName(*CallInst); !CalleeName.empty())
|
|
R << " to '" << CalleeName << "'";
|
|
R << " requires " << StateName << " save";
|
|
ORE->emit(R);
|
|
}
|
|
}
|
|
|
|
void MachineSMEABI::emitSetupLazySave(EmitContext &Context,
|
|
MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI) {
|
|
DebugLoc DL = getDebugLoc(MBB, MBBI);
|
|
|
|
emitCallSaveRemarks(MBB, MBBI, DL, AArch64::RequiresZASavePseudo,
|
|
"SMELazySaveZA", "lazy save");
|
|
|
|
// Get pointer to TPIDR2 block.
|
|
Register TPIDR2 = MRI->createVirtualRegister(&AArch64::GPR64spRegClass);
|
|
Register TPIDR2Ptr = MRI->createVirtualRegister(&AArch64::GPR64RegClass);
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::ADDXri), TPIDR2)
|
|
.addFrameIndex(Context.getTPIDR2Block(*MF))
|
|
.addImm(0)
|
|
.addImm(0);
|
|
BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::COPY), TPIDR2Ptr)
|
|
.addReg(TPIDR2);
|
|
// Set TPIDR2_EL0 to point to TPIDR2 block.
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::MSR))
|
|
.addImm(AArch64SysReg::TPIDR2_EL0)
|
|
.addReg(TPIDR2Ptr);
|
|
}
|
|
|
|
PhysRegSave MachineSMEABI::createPhysRegSave(LiveRegs PhysLiveRegs,
|
|
MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI,
|
|
DebugLoc DL) {
|
|
PhysRegSave RegSave{PhysLiveRegs};
|
|
if (PhysLiveRegs & LiveRegs::NZCV) {
|
|
RegSave.StatusFlags = MRI->createVirtualRegister(&AArch64::GPR64RegClass);
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::MRS), RegSave.StatusFlags)
|
|
.addImm(AArch64SysReg::NZCV)
|
|
.addReg(AArch64::NZCV, RegState::Implicit);
|
|
}
|
|
// Note: Preserving X0 is "free" as this is before register allocation, so
|
|
// the register allocator is still able to optimize these copies.
|
|
if (PhysLiveRegs & LiveRegs::W0) {
|
|
RegSave.X0Save = MRI->createVirtualRegister(PhysLiveRegs & LiveRegs::W0_HI
|
|
? &AArch64::GPR64RegClass
|
|
: &AArch64::GPR32RegClass);
|
|
BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::COPY), RegSave.X0Save)
|
|
.addReg(PhysLiveRegs & LiveRegs::W0_HI ? AArch64::X0 : AArch64::W0);
|
|
}
|
|
return RegSave;
|
|
}
|
|
|
|
void MachineSMEABI::restorePhyRegSave(const PhysRegSave &RegSave,
|
|
MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI,
|
|
DebugLoc DL) {
|
|
if (RegSave.StatusFlags != AArch64::NoRegister)
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::MSR))
|
|
.addImm(AArch64SysReg::NZCV)
|
|
.addReg(RegSave.StatusFlags)
|
|
.addReg(AArch64::NZCV, RegState::ImplicitDefine);
|
|
|
|
if (RegSave.X0Save != AArch64::NoRegister)
|
|
BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::COPY),
|
|
RegSave.PhysLiveRegs & LiveRegs::W0_HI ? AArch64::X0 : AArch64::W0)
|
|
.addReg(RegSave.X0Save);
|
|
}
|
|
|
|
void MachineSMEABI::addSMELibCall(MachineInstrBuilder &MIB, RTLIB::Libcall LC,
|
|
CallingConv::ID ExpectedCC) {
|
|
RTLIB::LibcallImpl LCImpl = LLI->getLibcallImpl(LC);
|
|
if (LCImpl == RTLIB::Unsupported)
|
|
emitError("cannot lower SME ABI (SME routines unsupported)");
|
|
CallingConv::ID CC = LLI->getLibcallImplCallingConv(LCImpl);
|
|
StringRef ImplName = RTLIB::RuntimeLibcallsInfo::getLibcallImplName(LCImpl);
|
|
if (CC != ExpectedCC)
|
|
emitError("invalid calling convention for SME routine: '" + ImplName + "'");
|
|
// FIXME: This assumes the ImplName StringRef is null-terminated.
|
|
MIB.addExternalSymbol(ImplName.data());
|
|
MIB.addRegMask(TRI->getCallPreservedMask(*MF, CC));
|
|
}
|
|
|
|
void MachineSMEABI::emitRestoreLazySave(EmitContext &Context,
|
|
MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI,
|
|
LiveRegs PhysLiveRegs) {
|
|
DebugLoc DL = getDebugLoc(MBB, MBBI);
|
|
Register TPIDR2EL0 = MRI->createVirtualRegister(&AArch64::GPR64RegClass);
|
|
Register TPIDR2 = AArch64::X0;
|
|
|
|
// TODO: Emit these within the restore MBB to prevent unnecessary saves.
|
|
PhysRegSave RegSave = createPhysRegSave(PhysLiveRegs, MBB, MBBI, DL);
|
|
|
|
// Enable ZA.
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::MSRpstatesvcrImm1))
|
|
.addImm(AArch64SVCR::SVCRZA)
|
|
.addImm(1);
|
|
// Get current TPIDR2_EL0.
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::MRS), TPIDR2EL0)
|
|
.addImm(AArch64SysReg::TPIDR2_EL0);
|
|
// Get pointer to TPIDR2 block.
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::ADDXri), TPIDR2)
|
|
.addFrameIndex(Context.getTPIDR2Block(*MF))
|
|
.addImm(0)
|
|
.addImm(0);
|
|
// (Conditionally) restore ZA state.
|
|
auto RestoreZA = BuildMI(MBB, MBBI, DL, TII->get(AArch64::RestoreZAPseudo))
|
|
.addReg(TPIDR2EL0)
|
|
.addReg(TPIDR2);
|
|
addSMELibCall(
|
|
RestoreZA, RTLIB::SMEABI_TPIDR2_RESTORE,
|
|
CallingConv::AArch64_SME_ABI_Support_Routines_PreserveMost_From_X0);
|
|
// Zero TPIDR2_EL0.
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::MSR))
|
|
.addImm(AArch64SysReg::TPIDR2_EL0)
|
|
.addReg(AArch64::XZR);
|
|
|
|
restorePhyRegSave(RegSave, MBB, MBBI, DL);
|
|
}
|
|
|
|
void MachineSMEABI::emitZAMode(MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI,
|
|
bool ClearTPIDR2, bool On) {
|
|
DebugLoc DL = getDebugLoc(MBB, MBBI);
|
|
|
|
if (ClearTPIDR2)
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::MSR))
|
|
.addImm(AArch64SysReg::TPIDR2_EL0)
|
|
.addReg(AArch64::XZR);
|
|
|
|
// Disable ZA.
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::MSRpstatesvcrImm1))
|
|
.addImm(AArch64SVCR::SVCRZA)
|
|
.addImm(On ? 1 : 0);
|
|
}
|
|
|
|
void MachineSMEABI::emitAllocateLazySaveBuffer(
|
|
EmitContext &Context, MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI) {
|
|
MachineFrameInfo &MFI = MF->getFrameInfo();
|
|
DebugLoc DL = getDebugLoc(MBB, MBBI);
|
|
Register SP = MRI->createVirtualRegister(&AArch64::GPR64RegClass);
|
|
Register SVL = MRI->createVirtualRegister(&AArch64::GPR64RegClass);
|
|
Register Buffer = AFI->getEarlyAllocSMESaveBuffer();
|
|
|
|
// Calculate SVL.
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::RDSVLI_XI), SVL).addImm(1);
|
|
|
|
// 1. Allocate the lazy save buffer.
|
|
if (Buffer == AArch64::NoRegister) {
|
|
// TODO: On Windows, we allocate the lazy save buffer in SelectionDAG (so
|
|
// Buffer != AArch64::NoRegister). This is done to reuse the existing
|
|
// expansions (which can insert stack checks). This works, but it means we
|
|
// will always allocate the lazy save buffer (even if the function contains
|
|
// no lazy saves). If we want to handle Windows here, we'll need to
|
|
// implement something similar to LowerWindowsDYNAMIC_STACKALLOC.
|
|
assert(!Subtarget->isTargetWindows() &&
|
|
"Lazy ZA save is not yet supported on Windows");
|
|
Buffer = MRI->createVirtualRegister(&AArch64::GPR64RegClass);
|
|
// Get original stack pointer.
|
|
BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::COPY), SP)
|
|
.addReg(AArch64::SP);
|
|
// Allocate a lazy-save buffer object of the size given, normally SVL * SVL
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::MSUBXrrr), Buffer)
|
|
.addReg(SVL)
|
|
.addReg(SVL)
|
|
.addReg(SP);
|
|
BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::COPY), AArch64::SP)
|
|
.addReg(Buffer);
|
|
// We have just allocated a variable sized object, tell this to PEI.
|
|
MFI.CreateVariableSizedObject(Align(16), nullptr);
|
|
}
|
|
|
|
// 2. Setup the TPIDR2 block.
|
|
{
|
|
// Note: This case just needs to do `SVL << 48`. It is not implemented as we
|
|
// generally don't support big-endian SVE/SME.
|
|
if (!Subtarget->isLittleEndian())
|
|
reportFatalInternalError(
|
|
"TPIDR2 block initialization is not supported on big-endian targets");
|
|
|
|
// Store buffer pointer and num_za_save_slices.
|
|
// Bytes 10-15 are implicitly zeroed.
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::STPXi))
|
|
.addReg(Buffer)
|
|
.addReg(SVL)
|
|
.addFrameIndex(Context.getTPIDR2Block(*MF))
|
|
.addImm(0);
|
|
}
|
|
}
|
|
|
|
static constexpr unsigned ZERO_ALL_ZA_MASK = 0b11111111;
|
|
|
|
void MachineSMEABI::emitSMEPrologue(MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI) {
|
|
DebugLoc DL = getDebugLoc(MBB, MBBI);
|
|
|
|
bool ZeroZA = AFI->getSMEFnAttrs().isNewZA();
|
|
bool ZeroZT0 = AFI->getSMEFnAttrs().isNewZT0();
|
|
if (AFI->getSMEFnAttrs().hasPrivateZAInterface()) {
|
|
// Get current TPIDR2_EL0.
|
|
Register TPIDR2EL0 = MRI->createVirtualRegister(&AArch64::GPR64RegClass);
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::MRS))
|
|
.addReg(TPIDR2EL0, RegState::Define)
|
|
.addImm(AArch64SysReg::TPIDR2_EL0);
|
|
// If TPIDR2_EL0 is non-zero, commit the lazy save.
|
|
// NOTE: Functions that only use ZT0 don't need to zero ZA.
|
|
auto CommitZASave =
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::CommitZASavePseudo))
|
|
.addReg(TPIDR2EL0)
|
|
.addImm(ZeroZA)
|
|
.addImm(ZeroZT0);
|
|
addSMELibCall(
|
|
CommitZASave, RTLIB::SMEABI_TPIDR2_SAVE,
|
|
CallingConv::AArch64_SME_ABI_Support_Routines_PreserveMost_From_X0);
|
|
if (ZeroZA)
|
|
CommitZASave.addDef(AArch64::ZAB0, RegState::ImplicitDefine);
|
|
if (ZeroZT0)
|
|
CommitZASave.addDef(AArch64::ZT0, RegState::ImplicitDefine);
|
|
// Enable ZA (as ZA could have previously been in the OFF state).
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::MSRpstatesvcrImm1))
|
|
.addImm(AArch64SVCR::SVCRZA)
|
|
.addImm(1);
|
|
} else if (AFI->getSMEFnAttrs().hasSharedZAInterface()) {
|
|
if (ZeroZA)
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::ZERO_M))
|
|
.addImm(ZERO_ALL_ZA_MASK)
|
|
.addDef(AArch64::ZAB0, RegState::ImplicitDefine);
|
|
if (ZeroZT0)
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::ZERO_T)).addDef(AArch64::ZT0);
|
|
}
|
|
}
|
|
|
|
void MachineSMEABI::emitFullZASaveRestore(EmitContext &Context,
|
|
MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI,
|
|
LiveRegs PhysLiveRegs, bool IsSave) {
|
|
DebugLoc DL = getDebugLoc(MBB, MBBI);
|
|
|
|
if (IsSave)
|
|
emitCallSaveRemarks(MBB, MBBI, DL, AArch64::RequiresZASavePseudo,
|
|
"SMEFullZASave", "full save");
|
|
|
|
PhysRegSave RegSave = createPhysRegSave(PhysLiveRegs, MBB, MBBI, DL);
|
|
|
|
// Copy the buffer pointer into X0.
|
|
Register BufferPtr = AArch64::X0;
|
|
BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::COPY), BufferPtr)
|
|
.addReg(Context.getAgnosticZABufferPtr(*MF));
|
|
|
|
// Call __arm_sme_save/__arm_sme_restore.
|
|
auto SaveRestoreZA = BuildMI(MBB, MBBI, DL, TII->get(AArch64::BL))
|
|
.addReg(BufferPtr, RegState::Implicit);
|
|
addSMELibCall(
|
|
SaveRestoreZA,
|
|
IsSave ? RTLIB::SMEABI_SME_SAVE : RTLIB::SMEABI_SME_RESTORE,
|
|
CallingConv::AArch64_SME_ABI_Support_Routines_PreserveMost_From_X1);
|
|
|
|
restorePhyRegSave(RegSave, MBB, MBBI, DL);
|
|
}
|
|
|
|
void MachineSMEABI::emitZT0SaveRestore(EmitContext &Context,
|
|
MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI,
|
|
bool IsSave) {
|
|
DebugLoc DL = getDebugLoc(MBB, MBBI);
|
|
|
|
// Note: This will report calls that _only_ need ZT0 saved. Call that save
|
|
// both ZA and ZT0 will be under the SMELazySaveZA remark. This prevents
|
|
// reporting the same calls twice.
|
|
if (IsSave)
|
|
emitCallSaveRemarks(MBB, MBBI, DL, AArch64::RequiresZT0SavePseudo,
|
|
"SMEZT0Save", "spill");
|
|
|
|
Register ZT0Save = MRI->createVirtualRegister(&AArch64::GPR64spRegClass);
|
|
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::ADDXri), ZT0Save)
|
|
.addFrameIndex(Context.getZT0SaveSlot(*MF))
|
|
.addImm(0)
|
|
.addImm(0);
|
|
|
|
if (IsSave) {
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::STR_TX))
|
|
.addReg(AArch64::ZT0)
|
|
.addReg(ZT0Save);
|
|
} else {
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::LDR_TX), AArch64::ZT0)
|
|
.addReg(ZT0Save);
|
|
}
|
|
}
|
|
|
|
void MachineSMEABI::emitAllocateFullZASaveBuffer(
|
|
EmitContext &Context, MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator MBBI, LiveRegs PhysLiveRegs) {
|
|
// Buffer already allocated in SelectionDAG.
|
|
if (AFI->getEarlyAllocSMESaveBuffer())
|
|
return;
|
|
|
|
DebugLoc DL = getDebugLoc(MBB, MBBI);
|
|
Register BufferPtr = Context.getAgnosticZABufferPtr(*MF);
|
|
Register BufferSize = MRI->createVirtualRegister(&AArch64::GPR64RegClass);
|
|
|
|
PhysRegSave RegSave = createPhysRegSave(PhysLiveRegs, MBB, MBBI, DL);
|
|
|
|
// Calculate the SME state size.
|
|
{
|
|
auto SMEStateSize = BuildMI(MBB, MBBI, DL, TII->get(AArch64::BL))
|
|
.addReg(AArch64::X0, RegState::ImplicitDefine);
|
|
addSMELibCall(
|
|
SMEStateSize, RTLIB::SMEABI_SME_STATE_SIZE,
|
|
CallingConv::AArch64_SME_ABI_Support_Routines_PreserveMost_From_X1);
|
|
BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::COPY), BufferSize)
|
|
.addReg(AArch64::X0);
|
|
}
|
|
|
|
// Allocate a buffer object of the size given __arm_sme_state_size.
|
|
{
|
|
MachineFrameInfo &MFI = MF->getFrameInfo();
|
|
BuildMI(MBB, MBBI, DL, TII->get(AArch64::SUBXrx64), AArch64::SP)
|
|
.addReg(AArch64::SP)
|
|
.addReg(BufferSize)
|
|
.addImm(AArch64_AM::getArithExtendImm(AArch64_AM::UXTX, 0));
|
|
BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::COPY), BufferPtr)
|
|
.addReg(AArch64::SP);
|
|
|
|
// We have just allocated a variable sized object, tell this to PEI.
|
|
MFI.CreateVariableSizedObject(Align(16), nullptr);
|
|
}
|
|
|
|
restorePhyRegSave(RegSave, MBB, MBBI, DL);
|
|
}
|
|
|
|
struct FromState {
|
|
ZAState From;
|
|
|
|
constexpr uint8_t to(ZAState To) const {
|
|
static_assert(NUM_ZA_STATE < 16, "expected ZAState to fit in 4-bits");
|
|
return uint8_t(From) << 4 | uint8_t(To);
|
|
}
|
|
};
|
|
|
|
constexpr FromState transitionFrom(ZAState From) { return FromState{From}; }
|
|
|
|
void MachineSMEABI::emitStateChange(EmitContext &Context,
|
|
MachineBasicBlock &MBB,
|
|
MachineBasicBlock::iterator InsertPt,
|
|
ZAState From, ZAState To,
|
|
LiveRegs PhysLiveRegs) {
|
|
// ZA not used.
|
|
if (From == ZAState::ANY || To == ZAState::ANY)
|
|
return;
|
|
|
|
// If we're exiting from the ENTRY state that means that the function has not
|
|
// used ZA, so in the case of private ZA/ZT0 functions we can omit any set up.
|
|
if (From == ZAState::ENTRY && To == ZAState::OFF)
|
|
return;
|
|
|
|
// TODO: Avoid setting up the save buffer if there's no transition to
|
|
// LOCAL_SAVED.
|
|
if (From == ZAState::ENTRY) {
|
|
assert(&MBB == &MBB.getParent()->front() &&
|
|
"ENTRY state only valid in entry block");
|
|
emitSMEPrologue(MBB, MBB.getFirstNonPHI());
|
|
if (To == ZAState::ACTIVE)
|
|
return; // Nothing more to do (ZA is active after the prologue).
|
|
|
|
// Note: "emitNewZAPrologue" zeros ZA, so we may need to setup a lazy save
|
|
// if "To" is "ZAState::LOCAL_SAVED". It may be possible to improve this
|
|
// case by changing the placement of the zero instruction.
|
|
From = ZAState::ACTIVE;
|
|
}
|
|
|
|
SMEAttrs SMEFnAttrs = AFI->getSMEFnAttrs();
|
|
bool IsAgnosticZA = SMEFnAttrs.hasAgnosticZAInterface();
|
|
bool HasZT0State = SMEFnAttrs.hasZT0State();
|
|
bool HasZAState = IsAgnosticZA || SMEFnAttrs.hasZAState();
|
|
|
|
switch (transitionFrom(From).to(To)) {
|
|
// This section handles: ACTIVE <-> ACTIVE_ZT0_SAVED
|
|
case transitionFrom(ZAState::ACTIVE).to(ZAState::ACTIVE_ZT0_SAVED):
|
|
emitZT0SaveRestore(Context, MBB, InsertPt, /*IsSave=*/true);
|
|
break;
|
|
case transitionFrom(ZAState::ACTIVE_ZT0_SAVED).to(ZAState::ACTIVE):
|
|
emitZT0SaveRestore(Context, MBB, InsertPt, /*IsSave=*/false);
|
|
break;
|
|
|
|
// This section handles: ACTIVE[_ZT0_SAVED] -> LOCAL_SAVED
|
|
case transitionFrom(ZAState::ACTIVE).to(ZAState::LOCAL_SAVED):
|
|
case transitionFrom(ZAState::ACTIVE_ZT0_SAVED).to(ZAState::LOCAL_SAVED):
|
|
if (HasZT0State && From == ZAState::ACTIVE)
|
|
emitZT0SaveRestore(Context, MBB, InsertPt, /*IsSave=*/true);
|
|
if (HasZAState)
|
|
emitZASave(Context, MBB, InsertPt, PhysLiveRegs);
|
|
break;
|
|
|
|
// This section handles: ACTIVE -> LOCAL_COMMITTED
|
|
case transitionFrom(ZAState::ACTIVE).to(ZAState::LOCAL_COMMITTED):
|
|
// TODO: We could support ZA state here, but this transition is currently
|
|
// only possible when we _don't_ have ZA state.
|
|
assert(HasZT0State && !HasZAState && "Expect to only have ZT0 state.");
|
|
emitZT0SaveRestore(Context, MBB, InsertPt, /*IsSave=*/true);
|
|
emitZAMode(MBB, InsertPt, /*ClearTPIDR2=*/false, /*On=*/false);
|
|
break;
|
|
|
|
// This section handles: LOCAL_COMMITTED -> (OFF|LOCAL_SAVED)
|
|
case transitionFrom(ZAState::LOCAL_COMMITTED).to(ZAState::OFF):
|
|
case transitionFrom(ZAState::LOCAL_COMMITTED).to(ZAState::LOCAL_SAVED):
|
|
// These transistions are a no-op.
|
|
break;
|
|
|
|
// This section handles: LOCAL_(SAVED|COMMITTED) -> ACTIVE[_ZT0_SAVED]
|
|
case transitionFrom(ZAState::LOCAL_COMMITTED).to(ZAState::ACTIVE):
|
|
case transitionFrom(ZAState::LOCAL_COMMITTED).to(ZAState::ACTIVE_ZT0_SAVED):
|
|
case transitionFrom(ZAState::LOCAL_SAVED).to(ZAState::ACTIVE):
|
|
case transitionFrom(ZAState::LOCAL_SAVED).to(ZAState::ACTIVE_ZT0_SAVED):
|
|
if (HasZAState)
|
|
emitZARestore(Context, MBB, InsertPt, PhysLiveRegs);
|
|
else
|
|
emitZAMode(MBB, InsertPt, /*ClearTPIDR2=*/false, /*On=*/true);
|
|
if (HasZT0State && To == ZAState::ACTIVE)
|
|
emitZT0SaveRestore(Context, MBB, InsertPt, /*IsSave=*/false);
|
|
break;
|
|
|
|
// This section handles transistions to OFF (not previously covered)
|
|
case transitionFrom(ZAState::ACTIVE).to(ZAState::OFF):
|
|
case transitionFrom(ZAState::ACTIVE_ZT0_SAVED).to(ZAState::OFF):
|
|
case transitionFrom(ZAState::LOCAL_SAVED).to(ZAState::OFF):
|
|
assert(SMEFnAttrs.hasPrivateZAInterface() &&
|
|
"Did not expect to turn ZA off in shared/agnostic ZA function");
|
|
emitZAMode(MBB, InsertPt, /*ClearTPIDR2=*/From == ZAState::LOCAL_SAVED,
|
|
/*On=*/false);
|
|
break;
|
|
|
|
default:
|
|
dbgs() << "Error: Transition from " << getZAStateString(From) << " to "
|
|
<< getZAStateString(To) << '\n';
|
|
llvm_unreachable("Unimplemented state transition");
|
|
}
|
|
}
|
|
|
|
} // end anonymous namespace
|
|
|
|
INITIALIZE_PASS(MachineSMEABI, "aarch64-machine-sme-abi", "Machine SME ABI",
|
|
false, false)
|
|
|
|
bool MachineSMEABI::runOnMachineFunction(MachineFunction &MF) {
|
|
Subtarget = &MF.getSubtarget<AArch64Subtarget>();
|
|
if (!Subtarget->hasSME())
|
|
return false;
|
|
|
|
AFI = MF.getInfo<AArch64FunctionInfo>();
|
|
SMEAttrs SMEFnAttrs = AFI->getSMEFnAttrs();
|
|
if (!SMEFnAttrs.hasZAState() && !SMEFnAttrs.hasZT0State() &&
|
|
!SMEFnAttrs.hasAgnosticZAInterface())
|
|
return false;
|
|
|
|
assert(MF.getRegInfo().isSSA() && "Expected to be run on SSA form!");
|
|
|
|
this->MF = &MF;
|
|
ORE = &getAnalysis<MachineOptimizationRemarkEmitterPass>().getORE();
|
|
LLI = &getAnalysis<LibcallLoweringInfoWrapper>().getLibcallLowering(
|
|
*MF.getFunction().getParent(), *Subtarget);
|
|
TII = Subtarget->getInstrInfo();
|
|
TRI = Subtarget->getRegisterInfo();
|
|
MRI = &MF.getRegInfo();
|
|
|
|
const EdgeBundles &Bundles =
|
|
getAnalysis<EdgeBundlesWrapperLegacy>().getEdgeBundles();
|
|
|
|
FunctionInfo FnInfo = collectNeededZAStates(SMEFnAttrs);
|
|
|
|
SmallVector<ZAState> BundleStates = assignBundleZAStates(Bundles, FnInfo);
|
|
|
|
EmitContext Context;
|
|
insertStateChanges(Context, FnInfo, Bundles, BundleStates);
|
|
|
|
if (Context.needsSaveBuffer()) {
|
|
if (FnInfo.AfterSMEProloguePt) {
|
|
// Note: With inline stack probes the AfterSMEProloguePt may not be in the
|
|
// entry block (due to the probing loop).
|
|
MachineBasicBlock::iterator MBBI = *FnInfo.AfterSMEProloguePt;
|
|
emitAllocateZASaveBuffer(Context, *MBBI->getParent(), MBBI,
|
|
FnInfo.PhysLiveRegsAfterSMEPrologue);
|
|
} else {
|
|
MachineBasicBlock &EntryBlock = MF.front();
|
|
emitAllocateZASaveBuffer(
|
|
Context, EntryBlock, EntryBlock.getFirstNonPHI(),
|
|
FnInfo.Blocks[EntryBlock.getNumber()].PhysLiveRegsAtEntry);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
FunctionPass *llvm::createMachineSMEABIPass(CodeGenOptLevel OptLevel) {
|
|
return new MachineSMEABI(OptLevel);
|
|
}
|