blob: c594f8fe6542ef93106b6a73914181d395f11701 [file] [edit]
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include <detail/program_manager.hpp>
#include <sycl/__impl/detail/get_device_kernel_info.hpp>
#include <sycl/__impl/exception.hpp>
#include <detail/context_impl.hpp>
#include <detail/device_impl.hpp>
#include <detail/offload/offload_utils.hpp>
#include <llvm/Frontend/Offloading/Utility.h>
_LIBSYCL_BEGIN_NAMESPACE_SYCL
namespace detail {
_LIBSYCL_EXPORT DeviceKernelInfo &
getDeviceKernelInfo(std::string_view KernelName) {
return ProgramAndKernelManager::getInstance().getDeviceKernelInfo(KernelName);
}
DeviceKernelInfo &
ProgramAndKernelManager::getDeviceKernelInfo(std::string_view KernelName) {
auto It = MDeviceKernelInfoMap.find(KernelName);
assert(It != MDeviceKernelInfoMap.end());
return It->second;
}
void ProgramAndKernelManager::releaseResources() {
std::lock_guard<std::mutex> Guard(MDataCollectionMutex);
// Contexts can outlive this call: platform default contexts are kept in the
// platform cache, which is static. Programs must not be left for
// their destructors to release, because olShutDown() follows this call.
for (const std::weak_ptr<ContextImpl> &WeakContext : MContextsWithPrograms) {
if (std::shared_ptr<ContextImpl> Context = WeakContext.lock())
Context->releaseAllPrograms();
}
MContextsWithPrograms.clear();
MDeviceKernelInfoMap.clear();
MDeviceImageManagers.clear();
}
void ProgramAndKernelManager::trackContext(
const std::shared_ptr<ContextImpl> &Context) {
bool AlreadyTracked = false;
for (auto It = MContextsWithPrograms.begin();
It != MContextsWithPrograms.end();) {
std::shared_ptr<ContextImpl> TrackedContext = It->lock();
if (!TrackedContext) {
// Remove expired context from the tracking list.
It = MContextsWithPrograms.erase(It);
continue;
}
AlreadyTracked |= (TrackedContext == Context);
++It;
}
if (!AlreadyTracked)
MContextsWithPrograms.push_back(Context);
}
static inline bool
checkDeviceImageValidity(const llvm::object::OffloadBinary &OB) {
return (OB.getOffloadKind() == llvm::object::OFK_SYCL) &&
(OB.getImageKind() == llvm::object::IMG_SPIRV);
}
void ProgramAndKernelManager::registerFatBin(const void *BinaryStart,
size_t Size) {
assert(BinaryStart && "Binary pointer can't be nullptr");
llvm::MemoryBufferRef MBR(
llvm::StringRef(static_cast<const char *>(BinaryStart), Size),
/*Identifier=*/"");
auto BinOrErr = llvm::object::OffloadBinary::create(MBR);
if (!BinOrErr) {
throw sycl::exception(sycl::make_error_code(sycl::errc::runtime),
"Failed to parse OffloadBinary: " +
llvm::toString(BinOrErr.takeError()));
}
assert(!BinOrErr->empty() && "OffloadBinary must contain at least one entry");
DeviceImageManagerVec Images;
Images.reserve(BinOrErr->size());
std::lock_guard<std::mutex> Guard(MDataCollectionMutex);
for (std::unique_ptr<llvm::object::OffloadBinary> &OB : *BinOrErr) {
if (!checkDeviceImageValidity(*OB))
throw sycl::exception(sycl::make_error_code(sycl::errc::runtime),
"Incompatible device image.");
llvm::StringRef Symbols = OB->getString("symbols");
Images.push_back(std::make_unique<DeviceImageManager>(std::move(OB)));
DeviceImageManager &NewImageWrapper = *Images.back();
llvm::offloading::sycl::forEachSymbol(Symbols, [&](llvm::StringRef Name) {
auto It = MDeviceKernelInfoMap.find(std::string_view(Name));
if (It == MDeviceKernelInfoMap.end()) {
[[maybe_unused]] auto [Iterator, EmplaceSucceeded] =
MDeviceKernelInfoMap.emplace(
std::piecewise_construct,
std::forward_as_tuple(std::string_view(Name)),
std::forward_as_tuple(std::string_view(Name), NewImageWrapper));
assert(EmplaceSucceeded && "Kernel name found in multiple images");
}
});
}
[[maybe_unused]] auto [It, Inserted] =
MDeviceImageManagers.emplace(BinaryStart, std::move(Images));
assert(Inserted && "Fat binary registered twice");
}
void ProgramAndKernelManager::unregisterFatBin(const void *BinaryStart,
size_t /*Size*/) {
assert(BinaryStart && "Binary pointer can't be nullptr");
std::lock_guard<std::mutex> Guard(MDataCollectionMutex);
auto It = MDeviceImageManagers.find(BinaryStart);
if (It == MDeviceImageManagers.end())
return;
for (auto &Image : It->second) {
// Programs created from this image are owned by the contexts they were
// created in, so they have to be destroyed here: the image is about to go
// away, and the kernel names cached alongside those programs point into its
// memory, which may be unmapped right after this call.
for (const std::weak_ptr<ContextImpl> &WeakContext :
MContextsWithPrograms) {
if (std::shared_ptr<ContextImpl> Context = WeakContext.lock())
Context->releaseProgramsForImage(*Image);
}
llvm::StringRef Symbols = Image->getOffloadBinary().getString("symbols");
llvm::offloading::sycl::forEachSymbol(Symbols, [&](llvm::StringRef Name) {
if (auto KernelIt = MDeviceKernelInfoMap.find(std::string_view(Name));
KernelIt != MDeviceKernelInfoMap.end()) {
// Clear kernel specific data by destroying its kernel info object.
MDeviceKernelInfoMap.erase(KernelIt);
}
});
}
MDeviceImageManagers.erase(It);
}
static bool isImageCompatible(const DeviceImageManager &Image,
const DeviceImpl &Device) {
const llvm::object::OffloadBinary &OB = Image.getOffloadBinary();
if (!(OB.getTriple() == DeviceBinaryTripleSPIRV64 &&
Device.getBackend() == sycl::backend::level_zero))
return false;
bool IsValid{};
llvm::StringRef ImageBytes = OB.getImage();
callAndThrow(olIsValidBinary, Device.getOLHandle(), ImageBytes.data(),
ImageBytes.size(), &IsValid);
return IsValid;
}
ol_symbol_handle_t ProgramAndKernelManager::getOrCreateKernel(
DeviceKernelInfo &KernelInfo, const std::shared_ptr<ContextImpl> &Context,
DeviceImpl &Device) {
assert(Context && "Context can't be nullptr");
std::lock_guard<std::mutex> KernelGuard(MDataCollectionMutex);
DeviceImageManager &DeviceImage = KernelInfo.getDeviceImage();
if (!isImageCompatible(DeviceImage, Device))
throw exception(make_error_code(errc::runtime),
std::string("No compatible image for ") +
KernelInfo.getName().data() + " was found");
// Track the context before it caches anything, so that unregisterFatBin() can
// reach the programs it is about to create.
trackContext(Context);
// Lock order is MDataCollectionMutex -> ContextImpl::MProgramCacheMutex.
return Context->getOrCreateKernel(DeviceImage, Device.getOLHandle(),
KernelInfo.getName());
}
bool ProgramAndKernelManager::hasCompatibleImage(const DeviceImpl &Device) {
std::lock_guard<std::mutex> Guard(MDataCollectionMutex);
for (const auto &BinaryImagesPair : MDeviceImageManagers) {
for (const auto &Image : BinaryImagesPair.second) {
if (isImageCompatible(*Image, Device))
return true;
}
}
return false;
}
} // namespace detail
_LIBSYCL_END_NAMESPACE_SYCL
extern "C" _LIBSYCL_EXPORT void __sycl_register_lib(const void *BinaryStart,
size_t Size) {
sycl::detail::ProgramAndKernelManager::getInstance().registerFatBin(
BinaryStart, Size);
}
extern "C" _LIBSYCL_EXPORT void __sycl_unregister_lib(const void *BinaryStart,
size_t Size) {
sycl::detail::ProgramAndKernelManager::getInstance().unregisterFatBin(
BinaryStart, Size);
}