https://github.com/koparasy updated https://github.com/llvm/llvm-project/pull/224667
>From d12197e30ab9bfc84977085d5d57208867044b98 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris <[email protected]> Date: Fri, 18 Sep 2026 07:04:11 -0700 Subject: [PATCH 1/2] [CIR][SYCL] Emit norecurse, mustprogress, and sycl-module-id on the kernel caller Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> --- .../clang/CIR/Dialect/IR/CIRDialect.td | 3 ++ clang/lib/CIR/CodeGen/CIRGenFunction.h | 16 ++++++++ clang/lib/CIR/CodeGen/CIRGenModule.h | 2 + clang/lib/CIR/CodeGen/CIRGenSYCL.cpp | 26 +++++++++++-- .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 27 ++++++++++--- .../CodeGenSYCL/kernel-caller-attributes.cpp | 39 +++++++++++++++++++ 6 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 clang/test/CIR/CodeGenSYCL/kernel-caller-attributes.cpp diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td index e323eff0b9aa6..f07cd7c0dbeb3 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td @@ -53,6 +53,9 @@ def CIR_Dialect : Dialect { static llvm::StringRef getStrictFPAttrName() { return "strictfp"; } static llvm::StringRef getNoDuplicatesAttrName() { return "noduplicate"; } static llvm::StringRef getConvergentAttrName() { return "convergent"; } + static llvm::StringRef getNoRecurseAttrName() { return "norecurse"; } + static llvm::StringRef getMustProgressAttrName() { return "mustprogress"; } + static llvm::StringRef getSYCLModuleIdAttrName() { return "sycl-module-id"; } static llvm::StringRef getNoUnwindAttrName() { return "nounwind"; } static llvm::StringRef getModuleLevelAsmAttrName() { return "cir.module_asm"; } static llvm::StringRef getGlobalCtorsAttrName() { return "cir.global_ctors"; } diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index f690774a25921..91cf7afafc248 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -571,6 +571,22 @@ class CIRGenFunction : public CIRGenTypeCache { const clang::LangOptions &getLangOpts() const { return cgm.getLangOpts(); } + bool checkIfFunctionMustProgress() { + if (cgm.getCodeGenOpts().getFiniteLoops() == + clang::CodeGenOptions::FiniteLoopsKind::Never) + return false; + + // C++11 and later guarantees that a thread eventually will do one of the + // following (C++11 [intro.multithread]p24 and C++17 [intro.progress]p1): + // - terminate, + // - make a call to a library I/O function, + // - perform an access through a volatile glvalue, or + // - perform a synchronization operation or an atomic operation. + // + // Hence each function is 'mustprogress' in C++11 or later. + return getLangOpts().CPlusPlus11; + } + /// True if an insertion point is defined. If not, this indicates that the /// current code being emitted is unreachable. /// FIXME(cir): we need to inspect this and perhaps use a cleaner mechanism diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index 51b9c420c94be..4bdd302764b44 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -668,6 +668,8 @@ class CIRGenModule : public CIRGenTypeCache { /// function declared with the sycl_kernel_entry_point attribute. void emitSYCLKernelCaller(const clang::FunctionDecl *kernelEntryPointFn, clang::ASTContext &ctx); + + void addSYCLModuleIdAttr(cir::FuncOp fn); void emitGlobalVarDefinition(const clang::VarDecl *vd, bool isTentative = false); diff --git a/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp b/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp index edba083c7c406..cfbd580fa3aff 100644 --- a/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp @@ -74,6 +74,24 @@ void CIRGenFunction::emitSYCLKernelCaller( eraseEmptyAndUnusedBlocks(funcOp); } +static void setSYCLKernelAttributes(CIRGenFunction &cgf, cir::FuncOp fn) { + mlir::MLIRContext *ctx = &cgf.getMLIRContext(); + // SYCL 2020 device language restrictions require forward progress and + // disallow recursion. + fn->setAttr(cir::CIRDialect::getNoRecurseAttrName(), + mlir::UnitAttr::get(ctx)); + if (cgf.checkIfFunctionMustProgress()) + fn->setAttr(cir::CIRDialect::getMustProgressAttrName(), + mlir::UnitAttr::get(ctx)); +} + +void CIRGenModule::addSYCLModuleIdAttr(cir::FuncOp fn) { + assert(getLangOpts().SYCLIsDevice); + StringRef moduleId = theModule.getSymName().value_or(""); + fn->setAttr(cir::CIRDialect::getSYCLModuleIdAttrName(), + mlir::StringAttr::get(&getMLIRContext(), moduleId)); +} + void CIRGenModule::emitSYCLKernelCaller(const FunctionDecl *kernelEntryPointFn, ASTContext &ctx) { assert(ctx.getLangOpts().SYCLIsDevice && @@ -119,18 +137,20 @@ void CIRGenModule::emitSYCLKernelCaller(const FunctionDecl *kernelEntryPointFn, // opFuncCallingConv onto the FuncOp, so set it from the target hook. funcOp.setCallingConv(getTargetCIRGenInfo().getDeviceKernelCallingConv()); + CIRGenFunction cgf(*this, builder); + // Route through the shared attribute path so generic function attributes // (e.g. convergent) are applied, matching classic CodeGen's // SetLLVMFunctionAttributes. There is no FunctionDecl, so pass an empty // GlobalDecl. setCIRFunctionAttributes(GlobalDecl(), fnInfo, funcOp, /*isThunk=*/false); - // TODO: attributes applied by classic CodeGen not yet handled in CIR: - // SetSYCLKernelAttributes (norecurse, mustprogress), addSYCLModuleIdAttr. + setSYCLKernelAttributes(cgf, funcOp); + addSYCLModuleIdAttr(funcOp); + assert(!cir::MissingFeatures::setLLVMFunctionFEnvAttributes()); // Emit the SYCL kernel caller function. - CIRGenFunction cgf(*this, builder); llvm::SaveAndRestore<CIRGenFunction *> savedCGF(curCGF, &cgf); { mlir::OpBuilder::InsertionGuard guard(builder); diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index 59654786d1eca..50adaad64a763 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -2694,6 +2694,9 @@ static bool shouldDropFuncAttribute(cir::FuncOp func, mlir::NamedAttribute attr, attr.getName() == func.getSideEffectAttrName() || attr.getName() == CIRDialect::getNoReturnAttrName() || attr.getName() == CIRDialect::getStrictFPAttrName() || + attr.getName() == CIRDialect::getNoRecurseAttrName() || + attr.getName() == CIRDialect::getMustProgressAttrName() || + attr.getName() == CIRDialect::getSYCLModuleIdAttrName() || attr.getName() == func.getAnnotationsAttrName() || attr.getName() == func.getComdatAttrName() || attr.getName() == func.getAlignmentAttrName(); @@ -2848,13 +2851,25 @@ mlir::LogicalResult CIRToLLVMFuncOpLowering::matchAndRewrite( if (op->hasAttr(CIRDialect::getNoReturnAttrName())) fn.setNoreturn(true); - // The LLVM dialect's LLVMFuncOp has no dedicated field for the `strictfp` - // function attribute, so route it through the `passthrough` array. The MLIR - // LLVM IR translator forwards `passthrough` entries to LLVM IR as function + // Function attributes with no dedicated field on the LLVM dialect's + // LLVMFuncOp are routed through the `passthrough` array. The MLIR LLVM IR + // translator forwards `passthrough` entries to LLVM IR as function // attributes. - if (op->hasAttr(CIRDialect::getStrictFPAttrName())) - fn.setPassthroughAttr(rewriter.getArrayAttr( - {rewriter.getStringAttr(CIRDialect::getStrictFPAttrName())})); + SmallVector<mlir::Attribute> passthrough; + for (llvm::StringRef flagAttr : + {CIRDialect::getStrictFPAttrName(), CIRDialect::getNoRecurseAttrName(), + CIRDialect::getMustProgressAttrName()}) + if (op->hasAttr(flagAttr)) + passthrough.push_back(rewriter.getStringAttr(flagAttr)); + + if (auto moduleId = op->getAttrOfType<mlir::StringAttr>( + CIRDialect::getSYCLModuleIdAttrName())) + passthrough.push_back(rewriter.getArrayAttr( + {rewriter.getStringAttr(CIRDialect::getSYCLModuleIdAttrName()), + moduleId})); + + if (!passthrough.empty()) + fn.setPassthroughAttr(rewriter.getArrayAttr(passthrough)); if (std::optional<cir::InlineKind> inlineKind = op.getInlineKind()) { fn.setNoInline(*inlineKind == cir::InlineKind::NoInline); diff --git a/clang/test/CIR/CodeGenSYCL/kernel-caller-attributes.cpp b/clang/test/CIR/CodeGenSYCL/kernel-caller-attributes.cpp new file mode 100644 index 0000000000000..ba0e563a4acff --- /dev/null +++ b/clang/test/CIR/CodeGenSYCL/kernel-caller-attributes.cpp @@ -0,0 +1,39 @@ +// RUN: %clang_cc1 -std=c++20 -fsycl-is-device -triple spirv64-unknown-unknown -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR +// RUN: %clang_cc1 -std=c++20 -fsycl-is-device -triple spirv64-unknown-unknown -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM +// RUN: %clang_cc1 -std=c++20 -fsycl-is-device -triple spirv64-unknown-unknown -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG + +// The SYCL kernel caller offload entry point receives the SYCL 2020 device +// language attributes: it must not recurse (norecurse) and is guaranteed to +// make forward progress in C++11 and later (mustprogress). It also carries the +// "sycl-module-id" attribute, marking it as an entry point for +// per-translation-unit device-code splitting. This matches classic CodeGen's +// SetSYCLKernelAttributes and addSYCLModuleIdAttr. + +template <typename KernelName, typename... Ts> +void sycl_kernel_launch(const char *, Ts...) {} + +template <typename KernelName, typename KernelType> +[[clang::sycl_kernel_entry_point(KernelName)]] +void kernel_single_task(KernelType kf) { kf(); } + +struct KN; + +void test(int *p) { + kernel_single_task<KN>([p]() { *p = 42; }); +} + +// CIR-LABEL: cir.func +// CIR-SAME: @_ZTS2KN +// CIR-SAME: cc(spir_kernel) +// CIR-SAME: mustprogress +// CIR-SAME: norecurse +// CIR-SAME: "sycl-module-id" = "{{.*}}kernel-caller-attributes.cpp" + +// LLVM: define spir_kernel void @_ZTS2KN({{.*}}) #[[KATTR:[0-9]+]] +// LLVM: attributes #[[KATTR]] = {{[{].*}}mustprogress{{.*}}norecurse{{.*}}"sycl-module-id"="{{.*}}kernel-caller-attributes.cpp" + +// OGCG: define spir_kernel void @_ZTS2KN({{.*}}) #[[KATTR:[0-9]+]] +// OGCG: attributes #[[KATTR]] = {{[{].*}}mustprogress{{.*}}norecurse{{.*}}"sycl-module-id"="{{.*}}kernel-caller-attributes.cpp" >From afb747a12347bc9b59801fa2a4734f62f9aba7d2 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris <[email protected]> Date: Fri, 18 Sep 2026 18:46:12 -0700 Subject: [PATCH 2/2] Address comments --- clang/lib/CIR/CodeGen/CIRGenSYCL.cpp | 6 +++++- clang/test/CIR/CodeGenSYCL/kernel-caller-attributes.cpp | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp b/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp index cfbd580fa3aff..6d2be6844da2e 100644 --- a/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp @@ -87,7 +87,11 @@ static void setSYCLKernelAttributes(CIRGenFunction &cgf, cir::FuncOp fn) { void CIRGenModule::addSYCLModuleIdAttr(cir::FuncOp fn) { assert(getLangOpts().SYCLIsDevice); - StringRef moduleId = theModule.getSymName().value_or(""); + // Classic CodeGen uses the LLVM module identifier, which is the main input + // file name. CIR stores that as the module's symbol name; fall back to the + // main file name so the attribute is never empty, matching classic CodeGen. + StringRef moduleId = + theModule.getSymName().value_or(codeGenOpts.MainFileName); fn->setAttr(cir::CIRDialect::getSYCLModuleIdAttrName(), mlir::StringAttr::get(&getMLIRContext(), moduleId)); } diff --git a/clang/test/CIR/CodeGenSYCL/kernel-caller-attributes.cpp b/clang/test/CIR/CodeGenSYCL/kernel-caller-attributes.cpp index ba0e563a4acff..54b168b2929f7 100644 --- a/clang/test/CIR/CodeGenSYCL/kernel-caller-attributes.cpp +++ b/clang/test/CIR/CodeGenSYCL/kernel-caller-attributes.cpp @@ -4,6 +4,8 @@ // RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM // RUN: %clang_cc1 -std=c++20 -fsycl-is-device -triple spirv64-unknown-unknown -emit-llvm %s -o %t.ll // RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG +// RUN: %clang_cc1 -std=c++20 -fsycl-is-device -triple spirv64-unknown-unknown -fclangir -emit-llvm -fno-finite-loops %s -o %t-nomp.ll +// RUN: FileCheck --input-file=%t-nomp.ll %s -check-prefix=LLVM-NOMP // The SYCL kernel caller offload entry point receives the SYCL 2020 device // language attributes: it must not recurse (norecurse) and is guaranteed to @@ -37,3 +39,9 @@ void test(int *p) { // OGCG: define spir_kernel void @_ZTS2KN({{.*}}) #[[KATTR:[0-9]+]] // OGCG: attributes #[[KATTR]] = {{[{].*}}mustprogress{{.*}}norecurse{{.*}}"sycl-module-id"="{{.*}}kernel-caller-attributes.cpp" + +// With -fno-finite-loops, checkIfFunctionMustProgress() is false, so the kernel +// caller must not carry mustprogress; norecurse and sycl-module-id remain. The +// exact attribute set (which omits mustprogress) is verified here. +// LLVM-NOMP: define spir_kernel void @_ZTS2KN({{.*}}) #[[KATTR:[0-9]+]] +// LLVM-NOMP: attributes #[[KATTR]] = { convergent noinline norecurse "sycl-module-id"="{{.*}}kernel-caller-attributes.cpp" } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
