| //===-- State.cpp - Kernel language persistent state ----------------------===// |
| // |
| // 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 "State.h" |
| #include "Stream.h" |
| #include "Types.h" |
| |
| #include "OffloadAPI.h" |
| #include "OffloadErrors.h" |
| #include "llvm/ADT/ArrayRef.h" |
| #include "llvm/ADT/DenseMap.h" |
| #include "llvm/ADT/SmallPtrSet.h" |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/Support/Compiler.h" |
| |
| #include <atomic> |
| #include <cassert> |
| #include <cstddef> |
| #include <cstdint> |
| #include <cstdio> |
| #include <mutex> |
| |
| using namespace llvm; |
| using namespace offload; |
| |
| // Weak fallback used unless the driver links the strong per-thread default |
| // stream mode object for -fgpu-default-stream=per-thread. |
| extern "C" { |
| __attribute__((weak)) uint32_t __LLVMOffloadingPerThreadDefaultStream = 0; |
| } |
| |
| // Process-wide singleton and thread-state registry. |
| static std::mutex &getStateLock() { |
| static std::mutex StateLock; |
| return StateLock; |
| } |
| static std::atomic<StateTy *> StatePtr = nullptr; |
| |
| static thread_local ThreadStateTy *ThreadState = nullptr; |
| |
| static std::mutex &getThreadStatesLock() { |
| static std::mutex ThreadStatesLock; |
| return ThreadStatesLock; |
| } |
| using ThreadStatesTy = SmallVector<ThreadStateTy *, 64>; |
| static ThreadStatesTy *ThreadStatesPtr = nullptr; |
| |
| static void deleteThreadStates() { |
| // Detach the registry before deletion because deleteThreadState may be called |
| // more than once via atexit and StateTy teardown. |
| std::lock_guard<std::mutex> LG(getThreadStatesLock()); |
| ThreadStatesTy *ThreadStates = ThreadStatesPtr; |
| ThreadStatesPtr = nullptr; |
| if (!ThreadStates) |
| return; |
| |
| for (auto *TS : *ThreadStates) |
| delete TS; |
| delete ThreadStates; |
| ThreadState = nullptr; |
| ThreadStates = nullptr; |
| } |
| |
| static void deleteState() { |
| StateTy *ST = StatePtr.load(std::memory_order_acquire); |
| if (!ST) |
| return; |
| delete ST; |
| StatePtr.store(nullptr, std::memory_order_release); |
| } |
| |
| static void destroyStreamHandle(StreamTy *&Stream) { |
| if (!Stream) |
| return; |
| |
| olSyncQueue(Stream->Queue); |
| olDestroyQueue(Stream->Queue); |
| delete Stream; |
| Stream = nullptr; |
| } |
| |
| namespace llvm { |
| namespace offload { |
| |
| static bool removeStreamFromMap( |
| DenseMap<ol_device_handle_t, SmallPtrSet<StreamTy *, 8>> &StreamsMap, |
| StreamTy *Stream) { |
| bool Removed = false; |
| SmallVector<ol_device_handle_t, 8> EmptyDevices; |
| |
| for (auto &It : StreamsMap) { |
| Removed |= It.second.erase(Stream); |
| if (It.second.empty()) |
| EmptyDevices.push_back(It.first); |
| } |
| |
| for (ol_device_handle_t Device : EmptyDevices) |
| StreamsMap.erase(Device); |
| |
| return Removed; |
| } |
| |
| // ThreadStateTy implementation. |
| |
| ThreadStateTy::ThreadStateTy() { |
| unsigned int NumDevices = StateTy::get().Devices.size(); |
| PerThreadDeviceDefaultStreamMap.reserve(NumDevices); |
| atexit(deleteThreadStates); |
| } |
| ThreadStateTy::~ThreadStateTy() { destroyDefaultStreams(); } |
| |
| ThreadStateTy &ThreadStateTy::get() { |
| auto *&TS = ThreadState; |
| if (!TS) { |
| TS = new ThreadStateTy(); |
| std::lock_guard<std::mutex> LG(getThreadStatesLock()); |
| if (!ThreadStatesPtr) |
| ThreadStatesPtr = new ThreadStatesTy; |
| ThreadStatesPtr->push_back(TS); |
| } |
| return *TS; |
| } |
| |
| ol_device_handle_t ThreadStateTy::getDefaultDevice() { |
| ArrayRef<ol_device_handle_t> Devices = StateTy::get().getDevices(); |
| int DD = DefaultDevice; |
| if (DD < 0 || DD >= static_cast<int>(Devices.size())) |
| return nullptr; |
| return Devices[DD]; |
| } |
| |
| StreamTy *ThreadStateTy::getDefaultStream() { |
| ol_device_handle_t Device = getDefaultDevice(); |
| if (!Device) |
| return nullptr; |
| |
| if (LLVM_LIKELY(!__LLVMOffloadingPerThreadDefaultStream)) |
| return StateTy::get().getOrCreateDefaultStream(Device); |
| |
| return getOrCreateDefaultStream(Device); |
| } |
| |
| ol_queue_handle_t ThreadStateTy::getDefaultQueue() { |
| if (StreamTy *Stream = getDefaultStream()) |
| return Stream->Queue; |
| return nullptr; |
| } |
| |
| CallConfigurationTy &ThreadStateTy::getCallConfiguration() { return CC; } |
| |
| ol_device_handle_t ThreadStateTy::setDefaultDevice(int DeviceNo) { |
| ArrayRef<ol_device_handle_t> Devices = StateTy::get().getDevices(); |
| if (DeviceNo < 0 || DeviceNo >= static_cast<int>(Devices.size())) |
| return nullptr; |
| DefaultDevice = DeviceNo; |
| return Devices[DeviceNo]; |
| } |
| |
| ol_device_handle_t ThreadStateTy::getDevice(int *DeviceNo) { |
| *DeviceNo = DefaultDevice; |
| return getDefaultDevice(); |
| } |
| |
| uint32_t ThreadStateTy::getLastError() { return LastError; } |
| |
| uint32_t ThreadStateTy::setLastError(uint32_t Error) { |
| return LastError = Error; |
| } |
| |
| StreamTy *ThreadStateTy::getOrCreateDefaultStream(ol_device_handle_t Device) { |
| if (!Device) |
| return nullptr; |
| |
| StateTy &State = StateTy::get(); |
| ol_context_handle_t Context = State.getContext(); |
| if (!Context) |
| return nullptr; |
| |
| StreamTy *&Stream = PerThreadDeviceDefaultStreamMap[Device]; |
| if (!Stream) { |
| ol_queue_handle_t Queue = nullptr; |
| CHECK_FATAL(olCreateQueue(Context, Device, &Queue), |
| "Failed to create per-thread default queue for device"); |
| Stream = new StreamTy{Queue, Device, QueueKind::PerThreadDefault}; |
| State.addStream(Stream); |
| } |
| return Stream; |
| } |
| |
| void ThreadStateTy::destroyDefaultStreams() { |
| for (auto &It : PerThreadDeviceDefaultStreamMap) { |
| if (StateTy *State = StateTy::tryGet()) |
| State->removeStream(It.second); |
| destroyStreamHandle(It.second); |
| } |
| PerThreadDeviceDefaultStreamMap.clear(); |
| } |
| |
| // StateTy implementation. |
| |
| StateTy &StateTy::get() { |
| StateTy *ST = StatePtr.load(std::memory_order_acquire); |
| if (LLVM_UNLIKELY(!ST)) { |
| std::lock_guard<std::mutex> LG(getStateLock()); |
| ST = StatePtr.load(std::memory_order_acquire); |
| if (!ST) { |
| ST = new StateTy(); |
| StatePtr.store(ST, std::memory_order_release); |
| } |
| } |
| return *ST; |
| } |
| |
| StateTy *StateTy::tryGet() { return StatePtr.load(std::memory_order_acquire); } |
| |
| StreamTy *StateTy::getOrCreateDefaultStream(ol_device_handle_t Device) { |
| if (!Device) |
| return nullptr; |
| |
| ol_context_handle_t RuntimeContext = getContext(); |
| if (!RuntimeContext) |
| return nullptr; |
| |
| std::lock_guard<std::mutex> LG(DeviceDefaultStreamsMapLock); |
| StreamTy *&Stream = DeviceDefaultStreamsMap[Device]; |
| if (!Stream) { |
| ol_queue_handle_t Queue = nullptr; |
| CHECK_FATAL(olCreateQueue(RuntimeContext, Device, &Queue), |
| "Failed to create default queue for device"); |
| Stream = new StreamTy{Queue, Device, QueueKind::LegacyDefault}; |
| addStream(Stream); |
| } |
| return Stream; |
| } |
| |
| void StateTy::destroyDefaultStreams() { |
| std::lock_guard<std::mutex> LG(DeviceDefaultStreamsMapLock); |
| for (auto &It : DeviceDefaultStreamsMap) { |
| removeStream(It.second); |
| destroyStreamHandle(It.second); |
| } |
| DeviceDefaultStreamsMap.clear(); |
| } |
| |
| void StateTy::addStream(StreamTy *Stream) { |
| std::lock_guard<std::mutex> LG(DeviceStreamsMapLock); |
| DeviceStreamsMap[Stream->Device].insert(Stream); |
| } |
| |
| void StateTy::removeStream(StreamTy *Stream) { |
| if (!Stream) |
| return; |
| |
| { |
| std::lock_guard<std::mutex> LG(DeviceStreamsMapLock); |
| if (!removeStreamFromMap(DeviceStreamsMap, Stream)) |
| return; |
| } |
| |
| std::lock_guard<std::mutex> LG(DeviceBlockingStreamsMapLock); |
| removeStreamFromMap(DeviceBlockingStreamsMap, Stream); |
| } |
| |
| SmallPtrSet<StreamTy *, 8> |
| StateTy::getDeviceStreams(ol_device_handle_t Device) { |
| std::lock_guard<std::mutex> LG(DeviceStreamsMapLock); |
| auto It = DeviceStreamsMap.find(Device); |
| if (It == DeviceStreamsMap.end()) |
| return {}; |
| return It->second; |
| } |
| |
| SmallPtrSet<StreamTy *, 8> |
| StateTy::getBlockingStreams(ol_device_handle_t Device) { |
| std::lock_guard<std::mutex> LG(DeviceBlockingStreamsMapLock); |
| auto It = DeviceBlockingStreamsMap.find(Device); |
| if (It == DeviceBlockingStreamsMap.end()) |
| return {}; |
| return It->second; |
| } |
| |
| bool StateTy::hasLegacyDefaultStream(ol_device_handle_t Device) { |
| std::lock_guard<std::mutex> LG(DeviceDefaultStreamsMapLock); |
| auto It = DeviceDefaultStreamsMap.find(Device); |
| return It != DeviceDefaultStreamsMap.end() && It->second; |
| } |
| |
| ol_result_t StateTy::createStream(ol_device_handle_t Device, QueueKind Kind, |
| StreamTy **Stream) { |
| if (!Stream) |
| return &InvalidNullPointerError; |
| *Stream = nullptr; |
| |
| ol_context_handle_t RuntimeContext = getContext(); |
| if (!Device || !RuntimeContext) |
| return &InvalidDeviceError; |
| |
| ol_queue_handle_t Queue = nullptr; |
| ol_result_t Result = olCreateQueue(RuntimeContext, Device, &Queue); |
| if (Result == OL_SUCCESS) { |
| *Stream = new StreamTy{Queue, Device, Kind}; |
| addStream(*Stream); |
| if (Kind == QueueKind::ExplicitBlocking) { |
| std::lock_guard<std::mutex> LG(DeviceBlockingStreamsMapLock); |
| DeviceBlockingStreamsMap[Device].insert(*Stream); |
| } |
| } |
| return Result; |
| } |
| |
| ol_result_t StateTy::destroyStream(StreamTy *Stream) { |
| if (!Stream) |
| return &InvalidNullPointerError; |
| |
| if (!isStreamRegistered(Stream)) |
| return &InvalidStreamError; |
| |
| removeStream(Stream); |
| ol_result_t Result = olDestroyQueue(Stream->Queue); |
| delete Stream; |
| return Result; |
| } |
| |
| bool StateTy::isStreamRegistered(StreamTy *Stream) { |
| if (!Stream) |
| return false; |
| |
| std::lock_guard<std::mutex> LG(DeviceStreamsMapLock); |
| for (auto &It : DeviceStreamsMap) |
| if (It.second.contains(Stream)) |
| return true; |
| return false; |
| } |
| |
| ol_device_handle_t StateTy::getHostDevice() { return HostDevice; } |
| |
| ol_context_handle_t StateTy::getContext() { return Context; } |
| |
| int StateTy::getDeviceCount() { return Devices.size(); } |
| |
| ArrayRef<ol_device_handle_t> StateTy::getDevices() const { return Devices; } |
| |
| void StateTy::addDevice(ol_device_handle_t Device) { |
| Devices.push_back(Device); |
| } |
| |
| void StateTy::setHostDevice(ol_device_handle_t Device) { |
| if (!HostDevice) |
| HostDevice = Device; |
| } |
| |
| void StateTy::registerKernel(const void *ID, ol_symbol_handle_t Kernel) { |
| KernelMap[ID] = Kernel; |
| } |
| |
| void StateTy::unregisterKernel(const void *ID) { KernelMap.erase(ID); } |
| |
| ol_symbol_handle_t StateTy::getKernel(const void *ID) { return KernelMap[ID]; } |
| |
| void StateTy::registerProgram(const void *ID, ol_program_handle_t Program) { |
| BinaryRegisterMap[ID] = Program; |
| } |
| |
| ol_program_handle_t StateTy::unregisterProgram(const void *ID) { |
| auto It = BinaryRegisterMap.find(ID); |
| if (It == BinaryRegisterMap.end()) |
| return nullptr; |
| ol_program_handle_t Program = It->second; |
| BinaryRegisterMap.erase(It); |
| return Program; |
| } |
| |
| ol_program_handle_t StateTy::getProgram(const void *ID) { |
| assert(BinaryRegisterMap.count(ID) && "Program not registered for binary"); |
| return BinaryRegisterMap[ID]; |
| } |
| |
| bool StateTy::addDevices(ol_device_handle_t Device, void *Payload) { |
| StateTy &State = *reinterpret_cast<StateTy *>(Payload); |
| ol_platform_handle_t Platform; |
| ol_result_t Result; |
| |
| Result = olGetDeviceInfo(Device, OL_DEVICE_INFO_PLATFORM, sizeof(Platform), |
| &Platform); |
| if (Result && Result->Code) |
| return true; |
| |
| ol_platform_backend_t Backend; |
| Result = olGetPlatformInfo(Platform, OL_PLATFORM_INFO_BACKEND, |
| sizeof(Backend), &Backend); |
| if (Result && Result->Code) |
| return true; |
| |
| if (Backend == OL_PLATFORM_BACKEND_HOST) |
| State.setHostDevice(Device); |
| else |
| State.addDevice(Device); |
| return true; |
| } |
| |
| StateTy::StateTy() { |
| CHECK_FATAL(olInit(nullptr), "Failed to initialize the LLVMOffload"); |
| CHECK_FATAL(olIterateDevices(StateTy::addDevices, this), |
| "Failed to identify devices"); |
| |
| if (!Devices.empty()) |
| CHECK_FATAL(olCreateContext(Devices.size(), Devices.data(), &Context), |
| "Failed to create default context"); |
| |
| unsigned int DeviceCount = Devices.size(); |
| DeviceDefaultStreamsMap.reserve(DeviceCount); |
| DeviceStreamsMap.reserve(DeviceCount); |
| DeviceBlockingStreamsMap.reserve(DeviceCount); |
| |
| atexit(deleteState); |
| } |
| |
| StateTy::~StateTy() { |
| deleteThreadStates(); |
| destroyDefaultStreams(); |
| destroyRegisteredStreams(); |
| destroyRegisteredPrograms(); |
| if (Context) |
| olDestroyContext(Context); |
| olShutDown(); |
| } |
| |
| void StateTy::destroyRegisteredStreams() { |
| SmallVector<StreamTy *, 16> Streams; |
| { |
| std::lock_guard<std::mutex> LG(DeviceStreamsMapLock); |
| for (auto &It : DeviceStreamsMap) |
| Streams.append(It.second.begin(), It.second.end()); |
| DeviceStreamsMap.clear(); |
| } |
| { |
| std::lock_guard<std::mutex> LG(DeviceBlockingStreamsMapLock); |
| DeviceBlockingStreamsMap.clear(); |
| } |
| |
| for (StreamTy *&Stream : Streams) |
| destroyStreamHandle(Stream); |
| } |
| |
| void StateTy::destroyRegisteredPrograms() { |
| SmallPtrSet<ol_program_handle_t, 8> Programs; |
| for (auto &It : BinaryRegisterMap) |
| Programs.insert(It.second); |
| |
| KernelMap.clear(); |
| BinaryRegisterMap.clear(); |
| |
| for (ol_program_handle_t Program : Programs) |
| olDestroyProgram(Program); |
| } |
| |
| } // namespace offload |
| } // namespace llvm |