Files
TheRock/compiler/amd-llvm/compiler-rt/lib/profile/InstrProfilingPlatformROCm.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

894 lines
32 KiB
C++

//===- InstrProfilingPlatformROCm.cpp - Profile data ROCm platform -------===//
//
// 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
//
//===----------------------------------------------------------------------===//
extern "C" {
#include "InstrProfiling.h"
#include "InstrProfilingInternal.h"
#include "InstrProfilingPort.h"
}
#include "interception/interception.h"
// C library headers (not <cstdio> etc.): clang_rt.profile is built with
// -nostdinc++ and avoids the C++ standard library (see profile/CMakeLists.txt).
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <dlfcn.h>
#include <pthread.h>
#endif
/* Serialize one-time HIP loader resolution and DynamicModules mutations.
* Inline to avoid a sanitizer_common dependency. */
#ifdef _WIN32
static INIT_ONCE HipLoadedOnce = INIT_ONCE_STATIC_INIT;
static CRITICAL_SECTION DynamicModulesLock;
static INIT_ONCE DynamicModulesLockInit = INIT_ONCE_STATIC_INIT;
static BOOL CALLBACK initDynamicModulesLockCb(PINIT_ONCE, PVOID, PVOID *) {
InitializeCriticalSection(&DynamicModulesLock);
return TRUE;
}
static void lockDynamicModules(void) {
InitOnceExecuteOnce(&DynamicModulesLockInit, initDynamicModulesLockCb, NULL,
NULL);
EnterCriticalSection(&DynamicModulesLock);
}
static void unlockDynamicModules(void) {
LeaveCriticalSection(&DynamicModulesLock);
}
#else
static pthread_once_t HipLoadedOnce = PTHREAD_ONCE_INIT;
static pthread_mutex_t DynamicModulesLock = PTHREAD_MUTEX_INITIALIZER;
static void lockDynamicModules(void) {
pthread_mutex_lock(&DynamicModulesLock);
}
static void unlockDynamicModules(void) {
pthread_mutex_unlock(&DynamicModulesLock);
}
#endif
static int processDeviceOffloadPrf(void *DeviceOffloadPrf, int TUIndex,
const char *Target);
static int isVerboseMode() {
static int IsVerbose = -1;
if (IsVerbose == -1)
IsVerbose = getenv("LLVM_PROFILE_VERBOSE") != nullptr;
return IsVerbose;
}
/* -------------------------------------------------------------------------- */
/* Dynamic loading of HIP runtime symbols */
/* -------------------------------------------------------------------------- */
typedef int (*hipGetSymbolAddressTy)(void **, const void *);
typedef int (*hipMemcpyTy)(void *, const void *, size_t, int);
typedef int (*hipModuleGetGlobalTy)(void **, size_t *, void *, const char *);
typedef int (*hipGetDeviceCountTy)(int *);
typedef int (*hipGetDeviceTy)(int *);
typedef int (*hipSetDeviceTy)(int);
/* Minimal hipDeviceProp_t (HIP 6.x R0600): only gcnArchName at offset 1160
* is read. Padded to 4096 to tolerate ABI growth. */
typedef struct {
char padding[1160];
char gcnArchName[256];
char tail_padding[2680];
} HipDevicePropMinimal;
typedef int (*hipGetDevicePropertiesTy)(HipDevicePropMinimal *, int);
static hipGetSymbolAddressTy pHipGetSymbolAddress = nullptr;
static hipMemcpyTy pHipMemcpy = nullptr;
static hipModuleGetGlobalTy pHipModuleGetGlobal = nullptr;
static hipGetDeviceCountTy pHipGetDeviceCount = nullptr;
static hipGetDeviceTy pHipGetDevice = nullptr;
static hipSetDeviceTy pHipSetDevice = nullptr;
static hipGetDevicePropertiesTy pHipGetDeviceProperties = nullptr;
static int NumDevices = 0;
/* 256 matches hipDeviceProp_t::gcnArchName, the source field width. */
static char (*DeviceArchNames)[256] = nullptr;
/* -------------------------------------------------------------------------- */
/* Device-to-host copies */
/* Keep HIP-only to avoid an HSA dependency. */
/* -------------------------------------------------------------------------- */
static void doEnsureHipLoaded(void) {
if (!__interception::DynamicLoaderAvailable()) {
if (isVerboseMode())
PROF_NOTE("%s", "Dynamic library loading not available - "
"HIP profiling disabled\n");
return;
}
#ifdef _WIN32
static const char HipLibName[] = "amdhip64.dll";
#else
static const char HipLibName[] = "libamdhip64.so";
#endif
void *Handle = __interception::OpenLibrary(HipLibName);
if (!Handle)
return;
pHipGetSymbolAddress = (hipGetSymbolAddressTy)__interception::LookupSymbol(
Handle, "hipGetSymbolAddress");
pHipMemcpy = (hipMemcpyTy)__interception::LookupSymbol(Handle, "hipMemcpy");
pHipModuleGetGlobal = (hipModuleGetGlobalTy)__interception::LookupSymbol(
Handle, "hipModuleGetGlobal");
pHipGetDeviceCount = (hipGetDeviceCountTy)__interception::LookupSymbol(
Handle, "hipGetDeviceCount");
pHipGetDevice =
(hipGetDeviceTy)__interception::LookupSymbol(Handle, "hipGetDevice");
pHipSetDevice =
(hipSetDeviceTy)__interception::LookupSymbol(Handle, "hipSetDevice");
pHipGetDeviceProperties =
(hipGetDevicePropertiesTy)__interception::LookupSymbol(
Handle, "hipGetDevicePropertiesR0600");
if (!pHipGetDeviceProperties)
pHipGetDeviceProperties =
(hipGetDevicePropertiesTy)__interception::LookupSymbol(
Handle, "hipGetDeviceProperties");
if (pHipGetDeviceCount && pHipGetDeviceProperties) {
int Count = 0;
if (pHipGetDeviceCount(&Count) == 0 && Count > 0) {
DeviceArchNames = (char (*)[256])calloc(Count, sizeof(*DeviceArchNames));
if (!DeviceArchNames) {
PROF_ERR("%s\n", "failed to allocate device arch name table");
return;
}
HipDevicePropMinimal Prop;
for (int i = 0; i < Count; ++i) {
__builtin_memset(&Prop, 0, sizeof(Prop));
if (pHipGetDeviceProperties(&Prop, i) == 0) {
strncpy(DeviceArchNames[i], Prop.gcnArchName,
sizeof(DeviceArchNames[i]) - 1);
DeviceArchNames[i][sizeof(DeviceArchNames[i]) - 1] = '\0';
if (isVerboseMode())
PROF_NOTE("Device %d arch: %s\n", i, DeviceArchNames[i]);
}
}
NumDevices = Count;
}
}
}
#ifdef _WIN32
static BOOL CALLBACK ensureHipLoadedCb(PINIT_ONCE, PVOID, PVOID *) {
doEnsureHipLoaded();
return TRUE;
}
#endif
static void ensureHipLoaded(void) {
#ifdef _WIN32
InitOnceExecuteOnce(&HipLoadedOnce, ensureHipLoadedCb, NULL, NULL);
#else
pthread_once(&HipLoadedOnce, doEnsureHipLoaded);
#endif
}
/* -------------------------------------------------------------------------- */
/* Public wrappers that forward to the loaded HIP symbols */
/* -------------------------------------------------------------------------- */
static int hipGetSymbolAddress(void **devPtr, const void *symbol) {
ensureHipLoaded();
return pHipGetSymbolAddress ? pHipGetSymbolAddress(devPtr, symbol) : -1;
}
static int hipMemcpy(void *dest, const void *src, size_t len,
int kind /*2=DToH*/) {
ensureHipLoaded();
return pHipMemcpy ? pHipMemcpy(dest, src, len, kind) : -1;
}
/* Device section symbols must be registered with CLR first; otherwise
* hipMemcpy may take a CPU path and crash. */
static int memcpyDeviceToHost(void *Dst, const void *Src, size_t Size) {
return hipMemcpy(Dst, Src, Size, 2 /* DToH */);
}
static int hipModuleGetGlobal(void **DevPtr, size_t *Bytes, void *Module,
const char *Name) {
ensureHipLoaded();
return pHipModuleGetGlobal ? pHipModuleGetGlobal(DevPtr, Bytes, Module, Name)
: -1;
}
static int hipGetDevice(int *DeviceId) {
ensureHipLoaded();
return pHipGetDevice ? pHipGetDevice(DeviceId) : -1;
}
__attribute__((unused))
static int hipSetDevice(int DeviceId) {
ensureHipLoaded();
return pHipSetDevice ? pHipSetDevice(DeviceId) : -1;
}
static const char *getDeviceArchName(int DeviceId) {
if (DeviceId < 0 || DeviceId >= NumDevices || !DeviceArchNames[DeviceId][0])
return "amdgpu";
return DeviceArchNames[DeviceId];
}
/* -------------------------------------------------------------------------- */
/* Dynamic module tracking */
/* -------------------------------------------------------------------------- */
/* Per-TU profile entry inside a dynamic module.
* A single dynamic module may contain multiple TUs (e.g. -fgpu-rdc). */
typedef struct {
void *DeviceVar; /* device address of __llvm_profile_sections_<CUID> */
int Processed; /* 0 = not yet collected, 1 = data already copied */
} OffloadDynamicTUInfo;
/* One entry per hipModuleLoad call. */
typedef struct {
void *ModulePtr; /* hipModule_t handle */
OffloadDynamicTUInfo *TUs; /* array of per-TU entries */
int NumTUs;
int CapTUs;
} OffloadDynamicModuleInfo;
static OffloadDynamicModuleInfo *DynamicModules = nullptr;
static int NumDynamicModules = 0;
static int CapDynamicModules = 0;
/* -------------------------------------------------------------------------- */
/* ELF symbol enumeration (manual parse: compiler-rt cannot link LLVM Support)
*/
/* -------------------------------------------------------------------------- */
#if __has_include(<elf.h>)
#include <elf.h>
/* Callback invoked for every matching symbol name found in the ELF image.
* Return 0 to continue iteration, non-zero to stop. */
typedef int (*SymbolCallback)(const char *Name, void *UserData);
/* If Image is a clang offload bundle, return a pointer to the first embedded
* ELF. Returns Image if not a bundle, nullptr if a bundle holds no ELF. */
static const void *unwrapOffloadBundle(const void *Image) {
static const char BundleMagic[] = "__CLANG_OFFLOAD_BUNDLE__";
if (memcmp(Image, BundleMagic, sizeof(BundleMagic) - 1) != 0)
return Image; /* Not a bundle, return as-is. */
const char *Buf = (const char *)Image;
uint64_t NumEntries;
__builtin_memcpy(&NumEntries, Buf + sizeof(BundleMagic) - 1,
sizeof(uint64_t));
/* Walk the entry table (starts at offset 32). */
const char *Cursor = Buf + 32;
for (uint64_t I = 0; I < NumEntries; ++I) {
uint64_t EntryOffset, EntrySize, IDSize;
__builtin_memcpy(&EntryOffset, Cursor, sizeof(EntryOffset));
Cursor += sizeof(EntryOffset);
__builtin_memcpy(&EntrySize, Cursor, sizeof(EntrySize));
Cursor += sizeof(EntrySize);
__builtin_memcpy(&IDSize, Cursor, sizeof(IDSize));
Cursor += sizeof(IDSize);
Cursor += IDSize; /* skip entry ID */
if (EntrySize >= sizeof(Elf64_Ehdr)) {
const Elf64_Ehdr *E = (const Elf64_Ehdr *)(Buf + EntryOffset);
if (E->e_ident[EI_MAG0] == ELFMAG0 && E->e_ident[EI_MAG1] == ELFMAG1 &&
E->e_ident[EI_MAG2] == ELFMAG2 && E->e_ident[EI_MAG3] == ELFMAG3) {
return (const void *)(Buf + EntryOffset);
}
}
}
PROF_WARN("%s", "offload bundle contains no valid ELF entries\n");
return nullptr;
}
/* Invoke CB for every global symbol in Image (an AMDGPU ELF or offload bundle)
* whose name starts with PREFIX. Image may be null. */
static void enumerateElfSymbols(const void *Image, const char *Prefix,
SymbolCallback CB, void *UserData) {
if (!Image)
return;
Image = unwrapOffloadBundle(Image);
if (!Image)
return;
const Elf64_Ehdr *Ehdr = (const Elf64_Ehdr *)Image;
if (Ehdr->e_ident[EI_MAG0] != ELFMAG0 || Ehdr->e_ident[EI_MAG1] != ELFMAG1 ||
Ehdr->e_ident[EI_MAG2] != ELFMAG2 || Ehdr->e_ident[EI_MAG3] != ELFMAG3) {
if (isVerboseMode())
PROF_NOTE("%s", "Image is not a valid ELF, skipping enumeration\n");
return;
}
size_t PrefixLen = strlen(Prefix);
const char *Base = (const char *)Image;
const Elf64_Shdr *Shdrs = (const Elf64_Shdr *)(Base + Ehdr->e_shoff);
for (int i = 0; i < Ehdr->e_shnum; ++i) {
if (Shdrs[i].sh_type != SHT_SYMTAB)
continue;
const Elf64_Sym *Syms = (const Elf64_Sym *)(Base + Shdrs[i].sh_offset);
int NumSyms = Shdrs[i].sh_size / sizeof(Elf64_Sym);
/* String table is the section referenced by sh_link. */
const char *StrTab = Base + Shdrs[Shdrs[i].sh_link].sh_offset;
for (int j = 0; j < NumSyms; ++j) {
if (Syms[j].st_name == 0)
continue;
const char *Name = StrTab + Syms[j].st_name;
if (strncmp(Name, Prefix, PrefixLen) == 0) {
if (CB(Name, UserData))
return;
}
}
}
}
/* State passed through the enumeration callback. */
typedef struct {
void *Module; /* hipModule_t */
OffloadDynamicModuleInfo *ModInfo;
} EnumState;
/* Register one __llvm_profile_sections_<CUID> symbol on the module entry.
* hipModuleGetGlobal also registers the device address with CLR so hipMemcpy
* can copy from it later. */
static int registerPrfSymbol(const char *Name, void *UserData) {
EnumState *S = (EnumState *)UserData;
OffloadDynamicModuleInfo *MI = S->ModInfo;
/* The symbol is the per-TU sections struct itself, not a pointer
* indirection, so this address is the hipMemcpy source. */
void *DeviceVar = nullptr;
size_t Bytes = 0;
if (hipModuleGetGlobal(&DeviceVar, &Bytes, S->Module, Name) != 0) {
PROF_WARN("failed to get symbol %s for module %p\n", Name, S->Module);
return 0; /* continue */
}
if (MI->NumTUs >= MI->CapTUs) {
int NewCap = MI->CapTUs ? MI->CapTUs * 2 : 4;
OffloadDynamicTUInfo *New = (OffloadDynamicTUInfo *)realloc(
MI->TUs, NewCap * sizeof(OffloadDynamicTUInfo));
if (!New) {
PROF_ERR("%s\n", "failed to grow TU array");
return 0;
}
MI->TUs = New;
MI->CapTUs = NewCap;
}
OffloadDynamicTUInfo *TU = &MI->TUs[MI->NumTUs++];
TU->DeviceVar = DeviceVar;
TU->Processed = 0;
(void)Name;
return 0; /* continue enumeration */
}
#endif /* __has_include(<elf.h>) */
/* -------------------------------------------------------------------------- */
/* Registration / un-registration helpers */
/* -------------------------------------------------------------------------- */
extern "C" void
__llvm_profile_offload_register_dynamic_module(int ModuleLoadRc, void **Ptr,
const void *Image) {
if (ModuleLoadRc)
return;
lockDynamicModules();
if (isVerboseMode())
PROF_NOTE("Registering loaded module %d: rc=%d, module=%p, image=%p\n",
NumDynamicModules, ModuleLoadRc, *Ptr, Image);
if (NumDynamicModules >= CapDynamicModules) {
int NewCap = CapDynamicModules ? CapDynamicModules * 2 : 64;
OffloadDynamicModuleInfo *New = (OffloadDynamicModuleInfo *)realloc(
DynamicModules, NewCap * sizeof(OffloadDynamicModuleInfo));
if (!New) {
unlockDynamicModules();
return;
}
DynamicModules = New;
CapDynamicModules = NewCap;
}
OffloadDynamicModuleInfo *MI = &DynamicModules[NumDynamicModules++];
MI->ModulePtr = *Ptr;
MI->TUs = nullptr;
MI->NumTUs = 0;
MI->CapTUs = 0;
/* Dynamic-module profiling needs ELF parsing for symbol enumeration. */
#if __has_include(<elf.h>)
EnumState State = {*Ptr, MI};
enumerateElfSymbols(Image, "__llvm_profile_sections_", registerPrfSymbol,
&State);
#else
(void)Image;
if (isVerboseMode())
PROF_NOTE("%s",
"Dynamic module profiling not supported on this platform\n");
#endif
if (MI->NumTUs == 0) {
PROF_WARN("no __llvm_profile_sections_* symbols found in module %p\n",
*Ptr);
} else if (isVerboseMode()) {
PROF_NOTE("Module %p: registered %d TU(s)\n", *Ptr, MI->NumTUs);
}
unlockDynamicModules();
}
extern "C" void __llvm_profile_offload_unregister_dynamic_module(void *Ptr) {
lockDynamicModules();
for (int i = 0; i < NumDynamicModules; ++i) {
OffloadDynamicModuleInfo *MI = &DynamicModules[i];
/* HIP recycles hipModule_t addresses; drained slots are cleared so a
* recycled handle finds the new slot, not the dead one. */
if (MI->ModulePtr != Ptr)
continue;
if (isVerboseMode())
PROF_NOTE("Unregistering module %p (%d TUs)\n", MI->ModulePtr,
MI->NumTUs);
static int NextTUIndex = 0;
for (int t = 0; t < MI->NumTUs; ++t) {
OffloadDynamicTUInfo *TU = &MI->TUs[t];
if (TU->Processed) {
if (isVerboseMode())
PROF_NOTE("Module %p TU %d already processed, skipping\n", Ptr, t);
continue;
}
int TUIndex = __atomic_fetch_add(&NextTUIndex, 1, __ATOMIC_RELAXED);
if (TU->DeviceVar) {
int CurDev = 0;
hipGetDevice(&CurDev);
const char *ArchName = getDeviceArchName(CurDev);
/* Encode TUIndex in Target so each drain writes a distinct profraw;
* otherwise back-to-back drains overwrite the same file. */
char TargetWithTU[64];
snprintf(TargetWithTU, sizeof(TargetWithTU), "%s.%d", ArchName,
TUIndex);
if (processDeviceOffloadPrf(TU->DeviceVar, TUIndex, TargetWithTU) == 0)
TU->Processed = 1;
else
PROF_WARN("failed to process profile data for module %p TU %d\n", Ptr,
t);
}
}
MI->ModulePtr = nullptr;
unlockDynamicModules();
return;
}
if (isVerboseMode())
PROF_WARN("unregister called for unknown module %p\n", Ptr);
unlockDynamicModules();
}
/* Grow a void* array, doubling capacity (or starting at InitCap). */
static int growPtrArray(void ***Arr, int *Num, int *Cap, int InitCap) {
if (*Num < *Cap)
return 0;
int NewCap = *Cap ? *Cap * 2 : InitCap;
void **New = (void **)realloc(*Arr, NewCap * sizeof(void *));
if (!New)
return -1;
*Arr = New;
*Cap = NewCap;
return 0;
}
static void **OffloadShadowVariables = nullptr;
static int NumShadowVariables = 0;
static int CapShadowVariables = 0;
extern "C" void __llvm_profile_offload_register_shadow_variable(void *ptr) {
if (growPtrArray(&OffloadShadowVariables, &NumShadowVariables,
&CapShadowVariables, 64))
return;
OffloadShadowVariables[NumShadowVariables++] = ptr;
}
static void **OffloadSectionShadowVariables = nullptr;
static int NumSectionShadowVariables = 0;
static int CapSectionShadowVariables = 0;
extern "C" void
__llvm_profile_offload_register_section_shadow_variable(void *ptr) {
if (growPtrArray(&OffloadSectionShadowVariables, &NumSectionShadowVariables,
&CapSectionShadowVariables, 64))
return;
OffloadSectionShadowVariables[NumSectionShadowVariables++] = ptr;
}
namespace {
// free()-based scope guard. Use .release() to transfer ownership.
struct UniqueFree {
void *Ptr;
explicit UniqueFree(void *P = nullptr) : Ptr(P) {}
~UniqueFree() { free(Ptr); }
UniqueFree(const UniqueFree &) = delete;
UniqueFree &operator=(const UniqueFree &) = delete;
char *get() const { return static_cast<char *>(Ptr); }
void reset(void *P) {
free(Ptr);
Ptr = P;
}
void *release() {
void *P = Ptr;
Ptr = nullptr;
return P;
}
};
} // namespace
static int processDeviceOffloadPrf(void *DeviceOffloadPrf, int TUIndex,
const char *Target) {
__llvm_profile_gpu_sections HostSections;
if (hipMemcpy(&HostSections, DeviceOffloadPrf, sizeof(HostSections),
2 /*DToH*/) != 0) {
PROF_ERR("%s\n", "failed to copy offload prf structure from device");
return -1;
}
const void *DevCntsBegin = HostSections.CountersStart;
const void *DevDataBegin = HostSections.DataStart;
const void *DevNamesBegin = HostSections.NamesStart;
const void *DevCntsEnd = HostSections.CountersStop;
const void *DevDataEnd = HostSections.DataStop;
const void *DevNamesEnd = HostSections.NamesStop;
size_t CountersSize = (const char *)DevCntsEnd - (const char *)DevCntsBegin;
size_t DataSize = (const char *)DevDataEnd - (const char *)DevDataBegin;
size_t NamesSize = (const char *)DevNamesEnd - (const char *)DevNamesBegin;
if (isVerboseMode())
PROF_NOTE("Section pointers: Cnts=[%p,%p]=%zu Data=[%p,%p]=%zu "
"Names=[%p,%p]=%zu\n",
DevCntsBegin, DevCntsEnd, CountersSize, DevDataBegin, DevDataEnd,
DataSize, DevNamesBegin, DevNamesEnd, NamesSize);
if (CountersSize == 0 || DataSize == 0)
return 0;
int ret = -1;
int NamesReused = 0, CntsReused = 0, DataReused = 0;
char *HostDataBegin = nullptr;
char *HostCountersBegin = nullptr;
char *HostNamesBegin = nullptr;
/* Sections using linker-defined __start_/__stop_ bounds are shared across
TU structs in RDC mode. Deduplicate by caching the last copied range. */
static const void *CachedDevNamesBegin = nullptr;
static char *CachedHostNames = nullptr;
static size_t CachedNamesSize = 0;
static const void *CachedDevCntsBegin = nullptr;
static char *CachedHostCnts = nullptr;
static size_t CachedCntsSize = 0;
static const void *CachedDevDataBegin = nullptr;
static char *CachedHostData = nullptr;
static size_t CachedDataSize = 0;
// Owns freshly malloc'd buffers; release() transfers ownership to the cache.
UniqueFree CntsOwner, DataOwner, NamesOwner;
if (CountersSize > 0 && DevCntsBegin == CachedDevCntsBegin &&
CountersSize == CachedCntsSize) {
HostCountersBegin = CachedHostCnts;
CntsReused = 1;
if (isVerboseMode())
PROF_NOTE("Reusing cached counters section (%zu bytes)\n", CountersSize);
} else if (CountersSize > 0) {
HostCountersBegin = (char *)malloc(CountersSize);
CntsOwner.reset(HostCountersBegin);
}
if (DataSize > 0 && DevDataBegin == CachedDevDataBegin &&
DataSize == CachedDataSize) {
HostDataBegin = CachedHostData;
DataReused = 1;
if (isVerboseMode())
PROF_NOTE("Reusing cached data section (%zu bytes)\n", DataSize);
} else if (DataSize > 0) {
HostDataBegin = (char *)malloc(DataSize);
DataOwner.reset(HostDataBegin);
}
if (NamesSize > 0 && DevNamesBegin == CachedDevNamesBegin &&
NamesSize == CachedNamesSize) {
HostNamesBegin = CachedHostNames;
NamesReused = 1;
if (isVerboseMode())
PROF_NOTE("Reusing cached names section (%zu bytes)\n", NamesSize);
} else if (NamesSize > 0) {
HostNamesBegin = (char *)malloc(NamesSize);
NamesOwner.reset(HostNamesBegin);
}
if ((DataSize > 0 && !HostDataBegin) ||
(CountersSize > 0 && !HostCountersBegin) ||
(NamesSize > 0 && !HostNamesBegin)) {
PROF_ERR("%s\n", "failed to allocate host memory for device sections");
return -1;
}
if ((DataSize > 0 && !DataReused &&
memcpyDeviceToHost(HostDataBegin, DevDataBegin, DataSize) != 0) ||
(CountersSize > 0 && !CntsReused &&
memcpyDeviceToHost(HostCountersBegin, DevCntsBegin, CountersSize) !=
0) ||
(NamesSize > 0 && !NamesReused &&
memcpyDeviceToHost(HostNamesBegin, DevNamesBegin, NamesSize) != 0)) {
PROF_ERR("%s\n", "failed to copy profile sections from device");
return -1;
}
/* Cache buffers so RDC-mode multi-shadow drains can reuse them.
* release() prevents the scope guards from freeing what the cache owns. */
if (!CntsReused && CountersSize > 0) {
CachedDevCntsBegin = DevCntsBegin;
CachedHostCnts = HostCountersBegin;
CachedCntsSize = CountersSize;
CntsOwner.release();
}
if (!DataReused && DataSize > 0) {
CachedDevDataBegin = DevDataBegin;
CachedHostData = HostDataBegin;
CachedDataSize = DataSize;
DataOwner.release();
}
if (!NamesReused && NamesSize > 0) {
CachedDevNamesBegin = DevNamesBegin;
CachedHostNames = HostNamesBegin;
CachedNamesSize = NamesSize;
NamesOwner.release();
}
if (isVerboseMode())
PROF_NOTE("Copied device sections: Counters=%zu, Data=%zu, Names=%zu\n",
CountersSize, DataSize, NamesSize);
// Arrange buffer as [Data][Padding][Counters][Names] to match the layout
// expected by lprofWriteDataImpl (CountersDelta = CountersBegin - DataBegin).
const uint64_t NumData = DataSize / sizeof(__llvm_profile_data);
const uint64_t NumBitmapBytes = 0;
const uint64_t VTableSectionSize = 0;
const uint64_t VNamesSize = 0;
uint64_t PaddingBytesBeforeCounters, PaddingBytesAfterCounters,
PaddingBytesAfterBitmapBytes, PaddingBytesAfterNames,
PaddingBytesAfterVTable, PaddingBytesAfterVNames;
if (__llvm_profile_get_padding_sizes_for_counters(
DataSize, CountersSize, NumBitmapBytes, NamesSize, VTableSectionSize,
VNamesSize, &PaddingBytesBeforeCounters, &PaddingBytesAfterCounters,
&PaddingBytesAfterBitmapBytes, &PaddingBytesAfterNames,
&PaddingBytesAfterVTable, &PaddingBytesAfterVNames) != 0) {
PROF_ERR("%s\n", "failed to get padding sizes");
return -1;
}
size_t ContiguousBufferSize =
DataSize + PaddingBytesBeforeCounters + CountersSize + NamesSize;
UniqueFree ContiguousBuf(malloc(ContiguousBufferSize));
if (!ContiguousBuf.get()) {
PROF_ERR("%s\n", "failed to allocate contiguous buffer");
return -1;
}
char *ContiguousBuffer = ContiguousBuf.get();
__builtin_memset(ContiguousBuffer, 0, ContiguousBufferSize);
char *BufDataBegin = ContiguousBuffer;
char *BufCountersBegin =
ContiguousBuffer + DataSize + PaddingBytesBeforeCounters;
char *BufNamesBegin = BufCountersBegin + CountersSize;
__builtin_memcpy(BufDataBegin, HostDataBegin, DataSize);
__builtin_memcpy(BufCountersBegin, HostCountersBegin, CountersSize);
__builtin_memcpy(BufNamesBegin, HostNamesBegin, NamesSize);
// CounterPtr is a device-relative offset; relocate it for the file layout
// where the Data section precedes Counters.
__llvm_profile_data *RelocatedData = (__llvm_profile_data *)BufDataBegin;
for (uint64_t i = 0; i < NumData; ++i) {
if (RelocatedData[i].CounterPtr) {
ptrdiff_t DeviceCounterPtrOffset = (ptrdiff_t)RelocatedData[i].CounterPtr;
const char *DeviceDataStructAddr =
(const char *)DevDataBegin + (i * sizeof(__llvm_profile_data));
const char *DeviceCountersAddr =
DeviceDataStructAddr + DeviceCounterPtrOffset;
ptrdiff_t OffsetIntoCountersSection =
DeviceCountersAddr - (const char *)DevCntsBegin;
ptrdiff_t NewRelativeOffset = DataSize + PaddingBytesBeforeCounters +
OffsetIntoCountersSection -
(i * sizeof(__llvm_profile_data));
__builtin_memcpy((char *)RelocatedData + i * sizeof(__llvm_profile_data) +
offsetof(__llvm_profile_data, CounterPtr),
&NewRelativeOffset, sizeof(NewRelativeOffset));
}
__builtin_memset((char *)RelocatedData + i * sizeof(__llvm_profile_data) +
offsetof(__llvm_profile_data, BitmapPtr),
0,
sizeof(RelocatedData[i].BitmapPtr) +
sizeof(RelocatedData[i].FunctionPointer) +
sizeof(RelocatedData[i].Values));
}
/* Target already encodes TUIndex when needed. */
(void)TUIndex;
ret = __llvm_write_custom_profile(
Target, (__llvm_profile_data *)BufDataBegin,
(__llvm_profile_data *)(BufDataBegin + DataSize), BufCountersBegin,
BufCountersBegin + CountersSize, BufNamesBegin, BufNamesBegin + NamesSize,
nullptr);
if (ret != 0) {
PROF_ERR("%s\n", "failed to write device profile using shared API");
} else if (isVerboseMode()) {
PROF_NOTE("%s\n", "Successfully wrote device profile using shared API");
}
return ret;
}
static int processShadowVariable(void *ShadowVar, int TUIndex,
const char *Target) {
void *DeviceSections = nullptr;
if (hipGetSymbolAddress(&DeviceSections, ShadowVar) != 0) {
if (isVerboseMode())
PROF_NOTE("shadow variable %p not available on current device, "
"skipping\n", ShadowVar);
return -1;
}
/* DeviceSections points at the per-TU sections struct itself. */
return processDeviceOffloadPrf(DeviceSections, TUIndex, Target);
}
static int isHipAvailable(void) {
ensureHipLoaded();
return pHipMemcpy != nullptr && pHipGetSymbolAddress != nullptr;
}
/* -------------------------------------------------------------------------- */
/* Collect device-side profile data */
/* -------------------------------------------------------------------------- */
extern "C" int __llvm_profile_hip_collect_device_data(void) {
if (NumShadowVariables == 0 && NumDynamicModules == 0)
return 0;
if (!isHipAvailable())
return 0;
int Ret = 0;
/* Shadow variables (static-linked kernels): collect from the active device.
* hipGetSymbolAddress can trigger lazy code-object compilation via comgr on
* devices that never launched a kernel; at atexit time comgr's internal
* state may be partially torn down, causing a segfault. Restrict collection
* to the device that was current when the program finished. */
if (NumShadowVariables > 0) {
int OrigDevice = -1;
hipGetDevice(&OrigDevice);
if (OrigDevice < 0)
OrigDevice = 0;
const char *ArchName = getDeviceArchName(OrigDevice);
if (isVerboseMode())
PROF_NOTE("Collecting static profile data from device %d (%s)\n",
OrigDevice, ArchName);
for (int i = 0; i < NumShadowVariables; ++i) {
const char *Target = ArchName;
char TargetWithIdx[64];
if (NumShadowVariables > 1) {
snprintf(TargetWithIdx, sizeof(TargetWithIdx), "%s.%d", ArchName, i);
Target = TargetWithIdx;
}
if (processShadowVariable(OffloadShadowVariables[i], i, Target) != 0)
Ret = -1;
}
}
/* Warn about unprocessed TUs; skip cleared slots (already drained). */
lockDynamicModules();
for (int i = 0; i < NumDynamicModules; ++i) {
OffloadDynamicModuleInfo *MI = &DynamicModules[i];
if (!MI->ModulePtr)
continue;
for (int t = 0; t < MI->NumTUs; ++t) {
if (!MI->TUs[t].Processed) {
PROF_WARN("dynamic module %p TU %d was not processed before exit\n",
MI->ModulePtr, t);
Ret = -1;
}
}
}
unlockDynamicModules();
if (Ret != 0)
PROF_WARN("%s\n", "failed to collect device profile data");
return Ret;
}
/* Interceptors for hipModuleLoad* / hipModuleUnload. Linux only. */
#if defined(__linux__) && !defined(_WIN32)
INTERCEPTOR(int, hipModuleLoad, void **module, const char *fname) {
int rc = REAL(hipModuleLoad)(module, fname);
/* Pass NULL image: no in-memory ELF is available for filename loads,
* so the register hook skips symbol enumeration. */
__llvm_profile_offload_register_dynamic_module(rc, module, nullptr);
return rc;
}
INTERCEPTOR(int, hipModuleLoadData, void **module, const void *image) {
int rc = REAL(hipModuleLoadData)(module, image);
__llvm_profile_offload_register_dynamic_module(rc, module, image);
return rc;
}
INTERCEPTOR(int, hipModuleLoadDataEx, void **module, const void *image,
unsigned numOptions, void **options, void **optionValues) {
int rc = REAL(hipModuleLoadDataEx)(module, image, numOptions, options,
optionValues);
__llvm_profile_offload_register_dynamic_module(rc, module, image);
return rc;
}
INTERCEPTOR(int, hipModuleUnload, void *module) {
/* Drain counters before the module is destroyed; device addresses
* captured at register time are invalid after unload. */
__llvm_profile_offload_unregister_dynamic_module(module);
return REAL(hipModuleUnload)(module);
}
__attribute__((constructor)) static void installHipModuleInterceptors() {
/* Skip when the HIP runtime is not loaded. INTERCEPT_FUNCTION uses the
* sanitizer interception framework, which can perturb dlsym/PLT state for
* the rest of the process even when the target symbol is absent; non-HIP
* programs linked with libclang_rt.profile.a must see zero side effects. */
if (!dlsym(RTLD_DEFAULT, "hipModuleLoad"))
return;
if (!INTERCEPT_FUNCTION(hipModuleLoad))
return;
if (isVerboseMode())
PROF_NOTE("%s", "Installing hipModuleLoad*/hipModuleUnload interceptors\n");
INTERCEPT_FUNCTION(hipModuleLoadData);
INTERCEPT_FUNCTION(hipModuleLoadDataEx);
INTERCEPT_FUNCTION(hipModuleUnload);
}
#endif /* __linux__ */