Skip to content

Commit e48229f

Browse files
lfmeadowcursoragent
andcommitted
[smoke-dev] Add HipDevCov and OmpDevCov device-coverage drain tests
Add two smoke-dev tests that validate device-side PGO/coverage drainage via the in-tree HSA-introspection drain (upstream LLVM #203056), independent of the HIP host-shadow drain. HipDevCov: builds a device-only kernel as an executable so the device link pulls in the amdgcn profile runtime, extracts its code object (mod.co) via llvm-objdump --offloading, then hipModuleLoad()s it from a program where that kernel has no host shadow. The kernel's counters can only reach the merged profile through the HSA drain; the test asserts both the host-shadow kernel and the module-only kernel are present. OmpDevCov: OpenMP target offload has no host-shadow drain, so a -fprofile-generate -> llvm-profdata merge -> -fprofile-use round trip that consumes a device function's counters exercises the HSA drain exclusively. Both honor $AOMP/$AOMP_GPU, use the smoke-dev Makefile harness, and skip cleanly on toolchains that do not yet ship the device profile runtime / drain. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent ecd229d commit e48229f

9 files changed

Lines changed: 373 additions & 0 deletions

File tree

test/smoke-dev/HipDevCov/Makefile

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
include ../../Makefile.defs
2+
3+
# HipDevCov: device-side coverage drained via the in-tree HSA introspection
4+
# pass. main (TESTNAME) is built by the harness with source-based coverage; it
5+
# hipModuleLoad()s mod.co (built+extracted by run_and_check.sh) whose kernel has
6+
# no host shadow and is therefore only reachable by the HSA drain.
7+
8+
TESTNAME = main
9+
TESTSRC_MAIN = main.hip
10+
TESTSRC_AUX =
11+
TESTSRC_ALL = $(TESTSRC_MAIN) $(TESTSRC_AUX)
12+
13+
AOMPHIP ?= $(AOMP)
14+
ROCM_PATH ?= $(AOMPHIP)
15+
16+
# Extra budget: run_and_check.sh also compiles mod.hip and extracts mod.co.
17+
TIMEOUT = 300s
18+
19+
CFLAGS = -x hip $(TARGET) -fno-gpu-rdc -fprofile-instr-generate -fcoverage-mapping --rocm-path=$(ROCM_PATH)
20+
LINK_FLAGS = -L$(ROCM_PATH)/lib -lamdhip64 -Wl,-rpath,$(ROCM_PATH)/lib
21+
22+
CC = $(AOMP)/bin/clang $(VERBOSE)
23+
24+
RUNCMD = ./run_and_check.sh "$(AOMP)" "$(GPU_W_FEATURES)" "$(FILECHECK)" "$(ROCM_PATH)"
25+
26+
include ../Makefile.rules
27+
28+
clean::
29+
rm -f mod.co builder builder.*.host-* builder.*.hip-amdgcn-amd-amdhsa--* \
30+
*.profraw merged.profdata

test/smoke-dev/HipDevCov/check.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Both kernels must appear in the merged host+device profile. host_kernel is the
2+
host-shadow path; mod_kernel (from the hipModuleLoad'd code object, no host
3+
shadow) can only be present if the HSA-introspection drain collected it.
4+
5+
CHECK-DAG: host_kernel
6+
CHECK-DAG: mod_kernel

test/smoke-dev/HipDevCov/main.hip

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// HipDevCov: prove the HSA-introspection device-coverage drain captures device
2+
// code that the HIP host-shadow drain cannot see.
3+
//
4+
// This program contains exactly one statically-registered kernel (host_kernel)
5+
// -- it has a host-side shadow, so the host-shadow drain covers it. It then
6+
// hipModuleLoad()s mod.co (built+extracted by run_and_check.sh) and launches
7+
// mod_kernel, which is NOT compiled into this program and therefore has no host
8+
// shadow here. mod_kernel's device counters can only reach the profile via the
9+
// HSA drain. run_and_check.sh asserts BOTH kernels appear in the merged
10+
// profile, so a passing run requires the HSA drain to be working.
11+
12+
#include <hip/hip_runtime.h>
13+
#include <cstdio>
14+
15+
__global__ void host_kernel(int *p, int n) {
16+
int i = blockIdx.x * blockDim.x + threadIdx.x;
17+
if (i < n)
18+
p[i] = p[i] * 3; // taken region
19+
else
20+
p[0] = p[0] - 1; // untaken region
21+
}
22+
23+
int main() {
24+
const int n = 64;
25+
int *d = nullptr;
26+
if (hipMalloc(&d, n * sizeof(int)) != hipSuccess)
27+
return 1;
28+
if (hipMemset(d, 0, n * sizeof(int)) != hipSuccess)
29+
return 2;
30+
31+
// (1) host-shadow path: a normally-registered kernel.
32+
host_kernel<<<dim3(1), dim3(64)>>>(d, n);
33+
if (hipDeviceSynchronize() != hipSuccess)
34+
return 3;
35+
36+
// (2) HSA-only path: a kernel from a separately loaded code object, with no
37+
// host shadow in this process.
38+
hipModule_t mod;
39+
if (hipModuleLoad(&mod, "mod.co") != hipSuccess) {
40+
fprintf(stderr, "hipModuleLoad(mod.co) failed\n");
41+
return 4;
42+
}
43+
hipFunction_t fn;
44+
if (hipModuleGetFunction(&fn, mod, "mod_kernel") != hipSuccess) {
45+
fprintf(stderr, "hipModuleGetFunction(mod_kernel) failed\n");
46+
return 5;
47+
}
48+
int nn = n;
49+
void *args[] = {&d, &nn};
50+
if (hipModuleLaunchKernel(fn, 1, 1, 1, 64, 1, 1, 0, nullptr, args, nullptr) !=
51+
hipSuccess)
52+
return 6;
53+
if (hipDeviceSynchronize() != hipSuccess)
54+
return 7;
55+
56+
// Intentionally do NOT hipModuleUnload(mod): the HSA-introspection drain runs
57+
// at process exit and walks the code objects still loaded on the agent.
58+
// Unloading mod.co here would remove its counters before the drain sees them.
59+
(void)hipFree(d);
60+
printf("HipDevCov: ran host_kernel + module mod_kernel\n");
61+
return 0;
62+
}

test/smoke-dev/HipDevCov/mod.hip

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Device-only kernel used to produce a standalone, loadable code object
2+
// (mod.co) that carries its own instrumentation (__llvm_profile_sections +
3+
// the device profile runtime). main.hip hipModuleLoad()s this code object, so
4+
// in that process mod_kernel has NO host-side shadow registration. The HIP
5+
// host-shadow drain therefore cannot see it; only the HSA-introspection drain
6+
// (which walks every code object loaded on the agent) can collect its
7+
// counters. That is exactly what this test asserts.
8+
//
9+
// When built with -DBUILD_MODULE_EXE this file is a normal HIP executable; the
10+
// full executable link makes clang's driver add the amdgcn device profile
11+
// runtime to the device link (addProfileRTLibs), so the embedded device code
12+
// object is fully instrumented. run_and_check.sh extracts that object as
13+
// mod.co. (The standalone --offload-device-only / hipcc --genco paths do NOT
14+
// add the device profile RT, leaving __llvm_profile_instrument_gpu /
15+
// __llvm_profile_raw_version unresolved -- hence the extract-from-executable
16+
// approach.)
17+
18+
#include <hip/hip_runtime.h>
19+
20+
extern "C" __global__ void mod_kernel(int *p, int n) {
21+
int i = blockIdx.x * blockDim.x + threadIdx.x;
22+
if (i < n)
23+
p[i] = p[i] + 7; // taken region
24+
else
25+
p[0] = p[0] - 1; // untaken region (non-trivial coverage)
26+
}
27+
28+
#ifdef BUILD_MODULE_EXE
29+
int main() {
30+
const int n = 64;
31+
int *d = nullptr;
32+
if (hipMalloc(&d, n * sizeof(int)) != hipSuccess)
33+
return 1;
34+
if (hipMemset(d, 0, n * sizeof(int)) != hipSuccess)
35+
return 2;
36+
mod_kernel<<<dim3(1), dim3(64)>>>(d, n);
37+
if (hipDeviceSynchronize() != hipSuccess)
38+
return 3;
39+
(void)hipFree(d);
40+
return 0;
41+
}
42+
#endif
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/bin/bash
2+
# HipDevCov check driver.
3+
#
4+
# Args (passed from the Makefile):
5+
# $1 = AOMP (LLVM dir containing bin/clang, llvm-profdata, llvm-objdump)
6+
# $2 = GPU arch (e.g. gfx90a, may include :features)
7+
# $3 = FILECHECK (path to FileCheck)
8+
# $4 = ROCM_PATH (HIP/ROCm install for --rocm-path and libamdhip64)
9+
#
10+
# Returns 0 only if BOTH the host-shadow kernel and the module-only kernel are
11+
# present in the merged profile (the latter requires the HSA drain).
12+
13+
set -u
14+
AOMP="$1"; ARCH="$2"; FILECHECK="$3"; ROCM="$4"
15+
CLANG="$AOMP/bin/clang"
16+
PROFDATA="$AOMP/bin/llvm-profdata"
17+
OBJDUMP="$AOMP/bin/llvm-objdump"
18+
COV="$AOMP/bin/llvm-cov"
19+
20+
here="$(cd "$(dirname "$0")" && pwd)"
21+
cd "$here" || exit 1
22+
23+
# Capability gate: skip cleanly on toolchains that do not ship the device
24+
# profile runtime / HSA drain yet (so this does not red-fail CI before the
25+
# feature lands). A real, drain-capable toolchain has the amdgcn device profile
26+
# RT and the host drain symbol.
27+
resdir="$("$CLANG" -print-resource-dir 2>/dev/null)"
28+
devrt="$resdir/lib/amdgcn-amd-amdhsa/libclang_rt.profile.a"
29+
if [ ! -f "$devrt" ]; then
30+
echo "SKIP HipDevCov: no device profile runtime at $devrt"
31+
exit 0
32+
fi
33+
if ! ls "$resdir"/lib/*/libclang_rt.profile_rocm*.a >/dev/null 2>&1; then
34+
echo "SKIP HipDevCov: no host profile_rocm runtime (HSA drain) in toolchain"
35+
exit 0
36+
fi
37+
38+
set -e
39+
rm -f ./*.profraw mod.co merged.profdata builder builder.*.host-* \
40+
builder.*.hip-amdgcn-amd-amdhsa--* 2>/dev/null || true
41+
42+
# 1. Build mod.hip as a full executable so the device link gets the device
43+
# profile RT, then extract its device code object as the loadable mod.co.
44+
"$CLANG" -x hip --offload-arch="$ARCH" -fno-gpu-rdc -DBUILD_MODULE_EXE \
45+
-fprofile-instr-generate -fcoverage-mapping --rocm-path="$ROCM" \
46+
mod.hip -o builder -L"$ROCM/lib" -lamdhip64 -Wl,-rpath,"$ROCM/lib"
47+
"$OBJDUMP" --offloading builder >/dev/null 2>&1 || true
48+
co="$(ls builder*.hip-amdgcn-amd-amdhsa--*gfx* 2>/dev/null | head -1)"
49+
if [ -z "$co" ]; then
50+
echo "FAIL HipDevCov: could not extract device code object from builder"
51+
exit 1
52+
fi
53+
cp "$co" mod.co
54+
55+
# 2. main is built by the smoke harness (TESTNAME=main). Build it here too if it
56+
# is missing, so the script also works when run standalone.
57+
if [ ! -x ./main ]; then
58+
"$CLANG" -x hip --offload-arch="$ARCH" -fno-gpu-rdc \
59+
-fprofile-instr-generate -fcoverage-mapping --rocm-path="$ROCM" \
60+
main.hip -o main -L"$ROCM/lib" -lamdhip64 -Wl,-rpath,"$ROCM/lib"
61+
fi
62+
63+
# 3. Run. Device .profraw files are written to CWD (arch-prefixed for the
64+
# host-shadow drain, arch.hsa<N>-prefixed for the HSA drain); the host one
65+
# goes to LLVM_PROFILE_FILE.
66+
rm -f ./*.profraw
67+
LLVM_PROFILE_FILE="$here/host.profraw" \
68+
LD_LIBRARY_PATH="$ROCM/lib:${LD_LIBRARY_PATH:-}" \
69+
./main
70+
71+
# 4. Merge host + all device profraws and assert both kernels are present.
72+
"$PROFDATA" merge -sparse -o merged.profdata ./*.profraw
73+
"$PROFDATA" show --all-functions merged.profdata | "$FILECHECK" check.txt
74+
75+
# 5. Sanity (non-fatal): llvm-cov can consume the merged device+host profile.
76+
# main's covmap only describes host_kernel/main, so llvm-cov warns about the
77+
# module-only function; that is expected, hence non-fatal.
78+
"$COV" report ./main -instr-profile=merged.profdata >/dev/null 2>&1 || true
79+
80+
echo "HipDevCov PASSED"

test/smoke-dev/OmpDevCov/Makefile

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
include ../../Makefile.defs
2+
3+
# OmpDevCov: device-side PGO round trip for OpenMP target offload. The harness
4+
# builds omp_pgo (TESTNAME) with -fprofile-generate; run_and_check.sh runs it,
5+
# verifies the device counters were drained via the HSA pass, merges, and feeds
6+
# them back with -fprofile-use.
7+
8+
TESTNAME = omp_pgo
9+
TESTSRC_MAIN = omp_pgo.c
10+
TESTSRC_AUX =
11+
TESTSRC_ALL = $(TESTSRC_MAIN) $(TESTSRC_AUX)
12+
13+
# Extra budget: run_and_check.sh also performs the -fprofile-use rebuild + run.
14+
TIMEOUT = 300s
15+
16+
CFLAGS = -fopenmp $(TARGET) -fprofile-generate
17+
LINK_FLAGS = -Wl,-rpath,$(AOMP)/lib
18+
19+
CC = $(AOMP)/bin/clang $(VERBOSE)
20+
21+
RUNCMD = ./run_and_check.sh "$(AOMP)" "$(GPU_W_FEATURES)" "$(FILECHECK)"
22+
23+
include ../Makefile.rules
24+
25+
clean::
26+
rm -f omp_pgo omp_pgo_use *.profraw merged.profdata

test/smoke-dev/OmpDevCov/check.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
The merged profile must contain device-side counters that could only have been
2+
collected via the HSA-introspection drain (OpenMP offload has no host-shadow
3+
drain). The device offload entry and the device function classify both appear.
4+
5+
CHECK-DAG: __omp_offloading_
6+
CHECK-DAG: classify

test/smoke-dev/OmpDevCov/omp_pgo.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/* OmpDevCov: device-side PGO round trip for OpenMP target offload.
2+
*
3+
* OpenMP offload has no HIP-style host-shadow drain for its device code, so the
4+
* only way device counters reach the .profraw is the in-tree HSA-introspection
5+
* drain. A successful -fprofile-generate -> llvm-profdata merge -> -fprofile-use
6+
* cycle that consumes a device function's counters therefore exercises the HSA
7+
* drain exclusively.
8+
*
9+
* classify() runs on the device and has a data-dependent branch so there is a
10+
* non-trivial counter to instrument, merge, and consume.
11+
*/
12+
13+
#include <stdio.h>
14+
#include <stdlib.h>
15+
16+
#define N 4096
17+
18+
#pragma omp declare target
19+
static int classify(int x) {
20+
if ((x & 1) == 0)
21+
return x * 2; /* even path */
22+
else
23+
return x + 1; /* odd path */
24+
}
25+
#pragma omp end declare target
26+
27+
int main(void) {
28+
int *a = (int *)malloc(N * sizeof(int));
29+
if (!a)
30+
return 1;
31+
for (int i = 0; i < N; i++)
32+
a[i] = i;
33+
34+
#pragma omp target teams distribute parallel for map(tofrom : a[0:N])
35+
for (int i = 0; i < N; i++)
36+
a[i] = classify(a[i]);
37+
38+
long sum = 0;
39+
for (int i = 0; i < N; i++)
40+
sum += a[i];
41+
42+
printf("sum=%ld\n", sum);
43+
free(a);
44+
return 0;
45+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/bin/bash
2+
# OmpDevCov check driver: device-side PGO round trip for OpenMP offload.
3+
#
4+
# Args (passed from the Makefile):
5+
# $1 = AOMP (LLVM dir containing bin/clang, llvm-profdata)
6+
# $2 = GPU arch (e.g. gfx90a, may include :features)
7+
# $3 = FILECHECK (path to FileCheck)
8+
#
9+
# omp_pgo (the -fprofile-generate binary) is built by the harness. Here we:
10+
# 1. run it -> host default.profraw + device amdgcn-amd-amdhsa.*.profraw
11+
# (the device file is produced ONLY by the HSA drain),
12+
# 2. assert a device profraw was produced and merge,
13+
# 3. FileCheck the merged profile for the device offload function,
14+
# 4. rebuild with -fprofile-use and assert it consumes cleanly (no profile
15+
# mismatch / out-of-date diagnostics) and runs.
16+
17+
set -u
18+
AOMP="$1"; ARCH="$2"; FILECHECK="$3"
19+
CLANG="$AOMP/bin/clang"
20+
PROFDATA="$AOMP/bin/llvm-profdata"
21+
22+
here="$(cd "$(dirname "$0")" && pwd)"
23+
cd "$here" || exit 1
24+
25+
# Capability gate: skip cleanly if the toolchain has no amdgcn device profile
26+
# runtime (i.e. no device PGO / HSA drain yet), so CI is not red before the
27+
# feature lands.
28+
resdir="$("$CLANG" -print-resource-dir 2>/dev/null)"
29+
if [ ! -f "$resdir/lib/amdgcn-amd-amdhsa/libclang_rt.profile.a" ]; then
30+
echo "SKIP OmpDevCov: no amdgcn device profile runtime in toolchain"
31+
exit 0
32+
fi
33+
34+
set -e
35+
rm -f ./*.profraw merged.profdata omp_pgo_use 2>/dev/null || true
36+
37+
run_env() { LD_LIBRARY_PATH="$AOMP/lib:${LD_LIBRARY_PATH:-}" "$@"; }
38+
39+
# 1. Run the generate binary (built by the harness as ./omp_pgo). Build it here
40+
# too if missing, so the script also works standalone.
41+
if [ ! -x ./omp_pgo ]; then
42+
"$CLANG" -fopenmp --offload-arch="$ARCH" -fprofile-generate \
43+
omp_pgo.c -o omp_pgo -Wl,-rpath,"$AOMP/lib"
44+
fi
45+
run_env ./omp_pgo
46+
47+
# 2. The HSA drain must have produced at least one device profraw.
48+
shopt -s nullglob
49+
dev=(amdgcn-amd-amdhsa*.profraw)
50+
if [ ${#dev[@]} -eq 0 ]; then
51+
echo "FAIL OmpDevCov: no device (amdgcn-amd-amdhsa) profraw -- HSA drain did not fire"
52+
ls -1 ./*.profraw 2>/dev/null || true
53+
exit 1
54+
fi
55+
echo "OmpDevCov: device profraw(s): ${dev[*]}"
56+
57+
"$PROFDATA" merge -o merged.profdata ./*.profraw
58+
59+
# 3. The merged profile must carry the device offload function counters.
60+
"$PROFDATA" show --all-functions merged.profdata | "$FILECHECK" check.txt
61+
62+
# 4. -fprofile-use round trip: must consume the profile without a mismatch /
63+
# out-of-date diagnostic, then run.
64+
use_log="$(mktemp)"
65+
"$CLANG" -fopenmp --offload-arch="$ARCH" -fprofile-use=merged.profdata \
66+
omp_pgo.c -o omp_pgo_use -Wl,-rpath,"$AOMP/lib" 2> "$use_log" || {
67+
echo "FAIL OmpDevCov: -fprofile-use build failed"; cat "$use_log"; rm -f "$use_log"; exit 1; }
68+
if grep -Eiq "out of date|profile data may be out of date|no profile data available|mismatch" "$use_log"; then
69+
echo "FAIL OmpDevCov: -fprofile-use reported stale/mismatched profile"
70+
cat "$use_log"; rm -f "$use_log"; exit 1
71+
fi
72+
rm -f "$use_log"
73+
74+
run_env ./omp_pgo_use
75+
76+
echo "OmpDevCov PASSED"

0 commit comments

Comments
 (0)