Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
934acaf
[PGO][AMDGPU] Add basic HIP offload PGO support
yxsamliu Apr 10, 2026
08c7fd2
[PGO][AMDGPU] Apply review feedback on InstrProfilingPlatformROCm.cpp
yxsamliu Apr 17, 2026
174dd04
[PGO][AMDGPU] Apply follow-up review cleanups
yxsamliu Apr 22, 2026
3db7483
[PGO][AMDGPU] Drop the IR RuntimeLibcalls helper
yxsamliu Apr 22, 2026
281645d
[PGO][AMDGPU] Use the existing InstrProf callback naming pattern
yxsamliu Apr 22, 2026
0642a41
[PGO][AMDGPU] Drop unrelated drive-by changes
yxsamliu May 8, 2026
21032a5
[PGO][AMDGPU] Look up __llvm_profile_instrument_gpu via RuntimeLibcalls
yxsamliu May 17, 2026
87005a4
[compiler-rt][profile] Make HIP runtime loading and dynamic module li…
yxsamliu May 17, 2026
a9cfb45
[compiler-rt][profile] Fix profraw filename collisions, module-handle…
yxsamliu May 19, 2026
0662788
[compiler-rt][profile][PGO] Move HIP dynamic-module instrumentation t…
yxsamliu May 19, 2026
e2ec1b1
[clang][PGO] Move HIP offload-PGO sections-table emission to CGCUDANV
yxsamliu May 19, 2026
84d2aab
[PGO] Remove AMDGPU-specific paths from InstrProfiling
yxsamliu May 19, 2026
360da75
[PGO] Generalize PGOInstrumentation to isGPU(); fix lit test and add …
yxsamliu May 20, 2026
85f02c6
[profile][ROCm] Simplify RAII cleanup and skip interceptor install wh…
yxsamliu May 20, 2026
182c707
[profile][ROCm] Remove MAX_DEVICES cap; allocate arch-name table at init
yxsamliu May 26, 2026
ff9c6d5
[profile][ROCm] Use monotonic atomic counter for per-TU profraw index
yxsamliu May 26, 2026
825c60b
[profile][ROCm] Install hipModule* interceptors via __attribute__((co…
yxsamliu May 27, 2026
c7316df
[profile] Warn on failure of __llvm_profile_hip_collect_device_data
yxsamliu May 27, 2026
3d01b22
[PGO] Remove redundant anonymous namespace around InstrLowerer::getPr…
yxsamliu May 27, 2026
bf8f77a
[profile][ROCm] clang-format the calloc cast spacing
yxsamliu May 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions clang/lib/CodeGen/CGCUDANV.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "llvm/IR/ReplaceConstant.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/Transforms/Utils/ModuleUtils.h"

using namespace clang;
using namespace CodeGen;
Expand Down Expand Up @@ -73,6 +74,11 @@ class CGNVCUDARuntime : public CGCUDARuntime {
/// ModuleCtorFunction() and used to create corresponding cleanup calls in
/// ModuleDtorFunction()
llvm::GlobalVariable *GpuBinaryHandle = nullptr;
/// Host-side shadow for the per-TU __llvm_profile_sections_<CUID> global,
/// emitted only for HIP host compiles when PGO is on. Registered via
/// __hipRegisterVar (non-RDC) or an offloading entry (RDC) so the runtime
/// can locate the device-side table by name.
llvm::GlobalVariable *OffloadProfShadow = nullptr;
/// Whether we generate relocatable device code.
bool RelocatableDeviceCode;
/// Mangle context for device.
Expand Down Expand Up @@ -177,6 +183,13 @@ class CGNVCUDARuntime : public CGCUDARuntime {
void transformManagedVars();
/// Create offloading entries to register globals in RDC mode.
void createOffloadingEntries();
/// For HIP+PGO, emit the per-TU __llvm_profile_sections_<CUID> global.
/// On the device side it is the populated 7-pointer section-bounds table.
/// On the host side it is a placeholder void* shadow stored in
/// OffloadProfShadow, registered later by makeRegisterGlobalsFn (non-RDC)
/// or createOffloadingEntries (RDC) so the runtime can locate the
/// device-side table by name.
void emitOffloadProfilingSections();

public:
CGNVCUDARuntime(CodeGenModule &CGM);
Expand Down Expand Up @@ -736,6 +749,32 @@ llvm::Function *CGNVCUDARuntime::makeRegisterGlobalsFn() {
}
}

// Register the per-TU offload-profiling shadow so the host runtime can
// locate the matching device-side __llvm_profile_sections_<CUID>. We
// emit both __hipRegisterVar (so the HIP runtime can map the host
// shadow to the device symbol) and
// __llvm_profile_offload_register_shadow_variable (so the profile
// runtime adds the shadow to its drain list).
if (OffloadProfShadow) {
llvm::Constant *Name =
makeConstantString(std::string(OffloadProfShadow->getName()));
llvm::Value *RegisterVarArgs[] = {
&GpuBinaryHandlePtr,
OffloadProfShadow,
Name,
Name,
llvm::ConstantInt::get(IntTy, /*Extern=*/0),
llvm::ConstantInt::get(VarSizeTy, CGM.getDataLayout().getPointerSize()),
llvm::ConstantInt::get(IntTy, /*Constant=*/0),
llvm::ConstantInt::get(IntTy, 0)};
Builder.CreateCall(RegisterVar, RegisterVarArgs);

llvm::FunctionCallee RegisterShadow = CGM.CreateRuntimeFunction(
llvm::FunctionType::get(VoidTy, {PtrTy}, false),
"__llvm_profile_offload_register_shadow_variable");
Builder.CreateCall(RegisterShadow, {OffloadProfShadow});
}

Builder.CreateRetVoid();
return RegisterKernelsFunc;
}
Expand Down Expand Up @@ -1260,11 +1299,124 @@ void CGNVCUDARuntime::createOffloadingEntries() {
I.Flags.getSurfTexType());
}
}

// Register the per-TU offload-profiling shadow. The offloading entry
// makes the linker-wrapper emit the host __hipRegisterVar call in the
// combined ctor. Separately emit a per-TU ctor that registers the
// shadow with the profile runtime's drain list.
if (OffloadProfShadow) {
llvm::offloading::emitOffloadingEntry(
M, Kind, OffloadProfShadow, OffloadProfShadow->getName(),
CGM.getDataLayout().getPointerSize(),
llvm::offloading::OffloadGlobalEntry, /*Data=*/0);

llvm::LLVMContext &Ctx = M.getContext();
auto *PtrTy = llvm::PointerType::getUnqual(Ctx);
llvm::FunctionCallee RegisterShadow = CGM.CreateRuntimeFunction(
llvm::FunctionType::get(VoidTy, {PtrTy}, false),
"__llvm_profile_offload_register_shadow_variable");
auto *CtorFn = llvm::Function::Create(
llvm::FunctionType::get(VoidTy, false),
llvm::GlobalValue::InternalLinkage,
"__llvm_profile_register_shadow." + CGM.getContext().getCUIDHash(), &M);
auto *Entry = llvm::BasicBlock::Create(Ctx, "entry", CtorFn);
llvm::IRBuilder<> B(Entry);
B.CreateCall(RegisterShadow, {OffloadProfShadow});
B.CreateRetVoid();
llvm::appendToGlobalCtors(M, CtorFn, /*Priority=*/65535);
}
}

// For HIP host+device compiles with PGO enabled, emit the per-TU global
// __llvm_profile_sections_<CUID>. Device side: a 7-pointer struct holding
// section start/stop bounds for the names/counters/data sections plus the
// raw-version variable. Host side: an opaque void* shadow whose only
// purpose is to give the host-runtime a registered symbol name to look up
// via hipGetSymbolAddress; the actual device-side data lives in the
// matching device-side global.
void CGNVCUDARuntime::emitOffloadProfilingSections() {
if (!CGM.getLangOpts().HIP)
return;
if (!CGM.getCodeGenOpts().hasProfileInstr())
return;

StringRef CUIDHash = CGM.getContext().getCUIDHash();
if (CUIDHash.empty())
return;

llvm::Module &M = CGM.getModule();
llvm::LLVMContext &Ctx = M.getContext();
std::string Name = ("__llvm_profile_sections_" + CUIDHash).str();

// If the global already exists (e.g. another TU was merged in), don't
// duplicate it.
if (M.getNamedValue(Name))
return;

if (CGM.getLangOpts().CUDAIsDevice) {
// Device side: emit the populated struct. Section start/stop symbols
// are linker-defined (ELF auto-generates __start_/__stop_ for any
// section whose name is a valid C identifier; AMDGPU is ELF).
unsigned GlobalAS = M.getDataLayout().getDefaultGlobalsAddressSpace();
auto *PtrTy = llvm::PointerType::get(Ctx, GlobalAS);
auto getOrDeclare = [&](StringRef SymName) {
if (auto *GV = M.getNamedGlobal(SymName))
return GV;
auto *GV = new llvm::GlobalVariable(
M, llvm::Type::getInt8Ty(Ctx), /*isConstant=*/false,
llvm::GlobalValue::ExternalLinkage, /*Initializer=*/nullptr, SymName,
/*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal,
GlobalAS);
GV->setVisibility(llvm::GlobalValue::HiddenVisibility);
return GV;
};
auto *VersionGV = M.getNamedGlobal("__llvm_profile_raw_version");
if (!VersionGV) {
VersionGV = new llvm::GlobalVariable(
M, llvm::Type::getInt64Ty(Ctx), /*isConstant=*/true,
llvm::GlobalValue::ExternalLinkage, /*Initializer=*/nullptr,
"__llvm_profile_raw_version",
/*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal,
GlobalAS);
}

auto *StructTy = llvm::StructType::get(
Ctx, {PtrTy, PtrTy, PtrTy, PtrTy, PtrTy, PtrTy, PtrTy});
llvm::Constant *Fields[] = {
getOrDeclare("__start___llvm_prf_names"),
getOrDeclare("__stop___llvm_prf_names"),
getOrDeclare("__start___llvm_prf_cnts"),
getOrDeclare("__stop___llvm_prf_cnts"),
getOrDeclare("__start___llvm_prf_data"),
getOrDeclare("__stop___llvm_prf_data"),
VersionGV,
};
auto *Init = llvm::ConstantStruct::get(StructTy, Fields);
auto *GV = new llvm::GlobalVariable(
M, StructTy, /*isConstant=*/true, llvm::GlobalValue::ExternalLinkage,
Init, Name, /*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal,
GlobalAS);
GV->setVisibility(llvm::GlobalValue::ProtectedVisibility);
CGM.addCompilerUsedGlobal(GV);
return;
}

// Host side: emit an opaque void* shadow. Layout doesn't matter — the
// runtime locates it by name via hipGetSymbolAddress and treats it as
// the address of the device-side struct. Registration with the HIP
// runtime is added by makeRegisterGlobalsFn (non-RDC) or
// createOffloadingEntries (RDC).
auto *PtrTy = llvm::PointerType::getUnqual(Ctx);
OffloadProfShadow = new llvm::GlobalVariable(
M, PtrTy, /*isConstant=*/false, llvm::GlobalValue::ExternalLinkage,
llvm::ConstantPointerNull::get(PtrTy), Name);
CGM.addCompilerUsedGlobal(OffloadProfShadow);
}

// Returns module constructor to be added.
llvm::Function *CGNVCUDARuntime::finalizeModule() {
transformManagedVars();
emitOffloadProfilingSections();
if (CGM.getLangOpts().CUDAIsDevice) {
// Mark ODR-used device variables as compiler used to prevent it from being
// eliminated by optimization. This is necessary for device variables
Expand Down
50 changes: 50 additions & 0 deletions clang/test/CodeGenHIP/offload-pgo-sections.hip
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// REQUIRES: amdgpu-registered-target
// REQUIRES: x86-registered-target

// Verify CGCUDANV emits the per-TU __llvm_profile_sections_<CUID> global
// for HIP+PGO compilations. Device subcompile: populated 7-pointer struct
// in addrspace(1). Host compile: void* shadow registered with the HIP
// runtime and with the profile runtime's drain list.

// RUN: %clang_cc1 -triple amdgcn-amd-amdhsa -fcuda-is-device -cuid=abc \
// RUN: -fprofile-instrument=clang -emit-llvm -o - -x hip %s \
// RUN: | FileCheck -check-prefix=DEV %s

// RUN: %clang_cc1 -triple x86_64-linux-gnu -cuid=abc \
// RUN: -fprofile-instrument=clang -emit-llvm -o - -x hip %s \
// RUN: | FileCheck -check-prefix=HOST %s

// Guard: no PGO -> no emission.
// RUN: %clang_cc1 -triple amdgcn-amd-amdhsa -fcuda-is-device -cuid=abc \
// RUN: -emit-llvm -o - -x hip %s \
// RUN: | FileCheck -check-prefix=NONE %s

// Guard: no CUID -> no emission.
// RUN: %clang_cc1 -triple amdgcn-amd-amdhsa -fcuda-is-device \
// RUN: -fprofile-instrument=clang -emit-llvm -o - -x hip %s \
// RUN: | FileCheck -check-prefix=NONE %s

#define __device__ __attribute__((device))
#define __global__ __attribute__((global))

__device__ int helper(int x) { return x + 1; }
__global__ void kernel(int *p) { *p = helper(*p); }

// DEV-DAG: @__start___llvm_prf_names = external hidden addrspace(1) global i8
// DEV-DAG: @__stop___llvm_prf_names = external hidden addrspace(1) global i8
// DEV-DAG: @__start___llvm_prf_cnts = external hidden addrspace(1) global i8
// DEV-DAG: @__stop___llvm_prf_cnts = external hidden addrspace(1) global i8
// DEV-DAG: @__start___llvm_prf_data = external hidden addrspace(1) global i8
// DEV-DAG: @__stop___llvm_prf_data = external hidden addrspace(1) global i8
// DEV-DAG: @__llvm_profile_raw_version = external addrspace(1) constant i64
// DEV: @__llvm_profile_sections_[[CUID:[0-9a-f]+]] = protected addrspace(1) constant {{.*}}@__start___llvm_prf_names{{.*}}@__stop___llvm_prf_names{{.*}}@__start___llvm_prf_cnts{{.*}}@__stop___llvm_prf_cnts{{.*}}@__start___llvm_prf_data{{.*}}@__stop___llvm_prf_data{{.*}}@__llvm_profile_raw_version
// DEV: @llvm.compiler.used = {{.*}}@__llvm_profile_sections_[[CUID]]

// HOST: @__llvm_profile_sections_[[CUID:[0-9a-f]+]] = global ptr null
// HOST: @llvm.compiler.used = {{.*}}@__llvm_profile_sections_[[CUID]]
// HOST: define internal void @__hip_register_globals
// HOST: call void @__hipRegisterVar({{.*}}@__llvm_profile_sections_[[CUID]],
// HOST: call void @__llvm_profile_offload_register_shadow_variable(ptr @__llvm_profile_sections_[[CUID]])

// NONE-NOT: __llvm_profile_sections_
// NONE-NOT: __llvm_profile_offload_register_shadow_variable
31 changes: 28 additions & 3 deletions compiler-rt/lib/profile/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ if (NOT COMPILER_RT_PROFILE_BAREMETAL)
list(APPEND PROFILE_SOURCES
GCDAProfiling.c
InstrProfilingFile.c
InstrProfilingPlatformROCm.cpp
InstrProfilingRuntime.cpp
InstrProfilingUtil.c
InstrProfilingValue.c
Expand Down Expand Up @@ -155,6 +156,17 @@ if(COMPILER_RT_PROFILE_BAREMETAL)
-DCOMPILER_RT_PROFILE_BAREMETAL=1)
endif()

set(PROFILE_OBJECT_LIBS)
if(COMPILER_RT_HAS_INTERCEPTION AND NOT COMPILER_RT_PROFILE_BAREMETAL)
# RTInterception references __sanitizer_internal_{memcpy,memset,memmove} and other
# sanitizer_common symbols; merge the same object libs as clang_rt.cfi (without
# coverage/symbolizer) so -fprofile-instr-generate links stay self-contained.
list(APPEND PROFILE_OBJECT_LIBS
RTInterception
RTSanitizerCommon
RTSanitizerCommonLibc)
endif()

if("${COMPILER_RT_DEFAULT_TARGET_ARCH}" MATCHES "amdgcn|nvptx")
append_list_if(COMPILER_RT_HAS_FFREESTANDING_FLAG -ffreestanding EXTRA_FLAGS)
append_list_if(COMPILER_RT_HAS_NOGPULIB_FLAG -nogpulib EXTRA_FLAGS)
Expand All @@ -168,13 +180,24 @@ if("${COMPILER_RT_DEFAULT_TARGET_ARCH}" MATCHES "amdgcn|nvptx")
endif()

if(MSVC)
# profile historically has only been supported with the static runtime
# on windows
set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded)
# profile historically used the static CRT (/MT). When we merge RTInterception and
# RTSanitizerCommon (same object libs as clang_rt.cfi on ELF), those targets are
# built with MultiThreadedDLL (/MD) — see interception/CMakeLists.txt and
# sanitizer_common/CMakeLists.txt. Mixing /MD objects into a /MT libclang_rt.profile
# yields LNK2019 (__imp__stricmp from interception_win.cpp) and LNK4098 in Profile-*.
if(COMPILER_RT_HAS_INTERCEPTION AND NOT COMPILER_RT_PROFILE_BAREMETAL)
set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreadedDLL)
else()
set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded)
endif()
endif()

# We don't use the C++ Standard Library here, so avoid including it by mistake.
append_list_if(COMPILER_RT_HAS_NOSTDINCXX_FLAG -nostdinc++ EXTRA_FLAGS)
# C++ profile sources (e.g. InstrProfilingPlatformROCm.cpp) must not emit exception
# personality symbols: host libclang_rt.profile.a is linked from C code and from C++
# tests that do not pull in __gxx_personality_v0 (Profile-* / premerge).
append_list_if(COMPILER_RT_HAS_FNO_EXCEPTIONS_FLAG -fno-exceptions EXTRA_FLAGS)
# XRay uses C++ standard library headers.
string(REGEX REPLACE "-?-stdlib=[a-zA-Z+]*" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")

Expand All @@ -200,6 +223,7 @@ if(APPLE)
STATIC
OS ${PROFILE_SUPPORTED_OS}
ARCHS ${PROFILE_SUPPORTED_ARCH}
OBJECT_LIBS ${PROFILE_OBJECT_LIBS}
CFLAGS ${EXTRA_FLAGS}
SOURCES ${PROFILE_SOURCES}
ADDITIONAL_HEADERS ${PROFILE_HEADERS}
Expand All @@ -209,6 +233,7 @@ else()
add_compiler_rt_runtime(clang_rt.profile
STATIC
ARCHS ${PROFILE_SUPPORTED_ARCH}
OBJECT_LIBS ${PROFILE_OBJECT_LIBS}
CFLAGS ${EXTRA_FLAGS}
SOURCES ${PROFILE_SOURCES}
ADDITIONAL_HEADERS ${PROFILE_HEADERS}
Expand Down
9 changes: 9 additions & 0 deletions compiler-rt/lib/profile/InstrProfilingFile.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
#include "InstrProfilingPort.h"
#include "InstrProfilingUtil.h"

/* HIP / offload collection hook implemented in InstrProfilingPlatformROCm.c.
* It is a no-op when no offload profile data was registered. */
extern int __llvm_profile_hip_collect_device_data(void);

/* From where is profile name specified.
* The order the enumerators define their
* precedence. Re-order them may lead to
Expand Down Expand Up @@ -1198,6 +1202,11 @@ int __llvm_profile_write_file(void) {
if (rc)
PROF_ERR("Failed to write file \"%s\": %s\n", Filename, strerror(errno));

/* No-op when no HIP shadow variables or dynamic modules are registered,
* or when the HIP runtime is not loaded. Warning on failure is handled
* inside the callee so non-HIP programs do not see spurious noise. */
(void)__llvm_profile_hip_collect_device_data();

// Restore SIGKILL.
if (PDeathSig == 1)
lprofRestoreSigKill();
Expand Down
Loading