#include <cassert>
#include <cstddef>
#include <cuda.h>
#include <string>
#include <unordered_map>
#include "Shared/APITypes.h"
#include "Shared/Debug.h"
#include "Shared/Environment.h"
#include "GlobalHandler.h"
#include "OpenMP/OMPT/Callback.h"
#include "PluginInterface.h"
#include "Utils/ELF.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/Frontend/OpenMP/OMPConstants.h"
#include "llvm/Frontend/OpenMP/OMPGridValues.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileOutputBuffer.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Program.h"
using namespace error;
namespace llvm {
namespace omp {
namespace target {
namespace plugin {
struct CUDAKernelTy;
struct CUDADeviceTy;
struct CUDAPluginTy;
#if (defined(CUDA_VERSION) && (CUDA_VERSION < 11000))
typedef void *CUmemGenericAllocationHandle;
typedef void *CUmemAllocationProp;
typedef void *CUmemAccessDesc;
typedef void *CUmemAllocationGranularity_flags;
CUresult cuMemAddressReserve(CUdeviceptr *ptr, size_t size, size_t alignment,
CUdeviceptr addr, unsigned long long flags) {}
CUresult cuMemMap(CUdeviceptr ptr, size_t size, size_t offset,
CUmemGenericAllocationHandle handle,
unsigned long long flags) {}
CUresult cuMemCreate(CUmemGenericAllocationHandle *handle, size_t size,
const CUmemAllocationProp *prop,
unsigned long long flags) {}
CUresult cuMemSetAccess(CUdeviceptr ptr, size_t size,
const CUmemAccessDesc *desc, size_t count) {}
CUresult
cuMemGetAllocationGranularity(size_t *granularity,
const CUmemAllocationProp *prop,
CUmemAllocationGranularity_flags option) {}
#endif
#if (defined(CUDA_VERSION) && (CUDA_VERSION < 11020))
CUresult cuMemAllocAsync(CUdeviceptr *ptr, size_t, CUstream) { *ptr = 0; }
CUresult cuMemFreeAsync(CUdeviceptr dptr, CUstream hStream) {}
#endif
struct CUDADeviceImageTy : public DeviceImageTy {
CUDADeviceImageTy(int32_t ImageId, GenericDeviceTy &Device,
std::unique_ptr<MemoryBuffer> &&TgtImage)
: DeviceImageTy(ImageId, Device, std::move(TgtImage)), Module(nullptr) {}
Error loadModule() {
assert(!Module && "Module already loaded");
CUresult Res = cuModuleLoadDataEx(&Module, getStart(), 0, nullptr, nullptr);
if (auto Err = Plugin::check(Res, "error in cuModuleLoadDataEx: %s"))
return Err;
return Plugin::success();
}
Error unloadModule() {
assert(Module && "Module not loaded");
CUresult Res = cuModuleUnload(Module);
if (auto Err = Plugin::check(Res, "error in cuModuleUnload: %s"))
return Err;
Module = nullptr;
return Plugin::success();
}
CUmodule getModule() const { return Module; }
private:
CUmodule Module;
};
struct CUDAKernelTy : public GenericKernelTy {
CUDAKernelTy(const char *Name) : GenericKernelTy(Name), Func(nullptr) {}
Error initImpl(GenericDeviceTy &GenericDevice,
DeviceImageTy &Image) override {
CUresult Res;
CUDADeviceImageTy &CUDAImage = static_cast<CUDADeviceImageTy &>(Image);
Res = cuModuleGetFunction(&Func, CUDAImage.getModule(), getName());
if (auto Err = Plugin::check(Res, "error in cuModuleGetFunction('%s'): %s",
getName()))
return Err;
if (!Func)
return Plugin::error(ErrorCode::INVALID_BINARY,
"invalid function for kernel %s", getName());
int MaxThreads;
Res = cuFuncGetAttribute(&MaxThreads,
CU_FUNC_ATTRIBUTE_MAX_THREADS_PER_BLOCK, Func);
if (auto Err = Plugin::check(Res, "error in cuFuncGetAttribute: %s"))
return Err;
MaxNumThreads = std::min(MaxNumThreads, (uint32_t)MaxThreads);
return Plugin::success();
}
Error launchImpl(GenericDeviceTy &GenericDevice, uint32_t NumThreads[3],
uint32_t NumBlocks[3], KernelArgsTy &KernelArgs,
KernelLaunchParamsTy LaunchParams,
AsyncInfoWrapperTy &AsyncInfoWrapper) const override;
Expected<uint64_t> maxGroupSize(GenericDeviceTy &,
uint64_t DynamicMemSize) const override {
int MinGridSize;
int MaxBlockSize;
auto Res = cuOccupancyMaxPotentialBlockSize(
&MinGridSize, &MaxBlockSize, Func, NULL, DynamicMemSize, INT_MAX);
if (auto Err = Plugin::check(
Res, "error in cuOccupancyMaxPotentialBlockSize: %s")) {
return Err;
}
return MaxBlockSize;
}
private:
CUfunction Func;
mutable uint32_t MaxDynCGroupMemLimit = 49152;
};
struct CUDAStreamRef final : public GenericDeviceResourceRef {
using HandleTy = CUstream;
CUDAStreamRef() : Stream(nullptr) {}
CUDAStreamRef(HandleTy Stream) : Stream(Stream) {}
Error create(GenericDeviceTy &Device) override {
if (Stream)
return Plugin::error(ErrorCode::INVALID_ARGUMENT,
"creating an existing stream");
CUresult Res = cuStreamCreate(&Stream, CU_STREAM_NON_BLOCKING);
if (auto Err = Plugin::check(Res, "error in cuStreamCreate: %s"))
return Err;
return Plugin::success();
}
Error destroy(GenericDeviceTy &Device) override {
if (!Stream)
return Plugin::error(ErrorCode::INVALID_ARGUMENT,
"destroying an invalid stream");
CUresult Res = cuStreamDestroy(Stream);
if (auto Err = Plugin::check(Res, "error in cuStreamDestroy: %s"))
return Err;
Stream = nullptr;
return Plugin::success();
}
operator HandleTy() const { return Stream; }
private:
HandleTy Stream;
};
struct CUDAEventRef final : public GenericDeviceResourceRef {
using HandleTy = CUevent;
CUDAEventRef() : Event(nullptr) {}
CUDAEventRef(HandleTy Event) : Event(Event) {}
Error create(GenericDeviceTy &Device) override {
if (Event)
return Plugin::error(ErrorCode::INVALID_ARGUMENT,
"creating an existing event");
CUresult Res = cuEventCreate(&Event, CU_EVENT_DEFAULT);
if (auto Err = Plugin::check(Res, "error in cuEventCreate: %s"))
return Err;
return Plugin::success();
}
Error destroy(GenericDeviceTy &Device) override {
if (!Event)
return Plugin::error(ErrorCode::INVALID_ARGUMENT,
"destroying an invalid event");
CUresult Res = cuEventDestroy(Event);
if (auto Err = Plugin::check(Res, "error in cuEventDestroy: %s"))
return Err;
Event = nullptr;
return Plugin::success();
}
operator HandleTy() const { return Event; }
private:
HandleTy Event;
};
struct CUDADeviceTy : public GenericDeviceTy {
CUDADeviceTy(GenericPluginTy &Plugin, int32_t DeviceId, int32_t NumDevices)
: GenericDeviceTy(Plugin, DeviceId, NumDevices, NVPTXGridValues),
CUDAStreamManager(*this), CUDAEventManager(*this) {}
~CUDADeviceTy() {}
Error initImpl(GenericPluginTy &Plugin) override {
CUresult Res = cuDeviceGet(&Device, DeviceId);
if (auto Err = Plugin::check(Res, "error in cuDeviceGet: %s"))
return Err;
CUuuid UUID = {0};
Res = cuDeviceGetUuid(&UUID, Device);
if (auto Err = Plugin::check(Res, "error in cuDeviceGetUuid: %s"))
return Err;
setDeviceUidFromVendorUid(toHex(UUID.bytes, true));
unsigned int FormerPrimaryCtxFlags = 0;
int FormerPrimaryCtxIsActive = 0;
Res = cuDevicePrimaryCtxGetState(Device, &FormerPrimaryCtxFlags,
&FormerPrimaryCtxIsActive);
if (auto Err =
Plugin::check(Res, "error in cuDevicePrimaryCtxGetState: %s"))
return Err;
if (FormerPrimaryCtxIsActive) {
INFO(OMP_INFOTYPE_PLUGIN_KERNEL, DeviceId,
"The primary context is active, no change to its flags\n");
if ((FormerPrimaryCtxFlags & CU_CTX_SCHED_MASK) !=
CU_CTX_SCHED_BLOCKING_SYNC)
INFO(OMP_INFOTYPE_PLUGIN_KERNEL, DeviceId,
"Warning: The current flags are not CU_CTX_SCHED_BLOCKING_SYNC\n");
} else {
INFO(OMP_INFOTYPE_PLUGIN_KERNEL, DeviceId,
"The primary context is inactive, set its flags to "
"CU_CTX_SCHED_BLOCKING_SYNC\n");
Res = cuDevicePrimaryCtxSetFlags(Device, CU_CTX_SCHED_BLOCKING_SYNC);
if (auto Err =
Plugin::check(Res, "error in cuDevicePrimaryCtxSetFlags: %s"))
return Err;
}
Res = cuDevicePrimaryCtxRetain(&Context, Device);
if (auto Err = Plugin::check(Res, "error in cuDevicePrimaryCtxRetain: %s"))
return Err;
if (auto Err = setContext())
return Err;
if (auto Err = CUDAStreamManager.init(OMPX_InitialNumStreams))
return Err;
if (auto Err = CUDAEventManager.init(OMPX_InitialNumEvents))
return Err;
if (auto Err = getDeviceAttr(CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_X,
GridValues.GV_Max_Teams))
return Err;
if (auto Err = getDeviceAttr(CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_X,
GridValues.GV_Max_WG_Size))
return Err;
if (auto Err = getDeviceAttr(CU_DEVICE_ATTRIBUTE_WARP_SIZE,
GridValues.GV_Warp_Size))
return Err;
if (auto Err = getDeviceAttr(CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR,
ComputeCapability.Major))
return Err;
if (auto Err = getDeviceAttr(CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR,
ComputeCapability.Minor))
return Err;
uint32_t NumMuliprocessors = 0;
uint32_t MaxThreadsPerSM = 0;
uint32_t WarpSize = 0;
if (auto Err = getDeviceAttr(CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT,
NumMuliprocessors))
return Err;
if (auto Err =
getDeviceAttr(CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_MULTIPROCESSOR,
MaxThreadsPerSM))
return Err;
if (auto Err = getDeviceAttr(CU_DEVICE_ATTRIBUTE_WARP_SIZE, WarpSize))
return Err;
HardwareParallelism = NumMuliprocessors * (MaxThreadsPerSM / WarpSize);
uint32_t MaxSharedMem;
if (auto Err = getDeviceAttr(
CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK, MaxSharedMem))
return Err;
MaxBlockSharedMemSize = MaxSharedMem;
return Plugin::success();
}
Error unloadBinaryImpl(DeviceImageTy *Image) override {
assert(Context && "Invalid CUDA context");
CUDADeviceImageTy &CUDAImage = static_cast<CUDADeviceImageTy &>(*Image);
if (auto Err = CUDAImage.unloadModule())
return Err;
Plugin.free(Image);
return Plugin::success();
}
Error deinitImpl() override {
if (Context) {
if (auto Err = setContext())
return Err;
}
if (auto Err = CUDAStreamManager.deinit())
return Err;
if (auto Err = CUDAEventManager.deinit())
return Err;
if (Context) {
CUresult Res = cuDevicePrimaryCtxRelease(Device);
if (auto Err =
Plugin::check(Res, "error in cuDevicePrimaryCtxRelease: %s"))
return Err;
}
Context = nullptr;
Device = CU_DEVICE_INVALID;
return Plugin::success();
}
virtual Error callGlobalConstructors(GenericPluginTy &Plugin,
DeviceImageTy &Image) override {
return callGlobalCtorDtorCommon(Plugin, Image, true);
}
virtual Error callGlobalDestructors(GenericPluginTy &Plugin,
DeviceImageTy &Image) override {
return callGlobalCtorDtorCommon(Plugin, Image, false);
}
Expected<std::unique_ptr<MemoryBuffer>>
doJITPostProcessing(std::unique_ptr<MemoryBuffer> MB) const override {
SmallString<128> PTXInputFilePath;
std::error_code EC = sys::fs::createTemporaryFile("nvptx-pre-link-jit", "s",
PTXInputFilePath);
if (EC)
return Plugin::error(ErrorCode::HOST_IO,
"failed to create temporary file for ptxas");
Expected<std::unique_ptr<FileOutputBuffer>> OutputOrErr =
FileOutputBuffer::create(PTXInputFilePath, MB->getBuffer().size());
if (!OutputOrErr)
return OutputOrErr.takeError();
std::unique_ptr<FileOutputBuffer> Output = std::move(*OutputOrErr);
llvm::copy(MB->getBuffer(), Output->getBufferStart());
if (Error E = Output->commit())
return std::move(E);
SmallString<128> PTXOutputFilePath;
EC = sys::fs::createTemporaryFile("nvptx-post-link-jit", "cubin",
PTXOutputFilePath);
if (EC)
return Plugin::error(ErrorCode::HOST_IO,
"failed to create temporary file for ptxas");
const auto ErrorOrPath = sys::findProgramByName("ptxas");
if (!ErrorOrPath)
return Plugin::error(ErrorCode::HOST_TOOL_NOT_FOUND,
"failed to find 'ptxas' on the PATH.");
std::string Arch = getComputeUnitKind();
StringRef Args[] = {*ErrorOrPath,
"-m64",
"-O2",
"--gpu-name",
Arch,
"--output-file",
PTXOutputFilePath,
PTXInputFilePath};
std::string ErrMsg;
if (sys::ExecuteAndWait(*ErrorOrPath, Args, std::nullopt, {}, 0, 0,
&ErrMsg))
return Plugin::error(ErrorCode::ASSEMBLE_FAILURE,
"running 'ptxas' failed: %s\n", ErrMsg.c_str());
auto BufferOrErr = MemoryBuffer::getFileOrSTDIN(PTXOutputFilePath.data());
if (!BufferOrErr)
return Plugin::error(ErrorCode::HOST_IO,
"failed to open temporary file for ptxas");
if (sys::fs::remove(PTXOutputFilePath))
return Plugin::error(ErrorCode::HOST_IO,
"failed to remove temporary file for ptxas");
if (sys::fs::remove(PTXInputFilePath))
return Plugin::error(ErrorCode::HOST_IO,
"failed to remove temporary file for ptxas");
return std::move(*BufferOrErr);
}
Expected<GenericKernelTy &> constructKernel(const char *Name) override {
CUDAKernelTy *CUDAKernel = Plugin.allocate<CUDAKernelTy>();
if (!CUDAKernel)
return Plugin::error(ErrorCode::OUT_OF_RESOURCES,
"failed to allocate memory for CUDA kernel");
new (CUDAKernel) CUDAKernelTy(Name);
return *CUDAKernel;
}
Error setContext() override {
CUresult Res = cuCtxSetCurrent(Context);
return Plugin::check(Res, "error in cuCtxSetCurrent: %s");
}
uint64_t getHardwareParallelism() const override {
return HardwareParallelism;
}
bool shouldSetupRPCServer() const override { return true; }
uint64_t requestedRPCPortCount() const override {
return getHardwareParallelism();
}
Error getStream(AsyncInfoWrapperTy &AsyncInfoWrapper, CUstream &Stream) {
auto WrapperStream =
AsyncInfoWrapper.getOrInitQueue<CUstream>(CUDAStreamManager);
if (!WrapperStream)
return WrapperStream.takeError();
Stream = *WrapperStream;
return Plugin::success();
}
CUcontext getCUDAContext() const { return Context; }
CUdevice getCUDADevice() const { return Device; }
Expected<DeviceImageTy *>
loadBinaryImpl(std::unique_ptr<MemoryBuffer> &&TgtImage,
int32_t ImageId) override {
if (auto Err = setContext())
return std::move(Err);
CUDADeviceImageTy *CUDAImage = Plugin.allocate<CUDADeviceImageTy>();
new (CUDAImage) CUDADeviceImageTy(ImageId, *this, std::move(TgtImage));
if (auto Err = CUDAImage->loadModule())
return std::move(Err);
return CUDAImage;
}
Expected<void *> allocate(size_t Size, void *, TargetAllocTy Kind) override {
if (Size == 0)
return nullptr;
if (auto Err = setContext())
return std::move(Err);
void *MemAlloc = nullptr;
CUdeviceptr DevicePtr;
CUresult Res;
switch (Kind) {
case TARGET_ALLOC_DEFAULT:
case TARGET_ALLOC_DEVICE:
Res = cuMemAlloc(&DevicePtr, Size);
MemAlloc = (void *)DevicePtr;
break;
case TARGET_ALLOC_HOST:
Res = cuMemAllocHost(&MemAlloc, Size);
break;
case TARGET_ALLOC_SHARED:
Res = cuMemAllocManaged(&DevicePtr, Size, CU_MEM_ATTACH_GLOBAL);
MemAlloc = (void *)DevicePtr;
break;
}
if (auto Err = Plugin::check(Res, "error in cuMemAlloc[Host|Managed]: %s"))
return std::move(Err);
return MemAlloc;
}
Error free(void *TgtPtr, TargetAllocTy Kind) override {
if (TgtPtr == nullptr)
return Plugin::success();
if (auto Err = setContext())
return Err;
CUresult Res;
switch (Kind) {
case TARGET_ALLOC_DEFAULT:
case TARGET_ALLOC_DEVICE:
case TARGET_ALLOC_SHARED:
Res = cuMemFree((CUdeviceptr)TgtPtr);
break;
case TARGET_ALLOC_HOST:
Res = cuMemFreeHost(TgtPtr);
break;
}
return Plugin::check(Res, "error in cuMemFree[Host]: %s");
}
Error synchronizeImpl(__tgt_async_info &AsyncInfo,
bool ReleaseQueue) override {
CUstream Stream = reinterpret_cast<CUstream>(AsyncInfo.Queue);
CUresult Res;
Res = cuStreamSynchronize(Stream);
if (ReleaseQueue) {
AsyncInfo.Queue = nullptr;
if (auto Err = CUDAStreamManager.returnResource(Stream))
return Err;
}
return Plugin::check(Res, "error in cuStreamSynchronize: %s");
}
bool supportVAManagement() const override {
#if (defined(CUDA_VERSION) && (CUDA_VERSION >= 11000))
return true;
#else
return false;
#endif
}
Error memoryVAMap(void **Addr, void *VAddr, size_t *RSize) override {
CUdeviceptr DVAddr = reinterpret_cast<CUdeviceptr>(VAddr);
auto IHandle = DeviceMMaps.find(DVAddr);
size_t Size = *RSize;
if (Size == 0)
return Plugin::error(ErrorCode::INVALID_ARGUMENT,
"memory Map Size must be larger than 0");
if (IHandle != DeviceMMaps.end())
return Plugin::error(ErrorCode::INVALID_ARGUMENT,
"address already memory mapped");
CUmemAllocationProp Prop = {};
size_t Granularity = 0;
size_t Free, Total;
CUresult Res = cuMemGetInfo(&Free, &Total);
if (auto Err = Plugin::check(Res, "Error in cuMemGetInfo: %s"))
return Err;
if (Size >= Free) {
*Addr = nullptr;
return Plugin::error(
ErrorCode::OUT_OF_RESOURCES,
"cannot map memory size larger than the available device memory");
}
Prop.type = CU_MEM_ALLOCATION_TYPE_PINNED;
Prop.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
Prop.location.id = DeviceId;
cuMemGetAllocationGranularity(&Granularity, &Prop,
CU_MEM_ALLOC_GRANULARITY_MINIMUM);
if (auto Err =
Plugin::check(Res, "error in cuMemGetAllocationGranularity: %s"))
return Err;
if (Granularity == 0)
return Plugin::error(ErrorCode::INVALID_ARGUMENT,
"wrong device Page size");
Size = utils::roundUp(Size, Granularity);
CUmemGenericAllocationHandle AHandle;
Res = cuMemCreate(&AHandle, Size, &Prop, 0);
if (auto Err = Plugin::check(Res, "error in cuMemCreate: %s"))
return Err;
CUdeviceptr DevPtr = 0;
Res = cuMemAddressReserve(&DevPtr, Size, 0, DVAddr, 0);
if (auto Err = Plugin::check(Res, "error in cuMemAddressReserve: %s"))
return Err;
Res = cuMemMap(DevPtr, Size, 0, AHandle, 0);
if (auto Err = Plugin::check(Res, "error in cuMemMap: %s"))
return Err;
CUmemAccessDesc ADesc = {};
ADesc.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
ADesc.location.id = DeviceId;
ADesc.flags = CU_MEM_ACCESS_FLAGS_PROT_READWRITE;
Res = cuMemSetAccess(DevPtr, Size, &ADesc, 1);
if (auto Err = Plugin::check(Res, "error in cuMemSetAccess: %s"))
return Err;
*Addr = reinterpret_cast<void *>(DevPtr);
*RSize = Size;
DeviceMMaps.insert({DevPtr, AHandle});
return Plugin::success();
}
Error memoryVAUnMap(void *VAddr, size_t Size) override {
CUdeviceptr DVAddr = reinterpret_cast<CUdeviceptr>(VAddr);
auto IHandle = DeviceMMaps.find(DVAddr);
if (IHandle == DeviceMMaps.end()) {
return Plugin::error(ErrorCode::INVALID_ARGUMENT,
"addr is not MemoryMapped");
}
if (IHandle == DeviceMMaps.end())
return Plugin::error(ErrorCode::INVALID_ARGUMENT,
"addr is not MemoryMapped");
CUmemGenericAllocationHandle &AllocHandle = IHandle->second;
CUresult Res = cuMemUnmap(DVAddr, Size);
if (auto Err = Plugin::check(Res, "error in cuMemUnmap: %s"))
return Err;
Res = cuMemRelease(AllocHandle);
if (auto Err = Plugin::check(Res, "error in cuMemRelease: %s"))
return Err;
Res = cuMemAddressFree(DVAddr, Size);
if (auto Err = Plugin::check(Res, "error in cuMemAddressFree: %s"))
return Err;
DeviceMMaps.erase(IHandle);
return Plugin::success();
}
Error queryAsyncImpl(__tgt_async_info &AsyncInfo) override {
CUstream Stream = reinterpret_cast<CUstream>(AsyncInfo.Queue);
CUresult Res = cuStreamQuery(Stream);
if (Res == CUDA_ERROR_NOT_READY)
return Plugin::success();
AsyncInfo.Queue = nullptr;
if (auto Err = CUDAStreamManager.returnResource(Stream))
return Err;
return Plugin::check(Res, "error in cuStreamQuery: %s");
}
Expected<void *> dataLockImpl(void *HstPtr, int64_t Size) override {
return HstPtr;
}
Error dataUnlockImpl(void *HstPtr) override { return Plugin::success(); }
Expected<bool> isPinnedPtrImpl(void *HstPtr, void *&BaseHstPtr,
void *&BaseDevAccessiblePtr,
size_t &BaseSize) const override {
return false;
}
Error dataSubmitImpl(void *TgtPtr, const void *HstPtr, int64_t Size,
AsyncInfoWrapperTy &AsyncInfoWrapper) override {
if (auto Err = setContext())
return Err;
CUstream Stream;
if (auto Err = getStream(AsyncInfoWrapper, Stream))
return Err;
CUresult Res = cuMemcpyHtoDAsync((CUdeviceptr)TgtPtr, HstPtr, Size, Stream);
return Plugin::check(Res, "error in cuMemcpyHtoDAsync: %s");
}
Error dataRetrieveImpl(void *HstPtr, const void *TgtPtr, int64_t Size,
AsyncInfoWrapperTy &AsyncInfoWrapper) override {
if (auto Err = setContext())
return Err;
CUstream Stream;
if (auto Err = getStream(AsyncInfoWrapper, Stream))
return Err;
CUresult Res = cuMemcpyDtoHAsync(HstPtr, (CUdeviceptr)TgtPtr, Size, Stream);
return Plugin::check(Res, "error in cuMemcpyDtoHAsync: %s");
}
Error dataExchangeImpl(const void *SrcPtr, GenericDeviceTy &DstGenericDevice,
void *DstPtr, int64_t Size,
AsyncInfoWrapperTy &AsyncInfoWrapper) override;
Error dataFillImpl(void *TgtPtr, const void *PatternPtr, int64_t PatternSize,
int64_t Size,
AsyncInfoWrapperTy &AsyncInfoWrapper) override {
if (auto Err = setContext())
return Err;
CUstream Stream;
if (auto Err = getStream(AsyncInfoWrapper, Stream))
return Err;
CUresult Res;
size_t N = Size / PatternSize;
if (PatternSize == 1) {
Res = cuMemsetD8Async((CUdeviceptr)TgtPtr,
*(static_cast<const uint8_t *>(PatternPtr)), N,
Stream);
} else if (PatternSize == 2) {
Res = cuMemsetD16Async((CUdeviceptr)TgtPtr,
*(static_cast<const uint16_t *>(PatternPtr)), N,
Stream);
} else if (PatternSize == 4) {
Res = cuMemsetD32Async((CUdeviceptr)TgtPtr,
*(static_cast<const uint32_t *>(PatternPtr)), N,
Stream);
} else {
int64_t MemsetSize = PatternSize % 4u == 0u ? 4u
: PatternSize % 2u == 0u ? 2u
: 1u;
int64_t NumberOfSteps = PatternSize / MemsetSize;
int64_t Pitch = NumberOfSteps * MemsetSize;
int64_t Height = Size / PatternSize;
for (auto Step = 0u; Step < NumberOfSteps; ++Step) {
if (MemsetSize == 4) {
Res = cuMemsetD2D32Async(
(CUdeviceptr)TgtPtr + Step * MemsetSize, Pitch,
*(static_cast<const uint32_t *>(PatternPtr) + Step), 1u, Height,
Stream);
} else if (MemsetSize == 2) {
Res = cuMemsetD2D16Async(
(CUdeviceptr)TgtPtr + Step * MemsetSize, Pitch,
*(static_cast<const uint16_t *>(PatternPtr) + Step), 1u, Height,
Stream);
} else {
Res = cuMemsetD2D8Async(
(CUdeviceptr)TgtPtr + Step * MemsetSize, Pitch,
*(static_cast<const uint8_t *>(PatternPtr) + Step), 1u, Height,
Stream);
}
}
}
return Plugin::check(Res, "error in cuMemset: %s");
}
Error initAsyncInfoImpl(AsyncInfoWrapperTy &AsyncInfoWrapper) override {
if (auto Err = setContext())
return Err;
CUstream Stream;
if (auto Err = getStream(AsyncInfoWrapper, Stream))
return Err;
return Plugin::success();
}
Error dataFence(__tgt_async_info *Async) override {
return Plugin::success();
}
interop_spec_t selectInteropPreference(int32_t InteropType,
int32_t NumPrefers,
interop_spec_t *Prefers) override {
return interop_spec_t{tgt_fr_cuda, {true, 0}, 0};
}
Expected<omp_interop_val_t *>
createInterop(int32_t InteropType, interop_spec_t &InteropSpec) override {
auto *Ret = new omp_interop_val_t(
DeviceId, static_cast<kmp_interop_type_t>(InteropType));
Ret->fr_id = tgt_fr_cuda;
Ret->vendor_id = omp_vendor_nvidia;
if (InteropType == kmp_interop_type_target ||
InteropType == kmp_interop_type_targetsync) {
Ret->device_info.Platform = nullptr;
Ret->device_info.Device = reinterpret_cast<void *>(Device);
Ret->device_info.Context = Context;
}
if (InteropType == kmp_interop_type_targetsync) {
Ret->async_info = new __tgt_async_info();
if (auto Err = setContext())
return Err;
CUstream Stream;
if (auto Err = CUDAStreamManager.getResource(Stream))
return Err;
Ret->async_info->Queue = Stream;
}
return Ret;
}
Error releaseInterop(omp_interop_val_t *Interop) override {
if (!Interop)
return Plugin::success();
if (Interop->async_info)
delete Interop->async_info;
delete Interop;
return Plugin::success();
}
Error enqueueHostCallImpl(void (*Callback)(void *), void *UserData,
AsyncInfoWrapperTy &AsyncInfo) override {
if (auto Err = setContext())
return Err;
CUstream Stream;
if (auto Err = getStream(AsyncInfo, Stream))
return Err;
CUresult Res = cuLaunchHostFunc(Stream, Callback, UserData);
return Plugin::check(Res, "error in cuStreamLaunchHostFunc: %s");
};
Error createEventImpl(void **EventPtrStorage) override {
CUevent *Event = reinterpret_cast<CUevent *>(EventPtrStorage);
return CUDAEventManager.getResource(*Event);
}
Error destroyEventImpl(void *EventPtr) override {
CUevent Event = reinterpret_cast<CUevent>(EventPtr);
return CUDAEventManager.returnResource(Event);
}
Error recordEventImpl(void *EventPtr,
AsyncInfoWrapperTy &AsyncInfoWrapper) override {
CUevent Event = reinterpret_cast<CUevent>(EventPtr);
CUstream Stream;
if (auto Err = getStream(AsyncInfoWrapper, Stream))
return Err;
CUresult Res = cuEventRecord(Event, Stream);
return Plugin::check(Res, "error in cuEventRecord: %s");
}
Error waitEventImpl(void *EventPtr,
AsyncInfoWrapperTy &AsyncInfoWrapper) override {
CUevent Event = reinterpret_cast<CUevent>(EventPtr);
CUstream Stream;
if (auto Err = getStream(AsyncInfoWrapper, Stream))
return Err;
CUresult Res = cuStreamWaitEvent(Stream, Event, 0);
return Plugin::check(Res, "error in cuStreamWaitEvent: %s");
}
Expected<bool> hasPendingWorkImpl(AsyncInfoWrapperTy &AsyncInfo) override {
CUstream Stream;
if (auto Err = getStream(AsyncInfo, Stream))
return Err;
CUresult Ret = cuStreamQuery(Stream);
if (Ret == CUDA_SUCCESS)
return false;
if (Ret == CUDA_ERROR_NOT_READY)
return true;
return Plugin::check(Ret, "error in cuStreamQuery: %s");
}
Expected<bool> isEventCompleteImpl(void *EventPtr,
AsyncInfoWrapperTy &) override {
CUevent Event = reinterpret_cast<CUevent>(EventPtr);
CUresult Ret = cuEventQuery(Event);
if (Ret == CUDA_SUCCESS)
return true;
if (Ret == CUDA_ERROR_NOT_READY)
return false;
return Plugin::check(Ret, "error in cuEventQuery: %s");
}
Error syncEventImpl(void *EventPtr) override {
CUevent Event = reinterpret_cast<CUevent>(EventPtr);
CUresult Res = cuEventSynchronize(Event);
return Plugin::check(Res, "error in cuEventSynchronize: %s");
}
Expected<InfoTreeNode> obtainInfoImpl() override {
char TmpChar[1000];
const char *TmpCharPtr;
size_t TmpSt;
int TmpInt;
InfoTreeNode Info;
CUresult Res = cuDriverGetVersion(&TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("CUDA Driver Version", std::to_string(TmpInt), "",
DeviceInfo::DRIVER_VERSION);
Info.add("CUDA OpenMP Device Number", DeviceId);
Res = cuDeviceGetName(TmpChar, 1000, Device);
if (Res == CUDA_SUCCESS) {
Info.add("Device Name", TmpChar, "", DeviceInfo::NAME);
Info.add("Product Name", TmpChar, "", DeviceInfo::PRODUCT_NAME);
}
Info.add("Vendor Name", "NVIDIA", "", DeviceInfo::VENDOR);
Info.add("Vendor ID", uint64_t{4318}, "", DeviceInfo::VENDOR_ID);
Info.add("Memory Address Size", std::numeric_limits<CUdeviceptr>::digits,
"bits", DeviceInfo::ADDRESS_BITS);
Res = cuDeviceTotalMem(&TmpSt, Device);
if (Res == CUDA_SUCCESS)
Info.add("Global Memory Size", TmpSt, "bytes",
DeviceInfo::GLOBAL_MEM_SIZE);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Number of Multiprocessors", TmpInt, "",
DeviceInfo::NUM_COMPUTE_UNITS);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_GPU_OVERLAP, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Concurrent Copy and Execution", (bool)TmpInt);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_TOTAL_CONSTANT_MEMORY, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Total Constant Memory", TmpInt, "bytes");
Info.add("Max Shared Memory per Block", MaxBlockSharedMemSize, "bytes",
DeviceInfo::WORK_GROUP_LOCAL_MEM_SIZE);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_BLOCK, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Registers per Block", TmpInt);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_WARP_SIZE, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Warp Size", TmpInt);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_BLOCK, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Maximum Threads per Block", TmpInt, "",
DeviceInfo::MAX_WORK_GROUP_SIZE);
auto &MaxBlock = *Info.add("Maximum Block Dimensions", std::monostate{}, "",
DeviceInfo::MAX_WORK_GROUP_SIZE_PER_DIMENSION);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_X, TmpInt);
if (Res == CUDA_SUCCESS)
MaxBlock.add("x", TmpInt);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_Y, TmpInt);
if (Res == CUDA_SUCCESS)
MaxBlock.add("y", TmpInt);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_Z, TmpInt);
if (Res == CUDA_SUCCESS)
MaxBlock.add("z", TmpInt);
Info.add("Maximum Grid Size", std::numeric_limits<uint32_t>::max(), "",
DeviceInfo::MAX_WORK_SIZE);
auto &MaxGrid = *Info.add("Maximum Grid Dimensions", std::monostate{}, "",
DeviceInfo::MAX_WORK_SIZE_PER_DIMENSION);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_X, TmpInt);
if (Res == CUDA_SUCCESS)
MaxGrid.add("x", TmpInt);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_Y, TmpInt);
if (Res == CUDA_SUCCESS)
MaxGrid.add("y", TmpInt);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_Z, TmpInt);
if (Res == CUDA_SUCCESS)
MaxGrid.add("z", TmpInt);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_MAX_PITCH, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Maximum Memory Pitch", TmpInt, "bytes");
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_TEXTURE_ALIGNMENT, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Texture Alignment", TmpInt, "bytes");
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_CLOCK_RATE, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Clock Rate", TmpInt / 1000, "MHz",
DeviceInfo::MAX_CLOCK_FREQUENCY);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_KERNEL_EXEC_TIMEOUT, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Execution Timeout", (bool)TmpInt);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_INTEGRATED, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Integrated Device", (bool)TmpInt);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_CAN_MAP_HOST_MEMORY, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Can Map Host Memory", (bool)TmpInt);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_COMPUTE_MODE, TmpInt);
if (Res == CUDA_SUCCESS) {
if (TmpInt == CU_COMPUTEMODE_DEFAULT)
TmpCharPtr = "Default";
else if (TmpInt == CU_COMPUTEMODE_PROHIBITED)
TmpCharPtr = "Prohibited";
else if (TmpInt == CU_COMPUTEMODE_EXCLUSIVE_PROCESS)
TmpCharPtr = "Exclusive process";
else
TmpCharPtr = "Unknown";
Info.add("Compute Mode", TmpCharPtr);
}
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_CONCURRENT_KERNELS, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Concurrent Kernels", (bool)TmpInt);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_ECC_ENABLED, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("ECC Enabled", (bool)TmpInt);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_MEMORY_CLOCK_RATE, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Memory Clock Rate", TmpInt / 1000, "MHz",
DeviceInfo::MEMORY_CLOCK_RATE);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_GLOBAL_MEMORY_BUS_WIDTH, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Memory Bus Width", TmpInt, "bits");
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_L2_CACHE_SIZE, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("L2 Cache Size", TmpInt, "bytes");
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_MULTIPROCESSOR,
TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Max Threads Per SMP", TmpInt);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_ASYNC_ENGINE_COUNT, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Async Engines", TmpInt);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_UNIFIED_ADDRESSING, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Unified Addressing", (bool)TmpInt);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_MANAGED_MEMORY, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Managed Memory", (bool)TmpInt);
Res =
getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_CONCURRENT_MANAGED_ACCESS, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Concurrent Managed Memory", (bool)TmpInt);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_COMPUTE_PREEMPTION_SUPPORTED,
TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Preemption Supported", (bool)TmpInt);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_COOPERATIVE_LAUNCH, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Cooperative Launch", (bool)TmpInt);
Res = getDeviceAttrRaw(CU_DEVICE_ATTRIBUTE_MULTI_GPU_BOARD, TmpInt);
if (Res == CUDA_SUCCESS)
Info.add("Multi-Device Boars", (bool)TmpInt);
Info.add("Compute Capabilities", ComputeCapability.str());
return Info;
}
Error getDeviceStackSize(uint64_t &Value) override {
return getCtxLimit(CU_LIMIT_STACK_SIZE, Value);
}
Error setDeviceStackSize(uint64_t Value) override {
return setCtxLimit(CU_LIMIT_STACK_SIZE, Value);
}
bool hasDeviceHeapSize() override { return true; }
Error getDeviceHeapSize(uint64_t &Value) override {
return getCtxLimit(CU_LIMIT_MALLOC_HEAP_SIZE, Value);
}
Error setDeviceHeapSize(uint64_t Value) override {
return setCtxLimit(CU_LIMIT_MALLOC_HEAP_SIZE, Value);
}
Error getDeviceMemorySize(uint64_t &Value) override {
CUresult Res = cuDeviceTotalMem(&Value, Device);
return Plugin::check(Res, "error in getDeviceMemorySize %s");
}
Error setCtxLimit(CUlimit Kind, uint64_t Value) {
CUresult Res = cuCtxSetLimit(Kind, Value);
return Plugin::check(Res, "error in cuCtxSetLimit: %s");
}
Error getCtxLimit(CUlimit Kind, uint64_t &Value) {
CUresult Res = cuCtxGetLimit(&Value, Kind);
return Plugin::check(Res, "error in cuCtxGetLimit: %s");
}
Error getDeviceAttr(uint32_t Kind, uint32_t &Value) {
CUresult Res =
cuDeviceGetAttribute((int *)&Value, (CUdevice_attribute)Kind, Device);
return Plugin::check(Res, "error in cuDeviceGetAttribute: %s");
}
CUresult getDeviceAttrRaw(uint32_t Kind, int &Value) {
return cuDeviceGetAttribute(&Value, (CUdevice_attribute)Kind, Device);
}
std::string getComputeUnitKind() const override {
return ComputeCapability.str();
}
uint64_t getClockFrequency() const override { return 1000000000; }
private:
using CUDAStreamManagerTy = GenericDeviceResourceManagerTy<CUDAStreamRef>;
using CUDAEventManagerTy = GenericDeviceResourceManagerTy<CUDAEventRef>;
Error callGlobalCtorDtorCommon(GenericPluginTy &Plugin, DeviceImageTy &Image,
bool IsCtor) {
const char *KernelName = IsCtor ? "nvptx$device$init" : "nvptx$device$fini";
GenericGlobalHandlerTy &Handler = Plugin.getGlobalHandler();
if (!Handler.isSymbolInImage(*this, Image, KernelName))
return Plugin::success();
auto ELFObjOrErr = Handler.getELFObjectFile(Image);
if (!ELFObjOrErr)
return ELFObjOrErr.takeError();
SmallVector<std::pair<StringRef, uint16_t>> Funcs;
for (ELFSymbolRef Sym : (*ELFObjOrErr)->symbols()) {
auto NameOrErr = Sym.getName();
if (!NameOrErr)
return NameOrErr.takeError();
if (!NameOrErr->starts_with(IsCtor ? "__init_array_object_"
: "__fini_array_object_"))
continue;
uint16_t Priority;
if (NameOrErr->rsplit('_').second.getAsInteger(10, Priority))
return Plugin::error(ErrorCode::INVALID_BINARY,
"invalid priority for constructor or destructor");
Funcs.emplace_back(*NameOrErr, Priority);
}
llvm::sort(Funcs, [=](auto X, auto Y) { return X.second < Y.second; });
auto BufferOrErr =
allocate(Funcs.size() * sizeof(void *), nullptr, TARGET_ALLOC_DEVICE);
if (!BufferOrErr)
return BufferOrErr.takeError();
void *Buffer = *BufferOrErr;
if (!Buffer)
return Plugin::error(ErrorCode::OUT_OF_RESOURCES,
"failed to allocate memory for global buffer");
auto *GlobalPtrStart = reinterpret_cast<uintptr_t *>(Buffer);
auto *GlobalPtrStop = reinterpret_cast<uintptr_t *>(Buffer) + Funcs.size();
SmallVector<void *> FunctionPtrs(Funcs.size());
std::size_t Idx = 0;
for (auto [Name, Priority] : Funcs) {
GlobalTy FunctionAddr(Name.str(), sizeof(void *), &FunctionPtrs[Idx++]);
if (auto Err = Handler.readGlobalFromDevice(*this, Image, FunctionAddr))
return Err;
}
if (auto Err = dataSubmit(GlobalPtrStart, FunctionPtrs.data(),
FunctionPtrs.size() * sizeof(void *), nullptr))
return Err;
GlobalTy StartGlobal(IsCtor ? "__init_array_start" : "__fini_array_start",
sizeof(void *), &GlobalPtrStart);
if (auto Err = Handler.writeGlobalToDevice(*this, Image, StartGlobal))
return Err;
GlobalTy StopGlobal(IsCtor ? "__init_array_end" : "__fini_array_end",
sizeof(void *), &GlobalPtrStop);
if (auto Err = Handler.writeGlobalToDevice(*this, Image, StopGlobal))
return Err;
CUDAKernelTy CUDAKernel(KernelName);
if (auto Err = CUDAKernel.init(*this, Image))
return Err;
AsyncInfoWrapperTy AsyncInfoWrapper(*this, nullptr);
KernelArgsTy KernelArgs = {};
uint32_t NumBlocksAndThreads[3] = {1u, 1u, 1u};
if (auto Err = CUDAKernel.launchImpl(
*this, NumBlocksAndThreads, NumBlocksAndThreads, KernelArgs,
KernelLaunchParamsTy{}, AsyncInfoWrapper))
return Err;
Error Err = Plugin::success();
AsyncInfoWrapper.finalize(Err);
if (Err)
return Err;
return free(Buffer, TARGET_ALLOC_DEVICE);
}
CUDAStreamManagerTy CUDAStreamManager;
CUDAEventManagerTy CUDAEventManager;
CUcontext Context = nullptr;
CUdevice Device = CU_DEVICE_INVALID;
std::unordered_map<CUdeviceptr, CUmemGenericAllocationHandle> DeviceMMaps;
struct ComputeCapabilityTy {
uint32_t Major;
uint32_t Minor;
std::string str() const {
return "sm_" + std::to_string(Major * 10 + Minor);
}
} ComputeCapability;
uint32_t HardwareParallelism = 0;
};
Error CUDAKernelTy::launchImpl(GenericDeviceTy &GenericDevice,
uint32_t NumThreads[3], uint32_t NumBlocks[3],
KernelArgsTy &KernelArgs,
KernelLaunchParamsTy LaunchParams,
AsyncInfoWrapperTy &AsyncInfoWrapper) const {
CUDADeviceTy &CUDADevice = static_cast<CUDADeviceTy &>(GenericDevice);
CUstream Stream;
if (auto Err = CUDADevice.getStream(AsyncInfoWrapper, Stream))
return Err;
uint32_t MaxDynCGroupMem =
std::max(KernelArgs.DynCGroupMem, GenericDevice.getDynamicMemorySize());
void *Config[] = {CU_LAUNCH_PARAM_BUFFER_POINTER, LaunchParams.Data,
CU_LAUNCH_PARAM_BUFFER_SIZE,
reinterpret_cast<void *>(&LaunchParams.Size),
CU_LAUNCH_PARAM_END};
if (GenericDevice.getRPCServer())
GenericDevice.Plugin.getRPCServer().Thread->notify();
if (MaxDynCGroupMem >= MaxDynCGroupMemLimit) {
CUresult AttrResult = cuFuncSetAttribute(
Func, CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, MaxDynCGroupMem);
if (auto Err = Plugin::check(
AttrResult,
"error in cuFuncSetAttribute while setting the memory limits: %s"))
return Err;
MaxDynCGroupMemLimit = MaxDynCGroupMem;
}
CUresult Res = cuLaunchKernel(Func, NumBlocks[0], NumBlocks[1], NumBlocks[2],
NumThreads[0], NumThreads[1], NumThreads[2],
MaxDynCGroupMem, Stream, nullptr, Config);
if (GenericDevice.getRPCServer())
cuLaunchHostFunc(
Stream,
[](void *Data) {
GenericPluginTy &Plugin = *reinterpret_cast<GenericPluginTy *>(Data);
Plugin.getRPCServer().Thread->finish();
},
&GenericDevice.Plugin);
return Plugin::check(Res, "error in cuLaunchKernel for '%s': %s", getName());
}
class CUDAGlobalHandlerTy final : public GenericGlobalHandlerTy {
public:
Error getGlobalMetadataFromDevice(GenericDeviceTy &Device,
DeviceImageTy &Image,
GlobalTy &DeviceGlobal) override {
CUDADeviceImageTy &CUDAImage = static_cast<CUDADeviceImageTy &>(Image);
const char *GlobalName = DeviceGlobal.getName().data();
size_t CUSize;
CUdeviceptr CUPtr;
CUresult Res =
cuModuleGetGlobal(&CUPtr, &CUSize, CUDAImage.getModule(), GlobalName);
if (auto Err = Plugin::check(Res, "error in cuModuleGetGlobal for '%s': %s",
GlobalName))
return Err;
if (DeviceGlobal.getSize() && CUSize != DeviceGlobal.getSize())
return Plugin::error(
ErrorCode::INVALID_BINARY,
"failed to load global '%s' due to size mismatch (%zu != %zu)",
GlobalName, CUSize, (size_t)DeviceGlobal.getSize());
DeviceGlobal.setPtr(reinterpret_cast<void *>(CUPtr));
DeviceGlobal.setSize(CUSize);
return Plugin::success();
}
};
struct CUDAPluginTy final : public GenericPluginTy {
CUDAPluginTy() : GenericPluginTy(getTripleArch()) {}
CUDAPluginTy(const CUDAPluginTy &) = delete;
CUDAPluginTy(CUDAPluginTy &&) = delete;
Expected<int32_t> initImpl() override {
CUresult Res = cuInit(0);
if (Res == CUDA_ERROR_INVALID_HANDLE) {
DP("Failed to load CUDA shared library\n");
return 0;
}
if (Res == CUDA_ERROR_NO_DEVICE) {
DP("There are no devices supporting CUDA.\n");
return 0;
}
if (auto Err = Plugin::check(Res, "error in cuInit: %s"))
return std::move(Err);
int NumDevices;
Res = cuDeviceGetCount(&NumDevices);
if (auto Err = Plugin::check(Res, "error in cuDeviceGetCount: %s"))
return std::move(Err);
if (NumDevices == 0)
DP("There are no devices supporting CUDA.\n");
return NumDevices;
}
Error deinitImpl() override { return Plugin::success(); }
GenericDeviceTy *createDevice(GenericPluginTy &Plugin, int32_t DeviceId,
int32_t NumDevices) override {
return new CUDADeviceTy(Plugin, DeviceId, NumDevices);
}
GenericGlobalHandlerTy *createGlobalHandler() override {
return new CUDAGlobalHandlerTy();
}
uint16_t getMagicElfBits() const override { return ELF::EM_CUDA; }
Triple::ArchType getTripleArch() const override {
return Triple::nvptx64;
}
const char *getName() const override { return GETNAME(TARGET_NAME); }
Expected<bool> isELFCompatible(uint32_t DeviceId,
StringRef Image) const override {
auto ElfOrErr =
ELF64LEObjectFile::create(MemoryBufferRef(Image, ""),
false);
if (!ElfOrErr)
return ElfOrErr.takeError();
const auto Header = ElfOrErr->getELFFile().getHeader();
unsigned SM =
Header.e_ident[ELF::EI_ABIVERSION] == ELF::ELFABIVERSION_CUDA_V1
? Header.e_flags & ELF::EF_CUDA_SM
: (Header.e_flags & ELF::EF_CUDA_SM_MASK) >> ELF::EF_CUDA_SM_OFFSET;
CUdevice Device;
CUresult Res = cuDeviceGet(&Device, DeviceId);
if (auto Err = Plugin::check(Res, "error in cuDeviceGet: %s"))
return std::move(Err);
int32_t Major, Minor;
Res = cuDeviceGetAttribute(
&Major, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, Device);
if (auto Err = Plugin::check(Res, "error in cuDeviceGetAttribute: %s"))
return std::move(Err);
Res = cuDeviceGetAttribute(
&Minor, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR, Device);
if (auto Err = Plugin::check(Res, "error in cuDeviceGetAttribute: %s"))
return std::move(Err);
int32_t ImageMajor = SM / 10;
int32_t ImageMinor = SM % 10;
return Major == ImageMajor && Minor >= ImageMinor;
}
};
Error CUDADeviceTy::dataExchangeImpl(const void *SrcPtr,
GenericDeviceTy &DstGenericDevice,
void *DstPtr, int64_t Size,
AsyncInfoWrapperTy &AsyncInfoWrapper) {
if (auto Err = setContext())
return Err;
CUDADeviceTy &DstDevice = static_cast<CUDADeviceTy &>(DstGenericDevice);
CUresult Res;
int32_t DstDeviceId = DstDevice.DeviceId;
CUdeviceptr CUSrcPtr = (CUdeviceptr)SrcPtr;
CUdeviceptr CUDstPtr = (CUdeviceptr)DstPtr;
int CanAccessPeer = 0;
if (DeviceId != DstDeviceId) {
std::lock_guard<std::mutex> Lock(PeerAccessesLock);
switch (PeerAccesses[DstDeviceId]) {
case PeerAccessState::AVAILABLE:
CanAccessPeer = 1;
break;
case PeerAccessState::UNAVAILABLE:
CanAccessPeer = 0;
break;
case PeerAccessState::PENDING:
Res = cuDeviceCanAccessPeer(&CanAccessPeer, Device, DstDevice.Device);
if (auto Err = Plugin::check(Res, "Error in cuDeviceCanAccessPeer: %s"))
return Err;
if (CanAccessPeer) {
Res = cuCtxEnablePeerAccess(DstDevice.Context, 0);
if (Res == CUDA_ERROR_TOO_MANY_PEERS) {
CanAccessPeer = 0;
DP("Too many P2P so fall back to D2D memcpy");
} else if (auto Err =
Plugin::check(Res, "error in cuCtxEnablePeerAccess: %s"))
return Err;
}
PeerAccesses[DstDeviceId] = (CanAccessPeer)
? PeerAccessState::AVAILABLE
: PeerAccessState::UNAVAILABLE;
}
}
CUstream Stream;
if (auto Err = getStream(AsyncInfoWrapper, Stream))
return Err;
if (CanAccessPeer) {
Res = cuMemcpyPeerAsync(CUDstPtr, Context, CUSrcPtr, DstDevice.Context,
Size, Stream);
return Plugin::check(Res, "error in cuMemcpyPeerAsync: %s");
}
Res = cuMemcpyDtoDAsync(CUDstPtr, CUSrcPtr, Size, Stream);
return Plugin::check(Res, "error in cuMemcpyDtoDAsync: %s");
}
template <typename... ArgsTy>
static Error Plugin::check(int32_t Code, const char *ErrFmt, ArgsTy... Args) {
CUresult ResultCode = static_cast<CUresult>(Code);
if (ResultCode == CUDA_SUCCESS)
return Plugin::success();
const char *Desc = "Unknown error";
CUresult Ret = cuGetErrorString(ResultCode, &Desc);
if (Ret != CUDA_SUCCESS)
REPORT("Unrecognized " GETNAME(TARGET_NAME) " error code %d\n", Code);
ErrorCode OffloadErrCode;
switch (ResultCode) {
case CUDA_ERROR_NOT_FOUND:
OffloadErrCode = ErrorCode::NOT_FOUND;
break;
default:
OffloadErrCode = ErrorCode::UNKNOWN;
}
return Plugin::error(OffloadErrCode, ErrFmt, Args..., Desc);
}
}
}
}
}
extern "C" {
llvm::omp::target::plugin::GenericPluginTy *createPlugin_cuda() {
return new llvm::omp::target::plugin::CUDAPluginTy();
}
}