[llvm] [AMDGPU][GlobalISel] Support lowering preloaded kernel arguments (PR #205049)

Matt Arsenault via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 24 03:02:54 PDT 2026


================
@@ -296,6 +297,147 @@ struct AMDGPUOutgoingArgHandler : public AMDGPUOutgoingValueHandler {
     assignValueToAddress(ValVReg, Addr, MemTy, MPO, VA);
   }
 };
+
+struct KernArgPart {
+  CallLowering::ArgInfo Arg;
+  uint64_t Offset;
+  Align Alignment;
+};
+
+// Materialize a preloaded kernarg from its assigned SGPRs into DstReg.
+static void lowerPreloadedKernArg(MachineIRBuilder &B, Register DstReg,
+                                  LLT DstTy, uint64_t Offset, Align Alignment,
+                                  ArrayRef<MCRegister> PreloadRegs) {
+  assert(!PreloadRegs.empty());
+
+  // Kernarg preloads are assigned in 32-bit SGPR units.
+  const LLT S32 = LLT::scalar(32);
+  auto BuildCopyFromPreload = [&B](LLT Ty, Register PhysReg) {
+    B.getMBB().addLiveIn(PhysReg.asMCReg());
+    return B.buildCopy(Ty, PhysReg).getReg(0);
+  };
+  auto getRawBitsTy = [](LLT Ty) { return LLT::scalar(Ty.getSizeInBits()); };
+  auto getPreloadStorageTy = [](LLT Ty) {
+    unsigned RoundedBits = alignTo(Ty.getSizeInBits(), 32);
+    if (!Ty.isPointer() && !Ty.isVector() && RoundedBits == Ty.getSizeInBits())
+      return Ty;
+    return LLT::scalar(RoundedBits);
+  };
+
+  Register Value;
+  LLT ValueTy;
+  const bool IsPackedSubDword = DstTy.getSizeInBits() < 32 && Alignment < 4;
+
+  if (PreloadRegs.size() == 1) {
+    if (IsPackedSubDword) {
+      // Extract sub-dword preloads from their containing 32-bit SGPR word.
+      Register Raw = BuildCopyFromPreload(S32, Register(PreloadRegs[0]));
+      uint64_t OffsetDiff = Offset - alignDown(Offset, 4);
+      Register ShiftAmt = B.buildConstant(S32, OffsetDiff * 8).getReg(0);
+      Value = B.buildLShr(S32, Raw, ShiftAmt).getReg(0);
+      ValueTy = S32;
+    } else {
+      // The physical register may cover more bits than the IR value uses.
+      ValueTy = getPreloadStorageTy(DstTy);
+      Value = BuildCopyFromPreload(ValueTy, Register(PreloadRegs[0]));
+    }
+  } else {
+    assert(!IsPackedSubDword && "packed sub-dword preload should use one SGPR");
+    // Reconstruct wider preloads from separate 32-bit SGPR entries.
+    SmallVector<Register, 4> Regs;
+    Regs.reserve(PreloadRegs.size());
+    for (MCRegister Reg : PreloadRegs) {
+      Regs.push_back(BuildCopyFromPreload(S32, Register(Reg)));
+    }
+
+    ValueTy = LLT::scalar(PreloadRegs.size() * 32);
+    Value = B.buildMergeLikeInstr(ValueTy, Regs).getReg(0);
+  }
+
+  LLT AdjustedTy =
+      (DstTy.isPointer() || DstTy.isVector()) ? getRawBitsTy(DstTy) : DstTy;
+  if (ValueTy != AdjustedTy)
+    Value = B.buildAnyExtOrTrunc(AdjustedTy, Value).getReg(0);
+
+  if (DstTy.isPointer())
+    Value = B.buildIntToPtr(DstTy, Value).getReg(0);
+  else if (DstTy.isVector())
+    Value = B.buildBitcast(DstTy, Value).getReg(0);
+
+  B.buildCopy(DstReg, Value);
+}
+
+// Assign SGPRs for the contiguous sequence of kernel argument parts marked for
+// kernarg preload.
+static void allocatePreloadKernArgSGPRs(CCState &CCInfo,
+                                        ArrayRef<KernArgPart> KernArgParts,
+                                        MachineFunction &MF,
+                                        const GCNSubtarget &Subtarget,
+                                        const SIRegisterInfo &TRI,
+                                        SIMachineFunctionInfo &Info) {
+  const DataLayout &DL = MF.getFunction().getDataLayout();
+  unsigned LastExplicitArgOffset = Subtarget.getExplicitKernelArgOffset();
+  GCNUserSGPRUsageInfo &SGPRInfo = Info.getUserSGPRInfo();
+  bool AlignedForImplicitArgs = false;
+  unsigned ImplicitArgOffset = 0;
+
+  for (auto [KernArgIdx, Part] : enumerate(KernArgParts)) {
+    const Argument &Arg = *cast<Argument>(Part.Arg.OrigValue);
+    // Hardware preloads a contiguous prefix of the kernarg segment. Once a
+    // non-preloaded argument is reached, no later argument can be preloaded.
+    if (!Arg.hasInRegAttr())
+      break;
+
+    unsigned ArgOffset = Part.Offset;
+    unsigned NumAllocSGPRs = alignTo(DL.getTypeAllocSize(Part.Arg.Ty), 4) / 4;
+
+    if (Arg.hasAttribute("amdgpu-hidden-argument")) {
+      // Hidden arguments are laid out after the explicit kernargs, aligned to
+      // the ABI-required implicit argument block boundary.
+      if (!AlignedForImplicitArgs) {
+        ImplicitArgOffset = alignTo(LastExplicitArgOffset,
+                                    Subtarget.getAlignmentForImplicitArgPtr()) -
+                            LastExplicitArgOffset;
+        AlignedForImplicitArgs = true;
+      }
+      ArgOffset += ImplicitArgOffset;
+    }
+
+    if (DL.getTypeStoreSize(Part.Arg.Ty) < 4 && Part.Alignment < 4) {
+      // Packed sub-dword values share the previous 32-bit SGPR. The extract
+      // happens later when materializing the argument value.
+      assert(KernArgIdx >= 1 && "no previous SGPR");
+      Info.getArgInfo().PreloadKernArgs[KernArgIdx].Regs.push_back(
+          Info.getArgInfo().PreloadKernArgs[KernArgIdx - 1].Regs[0]);
+      continue;
+    }
+
+    // Padding between the last preloaded byte and this argument still consumes
+    // preload SGPR slots because the hardware preload sequence is contiguous.
+    unsigned Padding = ArgOffset - LastExplicitArgOffset;
+    unsigned PaddingSGPRs = alignTo(Padding, 4) / 4;
+    // Kernarg preloads share the user-data SGPR budget with other ABI inputs.
+    if (PaddingSGPRs + NumAllocSGPRs > SGPRInfo.getNumFreeUserSGPRs())
+      break;
+
+    const TargetRegisterClass *RC =
+        TRI.getSGPRClassForBitWidth(NumAllocSGPRs * 32);
+    SmallVectorImpl<MCRegister> *PreloadRegs = Info.addPreloadedKernArg(
----------------
arsenm wrote:

addPreloadedKernArg really ought to return an ArrayRef, not a pointer to the underlying storage 

https://github.com/llvm/llvm-project/pull/205049


More information about the llvm-commits mailing list