[llvm] [NVPTX] Rewrite kernel signatures in param AS (PR #204192)
Akshay Deodhar via llvm-commits
llvm-commits at lists.llvm.org
Tue Jun 23 17:06:04 PDT 2026
================
@@ -395,108 +268,165 @@ void copyByValParam(Function &F, Argument &Arg) {
Arg.getParamAlign().value_or(DL.getPrefTypeAlign(ByValType)));
Arg.replaceAllUsesWith(AllocA);
- Value *ArgInParamAS = createNVVMInternalAddrspaceWrap(IRB, Arg);
-
- // Be sure to propagate alignment to this load; LLVM doesn't know that NVPTX
- // addrspacecast preserves alignment. Since params are constant, this load
+ // Be sure to propagate alignment to this copy; LLVM doesn't know that NVPTX
+ // addrspacecast preserves alignment. Since params are constant, this copy
// is definitely not volatile.
const auto ArgSize = *AllocA->getAllocationSize(DL);
- IRB.CreateMemCpy(AllocA, AllocA->getAlign(), ArgInParamAS, AllocA->getAlign(),
+ IRB.CreateMemCpy(AllocA, AllocA->getAlign(), &ParamPtr, AllocA->getAlign(),
ArgSize);
}
} // namespace
-static bool argIsProcessed(Argument *Arg) {
- if (Arg->use_empty())
- return true;
-
- // If the argument is already wrapped, it was processed by this pass before.
- if (Arg->hasOneUse())
- if (const auto *II = dyn_cast<IntrinsicInst>(*Arg->user_begin()))
- if (II->getIntrinsicID() == Intrinsic::nvvm_internal_addrspace_wrap)
- return true;
-
- return false;
+// Returns true if F has a byval argument not yet in the param address space.
+// Such arguments are lowered exactly once, so one already in param space means
+// the kernel has already been processed.
+static bool kernelNeedsByValLowering(const Function &F) {
+ return any_of(F.args(), [](const Argument &A) {
+ return A.hasByValAttr() &&
+ A.getType()->getPointerAddressSpace() != ADDRESS_SPACE_ENTRY_PARAM;
+ });
}
-static void lowerKernelByValParam(Argument *Arg, Function &F,
- const bool HasCvtaParam) {
+// Lower the uses of a single kernel byval argument. \p OldArg is the original
+// (generic) argument whose uses are being rewritten; \p NewParamArg is its
+// replacement, natively in the param address space.
+static void lowerKernelByValParam(Argument &OldArg, Argument &NewParamArg,
+ Function &F, const bool HasCvtaParam) {
assert(isKernelFunction(F));
const DataLayout &DL = F.getDataLayout();
IRBuilder<> IRB(&F.getEntryBlock().front());
- if (argIsProcessed(Arg))
+ if (OldArg.use_empty())
return;
// (1) First check the easy case, if were able to trace through all the uses
// and we can convert them all to param AS, then we'll do this.
ArgUseChecker AUC(DL);
- ArgUseChecker::PtrInfo PI = AUC.visitArgPtr(*Arg);
+ ArgUseChecker::PtrInfo PI = AUC.visitArgPtr(OldArg);
const bool ArgUseIsReadOnly = !(PI.isEscaped() || PI.isAborted());
if (ArgUseIsReadOnly && AUC.Conditionals.empty()) {
// Convert all loads and intermediate operations to use parameter AS and
// skip creation of a local copy of the argument.
- SmallVector<Use *, 16> UsesToUpdate(llvm::make_pointer_range(Arg->uses()));
- Value *ArgInParamAS = createNVVMInternalAddrspaceWrap(IRB, *Arg);
+ SmallVector<Use *, 16> UsesToUpdate(make_pointer_range(OldArg.uses()));
for (Use *U : UsesToUpdate)
- convertToParamAS(U, ArgInParamAS);
+ convertToParamAS(U, &NewParamArg);
+ // This path does not replaceAllUsesWith the old argument, so any debug-info
+ // uses would be left dangling and reset to poison when the old function is
+ // erased. Point them at the new param-space argument instead.
+ if (OldArg.isUsedByMetadata()) {
+ SmallVector<DbgVariableRecord *, 4> DbgUsers;
+ findDbgUsers(&OldArg, DbgUsers);
+ for (DbgVariableRecord *DVR : DbgUsers)
+ DVR->replaceVariableLocationOp(&OldArg, &NewParamArg);
+ }
return;
}
// (2) If the argument is grid constant, we get to use the pointer directly.
- if (HasCvtaParam && (ArgUseIsReadOnly || isParamGridConstant(*Arg))) {
- LLVM_DEBUG(dbgs() << "Using non-copy pointer to " << *Arg << "\n");
-
- // Cast argument to param address space. Because the backend will emit the
- // argument already in the param address space, we need to use the noop
- // intrinsic, this had the added benefit of preventing other optimizations
- // from folding away this pair of addrspacecasts.
- Instruction *ArgInParamAS = createNVVMInternalAddrspaceWrap(IRB, *Arg);
+ if (HasCvtaParam && (ArgUseIsReadOnly || isParamGridConstant(OldArg))) {
+ LLVM_DEBUG(dbgs() << "Using non-copy pointer to " << OldArg << "\n");
- // Cast param address to generic address space.
+ // Cast the param-space argument to the generic address space. Because the
+ // argument is natively in param space, this cast only ever goes
+ // param -> generic and lowers to cvta.param; there is no inverse cast for
+ // InferAddressSpaces to fold it away with.
Value *GenericArg = IRB.CreateAddrSpaceCast(
- ArgInParamAS, IRB.getPtrTy(ADDRESS_SPACE_GENERIC),
- Arg->getName() + ".gen");
-
- Arg->replaceAllUsesWith(GenericArg);
+ &NewParamArg, IRB.getPtrTy(ADDRESS_SPACE_GENERIC),
+ OldArg.getName() + ".gen");
- // Do not replace Arg in the cast to param space
- ArgInParamAS->setOperand(0, Arg);
+ OldArg.replaceAllUsesWith(GenericArg);
return;
}
// (3) Otherwise we have to create a copy of the argument in local memory.
- copyByValParam(F, *Arg);
+ copyByValParam(F, OldArg, NewParamArg);
+}
+
+// Rewrite a kernel's signature so that each byval argument is declared directly
+// as a pointer in the param address space, then lower the body to match. This
+// creates a new function, moves the body across, and erases \p F.
+static void rewriteKernelByValSignature(Function &F, const bool HasCvtaParam) {
+ LLVMContext &Ctx = F.getContext();
+ FunctionType *FTy = F.getFunctionType();
+
+ // Build the new signature: byval pointer arguments move to the param address
+ // space; all other arguments are unchanged.
+ SmallVector<Type *> Params(FTy->params());
+ for (const Argument &Arg : F.args())
+ if (Arg.hasByValAttr())
+ Params[Arg.getArgNo()] = PointerType::get(Ctx, ADDRESS_SPACE_ENTRY_PARAM);
+
+ Function *NF = Function::Create(
+ FunctionType::get(FTy->getReturnType(), Params, FTy->isVarArg()),
+ F.getLinkage(), F.getAddressSpace());
+ NF->copyAttributesFrom(&F);
+ NF->setComdat(F.getComdat());
+ F.getParent()->getFunctionList().insert(F.getIterator(), NF);
+
+ // ISel reads the param symbol directly for kernel byval arguments; this is
+ // valid because the signature rewrite above puts them in the param address
+ // space. Mark them readonly: any mutation is redirected to a local copy
+ // below, so the param itself is never written.
+ for (Argument &NewArg : NF->args())
+ if (NewArg.hasByValAttr())
+ NewArg.addAttr(Attribute::ReadOnly);
+
+ // Take over F's name and uses (e.g. @llvm.used, nvvm.annotations metadata),
+ // then move the body across.
+ F.replaceAllUsesWith(NF);
+ NF->takeName(&F);
+ NF->splice(NF->begin(), &F);
+
+ // Remap arguments. Non-byval arguments keep their type and are replaced
+ // directly; byval arguments change address space, so their uses are lowered
+ // to operate on the new param-space argument.
+ for (auto [OldArg, NewArg] : zip_equal(F.args(), NF->args())) {
+ if (OldArg.hasByValAttr())
+ lowerKernelByValParam(OldArg, NewArg, *NF, HasCvtaParam);
----------------
akshayrdeodhar wrote:
Would it be possible to decouple the function rewrite and the lowering? Or would the "rewritten" function be in an inconsistent state?
Original -> rewritten -> lower (use param space ptr / cast / create copy)
https://github.com/llvm/llvm-project/pull/204192
More information about the llvm-commits
mailing list