https://github.com/aobolensk created https://github.com/llvm/llvm-project/pull/230910
None >From a11022813ac64ae705ed90781a0a84cba3e4da57 Mon Sep 17 00:00:00 2001 From: Arseniy Obolenskiy <[email protected]> Date: Sun, 11 Oct 2026 09:31:06 +0200 Subject: [PATCH] [CIR][HIP] Support managed variables --- clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp | 70 +++++++++++++ clang/lib/CIR/CodeGen/CIRGenModule.cpp | 3 +- .../Dialect/Transforms/LoweringPrepare.cpp | 35 ++++++- .../template-class-static-member.cu | 6 +- clang/test/CIR/CodeGenHIP/managed-var.hip | 97 +++++++++++++++++++ 5 files changed, 204 insertions(+), 7 deletions(-) create mode 100644 clang/test/CIR/CodeGenHIP/managed-var.hip diff --git a/clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp b/clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp index 79153ca788756..8f7d65b827507 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp @@ -62,6 +62,7 @@ class CIRGenNVCUDARuntime : public CIRGenCUDARuntime { void emitDeviceStubBodyNew(CIRGenFunction &cgf, cir::FuncOp fn, FunctionArgList &args); void recordDeviceBinary(); + void transformManagedVars(); mlir::Value prepareKernelArgs(CIRGenFunction &cgf, mlir::Location loc, FunctionArgList &args); mlir::Operation *getKernelHandle(cir::FuncOp fn, GlobalDecl gd) override; @@ -581,7 +582,76 @@ void CIRGenNVCUDARuntime::recordDeviceBinary() { mlir::StringAttr::get(bytes, fatbinTy)); } +// Uses of a managed variable load its address from a pointer the runtime fills. +void CIRGenNVCUDARuntime::transformManagedVars() { + CIRGenBuilderTy &builder = cgm.getBuilder(); + mlir::ModuleOp module = cgm.getModule(); + for (auto &&info : deviceVars) { + cir::GlobalOp var = info.var; + if (info.flags != cir::CUDADeviceVarKind::Variable || + !info.d->hasAttr<HIPManagedAttr>()) + continue; + + cir::PointerType varPtrTy = + builder.getPointerTo(var.getSymType(), var.getAddrSpaceAttr()); + mlir::ptr::MemorySpaceAttrInterface managedAS = cir::toCIRAddressSpaceAttr( + cgm.getMLIRContext(), + cgm.getLangOpts().CUDAIsDevice ? LangAS::cuda_device : LangAS::Default); + std::optional<mlir::SymbolTable::UseRange> uses = var.getSymbolUses(module); + + std::string name = var.getSymName().str(); + // An empty comdat would follow the rename. + std::optional<llvm::StringRef> comdat = var.getComdat(); + if (comdat && comdat->empty()) + var.setComdat(llvm::StringRef(name)); + cgm.eraseGlobalSymbol(var); + var.setSymName(name + ".managed"); + cgm.insertGlobalSymbol(var); + + cir::GlobalOp managedVar = + cgm.createGlobalOp(var.getLoc(), name, varPtrTy, + /*isConstant=*/false, managedAS); + managedVar.setLinkage(var.getLinkage()); + if (!var.isDeclaration()) + managedVar.setInitialValueAttr(builder.getConstNullPtrAttr(varPtrTy)); + managedVar.setDsoLocal(var.getDsoLocal()); + managedVar.setGlobalVisibility(var.getGlobalVisibility()); + mlir::SymbolTable::setSymbolVisibility( + managedVar, mlir::SymbolTable::getSymbolVisibility(var)); + managedVar->setAttr( + cir::CUDAExternallyInitializedAttr::getMnemonic(), + cir::CUDAExternallyInitializedAttr::get(&cgm.getMLIRContext())); + + // The uses now name the pointer global. + cir::PointerType managedPtrTy = + builder.getPointerTo(varPtrTy, managedVar.getAddrSpaceAttr()); + for (mlir::SymbolTable::SymbolUse use : *uses) { + auto getGlobalOp = dyn_cast<cir::GetGlobalOp>(use.getUser()); + if (!getGlobalOp) { + cgm.errorNYI(info.d->getSourceRange(), + "transformManagedVars: non-instruction use"); + continue; + } + mlir::Value addr = getGlobalOp.getAddr(); + addr.setType(managedPtrTy); + mlir::OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointAfter(getGlobalOp); + mlir::Value ld = builder.createAlignedLoad( + getGlobalOp.getLoc(), varPtrTy, addr, + CharUnits::fromQuantity(var.getAlignment().value_or(1))); + addr.replaceAllUsesExcept(ld, ld.getDefiningOp()); + } + + // The runtime allocates them even if device code never uses them. + if (cgm.getLangOpts().CUDAIsDevice && !var.isDeclaration()) { + cgm.addCompilerUsedGlobal(var); + cgm.addCompilerUsedGlobal(managedVar); + } + } +} + void CIRGenNVCUDARuntime::finalizeModule() { + transformManagedVars(); if (!cgm.getLangOpts().CUDAIsDevice) { recordDeviceBinary(); return; diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 1f80f4e0ebf08..d38ed3a54e7eb 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -1414,8 +1414,7 @@ CIRGenModule::getOrCreateCIRGlobal(StringRef mangledName, mlir::Type ty, // in both device and host compilations. if (getLangOpts().CUDA && d && d->hasAttr<HIPManagedAttr>() && d->hasExternalStorage()) - errorNYI(d->getSourceRange(), - "getOrCreateCIRGlobal: HIP managed attribute"); + getCUDARuntime().handleVarRegistration(d, gv); } assert(!cir::MissingFeatures::addressSpace()); diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp index 9334bf65210c4..fb5eaf6fe1cbb 100644 --- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -3262,6 +3262,15 @@ void LoweringPreparePass::buildCUDARegisterVars(cir::CIRBaseBuilderTy &builder, FuncType::get({voidPtrPtrTy, voidPtrTy, voidPtrTy, voidPtrTy, intTy, sizeTy, intTy, intTy}, voidTy)); + // void __hipRegisterManagedVar(void **fatbinHandle, char *managedVar, + // char *var, const char *deviceName, + // size_t size, unsigned align); + FuncOp cudaRegisterManagedVar = buildRuntimeFunction( + globalBuilder, addUnderscoredPrefix(cudaPrefix, "RegisterManagedVar"), + loc, + FuncType::get( + {voidPtrPtrTy, voidPtrTy, voidPtrTy, voidPtrTy, sizeTy, intTy}, + voidTy)); auto makeConstantString = [&](llvm::StringRef str) -> GlobalOp { auto strType = ArrayType::get(&getContext(), charTy, 1 + str.size()); @@ -3287,8 +3296,9 @@ void LoweringPreparePass::buildCUDARegisterVars(cir::CIRBaseBuilderTy &builder, llvm_unreachable("Texture registration NYI"); } - if (regAttr.getIsManaged()) - llvm_unreachable("Managed variable registration NYI"); + // External managed variables are registered by their defining TU. + if (regAttr.getIsManaged() && global.isDeclaration()) + continue; GlobalOp deviceNameStr = makeConstantString(regAttr.getDeviceSideName()); mlir::Value deviceName = builder.createBitcast( @@ -3296,9 +3306,28 @@ void LoweringPreparePass::buildCUDARegisterVars(cir::CIRBaseBuilderTy &builder, mlir::Value hostVar = builder.createBitcast(builder.createGetGlobal(global), voidPtrTy); + llvm::TypeSize size = dataLayout.getTypeAllocSize(global.getSymType()); + + if (regAttr.getIsManaged()) { + llvm::StringRef name = global.getSymName(); + assert(name.ends_with(".managed") && + "HIP managed variables not transformed"); + auto managedVar = mlirModule.lookupSymbol<GlobalOp>( + name.drop_back(llvm::StringRef(".managed").size())); + mlir::Value managedPtr = + builder.createBitcast(builder.createGetGlobal(managedVar), voidPtrTy); + auto varSize = ConstantOp::create( + builder, loc, IntAttr::get(sizeTy, size.getFixedValue())); + auto align = ConstantOp::create( + builder, loc, IntAttr::get(intTy, global.getAlignment().value_or(1))); + builder.createCallOp( + loc, cudaRegisterManagedVar, + {fatbinHandle, managedPtr, hostVar, deviceName, varSize, align}); + continue; + } + auto isExtern = ConstantOp::create( builder, loc, IntAttr::get(intTy, regAttr.getIsExtern() ? 1 : 0)); - llvm::TypeSize size = dataLayout.getTypeAllocSize(global.getSymType()); auto varSize = ConstantOp::create( builder, loc, IntAttr::get(sizeTy, size.getFixedValue())); auto isConstant = ConstantOp::create( diff --git a/clang/test/CIR/CodeGenCUDA/template-class-static-member.cu b/clang/test/CIR/CodeGenCUDA/template-class-static-member.cu index fccfddc2b8018..32a8f62a8de30 100644 --- a/clang/test/CIR/CodeGenCUDA/template-class-static-member.cu +++ b/clang/test/CIR/CodeGenCUDA/template-class-static-member.cu @@ -56,7 +56,8 @@ template class A<int>; // CIR-DEV-DAG: cir.global "private" internal comdat dso_local target_address_space(1) @_ZN1AIiE8d_memberE = #cir.int<0> // CIR-DEV-DAG: cir.global "private" constant internal comdat dso_local target_address_space(4) @_ZN1AIiE8c_memberE = #cir.int<0> -// CIR-DEV-DAG: cir.global "private" internal comdat dso_local target_address_space(1) @_ZN1AIiE8m_memberE = #cir.int<0> +// CIR-DEV-DAG: cir.global "private" internal comdat("_ZN1AIiE8m_memberE") dso_local target_address_space(1) @_ZN1AIiE8m_memberE.managed = #cir.int<0> +// CIR-DEV-DAG: cir.global "private" internal dso_local target_address_space(1) @_ZN1AIiE8m_memberE = #cir.ptr<null> : !cir.ptr<!s32i, target_address_space(1)> {cu.externally_initialized = #cir.cu.externally_initialized} // CIR-DEV-DAG: cir.global "private" constant internal comdat dso_local target_address_space(4) @_ZN1AIiE12const_memberE = #cir.int<0> // CIR-DEV-NEG-NOT: @_ZN1AIiE8h_memberE @@ -69,7 +70,8 @@ template class A<int>; // CIR-HOST-DAG: cir.global weak_odr comdat @_ZN1AIiE8h_memberE = #cir.int<0> // CIR-HOST-DAG: cir.global "private" internal comdat dso_local @_ZN1AIiE8d_memberE = #cir.undef // CIR-HOST-DAG: cir.global "private" internal comdat dso_local @_ZN1AIiE8c_memberE = #cir.undef -// CIR-HOST-DAG: cir.global "private" internal comdat dso_local @_ZN1AIiE8m_memberE = #cir.int<0> +// CIR-HOST-DAG: cir.global "private" internal comdat("_ZN1AIiE8m_memberE") dso_local @_ZN1AIiE8m_memberE.managed = #cir.int<0> +// CIR-HOST-DAG: cir.global "private" internal dso_local @_ZN1AIiE8m_memberE = #cir.ptr<null> : !cir.ptr<!s32i> {cu.externally_initialized = #cir.cu.externally_initialized} // CIR-HOST-DAG: cir.global constant weak_odr comdat @_ZN1AIiE12const_memberE = #cir.int<0> // HOST-DAG: @_ZN1AIiE8h_memberE = weak_odr global i32 0, comdat, align 4 diff --git a/clang/test/CIR/CodeGenHIP/managed-var.hip b/clang/test/CIR/CodeGenHIP/managed-var.hip new file mode 100644 index 0000000000000..313a73e5935d9 --- /dev/null +++ b/clang/test/CIR/CodeGenHIP/managed-var.hip @@ -0,0 +1,97 @@ +// RUN: echo -n "GPU binary would be here." > %t + +// RUN: %clang_cc1 -triple amdgcn-amd-amdhsa -fcuda-is-device -fclangir \ +// RUN: -x hip -emit-cir %s -o %t.dev.cir +// RUN: FileCheck --check-prefix=CIR-DEV --input-file=%t.dev.cir %s + +// RUN: %clang_cc1 -triple amdgcn-amd-amdhsa -fcuda-is-device -fclangir \ +// RUN: -x hip -emit-llvm %s -o %t.dev.cir.ll +// RUN: FileCheck --check-prefix=DEV --input-file=%t.dev.cir.ll %s + +// RUN: %clang_cc1 -triple amdgcn-amd-amdhsa -fcuda-is-device \ +// RUN: -x hip -emit-llvm %s -o %t.dev.ll +// RUN: FileCheck --check-prefix=DEV --input-file=%t.dev.ll %s + +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir \ +// RUN: -x hip -fhip-new-launch-api -fcuda-include-gpubinary %t \ +// RUN: -emit-cir %s -o %t.host.cir +// RUN: FileCheck --check-prefix=CIR-HOST --input-file=%t.host.cir %s + +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir \ +// RUN: -x hip -fhip-new-launch-api -fcuda-include-gpubinary %t \ +// RUN: -emit-llvm %s -o %t.host.cir.ll +// RUN: FileCheck --check-prefix=HOST --input-file=%t.host.cir.ll %s + +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu \ +// RUN: -x hip -fhip-new-launch-api -fcuda-include-gpubinary %t \ +// RUN: -emit-llvm %s -o %t.host.ll +// RUN: FileCheck --check-prefix=HOST --input-file=%t.host.ll %s + +#include "../CodeGenCUDA/Inputs/cuda.h" + +__managed__ int x = 1; +__managed__ float arr[8]; +extern __managed__ int ex; +static __managed__ int sx = 2; + +template <typename T> __managed__ T tv = 3; + +// CIR-DEV-DAG: cir.global external target_address_space(1) @x.managed = #cir.int<1> : !s32i align(4) {cu.externally_initialized = #cir.cu.externally_initialized, cu.var_registration = #cir.cu.var_registration<x, Variable, managed>} +// CIR-DEV-DAG: cir.global external target_address_space(1) @x = #cir.ptr<null> : !cir.ptr<!s32i, target_address_space(1)> {cu.externally_initialized = #cir.cu.externally_initialized} +// CIR-DEV-DAG: cir.global linkonce_odr comdat("_Z2tvIiE") target_address_space(1) @_Z2tvIiE.managed = #cir.int<3> + +// DEV-DAG: @x.managed = addrspace(1) externally_initialized global i32 1, align 4 +// DEV-DAG: @x = addrspace(1) externally_initialized global ptr addrspace(1) null +// DEV-DAG: @arr.managed = addrspace(1) externally_initialized global [8 x float] zeroinitializer, align 4 +// DEV-DAG: @arr = addrspace(1) externally_initialized global ptr addrspace(1) null +// DEV-DAG: @ex.managed = external addrspace(1) global i32, align 4 +// DEV-DAG: @ex = external addrspace(1) externally_initialized global ptr addrspace(1){{$}} +// DEV-DAG: @_ZL2sx.managed = addrspace(1) externally_initialized global i32 2, align 4 +// DEV-DAG: @_ZL2sx = addrspace(1) externally_initialized global ptr addrspace(1) null +// DEV-DAG: @_Z2tvIiE.managed = linkonce_odr addrspace(1) externally_initialized global i32 3, comdat($_Z2tvIiE), align 4 +// DEV-DAG: @_Z2tvIiE = linkonce_odr addrspace(1) externally_initialized global ptr addrspace(1) null{{$}} +// DEV-DAG: @llvm.compiler.used = {{.*}}@x.managed{{.*}}@x to{{.*}}@arr.managed{{.*}}@arr to{{.*}}@_ZL2sx.managed{{.*}}@_ZL2sx to{{.*}}@_Z2tvIiE.managed{{.*}}@_Z2tvIiE to + +// HOST-DAG: @x.managed = internal global i32 1, align 4 +// HOST-DAG: @x = internal externally_initialized global ptr null +// HOST-DAG: @arr.managed = internal global [8 x float] zeroinitializer, align 16 +// HOST-DAG: @arr = internal externally_initialized global ptr null +// HOST-DAG: @ex.managed = external global i32, align 4 +// HOST-DAG: @ex = external externally_initialized global ptr{{$}} +// HOST-DAG: @_ZL2sx.managed = internal global i32 2, align 4 +// HOST-DAG: @_ZL2sx = internal externally_initialized global ptr null +// HOST-DAG: @_Z2tvIiE.managed = internal global i32 3, comdat($_Z2tvIiE), align 4 +// HOST-DAG: @_Z2tvIiE = internal externally_initialized global ptr null + +// CIR-DEV-LABEL: cir.func{{.*}} @_Z4kernPi +// CIR-DEV: %[[XADDR:.*]] = cir.get_global @x : !cir.ptr<!cir.ptr<!s32i, target_address_space(1)>, target_address_space(1)> +// CIR-DEV: %{{.*}} = cir.load align(4) %[[XADDR]] : !cir.ptr<!cir.ptr<!s32i, target_address_space(1)>, target_address_space(1)>, !cir.ptr<!s32i, target_address_space(1)> + +// DEV-LABEL: define {{.*}}@_Z4kernPi +// DEV: %[[LD_X:.*]] = load ptr addrspace(1), ptr addrspace(1) @x, align 4 +// DEV: store i32 2, ptr %{{.*}}, align 4 +// DEV: load ptr addrspace(1), ptr addrspace(1) @arr, align 4 +// DEV: load ptr addrspace(1), ptr addrspace(1) @ex, align 4 +// DEV: load ptr addrspace(1), ptr addrspace(1) @_ZL2sx, align 4 +// DEV: load ptr addrspace(1), ptr addrspace(1) @_Z2tvIiE, align 4 +__attribute__((global)) void kern(int *out) { + x = 2; + arr[1] = 3.0f; + *out = ex + sx + tv<int>; +} + +// HOST-DAG: load ptr, ptr @x, align 4 +// HOST-DAG: load ptr, ptr @ex, align 4 +// HOST-DAG: load ptr, ptr @_ZL2sx, align 4 +// HOST-DAG: load ptr, ptr @_Z2tvIiE, align 4 +int host_use() { return x + ex + sx + tv<int>; } + +// External managed variables are registered by their defining TU. +// CIR-HOST-LABEL: cir.func internal private @__hip_register_globals +// CIR-HOST-COUNT-4: cir.call @__hipRegisterManagedVar +// CIR-HOST-NEXT: cir.return + +// HOST-DAG: call void @__hipRegisterManagedVar(ptr %{{.*}}, ptr @x, ptr @x.managed, ptr @{{.*}}, i64 4, i32 4) +// HOST-DAG: call void @__hipRegisterManagedVar(ptr %{{.*}}, ptr @arr, ptr @arr.managed, ptr @{{.*}}, i64 32, i32 16) +// HOST-DAG: call void @__hipRegisterManagedVar(ptr %{{.*}}, ptr @_ZL2sx, ptr @_ZL2sx.managed, ptr @{{.*}}, i64 4, i32 4) +// HOST-DAG: call void @__hipRegisterManagedVar(ptr %{{.*}}, ptr @_Z2tvIiE, ptr @_Z2tvIiE.managed, ptr @{{.*}}, i64 4, i32 4) _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
