blob: ca5e185add5fc3a4e8228adf0406df95c19b1fba [file] [edit]
// Based on clang/test/CodeGenCUDA/device-stub.cu (incubator).
// Create a dummy GPU binary file for registration.
// RUN: echo -n "GPU binary would be here." > %t
// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-cir %s -x cuda \
// RUN: -target-sdk-version=12.3 -fcuda-include-gpubinary %t -o %t.cir
// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR
// RUN: %clang_cc1 -triple x86_64-linux-gnu -fclangir -emit-llvm %s -x cuda \
// RUN: -target-sdk-version=12.3 -fcuda-include-gpubinary %t -o %t-cir.ll
// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=LLVM
// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm %s -x cuda \
// RUN: -target-sdk-version=12.3 -fcuda-include-gpubinary %t -o %t.ll
// RUN: FileCheck --input-file=%t.ll %s --check-prefix=OGCG
// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-cir %s -x cuda \
// RUN: -target-sdk-version=12.3 -o %t.nogpu.cir
// RUN: FileCheck --input-file=%t.nogpu.cir %s --check-prefix=NOGPUBIN
// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-cir %s -x hip \
// RUN: -fhip-new-launch-api -fcuda-include-gpubinary %t -o %t.hip.cir
// RUN: FileCheck --input-file=%t.hip.cir %s --check-prefix=HIP-CIR
// RUN: %clang_cc1 -triple x86_64-linux-gnu -fclangir -emit-llvm %s -x hip \
// RUN: -fhip-new-launch-api -fcuda-include-gpubinary %t -o %t.hip-cir.ll
// RUN: FileCheck --input-file=%t.hip-cir.ll %s --check-prefix=HIP-LLVM
//
// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm %s -x hip \
// RUN: -fhip-new-launch-api -fcuda-include-gpubinary %t -o %t.hip.ll
// RUN: FileCheck --input-file=%t.hip.ll %s --check-prefix=HIP-OGCG
// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-cir %s -x hip \
// RUN: -fhip-new-launch-api -o %t.nogpu.hip.cir
// RUN: FileCheck --input-file=%t.nogpu.hip.cir %s --check-prefix=HIP-NOGPUBIN
#include "Inputs/cuda.h"
__global__ void kernelfunc(int i, int j, int k) {}
void hostfunc(void) { kernelfunc<<<1, 1>>>(1, 1, 1); }
// Device-side shadows: exercise the __cudaRegisterVar code path alongside the
// existing __cudaRegisterFunction kernel registration.
__device__ int a;
__constant__ int b;
__device__ _BitInt(36) c;
// Check module constructor is registered in module attributes.
// CIR: cir.global_ctors = [#cir.global_ctor<"__cuda_module_ctor", 65535>]
// Check runtime function declarations.
// CIR: cir.func private @atexit(!cir.ptr<!cir.func<()>>) -> !s32i
// CIR: cir.func private @__cudaUnregisterFatBinary(!cir.ptr<!cir.ptr<!void>>)
// Check the module destructor body: load handle and call UnregisterFatBinary.
// CIR: cir.func internal private @__cuda_module_dtor()
// CIR-NEXT: %[[HANDLE_ADDR:.*]] = cir.get_global @__cuda_gpubin_handle
// CIR-NEXT: %[[HANDLE:.*]] = cir.load %[[HANDLE_ADDR]]
// CIR-NEXT: cir.call @__cudaUnregisterFatBinary(%[[HANDLE]])
// CIR-NEXT: cir.return
// CIR: cir.func private @__cudaRegisterFatBinaryEnd(!cir.ptr<!cir.ptr<!void>>)
// __cudaRegisterVar runtime declaration and per-variable name strings for
// device shadows. These are emitted between __cudaRegisterFatBinaryEnd and
// __cudaRegisterFunction; relative order is not significant.
// CIR-DAG: cir.func private @__cudaRegisterVar(!cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>, !cir.ptr<!void>, !cir.ptr<!void>, !s32i, !u64i, !s32i, !s32i)
// CIR-DAG: cir.global "private" constant cir_private @".stra" = #cir.const_array<"a" : !cir.array<!u8i x 2>, trailing_zeros>
// CIR-DAG: cir.global "private" constant cir_private @".strb" = #cir.const_array<"b" : !cir.array<!u8i x 2>, trailing_zeros>
// CIR-DAG: cir.global "private" constant cir_private @".strc" = #cir.const_array<"c" : !cir.array<!u8i x 2>, trailing_zeros>
// Check the __cudaRegisterFunction runtime declaration:
// int __cudaRegisterFunction(void**, void*, void*, void*, int,
// void*, void*, void*, void*, void*)
// CIR: cir.func private @__cudaRegisterFunction(!cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>, !cir.ptr<!void>, !cir.ptr<!void>, !s32i, !cir.ptr<!void>, !cir.ptr<!void>, !cir.ptr<!void>, !cir.ptr<!void>, !cir.ptr<!void>) -> !s32i
// Check the device-side name string for kernelfunc (mangled, null-terminated).
// CIR: cir.global "private" constant cir_private @".str_Z10kernelfunciii" = #cir.const_array<"_Z10kernelfunciii" : !cir.array<!u8i x 18>, trailing_zeros> : !cir.array<!u8i x 18>
// Check __cuda_register_globals body: __cudaRegisterFunction for each kernel,
// then __cudaRegisterVar for each device shadow.
// CIR: cir.func internal private @__cuda_register_globals(%[[FATBIN:.*]]: !cir.ptr<!cir.ptr<!void>>
// CIR-NEXT: %[[NULL:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void>
// CIR-NEXT: %[[STR_ADDR:.*]] = cir.get_global @".str_Z10kernelfunciii"
// CIR-NEXT: %[[DEVICE_FUNC:.*]] = cir.cast bitcast %[[STR_ADDR]]
// CIR-NEXT: %[[HOST_FUNC_RAW:.*]] = cir.get_global @{{.*}}kernelfunc{{.*}}
// CIR-NEXT: %[[HOST_FUNC:.*]] = cir.cast bitcast %[[HOST_FUNC_RAW]]
// CIR-NEXT: %[[THREAD_LIMIT:.*]] = cir.const #cir.int<-1> : !s32i
// CIR-NEXT: cir.call @__cudaRegisterFunction(%{{.*}}, %[[HOST_FUNC]], %[[DEVICE_FUNC]], %[[DEVICE_FUNC]], %[[THREAD_LIMIT]], %[[NULL]], %[[NULL]], %[[NULL]], %[[NULL]], %[[NULL]])
// Registration for __device__ int a (constant=0):
// CIR: %[[#NAMEA_RAW:]] = cir.get_global @".stra"
// CIR-NEXT: %[[#NAMEA:]] = cir.cast bitcast %[[#NAMEA_RAW]]
// CIR-NEXT: %[[#HOSTA_RAW:]] = cir.get_global @a
// CIR-NEXT: %[[#HOSTA:]] = cir.cast bitcast %[[#HOSTA_RAW]]
// CIR-NEXT: %[[#EXTA:]] = cir.const #cir.int<0> : !s32i
// CIR-NEXT: %[[#SZA:]] = cir.const #cir.int<4> : !u64i
// CIR-NEXT: %[[#CONA:]] = cir.const #cir.int<0> : !s32i
// CIR-NEXT: %[[#NORMA:]] = cir.const #cir.int<0> : !s32i
// CIR-NEXT: cir.call @__cudaRegisterVar(%[[FATBIN]], %[[#HOSTA]], %[[#NAMEA]], %[[#NAMEA]], %[[#EXTA]], %[[#SZA]], %[[#CONA]], %[[#NORMA]])
// Registration for __constant__ int b (constant=1):
// CIR: %[[#NAMEB_RAW:]] = cir.get_global @".strb"
// CIR-NEXT: %[[#NAMEB:]] = cir.cast bitcast %[[#NAMEB_RAW]]
// CIR-NEXT: %[[#HOSTB_RAW:]] = cir.get_global @b
// CIR-NEXT: %[[#HOSTB:]] = cir.cast bitcast %[[#HOSTB_RAW]]
// CIR-NEXT: %[[#EXTB:]] = cir.const #cir.int<0> : !s32i
// CIR-NEXT: %[[#SZB:]] = cir.const #cir.int<4> : !u64i
// CIR-NEXT: %[[#CONB:]] = cir.const #cir.int<1> : !s32i
// CIR-NEXT: %[[#NORMB:]] = cir.const #cir.int<0> : !s32i
// CIR-NEXT: cir.call @__cudaRegisterVar(%[[FATBIN]], %[[#HOSTB]], %[[#NAMEB]], %[[#NAMEB]], %[[#EXTB]], %[[#SZB]], %[[#CONB]], %[[#NORMB]])
// Registration for __device__ _BitInt(36)
// CIR: %[[#NAMEC_RAW:]] = cir.get_global @".strc"
// CIR-NEXT: %[[#NAMEC:]] = cir.cast bitcast %[[#NAMEC_RAW]]
// CIR-NEXT: %[[#HOSTC_RAW:]] = cir.get_global @c
// CIR-NEXT: %[[#HOSTC:]] = cir.cast bitcast %[[#HOSTC_RAW]]
// CIR-NEXT: %[[#EXTC:]] = cir.const #cir.int<0> : !s32i
// CIR-NEXT: %[[#SZC:]] = cir.const #cir.int<8> : !u64i
// CIR-NEXT: %[[#CONC:]] = cir.const #cir.int<0> : !s32i
// CIR-NEXT: %[[#NORMC:]] = cir.const #cir.int<0> : !s32i
// CIR-NEXT: cir.call @__cudaRegisterVar(%[[FATBIN]], %[[#HOSTC]], %[[#NAMEC]], %[[#NAMEC]], %[[#EXTC]], %[[#SZC]], %[[#CONC]], %[[#NORMC]])
// CIR-NEXT: cir.return
// CIR: cir.global "private" constant cir_private @__cuda_fatbin_str = #cir.const_array<"GPU binary would be here." : !cir.array<!u8i x 25>> : !cir.array<!u8i x 25> {alignment = 8 : i64, section = ".nv_fatbin"}
// Check the fatbin wrapper struct: { magic, version, ptr to fatbin, null }, with section.
// CIR: cir.global constant cir_private @__cuda_fatbin_wrapper = #cir.const_record<{
// CIR-SAME: #cir.int<1180844977> : !s32i,
// CIR-SAME: #cir.int<1> : !s32i,
// CIR-SAME: #cir.global_view<@__cuda_fatbin_str> : !cir.ptr<!void>,
// CIR-SAME: #cir.ptr<null> : !cir.ptr<!void>
// CIR-SAME: }> : !rec_anon_struct {section = ".nvFatBinSegment"}
// Check the GPU binary handle global.
// CIR: cir.global "private" internal @__cuda_gpubin_handle = #cir.ptr<null> : !cir.ptr<!cir.ptr<!void>>
// CIR: cir.func private @__cudaRegisterFatBinary(!cir.ptr<!void>) -> !cir.ptr<!cir.ptr<!void>>
// Check the module constructor body: register fatbin, store handle,
// call __cuda_register_globals, call RegisterFatBinaryEnd (CUDA >= 10.1),
// then register dtor with atexit.
// CIR: cir.func internal private @__cuda_module_ctor()
// CIR-NEXT: %[[WRAPPER:.*]] = cir.get_global @__cuda_fatbin_wrapper
// CIR-NEXT: %[[VOID_PTR:.*]] = cir.cast bitcast %[[WRAPPER]]
// CIR-NEXT: %[[RET:.*]] = cir.call @__cudaRegisterFatBinary(%[[VOID_PTR]])
// CIR-NEXT: %[[HANDLE_ADDR:.*]] = cir.get_global @__cuda_gpubin_handle
// CIR-NEXT: cir.store %[[RET]], %[[HANDLE_ADDR]]
// CIR-NEXT: cir.call @__cuda_register_globals(%[[RET]])
// CIR-NEXT: cir.call @__cudaRegisterFatBinaryEnd(%[[RET]])
// CIR-NEXT: %[[DTOR_PTR:.*]] = cir.get_global @__cuda_module_dtor
// CIR-NEXT: {{.*}} = cir.call @atexit(%[[DTOR_PTR]])
// CIR-NEXT: cir.return
// OGCG: constant [25 x i8] c"GPU binary would be here.", section ".nv_fatbin", align 8
// OGCG: @__cuda_fatbin_wrapper = internal constant { i32, i32, ptr, ptr } { i32 1180844977, i32 1, ptr @{{.*}}, ptr null }, section ".nvFatBinSegment"
// OGCG: @__cuda_gpubin_handle = internal global ptr null
// OGCG: @llvm.global_ctors = appending global {{.*}}@__cuda_module_ctor
// OGCG: define internal void @__cuda_register_globals(ptr %[[#OGFATBIN:]])
// OGCG: call{{.*}}__cudaRegisterFunction(ptr %[[#OGFATBIN]], {{.*}}kernelfunc{{.*}}
// OGCG: call void @__cudaRegisterVar(ptr %[[#OGFATBIN]], ptr @a, {{.*}}, {{.*}}, i32 0, i64 4, i32 0, i32 0)
// OGCG: call void @__cudaRegisterVar(ptr %[[#OGFATBIN]], ptr @b, {{.*}}, {{.*}}, i32 0, i64 4, i32 1, i32 0)
// OGCG: call void @__cudaRegisterVar(ptr %[[#OGFATBIN]], ptr @c, {{.*}}, {{.*}}, i32 0, i64 8, i32 0, i32 0)
// OGCG: ret void
// OGCG: define internal void @__cuda_module_ctor
// OGCG: call{{.*}}__cudaRegisterFatBinary(ptr @__cuda_fatbin_wrapper)
// OGCG: store ptr %{{.*}}, ptr @__cuda_gpubin_handle
// OGCG-NEXT: call void @__cuda_register_globals
// OGCG: call i32 @atexit(ptr @__cuda_module_dtor)
// OGCG: define internal void @__cuda_module_dtor
// OGCG: load ptr, ptr @__cuda_gpubin_handle
// OGCG: call void @__cudaUnregisterFatBinary
// LLVM: constant [25 x i8] c"GPU binary would be here.", section ".nv_fatbin", align 8
// LLVM: @__cuda_fatbin_wrapper = {{.*}}constant { i32, i32, ptr, ptr } { i32 1180844977, i32 1, ptr @{{.*}}, ptr null }, section ".nvFatBinSegment"
// LLVM: @__cuda_gpubin_handle = internal global ptr null
// LLVM: @llvm.global_ctors = appending global {{.*}}@__cuda_module_ctor
// LLVM: define internal void @__cuda_module_dtor
// LLVM: load ptr, ptr @__cuda_gpubin_handle
// LLVM: call void @__cudaUnregisterFatBinary
// LLVM: define internal void @__cuda_register_globals(ptr %[[#FATBIN:]])
// LLVM: call{{.*}}@__cudaRegisterFunction(ptr %[[#FATBIN]], ptr @{{.*}}kernelfunc{{.*}}, ptr @{{.*}}, ptr @{{.*}}, i32 -1, ptr null, ptr null, ptr null, ptr null, ptr null)
// LLVM: call void @__cudaRegisterVar(ptr %[[#FATBIN]], ptr @a, ptr @.stra, ptr @.stra, i32 0, i64 4, i32 0, i32 0)
// LLVM: call void @__cudaRegisterVar(ptr %[[#FATBIN]], ptr @b, ptr @.strb, ptr @.strb, i32 0, i64 4, i32 1, i32 0)
// LLVM: call void @__cudaRegisterVar(ptr %[[#FATBIN]], ptr @c, ptr @.strc, ptr @.strc, i32 0, i64 8, i32 0, i32 0)
// LLVM: ret void
// LLVM: define internal void @__cuda_module_ctor
// LLVM: call{{.*}}@__cudaRegisterFatBinary(ptr @__cuda_fatbin_wrapper)
// LLVM: store ptr %{{.*}}, ptr @__cuda_gpubin_handle
// LLVM-NEXT: call void @__cuda_register_globals
// LLVM: call i32 @atexit(ptr @__cuda_module_dtor)
// No GPU binary — no registration infrastructure at all.
// NOGPUBIN-NOT: fatbin
// NOGPUBIN-NOT: gpubin
// NOGPUBIN-NOT: __cuda_register_globals
// NOGPUBIN-NOT: __cuda_module_ctor
// NOGPUBIN-NOT: __cuda_module_dtor
// =============================================================================
// HIP host-side registration (`buildCUDAModuleCtor` / `buildHIPModuleDtor` /
// `buildCUDARegisterGlobalFunctions` HIP arms in CIR LoweringPrepare).
// =============================================================================
// HIP module ctor is registered with the default global-ctor priority.
// HIP-CIR: cir.global_ctors = [#cir.global_ctor<"__hip_module_ctor", 65535>]
// Runtime function decls.
// HIP-CIR: cir.func private @atexit(!cir.ptr<!cir.func<()>>) -> !s32i
// HIP-CIR: cir.func private @__hipUnregisterFatBinary(!cir.ptr<!cir.ptr<!void>>)
// Module dtor: only unregister when the handle is non-null, then null it out.
// Reuses the SSA value loaded in the entry block for the unregister call.
// HIP-CIR: cir.func internal private @__hip_module_dtor()
// HIP-CIR: %[[DH0:.*]] = cir.get_global @__hip_gpubin_handle
// HIP-CIR: %[[H0:.*]] = cir.load %[[DH0]]
// HIP-CIR: %[[NULL0:.*]] = cir.const #cir.ptr<null>
// HIP-CIR: %[[NE:.*]] = cir.cmp ne %[[H0]], %[[NULL0]]
// HIP-CIR: cir.brcond %[[NE]] ^bb1, ^bb2
// HIP-CIR: ^bb1:
// HIP-CIR: cir.call @__hipUnregisterFatBinary(%[[H0]])
// HIP-CIR: %[[DH1:.*]] = cir.get_global @__hip_gpubin_handle
// HIP-CIR: cir.store %[[NULL0]], %[[DH1]]
// HIP-CIR: cir.br ^bb2
// HIP-CIR: ^bb2:
// HIP-CIR: cir.return
// __hipRegisterFunction runtime declaration.
// HIP-CIR: cir.func private @__hipRegisterFunction(!cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>, !cir.ptr<!void>, !cir.ptr<!void>, !s32i, !cir.ptr<!void>, !cir.ptr<!void>, !cir.ptr<!void>, !cir.ptr<!void>, !cir.ptr<!void>) -> !s32i
// __hip_register_globals: under -fhip-new-launch-api the host-side argument is
// the kernel-handle GlobalOp shadow (e.g. @_Z10kernelfunciii) — not the
// device-stub function pointer that the CUDA arm uses.
// HIP-CIR: cir.global "private" constant cir_private @".str_Z10kernelfunciii" = #cir.const_array<"_Z10kernelfunciii" : !cir.array<!u8i x 18>, trailing_zeros> : !cir.array<!u8i x 18>
// HIP-CIR: cir.func internal private @__hip_register_globals(%[[FATBIN:.*]]: !cir.ptr<!cir.ptr<!void>>
// HIP-CIR: %[[NULL1:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void>
// HIP-CIR: %[[STR_ADDR:.*]] = cir.get_global @".str_Z10kernelfunciii"
// HIP-CIR: %[[DEVICE_FUNC:.*]] = cir.cast bitcast %[[STR_ADDR]]
// HIP-CIR: %[[KH:.*]] = cir.get_global @_Z10kernelfunciii : !cir.ptr<!cir.ptr<!cir.func<(!s32i, !s32i, !s32i)>>>
// HIP-CIR: %[[HOST_FUNC:.*]] = cir.cast bitcast %[[KH]]
// HIP-CIR: %[[MINUS_ONE:.*]] = cir.const #cir.int<-1> : !s32i
// HIP-CIR: cir.call @__hipRegisterFunction(%[[FATBIN]], %[[HOST_FUNC]], %[[DEVICE_FUNC]], %[[DEVICE_FUNC]], %[[MINUS_ONE]], %[[NULL1]], %[[NULL1]], %[[NULL1]], %[[NULL1]], %[[NULL1]])
// HIP-CIR: cir.return
// Fatbin string + wrapper live in the HIP-specific sections; magic
// 0x48495046 = 1212764230.
// HIP-CIR: cir.global "private" constant cir_private @__hip_fatbin_str = #cir.const_array<"GPU binary would be here." : !cir.array<!u8i x 25>> : !cir.array<!u8i x 25> {alignment = 8 : i64, section = ".hip_fatbin"}
// HIP-CIR: cir.global constant cir_private @__hip_fatbin_wrapper = #cir.const_record<{
// HIP-CIR-SAME: #cir.int<1212764230> : !s32i,
// HIP-CIR-SAME: #cir.int<1> : !s32i,
// HIP-CIR-SAME: #cir.global_view<@__hip_fatbin_str> : !cir.ptr<!void>,
// HIP-CIR-SAME: #cir.ptr<null> : !cir.ptr<!void>
// HIP-CIR-SAME: }> : !rec_anon_struct {section = ".hipFatBinSegment"}
// HIP-CIR: cir.global "private" internal @__hip_gpubin_handle = #cir.ptr<null> : !cir.ptr<!cir.ptr<!void>>
// HIP-CIR: cir.func private @__hipRegisterFatBinary(!cir.ptr<!void>) -> !cir.ptr<!cir.ptr<!void>>
// Module ctor: guard registration on a null handle, register globals from the
// (possibly newly-stored) handle, then atexit(__hip_module_dtor).
// HIP-CIR: cir.func internal private @__hip_module_ctor()
// HIP-CIR: %[[GHA:.*]] = cir.get_global @__hip_gpubin_handle
// HIP-CIR: %[[H:.*]] = cir.load %[[GHA]]
// HIP-CIR: %[[NULLPTR:.*]] = cir.const #cir.ptr<null>
// HIP-CIR: %[[EQ:.*]] = cir.cmp eq %[[H]], %[[NULLPTR]]
// HIP-CIR: cir.brcond %[[EQ]] ^bb1, ^bb2
// HIP-CIR: ^bb1:
// HIP-CIR: %[[WRAPPER:.*]] = cir.get_global @__hip_fatbin_wrapper
// HIP-CIR: %[[VOID_PTR:.*]] = cir.cast bitcast %[[WRAPPER]]
// HIP-CIR: %[[REG:.*]] = cir.call @__hipRegisterFatBinary(%[[VOID_PTR]])
// HIP-CIR: %[[GHA2:.*]] = cir.get_global @__hip_gpubin_handle
// HIP-CIR: cir.store %[[REG]], %[[GHA2]]
// HIP-CIR: cir.br ^bb2
// HIP-CIR: ^bb2:
// HIP-CIR: %[[GHA3:.*]] = cir.get_global @__hip_gpubin_handle
// HIP-CIR: %[[H2:.*]] = cir.load %[[GHA3]]
// HIP-CIR: cir.call @__hip_register_globals(%[[H2]])
// HIP-CIR: %[[DTOR_PTR:.*]] = cir.get_global @__hip_module_dtor
// HIP-CIR: {{.*}} = cir.call @atexit(%[[DTOR_PTR]])
// HIP-CIR: cir.return
// HIP-CIR: cir.global constant external @_Z10kernelfunciii = #cir.global_view<@_Z25__device_stub__kernelfunciii> : !cir.ptr<!cir.func<(!s32i, !s32i, !s32i)>> {alignment = 8 : i64}
// HIP OGCG cross-check (LLVM IR matches what OG codegen emits for HIP).
// HIP-OGCG: @{{.*}} = private constant [25 x i8] c"GPU binary would be here.", section ".hip_fatbin"
// HIP-OGCG: @__hip_fatbin_wrapper = internal constant { i32, i32, ptr, ptr } { i32 1212764230, i32 1, ptr @{{.*}}, ptr null }, section ".hipFatBinSegment"
// HIP-OGCG: @__hip_gpubin_handle = internal global ptr null
// HIP-OGCG: @llvm.global_ctors = appending global {{.*}}@__hip_module_ctor
// HIP-OGCG: define internal void @__hip_module_ctor()
// HIP-OGCG: load ptr, ptr @__hip_gpubin_handle
// HIP-OGCG: icmp eq ptr {{.*}}, null
// HIP-OGCG: call ptr @__hipRegisterFatBinary(ptr @__hip_fatbin_wrapper)
// HIP-OGCG: store ptr {{.*}}, ptr @__hip_gpubin_handle
// HIP-OGCG: call void @__hip_register_globals(
// HIP-OGCG: call i32 @atexit(ptr @__hip_module_dtor)
// HIP-OGCG: ret void
// HIP-OGCG: define internal void @__hip_module_dtor()
// HIP-OGCG: load ptr, ptr @__hip_gpubin_handle
// HIP-OGCG: icmp ne ptr {{.*}}, null
// HIP-OGCG: call void @__hipUnregisterFatBinary
// HIP-OGCG: store ptr null, ptr @__hip_gpubin_handle
// HIP LLVM lowering cross-check.
// HIP-LLVM: @{{.*}} = private constant [25 x i8] c"GPU binary would be here.", section ".hip_fatbin", align 8
// HIP-LLVM: @__hip_fatbin_wrapper = {{.*}}constant { i32, i32, ptr, ptr } { i32 1212764230, i32 1, ptr @{{.*}}, ptr null }, section ".hipFatBinSegment"
// HIP-LLVM: @__hip_gpubin_handle = internal global ptr null
// HIP-LLVM: @_Z10kernelfunciii = constant ptr @_Z25__device_stub__kernelfunciii, align 8
// HIP-LLVM: @llvm.global_ctors = appending global {{.*}}@__hip_module_ctor
// HIP-LLVM: define internal void @__hip_module_dtor()
// HIP-LLVM: load ptr, ptr @__hip_gpubin_handle
// HIP-LLVM: icmp ne ptr {{.*}}, null
// HIP-LLVM: br i1 {{.*}}, label %{{.*}}, label %{{.*}}
// HIP-LLVM: call void @__hipUnregisterFatBinary(ptr {{.*}})
// HIP-LLVM: store ptr null, ptr @__hip_gpubin_handle
// HIP-LLVM: ret void
// HIP-LLVM: define internal void @__hip_register_globals(ptr %[[FATBIN:.*]])
// HIP-LLVM: call{{.*}}@__hipRegisterFunction(ptr %[[FATBIN]], ptr @_Z10kernelfunciii, ptr @{{.*}}, ptr @{{.*}}, i32 -1, ptr null, ptr null, ptr null, ptr null, ptr null)
// HIP-LLVM: ret void
// HIP-LLVM: define internal void @__hip_module_ctor()
// HIP-LLVM: load ptr, ptr @__hip_gpubin_handle
// HIP-LLVM: icmp eq ptr {{.*}}, null
// HIP-LLVM: br i1 {{.*}}, label %{{.*}}, label %{{.*}}
// HIP-LLVM: call ptr @__hipRegisterFatBinary(ptr @__hip_fatbin_wrapper)
// HIP-LLVM: store ptr {{.*}}, ptr @__hip_gpubin_handle
// HIP-LLVM: load ptr, ptr @__hip_gpubin_handle
// HIP-LLVM: call void @__hip_register_globals(ptr {{.*}})
// HIP-LLVM: call i32 @atexit(ptr @__hip_module_dtor)
// HIP-LLVM: ret void
// No GPU binary: no fatbin, no handle, no registration scaffolding.
// HIP-NOGPUBIN-NOT: __hip_fatbin
// HIP-NOGPUBIN-NOT: __hip_gpubin_handle
// HIP-NOGPUBIN-NOT: __hip_register_globals
// HIP-NOGPUBIN-NOT: __hip_module_ctor
// HIP-NOGPUBIN-NOT: __hip_module_dtor