blob: 489726046503b8c695b199abf1f86500bccf46c1 [file] [edit]
//===--- Level Zero Target RTL Implementation -----------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Level Zero Queue abstraction.
//
//===----------------------------------------------------------------------===//
#include "L0Queue.h"
#include "L0Device.h"
#include "L0Kernel.h"
#include "L0Plugin.h"
#include "L0Trace.h"
#include "PluginInterface.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/MathExtras.h"
#include <algorithm>
#include <vector>
namespace llvm::omp::target::plugin {
/// common methods
Error L0QueueTy::init(ze_context_handle_t UserZeCtx) {
auto CmdListOrErr = Device.getCmdListManager(UserZeCtx, CreateQueueInOrder);
if (!CmdListOrErr)
return CmdListOrErr.takeError();
CmdList = *CmdListOrErr;
return initImpl();
}
Error L0QueueTy::deinit() {
if (auto Err = deinitImpl())
return Err;
reset();
if (CmdList)
if (auto Err = Device.releaseCmdListManager(CmdList))
return Err;
CmdList = nullptr;
return Plugin::success();
}
Error L0QueueTy::dispatchLaunchKernel(ze_kernel_handle_t Kernel,
L0LaunchEnvTy &KEnv,
ze_event_handle_t SignalEvent,
uint32_t NumWaitEvents,
ze_event_handle_t *WaitEvents) {
// Unlock KEnv lock after launching the kernel.
llvm::scope_exit UnlockGuard([&KEnv]() { KEnv.Lock.unlock(); });
bool AppendLaunchKernelWithArgsAvailable =
Device.getL0Context().LaunchKernelWithArguments.available();
if (AppendLaunchKernelWithArgsAvailable &&
Device.getL0Context().AppendLaunchKernelWithArgsSupported.load(
std::memory_order_acquire)) {
auto Err = CmdList->appendLaunchKernelWithArgs(
Kernel, &KEnv.GroupCounts, &KEnv.GroupSizes, KEnv.ArgPtrs, SignalEvent,
NumWaitEvents, WaitEvents, KEnv.IsCooperative);
if (!Err)
return Plugin::success();
// Check if Err is ErrorCode::UNSUPPORTED, if so consume it
Err = llvm::handleErrors(
std::move(Err),
[&](std::unique_ptr<error::OffloadError> E) -> llvm::Error {
if (E->convertToErrorCode() ==
error::make_error_code(error::ErrorCode::UNSUPPORTED))
return llvm::Error::success(); // Swallow error
return llvm::Error(std::move(E));
});
if (Err)
// Err is still here, so it was not ErrorCode::UNSUPPORTED
return Err;
// No Err - it was ErrorCode::UNSUPPORTED, continue into fallback
}
auto &KernelProperties = KEnv.KernelPR;
if (KernelProperties.NumKernelArgs > 0 &&
(KEnv.ArgPtrs == nullptr || KEnv.ArgSizes == nullptr))
return error::createOffloadError(
ErrorCode::INVALID_ARGUMENT,
"zeCommandListAppendLaunchKernelWithArguments is not supported on this "
"platform and fallback is not possible, becaue ArgPtrs(%p) or "
"ArgSizes(%p) were not provided!",
KEnv.ArgPtrs, KEnv.ArgSizes);
// Submit kernel using older set of APIs - zeKernelSetArgumentValue
auto &GroupSizes = KEnv.GroupSizes;
CALL_ZE_RET_ERROR(zeKernelSetGroupSize, Kernel, GroupSizes.groupSizeX,
GroupSizes.groupSizeY, GroupSizes.groupSizeZ);
for (uint32_t KernelArg = 0; KernelArg < KernelProperties.NumKernelArgs;
KernelArg++) {
uint32_t ArgSize = KEnv.ArgSizes[KernelArg];
CALL_ZE_RET_ERROR(zeKernelSetArgumentValue, Kernel, KernelArg, ArgSize,
KEnv.ArgPtrs[KernelArg]);
}
return CmdList->appendLaunchKernel(Kernel, &KEnv.GroupCounts, SignalEvent,
NumWaitEvents, WaitEvents,
KEnv.IsCooperative);
}
Error L0QueueTy::memoryFill(void *Ptr, const void *Pattern, size_t PatternSize,
size_t Size) {
assert(PatternSize <= Size && "PatternSize > Size is unsupported");
if (Size == 0 || PatternSize == 0)
return Plugin::success();
if (llvm::isPowerOf2_64(PatternSize) && (Size % PatternSize == 0) &&
PatternSize <= Device.getMaxMemFillPatternSize()) {
// Native L0 memory fill is possible directly.
return memoryFillImpl(Ptr, Pattern, PatternSize, Size);
}
auto *PatternBytes = static_cast<const unsigned char *>(Pattern);
// Check if all bytes are equal.
if (std::memcmp(PatternBytes, PatternBytes + 1, PatternSize - 1) == 0) {
// Substitution of 1 as PatternSize is equivalent,
// so native L0 memory fill is still possible.
return memoryFillImpl(Ptr, Pattern, 1, Size);
}
// TODO: if we insist on plugins supporting arbitrary pattern sizes, extra
// detection of repeating power-of-two patterns could be added here to allow
// native L0 memory fill for those cases as well.
return memoryFillReplicateImpl(Ptr, Pattern, PatternSize, Size);
}
/// Construct a seed by repeating \p Pattern. When \p PatternSize is at most
/// \p MinSize, the seed size is a multiple of \p PatternSize in the range
/// [MinSize, 2 * MinSize). Otherwise, return a copy of \p Pattern.
static std::vector<unsigned char>
extendPattern(const void *Pattern, size_t PatternSize, size_t MinSize) {
assert(PatternSize > 0 && MinSize > 0 && "Invalid pattern extension size");
const auto *PatternBytes = static_cast<const unsigned char *>(Pattern);
if (PatternSize > MinSize)
return std::vector<unsigned char>(PatternBytes, PatternBytes + PatternSize);
const size_t NumPatterns = (MinSize + PatternSize - 1) / PatternSize;
std::vector<unsigned char> Seed(NumPatterns * PatternSize);
std::copy_n(PatternBytes, PatternSize, Seed.begin());
for (size_t Offset = PatternSize; Offset < Seed.size(); ++Offset)
Seed[Offset] = Seed[Offset - PatternSize];
return Seed;
}
Error L0QueueTy::memoryFillReplicateImpl(void *Ptr, const void *Pattern,
size_t PatternSize, size_t Size) {
auto *Dst = static_cast<unsigned char *>(Ptr);
// Extend small patterns to avoid several inefficient device copies.
const auto Seed = extendPattern(Pattern, PatternSize, /*MinSize=*/1024);
size_t BytesFilled = std::min(Seed.size(), Size);
const auto TgtType = Device.getMemAllocType(Ptr);
// dataSubmit() writes host/shared destinations directly, so complete earlier
// queue work before modifying the destination from the host.
if (TgtType == ZE_MEMORY_TYPE_HOST || TgtType == ZE_MEMORY_TYPE_SHARED) {
if (auto Err = synchronize())
return Err;
}
if (auto Err = dataSubmit(Dst, Seed.data(), BytesFilled))
return Err;
// Complete the seed submission before its host storage goes out of scope.
if (auto Err = synchronize())
return Err;
// Clone the seed, doubling each time, until it fills the entire destination.
while (BytesFilled < Size) {
if (auto Err = dataFence())
return Err;
const size_t CopyChunkSize = std::min(BytesFilled, Size - BytesFilled);
if (auto Err = memoryCopy(Dst + BytesFilled, Dst, CopyChunkSize))
return Err;
BytesFilled += CopyChunkSize;
}
return Plugin::success();
}
// L0InorderQueueTy implementation.
Error L0InorderQueueTy::synchronizeImpl() { return CmdList->hostSynchronize(); }
Expected<bool> L0InorderQueueTy::hasPendingWorkImpl() {
return CmdList->queryPendingWork();
}
Error L0InorderQueueTy::memoryCopyImpl(void *Dst, const void *Src,
size_t Size) {
return CmdList->appendMemoryCopy(Dst, Src, Size);
}
Error L0InorderQueueTy::launchKernelImpl(ze_kernel_handle_t Kernel,
L0LaunchEnvTy &KEnv) {
return dispatchLaunchKernel(Kernel, KEnv);
}
Error L0InorderQueueTy::hostCallImpl(void (*Callback)(void *), void *UserData) {
return CmdList->appendHostFunction(Callback, UserData);
}
// L0SyncQueueTy implementation.
Error L0SyncQueueTy::memoryCopyImpl(void *Dst, const void *Src, size_t Size) {
if (auto Err = L0InorderQueueTy::memoryCopyImpl(Dst, Src, Size))
return Err;
return CmdList->hostSynchronize();
}
Error L0SyncQueueTy::launchKernelImpl(ze_kernel_handle_t Kernel,
L0LaunchEnvTy &KEnv) {
if (auto Err = L0InorderQueueTy::launchKernelImpl(Kernel, KEnv))
return Err;
return CmdList->hostSynchronize();
}
Error L0SyncQueueTy::hostCallImpl(void (*Callback)(void *), void *UserData) {
if (auto Err = L0InorderQueueTy::hostCallImpl(Callback, UserData))
return Err;
return CmdList->hostSynchronize();
}
Error L0SyncQueueTy::memoryFillImpl(void *Ptr, const void *Pattern,
size_t PatternSize, size_t Size) {
if (auto Err =
L0InorderQueueTy::memoryFillImpl(Ptr, Pattern, PatternSize, Size))
return Err;
return CmdList->hostSynchronize();
}
// L0QueueCache implementation.
Expected<L0QueueTy *> L0QueueCacheTy::getQueue(L0DeviceTy &Device) {
{
std::lock_guard<std::mutex> Lock(Mtx);
auto Itr = Queues.find(&Device);
if (Itr != Queues.end() && !Itr->second.empty()) {
L0QueueTy *Queue = Itr->second.back();
Itr->second.pop_back();
return Queue;
}
}
L0QueueTy *Queue = nullptr;
switch (Device.getPlugin().getOptions().CommandMode) {
case CommandModeTy::Sync:
Queue = new L0SyncQueueTy(Device);
break;
case CommandModeTy::InOrder:
Queue = new L0InorderQueueTy(Device);
break;
}
Queue->setUserCtx(&UserCtx);
if (auto Err = Queue->init(UserCtx.getZeContext())) {
delete Queue;
return std::move(Err);
}
return Queue;
}
void L0QueueCacheTy::releaseQueue(L0QueueTy *Queue) {
if (!Queue)
return;
L0DeviceTy &Device = Queue->getDevice();
Queue->reset();
std::lock_guard<std::mutex> Lock(Mtx);
Queues[&Device].push_back(Queue);
}
Error L0QueueCacheTy::deinit() {
Error AllErrors = Error::success();
std::lock_guard<std::mutex> Lock(Mtx);
for (auto &Bucket : Queues) {
for (auto *Queue : Bucket.second) {
if (auto Err = Queue->deinit())
AllErrors = joinErrors(std::move(AllErrors), std::move(Err));
delete Queue;
}
}
Queues.clear();
return AllErrors;
}
} // namespace llvm::omp::target::plugin