forked from ROCm/llvm-project
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcomgr-hotswap-b0a0.cpp
More file actions
665 lines (598 loc) · 29 KB
/
Copy pathcomgr-hotswap-b0a0.cpp
File metadata and controls
665 lines (598 loc) · 29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
//===- comgr-hotswap-b0a0.cpp - GFX1250 B0-to-A0 patch dispatcher --------===//
//
// Part of Comgr, under the Apache License v2.0 with LLVM Exceptions.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Dispatcher for B0-to-A0 silicon stepping patches and the
/// retargetCodeObject orchestrator that drives the full pipeline:
/// decode -> patch -> trampoline growth -> DWARF update.
///
/// Patch passes are dispatched through HotswapPatchVTable. The membership
/// list lives in comgr-hotswap-patches.def; each entry corresponds to one
/// slot on the vtable and one register*Patch function in a sibling
/// comgr-hotswap-patch-*.cpp. installHotswapPatches() walks the .def to
/// bind every slot. The vtable is exposed through getHotswapPatchVTable(),
/// a Meyers singleton whose initializer eagerly runs installHotswapPatches
/// on its private storage; C++11 [stmt.dcl]/4 guarantees this happens
/// exactly once and is safe under concurrent first access, so the
/// dispatcher and the amd_comgr_hotswap_rewrite entry point can fetch the
/// fully-bound vtable with no explicit synchronization.
/// This replaces the prior LLVM_ATTRIBUTE_WEAK + `#if !defined(_MSC_VER)`
/// override pattern, which silently disabled hotswap on Windows because
/// PE/COFF does not honour weak the way ELF does
/// (issue ROCm/llvm-project#2479).
///
//===----------------------------------------------------------------------===//
#include "comgr-hotswap-internal.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Compiler.h"
#include <limits>
using namespace llvm;
namespace COMGR {
namespace hotswap {
// -- GFX1250 B0-to-A0 constants -----------------------------------------------
//
// All instruction encoding lives in LLVMState (s_branch opcode + pre-encoded
// s_nop bytes, populated at initLLVM time via the MC asm parser). This policy
// layer only carries ISA identifiers and register granularity -- no
// target-specific opcode bits should land here.
static constexpr unsigned Gfx1250MaxVgprs = 1024;
// GFX1250 wave32 VGPR ENCODING granularity is 16 (per
// AMDGPUBaseInfo::getVGPREncodingGranule with Feature1024AddressableVGPRs),
// not the 8 used by earlier GFX10/11 wave32. Used by ElfView's KD
// decode/encode helpers (getKernelVgprCount / updateKernelDescriptor) to
// interpret COMPUTE_PGM_RSRC1.GRANULATED_WORKITEM_VGPR_COUNT.
// GFX12 wave32: 106 user-addressable SGPRs (s0-s105); s106-s107 are VCC.
static constexpr unsigned Gfx1250MaxSgprs = 106;
static constexpr unsigned Gfx1250VgprGranuleSize = 16;
/// Build the default RewriteConfig used for the GFX1250 B0-to-A0 rewrite:
/// fills in the identity source / target ISA (both gfx1250) and the
/// AMDGPU register granularity constants consumed by
/// ElfView::updateKernelDescriptor. Instruction-encoding state is not
/// carried in RewriteConfig; see LLVMState for the s_branch opcode and
/// pre-encoded s_nop bytes.
static RewriteConfig makeGfx1250B0A0Config() {
// `Config` / `Cfg` are reserved below: `Config` always names a
// RewriteConfig; `Cfg` is only used for the CFG (control-flow graph)
// local in applyGfx1250B0toA0Rules.
RewriteConfig Config;
Config.SourceIsa = "amdgcn-amd-amdhsa--gfx1250";
Config.TargetIsa = "amdgcn-amd-amdhsa--gfx1250";
Config.TargetCpu = "gfx1250";
Config.MaxVgprs = Gfx1250MaxVgprs;
Config.MaxSgprs = Gfx1250MaxSgprs;
Config.VgprGranuleSize = Gfx1250VgprGranuleSize;
return Config;
}
// -- Forward declarations for liveness/DWARF stubs ----------------------------
//
// These have weak default definitions below. The apply* patch families use
// HotswapPatchVTable dispatch; these lower-level helpers stay on weak stubs
// until a real implementation lands, at which point they should migrate to
// an explicit registration contract as well.
CFG buildCfg(ArrayRef<InternalDecodedInst> Decoded, const MCInstrInfo &);
LivenessInfo computeLiveness(ArrayRef<InternalDecodedInst> Decoded, const CFG &,
const MCInstrInfo &, const MCRegisterInfo &,
unsigned MaxVgprs);
RegDefUse getInstRegDefUse(const MCInst &, const MCInstrInfo &,
const MCRegisterInfo &);
int64_t getBranchImm(const MCInst &);
bool verifyPatchCorrectness(const uint8_t *, uint64_t, const LLVMState &,
ArrayRef<ScratchPatchInfo>, unsigned);
bool addTrampolineSymbols(WritableMemoryBuffer &ElfBuf,
ArrayRef<Trampoline> Trampolines,
uint64_t TextSizeBefore, unsigned TextSectionIdx);
bool patchDebugLine(WritableMemoryBuffer &ElfBuf,
ArrayRef<Trampoline> Trampolines, uint64_t TextSizeBefore,
uint64_t TextAddr);
void patchDebugRanges(uint8_t *Elf, size_t ElfSize, uint64_t TextAddr,
uint64_t TextSizeBefore, uint64_t TrampTotal);
void patchDebugInfo(uint8_t *Elf, size_t ElfSize, uint64_t TextAddr,
uint64_t TextSizeBefore, uint64_t TrampTotal);
void patchDebugFrame(uint8_t *Elf, size_t ElfSize, uint64_t TextAddr,
uint64_t TextSizeBefore, uint64_t TrampTotal);
// -- HotswapPatchVTable plumbing ----------------------------------------------
//
// Patch-module forward declarations live in comgr-hotswap-internal.h
// (driven off the same comgr-hotswap-patches.def), so libamd_comgr and
// the unit tests share one prototype source. Here we supply the
// singleton accessor and the installer that walks the .def to invoke
// each register*Patch. A .def entry without a matching register*Patch
// definition produces a link error at libamd_comgr link time.
//
// installHotswapPatches() is exposed in the header so unit tests can
// bind a local HotswapPatchVTable for fixture-style coverage. Production
// code never calls it directly: getHotswapPatchVTable()'s initializer
// invokes it eagerly on the singleton's private storage, which the C++11
// magic-static rule guarantees runs exactly once even under concurrent
// first access. That removes both the explicit std::call_once at the
// retargetCodeObject entry point and any inter-TU static-init order
// dependency on the patch modules.
void installHotswapPatches(HotswapPatchVTable &VT) {
#define HOTSWAP_PATCH(Name) register##Name##Patch(VT);
#include "comgr-hotswap-patches.def"
#undef HOTSWAP_PATCH
}
HotswapPatchVTable &getHotswapPatchVTable() {
static HotswapPatchVTable VT = [] {
HotswapPatchVTable Tmp;
installHotswapPatches(Tmp);
return Tmp;
}();
return VT;
}
// -- Weak-symbol liveness stubs -----------------------------------------------
//
// Conservative defaults: all VGPRs reported live. VgprAllocator will
// allocate above KD count (correct but suboptimal until the real liveness
// layer lands).
LLVM_ATTRIBUTE_WEAK CFG buildCfg(ArrayRef<InternalDecodedInst> Decoded,
const MCInstrInfo &) {
(void)Decoded;
return CFG();
}
LLVM_ATTRIBUTE_WEAK LivenessInfo computeLiveness(
ArrayRef<InternalDecodedInst> Decoded, const CFG &, const MCInstrInfo &,
const MCRegisterInfo &, unsigned MaxVgprs) {
LivenessInfo Info;
BitVector AllLive(MaxVgprs);
AllLive.set(0, MaxVgprs);
Info.LiveBefore.resize(Decoded.size(), AllLive);
Info.LiveAfter.resize(Decoded.size(), AllLive);
Info.Converged = true;
return Info;
}
LLVM_ATTRIBUTE_WEAK RegDefUse getInstRegDefUse(const MCInst &,
const MCInstrInfo &,
const MCRegisterInfo &) {
return {};
}
LLVM_ATTRIBUTE_WEAK int64_t getBranchImm(const MCInst &) { return 0; }
LLVM_ATTRIBUTE_WEAK bool verifyPatchCorrectness(const uint8_t *, uint64_t,
const LLVMState &,
ArrayRef<ScratchPatchInfo>,
unsigned) {
return true;
}
// -- Weak-symbol DWARF stubs --------------------------------------------------
LLVM_ATTRIBUTE_WEAK bool addTrampolineSymbols(WritableMemoryBuffer &,
ArrayRef<Trampoline>, uint64_t,
unsigned) {
return true;
}
LLVM_ATTRIBUTE_WEAK bool patchDebugLine(WritableMemoryBuffer &,
ArrayRef<Trampoline>, uint64_t,
uint64_t) {
return true;
}
LLVM_ATTRIBUTE_WEAK void patchDebugRanges(uint8_t *, size_t, uint64_t, uint64_t,
uint64_t) {}
LLVM_ATTRIBUTE_WEAK void patchDebugInfo(uint8_t *, size_t, uint64_t, uint64_t,
uint64_t) {}
LLVM_ATTRIBUTE_WEAK void patchDebugFrame(uint8_t *, size_t, uint64_t, uint64_t,
uint64_t) {}
// -- NOP sled scanning --------------------------------------------------------
/// Scan \p Decoded for runs of consecutive `s_nop` instructions at least
/// MinNopSledSize bytes long and return the resulting NopSled list (each
/// sled records Start / End byte offsets in .text and the initial WritePos
/// at Start). These sleds are the landing zones emitToNopSled targets for
/// in-place rewrites. NOPs are identified by MC opcode (cached on \p LS at
/// initLLVM() time) rather than mnemonic string, so the scanner is robust
/// against printer aliasing / mnemonic formatting variations.
static std::vector<NopSled>
buildNopSledMap(ArrayRef<InternalDecodedInst> Decoded, const LLVMState &LS) {
std::vector<NopSled> Sleds;
const size_t N = Decoded.size();
size_t I = 0;
while (I < N) {
if (Decoded[I].Inst.getOpcode() == LS.SNopOpcode) {
uint64_t Start = Decoded[I].Offset;
uint64_t End = Start;
while (I < N && Decoded[I].Inst.getOpcode() == LS.SNopOpcode) {
End = Decoded[I].Offset + Decoded[I].Size;
++I;
}
if (End - Start >= MinNopSledSize)
Sleds.push_back({Start, End, Start});
} else {
++I;
}
}
return Sleds;
}
// -- Sled-or-trampoline code emission -----------------------------------------
/// Emit the replacement code for the instruction at [\p InstOffset,
/// \p InstOffset + \p InstSize) into a nearby NOP sled: writes \p Replacement
/// into the sled, appends a branch-back to the next instruction after the
/// original site, overwrites the original site with a branch-forward to the
/// sled, and pads the leftover bytes of the original slot with cached s_nop
/// bytes. Advances \c Sled.WritePos by the amount consumed. Returns false if
/// either branch encoding fails, leaving \c Ctx.Text partially written.
[[nodiscard]] bool emitToNopSled(PatchContext &Ctx, NopSled &Sled,
uint64_t InstOffset, uint32_t InstSize,
ArrayRef<uint8_t> Replacement) {
const LLVMState &LS = Ctx.LS;
std::memcpy(Ctx.Text + Sled.WritePos, Replacement.data(), Replacement.size());
SmallVector<uint8_t> BrBack = LS.encodeSBranch(
Sled.WritePos + Replacement.size(), InstOffset + InstSize);
if (BrBack.empty()) {
log() << "hotswap: error: emitToNopSled: encodeSBranch for branch-back "
<< "at sled offset 0x"
<< utohexstr(Sled.WritePos + Replacement.size()) << " -> 0x"
<< utohexstr(InstOffset + InstSize) << " failed.\n";
return false;
}
std::memcpy(Ctx.Text + Sled.WritePos + Replacement.size(), BrBack.data(),
BrBack.size());
SmallVector<uint8_t> BrFwd = LS.encodeSBranch(InstOffset, Sled.WritePos);
if (BrFwd.empty()) {
log() << "hotswap: error: emitToNopSled: encodeSBranch for branch-fwd "
<< "at original offset 0x" << utohexstr(InstOffset) << " -> sled 0x"
<< utohexstr(Sled.WritePos) << " failed.\n";
return false;
}
std::memcpy(Ctx.Text + InstOffset, BrFwd.data(), BrFwd.size());
// Pad the tail of the replaced instruction slot with cached s_nop bytes
// (pre-encoded in LLVMState at initLLVM() time).
for (uint32_t I = MinInstSize; I < InstSize; I += MinInstSize)
std::memcpy(Ctx.Text + InstOffset + I, LS.SNopBytes.data(), MinInstSize);
Sled.WritePos += Replacement.size() + MinInstSize;
return true;
}
/// Queue a deferred trampoline for the instruction at [\p InstOffset,
/// \p InstOffset + \p InstSize) with \p Replacement as its body. The final
/// branch encoding (branch-back at the trampoline tail and branch-forward
/// overwrite at the original site) is filled in by fixupTrampolineBranches
/// once the post-.text trampoline layout is known -- we reserve
/// MinInstSize zero bytes at the end of the trampoline body as a
/// placeholder rather than encoding twice. Used when there is no reachable
/// NOP sled for an in-place sled patch.
[[nodiscard]] bool emitToTrampoline(PatchContext &Ctx, uint64_t InstOffset,
uint32_t InstSize,
ArrayRef<uint8_t> Replacement) {
Trampoline T;
T.OriginalOffset = InstOffset;
T.OriginalSize = InstSize;
T.Bytes.insert(T.Bytes.end(), Replacement.begin(), Replacement.end());
// Reserve the branch-back slot; fixupTrampolineBranches fills it in.
T.Bytes.insert(T.Bytes.end(), MinInstSize, uint8_t{0});
Ctx.OutTrampolines.emplace_back(std::move(T));
return true;
}
/// Emit \p Replacement for the instruction at [\p InstOffset,
/// \p InstOffset + \p InstSize). Prefers an in-place NOP-sled rewrite when a
/// reachable sled with sufficient headroom exists; otherwise falls back to a
/// deferred trampoline.
[[nodiscard]] bool emitReplacementCode(PatchContext &Ctx, uint64_t InstOffset,
uint32_t InstSize,
ArrayRef<uint8_t> Replacement) {
// findNearestSled already enforces that the returned sled has at least
// `Needed` bytes of headroom, so a non-null result is sufficient to take
// the in-place path.
uint64_t Needed = Replacement.size() + MinInstSize;
if (NopSled *Sled = findNearestSled(Ctx.NopSleds, InstOffset, Needed))
return emitToNopSled(Ctx, *Sled, InstOffset, InstSize, Replacement);
return emitToTrampoline(Ctx, InstOffset, InstSize, Replacement);
}
// -- applyGfx1250B0toA0Rules --------------------------------------------------
/// Per-instruction patch-pass trampoline: invokes \p Fn with (\p Ctx,
/// \p Idx) if it is non-null, or returns 0 otherwise. nullptr means
/// the corresponding pass family has no implementation linked in
/// (e.g. scratch today), which the dispatcher treats as a no-op slot.
static uint32_t runPerInstPass(uint32_t (*Fn)(PatchContext &, size_t),
PatchContext &Ctx, size_t Idx) {
return Fn ? Fn(Ctx, Idx) : 0;
}
/// Main per-instruction dispatcher for the GFX1250 B0-to-A0 rewrite.
/// Builds the NOP sled map, CFG, and VGPR liveness for the decoded stream,
/// then walks each decoded instruction and runs the patch passes in order
/// (in-place -> trampoline -> WMMA split -> scratch). Each pass gets a
/// chance to claim the instruction; first non-zero return wins. Also runs
/// the whole-function WMMA-hazard pass after the per-instruction loop and
/// records per-kernel stats via ElfView::updateKernelDescriptor.
/// Returns the total number of applied patches across all passes.
static uint32_t
applyGfx1250B0toA0Rules(std::vector<InternalDecodedInst> &Decoded,
uint8_t *Text, uint64_t TextSize, const LLVMState &LS,
std::vector<Trampoline> &OutTrampolines, ElfView &Elf,
std::vector<ScratchPatchInfo> &OutScratchPatches,
const RewriteConfig &Config) {
uint32_t Patched = 0;
std::vector<NopSled> Sleds = buildNopSledMap(Decoded, LS);
CFG Cfg = buildCfg(Decoded, *LS.MCII);
LivenessInfo Liveness =
computeLiveness(Decoded, Cfg, *LS.MCII, *LS.MRI, Config.MaxVgprs);
if (!Liveness.Converged) {
log() << "hotswap: error: liveness analysis did not converge, using "
<< "conservative all-VGPRs-live fallback\n";
BitVector AllVgprs(Config.MaxVgprs);
AllVgprs.set(0, Config.MaxVgprs);
for (size_t I = 0, LE = Liveness.LiveBefore.size(); I < LE; ++I) {
Liveness.LiveBefore[I] = AllVgprs;
Liveness.LiveAfter[I] = AllVgprs;
}
}
StringMap<KernelPatchStats> KernelStats;
PatchContext Ctx{Config, Decoded, Text, TextSize, LS,
OutTrampolines, Sleds, Elf, Liveness, KernelStats,
OutScratchPatches};
const HotswapPatchVTable &VT = getHotswapPatchVTable();
// Skip undecoded slots produced by the decoder for bytes it could not
// classify as a valid instruction; the dispatcher has nothing to match
// against on these and we must not invoke the patch passes for them.
constexpr StringLiteral UnknownMnemonic = "<unknown>";
for (size_t Idx = 0, E = Decoded.size(); Idx < E; ++Idx) {
const InternalDecodedInst &DI = Decoded[Idx];
if (DI.Mnemonic == UnknownMnemonic)
continue;
if (uint32_t P = runPerInstPass(VT.applyInPlacePatches, Ctx, Idx)) {
Patched += P;
continue;
}
if (uint32_t P = runPerInstPass(VT.applyTrampolinePatches, Ctx, Idx)) {
Patched += P;
continue;
}
if (uint32_t P = runPerInstPass(VT.applyWmmaSplitPatches, Ctx, Idx)) {
Patched += P;
continue;
}
if (uint32_t P = runPerInstPass(VT.applyScratchPatches, Ctx, Idx)) {
Patched += P;
continue;
}
if (uint32_t P = runPerInstPass(VT.applyWmmaScale16Patches, Ctx, Idx)) {
Patched += P;
continue;
}
}
// Whole-kernel passes below run after per-instruction patches. Earlier
// passes may have modified Text bytes, but the Decoded stream still holds
// the original MCInst/Mnemonic/Offset entries. This is safe because:
// - In-place patches only change opcodes within the same encoding size,
// preserving instruction boundaries and offsets.
// - Trampoline patches replace the original instruction with a branch
// (same size), so the Decoded entry's Offset still points at the
// branch site; the WMMA classifier and VOP3PX2 mnemonic match won't
// treat a branch as WMMA/VALU/VOP3PX2.
// If a future patch family changes instruction boundaries, the Decoded
// stream must be rebuilt before these passes run.
if (VT.applyWmmaHazardPatch)
Patched += VT.applyWmmaHazardPatch(Ctx);
if (VT.applyVop3px2Src2Fix)
Patched += VT.applyVop3px2Src2Fix(Ctx);
for (const llvm::StringMapEntry<KernelPatchStats> &KV : KernelStats) {
StringRef KName = KV.first();
const KernelPatchStats &Stats = KV.second;
if (KName.empty())
continue;
std::optional<unsigned> VgprsBefore =
Elf.getKernelVgprCount(KName, Config.VgprGranuleSize);
if (Stats.ExtraVgprs > 0)
Elf.updateKernelDescriptor(KName, Stats.ExtraVgprs,
Config.VgprGranuleSize);
std::optional<unsigned> VgprsAfter =
Elf.getKernelVgprCount(KName, Config.VgprGranuleSize);
log() << "hotswap: liveness: kernel " << KName
<< ": vgprs_before=" << VgprsBefore.value_or(0)
<< ", vgprs_after=" << VgprsAfter.value_or(0)
<< ", scratch_reused=" << Stats.ScratchReused
<< ", scratch_above_kd=" << Stats.ScratchAboveKd << "\n";
}
return Patched;
}
// -- retargetCodeObject helpers -------------------------------------------
/// Finalize the deferred trampolines produced by emitToTrampoline: resolves
/// the branch-back at the tail of each trampoline to land on the next
/// instruction after the original site, writes the branch-forward + s_nop
/// padding at the original .text slot, and reports per-trampoline encoding
/// failures through log(). Runs after all patch passes finish so the
/// post-.text layout of trampolines is known. Returns false if any
/// trampoline could not be fixed up.
[[nodiscard]] static bool
fixupTrampolineBranches(std::vector<Trampoline> &Trampolines, uint8_t *Text,
uint64_t TextSize, const LLVMState &LS) {
// Fail-fast on the first encoding error: the position of later
// trampolines depends on earlier ones, so a single bad branch would
// cascade into incorrect layout. A single failure invalidates the whole
// rewrite, so there is nothing useful to recover beyond it.
uint64_t TrampOffset = TextSize;
for (Trampoline &T : Trampolines) {
uint64_t TP = TrampOffset;
TrampOffset += T.Bytes.size();
SmallVector<uint8_t> BrBack = LS.encodeSBranch(
TP + T.Bytes.size() - MinInstSize, T.OriginalOffset + T.OriginalSize);
if (BrBack.empty()) {
log() << "hotswap: error: trampoline branch-back encoding failed at 0x"
<< utohexstr(T.OriginalOffset) << "\n";
return false;
}
std::memcpy(T.Bytes.data() + T.Bytes.size() - MinInstSize, BrBack.data(),
BrBack.size());
SmallVector<uint8_t> BrFwd = LS.encodeSBranch(T.OriginalOffset, TP);
if (BrFwd.empty()) {
log() << "hotswap: error: trampoline branch-fwd encoding failed at 0x"
<< utohexstr(T.OriginalOffset) << "\n";
return false;
}
std::memcpy(Text + T.OriginalOffset, BrFwd.data(), BrFwd.size());
// Pad the tail of the replaced slot with cached s_nop bytes.
for (uint32_t I = MinInstSize; I < T.OriginalSize; I += MinInstSize)
std::memcpy(Text + T.OriginalOffset + I, LS.SNopBytes.data(),
MinInstSize);
}
return true;
}
/// Fix up DWARF sections of the grown ELF after trampolines have been
/// appended: adds trampoline symbols to the symbol table, shifts
/// .debug_line / .debug_ranges / .debug_info / .debug_frame addresses by
/// the total trampoline footprint, and reports per-section failures via
/// log(). Individual patchDebug* helpers are weak stubs here; concrete
/// implementations land in separate PRs.
static void patchDebugSections(WritableMemoryBuffer &ElfBuf,
ArrayRef<Trampoline> Trampolines,
const ElfView &Elf, size_t GrowthTotal) {
uint8_t *Data = reinterpret_cast<uint8_t *>(ElfBuf.getBufferStart());
size_t Size = ElfBuf.getBufferSize();
if (!addTrampolineSymbols(ElfBuf, Trampolines, Elf.textSize(),
Elf.textSectionIndex()))
log() << "hotswap: error: addTrampolineSymbols failed\n";
patchDebugRanges(Data, Size, Elf.textAddr(), Elf.textSize(), GrowthTotal);
patchDebugInfo(Data, Size, Elf.textAddr(), Elf.textSize(), GrowthTotal);
patchDebugFrame(Data, Size, Elf.textAddr(), Elf.textSize(), GrowthTotal);
if (!patchDebugLine(ElfBuf, Trampolines, Elf.textSize(), Elf.textAddr()))
log() << "hotswap: error: patchDebugLine failed\n";
}
/// Re-open the grown ELF and cross-check that no scratch-patched site
/// reads a VGPR still live at the patch point: builds a fresh ElfView over
/// the output buffer, hands the new .text to verifyPatchCorrectness, and
/// logs a diagnostic if the verifier detects a potential conflict. Runs
/// only when the scratch patch pass produced at least one ScratchPatchInfo
/// record.
static void runScratchVerification(WritableMemoryBuffer &OutBuf,
const LLVMState &LS,
ArrayRef<ScratchPatchInfo> ScratchPatches,
unsigned MaxVgprs) {
// Build a fresh ElfView over the grown buffer to find the new .text.
// WritableMemoryBuffer::getBufferStart() returns char *, so no const_cast
// is needed on the way to ElfView::create's uint8_t * contract.
uint8_t *Data = reinterpret_cast<uint8_t *>(OutBuf.getBufferStart());
Expected<ElfView> ViewOrErr = ElfView::create(Data, OutBuf.getBufferSize());
if (!ViewOrErr) {
consumeError(ViewOrErr.takeError());
return;
}
if (ViewOrErr->textSize() == 0)
return;
if (!verifyPatchCorrectness(ViewOrErr->textData(), ViewOrErr->textSize(), LS,
ScratchPatches, MaxVgprs))
log() << "hotswap: error: post-patch verification detected possible "
<< "scratch conflicts\n";
}
static std::unique_ptr<WritableMemoryBuffer>
copyOutputBuffer(const void *Data, size_t Size, StringRef CopyKind) {
std::unique_ptr<WritableMemoryBuffer> Result =
WritableMemoryBuffer::getNewUninitMemBuffer(Size);
if (!Result) {
log() << "hotswap: error: retargetCodeObject: "
<< "getNewUninitMemBuffer(" << Size
<< ") failed (out of memory) for the " << CopyKind
<< " output copy.\n";
return nullptr;
}
std::memcpy(Result->getBufferStart(), Data, Size);
return Result;
}
// -- retargetCodeObject -------------------------------------------------------
amd_comgr_status_t retargetCodeObject(const void *ElfData, size_t ElfSize,
const TargetIdentifier &TargetIdent,
const Gfx1250RewriteOptions &Options,
std::unique_ptr<MemoryBuffer> &Out) {
// The dispatcher fetches the patch vtable lazily via
// getHotswapPatchVTable() inside applyGfx1250B0toA0Rules; the singleton's
// initializer binds every register*Patch slot on first access, so no
// explicit install step is needed here.
if (!Options.RunB0A0Patches && !Options.RunEntryTrampolines) {
std::unique_ptr<WritableMemoryBuffer> Result =
copyOutputBuffer(ElfData, ElfSize, "no-op");
if (!Result)
return AMD_COMGR_STATUS_ERROR_OUT_OF_RESOURCES;
Out = std::move(Result);
return AMD_COMGR_STATUS_SUCCESS;
}
// Take a working copy so the input is preserved and we have a mutable
// buffer to parse / patch.
std::vector<uint8_t> Buf(static_cast<const uint8_t *>(ElfData),
static_cast<const uint8_t *>(ElfData) + ElfSize);
Expected<ElfView> ViewOrErr = ElfView::create(Buf.data(), Buf.size());
if (!ViewOrErr) {
log() << "hotswap: error: retargetCodeObject: input is not a "
<< "parseable ELF64 (" << toString(ViewOrErr.takeError()) << ").\n";
return AMD_COMGR_STATUS_ERROR_INVALID_ARGUMENT;
}
if (ViewOrErr->textSize() == 0) {
log() << "hotswap: error: retargetCodeObject: input ELF has empty "
<< ".text section; nothing to rewrite.\n";
return AMD_COMGR_STATUS_ERROR_INVALID_ARGUMENT;
}
ElfView &Elf = *ViewOrErr;
LLVMState LS = initLLVM(TargetIdent);
if (!LS.Valid) {
log() << "hotswap: error: retargetCodeObject: initLLVM failed "
<< "for CPU '" << TargetIdent.Processor << "'; aborting rewrite.\n";
return AMD_COMGR_STATUS_ERROR;
}
RewriteConfig Config = makeGfx1250B0A0Config();
uint8_t *Text = Elf.textData();
uint64_t Count = 0;
std::vector<Trampoline> Deferred;
std::vector<ScratchPatchInfo> ScratchPatches;
if (Options.RunB0A0Patches) {
std::vector<InternalDecodedInst> Decoded;
if (!decodeTextSection(Text, Elf.textSize(), LS, Decoded)) {
log() << "hotswap: error: retargetCodeObject: decodeTextSection "
<< "failed on .text (" << Elf.textSize() << " bytes).\n";
return AMD_COMGR_STATUS_ERROR;
}
Count = applyGfx1250B0toA0Rules(Decoded, Text, Elf.textSize(), LS, Deferred,
Elf, ScratchPatches, Config);
log() << "hotswap: applied " << Count << " B0-to-A0 patches\n";
} else {
log() << "hotswap: B0-to-A0 patches disabled for this rewrite\n";
}
std::unique_ptr<WritableMemoryBuffer> Result;
std::vector<Trampoline> Growth = Deferred;
if (!Deferred.empty()) {
if (!fixupTrampolineBranches(Deferred, Text, Elf.textSize(), LS)) {
log() << "hotswap: error: trampoline branch fixup failed; aborting "
"rewrite\n";
return AMD_COMGR_STATUS_ERROR;
}
Growth = Deferred;
}
std::vector<KernelEntryTrampolineFixup> EntryFixups;
if (Options.RunEntryTrampolines) {
std::optional<uint32_t> EntryCount = appendKernelEntryTrampolines(
Elf, LS, Config.MaxSgprs, Growth, EntryFixups);
if (!EntryCount)
return AMD_COMGR_STATUS_ERROR;
Count += *EntryCount;
} else {
log() << "hotswap: kernel-entry trampolines disabled for this rewrite\n";
}
if (!Growth.empty()) {
Result = Elf.growWithTrampolines(Growth, LS.SNopBytes);
if (!Result) {
log() << "hotswap: error: retargetCodeObject: "
<< "ElfView::growWithTrampolines returned null with "
<< Growth.size() << " trampolines queued.\n";
return AMD_COMGR_STATUS_ERROR;
}
size_t GrowthTotal = 0;
for (const Trampoline &T : Growth) {
if (T.Bytes.size() > std::numeric_limits<size_t>::max() - GrowthTotal) {
log() << "hotswap: error: retargetCodeObject: growth byte count "
<< "overflows size_t.\n";
return AMD_COMGR_STATUS_ERROR;
}
GrowthTotal += T.Bytes.size();
}
patchDebugSections(*Result, Deferred, Elf, GrowthTotal);
if (!rewriteKernelEntryDescriptorOffsets(*Result, Elf.textSize(),
EntryFixups))
return AMD_COMGR_STATUS_ERROR;
} else {
Result = copyOutputBuffer(Buf.data(), ElfSize, "patched");
if (!Result)
return AMD_COMGR_STATUS_ERROR_OUT_OF_RESOURCES;
}
if (!ScratchPatches.empty())
runScratchVerification(*Result, LS, ScratchPatches, Config.MaxVgprs);
Out = std::move(Result);
return AMD_COMGR_STATUS_SUCCESS;
}
} // namespace hotswap
} // namespace COMGR