clang 24.0.0git
CIRGenCUDANV.cpp
Go to the documentation of this file.
1//========- CIRGenCUDANV.cpp - Interface to NVIDIA CUDA Runtime -----=========//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This provides a class for CUDA code generation targeting the NVIDIA CUDA
10// runtime library.
11//
12//===----------------------------------------------------------------------===//
13
14#include "CIRGenCUDARuntime.h"
15#include "CIRGenCXXABI.h"
16#include "CIRGenFunction.h"
17#include "CIRGenModule.h"
18#include "mlir/IR/Operation.h"
20#include "clang/AST/Attrs.inc"
21#include "clang/AST/Decl.h"
24#include "clang/Basic/Cuda.h"
30#include "llvm/Support/Casting.h"
31#include "llvm/Support/MemoryBuffer.h"
32#include "llvm/Support/VirtualFileSystem.h"
33
34using namespace clang;
35using namespace clang::CIRGen;
36
37namespace {
38
39class CIRGenNVCUDARuntime : public CIRGenCUDARuntime {
40protected:
41 StringRef prefix;
42
43 // Map a device stub function to a symbol for identifying kernel in host
44 // code. For CUDA, the symbol for identifying the kernel is the same as the
45 // device stub function. For HIP, they are different.
46 llvm::StringMap<mlir::Operation *> kernelHandles;
47
48 // Map a kernel handle to the kernel stub.
49 llvm::DenseMap<mlir::Operation *, mlir::Operation *> kernelStubs;
50
51 struct VarInfo {
52 cir::GlobalOp var;
53 const VarDecl *d;
54 cir::CUDADeviceVarKind flags;
55 };
56 llvm::SmallVector<VarInfo, 16> deviceVars;
57
58 // Mangle context for device.
59 std::unique_ptr<MangleContext> deviceMC;
60
61private:
62 void emitDeviceStubBodyNew(CIRGenFunction &cgf, cir::FuncOp fn,
63 FunctionArgList &args);
64 void recordDeviceBinary();
65 void transformManagedVars();
66 mlir::Value prepareKernelArgs(CIRGenFunction &cgf, mlir::Location loc,
67 FunctionArgList &args);
68 mlir::Operation *getKernelHandle(cir::FuncOp fn, GlobalDecl gd) override;
69
70 mlir::Operation *getKernelStub(mlir::Operation *handle) override {
71 auto it = kernelStubs.find(handle);
72 assert(it != kernelStubs.end());
73 return it->second;
74 }
75 std::string addPrefixToName(StringRef funcName) const;
76 std::string addUnderscoredPrefixToName(StringRef funcName) const;
77
78public:
79 CIRGenNVCUDARuntime(CIRGenModule &cgm);
80 ~CIRGenNVCUDARuntime();
81
82 void emitDeviceStub(CIRGenFunction &cgf, cir::FuncOp fn,
83 FunctionArgList &args) override;
84
85 void handleVarRegistration(const VarDecl *vd, cir::GlobalOp var) override;
86 void finalizeModule() override;
87 void handleGlobalReplace(cir::GlobalOp oldGV, cir::GlobalOp newGV) override;
88
89 void internalizeDeviceSideVar(const VarDecl *d,
90 cir::GlobalLinkageKind &linkage) override;
91
92 std::string getDeviceSideName(const NamedDecl *nd) override;
93
94 void registerDeviceVar(const VarDecl *vd, cir::GlobalOp &var, bool isExtern,
95 bool isConstant) {
96 // Attach the device var attribute to the GlobalOp
97 auto &builder = cgm.getBuilder();
98 var->setAttr(cir::CUDAVarRegistrationInfoAttr::getMnemonic(),
99 cir::CUDAVarRegistrationInfoAttr::get(
100 builder.getContext(),
101 getDeviceSideName(cast<NamedDecl>(vd)),
102 cir::CUDADeviceVarKind::Variable, isExtern, isConstant,
103 vd->hasAttr<HIPManagedAttr>(),
104 /*surfTexType=*/0));
105 deviceVars.push_back({
106 var,
107 vd,
108 cir::CUDADeviceVarKind::Variable,
109 });
110 }
111
112 void registerDeviceSurf(const VarDecl *vd, cir::GlobalOp &var, bool isExtern,
113 int32_t surfaceType) {
114 auto &builder = cgm.getBuilder();
115
116 var->setAttr(cir::CUDAVarRegistrationInfoAttr::getMnemonic(),
117 cir::CUDAVarRegistrationInfoAttr::get(
118 builder.getContext(),
119 getDeviceSideName(cast<NamedDecl>(vd)),
120 cir::CUDADeviceVarKind::Surface, isExtern,
121 /*isConstant=*/false,
122 /*isManaged=*/false, surfaceType));
123
124 deviceVars.push_back({
125 var,
126 vd,
127 cir::CUDADeviceVarKind::Surface,
128 });
129 }
130
131 void registerDeviceTex(const VarDecl *vd, cir::GlobalOp &var, bool isExtern) {
132 auto &builder = cgm.getBuilder();
133
134 var->setAttr(cir::CUDAVarRegistrationInfoAttr::getMnemonic(),
135 cir::CUDAVarRegistrationInfoAttr::get(
136 builder.getContext(),
137 getDeviceSideName(cast<NamedDecl>(vd)),
138 cir::CUDADeviceVarKind::Texture, isExtern,
139 /*isConstant=*/false,
140 /*isManaged=*/false,
141 /*surfTexType=*/0));
142
143 deviceVars.push_back({
144 var,
145 vd,
146 cir::CUDADeviceVarKind::Texture,
147 });
148 }
149};
150
151} // namespace
152
153std::string CIRGenNVCUDARuntime::addPrefixToName(StringRef funcName) const {
154 return (prefix + funcName).str();
155}
156
157std::string
158CIRGenNVCUDARuntime::addUnderscoredPrefixToName(StringRef funcName) const {
159 return ("__" + prefix + funcName).str();
160}
161
162CIRGenNVCUDARuntime::CIRGenNVCUDARuntime(CIRGenModule &cgm)
163 : CIRGenCUDARuntime(cgm),
164 deviceMC(cgm.getASTContext().cudaNVInitDeviceMC()) {
165 if (cgm.getLangOpts().OffloadViaLLVM)
166 cgm.errorNYI("CIRGenNVCUDARuntime: Offload via LLVM");
167 else if (cgm.getLangOpts().HIP)
168 prefix = "hip";
169 else
170 prefix = "cuda";
171}
172
173mlir::Value CIRGenNVCUDARuntime::prepareKernelArgs(CIRGenFunction &cgf,
174 mlir::Location loc,
175 FunctionArgList &args) {
176 CIRGenBuilderTy &builder = cgm.getBuilder();
177
178 // Build void *args[] and populate with the addresses of kernel arguments.
179 auto voidPtrArrayTy = cir::ArrayType::get(cgm.voidPtrTy, args.size());
180 mlir::Value kernelArgs =
181 builder.createAlloca(loc, cir::PointerType::get(voidPtrArrayTy),
182 "kernel_args", CharUnits::fromQuantity(16));
183
184 mlir::Value kernelArgsDecayed =
185 builder.createCast(cir::CastKind::array_to_ptrdecay, kernelArgs,
186 cir::PointerType::get(cgm.voidPtrTy));
187
188 for (const auto &[i, arg] : llvm::enumerate(args)) {
189 mlir::Value index =
190 builder.getConstInt(loc, llvm::APInt(/*numBits=*/32, i));
191 mlir::Value storePos =
192 builder.createPtrStride(loc, kernelArgsDecayed, index);
193 mlir::Value argAddr = cgf.getAddrOfLocalVar(arg).getPointer();
194 mlir::Value argAsVoid = builder.createBitcast(argAddr, cgm.voidPtrTy);
195
196 builder.CIRBaseBuilderTy::createStore(loc, argAsVoid, storePos);
197 }
198
199 return kernelArgsDecayed;
200}
201
202// CUDA 9.0+ uses new way to launch kernels. Parameters are packed in a local
203// array and kernels are launched using cudaLaunchKernel().
204void CIRGenNVCUDARuntime::emitDeviceStubBodyNew(CIRGenFunction &cgf,
205 cir::FuncOp fn,
206 FunctionArgList &args) {
207
208 // This requires arguments to be sent to kernels in a different way.
209 if (cgm.getLangOpts().OffloadViaLLVM)
210 cgm.errorNYI("CIRGenNVCUDARuntime: Offload via LLVM");
211
212 CIRGenBuilderTy &builder = cgm.getBuilder();
213 mlir::Location loc = fn.getLoc();
214
215 // For [cuda|hip]LaunchKernel, we must add another layer of indirection
216 // to arguments. For example, for function `add(int a, float b)`,
217 // we need to pass it as `void *args[2] = { &a, &b }`.
218 mlir::Value kernelArgs = prepareKernelArgs(cgf, loc, args);
219
220 // Lookup cudaLaunchKernel/hipLaunchKernel function.
221 // HIP kernel launching API name depends on -fgpu-default-stream option. For
222 // the default value 'legacy', it is hipLaunchKernel. For 'per-thread',
223 // it is hipLaunchKernel_spt.
224 // cudaError_t cudaLaunchKernel(const void *func, dim3 gridDim, dim3 blockDim,
225 // void **args, size_t sharedMem,
226 // cudaStream_t stream);
227 // hipError_t hipLaunchKernel[_spt](const void *func, dim3 gridDim,
228 // dim3 blockDim, void **args,
229 // size_t sharedMem, hipStream_t stream);
230 TranslationUnitDecl *tuDecl = cgm.getASTContext().getTranslationUnitDecl();
231 DeclContext *dc = TranslationUnitDecl::castToDeclContext(tuDecl);
232
233 // The default stream is usually stream 0 (the legacy default stream).
234 // For per-thread default stream, we need a different LaunchKernel function.
235 std::string kernelLaunchAPI = "LaunchKernel";
236 if (cgm.getLangOpts().GPUDefaultStream ==
237 LangOptions::GPUDefaultStreamKind::PerThread) {
238 if (cgm.getLangOpts().HIP)
239 kernelLaunchAPI += "_spt";
240 else if (cgm.getLangOpts().CUDA)
241 kernelLaunchAPI += "_ptsz";
242 }
243
244 std::string launchKernelName = addPrefixToName(kernelLaunchAPI);
245 const IdentifierInfo &launchII =
246 cgm.getASTContext().Idents.get(launchKernelName);
247 FunctionDecl *cudaLaunchKernelFD = nullptr;
248 for (NamedDecl *result : dc->lookup(&launchII)) {
249 if (FunctionDecl *fd = dyn_cast<FunctionDecl>(result))
250 cudaLaunchKernelFD = fd;
251 }
252
253 if (cudaLaunchKernelFD == nullptr) {
254 cgm.error(cgf.curFuncDecl->getLocation(),
255 "Can't find declaration for " + launchKernelName);
256 return;
257 }
258
259 // Use this function to retrieve arguments for cudaLaunchKernel:
260 // int __[cuda|hip]PopCallConfiguration(dim3 *gridDim, dim3 *blockDim, size_t
261 // *sharedMem, cudaStream_t *stream)
262 //
263 // Here [cuda|hip]Stream_t, while also being the 6th argument of
264 // [cuda|hip]LaunchKernel, is a pointer to some opaque struct.
265
266 mlir::Type dim3Ty = cgf.getTypes().convertType(
267 cudaLaunchKernelFD->getParamDecl(1)->getType());
268 mlir::Type streamTy = cgf.getTypes().convertType(
269 cudaLaunchKernelFD->getParamDecl(5)->getType());
270
271 mlir::Value gridDim =
272 builder.createAlloca(loc, cir::PointerType::get(dim3Ty), "grid_dim",
274 mlir::Value blockDim =
275 builder.createAlloca(loc, cir::PointerType::get(dim3Ty), "block_dim",
277 mlir::Value sharedMem = builder.createAlloca(
278 loc, cir::PointerType::get(cgm.sizeTy), "shared_mem", cgm.getSizeAlign());
279 mlir::Value stream = builder.createAlloca(
280 loc, cir::PointerType::get(streamTy), "stream", cgm.getPointerAlign());
281
282 cir::FuncOp popConfig = cgm.createRuntimeFunction(
283 cir::FuncType::get({gridDim.getType(), blockDim.getType(),
284 sharedMem.getType(), stream.getType()},
285 cgm.sInt32Ty),
286 addUnderscoredPrefixToName("PopCallConfiguration"));
287 cgf.emitRuntimeCall(loc, popConfig, {gridDim, blockDim, sharedMem, stream});
288
289 // Now emit the call to cudaLaunchKernel
290 // [cuda|hip]Error_t [cuda|hip]LaunchKernel(const void *func, dim3 gridDim,
291 // dim3 blockDim,
292 // void **args, size_t sharedMem,
293 // [cuda|hip]Stream_t stream);
294
295 // We now either pick the function or the stub global for cuda, hip
296 // respectively.
297 mlir::Value kernel = [&]() -> mlir::Value {
298 if (cir::GlobalOp globalOp = llvm::dyn_cast_or_null<cir::GlobalOp>(
299 kernelHandles[fn.getSymName()])) {
300 cir::PointerType kernelTy = cir::PointerType::get(globalOp.getSymType());
301 mlir::Value kernelVal = cir::GetGlobalOp::create(builder, loc, kernelTy,
302 globalOp.getSymName());
303 mlir::Value func = builder.createBitcast(kernelVal, cgm.voidPtrTy);
304 return func;
305 }
306 if (cir::FuncOp funcOp = llvm::dyn_cast_or_null<cir::FuncOp>(
307 kernelHandles[fn.getSymName()])) {
308 cir::PointerType kernelTy =
309 cir::PointerType::get(funcOp.getFunctionType());
310 mlir::Value kernelVal =
311 cir::GetGlobalOp::create(builder, loc, kernelTy, funcOp.getSymName());
312 mlir::Value func = builder.createBitcast(kernelVal, cgm.voidPtrTy);
313 return func;
314 }
315 llvm_unreachable("Expected stub handle to be cir::GlobalOp or FuncOp");
316 }();
317
318 CallArgList launchArgs;
319 launchArgs.add(RValue::get(kernel),
320 cudaLaunchKernelFD->getParamDecl(0)->getType());
321 launchArgs.add(
323 cudaLaunchKernelFD->getParamDecl(1)->getType());
324 launchArgs.add(
326 cudaLaunchKernelFD->getParamDecl(2)->getType());
327 launchArgs.add(RValue::get(kernelArgs),
328 cudaLaunchKernelFD->getParamDecl(3)->getType());
329 launchArgs.add(
330 RValue::get(builder.CIRBaseBuilderTy::createLoad(loc, sharedMem)),
331 cudaLaunchKernelFD->getParamDecl(4)->getType());
332 launchArgs.add(RValue::get(builder.CIRBaseBuilderTy::createLoad(loc, stream)),
333 cudaLaunchKernelFD->getParamDecl(5)->getType());
334
335 mlir::Type launchTy =
336 cgm.getTypes().convertType(cudaLaunchKernelFD->getType());
337 mlir::Operation *cudaKernelLauncherFn = cgm.createRuntimeFunction(
338 cast<cir::FuncType>(launchTy), launchKernelName);
339 const CIRGenFunctionInfo &callInfo =
340 cgm.getTypes().arrangeFunctionDeclaration(cudaLaunchKernelFD);
341 cgf.emitCall(callInfo, CIRGenCallee::forDirect(cudaKernelLauncherFn),
342 ReturnValueSlot(), launchArgs, /*isMustTail=*/false);
343
345 !cgf.getLangOpts().HIP)
346 cgm.errorNYI("MSVC CUDA stub handling");
347}
348
349void CIRGenNVCUDARuntime::emitDeviceStub(CIRGenFunction &cgf, cir::FuncOp fn,
350 FunctionArgList &args) {
351 CIRGenBuilderTy &builder = cgm.getBuilder();
352 StringRef kernelName = cgm.getMangledName(
353 cgf.curGD.getWithKernelReferenceKind(KernelReferenceKind::Kernel));
354 auto kernelNameAttr = cir::CUDAKernelNameAttr::get(
355 builder.getContext(), builder.getStringAttr(kernelName));
356 fn->setDiscardableAttr(cir::CUDAKernelNameAttr::getMnemonic(),
357 kernelNameAttr);
358
359 if (auto globalOp =
360 llvm::dyn_cast<cir::GlobalOp>(kernelHandles[fn.getSymName()])) {
361 mlir::Type fnPtrTy = globalOp.getSymType();
362 auto sym = mlir::FlatSymbolRefAttr::get(fn.getSymNameAttr());
363 auto gv = cir::GlobalViewAttr::get(fnPtrTy, sym);
364
365 globalOp->setAttr("initial_value", gv);
366 globalOp->setAttr("alignment", builder.getI64IntegerAttr(
368
369 // The stub's linkage is only final once it is defined, so the handle
370 // takes it over here.
371 globalOp.setLinkage(fn.getLinkage());
372 mlir::SymbolTable::setSymbolVisibility(
373 globalOp, cgm.getMLIRVisibilityFromCIRLinkage(fn.getLinkage()));
374 cgm.setDSOLocal(static_cast<mlir::Operation *>(globalOp));
375 }
376
377 // CUDA 9.0 changed the way to launch kernels.
379 CudaFeature::CUDA_USES_NEW_LAUNCH) ||
380 (cgm.getLangOpts().HIP && cgm.getLangOpts().HIPUseNewLaunchAPI) ||
381 cgm.getLangOpts().OffloadViaLLVM)
382 emitDeviceStubBodyNew(cgf, fn, args);
383 else
384 cgm.errorNYI("Emit Stub Body Legacy");
385}
386
388 return new CIRGenNVCUDARuntime(cgm);
389}
390
391CIRGenNVCUDARuntime::~CIRGenNVCUDARuntime() {}
392
393mlir::Operation *CIRGenNVCUDARuntime::getKernelHandle(cir::FuncOp fn,
394 GlobalDecl gd) {
395
396 // Check if we already have a kernel handle for this function
397 auto it = kernelHandles.find(fn.getSymName());
398 if (it != kernelHandles.end()) {
399 mlir::Operation *oldHandle = it->second;
400 // Here we know that the fn did not change. Return it
401 if (kernelStubs[oldHandle] == fn)
402 return oldHandle;
403
404 // We've found the function name, but F itself has changed, so we need to
405 // update the references.
406 if (cgm.getLangOpts().HIP) {
407 // For HIP compilation the handle itself does not change, so we only need
408 // to update the Stub value.
409 kernelStubs[oldHandle] = fn;
410 return oldHandle;
411 }
412 // For non-HIP compilation, erase the old Stub and fall-through to creating
413 // new entries.
414 kernelStubs.erase(oldHandle);
415 }
416
417 // If not targeting HIP, store the function itself
418 if (!cgm.getLangOpts().HIP) {
419 kernelHandles[fn.getSymName()] = fn;
420 kernelStubs[fn] = fn;
421 return fn;
422 }
423
424 // Create a new CIR global variable to represent the kernel handle
425 CIRGenBuilderTy &builder = cgm.getBuilder();
426 StringRef globalName = cgm.getMangledName(
427 gd.getWithKernelReferenceKind(KernelReferenceKind::Kernel));
428 cir::PointerType fnPtrTy = builder.getPointerTo(fn.getFunctionType());
429 cir::GlobalOp globalOp =
430 cgm.createGlobalOp(fn.getLoc(), globalName, fnPtrTy, /*isConstant=*/true);
431
432 globalOp->setAttr("alignment", builder.getI64IntegerAttr(
434
435 // Inherit visibility and comdat from the kernel's declaration.
436 auto *fd = cast<FunctionDecl>(gd.getDecl());
437 cgm.setGVProperties(globalOp, fd);
438 FunctionTemplateDecl *ft = fd->getPrimaryTemplate();
439 if (!ft || ft->isThisDeclarationADefinition())
440 cgm.maybeSetTrivialComdat(*fd, globalOp);
441
442 // Store references
443 kernelHandles[fn.getSymName()] = globalOp;
444 kernelStubs[globalOp] = fn;
445
446 return globalOp;
447}
448
449void CIRGenNVCUDARuntime::internalizeDeviceSideVar(
450 const VarDecl *d, cir::GlobalLinkageKind &linkage) {
451 if (cgm.getLangOpts().GPURelocatableDeviceCode)
452 cgm.errorNYI(d->getSourceRange(),
453 "internalizeDeviceSideVar: GPU Relocatable Device Code (RDC)");
454
455 // __shared__ variables are odd. Shadows do get created, but
456 // they are not registered with the CUDA runtime, so they
457 // can't really be used to access their device-side
458 // counterparts. It's not clear yet whether it's nvcc's bug or
459 // a feature, but we've got to do the same for compatibility.
460 if (d->hasAttr<CUDADeviceAttr>() || d->hasAttr<CUDAConstantAttr>() ||
461 d->hasAttr<CUDASharedAttr>() ||
464 linkage = cir::GlobalLinkageKind::InternalLinkage;
465 }
466
468 cgm.errorNYI(d->getSourceRange(),
469 "internalizeDeviceSideVar: CUDA Texture support");
470}
471
472std::string CIRGenNVCUDARuntime::getDeviceSideName(const NamedDecl *nd) {
473 GlobalDecl gd;
474 // nd could be either a kernel or a variable.
475 if (auto *fd = dyn_cast<FunctionDecl>(nd))
476 gd = GlobalDecl(fd, KernelReferenceKind::Kernel);
477 else
478 gd = GlobalDecl(nd);
479 std::string deviceSideName;
480 MangleContext *mc;
481 if (cgm.getLangOpts().CUDAIsDevice)
482 mc = &cgm.getCXXABI().getMangleContext();
483 else
484 mc = deviceMC.get();
485 if (mc->shouldMangleDeclName(nd)) {
486 SmallString<256> buffer;
487 llvm::raw_svector_ostream out(buffer);
488 mc->mangleName(gd, out);
489 deviceSideName = std::string(out.str());
490 } else
491 deviceSideName = std::string(nd->getIdentifier()->getName());
492
493 // Make unique name for device side static file-scope variable for HIP.
494 if (cgm.getASTContext().shouldExternalize(nd) &&
495 cgm.getLangOpts().GPURelocatableDeviceCode) {
496 SmallString<256> buffer;
497 llvm::raw_svector_ostream out(buffer);
498 out << deviceSideName;
500 deviceSideName = std::string(out.str());
501 }
502 return deviceSideName;
503}
504
505void CIRGenNVCUDARuntime::handleVarRegistration(const VarDecl *vd,
506 cir::GlobalOp var) {
507 if (vd->hasAttr<CUDADeviceAttr>() || vd->hasAttr<CUDAConstantAttr>()) {
508 // Shadow variables and their properties must be registered with CUDA
509 // runtime. Skip Extern global variables, which will be registered in
510 // the TU where they are defined.
511 //
512 // Don't register a C++17 inline variable. The local symbol can be
513 // discarded and referencing a discarded local symbol from outside the
514 // comdat (__cuda_register_globals) is disallowed by the ELF spec.
515 //
516 // HIP managed variables need to be always recorded in device and host
517 // compilations for transformation.
518 //
519 // HIP managed variables and variables in CUDADeviceVarODRUsedByHost are
520 // added to llvm.compiler-used, therefore they are safe to be registered.
521 if ((!vd->hasExternalStorage() && !vd->isInline()) ||
522 cgm.getASTContext().CUDADeviceVarODRUsedByHost.contains(vd) ||
523 vd->hasAttr<HIPManagedAttr>()) {
524 registerDeviceVar(vd, var, !vd->hasDefinition(),
525 vd->hasAttr<CUDAConstantAttr>());
526 }
527 } else if (vd->getType()->isCUDADeviceBuiltinSurfaceType()) {
528 // Builtin surfaces and their template arguments are also registered
529 // with CUDA runtime.
532 const TemplateArgumentList &args = td->getTemplateArgs();
533
534 assert(args.size() == 2 &&
535 "Unexpected number of template arguments of CUDA device "
536 "builtin surface type.");
537
538 llvm::APSInt surfaceType = args[1].getAsIntegral();
539
540 if (!vd->hasExternalStorage())
541 registerDeviceSurf(vd, var, !vd->hasDefinition(),
542 surfaceType.getSExtValue());
543
544 } else if (vd->getType()->isCUDADeviceBuiltinTextureType()) {
545 // Builtin textures and their template arguments are also registered
546 // with CUDA runtime.
547 if (!vd->hasExternalStorage())
548 registerDeviceTex(vd, var, !vd->hasDefinition());
549 }
550}
551
552void CIRGenNVCUDARuntime::handleGlobalReplace(cir::GlobalOp oldGV,
553 cir::GlobalOp newGV) {
554 for (auto &info : deviceVars) {
555 if (info.var == oldGV)
556 info.var = newGV;
557 }
558}
559
560/// Whether this translation unit has anything for the CUDA runtime to register.
561/// Like LoweringPrepare, count only kernel stub definitions and device
562/// variables carrying registration metadata, not kernel declarations or
563/// launch-site calls.
564static bool hasEntitiesToRegister(mlir::ModuleOp module) {
565 // A walk, not a scan of the module body, so this stays in agreement with the
566 // recursive walk LoweringPrepare collects them with.
567 return module
568 ->walk([](mlir::Operation *op) {
569 if (auto fn = mlir::dyn_cast<cir::FuncOp>(op);
570 fn && !fn.isDeclaration() &&
571 fn->hasAttr(cir::CUDAKernelNameAttr::getMnemonic()))
572 return mlir::WalkResult::interrupt();
573 if (op->hasAttr(cir::CUDAVarRegistrationInfoAttr::getMnemonic()))
574 return mlir::WalkResult::interrupt();
575 return mlir::WalkResult::advance();
576 })
577 .wasInterrupted();
578}
579
580/// Read the device-side fat binary and record its contents on the module as
581/// `cir.cu.device_binary`, for LoweringPrepare to build the fatbin global from.
582///
583/// This mirrors the read in CGNVCUDARuntime::makeModuleCtorFunction, guards
584/// included: nothing is read in a compilation that would build no module
585/// constructor.
586void CIRGenNVCUDARuntime::recordDeviceBinary() {
587 StringRef binaryName = cgm.getCodeGenOpts().OffloadBinaryToEmbedFile;
588 if (binaryName.empty())
589 return;
590
591 const LangOptions &langOpts = cgm.getLangOpts();
592 if ((langOpts.HIP || !langOpts.GPURelocatableDeviceCode) &&
594 return;
595
596 llvm::vfs::FileSystem &fs = cgm.getASTContext()
600 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> binaryOrErr =
601 fs.getBufferForFile(binaryName, /*FileSize=*/-1,
602 /*RequiresNullTerminator=*/false);
603 if (std::error_code ec = binaryOrErr.getError()) {
604 cgm.getDiags().Report(diag::err_cannot_open_file)
605 << binaryName << ec.message();
606 return;
607 }
608
609 // Typed as the fatbin global's array type so LoweringPrepare can use this
610 // attribute as the initializer as-is: attributes are uniqued on {value, type}
611 // and never freed, so building a second, typed copy there would keep the fat
612 // binary in memory twice.
613 StringRef bytes = binaryOrErr.get()->getBuffer();
614 mlir::MLIRContext &ctx = cgm.getMLIRContext();
615 auto charTy = cir::IntType::get(&ctx, cgm.getTarget().getCharWidth(),
616 /*isSigned=*/false);
617 auto fatbinTy = cir::ArrayType::get(charTy, bytes.size());
618 cgm.getModule()->setAttr(cir::CIRDialect::getCUDADeviceBinaryAttrName(),
619 mlir::StringAttr::get(bytes, fatbinTy));
620}
621
622// Uses of a managed variable load its address from a pointer the runtime fills.
623void CIRGenNVCUDARuntime::transformManagedVars() {
624 CIRGenBuilderTy &builder = cgm.getBuilder();
625 mlir::ModuleOp module = cgm.getModule();
626 for (auto &&info : deviceVars) {
627 cir::GlobalOp var = info.var;
628 if (info.flags != cir::CUDADeviceVarKind::Variable ||
629 !info.d->hasAttr<HIPManagedAttr>())
630 continue;
631
632 cir::PointerType varPtrTy =
633 builder.getPointerTo(var.getSymType(), var.getAddrSpaceAttr());
634 mlir::ptr::MemorySpaceAttrInterface managedAS = cir::toCIRAddressSpaceAttr(
635 cgm.getMLIRContext(),
636 cgm.getLangOpts().CUDAIsDevice ? LangAS::cuda_device : LangAS::Default);
637 std::optional<mlir::SymbolTable::UseRange> uses = var.getSymbolUses(module);
638
639 std::string name = var.getSymName().str();
640 // An empty comdat would follow the rename.
641 std::optional<llvm::StringRef> comdat = var.getComdat();
642 if (comdat && comdat->empty())
643 var.setComdat(llvm::StringRef(name));
644 cgm.eraseGlobalSymbol(var);
645 var.setSymName(name + ".managed");
646 cgm.insertGlobalSymbol(var);
647
648 cir::GlobalOp managedVar =
649 cgm.createGlobalOp(var.getLoc(), name, varPtrTy,
650 /*isConstant=*/false, managedAS);
651 managedVar.setLinkage(var.getLinkage());
652 if (!var.isDeclaration())
653 managedVar.setInitialValueAttr(builder.getConstNullPtrAttr(varPtrTy));
654 managedVar.setDsoLocal(var.getDsoLocal());
655 managedVar.setGlobalVisibility(var.getGlobalVisibility());
656 mlir::SymbolTable::setSymbolVisibility(
657 managedVar, mlir::SymbolTable::getSymbolVisibility(var));
658 managedVar->setAttr(
659 cir::CUDAExternallyInitializedAttr::getMnemonic(),
660 cir::CUDAExternallyInitializedAttr::get(&cgm.getMLIRContext()));
661
662 // The uses now name the pointer global.
663 cir::PointerType managedPtrTy =
664 builder.getPointerTo(varPtrTy, managedVar.getAddrSpaceAttr());
665 for (mlir::SymbolTable::SymbolUse use : *uses) {
666 auto getGlobalOp = dyn_cast<cir::GetGlobalOp>(use.getUser());
667 if (!getGlobalOp) {
668 cgm.errorNYI(info.d->getSourceRange(),
669 "transformManagedVars: non-instruction use");
670 continue;
671 }
672 mlir::Value addr = getGlobalOp.getAddr();
673 addr.setType(managedPtrTy);
674 mlir::OpBuilder::InsertionGuard guard(builder);
675 builder.setInsertionPointAfter(getGlobalOp);
676 mlir::Value ld = builder.createAlignedLoad(
677 getGlobalOp.getLoc(), varPtrTy, addr,
678 CharUnits::fromQuantity(var.getAlignment().value_or(1)));
679 addr.replaceAllUsesExcept(ld, ld.getDefiningOp());
680 }
681
682 // The runtime allocates them even if device code never uses them.
683 if (cgm.getLangOpts().CUDAIsDevice && !var.isDeclaration()) {
684 cgm.addCompilerUsedGlobal(var);
685 cgm.addCompilerUsedGlobal(managedVar);
686 }
687 }
688}
689
690void CIRGenNVCUDARuntime::finalizeModule() {
691 transformManagedVars();
692 if (!cgm.getLangOpts().CUDAIsDevice) {
693 recordDeviceBinary();
694 return;
695 }
696
697 // Mark ODR-used device variables as compiler used to prevent them from being
698 // eliminated by optimization. This is necessary for device variables
699 // ODR-used by host functions. Sema correctly marks them as ODR-used no
700 // matter whether they are ODR-used by device or host functions.
701 //
702 // We do not need to do this if the variable has used attribute since it
703 // has already been added.
704 //
705 // Static device variables have been externalized at this point, therefore
706 // variables with private or internal linkage need not be added.
707 for (auto &&info : deviceVars) {
708 auto kind = info.flags;
709 bool isDecl = info.var.isDeclaration();
710 bool isLocalLinkage = cir::isLocalLinkage(info.var.getLinkage());
711 bool isVarOrSurfaceOrTexture = (kind == cir::CUDADeviceVarKind::Variable ||
712 kind == cir::CUDADeviceVarKind::Surface ||
713 kind == cir::CUDADeviceVarKind::Texture);
714 bool isUsed = info.d->isUsed();
715 bool hasUsedAttr = info.d->hasAttr<UsedAttr>();
716 if (!isDecl && !isLocalLinkage && isVarOrSurfaceOrTexture && isUsed &&
717 !hasUsedAttr) {
718 if (auto globalValue = mlir::dyn_cast<cir::CIRGlobalValueInterface>(
719 info.var.getOperation())) {
720 cgm.addCompilerUsedGlobal(globalValue);
721 }
722 }
723 }
724}
Defines the clang::ASTContext interface.
static StringRef bytes(const std::vector< T, Allocator > &v)
Provides definitions for the various language-specific address spaces.
static bool hasEntitiesToRegister(mlir::ModuleOp module)
Whether this translation unit has anything for the CUDA runtime to register.
Defines the clang::FileManager interface and associated types.
*collection of selector each with an associated kind and an ordered *collection of selectors A selector has a kind
Defines the SourceManager interface.
__CUDA_BUILTIN_VAR __cuda_builtin_blockDim_t blockDim
__CUDA_BUILTIN_VAR __cuda_builtin_gridDim_t gridDim
mlir::TypedAttr getConstNullPtrAttr(mlir::Type t)
mlir::Value createCast(mlir::Location loc, cir::CastKind kind, mlir::Value src, mlir::Type newTy)
cir::PtrStrideOp createPtrStride(mlir::Location loc, mlir::Value base, mlir::Value stride)
cir::PointerType getPointerTo(mlir::Type ty)
mlir::Value createAlloca(mlir::Location loc, cir::PointerType addrType, llvm::StringRef name, mlir::IntegerAttr alignment, mlir::Value dynAllocSize)
mlir::Value createBitcast(mlir::Value src, mlir::Type newTy)
SourceManager & getSourceManager()
Definition ASTContext.h:913
TranslationUnitDecl * getTranslationUnitDecl() const
bool shouldExternalize(const Decl *D) const
Whether a C++ static variable or CUDA/HIP kernel should be externalized.
IdentifierTable & Idents
Definition ASTContext.h:852
llvm::SetVector< const VarDecl * > CUDADeviceVarODRUsedByHost
Keep track of CUDA/HIP device-side variables ODR-used by host code.
const TargetInfo & getTargetInfo() const
Definition ASTContext.h:971
mlir::Value getPointer() const
Definition Address.h:98
cir::LoadOp createAlignedLoad(mlir::Location loc, mlir::Type ty, mlir::Value ptr, llvm::MaybeAlign align)
cir::ConstantOp getConstInt(mlir::Location loc, llvm::APSInt intVal)
clang::MangleContext & getMangleContext()
Gets the mangle context.
static CIRGenCallee forDirect(mlir::Operation *funcPtr, const CIRGenCalleeInfo &abstractInfo=CIRGenCalleeInfo())
Definition CIRGenCall.h:92
clang::GlobalDecl curGD
The GlobalDecl for the current function being compiled or the global variable currently being initial...
CIRGenTypes & getTypes() const
const clang::LangOptions & getLangOpts() const
const clang::Decl * curFuncDecl
Address getAddrOfLocalVar(const clang::VarDecl *vd)
Return the address of a local variable.
mlir::Value emitRuntimeCall(mlir::Location loc, cir::FuncOp callee, llvm::ArrayRef< mlir::Value > args={}, mlir::NamedAttrList attrs={})
RValue emitCall(const CIRGenFunctionInfo &funcInfo, const CIRGenCallee &callee, ReturnValueSlot returnValue, const CallArgList &args, cir::CIRCallOpInterface *callOp, bool isMustTail, SourceRange clangLoc)
This class organizes the cross-function state that is used while generating CIR code.
llvm::StringRef getMangledName(clang::GlobalDecl gd)
DiagnosticBuilder errorNYI(SourceLocation, llvm::StringRef)
Helpers to emit "not yet implemented" error diagnostics.
clang::ASTContext & getASTContext() const
void insertGlobalSymbol(mlir::Operation *op)
CIRGenBuilderTy & getBuilder()
void setDSOLocal(mlir::Operation *op) const
void setGVProperties(mlir::Operation *op, const NamedDecl *d) const
Set visibility, dllimport/dllexport and dso_local.
clang::DiagnosticsEngine & getDiags() const
static mlir::SymbolTable::Visibility getMLIRVisibilityFromCIRLinkage(cir::GlobalLinkageKind GLK)
const clang::TargetInfo & getTarget() const
void error(SourceLocation loc, llvm::StringRef error)
Emit a general error that something can't be done.
cir::FuncOp createRuntimeFunction(cir::FuncType ty, llvm::StringRef name, mlir::NamedAttrList extraAttrs={}, bool isLocal=false, bool assumeConvergent=false)
void eraseGlobalSymbol(mlir::Operation *op)
const clang::CodeGenOptions & getCodeGenOpts() const
const clang::LangOptions & getLangOpts() const
void printPostfixForExternalizedDecl(llvm::raw_ostream &os, const Decl *d)
Print the postfix for externalized static variable or kernels for single source offloading languages ...
cir::GlobalOp createGlobalOp(mlir::Location loc, llvm::StringRef name, mlir::Type t, bool isConstant=false, mlir::ptr::MemorySpaceAttrInterface addrSpace={}, mlir::Operation *insertPoint=nullptr)
mlir::ModuleOp getModule() const
void addCompilerUsedGlobal(cir::CIRGlobalValueInterface gv)
Add a global value to the llvmCompilerUsed list.
mlir::MLIRContext & getMLIRContext()
void maybeSetTrivialComdat(const clang::Decl &d, mlir::Operation *op)
CIRGenCXXABI & getCXXABI() const
const CIRGenFunctionInfo & arrangeFunctionDeclaration(const clang::FunctionDecl *fd)
Free functions are functions that are compatible with an ordinary C function pointer type.
mlir::Type convertType(clang::QualType type)
Convert a Clang type into a mlir::Type.
void add(RValue rvalue, clang::QualType type)
Definition CIRGenCall.h:241
Type for representing both the decl and type of parameters to a function.
Definition CIRGenCall.h:195
static RValue get(mlir::Value v)
Definition CIRGenValue.h:83
static RValue getAggregate(Address addr, bool isVolatile=false)
Convert an Address to an RValue.
QuantityType getQuantity() const
Get the raw integer representation of this quantity.
Definition CharUnits.h:155
static CharUnits fromQuantity(QuantityType Quantity)
Construct a CharUnits quantity from a raw integer type.
Definition CharUnits.h:58
std::string OffloadBinaryToEmbedFile
Name of file passed with -foffload-include-binary option to forward to offloading runtime back-end fo...
lookup_result lookup(DeclarationName Name) const
lookup - Find the declarations (if any) with the given Name in this context.
SourceLocation getLocation() const
Definition DeclBase.h:448
bool hasAttr() const
Definition DeclBase.h:586
DiagnosticBuilder Report(SourceLocation Loc, unsigned DiagID)
Issue the message to the client.
llvm::vfs::FileSystem & getVirtualFileSystem() const
const ParmVarDecl * getParamDecl(unsigned i) const
Definition Decl.h:2944
bool isThisDeclarationADefinition() const
Returns whether this template declaration defines the primary pattern.
GlobalDecl - represents a global declaration.
Definition GlobalDecl.h:60
GlobalDecl getWithKernelReferenceKind(KernelReferenceKind Kind)
Definition GlobalDecl.h:200
const Decl * getDecl() const
Definition GlobalDecl.h:115
StringRef getName() const
Return the actual identifier string.
IdentifierInfo & get(StringRef Name)
Return the identifier token info for the specified named identifier.
GPUDefaultStreamKind GPUDefaultStream
The default stream kind used for HIP kernel launching.
bool shouldMangleDeclName(const NamedDecl *D)
Definition Mangle.cpp:129
void mangleName(GlobalDecl GD, raw_ostream &)
Definition Mangle.cpp:249
IdentifierInfo * getIdentifier() const
Get the identifier that names this declaration, if there is one.
Definition Decl.h:297
FileManager & getFileManager() const
bool isMicrosoft() const
Is this ABI an MSVC-compatible ABI?
TargetCXXABI getCXXABI() const
Get the C++ ABI currently in use.
unsigned getCharWidth() const
Definition TargetInfo.h:528
const llvm::VersionTuple & getSDKVersion() const
unsigned size() const
Retrieve the number of template arguments in this template argument list.
static DeclContext * castToDeclContext(const TranslationUnitDecl *D)
Definition Decl.h:153
CXXRecordDecl * castAsCXXRecordDecl() const
Definition Type.h:36
bool isCUDADeviceBuiltinSurfaceType() const
Check if the type is the CUDA device builtin surface type.
Definition Type.cpp:5652
bool isCUDADeviceBuiltinTextureType() const
Check if the type is the CUDA device builtin texture type.
Definition Type.cpp:5661
QualType getType() const
Definition Decl.h:725
SourceRange getSourceRange() const override LLVM_READONLY
Source range that this declaration covers.
Definition Decl.cpp:2171
bool isInline() const
Whether this variable is (C++1z) inline.
Definition Decl.h:1588
bool hasExternalStorage() const
Returns true if a variable has extern or private_extern storage.
Definition Decl.h:1240
DefinitionKind hasDefinition(ASTContext &) const
Check whether this variable is defined in this translation unit.
Definition Decl.cpp:2377
static bool isLocalLinkage(GlobalLinkageKind linkage)
Definition CIROpsEnums.h:51
mlir::ptr::MemorySpaceAttrInterface toCIRAddressSpaceAttr(mlir::MLIRContext &ctx, clang::LangAS langAS)
Convert an AST LangAS to the appropriate CIR address space attribute interface.
CIRGenCUDARuntime * createNVCUDARuntime(CIRGenModule &cgm)
constexpr Variable var(Literal L)
Returns the variable of L.
Definition CNFFormula.h:64
@ Address
A pointer to a ValueDecl.
Definition Primitives.h:28
void info(bool Verbose, unsigned Level, const char *Fmt, Ts &&...Args)
Prints an indented note to stderr when Verbose is set.
Definition Utils.h:57
RangeSelector name(std::string ID)
Given a node with a "name", (like NamedDecl, DeclRefExpr, CxxCtorInitializer, and TypeLoc) selects th...
Top level wrappers for InstallAPI frontend operations.
if(T->getSizeExpr()) TRY_TO(TraverseStmt(const_cast< Expr * >(T -> getSizeExpr())))
bool CudaFeatureEnabled(llvm::VersionTuple, CudaFeature)
Definition Cuda.cpp:119
U cast(CodeGen::Address addr)
Definition Address.h:327
__builtin_bitreverse32 uint32_t __builtin_riscv_aaddu_u32 __builtin_riscv_asubu_u32 __builtin_elementwise_add_sat __builtin_elementwise_sub_sat int32_t
clang::CharUnits getPointerAlign() const
clang::CharUnits getSizeAlign() const
cir::PointerType voidPtrTy
void* in address space 0