[clang] [mlir] [CIR] Implement 'noreturn' attribute for functions/calls. (PR #177978)
Erich Keane via cfe-commits
cfe-commits at lists.llvm.org
Mon Jan 26 10:48:42 PST 2026
https://github.com/erichkeane updated https://github.com/llvm/llvm-project/pull/177978
>From e923db15e33e28a80a19270a868e4e73bcf3d7ef Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Tue, 20 Jan 2026 08:31:54 -0800
Subject: [PATCH 1/3] [CIR] Implement 'noreturn' attribute for functions/calls.
This mirrors what LLVM does, and requires propagating into the LLVM
dialect: When the user specifies 'noreturn' we propagate this down
throughout the stack.
Note the similar 'willreturn' is too strong of a guarantee (in that they
are not opposites of each other, as there is a 'unknown' implied by all
others), so we cannot use that on non-noreturn functions.
---
.../clang/CIR/Dialect/IR/CIRDialect.td | 1 +
clang/lib/CIR/CodeGen/CIRGenCall.cpp | 48 +++++++++++++++---
clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h | 26 ++++++++--
clang/lib/CIR/CodeGen/CIRGenTypes.cpp | 11 ++---
clang/lib/CIR/CodeGen/CIRGenTypes.h | 2 +-
.../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 13 ++++-
clang/test/CIR/CodeGen/noreturn.cpp | 49 +++++++++++++++++++
.../test/CIR/CodeGenBuiltins/builtin_call.cpp | 2 +-
mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td | 2 +
mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp | 4 ++
.../LLVMIR/LLVMToLLVMIRTranslation.cpp | 2 +
mlir/lib/Target/LLVMIR/ModuleImport.cpp | 1 +
mlir/lib/Target/LLVMIR/ModuleTranslation.cpp | 2 +
13 files changed, 141 insertions(+), 22 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/noreturn.cpp
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
index 7c38492544b39..058f096bfae3b 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
@@ -39,6 +39,7 @@ def CIR_Dialect : Dialect {
static llvm::StringRef getOptInfoAttrName() { return "cir.opt_info"; }
static llvm::StringRef getCalleeAttrName() { return "callee"; }
static llvm::StringRef getNoThrowAttrName() { return "nothrow"; }
+ static llvm::StringRef getNoReturnAttrName() { return "no_return"; }
static llvm::StringRef getSideEffectAttrName() { return "side_effect"; }
static llvm::StringRef getModuleLevelAsmAttrName() { return "cir.module_asm"; }
static llvm::StringRef getGlobalCtorsAttrName() { return "cir.global_ctors"; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 5fd11c6d97c07..628f39ae2cfba 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -21,7 +21,7 @@ using namespace clang;
using namespace clang::CIRGen;
CIRGenFunctionInfo *
-CIRGenFunctionInfo::create(CanQualType resultType,
+CIRGenFunctionInfo::create(FunctionType::ExtInfo info, CanQualType resultType,
llvm::ArrayRef<CanQualType> argTypes,
RequiredArgs required) {
// The first slot allocated for arg type slot is for the return value.
@@ -32,6 +32,8 @@ CIRGenFunctionInfo::create(CanQualType resultType,
CIRGenFunctionInfo *fi = new (buffer) CIRGenFunctionInfo();
+ fi->noReturn = info.getNoReturn();
+
fi->required = required;
fi->numArgs = argTypes.size();
@@ -120,6 +122,11 @@ void CIRGenModule::constructAttributeList(llvm::StringRef name,
assert(!cir::MissingFeatures::opCallCallConv());
sideEffect = cir::SideEffect::All;
+ if (info.isNoReturn())
+ attrs.set(cir::CIRDialect::getNoReturnAttrName(),
+ mlir::UnitAttr::get(&getMLIRContext()));
+ // TODO(cir): Check/add cmse_nonsecure_call attribute here.
+
addAttributesFromFunctionProtoType(getBuilder(), attrs,
calleeInfo.getCalleeFunctionProtoType());
@@ -129,11 +136,30 @@ void CIRGenModule::constructAttributeList(llvm::StringRef name,
if (targetDecl->hasAttr<NoThrowAttr>())
attrs.set(cir::CIRDialect::getNoThrowAttrName(),
mlir::UnitAttr::get(&getMLIRContext()));
+ // TODO(cir): This is actually only possible if targetDecl isn't a
+ // declarator, which ObjCMethodDecl seems to be the only way to get this to
+ // happen. We're including it here for completeness, but we should add a
+ // test for this when we start generating ObjectiveC.
+ if (targetDecl->hasAttr<NoReturnAttr>())
+ attrs.set(cir::CIRDialect::getNoReturnAttrName(),
+ mlir::UnitAttr::get(&getMLIRContext()));
if (const FunctionDecl *func = dyn_cast<FunctionDecl>(targetDecl)) {
addAttributesFromFunctionProtoType(
getBuilder(), attrs, func->getType()->getAs<FunctionProtoType>());
assert(!cir::MissingFeatures::opCallAttrs());
+
+ const CXXMethodDecl *md = dyn_cast<CXXMethodDecl>(func);
+ bool isVirtualCall = md && md->isVirtual();
+
+ // Don't use [[noreturn]], _Noreturn or [[no_builtin]] for a call to a
+ // virtual function. These attributes are not inherited by overloads.
+ if (!(attrOnCallSite && isVirtualCall)) {
+ if (func->isNoReturn())
+ attrs.set(cir::CIRDialect::getNoReturnAttrName(),
+ mlir::UnitAttr::get(&getMLIRContext()));
+ // TODO(cir): Set NoBuiltinAttr here.
+ }
}
assert(!cir::MissingFeatures::opCallAttrs());
@@ -222,7 +248,8 @@ CIRGenTypes::arrangeCXXStructorDeclaration(GlobalDecl gd) {
assert(!cir::MissingFeatures::opCallCIRGenFuncInfoExtParamInfo());
assert(!cir::MissingFeatures::opCallFnInfoOpts());
- return arrangeCIRFunctionInfo(resultType, argTypes, required);
+ return arrangeCIRFunctionInfo(fpt->getExtInfo(), resultType, argTypes,
+ required);
}
/// Derives the 'this' type for CIRGen purposes, i.e. ignoring method CVR
@@ -259,7 +286,8 @@ arrangeCIRFunctionInfo(CIRGenTypes &cgt, SmallVectorImpl<CanQualType> &prefix,
assert(!cir::MissingFeatures::opCallExtParameterInfo());
appendParameterTypes(cgt, prefix, fpt);
CanQualType resultType = fpt->getReturnType().getUnqualifiedType();
- return cgt.arrangeCIRFunctionInfo(resultType, prefix, required);
+ return cgt.arrangeCIRFunctionInfo(fpt->getExtInfo(), resultType, prefix,
+ required);
}
void CIRGenFunction::emitDelegateCallArg(CallArgList &args,
@@ -325,7 +353,8 @@ arrangeFreeFunctionLikeCall(CIRGenTypes &cgt, CIRGenModule &cgm,
CanQualType retType = fnType->getReturnType()->getCanonicalTypeUnqualified();
assert(!cir::MissingFeatures::opCallFnInfoOpts());
- return cgt.arrangeCIRFunctionInfo(retType, argTypes, required);
+ return cgt.arrangeCIRFunctionInfo(fnType->getExtInfo(), retType, argTypes,
+ required);
}
/// Arrange a call to a C++ method, passing the given arguments.
@@ -364,7 +393,8 @@ const CIRGenFunctionInfo &CIRGenTypes::arrangeCXXConstructorCall(
assert(!cir::MissingFeatures::opCallFnInfoOpts());
assert(!cir::MissingFeatures::opCallCIRGenFuncInfoExtParamInfo());
- return arrangeCIRFunctionInfo(resultType, argTypes, required);
+ return arrangeCIRFunctionInfo(fpt->getExtInfo(), resultType, argTypes,
+ required);
}
/// Arrange a call to a C++ method, passing the given arguments.
@@ -385,6 +415,7 @@ const CIRGenFunctionInfo &CIRGenTypes::arrangeCXXMethodCall(
assert(!cir::MissingFeatures::opCallFnInfoOpts());
return arrangeCIRFunctionInfo(
+ proto->getExtInfo(),
proto->getReturnType()->getCanonicalTypeUnqualified(), argTypes,
required);
}
@@ -456,8 +487,8 @@ CIRGenTypes::arrangeFunctionDeclaration(const FunctionDecl *fd) {
funcTy.getAs<FunctionNoProtoType>()) {
assert(!cir::MissingFeatures::opCallCIRGenFuncInfoExtParamInfo());
assert(!cir::MissingFeatures::opCallFnInfoOpts());
- return arrangeCIRFunctionInfo(noProto->getReturnType(), {},
- RequiredArgs::All);
+ return arrangeCIRFunctionInfo(
+ noProto->getExtInfo(), noProto->getReturnType(), {}, RequiredArgs::All);
}
return arrangeFreeFunctionType(funcTy.castAs<FunctionProtoType>());
@@ -535,7 +566,8 @@ const CIRGenFunctionInfo &
CIRGenTypes::arrangeFreeFunctionType(CanQual<FunctionNoProtoType> fnpt) {
CanQualType resultType = fnpt->getReturnType().getUnqualifiedType();
assert(!cir::MissingFeatures::opCallFnInfoOpts());
- return arrangeCIRFunctionInfo(resultType, {}, RequiredArgs(0));
+ return arrangeCIRFunctionInfo(fnpt->getExtInfo(), resultType, {},
+ RequiredArgs(0));
}
RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h b/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h
index 4f5754cb43986..fb7da9e414139 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h
@@ -72,6 +72,10 @@ class RequiredArgs {
class CIRGenFunctionInfo final
: public llvm::FoldingSetNode,
private llvm::TrailingObjects<CIRGenFunctionInfo, CanQualType> {
+ // Whether this function has noreturn.
+ LLVM_PREFERRED_TYPE(bool)
+ unsigned noReturn : 1;
+
RequiredArgs required;
unsigned numArgs;
@@ -81,8 +85,19 @@ class CIRGenFunctionInfo final
CIRGenFunctionInfo() : required(RequiredArgs::All) {}
+ FunctionType::ExtInfo getExtInfo() const {
+ // TODO(cir): as we add this information to this type, we need to add calls
+ // here instead of explicit false/0.
+ return FunctionType::ExtInfo(
+ isNoReturn(), /*getHasRegParm=*/false, /*getRegParm=*/false,
+ /*getASTCallingConvention=*/CallingConv(0), /*isReturnsRetained=*/false,
+ /*isNoCallerSavedRegs=*/false, /*isNoCfCheck=*/false,
+ /*isCmseNSCall=*/false);
+ }
+
public:
- static CIRGenFunctionInfo *create(CanQualType resultType,
+ static CIRGenFunctionInfo *create(FunctionType::ExtInfo info,
+ CanQualType resultType,
llvm::ArrayRef<CanQualType> argTypes,
RequiredArgs required);
@@ -97,9 +112,10 @@ class CIRGenFunctionInfo final
// This function has to be CamelCase because llvm::FoldingSet requires so.
// NOLINTNEXTLINE(readability-identifier-naming)
- static void Profile(llvm::FoldingSetNodeID &id, RequiredArgs required,
- CanQualType resultType,
+ static void Profile(llvm::FoldingSetNodeID &id, FunctionType::ExtInfo info,
+ RequiredArgs required, CanQualType resultType,
llvm::ArrayRef<CanQualType> argTypes) {
+ id.AddBoolean(info.getNoReturn());
id.AddBoolean(required.getOpaqueData());
resultType.Profile(id);
for (const CanQualType &arg : argTypes)
@@ -111,7 +127,7 @@ class CIRGenFunctionInfo final
// If the Profile functions get out of sync, we can end up with incorrect
// function signatures, so we call the static Profile function here rather
// than duplicating the logic.
- Profile(id, required, getReturnType(), arguments());
+ Profile(id, getExtInfo(), required, getReturnType(), arguments());
}
llvm::ArrayRef<CanQualType> arguments() const {
@@ -144,6 +160,8 @@ class CIRGenFunctionInfo final
return isVariadic() ? getRequiredArgs().getNumRequiredArgs()
: argTypeSize();
}
+
+ bool isNoReturn() const { return noReturn; }
};
} // namespace clang::CIRGen
diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
index 610ee2ceb1da4..1b0aad37569c4 100644
--- a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
@@ -662,15 +662,14 @@ bool CIRGenTypes::isZeroInitializable(const RecordDecl *rd) {
return getCIRGenRecordLayout(rd).isZeroInitializable();
}
-const CIRGenFunctionInfo &
-CIRGenTypes::arrangeCIRFunctionInfo(CanQualType returnType,
- llvm::ArrayRef<CanQualType> argTypes,
- RequiredArgs required) {
+const CIRGenFunctionInfo &CIRGenTypes::arrangeCIRFunctionInfo(
+ FunctionType::ExtInfo info, CanQualType returnType,
+ llvm::ArrayRef<CanQualType> argTypes, RequiredArgs required) {
assert(llvm::all_of(argTypes,
[](CanQualType t) { return t.isCanonicalAsParam(); }));
// Lookup or create unique function info.
llvm::FoldingSetNodeID id;
- CIRGenFunctionInfo::Profile(id, required, returnType, argTypes);
+ CIRGenFunctionInfo::Profile(id, info, required, returnType, argTypes);
void *insertPos = nullptr;
CIRGenFunctionInfo *fi = functionInfos.FindNodeOrInsertPos(id, insertPos);
@@ -687,7 +686,7 @@ CIRGenTypes::arrangeCIRFunctionInfo(CanQualType returnType,
assert(!cir::MissingFeatures::opCallCallConv());
// Construction the function info. We co-allocate the ArgInfos.
- fi = CIRGenFunctionInfo::create(returnType, argTypes, required);
+ fi = CIRGenFunctionInfo::create(info, returnType, argTypes, required);
functionInfos.InsertNode(fi, insertPos);
return *fi;
diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.h b/clang/lib/CIR/CodeGen/CIRGenTypes.h
index e79cdfc9f8224..bbc594c8656da 100644
--- a/clang/lib/CIR/CodeGen/CIRGenTypes.h
+++ b/clang/lib/CIR/CodeGen/CIRGenTypes.h
@@ -197,7 +197,7 @@ class CIRGenTypes {
const FunctionType *fnType);
const CIRGenFunctionInfo &
- arrangeCIRFunctionInfo(CanQualType returnType,
+ arrangeCIRFunctionInfo(FunctionType::ExtInfo info, CanQualType returnType,
llvm::ArrayRef<CanQualType> argTypes,
RequiredArgs required);
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 4877508b1c3da..20bfb6c3153e1 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -310,7 +310,8 @@ mlir::Value lowerCirAttrAsValue(mlir::Operation *parentOp,
void convertSideEffectForCall(mlir::Operation *callOp, bool isNothrow,
cir::SideEffect sideEffect,
mlir::LLVM::MemoryEffectsAttr &memoryEffect,
- bool &noUnwind, bool &willReturn) {
+ bool &noUnwind, bool &willReturn,
+ bool &noReturn) {
using mlir::LLVM::ModRefInfo;
switch (sideEffect) {
@@ -344,6 +345,8 @@ void convertSideEffectForCall(mlir::Operation *callOp, bool isNothrow,
willReturn = true;
break;
}
+
+ noReturn = callOp->hasAttr(CIRDialect::getNoReturnAttrName());
}
static mlir::LLVM::CallIntrinsicOp
@@ -1620,8 +1623,9 @@ rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands,
mlir::LLVM::MemoryEffectsAttr memoryEffects;
bool noUnwind = false;
bool willReturn = false;
+ bool noReturn = false;
convertSideEffectForCall(op, call.getNothrow(), call.getSideEffect(),
- memoryEffects, noUnwind, willReturn);
+ memoryEffects, noUnwind, willReturn, noReturn);
mlir::LLVM::LLVMFunctionType llvmFnTy;
@@ -1684,6 +1688,7 @@ rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands,
newOp.setMemoryEffectsAttr(memoryEffects);
newOp.setNoUnwind(noUnwind);
newOp.setWillReturn(willReturn);
+ newOp.setNoReturn(noReturn);
return mlir::success();
}
@@ -2036,6 +2041,7 @@ void CIRToLLVMFuncOpLowering::lowerFuncAttributes(
attr.getName() == func.getDsoLocalAttrName() ||
attr.getName() == func.getInlineKindAttrName() ||
attr.getName() == func.getSideEffectAttrName() ||
+ attr.getName() == CIRDialect::getNoReturnAttrName() ||
(filterArgAndResAttrs &&
(attr.getName() == func.getArgAttrsAttrName() ||
attr.getName() == func.getResAttrsAttrName())))
@@ -2151,6 +2157,9 @@ mlir::LogicalResult CIRToLLVMFuncOpLowering::matchAndRewrite(
}
}
+ if (op->hasAttr(CIRDialect::getNoReturnAttrName()))
+ fn.setNoReturn(true);
+
if (std::optional<cir::InlineKind> inlineKind = op.getInlineKind()) {
fn.setNoInline(*inlineKind == cir::InlineKind::NoInline);
fn.setInlineHint(*inlineKind == cir::InlineKind::InlineHint);
diff --git a/clang/test/CIR/CodeGen/noreturn.cpp b/clang/test/CIR/CodeGen/noreturn.cpp
new file mode 100644
index 0000000000000..ddb9e25802ddb
--- /dev/null
+++ b/clang/test/CIR/CodeGen/noreturn.cpp
@@ -0,0 +1,49 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
+
+extern "C" {
+// CIR: cir.func {{.*}} @bar() -> !s32i attributes {no_return} {
+// LLVM: Function Attrs:{{.*}} noreturn
+// LLVM-NEXT: define {{.*}} i32 @bar() #[[BAR_FOO_ATTR:.*]] {
+// OGCG: Function Attrs:{{.*}} noreturn
+// OGCG-NEXT: define {{.*}} i32 @bar() #[[BAR_FOO_ATTR:.*]] {
+__attribute((noreturn))
+int bar() { }
+
+// Note: Classic codegen puts this here, so we need this to make sure the
+// FunctionAttrs from `trap` doesn't interfere with 'foo'. However, CIR->LLVM
+// lowering puts the trap decl at the end, so it isn't here to worry about.
+// OGCG: declare void @llvm.trap
+
+// CIR: cir.func {{.*}} @foo() -> !s32i attributes {no_return} {
+// LLVM: Function Attrs:{{.*}} noreturn
+// LLVM-NEXT: define {{.*}} i32 @foo() #[[BAR_FOO_ATTR:.*]] {
+// OGCG: Function Attrs:{{.*}} noreturn
+// OGCG-NEXT: define {{.*}} i32 @foo() #[[BAR_FOO_ATTR:.*]] {
+[[noreturn]]
+int foo() { }
+
+void caller() {
+ // CIR: cir.call @bar() {no_return} : () -> !s32i
+ // LLVM: call i32 @bar() #[[CALL_ATTR:.*]]
+ // OGCG: call i32 @bar() #[[CALL_ATTR:.*]]
+ bar();
+}
+
+void caller2() {
+ // CIR: cir.call @foo() {no_return} : () -> !s32i
+ // LLVM: call i32 @foo() #[[CALL_ATTR:.*]]
+ // OGCG: call i32 @foo() #[[CALL_ATTR:.*]]
+ foo();
+}
+
+// LLVM: attributes #[[BAR_FOO_ATTR]] = {{.*}}noreturn
+// OGCG: attributes #[[BAR_FOO_ATTR]] = {{.*}}noreturn
+// LLVM: attributes #[[CALL_ATTR]] = {{.*}}noreturn
+// OGCG: attributes #[[CALL_ATTR]] = {{.*}}noreturn
+
+}
diff --git a/clang/test/CIR/CodeGenBuiltins/builtin_call.cpp b/clang/test/CIR/CodeGenBuiltins/builtin_call.cpp
index a08a784951247..fc9e89b0ac486 100644
--- a/clang/test/CIR/CodeGenBuiltins/builtin_call.cpp
+++ b/clang/test/CIR/CodeGenBuiltins/builtin_call.cpp
@@ -85,7 +85,7 @@ void library_builtins() {
// CIR: cir.func{{.*}} @_Z16library_builtinsv()
// CIR: %[[NULL:.+]] = cir.const #cir.ptr<null> : !cir.ptr<!s8i>
// CIR: cir.call @printf(%[[NULL]]) nothrow : (!cir.ptr<!s8i>) -> !s32i
-// CIR: cir.call @abort() nothrow : () -> ()
+// CIR: cir.call @abort() nothrow {no_return} : () -> ()
// LLVM: define{{.*}} void @_Z16library_builtinsv()
// LLVM: call i32 (ptr, ...) @printf(ptr null)
diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
index 6789ca22c3d5f..b8b31680df824 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
@@ -795,6 +795,7 @@ def LLVM_CallOp
DefaultValuedAttr<TailCallKind, "TailCallKind::None">:$TailCallKind,
OptionalAttr<LLVM_MemoryEffectsAttr>:$memory_effects,
UnitAttr:$convergent, UnitAttr:$no_unwind, UnitAttr:$will_return,
+ UnitAttr:$no_return,
VariadicOfVariadic<LLVM_Type, "op_bundle_sizes">:$op_bundle_operands,
DenseI32ArrayAttr:$op_bundle_sizes,
OptionalAttr<ArrayAttr>:$op_bundle_tags,
@@ -1992,6 +1993,7 @@ def LLVM_LLVMFuncOp : LLVM_Op<"func", [
OptionalAttr<UnitAttr>:$inline_hint,
OptionalAttr<UnitAttr>:$no_unwind,
OptionalAttr<UnitAttr>:$will_return,
+ OptionalAttr<UnitAttr>:$no_return,
OptionalAttr<UnitAttr>:$optimize_none,
OptionalAttr<LLVM_VecTypeHintAttr>:$vec_type_hint,
OptionalAttr<DenseI32ArrayAttr>:$work_group_size_hint,
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index 91fbc53c5eb32..e0b751fc55664 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -995,6 +995,7 @@ void CallOp::build(OpBuilder &builder, OperationState &state, TypeRange results,
/*CConv=*/nullptr, /*TailCallKind=*/nullptr,
/*memory_effects=*/nullptr,
/*convergent=*/nullptr, /*no_unwind=*/nullptr, /*will_return=*/nullptr,
+ /*no_return=*/nullptr,
/*op_bundle_operands=*/{}, /*op_bundle_tags=*/{},
/*arg_attrs=*/nullptr, /*res_attrs=*/nullptr,
/*access_groups=*/nullptr, /*alias_scopes=*/nullptr,
@@ -1025,6 +1026,7 @@ void CallOp::build(OpBuilder &builder, OperationState &state,
/*TailCallKind=*/nullptr, /*memory_effects=*/nullptr,
/*convergent=*/nullptr,
/*no_unwind=*/nullptr, /*will_return=*/nullptr,
+ /*no_return=*/nullptr,
/*op_bundle_operands=*/{}, /*op_bundle_tags=*/{},
/*arg_attrs=*/nullptr, /*res_attrs=*/nullptr,
/*access_groups=*/nullptr,
@@ -1041,6 +1043,7 @@ void CallOp::build(OpBuilder &builder, OperationState &state,
/*fastmathFlags=*/nullptr,
/*CConv=*/nullptr, /*TailCallKind=*/nullptr, /*memory_effects=*/nullptr,
/*convergent=*/nullptr, /*no_unwind=*/nullptr, /*will_return=*/nullptr,
+ /*no_return=*/nullptr,
/*op_bundle_operands=*/{}, /*op_bundle_tags=*/{},
/*arg_attrs=*/nullptr, /*res_attrs=*/nullptr,
/*access_groups=*/nullptr, /*alias_scopes=*/nullptr,
@@ -1057,6 +1060,7 @@ void CallOp::build(OpBuilder &builder, OperationState &state, LLVMFuncOp func,
/*fastmathFlags=*/nullptr,
/*CConv=*/nullptr, /*TailCallKind=*/nullptr, /*memory_effects=*/nullptr,
/*convergent=*/nullptr, /*no_unwind=*/nullptr, /*will_return=*/nullptr,
+ /*no_return=*/nullptr,
/*op_bundle_operands=*/{}, /*op_bundle_tags=*/{},
/*access_groups=*/nullptr, /*alias_scopes=*/nullptr,
/*arg_attrs=*/nullptr, /*res_attrs=*/nullptr,
diff --git a/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp
index b6ea4ba6e4921..1f53cfe8ece27 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp
@@ -421,6 +421,8 @@ convertOperationImpl(Operation &opInst, llvm::IRBuilderBase &builder,
call->addFnAttr(llvm::Attribute::NoUnwind);
if (callOp.getWillReturnAttr())
call->addFnAttr(llvm::Attribute::WillReturn);
+ if (callOp.getNoReturnAttr())
+ call->addFnAttr(llvm::Attribute::NoReturn);
if (callOp.getNoInlineAttr())
call->addFnAttr(llvm::Attribute::NoInline);
if (callOp.getAlwaysInlineAttr())
diff --git a/mlir/lib/Target/LLVMIR/ModuleImport.cpp b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
index cec968c02078d..dca3a36133748 100644
--- a/mlir/lib/Target/LLVMIR/ModuleImport.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
@@ -2673,6 +2673,7 @@ static constexpr std::array kExplicitLLVMFuncOpAttributes{
StringLiteral("uwtable"),
StringLiteral("vscale_range"),
StringLiteral("willreturn"),
+ StringLiteral("noreturn"),
};
/// Converts LLVM attributes from `func` into MLIR attributes and adds them
diff --git a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
index fad9bd6b78018..50beb1a2ec410 100644
--- a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
@@ -1676,6 +1676,8 @@ static void convertFunctionAttributes(LLVMFuncOp func,
llvmFunc->addFnAttr(llvm::Attribute::NoUnwind);
if (func.getWillReturnAttr())
llvmFunc->addFnAttr(llvm::Attribute::WillReturn);
+ if (func.getNoReturnAttr())
+ llvmFunc->addFnAttr(llvm::Attribute::NoReturn);
if (TargetFeaturesAttr targetFeatAttr = func.getTargetFeaturesAttr())
llvmFunc->addFnAttr("target-features", targetFeatAttr.getFeaturesString());
if (FramePointerKindAttr fpAttr = func.getFramePointerAttr())
>From 792d348d741d4b89b497d1ab735599d4c7362a01 Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Mon, 26 Jan 2026 08:19:36 -0800
Subject: [PATCH 2/3] Add requested MLIR tests, fixup all the missing import
functionality
---
mlir/lib/Target/LLVMIR/ModuleImport.cpp | 5 +++-
mlir/test/Dialect/LLVMIR/func.mlir | 5 ++++
mlir/test/Dialect/LLVMIR/roundtrip.mlir | 3 +++
.../LLVMIR/Import/function-attributes.ll | 6 +++++
.../test/Target/LLVMIR/Import/instructions.ll | 12 +++++++++
mlir/test/Target/LLVMIR/llvmir.mlir | 25 +++++++++++++++++++
6 files changed, 55 insertions(+), 1 deletion(-)
diff --git a/mlir/lib/Target/LLVMIR/ModuleImport.cpp b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
index dca3a36133748..78338f597b420 100644
--- a/mlir/lib/Target/LLVMIR/ModuleImport.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
@@ -2666,6 +2666,7 @@ static constexpr std::array kExplicitLLVMFuncOpAttributes{
StringLiteral("no-nans-fp-math"),
StringLiteral("no-signed-zeros-fp-math"),
StringLiteral("noinline"),
+ StringLiteral("noreturn"),
StringLiteral("nounwind"),
StringLiteral("optnone"),
StringLiteral("target-features"),
@@ -2673,7 +2674,6 @@ static constexpr std::array kExplicitLLVMFuncOpAttributes{
StringLiteral("uwtable"),
StringLiteral("vscale_range"),
StringLiteral("willreturn"),
- StringLiteral("noreturn"),
};
/// Converts LLVM attributes from `func` into MLIR attributes and adds them
@@ -2708,6 +2708,8 @@ void ModuleImport::processFunctionAttributes(llvm::Function *func,
funcOp.setNoUnwind(true);
if (func->hasFnAttribute(llvm::Attribute::WillReturn))
funcOp.setWillReturn(true);
+ if (func->hasFnAttribute(llvm::Attribute::NoReturn))
+ funcOp.setNoReturn(true);
if (func->hasFnAttribute("aarch64_pstate_sm_enabled"))
funcOp.setArmStreaming(true);
@@ -2928,6 +2930,7 @@ LogicalResult ModuleImport::convertCallAttributes(llvm::CallInst *inst,
op.setConvergent(callAttrs.getFnAttr(llvm::Attribute::Convergent).isValid());
op.setNoUnwind(callAttrs.getFnAttr(llvm::Attribute::NoUnwind).isValid());
op.setWillReturn(callAttrs.getFnAttr(llvm::Attribute::WillReturn).isValid());
+ op.setNoReturn(callAttrs.getFnAttr(llvm::Attribute::NoReturn).isValid());
op.setNoInline(callAttrs.getFnAttr(llvm::Attribute::NoInline).isValid());
op.setAlwaysInline(
callAttrs.getFnAttr(llvm::Attribute::AlwaysInline).isValid());
diff --git a/mlir/test/Dialect/LLVMIR/func.mlir b/mlir/test/Dialect/LLVMIR/func.mlir
index 094313ca048c7..09f481a9f908c 100644
--- a/mlir/test/Dialect/LLVMIR/func.mlir
+++ b/mlir/test/Dialect/LLVMIR/func.mlir
@@ -324,6 +324,11 @@ module {
llvm.return
}
+ llvm.func @noreturn_function() attributes {no_return} {
+ // CHECK: @noreturn_function
+ // CHECK-SAME: attributes {no_return}
+ llvm.return
+ }
}
diff --git a/mlir/test/Dialect/LLVMIR/roundtrip.mlir b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
index afbf47ef3041d..42e64835d6b7b 100644
--- a/mlir/test/Dialect/LLVMIR/roundtrip.mlir
+++ b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
@@ -122,6 +122,9 @@ func.func @ops(%arg0: i32, %arg1: f32,
// CHECK: llvm.call @baz() {will_return} : () -> ()
llvm.call @baz() {will_return} : () -> ()
+// CHECK: llvm.call @baz() {noreturn} : () -> ()
+ llvm.call @baz() {no_return} : () -> ()
+
// CHECK: llvm.call @baz() {memory = #llvm.memory_effects<other = none, argMem = read, inaccessibleMem = write, errnoMem = none, targetMem0 = none, targetMem1 = none>} : () -> ()
llvm.call @baz() {memory = #llvm.memory_effects<other = none, argMem = read, inaccessibleMem = write, errnoMem = none, targetMem0 = none, targetMem1 = none>} : () -> ()
diff --git a/mlir/test/Target/LLVMIR/Import/function-attributes.ll b/mlir/test/Target/LLVMIR/Import/function-attributes.ll
index 023b0120f9bbe..61b5caabdc0e4 100644
--- a/mlir/test/Target/LLVMIR/Import/function-attributes.ll
+++ b/mlir/test/Target/LLVMIR/Import/function-attributes.ll
@@ -411,5 +411,11 @@ declare void @willreturn_attribute() willreturn
// -----
+; CHECK-LABEL: @noreturn_attribute
+; CHECK-SAME: attributes {no_return}
+declare void @noreturn_attribute() noreturn
+
+// -----
+
; expected-warning @unknown {{'preallocated' attribute is invalid on current operation, skipping it}}
declare void @test() preallocated(i32)
diff --git a/mlir/test/Target/LLVMIR/Import/instructions.ll b/mlir/test/Target/LLVMIR/Import/instructions.ll
index 7f9c511ec75b0..c7efd69b8ff13 100644
--- a/mlir/test/Target/LLVMIR/Import/instructions.ll
+++ b/mlir/test/Target/LLVMIR/Import/instructions.ll
@@ -701,6 +701,18 @@ define void @call_will_return() {
; CHECK: llvm.func @f()
declare void @f()
+; CHECK-LABEL: @call_noreturn
+define void @call_noreturn() {
+; CHECK: llvm.call @f() {no_return}
+ call void @f() noreturn
+ ret void
+}
+
+; // -----
+
+; CHECK: llvm.func @f()
+declare void @f()
+
; CHECK-LABEL: @call_memory_effects
define void @call_memory_effects() {
; CHECK: llvm.call @f() {memory_effects = #llvm.memory_effects<other = none, argMem = none, inaccessibleMem = none, errnoMem = none, targetMem0 = none, targetMem1 = none>}
diff --git a/mlir/test/Target/LLVMIR/llvmir.mlir b/mlir/test/Target/LLVMIR/llvmir.mlir
index 819a514bc8b7e..1f1110af51c83 100644
--- a/mlir/test/Target/LLVMIR/llvmir.mlir
+++ b/mlir/test/Target/LLVMIR/llvmir.mlir
@@ -2631,6 +2631,17 @@ llvm.func @willreturn() attributes { will_return } {
// -----
+// CHECK-LABEL: @noreturn
+// CHECK-SAME: #[[ATTRS:[0-9]+]]
+llvm.func @noreturn() attributes { no_return } {
+ llvm.return
+}
+
+// CHECK: #[[ATTRS]]
+// CHECK-SAME: noreturn
+
+// -----
+
llvm.func @f()
// CHECK-LABEL: @convergent_call
@@ -2675,6 +2686,20 @@ llvm.func @willreturn_call() {
llvm.func @f()
+// CHECK-LABEL: @noreturn_call
+// CHECK: call void @f() #[[ATTRS:[0-9]+]]
+llvm.func @noreturn_call() {
+ llvm.call @f() {no_return} : () -> ()
+ llvm.return
+}
+
+// CHECK: #[[ATTRS]]
+// CHECK-SAME: noreturn
+
+// -----
+
+llvm.func @f()
+
// CHECK-LABEL: @no_inline_call
// CHECK: call void @f() #[[ATTRS:[0-9]+]]
llvm.func @no_inline_call() {
>From 0f6d45e10e9c13fdcc491864b5d1f1e23dc0f05c Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Mon, 26 Jan 2026 09:53:56 -0800
Subject: [PATCH 3/3] Remove duplicate OGCG lines, we can split them back out
in the future if these diverge in any reasonable way
---
clang/test/CIR/CodeGen/noreturn.cpp | 14 +++-----------
1 file changed, 3 insertions(+), 11 deletions(-)
diff --git a/clang/test/CIR/CodeGen/noreturn.cpp b/clang/test/CIR/CodeGen/noreturn.cpp
index ddb9e25802ddb..ef85622a83f6c 100644
--- a/clang/test/CIR/CodeGen/noreturn.cpp
+++ b/clang/test/CIR/CodeGen/noreturn.cpp
@@ -3,14 +3,12 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll
// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
-// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
+// RUN: FileCheck --input-file=%t.ll %s -check-prefixes=LLVM,OGCG
extern "C" {
// CIR: cir.func {{.*}} @bar() -> !s32i attributes {no_return} {
// LLVM: Function Attrs:{{.*}} noreturn
// LLVM-NEXT: define {{.*}} i32 @bar() #[[BAR_FOO_ATTR:.*]] {
-// OGCG: Function Attrs:{{.*}} noreturn
-// OGCG-NEXT: define {{.*}} i32 @bar() #[[BAR_FOO_ATTR:.*]] {
__attribute((noreturn))
int bar() { }
@@ -21,29 +19,23 @@ int bar() { }
// CIR: cir.func {{.*}} @foo() -> !s32i attributes {no_return} {
// LLVM: Function Attrs:{{.*}} noreturn
-// LLVM-NEXT: define {{.*}} i32 @foo() #[[BAR_FOO_ATTR:.*]] {
-// OGCG: Function Attrs:{{.*}} noreturn
-// OGCG-NEXT: define {{.*}} i32 @foo() #[[BAR_FOO_ATTR:.*]] {
+// LLVM-NEXT: define {{.*}} i32 @foo() #[[BAR_FOO_ATTR]] {
[[noreturn]]
int foo() { }
void caller() {
// CIR: cir.call @bar() {no_return} : () -> !s32i
// LLVM: call i32 @bar() #[[CALL_ATTR:.*]]
- // OGCG: call i32 @bar() #[[CALL_ATTR:.*]]
bar();
}
void caller2() {
// CIR: cir.call @foo() {no_return} : () -> !s32i
- // LLVM: call i32 @foo() #[[CALL_ATTR:.*]]
- // OGCG: call i32 @foo() #[[CALL_ATTR:.*]]
+ // LLVM: call i32 @foo() #[[CALL_ATTR]]
foo();
}
// LLVM: attributes #[[BAR_FOO_ATTR]] = {{.*}}noreturn
-// OGCG: attributes #[[BAR_FOO_ATTR]] = {{.*}}noreturn
// LLVM: attributes #[[CALL_ATTR]] = {{.*}}noreturn
-// OGCG: attributes #[[CALL_ATTR]] = {{.*}}noreturn
}
More information about the cfe-commits
mailing list