Skip to content

Commit 6068a5d

Browse files
[comgr][hotswap] preserve VGPR-MSB mode when splitting gfx1250 WMMAs
The WMMA split pass rewrites gfx1250 B0-only WMMAs into pairs of narrower WMMAs that also run on A0. On gfx1250, VGPR operands past v255 are reached through a persistent VGPR-MSB bank mode, so a split whose upper half crosses v255 (or a K-split whose second half reuses dst as src2) must execute that half under a different bank. Make the pass VGPR-MSB aware: - Recover the live VGPR-MSB mode at each WMMA via a whole-function CFG fixed point, fail-closed: an ambiguous or unprovable mode declines the split. - Bracket the upper half with s_set_vgpr_msb (switch before, restore after), and emit nothing when no bank change is needed. - Fail closed on every matched-but-unsplittable path (RequiredPatchFailed) instead of silently leaving an A0-illegal opcode in .text. Use the same control-flow surface as the rewrite so the mode proof cannot be seeded from a wrong incoming mode: - Decline the analysis when collectDirectBranchTargets() reports an unresolved call target, since such a call may enter any function at an interior offset. - Resolve absolute-immediate and PC-materialized s_swap_pc_i64 / s_set_pc_i64 targets in the interior-entry pre-pass so a cross-function interior entry declines the split rather than seeding the symbol-start mode. - Seed declared kernel-descriptor entries so an interior KD-entry block is analyzed from the real entry mode rather than misread as unreachable. Add lit coverage for the bank-crossing, fail-closed, and loop-carried mode cases, plus interior cross-function s_swap_pc_i64 (materialized and absolute), an interior s_set_pc_i64 jump, and an interior kernel-descriptor entry.
1 parent 4da88cf commit 6068a5d

17 files changed

Lines changed: 1341 additions & 55 deletions

amd/comgr/src/comgr-hotswap-b0a0.cpp

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -989,23 +989,18 @@ struct PcMaterializedCallInfo {
989989
/// by llvm/test/MC/AMDGPU/gfx1250_asm_salu_lit64.s. Stop at the first
990990
/// overlapping definition or control-flow boundary, so any variation remains
991991
/// unresolved and follows the existing fail-closed policy.
992-
static std::optional<PcMaterializedCallInfo>
993-
matchPcMaterializedCall(ArrayRef<InternalDecodedInst> Decoded, size_t CallIndex,
994-
const LLVMState &LS, uint64_t TextAddr) {
995-
const InternalDecodedInst &Call = Decoded[CallIndex];
996-
if (!Call.DecodeSucceeded || Call.Inst.getOpcode() != LS.SSwapPcI64Opcode ||
997-
Call.Inst.getNumOperands() < 2 || !Call.Inst.getOperand(0).isReg() ||
998-
!Call.Inst.getOperand(0).getReg())
999-
return std::nullopt;
1000-
const MCOperand &TargetOperand =
1001-
Call.Inst.getOperand(Call.Inst.getNumOperands() - 1);
1002-
if (!TargetOperand.isReg() || !TargetOperand.getReg())
1003-
return std::nullopt;
1004-
MCRegister TargetRegister(TargetOperand.getReg());
1005-
992+
// Shared get-PC/add resolver behind matchPcMaterializedCall (s_swap_pc_i64
993+
// calls) and the WMMA split pass's s_set_pc_i64 jump handling. The backward
994+
// scan and fail-closed policy are identical for both transfer kinds; only the
995+
// terminating opcode and the return-register semantics differ, which the
996+
// callers handle.
997+
std::optional<MaterializedPcSequence>
998+
resolveMaterializedPcTarget(ArrayRef<InternalDecodedInst> Decoded,
999+
size_t TransferIndex, MCRegister TargetRegister,
1000+
const LLVMState &LS, uint64_t TextAddr) {
10061001
std::optional<size_t> AddIndex;
10071002
int64_t AddImmediate = 0;
1008-
for (size_t I = CallIndex; I != 0;) {
1003+
for (size_t I = TransferIndex; I != 0;) {
10091004
--I;
10101005
const InternalDecodedInst &Candidate = Decoded[I];
10111006
if (!Candidate.DecodeSucceeded || isControlFlowBoundary(Candidate, LS))
@@ -1041,23 +1036,43 @@ matchPcMaterializedCall(ArrayRef<InternalDecodedInst> Decoded, size_t CallIndex,
10411036
return std::nullopt;
10421037

10431038
std::optional<uint64_t> GetPcAddress = checkedAddUint64(
1044-
TextAddr, Candidate.Offset, "PC-materialized call instruction");
1039+
TextAddr, Candidate.Offset, "PC-materialized transfer instruction");
10451040
if (!GetPcAddress)
10461041
return std::nullopt;
10471042
std::optional<uint64_t> PcValue = checkedAddUint64(
1048-
*GetPcAddress, Candidate.Size, "PC-materialized call PC value");
1043+
*GetPcAddress, Candidate.Size, "PC-materialized transfer PC value");
10491044
if (!PcValue)
10501045
return std::nullopt;
10511046
// s_add_nc_u64 uses modulo-2^64 arithmetic. Casting the signed MC
10521047
// immediate to uint64_t and adding it reproduces both positive and
10531048
// negative literals, including INT64_MIN, without signed overflow.
1054-
return PcMaterializedCallInfo{
1055-
*PcValue + static_cast<uint64_t>(AddImmediate), Candidate.Offset,
1056-
Call.Offset, MCRegister(Call.Inst.getOperand(0).getReg())};
1049+
return MaterializedPcSequence{
1050+
*PcValue + static_cast<uint64_t>(AddImmediate), Candidate.Offset};
10571051
}
10581052
return std::nullopt;
10591053
}
10601054

1055+
static std::optional<PcMaterializedCallInfo>
1056+
matchPcMaterializedCall(ArrayRef<InternalDecodedInst> Decoded, size_t CallIndex,
1057+
const LLVMState &LS, uint64_t TextAddr) {
1058+
const InternalDecodedInst &Call = Decoded[CallIndex];
1059+
if (!Call.DecodeSucceeded || Call.Inst.getOpcode() != LS.SSwapPcI64Opcode ||
1060+
Call.Inst.getNumOperands() < 2 || !Call.Inst.getOperand(0).isReg() ||
1061+
!Call.Inst.getOperand(0).getReg())
1062+
return std::nullopt;
1063+
const MCOperand &TargetOperand =
1064+
Call.Inst.getOperand(Call.Inst.getNumOperands() - 1);
1065+
if (!TargetOperand.isReg() || !TargetOperand.getReg())
1066+
return std::nullopt;
1067+
std::optional<MaterializedPcSequence> Sequence = resolveMaterializedPcTarget(
1068+
Decoded, CallIndex, MCRegister(TargetOperand.getReg()), LS, TextAddr);
1069+
if (!Sequence)
1070+
return std::nullopt;
1071+
return PcMaterializedCallInfo{Sequence->Target, Sequence->SequenceStart,
1072+
Call.Offset,
1073+
MCRegister(Call.Inst.getOperand(0).getReg())};
1074+
}
1075+
10611076
struct KnownCallSite {
10621077
size_t InstIndex = 0;
10631078
uint64_t Target = 0;
@@ -2273,10 +2288,11 @@ static std::optional<uint32_t> applyGfx1250B0toA0Rules(
22732288
*PoolVAddr, Elf.textAddr(), "trampoline pool base offset");
22742289
if (!PoolBaseOffset)
22752290
return std::nullopt;
2276-
PatchContext Ctx{
2277-
Config, Decoded, Text, TextSize, *PoolBaseOffset,
2278-
LS, OutTrampolines, Sleds, Elf, Liveness,
2279-
KernelStats, OutScratchPatches, *ControlFlow, Profile};
2291+
PatchContext Ctx{Config, Decoded, Text,
2292+
TextSize, *PoolBaseOffset, LS,
2293+
OutTrampolines, Sleds, Elf,
2294+
Liveness, KernelStats, OutScratchPatches,
2295+
*ControlFlow, Profile, DeclaredEntries->Entries};
22802296

22812297
const HotswapPatchVTable &VT = getHotswapPatchVTable();
22822298

amd/comgr/src/comgr-hotswap-internal.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,6 +1133,16 @@ struct DirectControlFlowInfo {
11331133
bool HasUnresolvedTargets = false;
11341134
};
11351135

1136+
// Per-instruction persistent gfx1250 VGPR-MSB mode (packed src0/src1/src2/dst,
1137+
// two bits each, values 0-255) recovered by the WMMA split pass's
1138+
// whole-function CFG fixed point. The sentinels distinguish "not analyzed",
1139+
// "validated unreachable", and "reachable but ambiguous" so a required WMMA
1140+
// split can fail closed when the incoming mode cannot be proven. See
1141+
// comgr-hotswap-patch-wmma-split.cpp.
1142+
inline constexpr int8_t VgprMsbUnanalyzed = -3;
1143+
inline constexpr int8_t VgprMsbUnreachable = -2;
1144+
inline constexpr int8_t VgprMsbUnknown = -1;
1145+
11361146
/// Mutable per-run context threaded through all patch passes. Bundles the
11371147
/// input config, decoded instruction stream, raw .text bytes, MC state,
11381148
/// output streams (trampolines / scratch info), and the shared ELF view +
@@ -1158,10 +1168,21 @@ struct PatchContext {
11581168
// Per-rewrite profiling session (inert unless AMD_COMGR_TIME_STATISTICS is
11591169
// set). Deep patch sites record into its lock-free local array.
11601170
HotswapProfile &Profile;
1171+
// Text-relative declared entry offsets (function symbol starts and kernel
1172+
// descriptor entries) from collectDeclaredTextEntries(). The WMMA split
1173+
// pass's VGPR-MSB analysis seeds each in-range entry as an ABI entry point so
1174+
// an interior kernel-descriptor entry is analyzed from the real entry mode
1175+
// instead of being misclassified as unreachable.
1176+
llvm::ArrayRef<uint64_t> DeclaredEntries;
11611177
// Required patches are transformations whose unpatched original code is
11621178
// unsafe to return when the selected rewrite policy needs the patch.
11631179
bool RequiredPatchFailed = false;
11641180
bool RequiredPatchApplied = false;
1181+
// Packed per-instruction gfx1250 VGPR-MSB mode, lazily populated by the WMMA
1182+
// split pass (empty until then). Indexed by position in Decoded; each entry
1183+
// is a VgprMsb* sentinel or a 0-255 mode. See
1184+
// comgr-hotswap-patch-wmma-split.cpp.
1185+
std::vector<int16_t> VgprMsbModeBefore;
11651186
// Sum of the bytes already queued in OutTrampolines. Keeping this in the
11661187
// per-rewrite context makes each new pool-position calculation constant
11671188
// time even for code objects with many thousands of patch sites.
@@ -1277,6 +1298,27 @@ std::optional<uint64_t>
12771298
evaluateDirectControlFlowTarget(const InternalDecodedInst &DI,
12781299
const LLVMState &LS);
12791300

1301+
/// A canonical get-PC/add materialized address feeding a PC-materialized
1302+
/// transfer (s_get_pc_i64 / s_add_nc_u64 / s_{swap,set}_pc_i64). \p Target is
1303+
/// the absolute in-.text virtual address; \p SequenceStart is the .text offset
1304+
/// of the s_get_pc_i64 that begins the sequence.
1305+
struct MaterializedPcSequence {
1306+
uint64_t Target = 0;
1307+
uint64_t SequenceStart = 0;
1308+
};
1309+
1310+
/// Resolve the target of a PC-materialized transfer whose target register is
1311+
/// \p TargetReg and whose transfer instruction (an s_swap_pc_i64 call or an
1312+
/// s_set_pc_i64 jump) is \p Decoded[TransferIndex]. Scans backward for the
1313+
/// single s_add_nc_u64 then s_get_pc_i64 that define \p TargetReg, stopping at
1314+
/// the first control-flow boundary or unexpected clobber so any variation
1315+
/// stays unresolved (nullopt) for fail-closed callers. \p TextAddr is the
1316+
/// .text base virtual address.
1317+
std::optional<MaterializedPcSequence>
1318+
resolveMaterializedPcTarget(llvm::ArrayRef<InternalDecodedInst> Decoded,
1319+
size_t TransferIndex, llvm::MCRegister TargetReg,
1320+
const LLVMState &LS, uint64_t TextAddr);
1321+
12801322
/// Collect branch and call targets used to protect interior entry points from
12811323
/// trampoline coalescing. Absolute addresses in TextAddr .. TextAddr +
12821324
/// TextSize are converted to text-relative offsets. Canonical PC-materialized

0 commit comments

Comments
 (0)