blob: 0b9ba507eea6109dd0914879127133777f92a20c [file] [edit]
//===--------- device.cpp - Target independent OpenMP target RTL ----------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Functionality for managing devices that are handled by RTL plugins.
//
//===----------------------------------------------------------------------===//
#include "device.h"
#include "OffloadEntry.h"
#include "OmpAccError.h"
#include "OpenMP/Mapping.h"
#include "OpenMP/OMPT/Callback.h"
#include "OpenMP/OMPT/Interface.h"
#include "PluginManager.h"
#include "Shared/APITypes.h"
#include "Shared/Debug.h"
#include "omptarget.h"
#include "rtl.h"
#include "Shared/EnvironmentVar.h"
#include "llvm/Frontend/OpenMP/OMPConstants.h"
#include "llvm/Support/Error.h"
#include <algorithm>
#include <cassert>
#include <climits>
#include <cstdint>
#include <cstdio>
#include <mutex>
#include <string>
#include <thread>
#ifdef OMPT_SUPPORT
using namespace llvm::omp::target::ompt;
#endif
using namespace llvm::omp::target;
using namespace llvm::omp::target::plugin;
using namespace llvm::omp::target::debug;
int HostDataToTargetTy::addEventIfNecessary(DeviceTy &Device,
AsyncInfoTy &AsyncInfo) const {
// First, check if the user disabled atomic map transfer/malloc/dealloc.
if (!MappingConfig::get().UseEventsForAtomicTransfers)
return OFFLOAD_SUCCESS;
// We cannot assume the event should not be nullptr because we don't
// know if the target support event. But if a target doesn't,
// recordEvent should always return success.
void *Event = getEvent();
if (Device.recordEvent(&Event, AsyncInfo) != OFFLOAD_SUCCESS) {
REPORT() << "Failed to set dependence on event " << Event;
return OFFLOAD_FAIL;
}
setEvent(Event);
return OFFLOAD_SUCCESS;
}
DeviceTy::DeviceTy(GenericPluginTy *RTL, int32_t DeviceID, int32_t RTLDeviceID,
ol_device_handle_t DeviceHandle)
: DeviceID(DeviceID), RTL(RTL), RTLDeviceID(RTLDeviceID),
DeviceHandle(DeviceHandle), MappingInfo(*this) {}
DeviceTy::~DeviceTy() {
if (DeviceID == -1 || !(getInfoLevel() & OMP_INFOTYPE_DUMP_TABLE))
return;
ident_t Loc = {0, 0, 0, 0, ";libomptarget;libomptarget;0;0;;"};
dumpTargetPointerMappings(&Loc, *this);
}
llvm::Error DeviceTy::init() {
if (olCreateContext(1, &DeviceHandle, &Context)) {
return createError(ErrorCode::BackendFailure,
"failed to create context for device %d\n", DeviceID);
}
// Envar that indicates whether mapped host buffers should be locked
// automatically. The possible values are boolean (on/off) and a special:
// off: Mapped host buffers are not locked.
// on: Mapped host buffers are locked in a best-effort approach.
// Failure to lock the buffers are silent.
// mandatory: Mapped host buffers are always locked and failures to lock
// a buffer results in a fatal error.
StringEnvar OMPX_LockMappedBuffers("LIBOMPTARGET_LOCK_MAPPED_HOST_BUFFERS",
"off");
bool Enabled;
if (StringParser::parse(OMPX_LockMappedBuffers.get().data(), Enabled)) {
// Parsed as a boolean value. Enable the feature if necessary.
LockMappedBuffers = Enabled;
IgnoreLockMappedFailures = true;
} else if (OMPX_LockMappedBuffers.get() == "mandatory") {
// Enable the feature and failures are fatal.
LockMappedBuffers = true;
IgnoreLockMappedFailures = false;
} else {
// Disable by default.
ODBG(ODT_Alloc) << "Invalid value LIBOMPTARGET_LOCK_MAPPED_HOST_BUFFERS="
<< OMPX_LockMappedBuffers.get();
LockMappedBuffers = false;
IgnoreLockMappedFailures = true;
}
// Enables recording kernels if set.
BoolEnvar OMPX_RecordKernel("LIBOMPTARGET_RECORD", false);
if (OMPX_RecordKernel) {
BoolEnvar OMPX_RecordOutput("LIBOMPTARGET_RECORD_OUTPUT", true);
Int64Envar OMPX_RecordMemSize("LIBOMPTARGET_RECORD_MEMSIZE",
8 * 1024 * 1024 * 1024ULL);
Int32Envar OMPX_RecordDevice("LIBOMPTARGET_RECORD_DEVICE", 0);
StringEnvar OMPX_RecordOutputDir("LIBOMPTARGET_RECORD_DIR", "");
BoolEnvar OMPX_EmitRecordReport("LIBOMPTARGET_RECORD_REPORT", false);
StringEnvar OMPX_RecordReportFilename("LIBOMPTARGET_RECORD_REPORT_FILENAME",
"");
if (OMPX_RecordDevice != RTLDeviceID)
return llvm::Error::success();
// Print report if it was enabled explicitly or a report file was indicated.
bool EmitReport =
OMPX_EmitRecordReport || !OMPX_RecordReportFilename.get().empty();
int32_t Ret = RTL->initialize_record_replay(
RTLDeviceID, OMPX_RecordMemSize, nullptr,
/*IsRecord=*/true, /*IsNative=*/true, OMPX_RecordOutput, EmitReport,
OMPX_RecordReportFilename.get().c_str(),
OMPX_RecordOutputDir.get().c_str());
if (Ret != OFFLOAD_SUCCESS)
return createError(ErrorCode::BackendFailure,
"failed to initialize RR in device %d\n", DeviceID);
}
return llvm::Error::success();
}
llvm::Error DeviceTy::deinit() {
if (olDestroyContext(Context)) {
return createError(ErrorCode::BackendFailure,
"failed to destroy context for device %d\n", DeviceID);
}
return llvm::Error::success();
}
// Resolve the device address of the global variable \p Name in \p Program,
// recording it for kernel record/replay if recording is currently active.
llvm::Expected<void *> getAndRecordGlobalAddress(DeviceTy &Device,
const ProgramTy &Program,
const char *Name) {
size_t Size = 0;
auto AddrOrErr = Program.getGlobalAddress(Name, &Size);
if (!AddrOrErr)
return AddrOrErr;
GenericDeviceTy &GenericDevice = Device.RTL->getDevice(Device.RTLDeviceID);
RecordReplayTy *RecordReplay = GenericDevice.getRecordReplay();
if (RecordReplay && RecordReplay->isRecording())
RecordReplay->addGlobal(Name, Size, *AddrOrErr);
return AddrOrErr;
}
// Extract the mapping of host function pointers to device function pointers
// from the entry table. Functions marked as 'indirect' in OpenMP will have
// offloading entries generated for them which map the host's function pointer
// to a global containing the corresponding function pointer on the device.
static llvm::Expected<std::pair<void *, uint64_t>>
setupIndirectCallTable(DeviceTy &Device, __tgt_device_image *Image,
const ProgramTy &Program) {
AsyncInfoTy AsyncInfo(Device);
llvm::ArrayRef<llvm::offloading::EntryTy> Entries(Image->EntriesBegin,
Image->EntriesEnd);
llvm::SmallVector<std::pair<void *, void *>> IndirectCallTable;
for (const auto &Entry : Entries) {
if (Entry.Kind != llvm::object::OffloadKind::OFK_OpenMP ||
Entry.Size == 0 ||
(!(Entry.Flags & OMP_DECLARE_TARGET_INDIRECT) &&
!(Entry.Flags & OMP_DECLARE_TARGET_INDIRECT_VTABLE)))
continue;
size_t PtrSize = sizeof(void *);
if (Entry.Flags & OMP_DECLARE_TARGET_INDIRECT_VTABLE) {
// This is a VTable entry, the current entry is the first index of the
// VTable and Entry.Size is the total size of the VTable. Unlike the
// indirect function case below, the Global is not of size Entry.Size and
// is instead of size PtrSize (sizeof(void*)).
void *res;
auto VtableOrErr =
getAndRecordGlobalAddress(Device, Program, Entry.SymbolName);
if (!VtableOrErr)
return VtableOrErr.takeError();
void *Vtable = *VtableOrErr;
// HstPtr = Entry.Address;
if (Device.retrieveData(&res, Vtable, PtrSize, AsyncInfo))
return createError(ErrorCode::InvalidBinary, "failed to load %s",
Entry.SymbolName);
if (Device.synchronize(AsyncInfo))
return createError(ErrorCode::InvalidBinary,
"failed to synchronize after retrieving %s",
Entry.SymbolName);
// Calculate and emplace entire Vtable from first Vtable byte
for (uint64_t i = 0; i < Entry.Size / PtrSize; ++i) {
auto &[HstPtr, DevPtr] = IndirectCallTable.emplace_back();
HstPtr = reinterpret_cast<void *>(
reinterpret_cast<uintptr_t>(Entry.Address) + i * PtrSize);
DevPtr = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(res) +
i * PtrSize);
}
} else {
// Indirect function case: Entry.Size should equal PtrSize since we're
// dealing with a single function pointer (not a VTable)
assert(Entry.Size == PtrSize && "Global not a function pointer?");
auto &[HstPtr, DevPtr] = IndirectCallTable.emplace_back();
auto PtrOrErr =
getAndRecordGlobalAddress(Device, Program, Entry.SymbolName);
if (!PtrOrErr)
return PtrOrErr.takeError();
void *Ptr = *PtrOrErr;
HstPtr = Entry.Address;
if (Device.retrieveData(&DevPtr, Ptr, Entry.Size, AsyncInfo))
return createError(ErrorCode::InvalidBinary, "failed to load %s",
Entry.SymbolName);
}
if (Device.synchronize(AsyncInfo))
return createError(ErrorCode::InvalidBinary,
"failed to synchronize after retrieving %s",
Entry.SymbolName);
}
// If we do not have any indirect globals we exit early.
if (IndirectCallTable.empty())
return std::pair{nullptr, 0};
// Sort the array to allow for more efficient lookup of device pointers.
llvm::sort(IndirectCallTable,
[](const auto &x, const auto &y) { return x.first < y.first; });
uint64_t TableSize =
IndirectCallTable.size() * sizeof(std::pair<void *, void *>);
void *DevicePtr = Device.allocData(TableSize, nullptr, TARGET_ALLOC_DEVICE);
if (Device.submitData(DevicePtr, IndirectCallTable.data(), TableSize,
AsyncInfo))
return createError(ErrorCode::InvalidBinary, "failed to copy data");
// The IndirectCallTable is on the stack, so we must synchronize to ensure
// the data is copied before we return.
if (Device.synchronize(AsyncInfo))
return createError(ErrorCode::InvalidBinary,
"failed to synchronize after copying data");
return std::pair<void *, uint64_t>(DevicePtr, IndirectCallTable.size());
}
// Load binary to device and perform global initialization if needed.
llvm::Expected<ProgramTy> DeviceTy::loadBinary(__tgt_device_image *Img) {
auto ProgramOrErr = ProgramTy::create(Context, DeviceHandle, Img);
if (!ProgramOrErr)
return ProgramOrErr.takeError();
ProgramTy Program = std::move(*ProgramOrErr);
// This symbol is optional.
auto DeviceEnvironmentPtrOrErr =
getAndRecordGlobalAddress(*this, Program, "__omp_rtl_device_environment");
if (!DeviceEnvironmentPtrOrErr) {
llvm::consumeError(DeviceEnvironmentPtrOrErr.takeError());
return std::move(Program);
}
void *DeviceEnvironmentPtr = *DeviceEnvironmentPtrOrErr;
// Obtain a table mapping host function pointers to device function pointers.
auto CallTablePairOrErr = setupIndirectCallTable(*this, Img, Program);
if (!CallTablePairOrErr)
return CallTablePairOrErr.takeError();
GenericDeviceTy &GenericDevice = RTL->getDevice(RTLDeviceID);
DeviceEnvironmentTy DeviceEnvironment;
DeviceEnvironment.DeviceDebugKind = GenericDevice.getDebugKind();
DeviceEnvironment.NumDevices = RTL->getNumDevices();
// TODO: The device ID used here is not the real device ID used by OpenMP.
DeviceEnvironment.DeviceNum = RTLDeviceID;
DeviceEnvironment.DynamicMemSize = 0;
DeviceEnvironment.ClockFrequency = GenericDevice.getClockFrequency();
DeviceEnvironment.IndirectCallTable =
reinterpret_cast<uintptr_t>(CallTablePairOrErr->first);
DeviceEnvironment.IndirectCallTableSize = CallTablePairOrErr->second;
DeviceEnvironment.HardwareParallelism =
GenericDevice.getHardwareParallelism();
AsyncInfoTy AsyncInfo(*this);
if (submitData(DeviceEnvironmentPtr, &DeviceEnvironment,
sizeof(DeviceEnvironment), AsyncInfo))
return createError(ErrorCode::InvalidBinary, "failed to copy data");
return std::move(Program);
}
void *DeviceTy::allocData(int64_t Size, void *HstPtr, int32_t Kind) {
/// RAII to establish tool anchors before and after data allocation
void *TargetPtr = nullptr;
OMPT_IF_BUILT(InterfaceRAII TargetDataAllocRAII(
RegionInterface.getCallbacks<ompt_target_data_alloc>(),
DeviceID, HstPtr, &TargetPtr, Size,
/*CodePtr=*/OMPT_GET_RETURN_ADDRESS);)
TargetPtr = RTL->data_alloc(RTLDeviceID, Size, HstPtr, Kind);
return TargetPtr;
}
int32_t DeviceTy::deleteData(void *TgtAllocBegin, int32_t Kind) {
/// RAII to establish tool anchors before and after data deletion
OMPT_IF_BUILT(InterfaceRAII TargetDataDeleteRAII(
RegionInterface.getCallbacks<ompt_target_data_delete>(),
DeviceID, TgtAllocBegin,
/*CodePtr=*/OMPT_GET_RETURN_ADDRESS);)
return RTL->data_delete(RTLDeviceID, TgtAllocBegin, Kind);
}
// Submit data to device
int32_t DeviceTy::submitData(void *TgtPtrBegin, void *HstPtrBegin, int64_t Size,
AsyncInfoTy &AsyncInfo, HostDataToTargetTy *Entry,
MappingInfoTy::HDTTMapAccessorTy *HDTTMapPtr) {
if (getInfoLevel() & OMP_INFOTYPE_DATA_TRANSFER)
MappingInfo.printCopyInfo(TgtPtrBegin, HstPtrBegin, Size, /*H2D=*/true,
Entry, HDTTMapPtr);
/// RAII to establish tool anchors before and after data submit
OMPT_IF_BUILT(
InterfaceRAII TargetDataSubmitRAII(
RegionInterface.getCallbacks<ompt_target_data_transfer_to_device>(),
omp_initial_device, HstPtrBegin, DeviceID, TgtPtrBegin, Size,
/*CodePtr=*/OMPT_GET_RETURN_ADDRESS);)
ol_queue_handle_t queue = AsyncInfo.getQueue();
if (!queue)
return OFFLOAD_FAIL;
if (auto Res = olMemcpy(queue, TgtPtrBegin, DeviceHandle, HstPtrBegin,
PM->getHostDevice(), Size)) {
REPORT() << "Failure to copy data from host to device. Pointers: host "
<< "= " << HstPtrBegin << ", device = " << TgtPtrBegin
<< ", size = " << Size << ": " << Res->Details;
return OFFLOAD_FAIL;
}
return OFFLOAD_SUCCESS;
}
// Retrieve data from device
int32_t DeviceTy::retrieveData(void *HstPtrBegin, void *TgtPtrBegin,
int64_t Size, AsyncInfoTy &AsyncInfo,
HostDataToTargetTy *Entry,
MappingInfoTy::HDTTMapAccessorTy *HDTTMapPtr) {
if (getInfoLevel() & OMP_INFOTYPE_DATA_TRANSFER)
MappingInfo.printCopyInfo(TgtPtrBegin, HstPtrBegin, Size, /*H2D=*/false,
Entry, HDTTMapPtr);
/// RAII to establish tool anchors before and after data retrieval
OMPT_IF_BUILT(
InterfaceRAII TargetDataRetrieveRAII(
RegionInterface.getCallbacks<ompt_target_data_transfer_from_device>(),
DeviceID, TgtPtrBegin, omp_initial_device, HstPtrBegin, Size,
/*CodePtr=*/OMPT_GET_RETURN_ADDRESS);)
ol_queue_handle_t queue = AsyncInfo.getQueue();
if (!queue)
return OFFLOAD_FAIL;
if (auto Res = olMemcpy(queue, HstPtrBegin, PM->getHostDevice(), TgtPtrBegin,
DeviceHandle, Size)) {
REPORT() << "Failure to copy data from device to host. Pointers: host "
<< "= " << HstPtrBegin << ", device = " << TgtPtrBegin
<< ", size = " << Size << ": " << Res->Details;
return OFFLOAD_FAIL;
}
return OFFLOAD_SUCCESS;
}
// Copy data from current device to destination device directly
int32_t DeviceTy::dataExchange(void *SrcPtr, DeviceTy &DstDev, void *DstPtr,
int64_t Size, AsyncInfoTy &AsyncInfo) {
/// RAII to establish tool anchors before and after data exchange
/// Note: Despite the fact that this is a data exchange, we use 'from_device'
/// operation enum (w.r.t. ompt_target_data_op_t) as there is currently
/// no better alternative. It is still possible to distinguish this
/// scenario from a real data retrieve by checking if both involved
/// device numbers are less than omp_get_num_devices().
OMPT_IF_BUILT(
InterfaceRAII TargetDataExchangeRAII(
RegionInterface.getCallbacks<ompt_target_data_transfer_from_device>(),
RTLDeviceID, SrcPtr, DstDev.RTLDeviceID, DstPtr, Size,
/*CodePtr=*/OMPT_GET_RETURN_ADDRESS);)
ol_queue_handle_t queue = AsyncInfo.getQueue();
if (!queue)
return OFFLOAD_FAIL;
if (auto Res = olMemcpy(queue, DstPtr, DstDev.DeviceHandle, SrcPtr,
DeviceHandle, Size)) {
REPORT() << "Failure to copy data from device (" << RTLDeviceID
<< ") to device (" << DstDev.RTLDeviceID
<< "). Pointers: host = " << SrcPtr << ", device = " << DstPtr
<< ", size = " << Size << ": " << Res->Details;
return OFFLOAD_FAIL;
}
return OFFLOAD_SUCCESS;
}
llvm::Expected<void *> DeviceTy::registerMemory(void *HstPtr, int64_t Size,
bool LockMemory) {
void *LockedPtr = nullptr;
ol_memory_register_flags_t Flags =
LockMemory ? OL_MEMORY_REGISTER_FLAG_LOCK_MEMORY : 0;
if (auto Res = olMemRegister(DeviceHandle, HstPtr, Size, Flags, &LockedPtr))
return createError(ErrorCode::Unknown, "failed to lock memory %p: %s",
HstPtr, Res->Details);
return LockedPtr;
}
llvm::Error DeviceTy::unregisterMemory(void *HstPtr, bool UnlockMemory) {
ol_memory_register_flags_t Flags =
UnlockMemory ? OL_MEMORY_REGISTER_FLAG_UNLOCK_MEMORY : 0;
if (auto Res = olMemUnregister(DeviceHandle, HstPtr, Flags))
return createError(ErrorCode::Unknown, "failed to unlock memory %p: %s",
HstPtr, Res->Details);
return llvm::Error::success();
}
int32_t DeviceTy::notifyDataMapped(void *HstPtr, int64_t Size) {
ODBG(ODT_Mapping) << "Notifying about new mapping: HstPtr=" << HstPtr
<< ", Size=" << Size;
auto LockedPtrOrErr = registerMemory(HstPtr, Size, LockMappedBuffers);
if (!LockedPtrOrErr) {
if (!IgnoreLockMappedFailures) {
REPORT() << "Notifying about data mapping failed: "
<< llvm::toString(LockedPtrOrErr.takeError());
return OFFLOAD_FAIL;
}
llvm::consumeError(LockedPtrOrErr.takeError());
}
return OFFLOAD_SUCCESS;
}
int32_t DeviceTy::notifyDataUnmapped(void *HstPtr) {
ODBG(ODT_Mapping) << "Notifying about an unmapping: HstPtr=" << HstPtr;
if (auto Err = unregisterMemory(HstPtr, LockMappedBuffers)) {
if (!IgnoreLockMappedFailures) {
REPORT() << "Notifying about data unmapping failed: "
<< llvm::toString(std::move(Err));
return OFFLOAD_FAIL;
}
llvm::consumeError(std::move(Err));
}
return OFFLOAD_SUCCESS;
}
/// Resolve \p NumArgs (base pointer, offset) pairs into a flattened array of
/// argument-value pointers suitable for a kernel launch, writing the result
/// into \p LaunchArgs.NumArgs/Args.
static void resolveKernelLaunchParams(void **const TgtArgs,
ptrdiff_t *const TgtOffsets,
uint32_t NumArgs,
llvm::SmallVector<void *> &Args,
llvm::SmallVector<void *> &Ptrs,
KernelLaunchArgsTy &LaunchArgs) {
LaunchArgs.NumArgs = NumArgs;
Args.resize(NumArgs);
Ptrs.resize(NumArgs);
if (NumArgs == 0)
return;
for (uint32_t I = 0; I < NumArgs; ++I) {
Args[I] = reinterpret_cast<void *>(reinterpret_cast<intptr_t>(TgtArgs[I]) +
TgtOffsets[I]);
Ptrs[I] = &Args[I];
}
LaunchArgs.Args = &Ptrs[0];
}
// Run region on device
int32_t DeviceTy::launchKernel(void *TgtEntryPtr, void **TgtVarsPtr,
ptrdiff_t *TgtOffsets, KernelArgsTy &KernelArgs,
KernelReplayOutcomeTy *ReplayOutcome,
AsyncInfoTy &AsyncInfo) {
llvm::SmallVector<void *> Args, Ptrs;
llvm::SmallVector<int64_t> ArgSizes;
KernelLaunchArgsTy LaunchArgs;
LaunchArgs.OmpABIVersion = KernelArgs.Version;
LaunchArgs.ReplayOutcome = ReplayOutcome;
LaunchArgs.ArgSizes = KernelArgs.ArgSizes;
LaunchArgs.Tripcount = KernelArgs.Tripcount;
LaunchArgs.DynCGroupMem = KernelArgs.DynCGroupMem;
llvm::copy(KernelArgs.UserNumBlocks, LaunchArgs.UserNumBlocks);
llvm::copy(KernelArgs.UserThreadLimit, LaunchArgs.UserThreadLimit);
LaunchArgs.Flags.Cooperative = KernelArgs.Flags.Cooperative;
LaunchArgs.Flags.StrictBlocks = KernelArgs.Flags.StrictBlocks;
LaunchArgs.Flags.StrictThreads = KernelArgs.Flags.StrictThreads;
LaunchArgs.Flags.DynCGroupMemFallback = KernelArgs.Flags.DynCGroupMemFallback;
if (KernelArgs.Flags.IsCUDA) {
// Kernel languages (CUDA/HIP) pass an already-flattened argument-pointer
// array through KernelArgs.ArgPtrs instead of using the OpenMP
// base-pointer/offset argument scheme.
auto *LaunchParams =
reinterpret_cast<KernelLaunchParamsTy *>(KernelArgs.ArgPtrs);
LaunchArgs.NumArgs = LaunchParams->NumArgs;
LaunchArgs.Args = LaunchParams->Args;
} else {
resolveKernelLaunchParams(TgtVarsPtr, TgtOffsets, KernelArgs.NumArgs, Args,
Ptrs, LaunchArgs);
// The dyn_ptr slot is reserved by the host (version >= 4) or by
// upgradeKernelArgs (version 3) as the last element of the argument
// array. Version 3 device kernels expect it first instead, so rotate it
// to the front to match that ABI.
if (KernelArgs.NumArgs > 0 &&
KernelArgs.Version >= OMP_KERNEL_ARG_MIN_VERSION_WITH_DYN_PTR) {
if (KernelArgs.Version == OMP_KERNEL_ARG_MIN_VERSION_WITH_DYN_PTR) {
std::rotate(Args.begin(), Args.end() - 1, Args.end());
LaunchArgs.DynPtrSlot = &Args[0];
// Keep ArgSizes in sync with the rotated Args, if present.
if (LaunchArgs.ArgSizes) {
ArgSizes.assign(LaunchArgs.ArgSizes,
LaunchArgs.ArgSizes + KernelArgs.NumArgs);
std::rotate(ArgSizes.begin(), ArgSizes.end() - 1, ArgSizes.end());
LaunchArgs.ArgSizes = ArgSizes.data();
}
} else {
LaunchArgs.DynPtrSlot = &Args[KernelArgs.NumArgs - 1];
}
}
}
return RTL->launch_kernel(RTLDeviceID, TgtEntryPtr, LaunchArgs, AsyncInfo);
}
static llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
ol_device_type_t Value) {
switch (Value) {
case OL_DEVICE_TYPE_DEFAULT:
return OS << "DEFAULT";
case OL_DEVICE_TYPE_ALL:
return OS << "ALL";
case OL_DEVICE_TYPE_GPU:
return OS << "GPU";
case OL_DEVICE_TYPE_CPU:
return OS << "CPU";
case OL_DEVICE_TYPE_HOST:
return OS << "HOST";
default:
return OS << "<< INVALID >>";
}
}
static llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
const ol_dimensions_t &Value) {
return OS << "{x: " << Value.x << ", y: " << Value.y << ", z: " << Value.z
<< "}";
}
static void printFPCapabilityFlags(llvm::raw_ostream &OS,
ol_device_fp_capability_flags_t Value) {
OS << Value << " {";
if (Value & OL_DEVICE_FP_CAPABILITY_FLAG_CORRECTLY_ROUNDED_DIVIDE_SQRT)
OS << " CORRECTLY_ROUNDED_DIVIDE_SQRT";
if (Value & OL_DEVICE_FP_CAPABILITY_FLAG_ROUND_TO_NEAREST)
OS << " ROUND_TO_NEAREST";
if (Value & OL_DEVICE_FP_CAPABILITY_FLAG_ROUND_TO_ZERO)
OS << " ROUND_TO_ZERO";
if (Value & OL_DEVICE_FP_CAPABILITY_FLAG_ROUND_TO_INF)
OS << " ROUND_TO_INF";
if (Value & OL_DEVICE_FP_CAPABILITY_FLAG_INF_NAN)
OS << " INF_NAN";
if (Value & OL_DEVICE_FP_CAPABILITY_FLAG_DENORM)
OS << " DENORM";
if (Value & OL_DEVICE_FP_CAPABILITY_FLAG_FMA)
OS << " FMA";
if (Value & OL_DEVICE_FP_CAPABILITY_FLAG_SOFT_FLOAT)
OS << " SOFT_FLOAT";
OS << " }";
}
// Print a scalar liboffload device info property, if supported.
template <typename T>
static void printDeviceInfoScalar(ol_device_handle_t DeviceHandle,
ol_device_info_t PropName,
llvm::StringRef Label,
llvm::StringRef Units = "") {
T Value{};
if (olGetDeviceInfo(DeviceHandle, PropName, sizeof(Value), &Value))
return;
llvm::outs() << " " << Label << ": " << Value;
if (!Units.empty())
llvm::outs() << " " << Units;
llvm::outs() << "\n";
}
// Print a boolean liboffload device info property, if supported.
static void printDeviceInfoBool(ol_device_handle_t DeviceHandle,
ol_device_info_t PropName,
llvm::StringRef Label) {
bool Value = false;
if (olGetDeviceInfo(DeviceHandle, PropName, sizeof(Value), &Value))
return;
llvm::outs() << " " << Label << ": " << (Value ? "Yes" : "No") << "\n";
}
// Print a floating point capability liboffload device info property, if
// supported.
static void printDeviceInfoFPCapability(ol_device_handle_t DeviceHandle,
ol_device_info_t PropName,
llvm::StringRef Label) {
ol_device_fp_capability_flags_t Value{};
if (olGetDeviceInfo(DeviceHandle, PropName, sizeof(Value), &Value))
return;
llvm::outs() << " " << Label << ": ";
printFPCapabilityFlags(llvm::outs(), Value);
llvm::outs() << "\n";
}
// Print a string liboffload device info property, if supported.
static void printDeviceInfoString(ol_device_handle_t DeviceHandle,
ol_device_info_t PropName,
llvm::StringRef Label) {
size_t Size = 0;
if (olGetDeviceInfoSize(DeviceHandle, PropName, &Size) || Size == 0)
return;
llvm::SmallVector<char> Value(Size);
if (olGetDeviceInfo(DeviceHandle, PropName, Size, Value.data()))
return;
llvm::outs() << " " << Label << ": " << Value.data() << "\n";
}
bool DeviceTy::printDeviceInfo() {
llvm::outs() << "Device " << DeviceID << ":\n";
printDeviceInfoScalar<ol_device_type_t>(DeviceHandle, OL_DEVICE_INFO_TYPE,
"Type");
printDeviceInfoScalar<ol_platform_handle_t>(
DeviceHandle, OL_DEVICE_INFO_PLATFORM, "Platform");
printDeviceInfoString(DeviceHandle, OL_DEVICE_INFO_NAME, "Name");
printDeviceInfoString(DeviceHandle, OL_DEVICE_INFO_PRODUCT_NAME,
"Product Name");
printDeviceInfoString(DeviceHandle, OL_DEVICE_INFO_UID, "UID");
printDeviceInfoString(DeviceHandle, OL_DEVICE_INFO_VENDOR, "Vendor");
printDeviceInfoString(DeviceHandle, OL_DEVICE_INFO_DRIVER_VERSION,
"Driver Version");
printDeviceInfoScalar<uint32_t>(
DeviceHandle, OL_DEVICE_INFO_MAX_WORK_GROUP_SIZE, "Max Work Group Size");
printDeviceInfoScalar<ol_dimensions_t>(
DeviceHandle, OL_DEVICE_INFO_MAX_WORK_GROUP_SIZE_PER_DIMENSION,
"Max Work Group Size Per Dimension");
printDeviceInfoScalar<uint32_t>(DeviceHandle, OL_DEVICE_INFO_MAX_WORK_SIZE,
"Max Work Size");
printDeviceInfoScalar<ol_dimensions_t>(
DeviceHandle, OL_DEVICE_INFO_MAX_WORK_SIZE_PER_DIMENSION,
"Max Work Size Per Dimension");
printDeviceInfoScalar<uint32_t>(DeviceHandle, OL_DEVICE_INFO_VENDOR_ID,
"Vendor ID");
printDeviceInfoScalar<uint32_t>(DeviceHandle,
OL_DEVICE_INFO_NUM_COMPUTE_UNITS,
"Number of Compute Units");
printDeviceInfoScalar<uint32_t>(DeviceHandle,
OL_DEVICE_INFO_MAX_CLOCK_FREQUENCY,
"Max Clock Frequency", "MHz");
printDeviceInfoScalar<uint32_t>(DeviceHandle,
OL_DEVICE_INFO_MEMORY_CLOCK_RATE,
"Memory Clock Rate", "MHz");
printDeviceInfoScalar<uint32_t>(DeviceHandle, OL_DEVICE_INFO_ADDRESS_BITS,
"Address Bits");
printDeviceInfoScalar<uint64_t>(DeviceHandle,
OL_DEVICE_INFO_MAX_MEM_ALLOC_SIZE,
"Max Memory Allocation Size", "B");
printDeviceInfoScalar<uint64_t>(DeviceHandle, OL_DEVICE_INFO_GLOBAL_MEM_SIZE,
"Global Memory Size", "B");
printDeviceInfoScalar<uint64_t>(DeviceHandle,
OL_DEVICE_INFO_WORK_GROUP_LOCAL_MEM_SIZE,
"Work Group Local Memory Size", "B");
printDeviceInfoScalar<uint32_t>(DeviceHandle, OL_DEVICE_INFO_NUM_LANES,
"Number of Lanes");
printDeviceInfoBool(DeviceHandle, OL_DEVICE_INFO_SINGLE_FP_SUPPORT,
"Single Precision Floating Point Support");
printDeviceInfoFPCapability(DeviceHandle, OL_DEVICE_INFO_SINGLE_FP_CONFIG,
"Single Precision Floating Point Capability");
printDeviceInfoBool(DeviceHandle, OL_DEVICE_INFO_DOUBLE_FP_SUPPORT,
"Double Precision Floating Point Support");
printDeviceInfoFPCapability(DeviceHandle, OL_DEVICE_INFO_DOUBLE_FP_CONFIG,
"Double Precision Floating Point Capability");
printDeviceInfoBool(DeviceHandle, OL_DEVICE_INFO_HALF_FP_SUPPORT,
"Half Precision Floating Point Support");
printDeviceInfoFPCapability(DeviceHandle, OL_DEVICE_INFO_HALF_FP_CONFIG,
"Half Precision Floating Point Capability");
printDeviceInfoScalar<uint32_t>(DeviceHandle,
OL_DEVICE_INFO_NATIVE_VECTOR_WIDTH_CHAR,
"Native Vector Width For Char");
printDeviceInfoScalar<uint32_t>(DeviceHandle,
OL_DEVICE_INFO_NATIVE_VECTOR_WIDTH_SHORT,
"Native Vector Width For Short");
printDeviceInfoScalar<uint32_t>(DeviceHandle,
OL_DEVICE_INFO_NATIVE_VECTOR_WIDTH_INT,
"Native Vector Width For Int");
printDeviceInfoScalar<uint32_t>(DeviceHandle,
OL_DEVICE_INFO_NATIVE_VECTOR_WIDTH_LONG,
"Native Vector Width For Long");
printDeviceInfoScalar<uint32_t>(DeviceHandle,
OL_DEVICE_INFO_NATIVE_VECTOR_WIDTH_FLOAT,
"Native Vector Width For Float");
printDeviceInfoScalar<uint32_t>(DeviceHandle,
OL_DEVICE_INFO_NATIVE_VECTOR_WIDTH_DOUBLE,
"Native Vector Width For Double");
printDeviceInfoScalar<uint32_t>(DeviceHandle,
OL_DEVICE_INFO_NATIVE_VECTOR_WIDTH_HALF,
"Native Vector Width For Half");
printDeviceInfoBool(DeviceHandle, OL_DEVICE_INFO_COOPERATIVE_LAUNCH_SUPPORT,
"Cooperative Kernel Launch Support");
printDeviceInfoScalar<uint32_t>(DeviceHandle, OL_DEVICE_INFO_DRIVER_ID,
"Driver ID");
return true;
}
int32_t DeviceTy::synchronize(AsyncInfoTy &AsyncInfo) {
ol_queue_handle_t Queue = AsyncInfo.getQueue();
if (!Queue)
return OFFLOAD_SUCCESS;
if (auto Res = olSyncQueue(Queue)) {
REPORT() << "Failure to synchronize stream " << Queue << ": "
<< Res->Details;
return OFFLOAD_FAIL;
}
return OFFLOAD_SUCCESS;
}
int32_t DeviceTy::queryAsync(AsyncInfoTy &AsyncInfo) {
ol_queue_handle_t Queue = AsyncInfo.getQueue();
if (!Queue)
return OFFLOAD_SUCCESS;
bool isComplete;
if (auto Res = olQueryQueue(Queue, &isComplete)) {
REPORT() << "Failure to query stream " << Queue << ": " << Res->Details;
return OFFLOAD_FAIL;
}
return OFFLOAD_SUCCESS;
}
int32_t DeviceTy::recordEvent(void **Event, AsyncInfoTy &AsyncInfo) {
ol_event_handle_t NewEvent;
if (auto Res =
olCreateEvent(AsyncInfo.getQueue(), OL_EVENT_FLAGS_NONE, &NewEvent)) {
REPORT() << "Failure to record event: " << Res->Details;
return OFFLOAD_FAIL;
}
if (*Event) {
if (auto Res = olDestroyEvent(static_cast<ol_event_handle_t>(*Event)))
REPORT() << "Failure to destroy previous event " << *Event << ": "
<< Res->Details;
}
*Event = NewEvent;
return OFFLOAD_SUCCESS;
}
int32_t DeviceTy::waitEvent(void *Event, AsyncInfoTy &AsyncInfo) {
ol_event_handle_t E = static_cast<ol_event_handle_t>(Event);
if (auto Res = olWaitEvents(AsyncInfo.getQueue(), &E, 1)) {
REPORT() << "Failure to wait for event " << Event << ": " << Res->Details;
return OFFLOAD_FAIL;
}
return OFFLOAD_SUCCESS;
}
int32_t DeviceTy::syncEvent(void *Event) {
if (auto Res = olSyncEvent(static_cast<ol_event_handle_t>(Event))) {
REPORT() << "Failure to synchronize event " << Event << ": "
<< Res->Details;
return OFFLOAD_FAIL;
}
return OFFLOAD_SUCCESS;
}
int32_t DeviceTy::destroyEvent(void *Event) {
if (auto Res = olDestroyEvent(static_cast<ol_event_handle_t>(Event))) {
REPORT() << "Failure to destroy event " << Event << ": " << Res->Details;
return OFFLOAD_FAIL;
}
return OFFLOAD_SUCCESS;
}
void DeviceTy::dumpOffloadEntries() {
fprintf(stderr, "Device %i offload entries:\n", DeviceID);
for (auto &It : *DeviceOffloadEntries.getExclusiveAccessor()) {
const char *Kind = "kernel";
if (It.second.isLink())
Kind = "link";
else if (It.second.isGlobal())
Kind = "global var.";
fprintf(stderr, " %11s: %s\n", Kind, It.second.getNameAsCStr());
}
}
bool DeviceTy::useAutoZeroCopy() {
if (PM->getRequirements() & OMP_REQ_UNIFIED_SHARED_MEMORY)
return false;
return RTL->use_auto_zero_copy(RTLDeviceID);
}
bool DeviceTy::isAccessiblePtr(const void *Ptr, size_t Size) {
bool IsAccessible = false;
if (auto Res = olMemIsAccessible(DeviceHandle, Ptr, Size, &IsAccessible)) {
REPORT() << "Failure to check pointer accessibility: " << Res->Details;
return false;
}
return IsAccessible;
}